Files
accounts-manager-web/components/accounts/account-details.tsx
Your Name 0b95ca36f1 Initial project setup with Next.js accounts manager
- Set up Next.js project structure
- Added UI components and styling
- Configured package dependencies
- Added feature documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-23 01:40:14 +08:00

246 lines
7.9 KiB
TypeScript

"use client";
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
import { Account, StatsOverview } from '@/lib/types';
import { PlatformSelector, OwnerSelector, StatusSelector } from '@/components/shared';
interface AccountDetailsProps {
account: Account | null;
open: boolean;
mode: 'view' | 'edit';
stats: StatsOverview | null;
onOpenChange: (open: boolean) => void;
onSave?: (account: Account) => Promise<void>;
onDelete?: (account: Account) => Promise<void>;
}
export function AccountDetails({
account,
open,
mode,
stats,
onOpenChange,
onSave,
onDelete
}: AccountDetailsProps) {
const [editData, setEditData] = useState<Partial<Account>>({});
const [loading, setLoading] = useState(false);
const isEditing = mode === 'edit';
const handleSave = async () => {
if (!account || !onSave) return;
setLoading(true);
try {
const updatedAccount = { ...account, ...editData };
await onSave(updatedAccount);
onOpenChange(false);
setEditData({});
} finally {
setLoading(false);
}
};
const handleDelete = async () => {
if (!account || !onDelete) return;
if (!confirm('确认删除此账户?')) return;
setLoading(true);
try {
await onDelete(account);
onOpenChange(false);
} finally {
setLoading(false);
}
};
const getStatusBadge = (status: string) => {
switch (status) {
case 'available':
return <Badge variant="default" className="bg-green-500"></Badge>;
case 'locked':
return <Badge variant="secondary"></Badge>;
case 'banned':
return <Badge variant="destructive"></Badge>;
default:
return <Badge variant="outline">{status}</Badge>;
}
};
const formatData = (data: string) => {
try {
const parsed = JSON.parse(data);
return JSON.stringify(parsed, null, 2);
} catch {
return data;
}
};
if (!account) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{isEditing ? '编辑账户' : '账户详情'}
</DialogTitle>
<DialogDescription>
ID: {account.id}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium"></label>
{isEditing ? (
<PlatformSelector
value={editData.platform ?? account.platform}
onValueChange={(value) => setEditData({...editData, platform: value})}
stats={stats}
placeholder="选择平台"
inputPlaceholder="或直接输入平台名称"
/>
) : (
<div className="p-2 bg-muted rounded">{account.platform}</div>
)}
</div>
<div>
<label className="text-sm font-medium">ID</label>
{isEditing ? (
<Input
value={editData.customId ?? account.customId}
onChange={(e) => setEditData({...editData, customId: e.target.value})}
/>
) : (
<div className="p-2 bg-muted rounded font-mono">{account.customId}</div>
)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium">ID</label>
{isEditing ? (
<OwnerSelector
value={editData.ownerId ?? account.ownerId}
onValueChange={(value) => setEditData({...editData, ownerId: value})}
stats={stats}
placeholder="选择所有者"
inputPlaceholder="或直接输入所有者ID"
/>
) : (
<div className="p-2 bg-muted rounded">{account.ownerId}</div>
)}
</div>
<div>
<label className="text-sm font-medium"></label>
{isEditing ? (
<StatusSelector
value={editData.status ?? account.status}
onValueChange={(value) => setEditData({...editData, status: value})}
stats={stats}
placeholder="选择状态"
inputPlaceholder="或直接输入状态"
/>
) : (
<div className="p-2">{getStatusBadge(account.status)}</div>
)}
</div>
</div>
<div>
<label className="text-sm font-medium"></label>
{isEditing ? (
<Textarea
value={editData.data ?? account.data}
onChange={(e) => setEditData({...editData, data: e.target.value})}
className="min-h-[120px] font-mono text-sm"
/>
) : (
<pre className="p-3 bg-muted rounded text-sm font-mono whitespace-pre-wrap break-all max-h-[200px] overflow-y-auto">
{formatData(account.data)}
</pre>
)}
</div>
<div>
<label className="text-sm font-medium"></label>
{isEditing ? (
<Textarea
value={editData.notes ?? account.notes ?? ''}
onChange={(e) => setEditData({...editData, notes: e.target.value})}
placeholder="输入备注..."
/>
) : (
<div className="p-2 bg-muted rounded min-h-[40px]">
{account.notes || '无备注'}
</div>
)}
</div>
{!isEditing && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-muted-foreground">
<div>
<label className="font-medium"></label>
<div>{new Date(account.createdAt).toLocaleString('zh-CN')}</div>
</div>
<div>
<label className="font-medium"></label>
<div>{new Date(account.updatedAt).toLocaleString('zh-CN')}</div>
</div>
{account.lockedAt && (
<div className="col-span-full md:col-span-2">
<label className="font-medium"></label>
<div>{new Date(account.lockedAt).toLocaleString('zh-CN')}</div>
</div>
)}
</div>
)}
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={loading}
>
{isEditing ? '取消' : '关闭'}
</Button>
{isEditing ? (
<Button
onClick={handleSave}
disabled={loading}
>
{loading ? '保存中...' : '保存'}
</Button>
) : (
<div className="space-x-2">
{onDelete && (
<Button
variant="destructive"
onClick={handleDelete}
disabled={loading}
>
{loading ? '删除中...' : '删除'}
</Button>
)}
</div>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}