diff --git a/.eslintrc.js b/.eslintrc.js index 6a9ae06f1..f8562c67f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -112,6 +112,7 @@ module.exports = { '@typescript-eslint/lines-between-class-members': 'off', 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': 'error', + 'jsx-a11y/label-has-associated-control': 'off', }, env: { jest: true, diff --git a/package.json b/package.json index dbe2e55b8..6341ef360 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "lodash.debounce": "4.0.8", "moment": "2.30.1", "observable-hooks": "^4.2.3", + "qrcode": "1.5.3", "react": "17.0.2", "react-dom": "17.0.2", "react-i18next": "11.18.6", @@ -56,6 +57,7 @@ "@types/jest": "29.5.12", "@types/lodash.debounce": "4.0.9", "@types/node": "16.18.79", + "@types/qrcode": "1.5.5", "@types/react": "17.0.65", "@types/react-dom": "17.0.20", "@types/react-outside-click-handler": "^1.3.0", diff --git a/src/components/Footer/index.module.scss b/src/components/Footer/index.module.scss index b61985475..f2e331af3 100644 --- a/src/components/Footer/index.module.scss +++ b/src/components/Footer/index.module.scss @@ -7,6 +7,7 @@ cursor: pointer; background: transparent; border: none; + text-align: left; &:hover { color: var(--primary-color); diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx index c6b792eed..db6a7d7a0 100644 --- a/src/components/Footer/index.tsx +++ b/src/components/Footer/index.tsx @@ -76,6 +76,10 @@ export default memo(() => { label: t('footer.faucet'), url: 'https://faucet.nervos.org/', }, + { + label: t('footer.api-doc'), + url: 'https://ckb-explorer.readme.io/reference/transaction', + }, ], }, { diff --git a/src/components/Qrcode/index.tsx b/src/components/Qrcode/index.tsx new file mode 100644 index 000000000..d5342192a --- /dev/null +++ b/src/components/Qrcode/index.tsx @@ -0,0 +1,42 @@ +import { useEffect, useRef } from 'react' +import QRCode from 'qrcode' +import { ReactComponent as QrCodeIcon } from './qrcode.svg' +import styles from './styles.module.scss' + +// TODO: add address verification +// network type +// joyID +const Qrcode = ({ text }: { text: string }) => { + const qrRef = useRef(null) + + useEffect(() => { + const cvs = qrRef.current + if (!cvs) return + QRCode.toCanvas( + cvs, + text, + { + margin: 5, + errorCorrectionLevel: 'H', + width: 144, + }, + err => { + if (err) { + console.error(err) + } + }, + ) + }, [qrRef, text]) + + return ( +
+ + + +
+ ) +} + +export default Qrcode diff --git a/src/components/Qrcode/qrcode.svg b/src/components/Qrcode/qrcode.svg new file mode 100644 index 000000000..1edda3e08 --- /dev/null +++ b/src/components/Qrcode/qrcode.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/components/Qrcode/styles.module.scss b/src/components/Qrcode/styles.module.scss new file mode 100644 index 000000000..5691ce84b --- /dev/null +++ b/src/components/Qrcode/styles.module.scss @@ -0,0 +1,57 @@ +.container { + width: 100%; + + label { + display: flex; + align-items: center; + line-height: 1; + cursor: pointer; + } + + input { + position: absolute; + width: 0; + height: 0; + opacity: 0; + } + + position: relative; + + .qrcode { + top: calc(100% + 10px); + box-shadow: 0 0 10px rgb(0 0 0 / 20%); + } + + &::after { + top: calc(100% - 10px); + content: ''; + width: 10px; + height: 10px; + border: 10px solid transparent; + border-bottom: 10px solid #fff; + filter: drop-shadow(0 -5px 5px rgb(0 0 0 / 10%)); + } + + .qrcode, + &::after { + display: none; + position: absolute; + left: 50%; + transform: translateX(-50%); + } + + &:hover, + &:focus-within { + .qrcode, + &::after { + display: block; + } + } + + @media screen and (width <= 768px) { + .qrcode, + &::after { + left: 0; + } + } +} diff --git a/src/locales/en.json b/src/locales/en.json index 0790fac23..c34350acd 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -329,7 +329,8 @@ "telegram": "Telegram", "reddit": "Reddit", "youtube": "YouTube", - "forum": "Forum" + "forum": "Forum", + "api-doc": "API Documentation" }, "search": { "loading": "Loading...", diff --git a/src/locales/zh.json b/src/locales/zh.json index 6eb1c4a91..7d03f8a59 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -329,7 +329,8 @@ "telegram": "Telegram", "reddit": "Reddit", "youtube": "YouTube", - "forum": "Forum" + "forum": "Forum", + "api-doc": "API 文档" }, "search": { "loading": "加载中...", diff --git a/src/pages/Address/AddressComp.tsx b/src/pages/Address/AddressComp.tsx index 10d3efe0c..1c78c6d4b 100644 --- a/src/pages/Address/AddressComp.tsx +++ b/src/pages/Address/AddressComp.tsx @@ -156,12 +156,17 @@ export const AddressOverviewCard: FC<{ address: Address }> = ({ address }) => { }, ) - const { data: cotaList } = useQuery(['cota-list', initList?.pagination.series], () => - Promise.all( - (initList?.pagination.series ?? []).map(p => - explorerService.api.fetchNFTItemByOwner(address.addressHash, 'cota', p), - ), - ).then(resList => resList.flatMap(res => res.data)), + const { data: cotaList } = useQuery( + ['cota-list', initList?.pagination?.series], + () => + Promise.all( + (initList?.pagination.series ?? []).map(p => + explorerService.api.fetchNFTItemByOwner(address.addressHash, 'cota', p), + ), + ).then(resList => resList.flatMap(res => res.data)), + { + enabled: !!initList?.pagination?.series?.length, + }, ) const overviewItems: CardCellInfo<'left' | 'right'>[] = [ diff --git a/src/pages/Address/index.tsx b/src/pages/Address/index.tsx index 48d042696..fdcdf7c0d 100644 --- a/src/pages/Address/index.tsx +++ b/src/pages/Address/index.tsx @@ -22,6 +22,7 @@ import { ReactComponent as ShareIcon } from './share.svg' import styles from './styles.module.scss' import { useDASAccount } from '../../hooks/useDASAccount' import { Link } from '../../components/Link' +import Qrcode from '../../components/Qrcode' export const Address = () => { const { address } = useParams<{ address: string }>() @@ -111,6 +112,7 @@ export const Address = () => { title={addressInfoQuery.data?.type === 'LockHash' ? t('address.lock_hash') : t('address.address')} hash={address} customActions={[ + , counterpartAddr ? (