feat: Upgrade to Next.js 16.1 & React 19.2, migrate to Supabase SSR with async client handling

This commit is contained in:
2025-12-19 20:31:46 +01:00
parent d9b44a0ec5
commit 24e243fff8
49 changed files with 942 additions and 852 deletions

View File

@@ -1,13 +1,12 @@
export const dynamic = 'force-dynamic';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
import { checkIsAdmin, getGlobalApiStats } from '@/services/track-api-usage';
import { BarChart3, TrendingUp, Users, Calendar, AlertCircle } from 'lucide-react';
import Link from 'next/link';
export default async function AdminPage() {
const supabase = createServerComponentClient({ cookies });
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
console.log('[Admin Page] User:', user?.id, user?.email);

View File

@@ -1,6 +1,5 @@
export const dynamic = 'force-dynamic';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
import { checkIsAdmin } from '@/services/track-api-usage';
import { getAllPlans } from '@/services/subscription-service';
@@ -9,7 +8,7 @@ import { ChevronLeft, Package } from 'lucide-react';
import PlanManagementClient from '@/components/PlanManagementClient';
export default async function AdminPlansPage() {
const supabase = createServerComponentClient({ cookies });
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {

View File

@@ -1,7 +1,7 @@
'use client';
import React, { useState, useEffect } from 'react';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { createClient } from '@/lib/supabase/client';
import Link from 'next/link';
import { ChevronLeft, Tag as TagIcon, Plus, Search, Trash2, Shield, User, Filter, Download } from 'lucide-react';
import { Tag, TagCategory, getTagsByCategory } from '@/services/tags';
@@ -9,7 +9,7 @@ import { useI18n } from '@/i18n/I18nContext';
export default function AdminTagsPage() {
const { t } = useI18n();
const supabase = createClientComponentClient();
const supabase = createClient();
const [tags, setTags] = useState<Tag[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [search, setSearch] = useState('');

View File

@@ -1,6 +1,5 @@
export const dynamic = 'force-dynamic';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
import { checkIsAdmin } from '@/services/track-api-usage';
import { getAllUsersWithCredits } from '@/services/admin-credit-service';
@@ -10,7 +9,7 @@ import { ChevronLeft, Users, Coins, TrendingUp, TrendingDown } from 'lucide-reac
import UserManagementClient from '@/components/UserManagementClient';
export default async function AdminUsersPage() {
const supabase = createServerComponentClient({ cookies });
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {

View File

@@ -1,10 +1,9 @@
export const dynamic = 'force-dynamic';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function GET() {
const supabase = createServerComponentClient({ cookies });
const supabase = await createClient();
// Get current user
const { data: { user }, error: userError } = await supabase.auth.getUser();

View File

@@ -1,12 +1,12 @@
export const dynamic = 'force-dynamic';
import sharp from 'sharp';
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
import { v4 as uuidv4 } from 'uuid';
export async function POST(req: Request) {
try {
const supabase = createRouteHandlerClient({ cookies });
const supabase = await createClient();
// Check session
const { data: { session } } = await supabase.auth.getSession();

View File

@@ -1,5 +1,5 @@
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
export const dynamic = 'force-dynamic';
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
@@ -7,7 +7,7 @@ export async function GET(request: Request) {
const code = requestUrl.searchParams.get('code');
if (code) {
const supabase = createRouteHandlerClient({ cookies });
const supabase = await createClient();
await supabase.auth.exchangeCodeForSession(code);
}

View File

@@ -1,5 +1,4 @@
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { createClient } from '@/lib/supabase/server';
import { notFound } from 'next/navigation';
import Link from 'next/link';
import { ChevronLeft, Calendar, Award, Droplets, MapPin, Tag, ExternalLink, Package, PlusCircle, Info } from 'lucide-react';
@@ -10,13 +9,12 @@ import DeleteBottleButton from '@/components/DeleteBottleButton';
import EditBottleForm from '@/components/EditBottleForm';
import { validateSession } from '@/services/validate-session';
export default async function BottlePage({
params,
searchParams
}: {
params: { id: string },
searchParams: { session_id?: string }
export default async function BottlePage(props: {
params: Promise<{ id: string }>,
searchParams: Promise<{ session_id?: string }>
}) {
const params = await props.params;
const searchParams = await props.searchParams;
let sessionId = searchParams.session_id;
// Validate Session Age (12 hour limit)
@@ -26,7 +24,7 @@ export default async function BottlePage({
sessionId = undefined;
}
}
const supabase = createServerComponentClient({ cookies });
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
const { data: bottle } = await supabase

36
src/app/global-error.tsx Normal file
View File

@@ -0,0 +1,36 @@
'use client';
import { RefreshCcw } from 'lucide-react';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="de">
<body>
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6">
<div className="space-y-2">
<h2 className="text-2xl font-black text-zinc-900 dark:text-white">Kritischer Fehler</h2>
<p className="text-zinc-500 text-sm">
Ein schwerwiegender Fehler ist aufgetreten. Bitte versuche die Seite neu zu laden.
</p>
</div>
<button
onClick={() => reset()}
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-xl font-bold flex items-center justify-center gap-2 transition-all"
>
<RefreshCcw size={18} />
Erneut versuchen
</button>
</div>
</div>
</body>
</html>
);
}

12
src/app/loading.tsx Normal file
View File

@@ -0,0 +1,12 @@
import { Loader2 } from 'lucide-react';
export default function Loading() {
return (
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
<div className="flex flex-col items-center gap-4">
<Loader2 size={40} className="animate-spin text-amber-600" />
<p className="text-zinc-500 font-medium animate-pulse">Whisky Vault wird geladen...</p>
</div>
</div>
);
}

29
src/app/not-found.tsx Normal file
View File

@@ -0,0 +1,29 @@
import Link from 'next/link';
import { Home, MoveLeft } from 'lucide-react';
export default function NotFound() {
return (
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6">
<div className="text-8xl font-black text-amber-600/20">404</div>
<div className="space-y-2">
<h2 className="text-2xl font-black text-zinc-900 dark:text-white">Seite nicht gefunden</h2>
<p className="text-zinc-500 text-sm">
Die gesuchte Seite existiert leider nicht oder wurde verschoben.
</p>
</div>
<div className="pt-4">
<Link
href="/"
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-xl font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-amber-600/20"
>
<Home size={18} />
Zurück zum Vault
</Link>
</div>
</div>
</div>
);
}

View File

@@ -1,7 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { createClient } from '@/lib/supabase/client';
import CameraCapture from "@/components/CameraCapture";
import BottleGrid from "@/components/BottleGrid";
import AuthForm from "@/components/AuthForm";
@@ -13,7 +13,7 @@ import LanguageSwitcher from "@/components/LanguageSwitcher";
import { useI18n } from "@/i18n/I18nContext";
export default function Home() {
const supabase = createClientComponentClient();
const supabase = createClient();
const [bottles, setBottles] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState<any>(null);
@@ -64,7 +64,7 @@ export default function Home() {
document.addEventListener('visibilitychange', handleVisibilityChange);
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: string, session: any) => {
console.log('[Auth] State change event:', event, {
hasSession: !!session,
userId: session?.user?.id,

View File

@@ -1,7 +1,7 @@
'use client';
import React, { useState, useEffect } from 'react';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { createClient } from '@/lib/supabase/client';
import { ChevronLeft, Users, Calendar, GlassWater, Plus, Trash2, Loader2, Sparkles, ChevronRight, Play, Square } from 'lucide-react';
import Link from 'next/link';
import AvatarStack from '@/components/AvatarStack';
@@ -43,7 +43,7 @@ export default function SessionDetailPage() {
const { t } = useI18n();
const { id } = useParams();
const router = useRouter();
const supabase = createClientComponentClient();
const supabase = createClient();
const [session, setSession] = useState<Session | null>(null);
const [participants, setParticipants] = useState<Participant[]>([]);
const [tastings, setTastings] = useState<SessionTasting[]>([]);