- Reverted theme from gold to amber and restored legacy typography. - Refactored ScanAndTasteFlow and TastingEditor for robust desktop scrolling. - Hotfixed sw.js to completely bypass Supabase Auth/API requests to fix 'Failed to fetch' in production. - Integrated full tasting note persistence (tags, buddies, sessions).
145 lines
6.2 KiB
TypeScript
145 lines
6.2 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { Plus, Check, ChevronRight, Loader2 } from 'lucide-react';
|
|
import { useSession } from '@/context/SessionContext';
|
|
import { createClient } from '@/lib/supabase/client';
|
|
|
|
interface Session {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
interface SessionBottomSheetProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomSheetProps) {
|
|
const { activeSession, setActiveSession } = useSession();
|
|
const [sessions, setSessions] = useState<Session[]>([]);
|
|
const [newSessionName, setNewSessionName] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
const supabase = createClient();
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
fetchSessions();
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const fetchSessions = async () => {
|
|
setIsLoading(true);
|
|
const { data, error } = await supabase
|
|
.from('tasting_sessions')
|
|
.select('id, name')
|
|
.order('scheduled_at', { ascending: false })
|
|
.limit(10);
|
|
|
|
if (!error && data) {
|
|
setSessions(data);
|
|
}
|
|
setIsLoading(false);
|
|
};
|
|
|
|
const handleCreateSession = async () => {
|
|
if (!newSessionName.trim()) return;
|
|
setIsCreating(true);
|
|
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) return;
|
|
|
|
const { data, error } = await supabase
|
|
.from('tasting_sessions')
|
|
.insert([{ name: newSessionName.trim(), user_id: user.id }])
|
|
.select()
|
|
.single();
|
|
|
|
if (!error && data) {
|
|
setSessions(prev => [data, ...prev]);
|
|
setNewSessionName('');
|
|
setActiveSession({ id: data.id, name: data.name });
|
|
onClose();
|
|
}
|
|
setIsCreating(false);
|
|
};
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<>
|
|
{/* Backdrop */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
onClick={onClose}
|
|
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[80]"
|
|
/>
|
|
|
|
{/* Sheet */}
|
|
<motion.div
|
|
initial={{ y: '100%' }}
|
|
animate={{ y: 0 }}
|
|
exit={{ y: '100%' }}
|
|
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
|
className="fixed bottom-0 left-0 right-0 bg-[#1A1B20] border-t border-white/10 rounded-t-[32px] z-[90] p-8 pb-12 max-h-[80vh] overflow-y-auto shadow-[0_-10px_40px_rgba(0,0,0,0.5)]"
|
|
>
|
|
{/* Drag Handle */}
|
|
<div className="w-12 h-1.5 bg-white/10 rounded-full mx-auto mb-8" />
|
|
|
|
<h2 className="text-2xl font-bold mb-6 font-display text-white">Tasting Session</h2>
|
|
|
|
{/* New Session Input */}
|
|
<div className="relative mb-8">
|
|
<input
|
|
type="text"
|
|
value={newSessionName}
|
|
onChange={(e) => setNewSessionName(e.target.value)}
|
|
onKeyDown={(e) => e.key === 'Enter' && handleCreateSession()}
|
|
placeholder="Neue Session erstellen..."
|
|
className="w-full bg-white/5 border border-white/10 rounded-2xl py-4 px-6 text-white focus:outline-none focus:border-[#C89D46] transition-colors"
|
|
/>
|
|
<button
|
|
onClick={handleCreateSession}
|
|
disabled={isCreating || !newSessionName.trim()}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 p-2 bg-[#C89D46] text-black rounded-xl disabled:opacity-50"
|
|
>
|
|
{isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Session List */}
|
|
<div className="space-y-4">
|
|
<p className="text-xs font-black uppercase tracking-widest text-white/40 mb-2">Aktuelle Sessions</p>
|
|
{isLoading ? (
|
|
<div className="flex justify-center py-8">
|
|
<Loader2 size={24} className="animate-spin text-white/20" />
|
|
</div>
|
|
) : sessions.length > 0 ? (
|
|
sessions.map((s) => (
|
|
<button
|
|
key={s.id}
|
|
onClick={() => {
|
|
setActiveSession({ id: s.id, name: s.name });
|
|
onClose();
|
|
}}
|
|
className={`w-full flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === s.id ? 'bg-[#C89D46]/10 border-[#C89D46] text-[#C89D46]' : 'bg-white/5 border-white/5 hover:border-white/20 text-white'}`}
|
|
>
|
|
<span className="font-bold">{s.name}</span>
|
|
{activeSession?.id === s.id ? <Check size={20} /> : <ChevronRight size={20} className="text-white/20" />}
|
|
</button>
|
|
))
|
|
) : (
|
|
<div className="text-center py-8 text-white/30 italic">Keine aktiven Sessions gefunden</div>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|