diff --git a/public/icons/microsoft.png b/public/icons/microsoft.png new file mode 100644 index 0000000..2ee14f0 Binary files /dev/null and b/public/icons/microsoft.png differ diff --git a/src/app/(auth-pages)/auth/callback/route.ts b/src/app/(auth-pages)/auth/callback/route.ts index a2ec361..24089c1 100644 --- a/src/app/(auth-pages)/auth/callback/route.ts +++ b/src/app/(auth-pages)/auth/callback/route.ts @@ -1,5 +1,5 @@ +// src/app/(auth-pages)/auth/callback/route.ts 'use server'; - import 'server-only'; import { createServerClient } from '@/utils/supabase'; import { type EmailOtpType } from '@supabase/supabase-js'; @@ -7,13 +7,26 @@ import { type NextRequest } from 'next/server'; import { redirect } from 'next/navigation'; export const GET = async (request: NextRequest) => { - const { searchParams } = new URL(request.url); + const { searchParams, origin } = new URL(request.url); + const code = searchParams.get('code'); const token_hash = searchParams.get('token'); const type = searchParams.get('type') as EmailOtpType | null; const redirectTo = searchParams.get('redirect_to') ?? '/'; + const supabase = await createServerClient(); + + // Handle OAuth callback (Microsoft, Apple, etc.) + if (code) { + const { error } = await supabase.auth.exchangeCodeForSession(code); + if (error) { + console.error('OAuth error:', error); + return redirect(`/sign-in?error=${encodeURIComponent(error.message)}`); + } + return redirect(redirectTo); + } + + // Handle email OTP (existing logic) if (token_hash && type) { - const supabase = await createServerClient(); const { error } = await supabase.auth.verifyOtp({ type, token_hash, @@ -25,9 +38,9 @@ export const GET = async (request: NextRequest) => { return redirect('/profile'); if (type === 'invite') return redirect('/sign-up'); - else return redirect(`/?Could not identify type ${type as string}`) } - else return redirect(`/?${error.message}`); + return redirect(`/?error=${encodeURIComponent(error?.message || 'Unknown error')}`); } + return redirect('/'); }; diff --git a/src/app/(auth-pages)/forgot-password/page.tsx b/src/app/(auth-pages)/forgot-password/page.tsx index 6a3f5a1..721f4de 100644 --- a/src/app/(auth-pages)/forgot-password/page.tsx +++ b/src/app/(auth-pages)/forgot-password/page.tsx @@ -71,7 +71,7 @@ const ForgotPassword = () => { }; return ( - + Reset Password diff --git a/src/app/(auth-pages)/sign-in/page.tsx b/src/app/(auth-pages)/sign-in/page.tsx index ab1e508..92c06c4 100644 --- a/src/app/(auth-pages)/sign-in/page.tsx +++ b/src/app/(auth-pages)/sign-in/page.tsx @@ -18,11 +18,13 @@ import { Input, } from '@/components/ui'; import Link from 'next/link'; -import { signIn } from '@/lib/actions'; +import { signIn, signInWithMicrosoft } from '@/lib/actions'; import { useRouter } from 'next/navigation'; import { useAuth } from '@/components/context/auth'; import { useEffect, useState } from 'react'; import { StatusMessage, SubmitButton } from '@/components/default'; +import { Separator } from '@/components/ui'; +import { SignInWithMicrosoft } from '@/components/default/auth/SignInWithMicrosoft'; const formSchema = z.object({ email: z.string().email({ @@ -91,7 +93,7 @@ const Login = () => {
{ - + +
); diff --git a/src/components/default/status-message.tsx b/src/components/default/StatusMessage.tsx similarity index 100% rename from src/components/default/status-message.tsx rename to src/components/default/StatusMessage.tsx diff --git a/src/components/default/submit-button.tsx b/src/components/default/SubmitButton.tsx similarity index 100% rename from src/components/default/submit-button.tsx rename to src/components/default/SubmitButton.tsx diff --git a/src/components/default/auth/SignInWithMicrosoft.tsx b/src/components/default/auth/SignInWithMicrosoft.tsx new file mode 100644 index 0000000..1334a27 --- /dev/null +++ b/src/components/default/auth/SignInWithMicrosoft.tsx @@ -0,0 +1,59 @@ +// src/components/default/auth/SignInWithMicrosoft.tsx +'use client'; +import { signInWithMicrosoft } from '@/lib/actions'; +import { StatusMessage, SubmitButton } from '@/components/default'; +import { useAuth } from '@/components/context/auth'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import Image from 'next/image'; + +export const SignInWithMicrosoft = () => { + const router = useRouter(); + const { isLoading, refreshUserData } = useAuth(); + const [statusMessage, setStatusMessage] = useState(''); + const [isSigningIn, setIsSigningIn] = useState(false); + + const handleSignInWithMicrosoft = async (e: React.FormEvent) => { + e.preventDefault(); + try { + setStatusMessage(''); + setIsSigningIn(true); + + const result = await signInWithMicrosoft(); + + if (result?.success && result.data) { + // Redirect to Microsoft OAuth page + window.location.href = result.data; + } else { + setStatusMessage(`Error: ${result.error}`); + } + } catch (error) { + setStatusMessage( + `Error: ${error instanceof Error ? error.message : 'Could not sign in!'}` + ); + } finally { + setIsSigningIn(false); + await refreshUserData(); + router.push(''); + } + }; + + return ( +
+ +
+ Microsoft logo +

