11import * as React from 'react'
22import { useMutation } from '@tanstack/react-query'
33import { useNavigate } from '@tanstack/react-router'
4- import { submitShowcase } from '~/utils/showcase.functions'
4+ import { submitShowcase , updateShowcase } from '~/utils/showcase.functions'
55import { libraries } from '~/libraries'
6- import { SHOWCASE_USE_CASES , type ShowcaseUseCase } from '~/db/types'
6+ import {
7+ SHOWCASE_USE_CASES ,
8+ type Showcase ,
9+ type ShowcaseUseCase ,
10+ } from '~/db/types'
711import {
812 getAutoIncludedLibraries ,
913 USE_CASE_LABELS ,
@@ -19,52 +23,83 @@ const selectableLibraries = libraries.filter(
1923 lib . name && lib . id !== 'react-charts' && lib . id !== 'create-tsrouter-app' ,
2024)
2125
22- export function ShowcaseSubmitForm ( ) {
26+ interface ShowcaseSubmitFormProps {
27+ showcase ?: Showcase
28+ }
29+
30+ export function ShowcaseSubmitForm ( { showcase } : ShowcaseSubmitFormProps ) {
2331 const navigate = useNavigate ( )
2432 const { notify } = useToast ( )
33+ const isEditMode = ! ! showcase
2534
26- const [ name , setName ] = React . useState ( '' )
27- const [ tagline , setTagline ] = React . useState ( '' )
28- const [ description , setDescription ] = React . useState ( '' )
29- const [ url , setUrl ] = React . useState ( '' )
30- const [ logoUrl , setLogoUrl ] = React . useState < string | undefined > ( )
31- const [ screenshotUrl , setScreenshotUrl ] = React . useState < string | undefined > ( )
32- const [ selectedLibraries , setSelectedLibraries ] = React . useState < string [ ] > ( [ ] )
35+ const [ name , setName ] = React . useState ( showcase ?. name ?? '' )
36+ const [ tagline , setTagline ] = React . useState ( showcase ?. tagline ?? '' )
37+ const [ description , setDescription ] = React . useState (
38+ showcase ?. description ?? '' ,
39+ )
40+ const [ url , setUrl ] = React . useState ( showcase ?. url ?? '' )
41+ const [ logoUrl , setLogoUrl ] = React . useState < string | undefined > (
42+ showcase ?. logoUrl ?? undefined ,
43+ )
44+ const [ screenshotUrl , setScreenshotUrl ] = React . useState < string | undefined > (
45+ showcase ?. screenshotUrl ?? undefined ,
46+ )
47+ const [ selectedLibraries , setSelectedLibraries ] = React . useState < string [ ] > (
48+ showcase ?. libraries ?? [ ] ,
49+ )
3350 const [ selectedUseCases , setSelectedUseCases ] = React . useState <
3451 ShowcaseUseCase [ ]
35- > ( [ ] )
52+ > ( showcase ?. useCases ?? [ ] )
3653
3754 // Get auto-included libraries based on selection
3855 const autoIncluded = React . useMemo (
3956 ( ) => getAutoIncludedLibraries ( selectedLibraries ) ,
4057 [ selectedLibraries ] ,
4158 )
4259
43- const submitMutation = useMutation ( {
60+ const onSuccess = ( ) => {
61+ notify (
62+ < div >
63+ < div className = "font-medium" >
64+ { isEditMode ? 'Showcase updated!' : 'Showcase submitted!' }
65+ </ div >
66+ < div className = "text-gray-500 dark:text-gray-400 text-xs" >
67+ { isEditMode
68+ ? 'Your changes are pending review. Votes have been preserved.'
69+ : "Your project is pending review. We'll notify you when it's approved." }
70+ </ div >
71+ </ div > ,
72+ )
73+ navigate ( { to : '/showcase/mine' } )
74+ }
75+
76+ const onError = ( error : Error ) => {
77+ notify (
78+ < div >
79+ < div className = "font-medium" >
80+ { isEditMode ? 'Update failed' : 'Submission failed' }
81+ </ div >
82+ < div className = "text-gray-500 dark:text-gray-400 text-xs" >
83+ { error . message }
84+ </ div >
85+ </ div > ,
86+ )
87+ }
88+
89+ const createMutation = useMutation ( {
4490 mutationFn : submitShowcase ,
45- onSuccess : ( ) => {
46- notify (
47- < div >
48- < div className = "font-medium" > Showcase submitted!</ div >
49- < div className = "text-gray-500 dark:text-gray-400 text-xs" >
50- Your project is pending review. We'll notify you when it's approved.
51- </ div >
52- </ div > ,
53- )
54- navigate ( { to : '/showcase/mine' } )
55- } ,
56- onError : ( error : Error ) => {
57- notify (
58- < div >
59- < div className = "font-medium" > Submission failed</ div >
60- < div className = "text-gray-500 dark:text-gray-400 text-xs" >
61- { error . message }
62- </ div >
63- </ div > ,
64- )
65- } ,
91+ onSuccess,
92+ onError,
93+ } )
94+
95+ const editMutation = useMutation ( {
96+ mutationFn : updateShowcase ,
97+ onSuccess,
98+ onError,
6699 } )
67100
101+ const isPending = createMutation . isPending || editMutation . isPending
102+
68103 const toggleLibrary = ( libraryId : string ) => {
69104 // Can't toggle auto-included libraries
70105 if ( autoIncluded [ libraryId ] ) return
@@ -105,29 +140,46 @@ export function ShowcaseSubmitForm() {
105140 return
106141 }
107142
108- submitMutation . mutate ( {
109- data : {
110- name,
111- tagline,
112- description : description || undefined ,
113- url,
114- logoUrl,
115- screenshotUrl,
116- libraries : selectedLibraries ,
117- useCases : selectedUseCases ,
118- } ,
119- } )
143+ // Warn user if editing an approved showcase
144+ if ( isEditMode && showcase . status === 'approved' ) {
145+ const confirmed = confirm (
146+ 'Saving changes will reset your showcase to pending review until re-approved. Your votes will be preserved. Continue?' ,
147+ )
148+ if ( ! confirmed ) {
149+ return
150+ }
151+ }
152+
153+ const formData = {
154+ name,
155+ tagline,
156+ description : description || undefined ,
157+ url,
158+ logoUrl,
159+ screenshotUrl,
160+ libraries : selectedLibraries ,
161+ useCases : selectedUseCases ,
162+ }
163+
164+ if ( isEditMode ) {
165+ editMutation . mutate ( {
166+ data : { ...formData , showcaseId : showcase . id } ,
167+ } )
168+ } else {
169+ createMutation . mutate ( { data : formData } )
170+ }
120171 }
121172
122173 return (
123174 < div className = "min-h-screen bg-gray-50 dark:bg-gray-950" >
124175 < div className = "max-w-2xl mx-auto px-4 py-12" >
125176 < h1 className = "text-3xl font-bold text-gray-900 dark:text-white" >
126- Submit Your Project
177+ { isEditMode ? 'Edit Your Project' : ' Submit Your Project' }
127178 </ h1 >
128179 < p className = "mt-2 text-gray-600 dark:text-gray-400" >
129- Share what you've built with TanStack libraries. Your submission will
130- be reviewed before appearing in the showcase.
180+ { isEditMode
181+ ? 'Update your showcase submission. Changes will require re-approval but votes will be preserved.'
182+ : "Share what you've built with TanStack libraries. Your submission will be reviewed before appearing in the showcase." }
131183 </ p >
132184
133185 < form onSubmit = { handleSubmit } className = "mt-8 space-y-6" >
@@ -306,17 +358,30 @@ export function ShowcaseSubmitForm() {
306358 </ div >
307359
308360 { /* Submit Button */ }
309- < div className = "pt-4" >
361+ < div className = "pt-4 flex gap-3" >
362+ { isEditMode && (
363+ < Button
364+ type = "button"
365+ onClick = { ( ) => navigate ( { to : '/showcase/mine' } ) }
366+ className = "flex-1 justify-center px-6 py-3 font-medium rounded-lg"
367+ >
368+ Cancel
369+ </ Button >
370+ ) }
310371 < Button
311372 type = "submit"
312373 disabled = {
313- submitMutation . isPending ||
314- selectedLibraries . length === 0 ||
315- ! screenshotUrl
374+ isPending || selectedLibraries . length === 0 || ! screenshotUrl
316375 }
317- className = " w-full justify-center px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium rounded-lg border-none"
376+ className = { ` ${ isEditMode ? 'flex-1' : ' w-full' } justify-center px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium rounded-lg border-none` }
318377 >
319- { submitMutation . isPending ? 'Submitting...' : 'Submit for Review' }
378+ { isPending
379+ ? isEditMode
380+ ? 'Saving...'
381+ : 'Submitting...'
382+ : isEditMode
383+ ? 'Save Changes'
384+ : 'Submit for Review' }
320385 </ Button >
321386 </ div >
322387 </ form >
0 commit comments