@@ -42,8 +42,13 @@ export default function NewUserPage() {
4242 const [ photoLink , setPhotoLink ] = useState ( "" ) ;
4343 const [ photoPreview , setPhotoPreview ] = useState < string | null > ( profile ?. photo_url ?? null ) ;
4444 const hasRequestedCubidId = useRef ( false ) ;
45+ const latestProfileRef = useRef ( profile ) ;
4546 const previewObjectUrl = useRef < string | null > ( null ) ;
4647
48+ useEffect ( ( ) => {
49+ latestProfileRef . current = profile ;
50+ } , [ profile ] ) ;
51+
4752 function updatePhotoPreview ( value : string | null , isObjectUrl : boolean ) {
4853 if ( previewObjectUrl . current ) {
4954 URL . revokeObjectURL ( previewObjectUrl . current ) ;
@@ -76,7 +81,7 @@ export default function NewUserPage() {
7681 } , [ ] ) ;
7782
7883 useEffect ( ( ) => {
79- if ( ! session ?. user ?. email ) {
84+ if ( ! ready || ! session ?. user ?. email ) {
8085 return ;
8186 }
8287 if ( profile ?. cubid_id || hasRequestedCubidId . current ) {
@@ -86,14 +91,17 @@ export default function NewUserPage() {
8691 hasRequestedCubidId . current = true ;
8792 requestCubidId ( session . user . email )
8893 . then ( ( cubid ) => {
94+ if ( latestProfileRef . current ?. cubid_id ) {
95+ return ;
96+ }
8997 setForm ( ( prev ) => ( { ...prev , cubidId : cubid } ) ) ;
9098 setStatus ( "Cubid ID prepared" ) ;
9199 } )
92100 . catch ( ( err ) => {
93101 const message = err instanceof Error ? err . message : "Failed to generate Cubid ID" ;
94102 setError ( message ) ;
95103 } ) ;
96- } , [ profile ?. cubid_id , session ?. user ?. email ] ) ;
104+ } , [ profile ?. cubid_id , ready , session ?. user ?. email ] ) ;
97105
98106 if ( ! ready ) {
99107 return (
@@ -113,7 +121,6 @@ export default function NewUserPage() {
113121 setUser ( updated ) ;
114122 setWalletAddress ( address ) ;
115123 setStatus ( "Wallet linked" ) ;
116- setStep ( 2 ) ;
117124 } catch ( err ) {
118125 const message = err instanceof Error ? err . message : "Wallet connection failed" ;
119126 setError ( message ) ;
@@ -184,13 +191,12 @@ export default function NewUserPage() {
184191 } else if ( photoLink ) {
185192 const response = await fetch ( photoLink ) ;
186193 if ( ! response . ok ) {
187- throw new Error ( "We couldn' t fetch that image link" ) ;
194+ throw new Error ( "We couldn' t fetch that image link" ) ;
188195 }
189196 const contentType = response . headers . get ( "content-type" ) ?? "image/jpeg" ;
190197 const extension = inferExtensionFromSource ( photoLink , contentType ) ;
191198 const blob = await response . blob ( ) ;
192199 fileToUpload = new File ( [ blob ] , `linked.${ extension } ` , { type : contentType } ) ;
193- updatePhotoPreview ( URL . createObjectURL ( blob ) , true ) ;
194200 } else if ( form . photoUrl ) {
195201 // Existing profile photo already stored in Supabase.
196202 setStatus ( "Photo ready" ) ;
@@ -218,6 +224,7 @@ export default function NewUserPage() {
218224 } = supabase . storage . from ( "profile-pictures" ) . getPublicUrl ( storagePath ) ;
219225
220226 setForm ( ( prev ) => ( { ...prev , photoUrl : publicUrl } ) ) ;
227+ updatePhotoPreview ( publicUrl , false ) ;
221228 setStatus ( "Photo uploaded" ) ;
222229 }
223230
@@ -233,7 +240,7 @@ export default function NewUserPage() {
233240 await uploadPhotoFromLinkOrFile ( ) ;
234241 setStep ( 2 ) ;
235242 } catch ( err ) {
236- const message = err instanceof Error ? err . message : "We couldn' t save your photo" ;
243+ const message = err instanceof Error ? err . message : "We couldn' t save your photo" ;
237244 setError ( message ) ;
238245 setStatus ( null ) ;
239246 } finally {
@@ -250,22 +257,39 @@ export default function NewUserPage() {
250257 < header className = "space-y-2" >
251258 < h1 className = "text-3xl font-semibold" > Welcome to Trust Me Bro</ h1 >
252259 < p className = "text-muted-foreground" >
253- We' ll gather a few details to build your profile: your name, a photo, and your wallet.
260+ We' ll gather a few details to build your profile: your name, a photo, and your wallet.
254261 </ p >
255262 </ header >
256263
257- < div className = "flex gap-2 text-xs uppercase tracking-widest text-muted-foreground" >
258- < span className = { step === 0 ? "font-semibold text-blue-600" : "" } > Name</ span >
259- < span > ›</ span >
260- < span className = { step === 1 ? "font-semibold text-blue-600" : step > 1 ? "text-blue-600" : "" } > Photo</ span >
261- < span > ›</ span >
262- < span className = { step === 2 ? "font-semibold text-blue-600" : "" } > Wallet</ span >
263- </ div >
264+ < nav aria-label = "Onboarding progress" >
265+ < ol className = "flex items-center gap-2 text-xs uppercase tracking-widest text-muted-foreground" >
266+ { ( [ "Name" , "Photo" , "Wallet" ] as const ) . map ( ( label , index , array ) => (
267+ < li
268+ key = { label }
269+ aria-current = { step === index ? "step" : undefined }
270+ className = { `flex items-center ${
271+ step === index
272+ ? "font-semibold text-blue-600"
273+ : index < step
274+ ? "text-blue-600"
275+ : ""
276+ } `}
277+ >
278+ < span > { label } </ span >
279+ { index < array . length - 1 ? (
280+ < span aria-hidden = "true" className = "px-1 text-muted-foreground" >
281+ ›
282+ </ span >
283+ ) : null }
284+ </ li >
285+ ) ) }
286+ </ ol >
287+ </ nav >
264288
265289 { step === 0 ? (
266290 < form className = "space-y-6" onSubmit = { handleNameNext } >
267291 < label className = "flex flex-col gap-3" >
268- < span className = "text-2xl font-medium" > What' s your name?</ span >
292+ < span className = "text-2xl font-medium" > What' s your name?</ span >
269293 < input
270294 autoFocus
271295 className = "w-full rounded-lg border border-neutral-300 bg-white px-4 py-6 text-2xl shadow-sm focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900"
@@ -291,7 +315,7 @@ export default function NewUserPage() {
291315 < div className = "space-y-3" >
292316 < p className = "text-2xl font-medium" > Share a photo</ p >
293317 < p className = "text-sm text-muted-foreground" >
294- Upload a file or paste a link to an image. We' ll store it securely for your profile.
318+ Upload a file or paste a link to an image. We' ll store it securely for your profile.
295319 </ p >
296320 < div className = "flex flex-col gap-4 rounded-lg border border-dashed border-neutral-300 p-6 dark:border-neutral-700" >
297321 < label className = "flex flex-col gap-2 text-sm font-medium" >
@@ -331,6 +355,7 @@ export default function NewUserPage() {
331355 className = "rounded-full border border-neutral-300 px-8 py-3 text-lg font-semibold transition hover:bg-neutral-100 dark:border-neutral-700 dark:hover:bg-neutral-900"
332356 onClick = { ( ) => {
333357 setStatus ( null ) ;
358+ setError ( null ) ;
334359 setStep ( 0 ) ;
335360 } }
336361 type = "button"
@@ -353,7 +378,7 @@ export default function NewUserPage() {
353378 < div className = "space-y-3" >
354379 < p className = "text-2xl font-medium" > Connect your wallet</ p >
355380 < p className = "text-sm text-muted-foreground" >
356- Link the wallet you' ll use for vouching. Once connected, we' ll confirm your Cubid ID.
381+ Link the wallet you' ll use for vouching. Once connected, we' ll confirm your Cubid ID.
357382 </ p >
358383 < div className = "rounded-lg border border-neutral-200 p-6 shadow-sm dark:border-neutral-800" >
359384 < button
@@ -392,6 +417,7 @@ export default function NewUserPage() {
392417 className = "rounded-full border border-neutral-300 px-8 py-3 text-lg font-semibold transition hover:bg-neutral-100 dark:border-neutral-700 dark:hover:bg-neutral-900"
393418 onClick = { ( ) => {
394419 setStatus ( null ) ;
420+ setError ( null ) ;
395421 setStep ( 1 ) ;
396422 } }
397423 type = "button"
@@ -420,7 +446,7 @@ function inferExtensionFromSource(source: string, contentType: string): string {
420446 if ( urlExtension && / ^ [ a - z 0 - 9 ] + $ / i. test ( urlExtension ) ) {
421447 return urlExtension ;
422448 }
423- const mimeExtension = contentType . split ( "/" ) [ 1 ] ;
449+ const mimeExtension = contentType . split ( "/" ) [ 1 ] ?. split ( ";" ) [ 0 ] ?. trim ( ) ;
424450 if ( mimeExtension ) {
425451 return mimeExtension ;
426452 }
0 commit comments