11"use client" ;
22
3- import { FormDialog , SmartFormDialog } from "@/components/form-dialog" ;
4- import { InputField , SelectField } from "@/components/form-fields" ;
3+ import { SmartFormDialog } from "@/components/form-dialog" ;
54import { SettingCard } from "@/components/settings" ;
65import { DeleteUserDialog , ImpersonateUserDialog } from "@/components/user-dialogs" ;
76import { useThemeWatcher } from '@/lib/theme' ;
@@ -11,9 +10,9 @@ import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-
1110import { fromNow } from "@stackframe/stack-shared/dist/utils/dates" ;
1211import { throwErr } from '@stackframe/stack-shared/dist/utils/errors' ;
1312import { deindent } from "@stackframe/stack-shared/dist/utils/strings" ;
14- import { Accordion , AccordionContent , AccordionItem , AccordionTrigger , ActionCell , Avatar , AvatarFallback , AvatarImage , Button , DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuSeparator , DropdownMenuTrigger , Input , Separator , SimpleTooltip , Table , TableBody , TableCell , TableHead , TableHeader , TableRow , Typography , cn } from "@stackframe/stack-ui" ;
13+ import { ActionCell , Avatar , AvatarFallback , AvatarImage , Button , DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuSeparator , DropdownMenuTrigger , Input , Separator , SimpleTooltip , Table , TableBody , TableCell , TableHead , TableHeader , TableRow , Typography , cn } from "@stackframe/stack-ui" ;
1514import { AtSign , Calendar , Check , Hash , Mail , MoreHorizontal , Shield , SquareAsterisk , X } from "lucide-react" ;
16- import { useEffect , useMemo , useRef , useState } from "react" ;
15+ import { useMemo , useRef , useState } from "react" ;
1716import * as yup from "yup" ;
1817import { PageLayout } from "../../page-layout" ;
1918import { useAdminApp } from "../../use-admin-app" ;
@@ -168,7 +167,6 @@ type MetadataEditorProps = {
168167function MetadataEditor ( { title, initialValue, onUpdate, hint } : MetadataEditorProps ) {
169168 const formatJson = ( json : string ) => JSON . stringify ( JSON . parse ( json ) , null , 2 ) ;
170169 const [ hasChanged , setHasChanged ] = useState ( false ) ;
171- const [ isMounted , setIsMounted ] = useState ( false ) ;
172170
173171 const { mounted, theme } = useThemeWatcher ( ) ;
174172
@@ -182,14 +180,6 @@ function MetadataEditor({ title, initialValue, onUpdate, hint }: MetadataEditorP
182180 }
183181 } , [ value ] ) ;
184182
185- // Ensure proper mounting lifecycle
186- useEffect ( ( ) => {
187- setIsMounted ( true ) ;
188- return ( ) => {
189- setIsMounted ( false ) ;
190- } ;
191- } , [ ] ) ;
192-
193183 const handleSave = async ( ) => {
194184 if ( isJson ) {
195185 const formatted = formatJson ( value ) ;
@@ -199,18 +189,14 @@ function MetadataEditor({ title, initialValue, onUpdate, hint }: MetadataEditorP
199189 }
200190 } ;
201191
202- // Only render Monaco when both mounted states are true
203- const shouldRenderMonaco = mounted && isMounted ;
204-
205192 return < div className = "flex flex-col" >
206193 < h3 className = 'text-sm mb-4 font-semibold' >
207194 { title }
208195 < SimpleTooltip tooltip = { hint } type = "info" inline className = "ml-2 mb-[2px]" />
209196 </ h3 >
210- { shouldRenderMonaco ? (
197+ { mounted && (
211198 < div className = { cn ( "rounded-md overflow-hidden" , theme !== 'dark' && "border" ) } >
212199 < MonacoEditor
213- key = { `monaco-${ theme } ` } // Force recreation on theme change
214200 height = "240px"
215201 defaultLanguage = "json"
216202 value = { value }
@@ -231,10 +217,6 @@ function MetadataEditor({ title, initialValue, onUpdate, hint }: MetadataEditorP
231217 } }
232218 />
233219 </ div >
234- ) : (
235- < div className = { cn ( "rounded-md overflow-hidden h-[240px] flex items-center justify-center" , theme !== 'dark' && "border" ) } >
236- < div className = "text-sm text-muted-foreground" > Loading editor...</ div >
237- </ div >
238220 ) }
239221 < div className = { cn ( 'self-end flex items-end gap-2 transition-all h-0 opacity-0 overflow-hidden' , hasChanged && 'h-[48px] opacity-100' ) } >
240222 < Button
@@ -422,101 +404,9 @@ function AddEmailDialog({ user, open, onOpenChange }: AddEmailDialogProps) {
422404 ) ;
423405}
424406
425- type SendVerificationEmailDialogProps = {
426- channel : ServerContactChannel ,
427- open : boolean ,
428- onOpenChange : ( open : boolean ) => void ,
429- } ;
430-
431- function SendVerificationEmailDialog ( { channel, open, onOpenChange } : SendVerificationEmailDialogProps ) {
432- const stackAdminApp = useAdminApp ( ) ;
433- const project = stackAdminApp . useProject ( ) ;
434- const domains = project . config . domains ;
435-
436- return (
437- < FormDialog
438- title = "Send Verification Email"
439- description = { `Send a verification email to ${ channel . value } ? The email will contain a callback link to your domain.` }
440- open = { open }
441- onOpenChange = { onOpenChange }
442- formSchema = { yup . object ( {
443- selected : yup . string ( ) . defined ( ) ,
444- localhostPort : yup . number ( ) . test ( "required-if-localhost" , "Required if localhost is selected" , ( value , context ) => {
445- return context . parent . selected === "localhost" ? value !== undefined : true ;
446- } ) ,
447- handlerPath : yup . string ( ) . optional ( ) ,
448- } ) }
449- okButton = { {
450- label : "Send" ,
451- } }
452- render = { ( { control, watch } ) => (
453- < >
454- < SelectField
455- control = { control }
456- name = "selected"
457- label = "Domain"
458- options = { [
459- ...domains . map ( ( domain , index ) => ( { value : index . toString ( ) , label : domain . domain } ) ) ,
460- ...( project . config . allowLocalhost ? [ { value : "localhost" , label : "localhost" } ] : [ ] )
461- ] }
462- />
463- { watch ( "selected" ) === "localhost" && (
464- < >
465- < InputField
466- control = { control }
467- name = "localhostPort"
468- label = "Localhost Port"
469- placeholder = "3000"
470- type = "number"
471- />
472- < Accordion type = "single" collapsible className = "w-full" >
473- < AccordionItem value = "item-1" >
474- < AccordionTrigger > Advanced</ AccordionTrigger >
475- < AccordionContent className = "flex flex-col gap-8" >
476- < div className = "flex flex-col gap-2" >
477- < InputField
478- label = "Handler path"
479- name = "handlerPath"
480- control = { control }
481- placeholder = '/handler'
482- />
483- < Typography variant = "secondary" type = "footnote" >
484- only modify this if you changed the default handler path in your app
485- </ Typography >
486- </ div >
487- </ AccordionContent >
488- </ AccordionItem >
489- </ Accordion >
490- </ >
491- ) }
492- </ >
493- ) }
494- onSubmit = { async ( values ) => {
495- let baseUrl : string ;
496- let handlerPath : string ;
497- if ( values . selected === "localhost" ) {
498- baseUrl = `http://localhost:${ values . localhostPort } ` ;
499- handlerPath = values . handlerPath || '/handler' ;
500- } else {
501- const domain = domains [ parseInt ( values . selected ) ] ;
502- baseUrl = domain . domain ;
503- handlerPath = domain . handlerPath ;
504- }
505- const callbackUrl = new URL ( handlerPath + '/email-verification' , baseUrl ) . toString ( ) ;
506- console . log ( callbackUrl ) ;
507- await channel . sendVerificationEmail ( { callbackUrl } ) ;
508- } }
509- />
510- ) ;
511- }
512-
513407function ContactChannelsSection ( { user } : ContactChannelsSectionProps ) {
514408 const contactChannels = user . useContactChannels ( ) ;
515409 const [ isAddEmailDialogOpen , setIsAddEmailDialogOpen ] = useState ( false ) ;
516- const [ sendVerificationEmailDialog , setSendVerificationEmailDialog ] = useState < {
517- channel : ServerContactChannel ,
518- isOpen : boolean ,
519- } | null > ( null ) ;
520410
521411 const toggleUsedForAuth = async ( channel : ServerContactChannel ) => {
522412 await channel . update ( { usedForAuth : ! channel . usedForAuth } ) ;
@@ -551,18 +441,6 @@ function ContactChannelsSection({ user }: ContactChannelsSectionProps) {
551441 onOpenChange = { setIsAddEmailDialogOpen }
552442 />
553443
554- { sendVerificationEmailDialog && (
555- < SendVerificationEmailDialog
556- channel = { sendVerificationEmailDialog . channel }
557- open = { sendVerificationEmailDialog . isOpen }
558- onOpenChange = { ( open ) => {
559- if ( ! open ) {
560- setSendVerificationEmailDialog ( null ) ;
561- }
562- } }
563- />
564- ) }
565-
566444 { contactChannels . length === 0 ? (
567445 < div className = "flex flex-col items-center gap-2 p-4 border rounded-md bg-muted/10" >
568446 < p className = 'text-sm text-gray-500 text-center' >
@@ -610,10 +488,7 @@ function ContactChannelsSection({ user }: ContactChannelsSectionProps) {
610488 ...( ! channel . isVerified ? [ {
611489 item : "Send verification email" ,
612490 onClick : async ( ) => {
613- setSendVerificationEmailDialog ( {
614- channel,
615- isOpen : true ,
616- } ) ;
491+ await channel . sendVerificationEmail ( ) ;
617492 } ,
618493 } ] : [ ] ) ,
619494 {
0 commit comments