Initial commit from template

This commit is contained in:
Lumina
2025-12-23 04:19:57 +01:00
commit b3d8fe8dfe
76 changed files with 10491 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
'use client';
import { createContext, useContext, useEffect, useState, useCallback } from 'react';
import { User, Session, AuthError } from '@supabase/supabase-js';
import { supabase } from '@/lib/supabase';
interface AuthContextType {
user: User | null;
session: Session | null;
loading: boolean;
signUp: (email: string, password: string, metadata?: Record<string, unknown>) => Promise<{ error: AuthError | null }>;
signIn: (email: string, password: string) => Promise<{ error: AuthError | null }>;
signInWithOAuth: (provider: 'google' | 'github' | 'azure') => Promise<void>;
signOut: () => Promise<void>;
resetPassword: (email: string) => Promise<{ error: AuthError | null }>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Initiale Session holen
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setUser(session?.user ?? null);
setLoading(false);
});
// Auth State Listener
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session);
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);
const signUp = useCallback(
async (email: string, password: string, metadata?: Record<string, unknown>) => {
const { error } = await supabase.auth.signUp({
email,
password,
options: { data: metadata },
});
return { error };
},
[]
);
const signIn = useCallback(async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
return { error };
}, []);
const signInWithOAuth = useCallback(async (provider: 'google' | 'github' | 'azure') => {
await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
}, []);
const signOut = useCallback(async () => {
await supabase.auth.signOut();
}, []);
const resetPassword = useCallback(async (email: string) => {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`,
});
return { error };
}, []);
return (
<AuthContext.Provider
value={{
user,
session,
loading,
signUp,
signIn,
signInWithOAuth,
signOut,
resetPassword,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}

View File

@@ -0,0 +1,123 @@
'use client';
import { useState } from 'react';
import { useAuth } from '@/contexts/AuthContext';
interface LoginFormProps {
onSuccess?: () => void;
redirectTo?: string;
}
export function LoginForm({ onSuccess, redirectTo = '/dashboard' }: LoginFormProps) {
const { signIn, signInWithOAuth } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
const { error } = await signIn(email, password);
if (error) {
setError(getErrorMessage(error.message));
setLoading(false);
} else {
onSuccess?.();
window.location.href = redirectTo;
}
};
const handleOAuth = async (provider: 'google' | 'github') => {
setLoading(true);
await signInWithOAuth(provider);
};
return (
<div className="space-y-6 w-full max-w-sm">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Passwort
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
<button
type="submit"
disabled={loading}
className="w-full py-2 px-4 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50"
>
{loading ? 'Wird geladen...' : 'Anmelden'}
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Oder weiter mit
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<button
type="button"
onClick={() => handleOAuth('google')}
disabled={loading}
className="py-2 px-4 border rounded-md hover:bg-muted disabled:opacity-50"
>
Google
</button>
<button
type="button"
onClick={() => handleOAuth('github')}
disabled={loading}
className="py-2 px-4 border rounded-md hover:bg-muted disabled:opacity-50"
>
GitHub
</button>
</div>
</div>
);
}
function getErrorMessage(message: string): string {
const errorMap: Record<string, string> = {
'Invalid login credentials': 'Ungueltige Anmeldedaten',
'Email not confirmed': 'Bitte bestaetigen Sie zuerst Ihre Email',
'Too many requests': 'Zu viele Versuche. Bitte warten Sie einen Moment.',
};
return errorMap[message] || 'Ein Fehler ist aufgetreten';
}