login.tsx219 lines · main
1import { useQuery } from '@tanstack/react-query'
2import { useParams } from 'common'
3import { LogOut } from 'lucide-react'
4import Head from 'next/head'
5import { useRouter } from 'next/router'
6import { useEffect } from 'react'
7import { Button } from 'ui'
8import { Admonition, ShimmeringLoader } from 'ui-patterns'
9
10import {
11 InterstitialAccountRow,
12 InterstitialLayout,
13 LogoPair,
14 PartnerLogo,
15 BrivenLogo,
16} from '@/components/layouts/InterstitialLayout'
17import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
18import { useConfirmAccountRequestMutation } from '@/data/partners/stripe-projects-confirm-mutation'
19import { accountRequestQueryOptions } from '@/data/partners/stripe-projects-query'
20import { withAuth } from '@/hooks/misc/withAuth'
21import { useSignOut } from '@/lib/auth'
22import { BASE_PATH } from '@/lib/constants'
23import { buildStudioPageTitle } from '@/lib/page-title'
24import { useProfileNameAndPicture } from '@/lib/profile'
25import type { NextPageWithLayout } from '@/types'
26
27const PAGE_TITLE = buildStudioPageTitle({ section: 'Authorize Stripe Projects', brand: 'Briven' })
28
29const StripeProjectsLoginPage: NextPageWithLayout = () => {
30 const router = useRouter()
31 const { ar_id } = useParams()
32 const signOut = useSignOut()
33 const { username, primaryEmail, avatarUrl } = useProfileNameAndPicture()
34
35 const {
36 data: accountRequest,
37 isPending: isQueryPending,
38 isSuccess: isQuerySuccess,
39 isError: isQueryError,
40 error,
41 } = useQuery({
42 ...accountRequestQueryOptions({ arId: ar_id }),
43 enabled: typeof ar_id !== 'undefined',
44 })
45
46 const {
47 mutate: confirmAccountRequest,
48 isPending: isConfirmationPending,
49 isSuccess: isConfirmationSuccess,
50 } = useConfirmAccountRequestMutation()
51
52 useEffect(() => {
53 if (!router.isReady) return
54
55 if (!ar_id) {
56 router.push('/404')
57 }
58 }, [router.isReady, ar_id, router])
59
60 const handleApprove = async () => {
61 if (!ar_id || isConfirmationPending) return
62 confirmAccountRequest({ arId: ar_id })
63 }
64
65 const linkedOrg = accountRequest?.linked_organization
66 const emailMatches = accountRequest?.email_matches ?? false
67 const displayName = primaryEmail ?? username ?? accountRequest?.email ?? ''
68 const isPending = router.isReady && isQueryPending
69 const isConfirmed = isConfirmationSuccess
70 const isConfirming = isConfirmationPending
71 const isSuccess = isQuerySuccess
72 const isError = isQueryError
73 const showAuthorizationState = isSuccess && !isConfirmed
74 const interstitialDescription = isConfirmed
75 ? undefined
76 : 'This will create an organization on your behalf in Briven'
77
78 return (
79 <>
80 <Head>
81 <title>{PAGE_TITLE}</title>
82 </Head>
83
84 <InterstitialLayout
85 logo={
86 <LogoPair
87 left={<PartnerLogo src={`${BASE_PATH}/img/icons/stripe-icon.svg`} alt="Stripe" />}
88 right={<BrivenLogo />}
89 />
90 }
91 title="Authorize Stripe Projects"
92 description={interstitialDescription}
93 >
94 <div className="px-6 pb-6">
95 {isPending && (
96 <div className="flex flex-col gap-6">
97 <div className="flex items-center gap-3 rounded-lg border border-secondary p-3">
98 <ShimmeringLoader className="size-9 flex-shrink-0 rounded-full py-0" />
99 <div className="min-w-0 flex-1 space-y-2">
100 <ShimmeringLoader className="h-3 w-20 py-0" />
101 <ShimmeringLoader className="h-4 w-40 max-w-full py-0" />
102 </div>
103 <div className="h-8 w-8 flex-shrink-0" />
104 </div>
105 <div className="flex flex-col gap-2">
106 <ShimmeringLoader className="h-10 w-full py-0" />
107 <ShimmeringLoader className="h-10 w-full py-0" />
108 </div>
109 </div>
110 )}
111
112 {isConfirmed && (
113 <div className="flex flex-col gap-4">
114 <Admonition type="success" title="Stripe Projects authorized" />
115 <p className="text-center text-xs text-foreground-lighter text-balance">
116 You can now close this tab.
117 </p>
118 </div>
119 )}
120
121 {showAuthorizationState && !emailMatches && (
122 <div className="flex flex-col gap-3">
123 <Admonition
124 type="warning"
125 description={
126 <>
127 You're signed in to a different account. Sign out and sign back in as{' '}
128 <span className="font-medium text-foreground">{accountRequest?.email}</span>.
129 Then return to Stripe to restart the request.
130 </>
131 }
132 />
133 <Button type="default" block onClick={() => signOut()}>
134 Sign out
135 </Button>
136 </div>
137 )}
138
139 {showAuthorizationState && emailMatches && linkedOrg && (
140 <div className="flex flex-col gap-3">
141 <Admonition
142 type="tip"
143 description={
144 <>
145 <span className="font-medium text-foreground">{linkedOrg.name}</span> is already
146 linked to this Stripe account, and just needs to be confirmed.
147 </>
148 }
149 />
150 <div className="flex flex-col gap-2">
151 <Button type="primary" block loading={isConfirming} onClick={handleApprove}>
152 Authorize Stripe Projects
153 </Button>
154 <Button type="text" block onClick={() => router.push('/')}>
155 Cancel
156 </Button>
157 </div>
158 </div>
159 )}
160
161 {showAuthorizationState && emailMatches && !linkedOrg && (
162 <div className="flex flex-col gap-6">
163 <InterstitialAccountRow
164 avatarUrl={avatarUrl}
165 displayName={displayName}
166 action={
167 <ButtonTooltip
168 type="text"
169 size="small"
170 className="h-8 w-8 px-0"
171 onClick={() => signOut()}
172 icon={
173 <LogOut size={16} strokeWidth={1.5} className="text-foreground-lighter" />
174 }
175 tooltip={{
176 content: {
177 side: 'top',
178 text: 'Sign out',
179 },
180 }}
181 />
182 }
183 />
184
185 <div className="flex flex-col gap-2">
186 <Button
187 type="primary"
188 loading={isConfirming}
189 disabled={isConfirming}
190 onClick={handleApprove}
191 >
192 Create organization
193 </Button>
194 <Button type="text" onClick={() => router.push('/')}>
195 Cancel
196 </Button>
197 </div>
198 </div>
199 )}
200
201 {isError && (
202 <div className="flex flex-col gap-3">
203 <Admonition
204 type="danger"
205 title="Unable to load authorization"
206 description={error?.message}
207 />
208 <Button type="default" block onClick={() => signOut()}>
209 Sign out
210 </Button>
211 </div>
212 )}
213 </div>
214 </InterstitialLayout>
215 </>
216 )
217}
218
219export default withAuth(StripeProjectsLoginPage)