@@ -9,160 +9,190 @@ import { toast } from "sonner";
99import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms" ;
1010import { Alert , AlertDescription } from "@/components/ui/alert" ;
1111import { Button } from "@/components/ui/button" ;
12- import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "@/components/ui/card" ;
12+ import {
13+ Card ,
14+ CardContent ,
15+ CardDescription ,
16+ CardHeader ,
17+ CardTitle ,
18+ } from "@/components/ui/card" ;
1319import { Input } from "@/components/ui/input" ;
1420import { Label } from "@/components/ui/label" ;
1521import { Skeleton } from "@/components/ui/skeleton" ;
1622import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service" ;
1723import { cacheKeys } from "@/lib/query-client/cache-keys" ;
24+ import { Spinner } from "../ui/spinner" ;
1825
1926interface GeneralSettingsManagerProps {
20- searchSpaceId : number ;
27+ searchSpaceId : number ;
2128}
2229
23- export function GeneralSettingsManager ( { searchSpaceId } : GeneralSettingsManagerProps ) {
24- const t = useTranslations ( "searchSpaceSettings" ) ;
25- const tCommon = useTranslations ( "common" ) ;
26- const {
27- data : searchSpace ,
28- isLoading : loading ,
29- refetch : fetchSearchSpace ,
30- } = useQuery ( {
31- queryKey : cacheKeys . searchSpaces . detail ( searchSpaceId . toString ( ) ) ,
32- queryFn : ( ) => searchSpacesApiService . getSearchSpace ( { id : searchSpaceId } ) ,
33- enabled : ! ! searchSpaceId ,
34- } ) ;
35-
36- const { mutateAsync : updateSearchSpace } = useAtomValue ( updateSearchSpaceMutationAtom ) ;
37-
38- const [ name , setName ] = useState ( "" ) ;
39- const [ description , setDescription ] = useState ( "" ) ;
40- const [ saving , setSaving ] = useState ( false ) ;
41- const [ hasChanges , setHasChanges ] = useState ( false ) ;
42-
43- // Initialize state from fetched search space
44- useEffect ( ( ) => {
45- if ( searchSpace ) {
46- setName ( searchSpace . name || "" ) ;
47- setDescription ( searchSpace . description || "" ) ;
48- setHasChanges ( false ) ;
49- }
50- } , [ searchSpace ] ) ;
51-
52- // Track changes
53- useEffect ( ( ) => {
54- if ( searchSpace ) {
55- const currentName = searchSpace . name || "" ;
56- const currentDescription = searchSpace . description || "" ;
57- const changed = currentName !== name || currentDescription !== description ;
58- setHasChanges ( changed ) ;
59- }
60- } , [ searchSpace , name , description ] ) ;
61-
62- const handleSave = async ( ) => {
63- try {
64- setSaving ( true ) ;
65-
66- await updateSearchSpace ( {
67- id : searchSpaceId ,
68- data : {
69- name : name . trim ( ) ,
70- description : description . trim ( ) || undefined ,
71- } ,
72- } ) ;
73-
74- setHasChanges ( false ) ;
75- await fetchSearchSpace ( ) ;
76- } catch ( error : any ) {
77- console . error ( "Error saving search space details:" , error ) ;
78- toast . error ( error . message || "Failed to save search space details" ) ;
79- } finally {
80- setSaving ( false ) ;
81- }
82- } ;
83-
84- if ( loading ) {
85- return (
86- < div className = "space-y-4 md:space-y-6" >
87- < Card >
88- < CardHeader className = "px-3 md:px-6 pt-3 md:pt-6 pb-2 md:pb-3" >
89- < Skeleton className = "h-5 md:h-6 w-36 md:w-48" />
90- < Skeleton className = "h-3 md:h-4 w-full max-w-md mt-2" />
91- </ CardHeader >
92- < CardContent className = "space-y-3 md:space-y-4 px-3 md:px-6 pb-3 md:pb-6" >
93- < Skeleton className = "h-10 md:h-12 w-full" />
94- < Skeleton className = "h-10 md:h-12 w-full" />
95- </ CardContent >
96- </ Card >
97- </ div >
98- ) ;
99- }
100-
101- return (
102- < div className = "space-y-4 md:space-y-6" >
103- < Alert className = "bg-muted/50 py-3 md:py-4" >
104- < Info className = "h-3 w-3 md:h-4 md:w-4 shrink-0" />
105- < AlertDescription className = "text-xs md:text-sm" >
106- Update your search space name and description. These details help identify and organize
107- your workspace.
108- </ AlertDescription >
109- </ Alert >
110-
111- { /* Search Space Details Card */ }
112- < Card >
113- < CardHeader className = "px-3 md:px-6 pt-3 md:pt-6 pb-2 md:pb-3" >
114- < CardTitle className = "text-base md:text-lg" > Search Space Details</ CardTitle >
115- < CardDescription className = "text-xs md:text-sm" >
116- Manage the basic information for this search space.
117- </ CardDescription >
118- </ CardHeader >
119- < CardContent className = "space-y-4 md:space-y-5 px-3 md:px-6 pb-3 md:pb-6" >
120- < div className = "space-y-1.5 md:space-y-2" >
121- < Label htmlFor = "search-space-name" className = "text-sm md:text-base font-medium" >
122- { t ( "general_name_label" ) }
123- </ Label >
124- < Input
125- id = "search-space-name"
126- placeholder = { t ( "general_name_placeholder" ) }
127- value = { name }
128- onChange = { ( e ) => setName ( e . target . value ) }
129- className = "text-sm md:text-base h-9 md:h-10"
130- />
131- < p className = "text-[10px] md:text-xs text-muted-foreground" >
132- { t ( "general_name_description" ) }
133- </ p >
134- </ div >
135-
136- < div className = "space-y-1.5 md:space-y-2" >
137- < Label htmlFor = "search-space-description" className = "text-sm md:text-base font-medium" >
138- { t ( "general_description_label" ) } { " " }
139- < span className = "text-muted-foreground font-normal" > ({ tCommon ( "optional" ) } )</ span >
140- </ Label >
141- < Input
142- id = "search-space-description"
143- placeholder = { t ( "general_description_placeholder" ) }
144- value = { description }
145- onChange = { ( e ) => setDescription ( e . target . value ) }
146- className = "text-sm md:text-base h-9 md:h-10"
147- />
148- < p className = "text-[10px] md:text-xs text-muted-foreground" >
149- { t ( "general_description_description" ) }
150- </ p >
151- </ div >
152- </ CardContent >
153- </ Card >
154-
155- { /* Action Buttons */ }
156- < div className = "flex justify-end pt-3 md:pt-4" >
157- < Button
158- variant = "outline"
159- onClick = { handleSave }
160- disabled = { ! hasChanges || saving || ! name . trim ( ) }
161- className = "gap-2 bg-white text-black hover:bg-neutral-100 dark:bg-white dark:text-black dark:hover:bg-neutral-200"
162- >
163- { saving ? t ( "general_saving" ) : t ( "general_save" ) }
164- </ Button >
165- </ div >
166- </ div >
167- ) ;
30+ export function GeneralSettingsManager ( {
31+ searchSpaceId,
32+ } : GeneralSettingsManagerProps ) {
33+ const t = useTranslations ( "searchSpaceSettings" ) ;
34+ const tCommon = useTranslations ( "common" ) ;
35+ const {
36+ data : searchSpace ,
37+ isLoading : loading ,
38+ refetch : fetchSearchSpace ,
39+ } = useQuery ( {
40+ queryKey : cacheKeys . searchSpaces . detail ( searchSpaceId . toString ( ) ) ,
41+ queryFn : ( ) => searchSpacesApiService . getSearchSpace ( { id : searchSpaceId } ) ,
42+ enabled : ! ! searchSpaceId ,
43+ } ) ;
44+
45+ const { mutateAsync : updateSearchSpace } = useAtomValue (
46+ updateSearchSpaceMutationAtom ,
47+ ) ;
48+
49+ const [ name , setName ] = useState ( "" ) ;
50+ const [ description , setDescription ] = useState ( "" ) ;
51+ const [ saving , setSaving ] = useState ( false ) ;
52+ const [ hasChanges , setHasChanges ] = useState ( false ) ;
53+
54+ // Initialize state from fetched search space
55+ useEffect ( ( ) => {
56+ if ( searchSpace ) {
57+ setName ( searchSpace . name || "" ) ;
58+ setDescription ( searchSpace . description || "" ) ;
59+ setHasChanges ( false ) ;
60+ }
61+ } , [ searchSpace ] ) ;
62+
63+ // Track changes
64+ useEffect ( ( ) => {
65+ if ( searchSpace ) {
66+ const currentName = searchSpace . name || "" ;
67+ const currentDescription = searchSpace . description || "" ;
68+ const changed =
69+ currentName !== name || currentDescription !== description ;
70+ setHasChanges ( changed ) ;
71+ }
72+ } , [ searchSpace , name , description ] ) ;
73+
74+ const handleSave = async ( ) => {
75+ try {
76+ setSaving ( true ) ;
77+
78+ await updateSearchSpace ( {
79+ id : searchSpaceId ,
80+ data : {
81+ name : name . trim ( ) ,
82+ description : description . trim ( ) || undefined ,
83+ } ,
84+ } ) ;
85+
86+ setHasChanges ( false ) ;
87+ await fetchSearchSpace ( ) ;
88+ } catch ( error : any ) {
89+ console . error ( "Error saving search space details:" , error ) ;
90+ toast . error ( error . message || "Failed to save search space details" ) ;
91+ } finally {
92+ setSaving ( false ) ;
93+ }
94+ } ;
95+
96+ const onSubmit = ( e : React . FormEvent ) => {
97+ e . preventDefault ( ) ;
98+ handleSave ( ) ;
99+ } ;
100+
101+ if ( loading ) {
102+ return (
103+ < div className = "space-y-4 md:space-y-6" >
104+ < Card >
105+ < CardHeader className = "px-3 md:px-6 pt-3 md:pt-6 pb-2 md:pb-3" >
106+ < Skeleton className = "h-5 md:h-6 w-36 md:w-48" />
107+ < Skeleton className = "h-3 md:h-4 w-full max-w-md mt-2" />
108+ </ CardHeader >
109+ < CardContent className = "space-y-3 md:space-y-4 px-3 md:px-6 pb-3 md:pb-6" >
110+ < Skeleton className = "h-10 md:h-12 w-full" />
111+ < Skeleton className = "h-10 md:h-12 w-full" />
112+ </ CardContent >
113+ </ Card >
114+ </ div >
115+ ) ;
116+ }
117+
118+ return (
119+ < div className = "space-y-4 md:space-y-6" >
120+ < Alert className = "bg-muted/50 py-3 md:py-4" >
121+ < Info className = "h-3 w-3 md:h-4 md:w-4 shrink-0" />
122+ < AlertDescription className = "text-xs md:text-sm" >
123+ Update your search space name and description. These details help
124+ identify and organize your workspace.
125+ </ AlertDescription >
126+ </ Alert >
127+
128+ { /* Search Space Details Card */ }
129+ < form onSubmit = { onSubmit } className = "space-y-4 md:space-y-6" >
130+ < Card >
131+ < CardHeader className = "px-3 md:px-6 pt-3 md:pt-6 pb-2 md:pb-3" >
132+ < CardTitle className = "text-base md:text-lg" >
133+ Search Space Details
134+ </ CardTitle >
135+ < CardDescription className = "text-xs md:text-sm" >
136+ Manage the basic information for this search space.
137+ </ CardDescription >
138+ </ CardHeader >
139+ < CardContent className = "space-y-4 md:space-y-5 px-3 md:px-6 pb-3 md:pb-6" >
140+ < div className = "space-y-1.5 md:space-y-2" >
141+ < Label
142+ htmlFor = "search-space-name"
143+ className = "text-sm md:text-base font-medium"
144+ >
145+ { t ( "general_name_label" ) }
146+ </ Label >
147+ < Input
148+ id = "search-space-name"
149+ placeholder = { t ( "general_name_placeholder" ) }
150+ value = { name }
151+ onChange = { ( e ) => setName ( e . target . value ) }
152+ className = "text-sm md:text-base h-9 md:h-10"
153+ />
154+ < p className = "text-[10px] md:text-xs text-muted-foreground" >
155+ { t ( "general_name_description" ) }
156+ </ p >
157+ </ div >
158+
159+ < div className = "space-y-1.5 md:space-y-2" >
160+ < Label
161+ htmlFor = "search-space-description"
162+ className = "text-sm md:text-base font-medium"
163+ >
164+ { t ( "general_description_label" ) } { " " }
165+ < span className = "text-muted-foreground font-normal" >
166+ ({ tCommon ( "optional" ) } )
167+ </ span >
168+ </ Label >
169+ < Input
170+ id = "search-space-description"
171+ placeholder = { t ( "general_description_placeholder" ) }
172+ value = { description }
173+ onChange = { ( e ) => setDescription ( e . target . value ) }
174+ className = "text-sm md:text-base h-9 md:h-10"
175+ />
176+ < p className = "text-[10px] md:text-xs text-muted-foreground" >
177+ { t ( "general_description_description" ) }
178+ </ p >
179+ </ div >
180+ </ CardContent >
181+ </ Card >
182+
183+ { /* Action Buttons */ }
184+ < div className = "flex justify-end pt-3 md:pt-4" >
185+ < Button
186+ type = "submit"
187+ variant = "outline"
188+ disabled = { ! hasChanges || saving || ! name . trim ( ) }
189+ className = "gap-2 bg-white text-black hover:bg-neutral-100 dark:bg-white dark:text-black dark:hover:bg-neutral-200"
190+ >
191+ { saving ? < Spinner size = "sm" /> : null }
192+ { saving ? t ( "general_saving" ) : t ( "general_save" ) }
193+ </ Button >
194+ </ div >
195+ </ form >
196+ </ div >
197+ ) ;
168198}
0 commit comments