fix: production login error masking, resolve navbar hydration mismatch, and gracefully handle database render errors
All checks were successful
Staging Build / build (push) Successful in 2m15s

This commit is contained in:
DanielS
2026-06-24 10:54:00 +02:00
parent e1f47ddcd0
commit 5c8c198fc5
4 changed files with 96 additions and 41 deletions

View File

@@ -2,6 +2,7 @@ export const dynamic = 'force-dynamic';
import { redirect } from 'next/navigation'
import Link from 'next/link'
import { getEndCustomers } from '@/lib/actions/end-customers'
import type { EndCustomer } from '@/lib/types'
import { ArrowLeft, Building2, Plus, Edit2, AlertTriangle } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
@@ -13,10 +14,20 @@ export default async function MyCustomersPage() {
const { data: { user } } = await supabase.auth.getUser()
if (!user) redirect('/auth/login')
const customers = await getEndCustomers()
let customers: EndCustomer[] = []
let fetchError: string | null = null
try {
customers = await getEndCustomers()
} catch (err: any) {
console.error("Error loading end customers:", err)
fetchError = err.message || "Es gab ein Problem beim Laden Ihrer Endkunden."
}
// Anzahl Bestellungen pro Endkunde
const { data: orderCounts } = await supabase.from('orders').select('end_customer_id').not('end_customer_id', 'is', null);
const { data: orderCounts, error: orderError } = await supabase.from('orders').select('end_customer_id').not('end_customer_id', 'is', null);
if (orderError) {
console.error("Error loading order counts:", orderError)
}
const countMap: Record<string, number> = {}
;(orderCounts ?? []).forEach(o => {
@@ -53,6 +64,12 @@ export default async function MyCustomersPage() {
</Link>
</div>
{fetchError && (
<div className="p-4 rounded-xl bg-red-500/10 border border-red-500/20 text-red-200 text-sm">
{fetchError}
</div>
)}
{/* Keine Kunden */}
{customers.length === 0 && (
<Card className="glass-dark border-white/10">

View File

@@ -21,6 +21,11 @@ export function NavbarClient({ user, role = "partner" }: NavbarClientProps) {
const [userRole, setUserRole] = useState<string>(role);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [open, setOpen] = useState(false);
const [pathname, setPathname] = useState("/");
useEffect(() => {
setPathname(window.location.pathname);
}, []);
// Supabase URL & Anon Key auslesen (für Browser-Client)
const supabaseUrl = process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;
@@ -186,7 +191,7 @@ export function NavbarClient({ user, role = "partner" }: NavbarClientProps) {
</div>
) : (
<Button asChild className="bg-primary hover:bg-primary/95 text-white dark:text-blue-500">
<Link href={`/auth/login?next=${typeof window !== 'undefined' ? window.location.pathname : '/'}`}>Anmelden</Link>
<Link href={`/auth/login?next=${pathname}`}>Anmelden</Link>
</Button>
)}
</nav>
@@ -269,7 +274,7 @@ export function NavbarClient({ user, role = "partner" }: NavbarClientProps) {
</Button>
) : (
<Button asChild className="w-full bg-primary hover:bg-primary/95 text-white dark:text-blue-500" onClick={() => setIsMobileMenuOpen(false)}>
<Link href={`/auth/login?next=${typeof window !== 'undefined' ? window.location.pathname : '/'}`}>Anmelden</Link>
<Link href={`/auth/login?next=${pathname}`}>Anmelden</Link>
</Button>
)}
</div>

View File

@@ -14,7 +14,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useState, useEffect } from "react";
export function LoginForm({
className,
@@ -24,8 +24,13 @@ export function LoginForm({
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [messageParam, setMessageParam] = useState<string | null>(null);
const router = useRouter();
useEffect(() => {
setMessageParam(new URLSearchParams(window.location.search).get('message'));
}, []);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
@@ -35,6 +40,10 @@ export function LoginForm({
try {
const res = await signIn(email, password);
if (!res.success) {
setError(res.error || "An error occurred");
return;
}
if (nextParam) {
router.push(nextParam);
} else if (res.role === "admin") {
@@ -90,17 +99,11 @@ export function LoginForm({
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{(() => {
const messageParam = typeof window !== 'undefined' ? new URLSearchParams(window.location.search).get('message') : null;
if (messageParam === "concurrent") {
return (
{messageParam === "concurrent" && (
<p className="text-sm text-amber-500 bg-amber-500/10 border border-amber-500/20 p-3 rounded-lg text-center font-medium">
Sie wurden abgemeldet, da Sie sich an einem anderen Gerät angemeldet haben.
</p>
);
}
return null;
})()}
)}
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Logging in..." : "Login"}

View File

@@ -1,29 +1,55 @@
'use server'
import { createClient } from '@/lib/supabase/server'
import { createAdminClient } from '@/lib/supabase/admin'
import { headers } from 'next/headers'
export async function signIn(email: string, password: string) {
try {
const supabase = await createClient()
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
throw new Error(error.message)
return { success: false, error: error.message }
}
const userId = data.user?.id
if (userId) {
const { data: userData, error: userError } = await supabase
let { data: userData, error: userError } = await supabase
.from('users')
.select('role')
.eq('id', userId)
.single()
if (userError || !userData) {
// Fallback: create database records if missing for this authenticated user
try {
const adminSupabase = createAdminClient()
// Ensure profile exists
await adminSupabase.from('profiles').insert([{ id: userId }]).select()
// Ensure user exists
const { data: insertedUser, error: insertError } = await adminSupabase
.from('users')
.insert([{ id: userId, role: 'partner' }])
.select('role')
.single()
if (!insertError && insertedUser) {
userData = insertedUser
userError = null
}
} catch (adminErr) {
console.error("Failed to backfill user records:", adminErr)
}
}
if (userError || !userData || (userData.role !== 'partner' && userData.role !== 'admin')) {
await supabase.auth.signOut()
throw new Error("Zugriff verweigert. Nur registrierte Partner dürfen sich anmelden.")
return { success: false, error: "Zugriff verweigert. Nur registrierte Partner dürfen sich anmelden." }
}
// Andere aktive Sitzungen beenden
@@ -33,6 +59,10 @@ export async function signIn(email: string, password: string) {
}
return { success: true, role: 'partner' }
} catch (err: any) {
console.error("SignIn error:", err)
return { success: false, error: err?.message || "Ein unerwarteter Fehler ist aufgetreten." }
}
}
export async function signUp(email: string, password: string) {