SignInLayout.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { useQueryClient } from '@tanstack/react-query'
  2. import { getAccessToken, useFlag } from 'common'
  3. import { useTheme } from 'next-themes'
  4. import Link from 'next/link'
  5. import { useRouter } from 'next/router'
  6. import { PropsWithChildren, useEffect, useState } from 'react'
  7. import { tweets } from 'shared-data'
  8. import { DocsButton } from '@/components/ui/DocsButton'
  9. import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
  10. import { BASE_PATH, DOCS_URL } from '@/lib/constants'
  11. import { auth, buildPathWithParams, getReturnToPath } from '@/lib/gotrue'
  12. type SignInLayoutProps = {
  13. heading: string
  14. subheading: string
  15. showDisclaimer?: boolean
  16. logoLinkToMarketingSite?: boolean
  17. }
  18. const SignInLayout = ({
  19. heading,
  20. subheading,
  21. showDisclaimer = true,
  22. logoLinkToMarketingSite = false,
  23. children,
  24. }: PropsWithChildren<SignInLayoutProps>) => {
  25. const router = useRouter()
  26. const queryClient = useQueryClient()
  27. const { resolvedTheme } = useTheme()
  28. const ongoingIncident = useFlag('ongoingIncident')
  29. const {
  30. dashboardAuthShowTestimonial: showTestimonial,
  31. brandingLargeLogo: largeLogo,
  32. dashboardAuthShowTos: showTos,
  33. } = useIsFeatureEnabled([
  34. 'dashboard_auth:show_testimonial',
  35. 'branding:large_logo',
  36. 'dashboard_auth:show_tos',
  37. ])
  38. // This useEffect redirects the user to MFA if they're already halfway signed in
  39. useEffect(() => {
  40. auth
  41. .initialize()
  42. .then(async ({ error }) => {
  43. if (error) {
  44. // if there was a problem signing in via the url, don't redirect
  45. return
  46. }
  47. const token = await getAccessToken()
  48. if (token) {
  49. const { data, error } = await auth.mfa.getAuthenticatorAssuranceLevel()
  50. if (error) {
  51. // if there was a problem signing in via the url, don't redirect
  52. return
  53. }
  54. if (data) {
  55. // we're already where we need to be
  56. if (router.pathname === '/sign-in-mfa') {
  57. return
  58. }
  59. if (data.currentLevel !== data.nextLevel) {
  60. const redirectTo = buildPathWithParams('/sign-in-mfa')
  61. router.replace(redirectTo)
  62. return
  63. }
  64. }
  65. await queryClient.resetQueries()
  66. router.push(getReturnToPath())
  67. }
  68. })
  69. .catch(() => {}) // catch all errors thrown by auth methods
  70. }, [])
  71. const [quote, setQuote] = useState<{
  72. text: string
  73. url: string
  74. handle: string
  75. img_url: string
  76. } | null>(null)
  77. useEffect(() => {
  78. // Weighted random selection
  79. // Calculate total weight (default weight is fallbackWeight for tweets without weight specified)
  80. const fallbackWeight = 1
  81. const totalWeight = tweets.reduce((sum, tweet) => sum + (tweet.weight ?? fallbackWeight), 0)
  82. // Generate random number between 0 and totalWeight
  83. const random = Math.random() * totalWeight
  84. // Find the selected tweet based on cumulative weights
  85. let accumulatedWeight = 0
  86. for (const tweet of tweets) {
  87. const weight = tweet.weight ?? fallbackWeight
  88. accumulatedWeight += weight
  89. if (random <= accumulatedWeight) {
  90. setQuote(tweet)
  91. break
  92. }
  93. }
  94. }, [])
  95. return (
  96. <>
  97. <div className="relative flex flex-col bg-alternative min-h-screen">
  98. <div
  99. className={`absolute top-0 w-full px-8 mx-auto sm:px-6 lg:px-8 ${
  100. ongoingIncident ? 'mt-14' : 'mt-6'
  101. }`}
  102. >
  103. <nav className="relative flex items-center justify-between sm:h-10">
  104. <div className="flex items-center grow shrink-0 lg:grow-0">
  105. <div className="flex items-center justify-between w-full md:w-auto">
  106. <Link href={logoLinkToMarketingSite ? 'https://supabase.com' : '/organizations'}>
  107. <img
  108. src={
  109. resolvedTheme?.includes('dark')
  110. ? `${BASE_PATH}/img/briven-dark.svg`
  111. : `${BASE_PATH}/img/briven-light.svg`
  112. }
  113. alt="Briven Logo"
  114. className={largeLogo ? 'h-[48px]' : 'h-[24px]'}
  115. />
  116. </Link>
  117. </div>
  118. </div>
  119. <div className="items-center hidden space-x-3 md:ml-10 md:flex md:pr-4">
  120. <DocsButton abbrev={false} href={`${DOCS_URL}`} />
  121. </div>
  122. </nav>
  123. </div>
  124. <div className="flex flex-1 h-full">
  125. <main className="flex flex-col items-center flex-1 shrink-0 px-5 pt-16 pb-8 border-r shadow-lg bg-studio border-default">
  126. <div className="flex-1 flex flex-col justify-center w-[330px] sm:w-[384px]">
  127. <div className="mb-10">
  128. <h1 className="mt-8 mb-2 lg:text-3xl">{heading}</h1>
  129. <h2 className="text-sm text-foreground-light">{subheading}</h2>
  130. </div>
  131. {children}
  132. </div>
  133. {showDisclaimer && showTos && (
  134. <div className="text-center text-balance">
  135. <p className="text-xs text-foreground-lighter sm:mx-auto sm:max-w-sm">
  136. By continuing, you agree to Briven’s{' '}
  137. <Link
  138. href="https://supabase.com/terms"
  139. className="underline hover:text-foreground-light"
  140. >
  141. Terms of Service
  142. </Link>{' '}
  143. and{' '}
  144. <Link
  145. href="https://supabase.com/privacy"
  146. className="underline hover:text-foreground-light"
  147. >
  148. Privacy Policy
  149. </Link>
  150. , and to receive periodic emails with updates.
  151. </p>
  152. </div>
  153. )}
  154. </main>
  155. <aside className="flex-col items-center justify-center flex-1 shrink hidden basis-1/4 xl:flex">
  156. {quote !== null && showTestimonial && (
  157. <div className="relative flex flex-col gap-6">
  158. <div className="absolute select-none -top-12 -left-11">
  159. <span className="text-[160px] leading-none text-foreground-muted/30">{'“'}</span>
  160. </div>
  161. <blockquote className="z-10 max-w-lg text-3xl">{quote.text}</blockquote>
  162. <a
  163. href={quote.url}
  164. target="_blank"
  165. rel="noopener noreferrer"
  166. className="flex items-center gap-4"
  167. >
  168. <img
  169. src={`https://supabase.com${quote.img_url}`}
  170. alt={quote.handle}
  171. className="w-12 h-12 rounded-full"
  172. />
  173. <div className="flex flex-col">
  174. <cite className="not-italic font-medium text-foreground-light whitespace-nowrap">
  175. @{quote.handle}
  176. </cite>
  177. </div>
  178. </a>
  179. </div>
  180. )}
  181. </aside>
  182. </div>
  183. </div>
  184. </>
  185. )
  186. }
  187. export default SignInLayout