@@ -227,6 +279,7 @@ export const ContractDetailPage = memo(() => {
+
>
);
diff --git a/src/app/containers/Header/index.tsx b/src/app/containers/Header/index.tsx
index 4af972bfd..c4430ce30 100644
--- a/src/app/containers/Header/index.tsx
+++ b/src/app/containers/Header/index.tsx
@@ -519,6 +519,14 @@ export const Header = memo(() => {
afterClick: menuClick,
href: '/balance-checker',
},
+ {
+ // profile
+ title: t(translations.header.profile),
+ name: ScanEvent.menu.action.home,
+ afterClick: menuClick,
+ href: '/profile',
+ className: 'profile',
+ },
],
},
{
@@ -535,6 +543,14 @@ export const Header = memo(() => {
];
const endLinks: HeaderLinks = [
+ // {
+ // // profile
+ // title: t(translations.header.profile),
+ // name: ScanEvent.menu.action.home,
+ // afterClick: menuClick,
+ // href: '/profile',
+ // className: 'profile',
+ // },
{
// switch network
name: 'switch-network',
diff --git a/src/app/containers/Profile/AddressLabel.tsx b/src/app/containers/Profile/AddressLabel.tsx
new file mode 100644
index 000000000..a0e4ee749
--- /dev/null
+++ b/src/app/containers/Profile/AddressLabel.tsx
@@ -0,0 +1,295 @@
+import React, { useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { translations } from 'locales/i18n';
+import { Space, Modal, Input } from '@cfxjs/antd';
+import { Button } from 'app/components/Button/Loadable';
+import { formatTimeStamp } from 'utils';
+import { LOCALSTORAGE_KEYS_MAP } from 'utils/constants';
+import { ContentWrapper } from 'utils/tableColumns/utils';
+import { TablePanel as TablePanelNew } from 'app/components/TablePanelNew';
+import { useGlobalData } from 'utils/hooks/useGlobal';
+import { Link } from 'app/components/Link/Loadable';
+import { useHistory, useLocation } from 'react-router-dom';
+import styled from 'styled-components/macro';
+import qs from 'query-string';
+import { CreateAddressLabel } from './CreateAddressLabel';
+
+const { confirm, warning } = Modal;
+const { Search } = Input;
+
+type Type = {
+ a: string;
+ l: string;
+ t: number;
+ u: number;
+};
+
+export function AddressLabel() {
+ const history = useHistory();
+ const { search: s } = useLocation();
+ const { t } = useTranslation();
+ const [visible, setVisible] = useState(false);
+ const [stage, setStage] = useState('create');
+ const [list, setList] = useState
([]);
+ const [loading, setLoading] = useState(false);
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+ const [globalData, setGlobalData] = useGlobalData();
+ const [search, setSearch] = useState(() => {
+ return qs.parse(s).search || '';
+ });
+ const [data, setData] = useState({
+ address: '',
+ label: '',
+ });
+
+ useEffect(() => {
+ try {
+ setLoading(true);
+ const l = localStorage.getItem(LOCALSTORAGE_KEYS_MAP.addressLabel);
+ if (l) {
+ setList(JSON.parse(l));
+ }
+ setLoading(false);
+ } catch (e) {}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const columns = [
+ {
+ title: t(translations.profile.address.address),
+ dataIndex: 'a',
+ key: 'a',
+ width: 8,
+ render(v) {
+ return (
+
+ {v}
+
+ );
+ },
+ },
+ {
+ title: t(translations.profile.address.label),
+ dataIndex: 'l',
+ key: 'l',
+ width: 4,
+ },
+ {
+ title: (
+
+ {t(translations.general.timestamp)}
+
+ ),
+ dataIndex: 'u',
+ key: 'u',
+ width: 4,
+ render(v, r) {
+ return (
+
+ {formatTimeStamp(v * 1000, 'standard')}
+
+ );
+ },
+ },
+ ];
+
+ const handleClickC = (e, address = '') => {
+ if (list.length > 1000) {
+ warning({
+ title: t(translations.general.warning),
+ content: t(translations.general.exceedTip),
+ icon: null,
+ okText: t(translations.general.buttonOk),
+ cancelText: t(translations.general.buttonCancel),
+ });
+
+ return;
+ }
+
+ setData({
+ address: address,
+ label: '',
+ });
+ setStage('create');
+ setVisible(true);
+ };
+
+ const handleClickE = () => {
+ const selectedItem = list.filter(l => l.a === selectedRowKeys[0])[0];
+ setData({
+ address: selectedItem?.a,
+ label: selectedItem?.l,
+ });
+ setStage('edit');
+ setVisible(true);
+ };
+
+ const handleClickD = () => {
+ confirm({
+ title: t(translations.general.warning),
+ content: t(translations.general.deleteTip),
+ icon: null,
+ okText: t(translations.general.buttonOk),
+ cancelText: t(translations.general.buttonCancel),
+ onOk() {
+ const newList = list.filter(l => !selectedRowKeys.includes(l.a));
+
+ setLoading(true);
+
+ localStorage.setItem(
+ LOCALSTORAGE_KEYS_MAP.addressLabel,
+ JSON.stringify(newList),
+ );
+
+ setGlobalData({
+ ...globalData,
+ [LOCALSTORAGE_KEYS_MAP.addressLabel]: newList.reduce((prev, curr) => {
+ return {
+ ...prev,
+ [curr.a]: curr.l,
+ };
+ }, {}),
+ });
+
+ handleSearch('');
+
+ setList(newList);
+ setLoading(false);
+ },
+ onCancel() {
+ console.log('Cancel');
+ },
+ });
+ };
+
+ const handleOk = () => {
+ setVisible(true);
+ };
+
+ const handleCancel = () => {
+ setVisible(false);
+ };
+
+ const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
+ setSelectedRowKeys(newSelectedRowKeys);
+ };
+
+ const handleSearch = value => {
+ const search = value ? `&&search=${value}` : '';
+ history.push(`/profile?tab=address-label${search}`);
+ };
+
+ const handleSearchChange = e => {
+ setSearch(e.target.value.trim());
+ };
+
+ const rowSelection = {
+ selectedRowKeys,
+ onChange: onSelectChange,
+ columnWidth: 1,
+ };
+
+ const text = {
+ create: t(translations.general.create),
+ edit: t(translations.general.edit),
+ delete: t(translations.general.delete),
+ };
+
+ const queryKey = (qs.parse(s).search as string) || '';
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ l.a.includes(queryKey) || l.l.includes(queryKey),
+ )}
+ columns={columns}
+ loading={loading}
+ rowKey="a"
+ key={Math.random()}
+ />
+
+ >
+ );
+}
+
+const SearchWrapper = styled.div`
+ width: 500px;
+
+ .ant-input {
+ border-radius: 16px !important;
+ background: rgba(30, 61, 228, 0.04);
+ border: none !important;
+ padding-right: 41px;
+ }
+
+ .ant-input-group {
+ display: flex;
+ }
+
+ .ant-input-group-addon {
+ background: transparent !important;
+ left: -38px !important;
+ z-index: 80;
+
+ .ant-btn {
+ background: transparent !important;
+ border: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ line-height: 1 !important;
+ box-shadow: none !important;
+
+ &:after {
+ display: none !important;
+ }
+
+ .anticon {
+ font-size: 18px;
+ margin-bottom: 3px;
+ }
+ }
+ }
+`;
diff --git a/src/app/containers/Profile/CreateAddressLabel.tsx b/src/app/containers/Profile/CreateAddressLabel.tsx
new file mode 100644
index 000000000..9d82ce49c
--- /dev/null
+++ b/src/app/containers/Profile/CreateAddressLabel.tsx
@@ -0,0 +1,245 @@
+import React, { useState, useCallback, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { translations } from 'locales/i18n';
+import { Form, Modal, Input, message } from '@cfxjs/antd';
+import {
+ isBase32Address,
+ isCurrentNetworkAddress,
+ publishRequestError,
+} from 'utils';
+import { NETWORK_TYPE } from 'utils/constants';
+import { LOCALSTORAGE_KEYS_MAP } from 'utils/constants';
+import { useGlobalData } from 'utils/hooks/useGlobal';
+
+type Type = {
+ a: string;
+ l: string;
+ t: number;
+ u: number;
+};
+
+type Props = {
+ visible: boolean;
+ stage: string;
+ data: {
+ address: string;
+ label?: string;
+ note?: string;
+ };
+ list?: null | Array;
+ labelLengthLimit?: number;
+ onOk: () => void;
+ onCancel: () => void;
+};
+
+export function CreateAddressLabel({
+ visible = false,
+ stage = 'create',
+ data = {
+ address: '',
+ label: '',
+ },
+ list: outerList,
+ labelLengthLimit = 20,
+ onOk = () => {},
+ onCancel = () => {},
+}: Props) {
+ const { t } = useTranslation();
+ const [form] = Form.useForm();
+ const [list, setList] = useState(outerList || []);
+ const [loading, setLoading] = useState(false);
+ const [globalData, setGlobalData] = useGlobalData();
+
+ useEffect(() => {
+ try {
+ if (!outerList) {
+ setLoading(true);
+ const l = localStorage.getItem(LOCALSTORAGE_KEYS_MAP.addressLabel);
+ if (l) {
+ setList(JSON.parse(l));
+ }
+ setLoading(false);
+ } else {
+ setList(outerList);
+ }
+ } catch (e) {}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [outerList]);
+
+ const handleOk = () => {
+ form
+ .validateFields()
+ .then(async function ({ address, label }) {
+ try {
+ let newList: Array = list;
+ const timestamp = Math.floor(+new Date() / 1000);
+
+ if (stage === 'create') {
+ if (list.some(l => l.a === address)) {
+ message.error(t(translations.profile.address.error.duplicated));
+ return;
+ }
+
+ const item: Type = {
+ a: address as string, // address
+ l: label as string, // label
+ t: timestamp, // create timestamp
+ u: timestamp, // update timestamp
+ };
+
+ newList = [item].concat(list);
+ } else if (stage === 'edit') {
+ const i = list.findIndex(l => l.a === address);
+ const old = list[i];
+
+ newList.splice(i, 1);
+ newList = [
+ {
+ ...old,
+ u: timestamp,
+ l: label as string,
+ },
+ ].concat(newList);
+ }
+
+ setLoading(true);
+
+ localStorage.setItem(
+ LOCALSTORAGE_KEYS_MAP.addressLabel,
+ JSON.stringify(newList),
+ );
+
+ setGlobalData({
+ ...globalData,
+ [LOCALSTORAGE_KEYS_MAP.addressLabel]: newList.reduce(
+ (prev, curr) => {
+ return {
+ ...prev,
+ [curr.a]: curr.l,
+ };
+ },
+ {},
+ ),
+ });
+
+ setLoading(false);
+ onOk();
+ } catch (e) {
+ publishRequestError(e, 'code');
+ }
+ })
+ .catch(e => publishRequestError(e, 'code'));
+ };
+
+ const handleCancel = () => {
+ form.resetFields([]);
+ setLoading(false);
+ onCancel();
+ };
+
+ const validator = useCallback(() => {
+ return {
+ validator(_, value) {
+ if (isBase32Address(value)) {
+ if (isCurrentNetworkAddress(value)) {
+ return Promise.resolve();
+ } else {
+ return Promise.reject(
+ new Error(
+ t(translations.nftDetail.error.invalidNetwork, {
+ network: t(
+ translations.general.networks[
+ String(NETWORK_TYPE).toLowerCase()
+ ],
+ ),
+ }),
+ ),
+ );
+ }
+ }
+ return Promise.reject(
+ new Error(t(translations.nftDetail.error.invalidAddress)),
+ );
+ },
+ };
+ }, [t]);
+
+ const tagValidator = useCallback(() => {
+ return {
+ validator(_, value) {
+ if (value.length > labelLengthLimit) {
+ return Promise.reject(
+ new Error(
+ t(translations.profile.address.error.invalidLabelRange, {
+ amount: 20,
+ }),
+ ),
+ );
+ } else {
+ return Promise.resolve();
+ }
+ },
+ };
+ }, [labelLengthLimit, t]);
+
+ const text = {
+ create: t(translations.general.create),
+ edit: t(translations.general.edit),
+ delete: t(translations.general.delete),
+ };
+
+ const d = {
+ ...data,
+ label: list.filter(l => l.a === data.address)[0]?.l,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/containers/Profile/CreateTxNote.tsx b/src/app/containers/Profile/CreateTxNote.tsx
new file mode 100644
index 000000000..6c34be932
--- /dev/null
+++ b/src/app/containers/Profile/CreateTxNote.tsx
@@ -0,0 +1,228 @@
+import React, { useState, useCallback, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { translations } from 'locales/i18n';
+import { Form, Modal, Input, message } from '@cfxjs/antd';
+import { isHash, publishRequestError } from 'utils';
+import { LOCALSTORAGE_KEYS_MAP } from 'utils/constants';
+import { useGlobalData } from 'utils/hooks/useGlobal';
+
+type Type = {
+ h: string;
+ n: string;
+ t: number;
+ u: number;
+};
+
+type Props = {
+ visible: boolean;
+ stage: string;
+ data: {
+ hash: string;
+ note?: string;
+ };
+ list?: null | Array;
+ noteLengthLimit?: number;
+ onOk: () => void;
+ onCancel: () => void;
+};
+
+export function CreateTxNote({
+ visible = false,
+ stage = 'create',
+ data = {
+ hash: '',
+ note: '',
+ },
+ list: outerList,
+ noteLengthLimit = 20,
+ onOk = () => {},
+ onCancel = () => {},
+}: Props) {
+ const { t } = useTranslation();
+ const [form] = Form.useForm();
+ const [list, setList] = useState(outerList || []);
+ const [loading, setLoading] = useState(false);
+ const [globalData, setGlobalData] = useGlobalData();
+
+ useEffect(() => {
+ try {
+ if (!outerList) {
+ setLoading(true);
+ const l = localStorage.getItem(LOCALSTORAGE_KEYS_MAP.txPrivateNote);
+ if (l) {
+ setList(JSON.parse(l));
+ }
+ setLoading(false);
+ } else {
+ setList(outerList);
+ }
+ } catch (e) {}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [outerList]);
+
+ const handleOk = () => {
+ form
+ .validateFields()
+ .then(async function ({ hash, note }) {
+ try {
+ let newList: Array = list;
+ const timestamp = Math.floor(+new Date() / 1000);
+
+ if (stage === 'create') {
+ if (list.some(l => l.h === hash)) {
+ message.error(t(translations.profile.tx.error.duplicated));
+ return;
+ }
+
+ const item: Type = {
+ h: hash as string, // hash
+ n: note as string, // note
+ t: timestamp, // create timestamp
+ u: timestamp, // update timestamp
+ };
+
+ newList = [item].concat(list);
+ } else if (stage === 'edit') {
+ const i = list.findIndex(l => l.h === hash);
+ const old = list[i];
+
+ newList.splice(i, 1);
+ newList = [
+ {
+ ...old,
+ u: timestamp,
+ n: note as string,
+ },
+ ].concat(newList);
+ }
+
+ setLoading(true);
+
+ localStorage.setItem(
+ LOCALSTORAGE_KEYS_MAP.txPrivateNote,
+ JSON.stringify(newList),
+ );
+
+ const d = {
+ ...globalData,
+ [LOCALSTORAGE_KEYS_MAP.txPrivateNote]: newList.reduce(
+ (prev, curr) => {
+ return {
+ ...prev,
+ [curr.h]: curr.n,
+ };
+ },
+ {},
+ ),
+ };
+ setGlobalData(d);
+
+ console.log(19999, LOCALSTORAGE_KEYS_MAP.txPrivateNote, d);
+
+ setLoading(false);
+ onOk();
+ } catch (e) {
+ publishRequestError(e, 'code');
+ }
+ })
+ .catch(e => publishRequestError(e, 'code'));
+ };
+
+ const handleCancel = () => {
+ form.resetFields([]);
+ setLoading(false);
+ onCancel();
+ };
+
+ const validator = useCallback(() => {
+ return {
+ validator(_, value) {
+ if (isHash(value)) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(t(translations.profile.tx.error.invalidHash)),
+ );
+ },
+ };
+ }, [t]);
+
+ const tagValidator = useCallback(() => {
+ return {
+ validator(_, value) {
+ if (value.length > noteLengthLimit) {
+ return Promise.reject(
+ new Error(
+ t(translations.profile.tx.error.invalidNoteRange, {
+ amount: 20,
+ }),
+ ),
+ );
+ } else {
+ return Promise.resolve();
+ }
+ },
+ };
+ }, [noteLengthLimit, t]);
+
+ const text = {
+ create: t(translations.general.create),
+ edit: t(translations.general.edit),
+ delete: t(translations.general.delete),
+ };
+
+ const d = {
+ ...data,
+ note: list.filter(l => l.h === data.hash)[0]?.n,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/containers/Profile/Loadable.tsx b/src/app/containers/Profile/Loadable.tsx
new file mode 100644
index 000000000..0fc2a01d3
--- /dev/null
+++ b/src/app/containers/Profile/Loadable.tsx
@@ -0,0 +1,10 @@
+/**
+ * Asynchronously loads the component for Profile
+ */
+
+import { lazyLoad } from 'utils/loadable';
+
+export const Profile = lazyLoad(
+ () => import('./index'),
+ module => module.Profile,
+);
diff --git a/src/app/containers/Profile/TxNote.tsx b/src/app/containers/Profile/TxNote.tsx
new file mode 100644
index 000000000..a68005f79
--- /dev/null
+++ b/src/app/containers/Profile/TxNote.tsx
@@ -0,0 +1,298 @@
+import React, { useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { translations } from 'locales/i18n';
+import { Space, Modal, Input } from '@cfxjs/antd';
+import { Button } from 'app/components/Button/Loadable';
+import { formatTimeStamp } from 'utils';
+import { LOCALSTORAGE_KEYS_MAP } from 'utils/constants';
+import { ContentWrapper } from 'utils/tableColumns/utils';
+import { TablePanel as TablePanelNew } from 'app/components/TablePanelNew';
+import { useGlobalData } from 'utils/hooks/useGlobal';
+import { Link } from 'app/components/Link/Loadable';
+import { useHistory, useLocation } from 'react-router-dom';
+import styled from 'styled-components/macro';
+import qs from 'query-string';
+import { CreateTxNote } from './CreateTxNote';
+
+const { confirm, warning } = Modal;
+const { Search } = Input;
+
+type Type = {
+ h: string;
+ n: string;
+ t: number;
+ u: number;
+};
+
+export function TxNote() {
+ const history = useHistory();
+ const { search: s } = useLocation();
+ const { t } = useTranslation();
+ const [visible, setVisible] = useState(false);
+ const [stage, setStage] = useState('create');
+ const [list, setList] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+ const [globalData, setGlobalData] = useGlobalData();
+ const [search, setSearch] = useState(() => {
+ return qs.parse(s).search || '';
+ });
+ const [data, setData] = useState({
+ hash: '',
+ note: '',
+ });
+
+ useEffect(() => {
+ try {
+ setLoading(true);
+ const l = localStorage.getItem(LOCALSTORAGE_KEYS_MAP.txPrivateNote);
+ if (l) {
+ setList(JSON.parse(l));
+ }
+ setLoading(false);
+ } catch (e) {}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const columns = [
+ {
+ title: t(translations.profile.tx.hash),
+ dataIndex: 'h',
+ key: 'h',
+ width: 8,
+ render(v) {
+ return (
+
+ {v}
+
+ );
+ },
+ },
+ {
+ title: t(translations.profile.tx.note),
+ dataIndex: 'n',
+ key: 'n',
+ width: 4,
+ },
+ {
+ title: (
+
+ {t(translations.general.timestamp)}
+
+ ),
+ dataIndex: 'u',
+ key: 'u',
+ width: 4,
+ render(v, r) {
+ return (
+
+ {formatTimeStamp(v * 1000, 'standard')}
+
+ );
+ },
+ },
+ ];
+
+ const handleClickC = (e, hash = '') => {
+ if (list.length > 1000) {
+ warning({
+ title: t(translations.general.warning),
+ content: t(translations.general.exceedTip),
+ icon: null,
+ okText: t(translations.general.buttonOk),
+ cancelText: t(translations.general.buttonCancel),
+ });
+
+ return;
+ }
+
+ setData({
+ hash: hash,
+ note: '',
+ });
+ setStage('create');
+ setVisible(true);
+ };
+
+ const handleClickE = () => {
+ const selectedItem = list.filter(l => l.h === selectedRowKeys[0])[0];
+ setData({
+ hash: selectedItem?.h,
+ note: selectedItem?.n,
+ });
+ setStage('edit');
+ setVisible(true);
+ };
+
+ const handleClickD = () => {
+ confirm({
+ title: t(translations.general.warning),
+ content: t(translations.general.deleteTip),
+ icon: null,
+ okText: t(translations.general.buttonOk),
+ cancelText: t(translations.general.buttonCancel),
+ onOk() {
+ const newList = list.filter(l => !selectedRowKeys.includes(l.h));
+
+ setLoading(true);
+
+ localStorage.setItem(
+ LOCALSTORAGE_KEYS_MAP.txPrivateNote,
+ JSON.stringify(newList),
+ );
+ const d = {
+ ...globalData,
+ [LOCALSTORAGE_KEYS_MAP.txPrivateNote]: newList.reduce(
+ (prev, curr) => {
+ return {
+ ...prev,
+ [curr.h]: curr.n,
+ };
+ },
+ {},
+ ),
+ };
+ setGlobalData(d);
+
+ handleSearch('');
+
+ setList(newList);
+ setLoading(false);
+ },
+ onCancel() {
+ console.log('Cancel');
+ },
+ });
+ };
+
+ const handleOk = () => {
+ setVisible(true);
+ };
+
+ const handleCancel = () => {
+ setVisible(false);
+ };
+
+ const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
+ setSelectedRowKeys(newSelectedRowKeys);
+ };
+
+ const handleSearch = value => {
+ const search = value ? `&&search=${value}` : '';
+ history.push(`/profile?tab=tx-note${search}`);
+ };
+
+ const handleSearchChange = e => {
+ setSearch(e.target.value.trim());
+ };
+
+ const rowSelection = {
+ selectedRowKeys,
+ onChange: onSelectChange,
+ columnWidth: 1,
+ };
+
+ const text = {
+ create: t(translations.general.create),
+ edit: t(translations.general.edit),
+ delete: t(translations.general.delete),
+ };
+
+ const queryKey = (qs.parse(s).search as string) || '';
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ l.h.includes(queryKey) || l.n.includes(queryKey),
+ )}
+ columns={columns}
+ loading={loading}
+ rowKey="h"
+ key={Math.random()}
+ />
+
+ >
+ );
+}
+
+const SearchWrapper = styled.div`
+ width: 500px;
+
+ .ant-input {
+ border-radius: 16px !important;
+ background: rgba(30, 61, 228, 0.04);
+ border: none !important;
+ padding-right: 41px;
+ }
+
+ .ant-input-group {
+ display: flex;
+ }
+
+ .ant-input-group-addon {
+ background: transparent !important;
+ left: -38px !important;
+ z-index: 80;
+
+ .ant-btn {
+ background: transparent !important;
+ border: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ line-height: 1 !important;
+ box-shadow: none !important;
+
+ &:after {
+ display: none !important;
+ }
+
+ .anticon {
+ font-size: 18px;
+ margin-bottom: 3px;
+ }
+ }
+ }
+`;
diff --git a/src/app/containers/Profile/index.tsx b/src/app/containers/Profile/index.tsx
new file mode 100644
index 000000000..7e80cdef5
--- /dev/null
+++ b/src/app/containers/Profile/index.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { TabsTablePanel } from 'app/components/TabsTablePanel/Loadable';
+import { useTranslation } from 'react-i18next';
+import { translations } from 'locales/i18n';
+import styled from 'styled-components/macro';
+import { Helmet } from 'react-helmet-async';
+import { PageHeader } from 'app/components/PageHeader/Loadable';
+import { AddressLabel } from './AddressLabel';
+import { TxNote } from './TxNote';
+
+export function Profile() {
+ const { t } = useTranslation();
+ const tabs = [
+ {
+ value: 'address-label',
+ label: t(translations.profile.address.title),
+ content: ,
+ },
+ {
+ value: 'tx-note',
+ label: t(translations.profile.tx.title),
+ content: ,
+ },
+ ];
+ return (
+
+
+ {t(translations.profile.title)}
+
+
+
+ {t(translations.profile.title)}
+
+
+
+ );
+}
+
+const StyledWrapper = styled.div`
+ .button-group-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+`;
diff --git a/src/app/containers/Transaction/Detail.tsx b/src/app/containers/Transaction/Detail.tsx
index 56d961e2c..5b51af41b 100644
--- a/src/app/containers/Transaction/Detail.tsx
+++ b/src/app/containers/Transaction/Detail.tsx
@@ -41,10 +41,12 @@ import {
TokenTypeTag,
} from 'app/components/TxnComponents';
import _ from 'lodash';
-
+import { LOCALSTORAGE_KEYS_MAP } from 'utils/constants';
import imgChevronDown from 'images/chevronDown.png';
import { renderAddress } from 'utils/tableColumns/token';
import { NFTPreview } from '../../components/NFTPreview/Loadable';
+import { useGlobalData } from 'utils/hooks/useGlobal';
+import { CreateTxNote } from '../Profile/CreateTxNote';
import iconInfo from 'images/info.svg';
@@ -53,6 +55,8 @@ const getStorageFee = byteSize =>
// Transaction Detail Page
export const Detail = () => {
+ const [visible, setVisible] = useState(false);
+ const [globalData = {}] = useGlobalData();
const { t, i18n } = useTranslation();
const [isContract, setIsContract] = useState(false);
const [transactionDetail, setTransactionDetail] = useState({});
@@ -611,6 +615,22 @@ export const Detail = () => {
}, 0),
)
: 0;
+ const txNoteMap = globalData[LOCALSTORAGE_KEYS_MAP.txPrivateNote];
+ const txNote = txNoteMap[routeHash];
+
+ const txNoteProps = {
+ stage: txNote ? 'edit' : 'create',
+ visible,
+ data: {
+ hash: routeHash,
+ },
+ onOk: () => {
+ setVisible(false);
+ },
+ onCancel: () => {
+ setVisible(false);
+ },
+ };
return (
@@ -959,17 +979,49 @@ export const Detail = () => {
)}
-