@@ -16,16 +16,27 @@ const email = ref("");
1616const password = ref (" " );
1717const error = ref (" " );
1818const isLoading = ref (false );
19+ const socialLoading = ref <string | null >(null );
1920const ssoRedirecting = ref (false );
2021const route = useRoute ();
2122const config = useRuntimeConfig ();
2223const localePath = useLocalePath ();
2324const { track } = useTrack ();
24- const oidcEnabled = computed (() => config .public .oidcEnabled as boolean );
25+
26+ const { data : authProviders } = await useFetch (' /api/auth/providers' );
27+ const oidcEnabled = computed (() => authProviders .value ?.oidc ?? false );
2528const oidcProviderName = computed (
26- () => ( config . public .oidcProviderName as string ) || " SSO" ,
29+ () => authProviders . value ? .oidcProviderName || " SSO" ,
2730);
2831
32+ const socialProviders = computed (() => {
33+ const providers: { id: string ; name: string }[] = [];
34+ if (authProviders .value ?.google ) providers .push ({ id: " google" , name: " Google" });
35+ if (authProviders .value ?.github ) providers .push ({ id: " github" , name: " GitHub" });
36+ if (authProviders .value ?.microsoft ) providers .push ({ id: " microsoft" , name: " Microsoft" });
37+ return providers ;
38+ });
39+
2940onMounted (() => track (" signin_page_viewed" ));
3041
3142if (route .query .live === " 1" ) {
@@ -163,6 +174,31 @@ async function handleEnterpriseSso() {
163174 ssoRedirecting .value = false ;
164175 }
165176}
177+
178+ /**
179+ * Social sign-in — Google, GitHub, Microsoft.
180+ * Uses better-auth's built-in signIn.social() which handles the full OAuth redirect flow.
181+ */
182+ async function handleSocialSignIn(providerId : string ) {
183+ socialLoading .value = providerId ;
184+ error .value = " " ;
185+ const pendingInvitation = route .query .invitation as string | undefined ;
186+ const callbackURL = pendingInvitation
187+ ? localePath (` /auth/accept-invitation/${pendingInvitation } ` )
188+ : localePath (" /dashboard" );
189+ try {
190+ await authClient .signIn .social ({
191+ provider: providerId as " google" | " github" | " microsoft" ,
192+ callbackURL ,
193+ });
194+ } catch (e : unknown ) {
195+ error .value =
196+ e instanceof Error
197+ ? e .message
198+ : " Social sign-in failed. Please try again." ;
199+ socialLoading .value = null ;
200+ }
201+ }
166202 </script >
167203
168204<template >
@@ -180,12 +216,61 @@ async function handleEnterpriseSso() {
180216 {{ error }}
181217 </div >
182218
219+ <!-- Social sign-in providers (Google, GitHub, Microsoft) -->
220+ <template v-if =" socialProviders .length " >
221+ <div class =" flex flex-col gap-2.5" >
222+ <button
223+ v-for =" provider in socialProviders"
224+ :key =" provider.id"
225+ type =" button"
226+ :disabled =" !!socialLoading || isLoading || ssoRedirecting"
227+ class =" relative px-4 py-2.5 rounded-lg text-sm font-medium shadow-sm transition-all flex items-center justify-center gap-3 cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed border border-surface-200 dark:border-surface-700 bg-white dark:bg-surface-800 text-surface-800 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-700 hover:border-surface-300 dark:hover:border-surface-600"
228+ @click =" handleSocialSignIn(provider.id)"
229+ >
230+ <template v-if =" socialLoading === provider .id " >
231+ <svg class =" animate-spin size-4 text-surface-400" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" ><circle class =" opacity-25" cx =" 12" cy =" 12" r =" 10" stroke =" currentColor" stroke-width =" 4" /><path class =" opacity-75" fill =" currentColor" d =" M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /></svg >
232+ Redirecting…
233+ </template >
234+ <template v-else >
235+ <!-- Google icon -->
236+ <svg v-if =" provider.id === 'google'" class =" size-5" viewBox =" 0 0 24 24" xmlns =" http://www.w3.org/2000/svg" >
237+ <path d =" M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill =" #4285F4" />
238+ <path d =" M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill =" #34A853" />
239+ <path d =" M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill =" #FBBC05" />
240+ <path d =" M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill =" #EA4335" />
241+ </svg >
242+ <!-- GitHub icon -->
243+ <svg v-else-if =" provider.id === 'github'" class =" size-5 text-surface-900 dark:text-surface-100" viewBox =" 0 0 24 24" fill =" currentColor" xmlns =" http://www.w3.org/2000/svg" >
244+ <path d =" M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
245+ </svg >
246+ <!-- Microsoft icon -->
247+ <svg v-else-if =" provider.id === 'microsoft'" class =" size-5" viewBox =" 0 0 23 23" xmlns =" http://www.w3.org/2000/svg" >
248+ <rect x =" 1" y =" 1" width =" 10" height =" 10" fill =" #F25022" />
249+ <rect x =" 12" y =" 1" width =" 10" height =" 10" fill =" #7FBA00" />
250+ <rect x =" 1" y =" 12" width =" 10" height =" 10" fill =" #00A4EF" />
251+ <rect x =" 12" y =" 12" width =" 10" height =" 10" fill =" #FFB900" />
252+ </svg >
253+ Continue with {{ provider.name }}
254+ </template >
255+ </button >
256+ </div >
257+
258+ <div v-if =" !oidcEnabled" class =" relative" >
259+ <div class =" absolute inset-0 flex items-center" >
260+ <div class =" w-full border-t border-surface-200 dark:border-surface-700" />
261+ </div >
262+ <div class =" relative flex justify-center text-xs" >
263+ <span class =" bg-white dark:bg-surface-900 px-2 text-surface-400" >or continue with email</span >
264+ </div >
265+ </div >
266+ </template >
267+
183268 <!-- Self-hosted OIDC SSO — only shown when global OIDC is configured via environment variables -->
184269 <template v-if =" oidcEnabled " >
185270 <button
186271 type =" button"
187272 :disabled =" isLoading"
188- class =" px-4 py-2.5 bg-surface-900 dark:bg-white text-white dark:text-surface-900 rounded-lg text-sm font-semibold shadow-md hover:bg-surface-800 dark:hover:bg-surface-100 disabled:opacity-60 disabled:cursor-not-allowed transition-all flex items-center justify-center gap-2.5 ring-1 ring-surface-700 dark:ring-surface-300"
273+ class =" px-4 py-2.5 bg-surface-900 dark:bg-white text-white dark:text-surface-900 rounded-lg text-sm font-semibold shadow-md cursor-pointer hover:bg-surface-800 dark:hover:bg-surface-100 disabled:opacity-60 disabled:cursor-not-allowed transition-all flex items-center justify-center gap-2.5 ring-1 ring-surface-700 dark:ring-surface-300"
189274 @click =" handleSelfHostedSso"
190275 >
191276 <template v-if =" isLoading " >Redirecting…</template >
@@ -240,7 +325,7 @@ async function handleEnterpriseSso() {
240325 <button
241326 type =" submit"
242327 :disabled =" isLoading"
243- class =" mt-2 px-4 py-2.5 bg-brand-600 text-white rounded-md text-sm font-medium hover:bg-brand-700 disabled:opacity-60 disabled:cursor-not-allowed transition-colors"
328+ class =" mt-2 px-4 py-2.5 bg-brand-600 text-white rounded-md text-sm font-medium cursor-pointer hover:bg-brand-700 disabled:opacity-60 disabled:cursor-not-allowed transition-colors"
244329 >
245330 {{ isLoading ? "Signing in…" : "Sign in" }}
246331 </button >
@@ -264,7 +349,7 @@ async function handleEnterpriseSso() {
264349 <button
265350 type =" button"
266351 :disabled =" ssoRedirecting"
267- class =" px-4 py-2.5 bg-surface-900 dark:bg-white text-white dark:text-surface-900 rounded-lg text-sm font-semibold shadow-md hover:bg-surface-800 dark:hover:bg-surface-100 disabled:opacity-60 disabled:cursor-not-allowed transition-all flex items-center justify-center gap-2.5 ring-1 ring-surface-700 dark:ring-surface-300"
352+ class =" px-4 py-2.5 bg-surface-900 dark:bg-white text-white dark:text-surface-900 rounded-lg text-sm font-semibold shadow-md cursor-pointer hover:bg-surface-800 dark:hover:bg-surface-100 disabled:opacity-60 disabled:cursor-not-allowed transition-all flex items-center justify-center gap-2.5 ring-1 ring-surface-700 dark:ring-surface-300"
268353 @click =" handleEnterpriseSso"
269354 >
270355 <ShieldCheck class =" size-4" />
0 commit comments