33import { useState , useEffect } from 'react' ;
44import { useRouter } from 'next/navigation' ;
55import Link from 'next/link' ;
6- import { TextInput , TextareaInput , MarkdownInput , CheckboxInput , ConfirmDelete , PrimaryButton , Combobox , CloseButton } from '@servicestack/react' ;
6+ import {
7+ TextInput , TextareaInput , MarkdownInput , CheckboxInput , ConfirmDelete , PrimaryButton ,
8+ Combobox , CloseButton , useClient , ApiStateContext , ErrorSummary , SecondaryButton , FileInput
9+ } from '@servicestack/react' ;
10+ import { ResponseStatus , toFormData } from '@servicestack/client' ;
711import { useRequireAuth } from '@/lib/hooks/useRequireAuth' ;
812import * as gateway from '@/lib/api/gateway' ;
913import routes from '@/lib/utils/routes' ;
10- import { ResponseStatus } from '@servicestack/client ' ;
14+ import { CreateTechnologyStack , UpdateTechnologyStack } from '@/shared/dtos ' ;
1115
1216interface TechStackFormProps {
1317 slug ?: string ;
@@ -17,6 +21,7 @@ interface TechStackFormProps {
1721export function TechStackForm ( { slug, onDone } : TechStackFormProps ) {
1822 const router = useRouter ( ) ;
1923 const isAuthenticated = useRequireAuth ( ) ;
24+ const client = useClient ( ) ;
2025 const [ loading , setLoading ] = useState ( false ) ;
2126 const [ error , setError ] = useState < ResponseStatus > ( ) ;
2227 const [ technologies , setTechnologies ] = useState < Record < string , string > > ( { } ) ;
@@ -126,7 +131,6 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
126131 const handleSubmit = async ( e : React . FormEvent ) => {
127132 e . preventDefault ( ) ;
128133 setLoading ( true ) ;
129- setError ( undefined ) ;
130134
131135 try {
132136 // Convert technologyIds from strings to numbers for the API
@@ -135,22 +139,23 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
135139 technologyIds : formData . technologyIds . map ( id => parseInt ( id , 10 ) )
136140 } ;
137141
142+ let api ;
138143 if ( isUpdate ) {
139- await gateway . updateTechStack ( submitData , screenshotFile || undefined ) ;
144+ const body = toFormData ( { ...submitData , screenshot : screenshotFile || undefined } ) ;
145+ api = await client . apiForm ( new UpdateTechnologyStack ( ) , body ) ;
140146 } else {
141- await gateway . createTechStack ( submitData , screenshotFile || undefined ) ;
147+ const body = toFormData ( { ...submitData , screenshot : screenshotFile || undefined } ) ;
148+ api = await client . apiForm ( new CreateTechnologyStack ( ) , body ) ;
142149 }
143150
144- if ( onDone ) {
145- onDone ( ) ;
151+ if ( api . succeeded ) {
152+ if ( onDone ) {
153+ onDone ( ) ;
154+ } else {
155+ router . push ( routes . stack ( formData . slug ) ) ;
156+ }
146157 } else {
147- router . push ( routes . stack ( formData . slug ) ) ;
148- }
149- } catch ( err : any ) {
150- if ( err . responseStatus ) {
151- setError ( err . responseStatus ) ;
152- } else {
153- setError ( { message : err . message || 'Failed to save tech stack' } ) ;
158+ setError ( api . error ) ;
154159 }
155160 } finally {
156161 setLoading ( false ) ;
@@ -189,19 +194,16 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
189194 }
190195
191196 return (
197+ < ApiStateContext . Provider value = { client } >
192198 < form onSubmit = { handleSubmit } >
193199 < div className = "relative shadow sm:overflow-hidden sm:rounded-md" >
200+ { slug && onDone && ( < CloseButton onClose = { onDone } /> ) }
194201 < div className = "space-y-6 py-6 px-4 sm:p-6 bg-white dark:bg-black" >
195- < CloseButton onClose = { onDone } />
196202 < h2 className = "text-lg font-medium leading-6 text-gray-900 dark:text-gray-100" >
197203 { isUpdate ? 'Edit Tech Stack' : 'Add New Tech Stack' }
198204 </ h2 >
199-
200- { error && (
201- < div className = "bg-red-50 border border-red-200 rounded-lg p-4 mb-6" >
202- < p className = "text-red-800" > { error . message || 'An error occurred' } </ p >
203- </ div >
204- ) }
205+ < fieldset >
206+ < ErrorSummary except = { [ 'name' , 'slug' , 'vendorName' , 'description' , 'appUrl' , 'technologyIds' ] } status = { error } className = "mb-4" />
205207
206208 < div className = "grid grid-cols-1 md:grid-cols-2 gap-6" >
207209 < div className = "space-y-4" >
@@ -262,16 +264,18 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
262264 </ div >
263265 </ div >
264266
265- < TextareaInput
266- id = "description"
267- label = "Description"
268- value = { formData . description }
269- onChange = { handleChange ( 'description' ) }
270- required
271- rows = { 4 }
272- maxLength = { 500 }
273- help = { `${ formData . description . length } /500` }
274- />
267+ < div className = "my-4" >
268+ < TextareaInput
269+ id = "description"
270+ label = "Description"
271+ value = { formData . description }
272+ onChange = { handleChange ( 'description' ) }
273+ required
274+ rows = { 4 }
275+ maxLength = { 500 }
276+ help = { `${ formData . description . length } /500` }
277+ />
278+ </ div >
275279
276280 < MarkdownInput
277281 id = "details"
@@ -300,12 +304,15 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
300304 />
301305 </ div >
302306
303- < CheckboxInput
304- id = "isLocked"
305- label = "Prevent others from editing this Tech Stack?"
306- value = { formData . isLocked as any }
307- onChange = { handleChange ( 'isLocked' ) }
308- />
307+ < div className = "border-t border-gray-200 dark:border-gray-700 pt-6 mt-6" >
308+ < CheckboxInput
309+ id = "isLocked"
310+ label = "Prevent others from editing this Tech Stack?"
311+ value = { formData . isLocked as any }
312+ onChange = { handleChange ( 'isLocked' ) }
313+ />
314+ </ div >
315+ </ fieldset >
309316 </ div >
310317
311318 < div className = "bg-gray-50 dark:bg-gray-800 px-4 py-3 text-right sm:px-6" >
@@ -317,16 +324,18 @@ export function TechStackForm({ slug, onDone }: TechStackFormProps) {
317324 </ ConfirmDelete >
318325 ) }
319326 </ div >
320- < div >
327+ < div className = "flex gap-4" >
328+ { onDone && < SecondaryButton onClick = { onDone } > Cancel</ SecondaryButton > }
321329 < PrimaryButton type = "submit" disabled = { loading } >
322- { loading ? 'Saving ...' : isUpdate ? 'Update' : 'Create' }
330+ { loading ? ( isUpdate ? 'Updating ...' : 'Creating...' ) : ( isUpdate ? 'Update Tech Stack ' : 'Create Tech Stack' ) }
323331 </ PrimaryButton >
324332 </ div >
325333 </ div >
326334 </ div >
327335
328336 </ div >
329337 </ form >
338+ </ ApiStateContext . Provider >
330339 ) ;
331340}
332341
0 commit comments