1
1
import { OnboardingLayout } from '@/components/onboarding/OnboardingLayout' ;
2
- import { SignOut } from '@/components/sign-out' ;
3
2
import { auth } from '@/utils/auth' ;
4
- import { Icons } from '@comp/ui/icons' ;
5
3
import { db } from '@db' ;
6
4
import { headers } from 'next/headers' ;
7
- import { notFound , redirect } from 'next/navigation' ;
5
+ import { redirect } from 'next/navigation' ;
8
6
import { AcceptInvite } from '../../setup/components/accept-invite' ;
7
+ import { InviteNotMatchCard } from './components/InviteNotMatchCard' ;
8
+ import { InviteStatusCard } from './components/InviteStatusCard' ;
9
+ import { maskEmail } from './utils' ;
9
10
10
11
interface InvitePageProps {
11
12
params : Promise < { code : string } > ;
12
13
}
13
14
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
-
29
15
export default async function InvitePage ( { params } : InvitePageProps ) {
30
16
const { code } = await params ;
31
17
const session = await auth . api . getSession ( {
32
18
headers : await headers ( ) ,
33
19
} ) ;
34
20
35
21
if ( ! session ) {
36
- // Redirect to auth with the invite code
37
22
return redirect ( `/auth?inviteCode=${ code } ` ) ;
38
23
}
39
24
40
- // Load invite by code, then verify email after
41
25
const invitation = await db . invitation . findFirst ( {
42
26
where : {
43
27
id : code ,
44
- status : 'pending' ,
45
28
} ,
46
29
include : {
47
30
organization : {
@@ -53,40 +36,47 @@ export default async function InvitePage({ params }: InvitePageProps) {
53
36
} ) ;
54
37
55
38
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
+ ) ;
57
70
}
58
71
59
- // If signed-in user email doesn't match the invited email, prompt to switch accounts
60
72
if ( invitation . email !== session . user . email ) {
61
73
return (
62
74
< OnboardingLayout variant = "setup" currentOrganization = { null } >
63
75
< 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
+ />
90
80
</ div >
91
81
</ OnboardingLayout >
92
82
) ;
0 commit comments