@@ -25,6 +25,7 @@ import {
2525 FormMessage ,
2626} from '~/components/ui/form' ;
2727import { Input } from '~/components/ui/input' ;
28+ import { LoadingSpinner } from '~/components/ui/spinner' ;
2829import { env } from '~/env' ;
2930import { getServerAuthSession } from '~/server/auth' ;
3031import { customServerSideTranslations } from '~/utils/i18n/server' ;
@@ -56,10 +57,12 @@ const Home: NextPage<{
5657 feedbackEmail : string ;
5758 providers : ClientSafeProvider [ ] ;
5859 callbackUrl ?: string ;
59- } > = ( { error, providers, feedbackEmail, callbackUrl } ) => {
60+ } > = ( { error, providers : serverProviders , feedbackEmail, callbackUrl } ) => {
6061 const { t } = useTranslation ( ) ;
6162 const [ emailStatus , setEmailStatus ] = useState < 'idle' | 'sending' | 'success' > ( 'idle' ) ;
6263 const [ showVerificationStep , setShowVerificationStep ] = useState ( false ) ;
64+ const [ providers , setProviders ] = useState < ClientSafeProvider [ ] > ( serverProviders ) ;
65+ const [ isLoadingProviders , setIsLoadingProviders ] = useState ( false ) ;
6366
6467 const emailForm = useForm < EmailFormValues > ( {
6568 resolver : zodResolver ( emailSchema ( t ) ) ,
@@ -68,6 +71,30 @@ const Home: NextPage<{
6871 } ,
6972 } ) ;
7073
74+ // Client-side fallback for getProviders when server-side call fails
75+ useEffect ( ( ) => {
76+ if ( serverProviders . length > 0 ) {
77+ return ;
78+ }
79+
80+ void ( async ( ) => {
81+ setIsLoadingProviders ( true ) ;
82+ try {
83+ const clientProviders = await getProviders ( ) ;
84+ if ( clientProviders && Object . keys ( clientProviders ) . length > 0 ) {
85+ setProviders ( Object . values ( clientProviders ) ) ;
86+ } else {
87+ throw new Error ( 'No providers returned from getProviders()' ) ;
88+ }
89+ } catch ( error ) {
90+ console . error ( 'Error fetching providers client-side:' , error ) ;
91+ toast . error ( t ( 'errors.no_providers' ) , { duration : 8000 } ) ;
92+ } finally {
93+ setIsLoadingProviders ( false ) ;
94+ }
95+ } ) ( ) ;
96+ } , [ serverProviders . length , t ] ) ;
97+
7198 useEffect ( ( ) => {
7299 if ( error ) {
73100 if ( 'SignupDisabled' === error ) {
@@ -148,75 +175,85 @@ const Home: NextPage<{
148175 < LanguageSelector />
149176 </ div >
150177
151- { providers . length === 0 ? (
152- < div className = "text-muted-foreground flex w-[300px] flex-col items-center gap-4 text-center" >
153- < p className = "text-lg font-semibold" > { t ( 'auth.no_providers_configured' ) } </ p >
154- < p className = "text-sm" >
155- { t ( 'auth.no_providers_instructions' ) } { ' ' }
156- < a
157- className = "text-primary underline"
158- href = "https://github.com/oss-apps/split-pro/blob/main/docker/README.md"
159- target = "_blank"
160- rel = "noopener noreferrer"
161- >
162- { t ( 'auth.setup_instructions' ) }
163- </ a >
164- .
165- </ p >
178+ { isLoadingProviders ? (
179+ < div className = "flex h-[200px] w-[300px] items-center justify-center" >
180+ < LoadingSpinner className = "h-8 w-8" />
166181 </ div >
167182 ) : (
168183 < >
169- { providers
170- . filter ( ( provider ) => 'email' !== provider . id )
171- . map ( ( provider ) => (
172- < Button
173- className = "mx-auto flex w-[300px] items-center gap-3 bg-white hover:bg-gray-100 focus:bg-gray-100"
174- onClick = { handleProviderSignIn ( provider . id ) }
175- key = { provider . id }
176- >
177- { providerTypeGuard ( provider . id ) && providerSvgs [ provider . id ] }
178- { t ( 'auth.continue_with' , { provider : provider . name } ) }
179- </ Button >
180- ) ) }
181- { providers && 2 === providers . length && (
182- < div className = "mt-6 flex w-[300px] items-center justify-between gap-2" >
183- < p className = "bg-background z-10 ml-[150px] -translate-x-1/2 px-4 text-sm" >
184- { t ( 'ui.or' ) }
184+ { providers . length === 0 ? (
185+ < div className = "text-muted-foreground flex w-[300px] flex-col items-center gap-4 text-center" >
186+ < p className = "text-lg font-semibold" > { t ( 'auth.no_providers_configured' ) } </ p >
187+ < p className = "text-sm" >
188+ { t ( 'auth.no_providers_instructions' ) } { ' ' }
189+ < a
190+ className = "text-primary underline"
191+ href = "https://github.com/oss-apps/split-pro/blob/main/docker/README.md"
192+ target = "_blank"
193+ rel = "noopener noreferrer"
194+ >
195+ { t ( 'auth.setup_instructions' ) }
196+ </ a >
197+ .
185198 </ p >
186- < div className = "absolute h-px w-[300px] bg-linear-to-r from-zinc-800 via-zinc-300 to-zinc-800" />
187199 </ div >
188- ) }
189- { providers . find ( ( provider ) => 'email' === provider . id ) ? (
200+ ) : (
190201 < >
191- < Form { ...emailForm } >
192- < form
193- onSubmit = { emailForm . handleSubmit ( onEmailSubmit ) }
194- className = "mt-6 space-y-8"
195- >
196- < FormField control = { emailForm . control } name = "email" render = { field } />
202+ { providers
203+ . filter ( ( provider ) => 'email' !== provider . id )
204+ . map ( ( provider ) => (
197205 < Button
198- className = "mt-6 w-[300px] bg-white hover:bg-gray-100 focus:bg-gray-100"
199- type = "submit"
200- disabled = { 'sending' === emailStatus }
206+ className = "mx-auto flex w-[300px] items-center gap-3 bg-white hover:bg-gray-100 focus:bg-gray-100"
207+ onClick = { handleProviderSignIn ( provider . id ) }
208+ key = { provider . id }
201209 >
202- { 'sending' === emailStatus ? t ( 'auth.sending' ) : t ( 'auth.send_magic_link' ) }
210+ { providerTypeGuard ( provider . id ) && providerSvgs [ provider . id ] }
211+ { t ( 'auth.continue_with' , { provider : provider . name } ) }
203212 </ Button >
204- </ form >
205- </ Form >
213+ ) ) }
214+ { providers && 2 === providers . length && (
215+ < div className = "mt-6 flex w-[300px] items-center justify-between gap-2" >
216+ < p className = "bg-background z-10 ml-[150px] -translate-x-1/2 px-4 text-sm" >
217+ { t ( 'ui.or' ) }
218+ </ p >
219+ < div className = "absolute h-px w-[300px] bg-linear-to-r from-zinc-800 via-zinc-300 to-zinc-800" />
220+ </ div >
221+ ) }
222+ { providers . find ( ( provider ) => 'email' === provider . id ) ? (
223+ < >
224+ < Form { ...emailForm } >
225+ < form
226+ onSubmit = { emailForm . handleSubmit ( onEmailSubmit ) }
227+ className = "mt-6 space-y-8"
228+ >
229+ < FormField control = { emailForm . control } name = "email" render = { field } />
230+ < Button
231+ className = "mt-6 w-[300px] bg-white hover:bg-gray-100 focus:bg-gray-100"
232+ type = "submit"
233+ disabled = { 'sending' === emailStatus }
234+ >
235+ { 'sending' === emailStatus
236+ ? t ( 'auth.sending' )
237+ : t ( 'auth.send_magic_link' ) }
238+ </ Button >
239+ </ form >
240+ </ Form >
241+ </ >
242+ ) : null }
206243 </ >
207- ) : null }
244+ ) }
245+ { feedbackEmail && (
246+ < p className = "text-muted-foreground mt-6 w-[300px] text-center text-sm" >
247+ { t ( 'auth.trouble_logging_in' ) }
248+ < br />
249+ { /* oxlint-disable-next-line next/no-html-link-for-pages */ }
250+ < a className = "underline" href = { feedbackEmailLink } >
251+ { feedbackEmail ?? '' }
252+ </ a >
253+ </ p >
254+ ) }
208255 </ >
209256 ) }
210- { feedbackEmail && (
211- < p className = "text-muted-foreground mt-6 w-[300px] text-center text-sm" >
212- { t ( 'auth.trouble_logging_in' ) }
213- < br />
214- { /* oxlint-disable-next-line next/no-html-link-for-pages */ }
215- < a className = "underline" href = { feedbackEmailLink } >
216- { feedbackEmail ?? '' }
217- </ a >
218- </ p >
219- ) }
220257 </ div >
221258 </ main >
222259 </ >
@@ -227,7 +264,12 @@ export default Home;
227264
228265export const getServerSideProps : GetServerSideProps = async ( context ) => {
229266 const session = await getServerAuthSession ( context ) ;
230- const providers = await getProviders ( ) ;
267+ let providers : Record < string , ClientSafeProvider > | null = null ;
268+ try {
269+ providers = await getProviders ( ) ;
270+ } catch ( error ) {
271+ console . error ( error ) ;
272+ }
231273 const { callbackUrl, error } = context . query ;
232274
233275 if ( session ) {
0 commit comments