1+ 'use client' ;
2+
3+ import { Card , CardHeader , CardTitle , CardContent } from "@/components/ui/card" ;
4+ import { SourcebotLogo } from "@/app/components/sourcebotLogo" ;
5+ import Link from "next/link" ;
6+ import { Avatar , AvatarImage } from "@/components/ui/avatar" ;
7+ import placeholderAvatar from "@/public/placeholder_avatar.png" ;
8+ import { ArrowRight , Loader2 } from "lucide-react" ;
9+ import { Button } from "@/components/ui/button" ;
10+ import { useCallback , useState } from "react" ;
11+ import { redeemInvite } from "@/actions" ;
12+ import { useRouter } from "next/navigation" ;
13+ import { useToast } from "@/components/hooks/use-toast" ;
14+ import { isServiceError } from "@/lib/utils" ;
15+
16+ interface AcceptInviteCardProps {
17+ inviteId : string ;
18+ orgName : string ;
19+ orgDomain : string ;
20+ orgImageUrl ?: string ;
21+ host : {
22+ name ?: string ;
23+ email : string ;
24+ avatarUrl ?: string ;
25+ } ;
26+ recipient : {
27+ name ?: string ;
28+ email : string ;
29+ } ;
30+ }
31+
32+ export const AcceptInviteCard = ( { inviteId, orgName, orgDomain, orgImageUrl, host, recipient } : AcceptInviteCardProps ) => {
33+ const [ isLoading , setIsLoading ] = useState ( false ) ;
34+ const router = useRouter ( ) ;
35+ const { toast } = useToast ( ) ;
36+
37+ const onRedeemInvite = useCallback ( ( ) => {
38+ setIsLoading ( true ) ;
39+ redeemInvite ( inviteId )
40+ . then ( ( response ) => {
41+ if ( isServiceError ( response ) ) {
42+ toast ( {
43+ description : `Failed to redeem invite with error: ${ response . message } ` ,
44+ variant : "destructive" ,
45+ } ) ;
46+ } else {
47+ toast ( {
48+ description : `✅ You are now a member of the ${ orgName } organization.` ,
49+ } ) ;
50+ router . push ( `/${ orgDomain } ` ) ;
51+ }
52+ } )
53+ . finally ( ( ) => {
54+ setIsLoading ( false ) ;
55+ } ) ;
56+ } , [ inviteId , orgDomain , orgName , router , toast ] ) ;
57+
58+ return (
59+ < Card className = "p-12 max-w-lg" >
60+ < CardHeader className = "text-center" >
61+ < SourcebotLogo
62+ className = "h-16 w-auto mx-auto mb-2"
63+ size = "small"
64+ />
65+ < CardTitle className = "font-medium" >
66+ Join < strong > { orgName } </ strong >
67+ </ CardTitle >
68+ </ CardHeader >
69+ < CardContent className = "mt-3" >
70+ < p >
71+ Hello { recipient . name ?. split ( ' ' ) [ 0 ] ?? recipient . email } ,
72+ </ p >
73+ < p className = "mt-5" >
74+ < InvitedByText email = { host . email } name = { host . name } /> invited you to join the < strong > { orgName } </ strong > organization.
75+ </ p >
76+ < div className = "flex fex-row items-center justify-center gap-2 mt-12" >
77+ < Avatar className = "w-14 h-14" >
78+ < AvatarImage src = { host . avatarUrl ?? placeholderAvatar . src } />
79+ </ Avatar >
80+ < ArrowRight className = "w-4 h-4 text-muted-foreground" />
81+ < Avatar className = "w-14 h-14" >
82+ < AvatarImage src = { orgImageUrl ?? placeholderAvatar . src } />
83+ </ Avatar >
84+ </ div >
85+ < Button
86+ className = "mt-12 mx-auto w-full"
87+ disabled = { isLoading }
88+ onClick = { onRedeemInvite }
89+ >
90+ { isLoading && < Loader2 className = "w-4 h-4 mr-2 animate-spin" /> }
91+ Accept Invite
92+ </ Button >
93+ </ CardContent >
94+ </ Card >
95+ )
96+ }
97+
98+ const InvitedByText = ( { email, name } : { email : string , name ?: string } ) => {
99+ const emailElement = < Link href = { `mailto:${ email } ` } className = "text-blue-500 hover:text-blue-600" >
100+ { email }
101+ </ Link > ;
102+
103+ if ( name ) {
104+ const firstName = name . split ( ' ' ) [ 0 ] ;
105+ return < span > < strong > { firstName } </ strong > ({ emailElement } )</ span > ;
106+ }
107+
108+ return emailElement ;
109+ }
0 commit comments