diff --git a/.changeset/dirty-berries-try.md b/.changeset/dirty-berries-try.md new file mode 100644 index 000000000..a73ff3d63 --- /dev/null +++ b/.changeset/dirty-berries-try.md @@ -0,0 +1,5 @@ +--- +"@mochi-ui/pagination": minor +--- + +Add custom items per page & hide on single page diff --git a/.changeset/twelve-dolphins-add.md b/.changeset/twelve-dolphins-add.md new file mode 100644 index 000000000..8cf4e9357 --- /dev/null +++ b/.changeset/twelve-dolphins-add.md @@ -0,0 +1,5 @@ +--- +"@mochi-ui/pagination": minor +--- + +Add custom page input diff --git a/apps/mochi-web/components/Profile/RecapSection.tsx b/apps/mochi-web/components/Profile/RecapSection.tsx index 4ad2a4290..d089a1d6c 100644 --- a/apps/mochi-web/components/Profile/RecapSection.tsx +++ b/apps/mochi-web/components/Profile/RecapSection.tsx @@ -256,7 +256,7 @@ export const RecapSection = () => { total_spending = 0, total_volume = 0, } = exchange || {} - const totalExchange = total_spending + total_receive + total_volume + const totalExchange = total_spending + total_receive + mochiBalance const receive_pnl = getPnl(exchange?.receive_pnl) const spending_pnl = getPnl(exchange?.spending_pnl) @@ -309,7 +309,7 @@ export const RecapSection = () => {
- Mochi Exchange Flow + Exchange Flow Recap { : 0, }} /> -
+
{[ { - label: 'Received', + label: 'Income', value: total_receive, pnl: receive_pnl, icon: ( @@ -368,13 +368,20 @@ export const RecapSection = () => { ), }, { - label: 'Sent', + label: 'Expense', value: total_spending, pnl: spending_pnl, icon: (
), }, + { + label: 'Current balance', + value: mochiBalance, + icon: ( +
+ ), + }, ].map((item) => (
@@ -383,34 +390,30 @@ export const RecapSection = () => { {item.label} - {`$${mochiUtils.formatDigit({ - value: item.value || 0, - fractionDigits: 2, - })}`} + {`${mochiUtils.formatUsdDigit(item.value || 0)}`}
-
- Compare with last period - - - {mochiUtils.formatPercentDigit( - Number.isNaN(Number(item.pnl)) || item.pnl === '' - ? 0 - : Math.abs(Number(item.pnl)), - )} - -
+ {!!item.pnl && ( +
+ Compare with last period + + + {mochiUtils.formatPercentDigit( + Number.isNaN(Number(item.pnl)) || item.pnl === '' + ? 0 + : Math.abs(Number(item.pnl)), + )} + +
+ )}
))}
Net Worth - - {`$${mochiUtils.formatDigit({ - value: total_volume || 0, - fractionDigits: 2, - })}`} - + + {`${mochiUtils.formatUsdDigit(total_volume || 0)}`} +
diff --git a/apps/mochi-web/components/Profile/TransactionSection.tsx b/apps/mochi-web/components/Profile/TransactionSection.tsx index 1f50b1dc8..ad12414a9 100644 --- a/apps/mochi-web/components/Profile/TransactionSection.tsx +++ b/apps/mochi-web/components/Profile/TransactionSection.tsx @@ -95,6 +95,7 @@ export const TransactionOverviewSection = () => { onItemPerPageChange: setSize, onPageChange: setPage, className: 'px-4', + allowCustomPage: true, }, }} onRow={(tx) => { diff --git a/apps/mochi-web/components/ProfileDropdown.tsx b/apps/mochi-web/components/ProfileDropdown.tsx index c83e5e346..4fab1ac62 100644 --- a/apps/mochi-web/components/ProfileDropdown.tsx +++ b/apps/mochi-web/components/ProfileDropdown.tsx @@ -101,7 +101,7 @@ export default function ProfileDropdown({ rightExtra={ } onClick={(e) => { @@ -119,7 +119,7 @@ export default function ProfileDropdown({ } onClick={(e) => { e.preventDefault() - setTheme(theme === 'system' ? activeTheme ?? 'light' : 'system') + setTheme(theme === 'system' ? 'light' : 'system') }} > System Theme diff --git a/apps/mochi-web/components/TokenTableList.tsx b/apps/mochi-web/components/TokenTableList.tsx index b21b34919..73585b714 100644 --- a/apps/mochi-web/components/TokenTableList.tsx +++ b/apps/mochi-web/components/TokenTableList.tsx @@ -176,7 +176,7 @@ export const TokenTableList = ({ const [sort, setSort] = useState('') return ( - + { totalItems: total, onItemPerPageChange: setSize, onPageChange: setPage, + allowCustomPage: true, }, }} className="px-6" diff --git a/apps/mochi-web/components/settings/general/AddNewWalletModal.tsx b/apps/mochi-web/components/settings/general/AddNewWalletModal.tsx index f84fc44e0..1599626ad 100644 --- a/apps/mochi-web/components/settings/general/AddNewWalletModal.tsx +++ b/apps/mochi-web/components/settings/general/AddNewWalletModal.tsx @@ -1,3 +1,4 @@ +import { Profile } from '@consolelabs/mochi-rest' import { Modal, ModalContent, @@ -9,6 +10,8 @@ import { LoginWidget } from '@mochi-web3/login-widget' import { useState } from 'react' import { WretchError } from 'wretch/resolver' import { API } from '~constants/api' +import { useWalletStore } from '~store' +import { truncateWallet } from '~utils/string' interface Props { code?: string @@ -18,6 +21,7 @@ interface Props { export const AddNewWalletModal = ({ code, isOpen, onOpenChange }: Props) => { const [loading, setLoading] = useState(false) + const { setWallets } = useWalletStore() return ( @@ -59,7 +63,15 @@ export const AddNewWalletModal = ({ code, isOpen, onOpenChange }: Props) => { scheme: 'danger', }), ]) - .json() + .json((r: Profile) => { + setWallets(r) + toast({ + description: `Your ${truncateWallet( + address, + )} wallet is now connected to Mochi.`, + scheme: 'success', + }) + }) .finally(() => { setLoading(false) onOpenChange(false) diff --git a/apps/mochi-web/components/settings/general/MoneySource.tsx b/apps/mochi-web/components/settings/general/MoneySource.tsx index 4164b8538..b06659cfd 100644 --- a/apps/mochi-web/components/settings/general/MoneySource.tsx +++ b/apps/mochi-web/components/settings/general/MoneySource.tsx @@ -1,6 +1,10 @@ import { Button, FormControl, + ScrollArea, + ScrollAreaScrollbar, + ScrollAreaThumb, + ScrollAreaViewport, SectionHeader, SectionHeaderActions, SectionHeaderDescription, @@ -32,6 +36,17 @@ export const MoneySource = () => { const { isOpen, onOpenChange } = useDisclosure() const [code, setCode] = useState('') + const sortedWallets = [...wallets].sort((a, b) => { + if (a.id === defaultMoneySource.platform) return -1 + if (b.id === defaultMoneySource.platform) return 1 + if (a.chainSymbol === 'SOL' && b.chainSymbol !== 'SOL') return -1 + if (a.chainSymbol !== 'SOL' && b.chainSymbol === 'SOL') return 1 + return ( + Number(b.usd_amount.slice(b.usd_amount.indexOf('$') + 1)) - + Number(a.usd_amount.slice(a.usd_amount.indexOf('$') + 1)) + ) + }) + const onAddNewWallet = async () => { if (!profile?.id) return const res = await api.profile.connect.requestCode(profile.id) @@ -96,50 +111,61 @@ export const MoneySource = () => { - {wallets.map((each) => - each.id === defaultMoneySource.platform ? ( - } - subTitle={ - - {each.usd_amount} - - } - > - {each.title} - - ) : ( - - } - subTitle={ - - {each.usd_amount} - - } - disabled={!each.balances.length} - > - {each.title} - - ), - )} - - + + {sortedWallets.map((each) => + each.id === defaultMoneySource.platform ? ( + } + subTitle={ + + {each.usd_amount} + + } + > + {each.title} + + ) : ( + + } + subTitle={ + + {each.usd_amount} + + } + disabled={!each.balances.length} + > + {each.title} + + ), + )} + + + + + + + diff --git a/apps/mochi-web/constants/api.ts b/apps/mochi-web/constants/api.ts index 941bf8ba0..9f72776c5 100644 --- a/apps/mochi-web/constants/api.ts +++ b/apps/mochi-web/constants/api.ts @@ -86,7 +86,7 @@ export const GET_PATHS = { GET_TOTAL_BALANCES: (profileId: string) => `/users/${profileId}/balances`, GET_MONTHLY_STATS: (profileId: string) => `/profile/${profileId}/monthly-stats`, - CHANGELOGS: '/product-metadata/changelogs/', + CHANGELOGS: '/product-metadata/changelogs', CHANGELOG_DETAIL: (version: string) => `/product-metadata/changelogs/${version}`, CHANGELOGS_LATEST: '/product-metadata/changelogs/latest', diff --git a/apps/mochi-web/hooks/changelog/useFetchChangelogs.ts b/apps/mochi-web/hooks/changelog/useFetchChangelogs.ts new file mode 100644 index 000000000..3bdef10ac --- /dev/null +++ b/apps/mochi-web/hooks/changelog/useFetchChangelogs.ts @@ -0,0 +1,24 @@ +import useSWR from 'swr' +import { API, GET_PATHS } from '~constants/api' +import { ResponseProductChangelogs } from '~types/mochi-schema' + +export const SWR_KEY_FETCH_CHANGELOGS = 'SWR_KEY_FETCH_CHANGELOGS' + +export const useFetchChangelogs = ( + query: { page?: number; size?: number } = {}, +) => { + const { data } = useSWR( + [SWR_KEY_FETCH_CHANGELOGS, query], + async ([_, id]: [any, string]) => { + if (!id) return {} + return API.MOCHI.query(query) + .get(GET_PATHS.CHANGELOGS) + .json((r) => r ?? {}) + }, + ) + + return { + data: data?.data, + pagination: data?.pagination, + } +} diff --git a/apps/mochi-web/hooks/useTheme.ts b/apps/mochi-web/hooks/useTheme.ts index e0a904419..d43a4cf12 100644 --- a/apps/mochi-web/hooks/useTheme.ts +++ b/apps/mochi-web/hooks/useTheme.ts @@ -5,7 +5,7 @@ export type ActiveThemeValue = 'light' | 'dark' | undefined export type ThemeValue = 'light' | 'dark' | 'system' | undefined export const useTheme = () => { - const { theme, systemTheme, ...rest } = useInternalTheme() + const { theme = 'system', systemTheme, ...rest } = useInternalTheme() const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) diff --git a/apps/mochi-web/pages/changelog/index.tsx b/apps/mochi-web/pages/changelog/index.tsx index f2fb43c91..15320f84c 100644 --- a/apps/mochi-web/pages/changelog/index.tsx +++ b/apps/mochi-web/pages/changelog/index.tsx @@ -1,38 +1,25 @@ -import { GetStaticProps } from 'next' -import { Badge, Button, IconButton, Typography } from '@mochi-ui/core' +import { + Badge, + Button, + IconButton, + Pagination, + Typography, +} from '@mochi-ui/core' import { Layout } from '~app/layout' import { SEO } from '~app/layout/seo' import { PAGES } from '~constants' import { Markdown } from '~cpn/Changelog/Markdown' -import { - ModelProductChangelogs, - ResponseProductChangelogs, -} from '~types/mochi-schema' +import { ModelProductChangelogs } from '~types/mochi-schema' import { format, isValid } from 'date-fns' import Link from 'next/link' import { ROUTES } from '~constants/routes' -import { API, GET_PATHS } from '~constants/api' import { InboxSolid } from '@mochi-ui/icons' import { HOME_URL, TWITTER_LINK } from '~envs' -import { useRef } from 'react' +import { useRef, useState } from 'react' import { Footer } from '~app/layout/footer' +import { useFetchChangelogs } from '~hooks/changelog/useFetchChangelogs' -type Props = { - data?: Array -} - -export const getStaticProps: GetStaticProps = async () => { - const { data } = await API.MOCHI.get( - GET_PATHS.CHANGELOGS, - ).json((r) => r) - - return { - props: { - data, - }, - revalidate: 60, - } -} +const DEFAULT_PAGE_SIZE = 5 const ChangelogItem = ({ content, @@ -69,8 +56,13 @@ const ChangelogItem = ({ ) } -export default function Changelog({ data }: Props) { +export default function Changelog() { const ref = useRef(null) + const [page, setPage] = useState(1) + const { data, pagination } = useFetchChangelogs({ + page: page - 1, + size: DEFAULT_PAGE_SIZE, + }) return ( }> @@ -119,6 +111,18 @@ export default function Changelog({ data }: Props) { {data?.map( (d, i) => d && , )} + + ) diff --git a/packages/components/pagination/package.json b/packages/components/pagination/package.json index d5b36fe95..1edf74e31 100644 --- a/packages/components/pagination/package.json +++ b/packages/components/pagination/package.json @@ -35,7 +35,9 @@ "dependencies": { "@mochi-ui/icons": "workspace:*", "@mochi-ui/select": "workspace:*", - "@mochi-ui/theme": "workspace:*" + "@mochi-ui/theme": "workspace:*", + "@mochi-ui/separator": "workspace:*", + "@mochi-ui/input": "workspace:*" }, "clean-package": "../../../clean-package.config.json" } diff --git a/packages/components/pagination/src/pagination.tsx b/packages/components/pagination/src/pagination.tsx index 60075656b..f0214f0f4 100644 --- a/packages/components/pagination/src/pagination.tsx +++ b/packages/components/pagination/src/pagination.tsx @@ -9,6 +9,8 @@ import { SelectTrigger, SelectValue, } from '@mochi-ui/select' +import { Separator } from '@mochi-ui/separator' +import { TextFieldInput } from '@mochi-ui/input' import { formatNumber } from './utils' const { @@ -32,6 +34,9 @@ interface PaginationProps { onPageChange?: (page: number) => void onItemPerPageChange?: (page: number) => void recordName?: string + hideOnSinglePage?: boolean + allowCustomItemsPerPage?: boolean + allowCustomPage?: boolean } function PageButton({ @@ -66,14 +71,19 @@ export default function Pagination({ onItemPerPageChange, className, recordName = 'members', + hideOnSinglePage = false, + allowCustomItemsPerPage = true, + allowCustomPage = false, }: PaginationProps) { const [currentPage, setCurrentPage] = useState(page) const [currentItemPerPage, setCurrentItemPerPage] = useState(initItemsPerPage) + const [customPage, setCustomPage] = useState('') useEffect(() => { if (onPageChange) { onPageChange(currentPage) } + setCustomPage('') }, [currentPage, onPageChange]) useEffect(() => { @@ -91,6 +101,17 @@ export default function Pagination({ const totalPages = Math.ceil(totalItems / currentItemPerPage) || initTotalPage + const onCustomPageChange = (value: string) => { + if (!value) return + const page = Number(value) + if (page >= 1 && page <= totalPages && Number.isInteger(page)) { + setCurrentPage(page) + } else if (page > totalPages && Number.isInteger(page)) { + setCurrentPage(totalPages) + } + setCustomPage('') + } + const renderPagination = () => { const pages = [] @@ -239,40 +260,64 @@ export default function Pagination({ return pages } + if (hideOnSinglePage && totalPages <= 1) { + return null + } + return (
-
-
Showing
- { + setCurrentPage(1) + setCurrentItemPerPage(Number(value)) + }} > - - - - {[5, 15, 25, 50, 100].map((value) => ( - - {value} - - ))} - - -
- {recordName} of {formatNumber(totalItems)} + + + + + {[5, 15, 25, 50, 100].map((value) => ( + + {value} + + ))} + + +
+ {recordName} of {formatNumber(totalItems)} +
+ {allowCustomPage && ( +
+ + Go to + setCustomPage(e.target.value)} + onBlur={(e) => onCustomPageChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onCustomPageChange(e.currentTarget.value) + } + }} + /> + Page +
+ )}
-
+ )}