11import { createFileRoute } from "@tanstack/react-router" ;
2- import { useState , type FormEvent } from "react" ;
2+ import { useEffect , useState , type FormEvent } from "react" ;
33
44import { Button } from "@executor-js/react/components/button" ;
5+ import { Card , CardDescription , CardHeader , CardTitle } from "@executor-js/react/components/card" ;
56import { Input } from "@executor-js/react/components/input" ;
67import { Label } from "@executor-js/react/components/label" ;
78
@@ -18,12 +19,37 @@ export const Route = createFileRoute("/join/$code")({
1819// auth gate (an un-redeemed visitor has no session yet).
1920function JoinPage ( ) {
2021 const { code } = Route . useParams ( ) ;
22+ const [ inviteState , setInviteState ] = useState < "checking" | "valid" | "invalid" > ( "checking" ) ;
2123 const [ name , setName ] = useState ( "" ) ;
2224 const [ email , setEmail ] = useState ( "" ) ;
2325 const [ password , setPassword ] = useState ( "" ) ;
2426 const [ error , setError ] = useState < string | null > ( null ) ;
2527 const [ busy , setBusy ] = useState ( false ) ;
2628
29+ useEffect ( ( ) => {
30+ let alive = true ;
31+ setInviteState ( "checking" ) ;
32+ void fetch ( `/api/invite-status/${ encodeURIComponent ( code ) } ` , {
33+ credentials : "same-origin" ,
34+ } ) . then (
35+ async ( response ) => {
36+ const body = response . ok
37+ ? ( ( await response . json ( ) . then (
38+ ( value ) => value ,
39+ ( ) => ( { } ) ,
40+ ) ) as { valid ?: boolean } )
41+ : { } ;
42+ if ( alive ) setInviteState ( body . valid === true ? "valid" : "invalid" ) ;
43+ } ,
44+ ( ) => {
45+ if ( alive ) setInviteState ( "invalid" ) ;
46+ } ,
47+ ) ;
48+ return ( ) => {
49+ alive = false ;
50+ } ;
51+ } , [ code ] ) ;
52+
2753 const submit = async ( event : FormEvent ) => {
2854 event . preventDefault ( ) ;
2955 setBusy ( true ) ;
@@ -43,6 +69,30 @@ function JoinPage() {
4369 window . location . href = "/" ;
4470 } ;
4571
72+ if ( inviteState === "checking" ) {
73+ return (
74+ < div className = "flex min-h-screen items-center justify-center text-sm text-muted-foreground" >
75+ Loading…
76+ </ div >
77+ ) ;
78+ }
79+
80+ if ( inviteState === "invalid" ) {
81+ return (
82+ < div className = "flex min-h-screen items-center justify-center bg-background p-6" >
83+ < Card className = "w-full max-w-md" >
84+ < CardHeader >
85+ < CardTitle > Invite not valid</ CardTitle >
86+ < CardDescription >
87+ This invite link is invalid or has expired. Ask the person who invited you for a new
88+ link.
89+ </ CardDescription >
90+ </ CardHeader >
91+ </ Card >
92+ </ div >
93+ ) ;
94+ }
95+
4696 return (
4797 < div className = "flex min-h-screen items-center justify-center bg-background p-6" >
4898 < form
0 commit comments