diff --git a/apps/admin/app/dashboard/user/page.tsx b/apps/admin/app/dashboard/user/page.tsx index 74d0ec3..3a76de8 100644 --- a/apps/admin/app/dashboard/user/page.tsx +++ b/apps/admin/app/dashboard/user/page.tsx @@ -1,6 +1,5 @@ 'use client'; -import { Display } from '@/components/display'; import { ProTable, ProTableActions } from '@/components/pro-table'; import { createUser, @@ -12,7 +11,6 @@ import { import { useSubscribe } from '@/store/subscribe'; import { formatDate } from '@/utils/common'; import { useQuery } from '@tanstack/react-query'; -import { Badge } from '@workspace/ui/components/badge'; import { Button } from '@workspace/ui/components/button'; import { DropdownMenu, @@ -20,6 +18,13 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@workspace/ui/components/dropdown-menu'; +import { Input } from '@workspace/ui/components/input'; +import { + Popover, + PopoverClose, + PopoverContent, + PopoverTrigger, +} from '@workspace/ui/components/popover'; import { ScrollArea } from '@workspace/ui/components/scroll-area'; import { Sheet, @@ -31,6 +36,7 @@ import { import { Switch } from '@workspace/ui/components/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs'; import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button'; +import { FilePenLine } from 'lucide-react'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; @@ -38,11 +44,69 @@ import { useRef, useState } from 'react'; import { toast } from 'sonner'; import { UserDetail } from './user-detail'; import UserForm from './user-form'; -import { AuthMethodsForm } from './user-profile/auth-methods-form'; import { BasicInfoForm } from './user-profile/basic-info-form'; import { NotifySettingsForm } from './user-profile/notify-settings-form'; import UserSubscription from './user-subscription'; +function getDeviceTypeInfo(userAgent = '') { + let deviceType = 'Unknown'; + const ua = userAgent.toLowerCase(); + + if (ua.includes('android')) { + deviceType = 'Android'; + } else if (ua.includes('iphone') || ua.includes('ios')) { + deviceType = 'iPhone'; + } else if (ua.includes('ipad')) { + deviceType = 'iPad'; + } else if (ua.includes('mac os') || ua.includes('mac')) { + deviceType = 'Mac'; + } else if (ua.includes('windows')) { + deviceType = 'Windows'; + } else if (ua.includes('linux')) { + deviceType = 'Linux'; + } + + return { deviceType }; +} + +// 为 RemarkForm 组件定义 props 类型 +interface RemarkFormProps { + initialRemark?: string | null; + onSave: (remark: string) => void; + CloseComponent: React.ComponentType<{ asChild?: boolean; children: React.ReactNode }>; +} + +// 新的子组件,在管理它自己的备注状态 +const RemarkForm: React.FC = ({ onSave, initialRemark, CloseComponent }) => { + const [remark, setRemark] = useState(initialRemark ?? ''); + + const handleInputChange = (event: React.ChangeEvent) => { + setRemark(event.target.value); + }; + + const handleSaveClick = () => { + onSave(remark); + }; + + return ( + <> +
备注
+ + + + + + ); +}; + export default function Page() { const t = useTranslations('user'); const [loading, setLoading] = useState(false); @@ -56,6 +120,7 @@ export default function Page() { user_id: sp.get('user_id') || undefined, subscribe_id: sp.get('subscribe_id') || undefined, user_subscribe_id: sp.get('user_subscribe_id') || undefined, + device_id: sp.get('device_id') || undefined, }; return ( @@ -128,20 +193,96 @@ export default function Page() { }, { accessorKey: 'auth_methods', - header: t('userName'), + header: '绑定邮箱', cell: ({ row }) => { - const method = row.original.auth_methods?.[0]; + const method = row.original.auth_methods; return (
- - {method?.auth_type} - - {method?.auth_identifier} + + +
+ {method?.find((v) => v.auth_type === 'email')?.auth_identifier || '待绑定'} + {row.original?.remark ? `(${row.original.remark})` : ''} + +
+
+ + { + const { + auth_methods, + user_devices, + enable_balance_notify, + enable_login_notify, + enable_subscribe_notify, + enable_trade_notify, + updated_at, + created_at, + id, + ...rest + } = row.original; + await updateUserBasicInfo({ + user_id: id, + ...rest, + remark, + } as unknown as API.UpdateUserBasiceInfoRequest); + toast.success(t('updateSuccess')); + ref.current?.refresh(); + }} + /> + +
); }, }, { + accessorKey: 'user_devices', + header: '绑定设备', + cell: ({ row }) => { + const devices = row?.original.user_devices ?? []; + + return ( +
+ {devices.map((v, index) => { + const { deviceType } = getDeviceTypeInfo(v.user_agent); + + return ( +
+
+
+ ID:{v.id}({deviceType}) +
+
+ + {index !== devices.length - 1 && ( +
+ )} +
+ ); + })} +
+ ); + }, + }, + /*{ accessorKey: 'balance', header: t('balance'), cell: ({ row }) => , @@ -155,7 +296,7 @@ export default function Page() { accessorKey: 'commission', header: t('commission'), cell: ({ row }) => , - }, + },*/ { accessorKey: 'refer_code', header: t('inviteCode'), @@ -203,6 +344,10 @@ export default function Page() { key: 'user_subscribe_id', placeholder: t('subscriptionId'), }, + { + key: 'device_id', + placeholder: '设备id', + }, ]} actions={{ render: (row) => { @@ -281,7 +426,7 @@ function ProfileSheet({ userId }: { userId: number }) { {t('basicInfoTitle')} {t('notifySettingsTitle')} - {t('authMethodsTitle')} + {/*{t('authMethodsTitle')}*/} @@ -289,9 +434,9 @@ function ProfileSheet({ userId }: { userId: number }) { - + {/* - + */} )} diff --git a/apps/admin/app/dashboard/user/user-detail.tsx b/apps/admin/app/dashboard/user/user-detail.tsx index bd0a260..638ea3e 100644 --- a/apps/admin/app/dashboard/user/user-detail.tsx +++ b/apps/admin/app/dashboard/user/user-detail.tsx @@ -148,7 +148,8 @@ export function UserDetail({ id }: { id: number }) { const identifier = data?.auth_methods.find((m) => m.auth_type === 'email')?.auth_identifier || - data?.auth_methods[0]?.auth_identifier; + `设备Id:${data?.user_devices[0]?.id}` || + '账号不存在'; return ( diff --git a/apps/admin/services/admin/typings.d.ts b/apps/admin/services/admin/typings.d.ts index 0d3f11b..a76eeb4 100644 --- a/apps/admin/services/admin/typings.d.ts +++ b/apps/admin/services/admin/typings.d.ts @@ -2360,6 +2360,7 @@ declare namespace API { id: number; avatar: string; balance: number; + remark: string; commission: number; referral_percentage: number; only_first_purchase: boolean; diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index c1296e1..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config } from '@workspace/eslint-config/base'; - -/** @type {import("eslint").Linter.Config} */ -export default config; diff --git a/packages/ui/src/components/popover.tsx b/packages/ui/src/components/popover.tsx index a5dbddb..6e8dedc 100644 --- a/packages/ui/src/components/popover.tsx +++ b/packages/ui/src/components/popover.tsx @@ -10,6 +10,7 @@ const Popover = PopoverPrimitive.Root; const PopoverTrigger = PopoverPrimitive.Trigger; const PopoverAnchor = PopoverPrimitive.Anchor; +const PopoverClose = PopoverPrimitive.Close; const PopoverContent = React.forwardRef< React.ElementRef, @@ -30,4 +31,4 @@ const PopoverContent = React.forwardRef< )); PopoverContent.displayName = PopoverPrimitive.Content.displayName; -export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }; +export { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger }; diff --git a/packages/ui/src/custom-components/pro-table/pro-table.tsx b/packages/ui/src/custom-components/pro-table/pro-table.tsx index 5a395c2..aabb4b5 100644 --- a/packages/ui/src/custom-components/pro-table/pro-table.tsx +++ b/packages/ui/src/custom-components/pro-table/pro-table.tsx @@ -109,7 +109,7 @@ export function ProTable< const [rowCount, setRowCount] = useState(0); const [pagination, setPagination] = useState({ pageIndex: 0, - pageSize: 10, + pageSize: 200, }); const loading = useRef(false);