login.tsx 7.4 KB

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