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