Sign in with Microsoft

+
+
+ {statusMessage && ( + + )} + + ); +}; diff --git a/src/components/default/index.tsx b/src/components/default/index.tsx index dfb263d..ecfdfcc 100644 --- a/src/components/default/index.tsx +++ b/src/components/default/index.tsx @@ -1,2 +1,2 @@ -export { StatusMessage, type Message } from '@/components/default/status-message'; -export { SubmitButton } from '@/components/default/submit-button'; +export { StatusMessage, type Message } from '@/components/default/StatusMessage'; +export { SubmitButton } from '@/components/default/SubmitButton'; diff --git a/src/components/default/tutorial/code-block.tsx b/src/components/default/tutorial/CodeBlock.tsx similarity index 100% rename from src/components/default/tutorial/code-block.tsx rename to src/components/default/tutorial/CodeBlock.tsx diff --git a/src/components/default/tutorial/fetch-data-steps.tsx b/src/components/default/tutorial/FetchDataSteps.tsx similarity index 100% rename from src/components/default/tutorial/fetch-data-steps.tsx rename to src/components/default/tutorial/FetchDataSteps.tsx diff --git a/src/components/default/tutorial/tutorial-step.tsx b/src/components/default/tutorial/TutorialStep.tsx similarity index 100% rename from src/components/default/tutorial/tutorial-step.tsx rename to src/components/default/tutorial/TutorialStep.tsx diff --git a/src/components/default/tutorial/index.tsx b/src/components/default/tutorial/index.tsx index 024de97..b2a82b0 100644 --- a/src/components/default/tutorial/index.tsx +++ b/src/components/default/tutorial/index.tsx @@ -1,3 +1,3 @@ -export { CodeBlock } from './code-block'; -export { FetchDataSteps } from './fetch-data-steps'; -export { TutorialStep } from './tutorial-step'; +export { CodeBlock } from './CodeBlock'; +export { FetchDataSteps } from './FetchDataSteps'; +export { TutorialStep } from './TutorialStep'; diff --git a/src/lib/actions/auth.ts b/src/lib/actions/auth.ts index 227d1b9..49483e4 100644 --- a/src/lib/actions/auth.ts +++ b/src/lib/actions/auth.ts @@ -62,6 +62,18 @@ export const signIn = async ( } }; +export const signInWithMicrosoft = async (): Promise> => { + const supabase = await createServerClient(); + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: 'azure', + options: { + scopes: 'email', + } + }); + if (error) return { success: false, error: error.message }; + return { success: true, data: data.url}; +}; + export const forgotPassword = async (formData: FormData): Promise> => { const email = formData.get('email') as string; const supabase = await createServerClient();