feat:项目界面补充
This commit is contained in:
@@ -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<AddressListItem | null>(null)
|
||||
const [deleteTarget, setDeleteTarget] = useState<AddressListItem | null>(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<AddressTableRow>[] = [
|
||||
{
|
||||
label: 'Name',
|
||||
key: 'name',
|
||||
render: (value: string) => <div className="font-medium text-white">{value}</div>,
|
||||
},
|
||||
{
|
||||
label: 'Phone / Mobile',
|
||||
key: 'phone',
|
||||
render: (value: string) => <div className="text-white/72">{value}</div>,
|
||||
},
|
||||
{
|
||||
label: 'Address',
|
||||
key: 'address',
|
||||
render: (value: string) => <div className="max-w-[280px] text-white/72">{value}</div>,
|
||||
},
|
||||
{
|
||||
label: 'Action',
|
||||
key: 'action',
|
||||
render: (_value: string, _record: AddressTableRow, index: number) => (
|
||||
<div className="flex items-center gap-[8px]">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-[6px] rounded-full bg-white/6 px-[10px] py-[6px] text-[12px] text-white/82 transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
|
||||
onClick={() => handleOpenEditAddress(addressBook.addresses[index])}
|
||||
>
|
||||
<PencilLine className="h-[12px] w-[12px]" aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-[6px] rounded-full bg-[#4B1818]/70 px-[10px] py-[6px] text-[12px] text-[#FFB1B1] transition-colors hover:bg-[#612121]/80 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
|
||||
onClick={() => setDeleteTarget(addressBook.addresses[index])}
|
||||
>
|
||||
<Trash2 className="h-[12px] w-[12px]" aria-hidden="true" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Default Setting',
|
||||
key: 'setting',
|
||||
render: (value: string) => (
|
||||
<div
|
||||
className={`inline-flex rounded-full px-[10px] py-[5px] text-[12px] ${
|
||||
value === 'Default'
|
||||
? 'bg-[#FA6A00]/14 text-[#FFB36D]'
|
||||
: 'bg-white/6 text-white/62'
|
||||
}`}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<PageLayout contentClassName="mx-auto flex min-h-screen w-full max-w-[1120px] flex-col px-4 pb-8 sm:px-6 lg:px-8">
|
||||
<Link
|
||||
@@ -173,61 +96,62 @@ function AccountPage() {
|
||||
</div>
|
||||
) : !addressBook.addresses.length ? (
|
||||
<div className="liquid-glass-bg px-[16px] py-[18px] text-[14px] text-white/60">
|
||||
No shipping address found. Add one to start redeeming physical rewards.
|
||||
No shipping address found. Add one.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-[12px] lg:hidden">
|
||||
{rows.map((item, index) => (
|
||||
<div key={item.id} className="liquid-glass-bg p-[14px]">
|
||||
<div className="grid grid-cols-1 gap-[12px]">
|
||||
{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 (
|
||||
<div key={addressId} className="liquid-glass-bg p-[14px]">
|
||||
<div className="flex items-start justify-between gap-[12px]">
|
||||
<div>
|
||||
<div className="text-[16px] font-semibold text-white">{item.name}</div>
|
||||
<div className="mt-[4px] text-[13px] text-white/62">{item.phone}</div>
|
||||
<div className="text-[16px] font-semibold text-white">{address.receiver_name}</div>
|
||||
<div className="mt-[4px] text-[13px] text-white/62">{address.phone}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`inline-flex rounded-full px-[10px] py-[5px] text-[12px] ${
|
||||
item.setting === 'Default'
|
||||
isDefault
|
||||
? 'bg-[#FA6A00]/14 text-[#FFB36D]'
|
||||
: 'bg-white/6 text-white/62'
|
||||
}`}
|
||||
>
|
||||
{item.setting === 'Default' ? (
|
||||
{isDefault ? (
|
||||
<span className="inline-flex items-center gap-[5px]">
|
||||
<BadgeCheck className="h-[12px] w-[12px]" aria-hidden="true" />
|
||||
{item.setting}
|
||||
Default
|
||||
</span>
|
||||
) : item.setting}
|
||||
) : 'Optional'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-[12px] rounded-[10px] bg-black/12 p-[12px]">
|
||||
<div className="text-[12px] uppercase tracking-[0.08em] text-white/44">Address</div>
|
||||
<div className="mt-[6px] text-[13px] leading-[1.6] text-white/78">{item.address}</div>
|
||||
<div className="mt-[6px] text-[13px] leading-[1.6] text-white/78">{addressText}</div>
|
||||
</div>
|
||||
<div className="mt-[12px] flex justify-end gap-[10px]">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-[6px] rounded-full bg-white/6 px-[10px] py-[6px] text-[12px] text-white/82 transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
|
||||
onClick={() => handleOpenEditAddress(address)}
|
||||
>
|
||||
<PencilLine className="h-[12px] w-[12px]" aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-[6px] rounded-full bg-[#4B1818]/70 px-[10px] py-[6px] text-[12px] text-[#FFB1B1] transition-colors hover:bg-[#612121]/80 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
|
||||
onClick={() => setDeleteTarget(address)}
|
||||
>
|
||||
<Trash2 className="h-[12px] w-[12px]" aria-hidden="true" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-[12px] inline-flex items-center gap-[6px] rounded-full bg-white/6 px-[10px] py-[6px] text-[12px] text-white/82 transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
|
||||
onClick={() => handleOpenEditAddress(addressBook.addresses[index])}
|
||||
>
|
||||
<PencilLine className="h-[12px] w-[12px]" aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-[10px] inline-flex items-center gap-[6px] rounded-full bg-[#4B1818]/70 px-[10px] py-[6px] text-[12px] text-[#FFB1B1] transition-colors hover:bg-[#612121]/80 focus-visible:ring-2 focus-visible:ring-[#FE9F00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#08070E]"
|
||||
onClick={() => setDeleteTarget(addressBook.addresses[index])}
|
||||
>
|
||||
<Trash2 className="h-[12px] w-[12px]" aria-hidden="true" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="hidden lg:block">
|
||||
<BorderlessTable columns={columns} dataSource={rows} />
|
||||
</div>
|
||||
</>
|
||||
)})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -132,11 +132,11 @@ function HomePage() {
|
||||
|
||||
<div className="mt-[4px]">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-stretch">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:w-[544px] lg:shrink-0">
|
||||
<div className="grid grid-cols-2 gap-3 lg:w-[544px] lg:shrink-0">
|
||||
<div className="liquid-glass-bg flex min-h-[167px] flex-col justify-between p-[14px]">
|
||||
<div className="flex items-start justify-between gap-[12px]">
|
||||
<div>
|
||||
<div className="text-[13px] uppercase tracking-[0.16em] text-white/58">Claimable
|
||||
<div className="text-[13px] tracking-[0.16em] text-white/58">Claimable
|
||||
Points
|
||||
</div>
|
||||
<div
|
||||
@@ -154,7 +154,7 @@ function HomePage() {
|
||||
<div className="liquid-glass-bg flex min-h-[167px] flex-col justify-around p-[14px]">
|
||||
<div className="flex items-start justify-between gap-[12px]">
|
||||
<div>
|
||||
<div className="text-[13px] uppercase tracking-[0.16em] text-white/58">Daily Claim
|
||||
<div className="text-[13px] tracking-[0.16em] text-white/58">Daily Claim
|
||||
Limit
|
||||
</div>
|
||||
<div
|
||||
@@ -178,20 +178,25 @@ function HomePage() {
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
className="mt-[10px] text-[13px] text-white/68">Claimed: <span className={'text-[#FE9C00]'}>{assetsInfo?.today_claimed || 0}</span> / {assetsInfo?.today_limit || 0}</div>
|
||||
className="mt-[10px] text-[13px] text-white/68">Claimed: <span
|
||||
className={'text-[#FE9C00]'}>{assetsInfo?.today_claimed || 0}</span> / {assetsInfo?.today_limit || 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-3">
|
||||
<div
|
||||
className="liquid-glass-bg flex min-h-[109px] flex-col justify-between p-[14px] sm:p-[16px]">
|
||||
<div className="flex items-start justify-between gap-[12px]">
|
||||
className="liquid-glass-bg flex min-h-[100px] flex-col justify-between p-[14px] sm:p-[16px]">
|
||||
<div className="flex items-center justify-between gap-[12px]">
|
||||
<div>
|
||||
<div className="text-[13px] uppercase tracking-[0.16em] text-white/58">Available for
|
||||
Withdrawal
|
||||
<div className="text-[13px] tracking-[0.16em] text-white/58">Available for
|
||||
Withdrawal (Cash)
|
||||
</div>
|
||||
<div
|
||||
className="mt-[10px] text-[32px] font-semibold leading-none text-white">{assetsInfo?.withdrawable_cash || 0} CNY</div>
|
||||
className="mt-[10px] text-[32px] font-semibold leading-none text-white">{assetsInfo?.withdrawable_cash || 0}
|
||||
<span
|
||||
className="text-[13px] tracking-[0.16em] text-white/58 ml-[10px]">CNY</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex h-[38px] w-[38px] items-center justify-center rounded-[12px] bg-[#FA6A00]/16 text-[#FE9F00]">
|
||||
@@ -266,7 +271,8 @@ function HomePage() {
|
||||
>
|
||||
<div
|
||||
className="rounded-[12px] bg-[#1C1818]/82 px-[14px] py-[18px] text-[17px] leading-[1.65] text-white/92 shadow-[0_10px_30px_rgba(0,0,0,0.2)]">
|
||||
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?
|
||||
</div>
|
||||
</Modal>
|
||||
</PageLayout>
|
||||
|
||||
@@ -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 (
|
||||
<div className="overflow-hidden rounded-[12px] shadow-[0_10px_30px_rgba(0,0,0,0.24)]">
|
||||
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[13px] text-white sm:text-[14px]">
|
||||
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[14px] text-white">
|
||||
{record.date} {record.time} • {record.category}
|
||||
</div>
|
||||
<div className="liquid-glass-bg !rounded-t-none flex flex-col gap-[14px] px-[12px] py-[14px] sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="liquid-glass-bg !rounded-t-none flex items-center justify-between gap-[14px] px-[12px] py-[14px]">
|
||||
<div className="min-w-0 flex-1 pr-[16px]">
|
||||
<div className="text-[16px] font-medium text-white">{record.title}</div>
|
||||
{record.trackingNumber ? (
|
||||
<div className="mt-[4px] text-[13px] text-white/45">
|
||||
@@ -295,7 +295,7 @@ function OrderCard({ record, onOpenDetails }: OrderCardProps) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center justify-between gap-[10px] sm:flex-col sm:items-end">
|
||||
<div className="flex shrink-0 flex-col items-end gap-[10px]">
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-[6px] px-[8px] py-[3px] text-[11px] leading-none',
|
||||
@@ -314,10 +314,10 @@ function OrderCard({ record, onOpenDetails }: OrderCardProps) {
|
||||
function PointsCard({ record }: PointsCardProps) {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-[12px] shadow-[0_10px_30px_rgba(0,0,0,0.24)]">
|
||||
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[14px] text-white sm:text-[15px]">
|
||||
<div className="bg-linear-to-r from-[#F96C02] to-[#FE9F00] px-[12px] py-[9px] text-[15px] text-white">
|
||||
{record.title}
|
||||
</div>
|
||||
<div className="liquid-glass-bg !rounded-t-none flex flex-col gap-[10px] px-[12px] py-[18px] sm:flex-row sm:items-center sm:justify-between sm:py-[22px]">
|
||||
<div className="liquid-glass-bg !rounded-t-none flex items-center justify-between px-[12px] py-[22px]">
|
||||
<div className="text-[13px] text-white/40">
|
||||
{record.date} {record.time}
|
||||
</div>
|
||||
@@ -427,10 +427,6 @@ function RecordPage() {
|
||||
setSelectedOrder(null)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedOrder(null)
|
||||
}, [tab])
|
||||
|
||||
return (
|
||||
<PageLayout contentClassName="mx-auto flex min-h-screen w-full max-w-[980px] flex-col px-4 pb-8 sm:px-6 lg:px-8">
|
||||
<Link
|
||||
@@ -452,13 +448,19 @@ function RecordPage() {
|
||||
active={tab === 'order'}
|
||||
icon={PackageSearch}
|
||||
label="My Orders"
|
||||
onClick={() => setTab('order')}
|
||||
onClick={() => {
|
||||
setSelectedOrder(null)
|
||||
setTab('order')
|
||||
}}
|
||||
/>
|
||||
<TabButton
|
||||
active={tab === 'record'}
|
||||
icon={Coins}
|
||||
label="Points Record"
|
||||
onClick={() => setTab('record')}
|
||||
onClick={() => {
|
||||
setSelectedOrder(null)
|
||||
setTab('record')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -487,7 +489,7 @@ function RecordPage() {
|
||||
}
|
||||
>
|
||||
{selectedOrder ? (
|
||||
<div className="rounded-[10px] bg-[#1C1818]/78 px-[12px] py-[6px]">
|
||||
<div className="mt-[10px] rounded-[10px] bg-[#1C1818]/78 px-[12px] py-[6px]">
|
||||
{[
|
||||
{ label: 'Order Number', value: selectedOrder.orderNumber ?? '--' },
|
||||
{ label: 'Order Time', value: `${selectedOrder.date} ${selectedOrder.time}` },
|
||||
|
||||
Reference in New Issue
Block a user