feat: Add EU cookie banner and user settings page
Cookie Banner: - GDPR-compliant consent banner - Shows on first visit, stores consent in localStorage - Two options: Accept All / Only Essential - Dark theme with smooth animations Settings Page (/settings): - Profile form: display name editing - Password change with validation - Cookie settings info section - Privacy links and account info New files: - src/hooks/useCookieConsent.ts - src/components/CookieBanner.tsx - src/components/ProfileForm.tsx - src/components/PasswordChangeForm.tsx - src/services/profile-actions.ts - src/app/settings/page.tsx
This commit is contained in:
150
src/services/profile-actions.ts
Normal file
150
src/services/profile-actions.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
'use server';
|
||||
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
|
||||
export interface ProfileUpdateResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user profile (display_name, avatar_url)
|
||||
*/
|
||||
export async function updateProfile(formData: FormData): Promise<ProfileUpdateResult> {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return { success: false, error: 'Nicht autorisiert' };
|
||||
}
|
||||
|
||||
const displayName = formData.get('display_name') as string;
|
||||
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.upsert({
|
||||
id: user.id,
|
||||
display_name: displayName?.trim() || null,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('[updateProfile] Error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
|
||||
revalidatePath('/settings');
|
||||
return { success: true };
|
||||
|
||||
} catch (error) {
|
||||
console.error('[updateProfile] Error:', error);
|
||||
return { success: false, error: 'Profil konnte nicht aktualisiert werden' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change user password
|
||||
*/
|
||||
export async function changePassword(formData: FormData): Promise<ProfileUpdateResult> {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return { success: false, error: 'Nicht autorisiert' };
|
||||
}
|
||||
|
||||
const newPassword = formData.get('new_password') as string;
|
||||
const confirmPassword = formData.get('confirm_password') as string;
|
||||
|
||||
if (!newPassword || newPassword.length < 6) {
|
||||
return { success: false, error: 'Passwort muss mindestens 6 Zeichen lang sein' };
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
return { success: false, error: 'Passwörter stimmen nicht überein' };
|
||||
}
|
||||
|
||||
const { error } = await supabase.auth.updateUser({
|
||||
password: newPassword
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('[changePassword] Error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
|
||||
} catch (error) {
|
||||
console.error('[changePassword] Error:', error);
|
||||
return { success: false, error: 'Passwort konnte nicht geändert werden' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile data
|
||||
*/
|
||||
export async function getProfile() {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('id, display_name, avatar_url, created_at, updated_at')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
display_name: profile?.display_name || null,
|
||||
avatar_url: profile?.avatar_url || null,
|
||||
created_at: user.created_at,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('[getProfile] Error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user account (marks for deletion, doesn't immediately delete)
|
||||
*/
|
||||
export async function requestAccountDeletion(): Promise<ProfileUpdateResult> {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return { success: false, error: 'Nicht autorisiert' };
|
||||
}
|
||||
|
||||
// Mark profile for deletion (actual deletion handled separately)
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
deletion_requested_at: new Date().toISOString(),
|
||||
})
|
||||
.eq('id', user.id);
|
||||
|
||||
if (error) {
|
||||
console.error('[requestAccountDeletion] Error:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
|
||||
} catch (error) {
|
||||
console.error('[requestAccountDeletion] Error:', error);
|
||||
return { success: false, error: 'Löschanfrage konnte nicht erstellt werden' };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user