11import { OnboardingLayout } from '@/components/onboarding/OnboardingLayout' ;
2- import { SignOut } from '@/components/sign-out' ;
32import { auth } from '@/utils/auth' ;
4- import { Icons } from '@comp/ui/icons' ;
53import { db } from '@db' ;
64import { headers } from 'next/headers' ;
7- import { notFound , redirect } from 'next/navigation' ;
5+ import { redirect } from 'next/navigation' ;
86import { AcceptInvite } from '../../setup/components/accept-invite' ;
7+ import { InviteNotMatchCard } from './components/InviteNotMatchCard' ;
8+ import { InviteStatusCard } from './components/InviteStatusCard' ;
9+ import { maskEmail } from './utils' ;
910
1011interface InvitePageProps {
1112 params : Promise < { code : string } > ;
1213}
1314
14- const maskEmail = ( email : string ) => {
15- const [ local , domain ] = email . split ( '@' ) ;
16- if ( ! domain ) return email ;
17- const maskedLocal =
18- local . length <= 2 ? `${ local [ 0 ] ?? '' } ***` : `${ local [ 0 ] } ***${ local . slice ( - 1 ) } ` ;
19-
20- const domainParts = domain . split ( '.' ) ;
21- if ( domainParts . length === 0 ) return `${ maskedLocal } @***` ;
22- const tld = domainParts [ domainParts . length - 1 ] ;
23- const secondLevel = domainParts . length >= 2 ? domainParts [ domainParts . length - 2 ] : '' ;
24- const maskedSecondLevel = secondLevel ? `${ secondLevel [ 0 ] } ***` : '***' ;
25-
26- return `${ maskedLocal } @${ maskedSecondLevel } .${ tld } ` ;
27- } ;
28-
2915export default async function InvitePage ( { params } : InvitePageProps ) {
3016 const { code } = await params ;
3117 const session = await auth . api . getSession ( {
3218 headers : await headers ( ) ,
3319 } ) ;
3420
3521 if ( ! session ) {
36- // Redirect to auth with the invite code
3722 return redirect ( `/auth?inviteCode=${ code } ` ) ;
3823 }
3924
40- // Load invite by code, then verify email after
4125 const invitation = await db . invitation . findFirst ( {
4226 where : {
4327 id : code ,
44- status : 'pending' ,
4528 } ,
4629 include : {
4730 organization : {
@@ -53,40 +36,47 @@ export default async function InvitePage({ params }: InvitePageProps) {
5336 } ) ;
5437
5538 if ( ! invitation ) {
56- notFound ( ) ;
39+ return (
40+ < OnboardingLayout variant = "setup" currentOrganization = { null } >
41+ < div className = "flex min-h-[calc(100dvh-80px)] w-full items-center justify-center p-4" >
42+ < InviteStatusCard
43+ title = "Invite not found"
44+ description = "This invitation code does not exist. Please check the link or ask your admin to resend the invite."
45+ primaryHref = "/"
46+ primaryLabel = "Go home"
47+ />
48+ </ div >
49+ </ OnboardingLayout >
50+ ) ;
51+ }
52+
53+ if ( invitation . status !== 'pending' ) {
54+ return (
55+ < OnboardingLayout variant = "setup" currentOrganization = { null } >
56+ < div className = "flex min-h-[calc(100dvh-80px)] w-full items-center justify-center p-4" >
57+ < InviteStatusCard
58+ title = { invitation . status === 'accepted' ? 'Invite already accepted' : 'Invite expired' }
59+ description = {
60+ invitation . status === 'accepted'
61+ ? 'This invitation has already been accepted. If you believe this is a mistake, contact your organization admin.'
62+ : 'This invitation has expired. Please ask your organization admin to send a new invite.'
63+ }
64+ primaryHref = "/"
65+ primaryLabel = "Go home"
66+ />
67+ </ div >
68+ </ OnboardingLayout >
69+ ) ;
5770 }
5871
59- // If signed-in user email doesn't match the invited email, prompt to switch accounts
6072 if ( invitation . email !== session . user . email ) {
6173 return (
6274 < OnboardingLayout variant = "setup" currentOrganization = { null } >
6375 < div className = "flex min-h-[calc(100dvh-80px)] w-full items-center justify-center p-4" >
64- < div className = "bg-card relative w-full max-w-[480px] rounded-sm border p-10 shadow-lg" >
65- < div className = "flex flex-col items-center gap-6 text-center" >
66- < Icons . Logo />
67- < h1 className = "text-2xl font-semibold tracking-tight" > Wrong account</ h1 >
68- < div className = "mx-auto max-w-[42ch] text-muted-foreground leading-relaxed flex flex-col gap-4" >
69- < div className = "space-y-2 text-sm" >
70- < p >
71- You are signed in as
72- < span className = "mx-1 inline-flex items-center rounded-xs border border-muted bg-muted/40 px-2 py-0.5 text-sm" >
73- { session . user . email }
74- </ span >
75- </ p >
76- < p >
77- This invite is for
78- < span className = "mx-1 inline-flex items-center rounded-xs border border-muted bg-muted/40 px-2 py-0.5 text-sm" >
79- { maskEmail ( invitation . email ) }
80- </ span >
81- </ p >
82- </ div >
83- < p className = "text-base font-medium" >
84- To accept, sign out and sign back in with the invited email.
85- </ p >
86- </ div >
87- < SignOut asButton className = "w-full" />
88- </ div >
89- </ div >
76+ < InviteNotMatchCard
77+ currentEmail = { session . user . email }
78+ invitedEmail = { maskEmail ( invitation . email ) }
79+ />
9080 </ div >
9181 </ OnboardingLayout >
9282 ) ;
0 commit comments