返回 AiToEarn
CreateSpaceSection.tsx
根目录 / project / aitoearn-web / src / components / ChannelManager / components / CreateSpaceSection.tsx
1 /**
2 * CreateSpaceSection - 新建空间部分组件
3 *
4 * 功能:
5 * - 显示新建空间的输入框和按钮
6 * - 处理新建空间的逻辑
7 */
8
9 'use client'
10
11 import { Loader2, Plus } from 'lucide-react'
12 import { useCallback, useState } from 'react'
13 import { createAccountGroupApi } from '@/api/accounts/account.api'
14 import { useTransClient } from '@/app/i18n/client'
15 import { Button } from '@/components/ui/button'
16 import { Input } from '@/components/ui/input'
17 import { toast } from '@/utils/ui/toast'
18
19 interface CreateSpaceSectionProps {
20 onSpaceCreated: () => void
21 }
22
23 export function CreateSpaceSection({ onSpaceCreated }: CreateSpaceSectionProps) {
24 const { t } = useTransClient('account')
25 const [showCreateSpace, setShowCreateSpace] = useState(false)
26 const [creatingSpace, setCreatingSpace] = useState(false)
27 const [newSpaceName, setNewSpaceName] = useState('')
28
29 // 添加新空间
30 const addNewSpace = useCallback(async () => {
31 if (!newSpaceName.trim())
32 return
33
34 setCreatingSpace(true)
35 try {
36 await createAccountGroupApi({ name: newSpaceName.trim() })
37 await onSpaceCreated()
38 toast.success(t('channelManager.createSpaceSuccess', '创建空间成功'))
39 setNewSpaceName('')
40 setCreatingSpace(false)
41 setShowCreateSpace(false)
42 }
43 catch (error) {
44 toast.error(t('channelManager.createSpaceFailed', '创建空间失败'))
45 setCreatingSpace(false)
46 // 注意:这里不重置showCreateSpace,让用户可以重试
47 }
48 }, [newSpaceName, onSpaceCreated, t])
49
50 return (
51 <div className="flex min-w-0 gap-2 p-px">
52 {showCreateSpace ? (
53 <>
54 <Input
55 data-testid="cm-create-space-input"
56 placeholder={t('channelManager.createSpaceNamePlaceholder', '输入空间名称')}
57 value={newSpaceName}
58 onChange={e => setNewSpaceName(e.target.value)}
59 onKeyDown={(e) => {
60 if (e.key === 'Enter')
61 addNewSpace()
62 if (e.key === 'Escape') {
63 setNewSpaceName('')
64 setShowCreateSpace(false)
65 }
66 }}
67 disabled={creatingSpace}
68 autoFocus
69 className="h-11 min-w-0 flex-1 rounded-lg border-border/70 bg-background"
70 />
71 <Button data-testid="cm-create-space-confirm" onClick={addNewSpace} size="sm" disabled={!newSpaceName.trim() || creatingSpace} className="h-11 cursor-pointer rounded-lg px-4">
72 {creatingSpace ? (
73 <Loader2 className="h-4 w-4 animate-spin mr-1" />
74 ) : (
75 <Plus className="h-4 w-4 mr-1" />
76 )}
77 {t('deleteConfirm.confirm')}
78 </Button>
79 <Button
80 data-testid="cm-create-space-cancel"
81 variant="outline"
82 onClick={() => {
83 setNewSpaceName('')
84 setShowCreateSpace(false)
85 }}
86 size="sm"
87 className="h-11 cursor-pointer rounded-lg px-4"
88 >
89 {t('deleteConfirm.cancel')}
90 </Button>
91 </>
92 ) : (
93 <Button
94 data-testid="cm-create-space-btn"
95 variant="outline"
96 onClick={() => setShowCreateSpace(true)}
97 className="h-11 w-full max-w-[166px] cursor-pointer justify-center rounded-lg border-dashed border-primary/50 bg-background px-5 font-semibold text-primary shadow-sm hover:border-primary/70 hover:bg-primary/5 hover:text-primary"
98 disabled={showCreateSpace}
99 >
100 <Plus className="mr-2 h-4 w-4" />
101 {t('channelManager.createSpace')}
102 </Button>
103 )}
104 </div>
105 )
106 }
107
107 lines Plain Text