From 8fcf0355b3b1d8b1aa6edb2da69aff9fc3811b44 Mon Sep 17 00:00:00 2001 From: JiaJun <2394389886@qq.com> Date: Fri, 10 Apr 2026 17:29:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E9=A1=B9=E7=9B=AE=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 - src/components/modal/index.tsx | 16 +- src/components/table/index.tsx | 55 ------ src/features/addressBook/addressValidation.ts | 6 +- src/features/addressBook/useAddressBook.ts | 15 +- src/features/goods/GoodsCategoryList.tsx | 2 +- src/features/goods/GoodsRedeemModal.tsx | 6 +- src/features/goods/RegionPicker.tsx | 68 +++++--- src/features/goods/useAssetsQuery.ts | 2 - src/features/goods/useGoodsCatalog.ts | 5 +- src/features/notifications/GlobalToast.tsx | 14 +- src/features/notifications/store.ts | 35 +++- src/index.css | 11 ++ src/lib/request.ts | 91 +++++++++- src/mock/index.ts | 162 ------------------ src/types/index.ts | 5 - src/views/account/index.tsx | 148 ++++------------ src/views/home/index.tsx | 26 +-- src/views/record/index.tsx | 30 ++-- 19 files changed, 292 insertions(+), 407 deletions(-) delete mode 100644 src/components/table/index.tsx delete mode 100644 src/mock/index.ts diff --git a/src/App.tsx b/src/App.tsx index a772879..9cb8da7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,8 +21,6 @@ function App() { // // const {token, language} = message.payload // - // console.log('postMessage', token, language) - // // if (typeof token === 'string' && token.trim()) { // sessionStorage.setItem('host_token', token) // } diff --git a/src/components/modal/index.tsx b/src/components/modal/index.tsx index 51d13fb..f855269 100644 --- a/src/components/modal/index.tsx +++ b/src/components/modal/index.tsx @@ -1,3 +1,5 @@ +import {Children} from 'react' + import { cn } from '@/lib' import type { ModalProps } from '@/types' @@ -15,6 +17,9 @@ export function Modal({ return null } + const footerItems = Children.toArray(footer) + const hasMultipleFooterItems = footerItems.length > 1 + const handleOverlayClick = () => { if (closeOnOverlayClick) { onClose?.() @@ -62,8 +67,15 @@ export function Modal({ {footer ? (
-
- {footer} +
*]:min-w-0 [&>*]:flex-1 sm:[&>*]:flex-none' + : 'flex', + )} + > + {footerItems}
) : null} diff --git a/src/components/table/index.tsx b/src/components/table/index.tsx deleted file mode 100644 index 6578f43..0000000 --- a/src/components/table/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type { BorderlessTableProps } from '@/types' - -function BorderlessTable>({ - columns, - dataSource, -}: BorderlessTableProps) { - return ( -
- - - - {columns.map((column) => ( - - ))} - - - - {dataSource.map((record, index) => ( - - {columns.map((column, columnIndex) => { - const value = record[column.key] - const isLastRow = index === dataSource.length - 1 - const isFirstColumn = columnIndex === 0 - const isLastColumn = columnIndex === columns.length - 1 - - return ( - - ) - })} - - ))} - -
- {column.label} -
- {column.render ? column.render(value, record, index) : value} -
-
- ) -} - -export default BorderlessTable diff --git a/src/features/addressBook/addressValidation.ts b/src/features/addressBook/addressValidation.ts index a8d85aa..6be749f 100644 --- a/src/features/addressBook/addressValidation.ts +++ b/src/features/addressBook/addressValidation.ts @@ -5,6 +5,8 @@ type AddressValidationResult = | { valid: false; message: string } export function validateAddressFormSubmission(addressForm: AddAddressForm): AddressValidationResult { + const normalizedRegion = addressForm.region.filter((value) => value.trim()) + if (!addressForm.name.trim()) { return { valid: false, @@ -19,10 +21,10 @@ export function validateAddressFormSubmission(addressForm: AddAddressForm): Addr } } - if (addressForm.region.length !== 3) { + if (normalizedRegion.length === 0) { return { valid: false, - message: 'Please select province, city and district.', + message: 'Please select a region.', } } diff --git a/src/features/addressBook/useAddressBook.ts b/src/features/addressBook/useAddressBook.ts index fc7eccd..e3af014 100644 --- a/src/features/addressBook/useAddressBook.ts +++ b/src/features/addressBook/useAddressBook.ts @@ -5,7 +5,6 @@ import {addressAdd, addressDelete, addressEdit, addressList} from '@/api/address import {validateAddressFormSubmission} from '@/features/addressBook/addressValidation' import {notifyError} from '@/features/notifications' import {queryKeys} from '@/lib/queryKeys.ts' -import {emptyAddressFormMock} from '@/mock' import {useUserStore} from '@/store/user.ts' import type {AddressListItem} from '@/types/address.type.ts' import type {AddAddressForm, AddressOption} from '@/types' @@ -14,6 +13,14 @@ type UseAddressBookOptions = { autoLoad?: boolean } +const emptyAddressForm: AddAddressForm = { + name: '', + phone: '', + region: [], + detailedAddress: '', + isDefault: false, +} + export function getAddressText(item: AddressListItem) { const regionText = item.region_text || item.region.map((part) => part.trim()).join(', ') return [regionText, item.detail_address].filter(Boolean).join(', ') @@ -42,7 +49,7 @@ export function mapAddressToForm(item: AddressListItem): AddAddressForm { export function useAddressBook(options?: UseAddressBookOptions) { const queryClient = useQueryClient() const sessionId = useUserStore((state) => state.authInfo?.session_id ?? '') - const [addressForm, setAddressForm] = useState(emptyAddressFormMock) + const [addressForm, setAddressForm] = useState(emptyAddressForm) const addressListQuery = useQuery({ queryKey: queryKeys.addressList(sessionId), @@ -111,12 +118,12 @@ export function useAddressBook(options?: UseAddressBookOptions) { }, [queryClient, sessionId]) const resetAddressForm = () => { - setAddressForm(emptyAddressFormMock) + setAddressForm(emptyAddressForm) saveAddressMutation.reset() } const fillAddressForm = (address?: AddressListItem | null) => { - setAddressForm(address ? mapAddressToForm(address) : emptyAddressFormMock) + setAddressForm(address ? mapAddressToForm(address) : emptyAddressForm) saveAddressMutation.reset() } diff --git a/src/features/goods/GoodsCategoryList.tsx b/src/features/goods/GoodsCategoryList.tsx index a206f13..2e06d49 100644 --- a/src/features/goods/GoodsCategoryList.tsx +++ b/src/features/goods/GoodsCategoryList.tsx @@ -64,7 +64,7 @@ export function GoodsCategoryList({ if (!categories.length) { return ( -
+
{emptyText}
diff --git a/src/features/goods/GoodsRedeemModal.tsx b/src/features/goods/GoodsRedeemModal.tsx index e39aa8e..9601953 100644 --- a/src/features/goods/GoodsRedeemModal.tsx +++ b/src/features/goods/GoodsRedeemModal.tsx @@ -92,7 +92,7 @@ export function GoodsRedeemModal({
+ className="flex items-center justify-between gap-[10px] border-b border-white/10 px-[6px] pb-[16px]">
onChangeAddressForm('detailedAddress', event.target.value)} - placeholder="Street, building, unit and room number" + placeholder="Enter detail address" className="bg-transparent text-[14px] text-white outline-none placeholder:text-white/35" />
diff --git a/src/features/goods/RegionPicker.tsx b/src/features/goods/RegionPicker.tsx index 81d7a0a..b317295 100644 --- a/src/features/goods/RegionPicker.tsx +++ b/src/features/goods/RegionPicker.tsx @@ -1,4 +1,5 @@ import {useEffect, useState} from 'react' +import {ChevronDown} from 'lucide-react' type ProvinceNode = { c: string @@ -32,6 +33,8 @@ type RegionDataset = { area: AreaNode[] } +const DIRECT_MUNICIPALITY_NAMES = new Set(['北京市', '上海市', '天津市', '重庆市']) + function RegionSelect({ value, placeholder, @@ -60,8 +63,8 @@ function RegionSelect({ ))} -
- v +
+
) @@ -70,7 +73,8 @@ function RegionSelect({ export function RegionPicker({value, onChange}: RegionPickerProps) { const [regionData, setRegionData] = useState(null) const [loadFailed, setLoadFailed] = useState(false) - const [province = '', city = '', district = ''] = value + const normalizedValue = value.filter(Boolean) + const province = normalizedValue[0] ?? '' useEffect(() => { let cancelled = false @@ -120,18 +124,24 @@ export function RegionPicker({value, onChange}: RegionPickerProps) { const areaList = regionData?.area ?? [] const selectedProvince = provinceList.find((item) => item.n === province) - const selectedCity = cityList.find((item) => item.n === city && item.p === selectedProvince?.p) - const cities = selectedProvince ? cityList.filter((item) => item.p === selectedProvince.p) : [] + const isDirectMunicipality = province ? DIRECT_MUNICIPALITY_NAMES.has(province) || (selectedProvince != null && cities.length === 0) : false + const city = isDirectMunicipality ? '' : normalizedValue[1] ?? '' + const district = isDirectMunicipality ? normalizedValue[1] ?? '' : normalizedValue[2] ?? '' + const selectedCity = cityList.find((item) => item.n === city && item.p === selectedProvince?.p) - const districts = selectedCity - ? areaList.filter((item) => item.p === selectedCity.p && item.y === selectedCity.y) - : [] + const districts = isDirectMunicipality + ? selectedProvince + ? areaList.filter((item) => item.p === selectedProvince.p) + : [] + : selectedCity + ? areaList.filter((item) => item.p === selectedCity.p && item.y === selectedCity.y) + : [] return ( -
+
onChange(nextProvince ? [nextProvince] : [])} disabled={loadFailed || !regionData} /> - onChange(nextCity ? [province, nextCity] : [province])} - /> - onChange(nextDistrict ? [province, city, nextDistrict] : [province, city])} - /> + {isDirectMunicipality ? ( + onChange(nextDistrict ? [province, nextDistrict] : [province])} + /> + ) : ( + onChange(nextCity ? [province, nextCity] : [province])} + /> + )} + {isDirectMunicipality ? null : ( + onChange(nextDistrict ? [province, city, nextDistrict] : [province, city])} + /> + )}
) } diff --git a/src/features/goods/useAssetsQuery.ts b/src/features/goods/useAssetsQuery.ts index ab3cdb2..518357c 100644 --- a/src/features/goods/useAssetsQuery.ts +++ b/src/features/goods/useAssetsQuery.ts @@ -33,7 +33,5 @@ export function useAssetsQuery() { return { assetsInfo: assetsQuery.data ?? null, - assetsLoading: assetsQuery.isPending, - assetsError: assetsQuery.error instanceof Error ? assetsQuery.error.message : null, } } diff --git a/src/features/goods/useGoodsCatalog.ts b/src/features/goods/useGoodsCatalog.ts index de86d05..607b5d3 100644 --- a/src/features/goods/useGoodsCatalog.ts +++ b/src/features/goods/useGoodsCatalog.ts @@ -1,4 +1,4 @@ -import {useQuery, useQueryClient} from '@tanstack/react-query' +import {useQuery} from '@tanstack/react-query' import {goodList} from '@/api/business.ts' import { @@ -52,7 +52,6 @@ type UseGoodsCatalogOptions = { } export function useGoodsCatalog(options?: UseGoodsCatalogOptions) { - const queryClient = useQueryClient() const types = options?.types?.length ? [...options.types] : HOME_GOOD_TYPE_ORDER const goodsCatalogQuery = useQuery({ queryKey: queryKeys.goodsCatalog(types), @@ -83,7 +82,5 @@ export function useGoodsCatalog(options?: UseGoodsCatalogOptions) { return { productCategories: goodsCatalogQuery.data ?? [], loading: goodsCatalogQuery.isPending, - error: goodsCatalogQuery.error instanceof Error ? goodsCatalogQuery.error.message : null, - invalidateGoods: () => queryClient.invalidateQueries({queryKey: ['goods-catalog']}), } } diff --git a/src/features/notifications/GlobalToast.tsx b/src/features/notifications/GlobalToast.tsx index 8f81386..939d15b 100644 --- a/src/features/notifications/GlobalToast.tsx +++ b/src/features/notifications/GlobalToast.tsx @@ -5,16 +5,24 @@ import {useToastStore} from './store' export function GlobalToast() { const toast = useToastStore((state) => state.toast) - if (!toast?.visible) { + if (!toast) { return null } const isError = toast.type === 'error' return ( -
+
| null = null +let toastRemoveTimer: ReturnType | null = null export function resolveToastMessage(source: unknown, fallback = '') { if (typeof source === 'string' && source.trim()) { @@ -37,18 +38,44 @@ export const useToastStore = create((set) => ({ if (toastTimer) { clearTimeout(toastTimer) } + if (toastRemoveTimer) { + clearTimeout(toastRemoveTimer) + } set({ toast: { message, type, - visible: true, + visible: false, }, }) + requestAnimationFrame(() => { + set((state) => state.toast + ? { + toast: { + ...state.toast, + visible: true, + }, + } + : state) + }) + toastTimer = setTimeout(() => { - set({toast: null}) + set((state) => state.toast + ? { + toast: { + ...state.toast, + visible: false, + }, + } + : state) toastTimer = null + + toastRemoveTimer = setTimeout(() => { + set({toast: null}) + toastRemoveTimer = null + }, 220) }, 2000) }, clearToast: () => { @@ -56,6 +83,10 @@ export const useToastStore = create((set) => ({ clearTimeout(toastTimer) toastTimer = null } + if (toastRemoveTimer) { + clearTimeout(toastRemoveTimer) + toastRemoveTimer = null + } set({toast: null}) }, })) diff --git a/src/index.css b/src/index.css index 267f90c..0c33ad5 100644 --- a/src/index.css +++ b/src/index.css @@ -6,6 +6,17 @@ body, font-family: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +.input-no-spin::-webkit-outer-spin-button, +.input-no-spin::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.input-no-spin[type='number'] { + -moz-appearance: textfield; + appearance: textfield; +} + .button-play { align-items: center; diff --git a/src/lib/request.ts b/src/lib/request.ts index 1503958..1ecc24a 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -1,7 +1,9 @@ -import ky, {HTTPError, type Input, type KyInstance, type Options} from 'ky' +import ky, {HTTPError, type AfterResponseHook, type Input, type KyInstance, type Options} from 'ky' import {notifyError, resolveToastMessage} from '@/features/notifications' import {useUserStore} from '@/store/user.ts' +import type {ValidateTokenData} from '@/types/auth.type.ts' +import {objectToFormData} from './tool' type RequestMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' type ResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData' | 'response' @@ -44,13 +46,97 @@ const isApiEnvelope = (value: unknown): value is ApiEnvelope => const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '/api/' const REQUEST_TIMEOUT = 10_000 +const AUTH_RETRY_HEADER = 'x-auth-retried' +const VERIFY_TOKEN_PATH = '/v1/mall/verifyToken' let accessTokenFormatter: TokenFormatter = (token) => `Bearer ${token}` +let refreshAuthInfoPromise: Promise | null = null export const setAccessTokenFormatter = (formatter?: TokenFormatter) => { accessTokenFormatter = formatter ?? ((token) => `Bearer ${token}`) } +const authRefreshClient = ky.create({ + baseUrl: API_BASE_URL, + timeout: REQUEST_TIMEOUT, + retry: 0, + headers: { + lang: 'zh', + }, +}) + +const handleUnauthorizedRetry: AfterResponseHook = async ({request, response}) => { + if (!shouldRefreshOnUnauthorized(request, response)) { + return response + } + + try { + const authInfo = await refreshAuthInfo() + const nextRequest = withRefreshedSessionId(request, authInfo.session_id) + return await requestClient(nextRequest) + } catch { + useUserStore.getState().clearUserInfo() + return response + } +} + +async function refreshAuthInfo() { + if (refreshAuthInfoPromise) { + return refreshAuthInfoPromise + } + + refreshAuthInfoPromise = (async () => { + const token = useUserStore.getState().userInfo?.token?.trim() + if (!token) { + throw new RequestError('Unauthorized') + } + + const response = await authRefreshClient.post('v1/mall/verifyToken', { + body: objectToFormData({token}), + }).json<{ + code: number + msg?: string + data?: ValidateTokenData + }>() + + if (!response || (response.code !== 1 && response.code !== 200) || !response.data?.session_id) { + throw new RequestError(resolveToastMessage(response, 'Unauthorized')) + } + + useUserStore.getState().setAuthInfo(response.data) + return response.data + })() + + try { + return await refreshAuthInfoPromise + } finally { + refreshAuthInfoPromise = null + } +} + +function shouldRefreshOnUnauthorized(request: Request, response: Response) { + if (response.status !== 401) { + return false + } + + if (request.headers.get(AUTH_RETRY_HEADER) === '1') { + return false + } + + return !request.url.includes(VERIFY_TOKEN_PATH) +} + +function withRefreshedSessionId(request: Request, sessionId: string) { + const nextUrl = new URL(request.url) + if (nextUrl.searchParams.has('session_id')) { + nextUrl.searchParams.set('session_id', sessionId) + } + + const nextRequest = new Request(nextUrl, request.clone()) + nextRequest.headers.set(AUTH_RETRY_HEADER, '1') + return nextRequest +} + const requestClient = ky.create({ baseUrl: API_BASE_URL, timeout: REQUEST_TIMEOUT, @@ -69,6 +155,9 @@ const requestClient = ky.create({ request.headers.set('Authorization', accessTokenFormatter(token)) }, ], + afterResponse: [ + handleUnauthorizedRetry, + ], beforeError: [ async ({error}) => { if (error instanceof HTTPError) { diff --git a/src/mock/index.ts b/src/mock/index.ts deleted file mode 100644 index e2b841e..0000000 --- a/src/mock/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { - AccountTableRow, - AddressOption, - AddAddressForm, - OrderRecord, - PointsRecord, - PointsRecordTone, -} from '@/types' - -export const accountAddressRowsMock: AccountTableRow[] = [ - { - name: 'Jia Jun', - phone: '+86 138 0000 1288', - address: 'No. 88 Century Avenue, Pudong New Area, Shanghai', - code: '200120', - action: 'Edit', - setting: 'Default', - }, - { - name: 'Alicia Tan', - phone: '+65 9123 4567', - address: '18 Robinson Road, Singapore', - code: '048547', - action: 'Edit', - setting: 'Optional', - }, - { - name: 'Marcus Lee', - phone: '+60 12 778 9911', - address: '27 Jalan Bukit Bintang, Kuala Lumpur', - code: '55100', - action: 'Edit', - setting: 'Optional', - }, -] - -export const initialAddressOptionsMock: AddressOption[] = [ - { - id: 'address-shanghai', - name: 'Jia Jun', - phone: '+86 138 0000 1288', - address: 'No. 88 Century Avenue, Pudong New Area, Shanghai', - isDefault: true, - }, - { - id: 'address-singapore', - name: 'Alicia Tan', - phone: '+65 9123 4567', - address: '18 Robinson Road, Singapore', - }, - { - id: 'address-kuala-lumpur', - name: 'Marcus Lee', - phone: '+60 12 778 9911', - address: '27 Jalan Bukit Bintang, Kuala Lumpur', - }, -] - -export const emptyAddressFormMock: AddAddressForm = { - name: '', - phone: '', - region: [], - detailedAddress: '', - isDefault: false, -} - -export const orderRecordsMock: OrderRecord[] = [ - { - id: 'order-1', - date: '2025-03-04', - time: '10:20', - category: 'Bonus', - title: 'Daily Rebate 50', - status: 'Issued', - points: '-500 points', - }, - { - id: 'order-2', - date: '2025-03-03', - time: '14:00', - category: 'Physical', - title: 'Weekly Bonus 200', - trackingNumber: 'SF1234567890', - status: 'Shipped', - points: '-1200 points', - }, - { - id: 'order-3', - date: '2025-03-02', - time: '09:15', - category: 'Withdrawal', - title: 'Wireless Earbuds', - status: 'Issued', - points: '-1000 points', - }, - { - id: 'order-4', - date: '2025-03-01', - time: '16:30', - category: 'Bonus', - title: 'Fitness Tracker', - status: 'Pending', - points: '-1800 points', - }, - { - id: 'order-5', - date: '2025-02-28', - time: '11:00', - category: 'Physical', - title: 'Withdraw 100', - status: 'Rejected', - points: '-2500 points', - }, -] - -export const pointsRecordsMock: PointsRecord[] = [ - { - id: 'points-1', - title: 'Bonus Redemption - Daily Rewards 50', - date: '2025-03-04', - time: '10:20', - amount: '-500', - tone: 'negative', - }, - { - id: 'points-2', - title: "Claim Yesterday's Protection Funds", - date: '2025-03-04', - time: '09:20', - amount: '+800', - tone: 'positive', - }, - { - id: 'points-3', - title: 'Physical Item Redemption - Bluetooth Headphones', - date: '2025-03-03', - time: '14:00', - amount: '-1200', - tone: 'negative', - }, - { - id: 'points-4', - title: "Claim Yesterday's Protection Funds", - date: '2025-03-03', - time: '09:00', - amount: '+700', - tone: 'positive', - }, - { - id: 'points-5', - title: 'Withdraw to Platform - 100', - date: '2025-03-02', - time: '09:15', - amount: '-1000', - tone: 'negative', - }, -] - -export const pointsRecordToneClassNameMock: Record = { - positive: 'bg-[#9BFFC0] text-[#176640]', - negative: 'bg-[#FF9BA4] text-[#7B2634]', -} diff --git a/src/types/index.ts b/src/types/index.ts index 4d33f33..946d9ca 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -40,11 +40,6 @@ export type TableColumn> = { render?: (value: T[keyof T], record: T, index: number) => ReactNode } -export type BorderlessTableProps> = { - columns: TableColumn[] - dataSource: T[] -} - export type QuickNavCardProps = { icon: LucideIcon label: string diff --git a/src/views/account/index.tsx b/src/views/account/index.tsx index 2ab4b70..cf9b191 100644 --- a/src/views/account/index.tsx +++ b/src/views/account/index.tsx @@ -1,7 +1,6 @@ import {useState} from 'react' import PageLayout from '@/components/layout' -import BorderlessTable from '@/components/table' import Modal from '@/components/modal' import Button from '@/components/button' import {Link} from 'react-router-dom' @@ -10,16 +9,6 @@ import {useAddressBook} from '@/features/addressBook' import {GoodsRedeemModal} from '@/features/goods' import {notifySuccess} from '@/features/notifications' import type {AddressListItem} from '@/types/address.type.ts' -import type {TableColumn} from '@/types' - -type AddressTableRow = { - id: string - name: string - phone: string - address: string - action: string - setting: string -} function AccountPage() { const addressBook = useAddressBook({autoLoad: true}) @@ -27,15 +16,6 @@ function AccountPage() { const [editingAddress, setEditingAddress] = useState(null) const [deleteTarget, setDeleteTarget] = useState(null) - const rows: AddressTableRow[] = addressBook.addresses.map((item) => ({ - id: String(item.id), - name: item.receiver_name, - phone: item.phone, - address: addressBook.addressOptions.find((option) => option.id === String(item.id))?.address ?? '', - action: 'Edit', - setting: item.default_setting === 1 ? 'Default' : 'Optional', - })) - const isAddressFormValid = addressBook.isAddressFormValid const handleOpenAddAddress = () => { @@ -76,63 +56,6 @@ function AccountPage() { } } - const columns: TableColumn[] = [ - { - label: 'Name', - key: 'name', - render: (value: string) =>
{value}
, - }, - { - label: 'Phone / Mobile', - key: 'phone', - render: (value: string) =>
{value}
, - }, - { - label: 'Address', - key: 'address', - render: (value: string) =>
{value}
, - }, - { - label: 'Action', - key: 'action', - render: (_value: string, _record: AddressTableRow, index: number) => ( -
- - -
- ), - }, - { - label: 'Default Setting', - key: 'setting', - render: (value: string) => ( -
- {value} -
- ), - }, - ] - return ( ) : !addressBook.addresses.length ? (
- No shipping address found. Add one to start redeeming physical rewards. + No shipping address found. Add one.
) : ( - <> -
- {rows.map((item, index) => ( -
+
+ {addressBook.addresses.map((address) => { + const addressId = String(address.id) + const addressText = addressBook.addressOptions.find((option) => option.id === addressId)?.address ?? '' + const isDefault = address.default_setting === 1 + + return ( +
-
{item.name}
-
{item.phone}
+
{address.receiver_name}
+
{address.phone}
- {item.setting === 'Default' ? ( + {isDefault ? ( - ) : item.setting} + ) : 'Optional'}
Address
-
{item.address}
+
{addressText}
+
+
+ +
- -
- ))} -
- -
- -
- + )})} +
)}
diff --git a/src/views/home/index.tsx b/src/views/home/index.tsx index b8b814d..ce91052 100644 --- a/src/views/home/index.tsx +++ b/src/views/home/index.tsx @@ -132,11 +132,11 @@ function HomePage() {
-
+
-
Claimable +
Claimable Points
-
Daily Claim +
Daily Claim Limit
Claimed: {assetsInfo?.today_claimed || 0} / {assetsInfo?.today_limit || 0}
+ className="mt-[10px] text-[13px] text-white/68">Claimed: {assetsInfo?.today_claimed || 0} / {assetsInfo?.today_limit || 0} +
-
+ className="liquid-glass-bg flex min-h-[100px] flex-col justify-between p-[14px] sm:p-[16px]"> +
-
Available for - Withdrawal +
Available for + Withdrawal (Cash)
{assetsInfo?.withdrawable_cash || 0} CNY
+ className="mt-[10px] text-[32px] font-semibold leading-none text-white">{assetsInfo?.withdrawable_cash || 0} + CNY +
@@ -266,7 +271,8 @@ function HomePage() { >
- After converting the points to be collected into usable points, they can be redeemed or withdrawn. Are you sure to claim it? + After converting the points to be collected into usable points, they can be redeemed or withdrawn. + Are you sure to claim it?
diff --git a/src/views/record/index.tsx b/src/views/record/index.tsx index 0b74d56..5f66c18 100644 --- a/src/views/record/index.tsx +++ b/src/views/record/index.tsx @@ -1,4 +1,4 @@ -import {useEffect, useState} from 'react' +import {useState} from 'react' import {useQuery} from '@tanstack/react-query' import PageLayout from '@/components/layout' @@ -274,11 +274,11 @@ function TabButton({ active, label, icon: Icon, onClick }: TabButtonProps) { function OrderCard({ record, onOpenDetails }: OrderCardProps) { return (
-
+
{record.date} {record.time} • {record.category}
-
-
+
+
{record.title}
{record.trackingNumber ? (
@@ -295,7 +295,7 @@ function OrderCard({ record, onOpenDetails }: OrderCardProps) {
-
+
-
+
{record.title}
-
+
{record.date}   {record.time}
@@ -427,10 +427,6 @@ function RecordPage() { setSelectedOrder(null) } - useEffect(() => { - setSelectedOrder(null) - }, [tab]) - return ( setTab('order')} + onClick={() => { + setSelectedOrder(null) + setTab('order') + }} /> setTab('record')} + onClick={() => { + setSelectedOrder(null) + setTab('record') + }} />
@@ -487,7 +489,7 @@ function RecordPage() { } > {selectedOrder ? ( -
+
{[ { label: 'Order Number', value: selectedOrder.orderNumber ?? '--' }, { label: 'Order Time', value: `${selectedOrder.date} ${selectedOrder.time}` },