common oauth hanlders

This commit is contained in:
Nate Kelley 2025-09-04 13:46:14 -06:00
parent 6f1a60d4e5
commit 567142c7fc
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
2 changed files with 89 additions and 135 deletions

View File

@ -39,6 +39,35 @@ export const LoginForm: React.FC<{
);
const [signUpSuccess, setSignUpSuccess] = useState(false);
// Reusable OAuth handler to reduce code duplication
const handleOAuthSignIn = async (
provider: 'google' | 'github' | 'azure',
signInFn: (data: {
data: { redirectTo?: string | null };
}) => Promise<{ success: boolean; url?: string; error?: string }>
) => {
setLoading(provider);
try {
const result = await signInFn({ data: { redirectTo } });
console.log(`${provider} result:`, result);
if (result && 'success' in result && !result.success) {
setErrorMessages([result.error || `An error occurred during ${provider} sign-in`]);
setLoading(null);
return;
}
if (result && 'success' in result && result.success && result.url) {
// Redirect to OAuth provider's URL
window.location.href = result.url;
}
} catch (error: unknown) {
console.error(error);
setErrorMessages(['An unexpected error occurred. Please try again.']);
setLoading(null);
}
};
const onSignInWithUsernameAndPassword = useMemoizedFn(
async ({ email, password }: { email: string; password: string }) => {
setLoading('email');
@ -61,63 +90,15 @@ export const LoginForm: React.FC<{
);
const onSignInWithGoogle = useMemoizedFn(async () => {
setLoading('google');
try {
const result = await signInWithGoogle({ data: { redirectTo } });
console.log('result', result);
if (result && 'success' in result && !result.success) {
setErrorMessages([result.error || 'An error occurred during sign-in']);
setLoading(null);
return;
}
if (result && 'success' in result && result.success && result.url) {
// Redirect to OAuth provider's URL
window.location.href = result.url;
}
} catch (error: unknown) {
console.error(error);
setErrorMessages(['An unexpected error occurred. Please try again.']);
setLoading(null);
}
return handleOAuthSignIn('google', signInWithGoogle);
});
const onSignInWithGithub = useMemoizedFn(async () => {
setLoading('github');
try {
const result = await signInWithGithub({ data: { redirectTo } });
if (result && 'success' in result && !result.success) {
setErrorMessages([result.error]);
setLoading(null);
}
if (!result?.error) {
navigate({ to: redirectTo || '/' });
}
} catch (error: unknown) {
console.error(error);
setErrorMessages(['An unexpected error occurred. Please try again.']);
setLoading(null);
}
return handleOAuthSignIn('github', signInWithGithub);
});
const onSignInWithAzure = useMemoizedFn(async () => {
setLoading('azure');
try {
const result = await signInWithAzure({ data: { redirectTo } });
if (result && 'success' in result && !result.success) {
setErrorMessages([result.error]);
setLoading(null);
}
if (!result?.error) {
navigate({ to: redirectTo || '/' });
}
} catch (error: unknown) {
console.error(error);
setErrorMessages(['An unexpected error occurred. Please try again.']);
setLoading(null);
}
return handleOAuthSignIn('azure', signInWithAzure);
});
const onSignUp = useMemoizedFn(async (d: { email: string; password: string }) => {
@ -127,15 +108,17 @@ export const LoginForm: React.FC<{
data: { ...d, redirectTo },
});
if ((result && 'success' in result && !result.success) || result.error) {
setErrorMessages([result.error]);
if (result && 'success' in result && !result.success) {
setErrorMessages([result.error || 'An error occurred during sign-up']);
setLoading(null);
} else {
setSignUpSuccess(true);
return;
}
if (!result?.error) {
navigate({ to: redirectTo || '/' });
if (result && 'success' in result && result.success) {
setSignUpSuccess(true);
setLoading(null);
// For sign-up, we don't need to navigate immediately as the user
// needs to check their email for verification
}
} catch (error: unknown) {
console.error(error);

View File

@ -1,4 +1,3 @@
import { redirect } from '@tanstack/react-router';
import { createServerFn } from '@tanstack/react-start';
import { z } from 'zod';
import { env } from '@/env';
@ -14,6 +13,37 @@ const isValidRedirectUrl = (url: string): boolean => {
}
};
// Common OAuth handler to reduce code duplication
const handleOAuthSignIn = async (
provider: 'google' | 'github' | 'azure',
redirectTo?: string | null,
options?: Record<string, string>
) => {
const supabase = getSupabaseServerClient();
const callbackUrl = new URL(AuthCallbackRoute.to, env.VITE_PUBLIC_URL);
if (redirectTo && isValidRedirectUrl(redirectTo)) {
callbackUrl.searchParams.set('next', redirectTo);
}
const { data, error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: callbackUrl.toString(),
...options,
},
});
if (error) {
return { success: false, error: error.message };
}
console.log(`OAuth ${provider} data:`, data);
return { success: true, url: data.url };
};
export const signInWithEmailAndPassword = createServerFn({ method: 'POST' })
.validator(
z.object({
@ -40,33 +70,6 @@ export const signInWithEmailAndPassword = createServerFn({ method: 'POST' })
};
});
export const signInWithGoogle = createServerFn({ method: 'POST' })
.validator(z.object({ redirectTo: z.string().nullable().optional() }))
.handler(async ({ data: { redirectTo } }) => {
const supabase = getSupabaseServerClient();
const callbackUrl = new URL(AuthCallbackRoute.to, env.VITE_PUBLIC_URL);
if (redirectTo && isValidRedirectUrl(redirectTo)) {
callbackUrl.searchParams.set('next', redirectTo);
}
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: callbackUrl.toString(),
},
});
if (error) {
return { success: false, error: error.message };
}
console.log('OAuth data:', data);
return { success: true, url: data.url };
});
export const signInWithAnonymousUser = createServerFn({ method: 'POST' }).handler(async () => {
const supabase = getSupabaseServerClient();
const { data, error } = await supabase.auth.signInAnonymously();
@ -111,55 +114,24 @@ export const signInWithAnonymousUser = createServerFn({ method: 'POST' }).handle
};
});
export const signInWithGithub = createServerFn({ method: 'POST' })
.validator(z.object({ redirectTo: z.string().nullable().optional() }))
const oAuthRedirectValidator = z.object({ redirectTo: z.string().nullable().optional() });
export const signInWithGoogle = createServerFn({ method: 'POST' })
.validator(oAuthRedirectValidator)
.handler(async ({ data: { redirectTo } }) => {
const supabase = getSupabaseServerClient();
return handleOAuthSignIn('google', redirectTo);
});
const callbackUrl = new URL(AuthCallbackRoute.to, env.VITE_PUBLIC_URL);
if (redirectTo && isValidRedirectUrl(redirectTo)) {
callbackUrl.searchParams.set('next', redirectTo);
}
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: callbackUrl.toString(),
},
});
if (error) {
return { success: false, error: error.message };
}
throw redirect({ to: data.url });
export const signInWithGithub = createServerFn({ method: 'POST' })
.validator(oAuthRedirectValidator)
.handler(async ({ data: { redirectTo } }) => {
return handleOAuthSignIn('github', redirectTo);
});
export const signInWithAzure = createServerFn({ method: 'POST' })
.validator(z.object({ redirectTo: z.string().nullable().optional() }))
.validator(oAuthRedirectValidator)
.handler(async ({ data: { redirectTo } }) => {
const supabase = getSupabaseServerClient();
const callbackUrl = new URL(AuthCallbackRoute.to, env.VITE_PUBLIC_URL);
if (redirectTo && isValidRedirectUrl(redirectTo)) {
callbackUrl.searchParams.set('next', redirectTo);
}
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
redirectTo: callbackUrl.toString(),
scopes: 'email',
},
});
if (error) {
return { success: false, error: error.message };
}
throw redirect({ to: data.url });
return handleOAuthSignIn('azure', redirectTo, { scopes: 'email' });
});
export const signUpWithEmailAndPassword = createServerFn({ method: 'POST' })
@ -173,9 +145,7 @@ export const signUpWithEmailAndPassword = createServerFn({ method: 'POST' })
.handler(async ({ data }) => {
const supabase = getSupabaseServerClient();
const authURLFull = `${AuthCallbackRoute.to}`;
console.log(data);
console.log('Sign up data:', data);
const { error } = await supabase.auth.signUp({
email: data.email,
@ -185,7 +155,7 @@ export const signUpWithEmailAndPassword = createServerFn({ method: 'POST' })
},
});
console.log('error', error);
console.log('Sign up error:', error);
if (error) {
return {
@ -194,7 +164,8 @@ export const signUpWithEmailAndPassword = createServerFn({ method: 'POST' })
};
}
console.log('authURLFull', authURLFull);
throw redirect({ to: authURLFull });
return {
success: true,
redirectTo: AuthCallbackRoute.to,
};
});