import { Link, useNavigate } from "@tanstack/react-router"; import { Alert, AlertDescription } from "@workspace/ui/components/alert"; import { Badge } from "@workspace/ui/components/badge"; import { Button } from "@workspace/ui/components/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@workspace/ui/components/dropdown-menu"; import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; import { ProTable, type ProTableActions, } from "@workspace/ui/composed/pro-table/pro-table"; import { createUserSubscribe, deleteUserSubscribe, getFamilyList, getUserSubscribe, resetUserSubscribeToken, toggleUserSubscribeStatus, updateUserSubscribe, } from "@workspace/ui/services/admin/user"; import { Info } from "lucide-react"; import { useCallback, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Display } from "@/components/display"; import { useGlobalStore } from "@/stores/global"; import { formatDate } from "@/utils/common"; import { SubscriptionDetail } from "./subscription-detail"; import { SubscriptionForm } from "./subscription-form"; interface SharedInfo { ownerUserId: number; familyId: number; } export default function UserSubscription({ userId }: { userId: number }) { const { t } = useTranslation("user"); const [loading, setLoading] = useState(false); const ref = useRef(null); const [sharedInfo, setSharedInfo] = useState(null); const navigate = useNavigate(); const request = useCallback( async (pagination: { page: number; size: number }) => { // 1. Fetch user's own subscriptions const { data } = await getUserSubscribe({ user_id: userId, ...pagination, }); const list = data.data?.list || []; const total = data.data?.total || 0; // 2. If user has own subscriptions, show them directly if (list.length > 0) { setSharedInfo(null); return { list, total }; } // 3. Check if user belongs to a device group try { const { data: familyData } = await getFamilyList({ user_id: userId, page: 1, size: 1, }); const familyList = familyData.data?.list || []; const family = familyList.find( (f) => f.owner_user_id !== userId && f.status === "active" ); if (family) { // 4. Fetch owner's subscriptions const { data: ownerData } = await getUserSubscribe({ user_id: family.owner_user_id, ...pagination, }); const ownerList = ownerData.data?.list || []; const ownerTotal = ownerData.data?.total || 0; if (ownerList.length > 0) { setSharedInfo({ ownerUserId: family.owner_user_id, familyId: family.family_id, }); return { list: ownerList, total: ownerTotal }; } } } catch { // Silently fall through to show empty list } setSharedInfo(null); return { list: [], total: 0 }; }, [userId] ); const isSharedView = !!sharedInfo; return (
{isSharedView && ( {t("sharedSubscriptionInfo", { defaultValue: "This user is a device group member. Showing shared subscriptions from owner (ID: {{ownerId}})", ownerId: sharedInfo.ownerUserId, })} )} > action={ref} actions={{ render: (row) => isSharedView ? [ {t("sharedSubscription", "Shared")} , ref.current?.refresh()} row={row} token={row.token} userId={sharedInfo!.ownerUserId} />, ] : [ { setLoading(true); await updateUserSubscribe({ user_id: Number(userId), user_subscribe_id: row.id, ...values, }); toast.success(t("updateSuccess", "Updated successfully")); ref.current?.refresh(); setLoading(false); return true; }} title={t("editSubscription", "Edit Subscription")} trigger={t("edit", "Edit")} />, ref.current?.refresh()} row={row} token={row.token} userId={userId} />, ], }} columns={[ { accessorKey: "id", header: "ID", }, { accessorKey: "name", header: t("subscriptionName", "Subscription Name"), cell: ({ row }) => ( {row.original.subscribe.name} {isSharedView && ( {t("sharedSubscription", "Shared")} )} ), }, { accessorKey: "status", header: t("status", "Status"), cell: ({ row }) => { const status = row.getValue("status") as number; const expireTime = row.original.expire_time; // 如果过期时间为0,说明是永久订阅,应该显示为激活状态 const displayStatus = status === 3 && expireTime === 0 ? 1 : status; const statusMap: Record< number, { label: string; variant: "default" | "secondary" | "destructive" | "outline"; } > = { 0: { label: t("statusPending", "Pending"), variant: "outline", }, 1: { label: t("statusActive", "Active"), variant: "default" }, 2: { label: t("statusFinished", "Finished"), variant: "secondary", }, 3: { label: t("statusExpired", "Expired"), variant: "destructive", }, 4: { label: t("statusDeducted", "Deducted"), variant: "secondary", }, 5: { label: t("statusStopped", "Stopped"), variant: "destructive", }, }; const statusInfo = statusMap[displayStatus] || { label: "Unknown", variant: "outline", }; return ( {statusInfo.label} ); }, }, { accessorKey: "upload", header: t("upload", "Upload"), cell: ({ row }) => ( ), }, { accessorKey: "download", header: t("download", "Download"), cell: ({ row }) => ( ), }, { accessorKey: "traffic", header: t("totalTraffic", "Total Traffic"), cell: ({ row }) => ( ), }, { id: "remaining_traffic", header: t("remainingTraffic", "Remaining Traffic"), cell: ({ row }) => { const upload = row.original.upload || 0; const download = row.original.download || 0; const totalTraffic = row.original.traffic || 0; const remainingTraffic = totalTraffic > 0 ? totalTraffic - upload - download : 0; return ( ); }, }, { accessorKey: "speed_limit", header: t("speedLimit", "Speed Limit"), cell: ({ row }) => { const speed = row.original?.subscribe?.speed_limit; return ; }, }, { accessorKey: "device_limit", header: t("deviceLimit", "Device Limit"), cell: ({ row }) => { const limit = row.original?.subscribe?.device_limit; return ; }, }, { accessorKey: "reset_time", header: t("resetTime", "Reset Time"), cell: ({ row }) => ( ), }, { accessorKey: "expire_time", header: t("expireTime", "Expire Time"), cell: ({ row }) => { const expireTime = row.getValue("expire_time") as number; return expireTime && expireTime !== 0 ? formatDate(expireTime) : t("permanent", "Permanent"); }, }, { accessorKey: "created_at", header: t("createdAt", "Created At"), cell: ({ row }) => formatDate(row.getValue("created_at")), }, ]} header={{ title: isSharedView ? t("sharedSubscriptionList", "Shared Subscription List") : t("subscriptionList", "Subscription List"), toolbar: isSharedView ? undefined : ( { setLoading(true); await createUserSubscribe({ user_id: Number(userId), ...values, }); toast.success(t("createSuccess", "Created successfully")); ref.current?.refresh(); setLoading(false); return true; }} title={t("createSubscription", "Create Subscription")} trigger={t("add", "Add")} /> ), }} request={request} />
); } function RowReadOnlyActions({ userId, row, token, refresh, }: { userId: number; row: API.UserSubscribe; token: string; refresh?: () => void; }) { const { t } = useTranslation("user"); const triggerRef = useRef(null); const deleteRef = useRef(null); const { getUserSubscribe: getUserSubscribeUrls } = useGlobalStore(); return (
{ e.preventDefault(); await navigator.clipboard.writeText( getUserSubscribeUrls(row.short, token)[0] || "" ); toast.success(t("copySuccess", "Copied successfully")); }} > {t("copySubscription", "Copy Subscription")} {t("subscriptionLogs", "Subscription Logs")} {t("trafficStats", "Traffic Stats")} {t("trafficDetails", "Traffic Details")} { e.preventDefault(); triggerRef.current?.click(); }} > {t("onlineDevices", "Online Devices")} { e.preventDefault(); deleteRef.current?.click(); }} > {t("delete", "Delete")} { await deleteUserSubscribe({ user_subscribe_id: String(row.id) }); toast.success(t("deleteSuccess", "Deleted successfully")); refresh?.(); }} title={t("confirmDelete", "Confirm Delete")} trigger={
); } function RowMoreActions({ userId, row, token, refresh, }: { userId: number; row: API.UserSubscribe; token: string; refresh: () => void; }) { const triggerRef = useRef(null); const resetTokenRef = useRef(null); const toggleStatusRef = useRef(null); const deleteRef = useRef(null); const { t } = useTranslation("user"); const { getUserSubscribe: getUserSubscribeUrls } = useGlobalStore(); return (
{ e.preventDefault(); await navigator.clipboard.writeText( getUserSubscribeUrls(row.short, token)[0] || "" ); toast.success(t("copySuccess", "Copied successfully")); }} > {t("copySubscription", "Copy Subscription")} { e.preventDefault(); resetTokenRef.current?.click(); }} > {t("resetToken", "Reset Subscription Address")} { e.preventDefault(); toggleStatusRef.current?.click(); }} > {row.status === 5 ? t("resumeSubscribe", "Resume Subscription") : t("stopSubscribe", "Stop Subscription")} { e.preventDefault(); deleteRef.current?.click(); }} > {t("delete", "Delete")} {t("subscriptionLogs", "Subscription Logs")} {t("resetLogs", "Reset Logs")} {t("trafficStats", "Traffic Stats")} {t("trafficDetails", "Traffic Details")} { e.preventDefault(); triggerRef.current?.click(); }} > {t("onlineDevices", "Online Devices")} {/* Hidden triggers for confirm dialogs */} { await resetUserSubscribeToken({ user_subscribe_id: row.id, }); toast.success( t("resetTokenSuccess", "Subscription address reset successfully") ); refresh(); }} title={t("confirmResetToken", "Confirm Reset Subscription Address")} trigger={
); }