Files
accounts-manager-web/components/accounts/account-upload.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

299 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } 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, DialogTrigger } from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Upload, Plus, FileText } from 'lucide-react';
import { ScriptUploadItem, StatsOverview } from '@/lib/types';
import { PlatformSelector, OwnerSelector, StatusSelector } from '@/components/shared';
interface AccountUploadProps {
onUpload: (accounts: ScriptUploadItem[], ownerId: string) => Promise<void>;
stats?: StatsOverview | null;
}
export function AccountUpload({ onUpload, stats }: AccountUploadProps) {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [ownerId, setOwnerId] = useState('');
// 文本上传
const [textData, setTextData] = useState('');
const [defaultStatus, setDefaultStatus] = useState('available');
const [defaultPlatform, setDefaultPlatform] = useState('');
// 单个添加
const [singleAccount, setSingleAccount] = useState({
customId: '',
data: ''
});
const parseTextData = (text: string): ScriptUploadItem[] => {
const lines = text.trim().split('\n').filter(line => line.trim());
const accounts: ScriptUploadItem[] = [];
for (const line of lines) {
try {
// 支持多种格式
if (line.includes('----')) {
// ---- 分隔格式customId----datadata中可能包含更多的----
const firstSeparatorIndex = line.indexOf('----');
if (firstSeparatorIndex > 0) {
const customId = line.substring(0, firstSeparatorIndex).trim();
const data = line.substring(firstSeparatorIndex + 4).trim(); // 跳过第一个----
accounts.push({
platform: defaultPlatform,
customId,
data,
status: defaultStatus
});
}
} else if (line.includes('\t')) {
// 制表符分隔platform\tcustomId\tdata\tstatus
const parts = line.split('\t');
if (parts.length >= 3) {
accounts.push({
platform: parts[0].trim(),
customId: parts[1].trim(),
data: parts[2].trim(),
status: parts[3]?.trim() || defaultStatus
});
}
} else if (line.includes(',')) {
// 逗号分隔platform,customId,data,status
const parts = line.split(',');
if (parts.length >= 3) {
accounts.push({
platform: parts[0].trim(),
customId: parts[1].trim(),
data: parts[2].trim(),
status: parts[3]?.trim() || defaultStatus
});
}
} else if (line.startsWith('{') && line.endsWith('}')) {
// JSON格式
const parsed = JSON.parse(line);
if (parsed.platform && parsed.customId && parsed.data) {
accounts.push({
platform: parsed.platform,
customId: parsed.customId,
data: typeof parsed.data === 'string' ? parsed.data : JSON.stringify(parsed.data),
status: parsed.status || defaultStatus
});
}
} else {
// 默认格式整行作为ID和data
const trimmedLine = line.trim();
if (trimmedLine) {
accounts.push({
platform: defaultPlatform,
customId: trimmedLine,
data: trimmedLine,
status: defaultStatus
});
}
}
} catch (error) {
console.warn('Failed to parse line:', line, error);
}
}
return accounts;
};
const handleTextUpload = async () => {
if (!ownerId.trim()) {
alert('请输入所有者ID');
return;
}
if (!textData.trim()) {
alert('请输入账户数据');
return;
}
const accounts = parseTextData(textData);
if (accounts.length === 0) {
alert('未解析到有效的账户数据');
return;
}
setLoading(true);
try {
await onUpload(accounts, ownerId);
setOpen(false);
setTextData('');
setOwnerId('');
} finally {
setLoading(false);
}
};
const handleSingleUpload = async () => {
if (!ownerId.trim()) {
alert('请输入所有者ID');
return;
}
if (!singleAccount.customId || !singleAccount.data) {
alert('请填写完整的账户信息');
return;
}
setLoading(true);
try {
const accountToUpload = {
platform: defaultPlatform,
customId: singleAccount.customId,
data: singleAccount.data,
status: defaultStatus
};
await onUpload([accountToUpload], ownerId);
setOpen(false);
setSingleAccount({ customId: '', data: '' });
setOwnerId('');
} finally {
setLoading(false);
}
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>
<Upload className="h-4 w-4 mr-2" />
</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto space-y-4 pr-2">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<div>
<label className="text-sm font-medium">ID</label>
<OwnerSelector
value={ownerId}
onValueChange={setOwnerId}
stats={stats || null}
placeholder="选择所有者ID"
/>
</div>
<div>
<label className="text-sm font-medium"></label>
<PlatformSelector
value={defaultPlatform}
onValueChange={setDefaultPlatform}
stats={stats || null}
placeholder="选择平台"
/>
</div>
<div>
<label className="text-sm font-medium"></label>
<StatusSelector
value={defaultStatus}
onValueChange={setDefaultStatus}
stats={stats || null}
placeholder="选择状态"
/>
</div>
</div>
<Tabs defaultValue="batch" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="batch">
<FileText className="h-4 w-4 mr-2" />
</TabsTrigger>
<TabsTrigger value="single">
<Plus className="h-4 w-4 mr-2" />
</TabsTrigger>
</TabsList>
<TabsContent value="batch" className="space-y-4">
<div>
<label className="text-sm font-medium"></label>
<Textarea
placeholder={`支持以下格式:
1. ---- 分割格式(推荐):
user1@gmail.com----password123----cookie=abc123----token=xyz789
user2@gmail.com----{"password":"456789","token":"fb_token"}
user3@twitter.com----oauth_token----oauth_secret----user_data
2. 默认格式(一行一个):
user1@gmail.com
user2@facebook.com
simple_username
3. 制表符分隔platform customId data status
4. 逗号分隔platform,customId,data,status
5. JSON格式{"platform":"google","customId":"user@gmail.com","data":"..."}
格式说明:
- ---- 分割: customId----纯数据内容(第一个----后的所有内容作为data
- 默认格式: 整行既作为customId也作为data
- 平台和状态: 在上方外部设置`}
value={textData}
onChange={(e) => setTextData(e.target.value)}
className="h-[200px] max-h-[300px] font-mono text-sm resize-y overflow-y-auto"
/>
</div>
<Button
onClick={handleTextUpload}
disabled={loading}
className="w-full"
>
{loading ? '上传中...' : '批量上传'}
</Button>
</TabsContent>
<TabsContent value="single" className="space-y-4">
<div className="grid grid-cols-1 gap-4">
<div>
<label className="text-sm font-medium">ID</label>
<Input
placeholder="如user@gmail.com"
value={singleAccount.customId}
onChange={(e) => setSingleAccount({...singleAccount, customId: e.target.value})}
/>
</div>
</div>
<div>
<label className="text-sm font-medium"></label>
<Textarea
placeholder='纯数据内容password123----cookie=abc123----token=xyz789 或 {"username":"user@gmail.com","password":"123456","token":"abc"}'
value={singleAccount.data}
onChange={(e) => setSingleAccount({...singleAccount, data: e.target.value})}
className="min-h-[120px]"
/>
</div>
<Button
onClick={handleSingleUpload}
disabled={loading}
className="w-full"
>
{loading ? '添加中...' : '添加账户'}
</Button>
</TabsContent>
</Tabs>
</div>
</DialogContent>
</Dialog>
);
}