439 lines
18 KiB
TypeScript
439 lines
18 KiB
TypeScript
import { useState, useEffect } from "react";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { queryClient } from "@/client/rpc-client";
|
||
import { useAuth } from "@/client/components/auth-provider";
|
||
import { LoginForm } from "@/client/components/login-form";
|
||
import { UserProfile } from "@/client/components/user-profile";
|
||
import { BookingForm } from "@/client/components/booking-form";
|
||
import { AdminTreatments } from "@/client/components/admin-treatments";
|
||
import { AdminBookings } from "@/client/components/admin-bookings";
|
||
import { AdminCalendar } from "@/client/components/admin-calendar";
|
||
import { InitialDataLoader } from "@/client/components/initial-data-loader";
|
||
import { AdminAvailability } from "@/client/components/admin-availability";
|
||
import { AdminGallery } from "@/client/components/admin-gallery";
|
||
import { AdminReviews } from "@/client/components/admin-reviews";
|
||
import BookingStatusPage from "@/client/components/booking-status-page";
|
||
import ReviewSubmissionPage from "@/client/components/review-submission-page";
|
||
import LegalPage from "@/client/components/legal-page";
|
||
import { ProfileLanding } from "@/client/components/profile-landing";
|
||
import { PWAInstallPrompt } from "@/client/components/pwa-install-prompt";
|
||
|
||
function App() {
|
||
const { user, isLoading, isOwner } = useAuth();
|
||
const [activeTab, setActiveTab] = useState<"profile-landing" | "booking" | "admin-treatments" | "admin-bookings" | "admin-calendar" | "admin-availability" | "admin-gallery" | "admin-reviews" | "profile" | "legal">("profile-landing");
|
||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||
|
||
const { data: socialMedia } = useQuery(
|
||
queryClient.social.getSocialMedia.queryOptions()
|
||
);
|
||
|
||
const hasSocialMedia = (socialMedia as any)?.tiktokProfile || (socialMedia as any)?.instagramProfile;
|
||
|
||
// Prevent background scroll when menu is open
|
||
useEffect(() => {
|
||
document.body.classList.toggle('overflow-hidden', isMobileMenuOpen);
|
||
return () => document.body.classList.remove('overflow-hidden');
|
||
}, [isMobileMenuOpen]);
|
||
|
||
// Handle booking status page
|
||
const path = window.location.pathname;
|
||
const PwaPrompt = <PWAInstallPrompt />;
|
||
|
||
if (path.startsWith('/booking/')) {
|
||
const token = path.split('/booking/')[1];
|
||
if (token) {
|
||
return <>
|
||
{PwaPrompt}
|
||
<BookingStatusPage token={token} />
|
||
</>;
|
||
}
|
||
}
|
||
|
||
// Handle review submission page
|
||
if (path.startsWith('/review/')) {
|
||
const token = path.split('/review/')[1];
|
||
if (token) {
|
||
return <>
|
||
{PwaPrompt}
|
||
<ReviewSubmissionPage token={token} />
|
||
</>;
|
||
}
|
||
}
|
||
|
||
// Show loading spinner while checking authentication
|
||
if (isLoading) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-pink-50 to-purple-50">
|
||
<div className="text-center">
|
||
<img
|
||
src="/assets/stargilnails_logo_transparent_112.png"
|
||
alt="Stargil Nails Logo"
|
||
className="w-16 h-16 mx-auto mb-4 object-contain animate-pulse"
|
||
/>
|
||
<div className="text-lg text-gray-600">Lade...</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Show login form if user is not authenticated and trying to access admin features
|
||
const needsAuth = !user && (activeTab === "admin-treatments" || activeTab === "admin-bookings" || activeTab === "admin-calendar" || activeTab === "admin-availability" || activeTab === "admin-gallery" || activeTab === "admin-reviews" || activeTab === "profile");
|
||
if (needsAuth) {
|
||
return <LoginForm />;
|
||
}
|
||
|
||
// Show legal page if legal tab is active
|
||
if (activeTab === "legal") {
|
||
return <LegalPage />;
|
||
}
|
||
|
||
const tabs = [
|
||
{ id: "profile-landing", label: "Startseite", icon: "🏠", requiresAuth: false },
|
||
{ id: "booking", label: "Termin buchen", icon: "📅", requiresAuth: false },
|
||
{ id: "legal", label: "Impressum/Datenschutz", icon: "📋", requiresAuth: false },
|
||
{ id: "admin-treatments", label: "Behandlungen verwalten", icon: "💅", requiresAuth: true },
|
||
{ id: "admin-bookings", label: "Buchungen verwalten", icon: "📋", requiresAuth: true },
|
||
{ id: "admin-calendar", label: "Kalender", icon: "📆", requiresAuth: true },
|
||
{ id: "admin-availability", label: "Verfügbarkeiten", icon: "⏰", requiresAuth: true },
|
||
{ id: "admin-gallery", label: "Photo-Wall", icon: "📸", requiresAuth: true },
|
||
{ id: "admin-reviews", label: "Bewertungen", icon: "⭐", requiresAuth: true },
|
||
...(user ? [{ id: "profile", label: "Profil", icon: "👤", requiresAuth: true }] : []),
|
||
] as const;
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-pink-50 to-purple-50">
|
||
<InitialDataLoader />
|
||
|
||
{/* Header */}
|
||
<header className="bg-white shadow-sm border-b border-pink-100">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="flex justify-between items-center py-6">
|
||
<div
|
||
className="flex items-center space-x-3 cursor-pointer hover:opacity-80 transition-opacity"
|
||
onClick={() => setActiveTab("profile-landing")}
|
||
>
|
||
<img
|
||
src="/assets/stargilnails_logo_transparent_112.png"
|
||
alt="Stargil Nails Logo"
|
||
className="w-12 h-12 object-contain"
|
||
/>
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900">Stargirlnails Kiel</h1>
|
||
<p className="text-sm text-gray-600">Professional Nail Design & Care</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Hamburger Button für Mobile */}
|
||
<button
|
||
type="button"
|
||
aria-label="Menü öffnen"
|
||
aria-controls="mobile-menu"
|
||
aria-expanded={isMobileMenuOpen}
|
||
className="md:hidden p-2 -ml-2 text-3xl text-gray-700 hover:text-pink-600 transition-colors"
|
||
onClick={() => setIsMobileMenuOpen(true)}
|
||
>
|
||
☰
|
||
</button>
|
||
|
||
{user && (
|
||
<div className="flex items-center space-x-4">
|
||
<span className="text-sm text-gray-600 hidden sm:inline">
|
||
Willkommen, {user.username}
|
||
</span>
|
||
<span className="text-sm text-gray-600 sm:hidden">
|
||
{user.username}
|
||
</span>
|
||
{isOwner && (
|
||
<span className="bg-pink-100 text-pink-800 px-2 py-1 rounded-full text-xs font-medium">
|
||
Inhaber
|
||
</span>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
{/* Desktop Navigation */}
|
||
<nav className="bg-white shadow-sm hidden md:block">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="flex space-x-8">
|
||
{tabs.map((tab) => {
|
||
// Hide admin tabs for non-owners
|
||
if (tab.requiresAuth && !isOwner && tab.id !== "profile") {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<button
|
||
key={tab.id}
|
||
onClick={() => {
|
||
if (tab.requiresAuth && !user) {
|
||
// This will trigger the login form
|
||
setActiveTab(tab.id as any);
|
||
} else {
|
||
setActiveTab(tab.id as any);
|
||
}
|
||
}}
|
||
className={`py-4 px-1 border-b-2 font-medium text-sm flex items-center space-x-2 ${
|
||
activeTab === tab.id
|
||
? "border-pink-500 text-pink-600"
|
||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||
}`}
|
||
>
|
||
<span>{tab.icon}</span>
|
||
<span>{tab.label}</span>
|
||
</button>
|
||
);
|
||
})}
|
||
|
||
{!user && (
|
||
<button
|
||
onClick={() => setActiveTab("profile")} // This will trigger login
|
||
className="py-4 px-1 border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 font-medium text-sm flex items-center space-x-2"
|
||
>
|
||
<span>🔑</span>
|
||
<span>Inhaber Login</span>
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
{/* Mobile Backdrop */}
|
||
{isMobileMenuOpen && (
|
||
<div
|
||
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
||
onClick={() => setIsMobileMenuOpen(false)}
|
||
/>
|
||
)}
|
||
|
||
{/* Mobile Slide-in Panel */}
|
||
<div
|
||
id="mobile-menu"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-label="Navigation"
|
||
className={`fixed inset-y-0 left-0 w-64 bg-white shadow-xl z-50 md:hidden transform transition-transform duration-300 ease-in-out ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}`}
|
||
>
|
||
<button
|
||
type="button"
|
||
aria-label="Menü schließen"
|
||
className="absolute top-4 right-4 text-2xl text-gray-600 hover:text-pink-600"
|
||
onClick={() => setIsMobileMenuOpen(false)}
|
||
>
|
||
×
|
||
</button>
|
||
|
||
<div className="p-6">
|
||
<img
|
||
src="/assets/stargilnails_logo_transparent_112.png"
|
||
alt="Stargil Nails Logo"
|
||
className="w-10 h-10 mb-6 object-contain"
|
||
/>
|
||
|
||
<nav className="mt-2 flex flex-col space-y-2">
|
||
{tabs.map((tab) => {
|
||
// Hide admin tabs for non-owners
|
||
if (tab.requiresAuth && !isOwner && tab.id !== 'profile') return null;
|
||
|
||
return (
|
||
<button
|
||
key={tab.id}
|
||
onClick={() => {
|
||
setActiveTab(tab.id as any);
|
||
setIsMobileMenuOpen(false);
|
||
}}
|
||
className={`flex items-center space-x-3 px-4 py-3 rounded-lg transition-colors ${
|
||
activeTab === tab.id ? 'bg-pink-100 text-pink-600' : 'text-gray-700 hover:bg-gray-100'
|
||
}`}
|
||
>
|
||
<span>{tab.icon}</span>
|
||
<span>{tab.label}</span>
|
||
</button>
|
||
);
|
||
})}
|
||
|
||
{!user && (
|
||
<button
|
||
onClick={() => {
|
||
setActiveTab('profile');
|
||
setIsMobileMenuOpen(false);
|
||
}}
|
||
className="flex items-center space-x-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 transition-colors"
|
||
>
|
||
<span>🔑</span>
|
||
<span>Inhaber Login</span>
|
||
</button>
|
||
)}
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Main Content */}
|
||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
{activeTab === "profile-landing" && (
|
||
<ProfileLanding onNavigateToBooking={() => setActiveTab("booking")} />
|
||
)}
|
||
|
||
{activeTab === "booking" && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Buche deine perfekte Nagelbehandlung
|
||
</h2>
|
||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||
Erlebe professionelle Nagelpflege mit unseren Experten.
|
||
Wähle aus unserem breiten Angebot an Behandlungen und buche noch heute deinen Termin.
|
||
</p>
|
||
</div>
|
||
<BookingForm />
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "admin-treatments" && isOwner && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Behandlungen verwalten
|
||
</h2>
|
||
<p className="text-lg text-gray-600">
|
||
Füge Behandlungen hinzu, bearbeite und verwalte deine Nagelbehandlungen.
|
||
</p>
|
||
</div>
|
||
<AdminTreatments />
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "admin-bookings" && isOwner && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Buchungen verwalten
|
||
</h2>
|
||
<p className="text-lg text-gray-600">
|
||
Sieh dir Kundentermine an und verwalte Buchungen.
|
||
</p>
|
||
</div>
|
||
<AdminBookings />
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "admin-calendar" && isOwner && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Kalender - Bevorstehende Buchungen
|
||
</h2>
|
||
<p className="text-lg text-gray-600">
|
||
Übersichtliche Darstellung aller bevorstehenden Termine im Kalenderformat.
|
||
</p>
|
||
</div>
|
||
<AdminCalendar />
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "admin-availability" && isOwner && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Verfügbarkeiten verwalten
|
||
</h2>
|
||
<p className="text-lg text-gray-600">
|
||
Verwalte wiederkehrende Zeiten und Urlaubszeiten.
|
||
</p>
|
||
</div>
|
||
<AdminAvailability />
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "admin-gallery" && isOwner && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Photo-Wall verwalten
|
||
</h2>
|
||
<p className="text-lg text-gray-600">
|
||
Lade Fotos hoch und verwalte deine Galerie.
|
||
</p>
|
||
</div>
|
||
<AdminGallery />
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "admin-reviews" && isOwner && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Bewertungen verwalten
|
||
</h2>
|
||
<p className="text-lg text-gray-600">
|
||
Prüfe und verwalte Kundenbewertungen.
|
||
</p>
|
||
</div>
|
||
<AdminReviews />
|
||
</div>
|
||
)}
|
||
|
||
{activeTab === "profile" && user && (
|
||
<div>
|
||
<div className="text-center mb-8">
|
||
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||
Benutzerprofil
|
||
</h2>
|
||
<p className="text-lg text-gray-600">
|
||
Verwalte deine Kontoinformationen und Einstellungen.
|
||
</p>
|
||
</div>
|
||
<UserProfile />
|
||
</div>
|
||
)}
|
||
</main>
|
||
|
||
{/* PWA Installation Prompt for iOS */}
|
||
<PWAInstallPrompt hidden={isMobileMenuOpen} />
|
||
|
||
{/* Footer */}
|
||
<footer className="bg-white border-t border-pink-100 mt-16">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
<div className="text-center text-gray-600">
|
||
<p className="mb-4">© 2025 Stargirlnails Kiel. Professional nail design & care with 🩷 and passion in Kiel 🌇.</p>
|
||
{hasSocialMedia && (
|
||
<div className="flex justify-center items-center gap-3 mt-4">
|
||
{(socialMedia as any)?.instagramProfile && (
|
||
<a
|
||
href={(socialMedia as any).instagramProfile}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-pink-600 hover:text-pink-700 transition-colors"
|
||
aria-label="Instagram"
|
||
>
|
||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
|
||
</svg>
|
||
</a>
|
||
)}
|
||
{(socialMedia as any)?.tiktokProfile && (
|
||
<a
|
||
href={(socialMedia as any).tiktokProfile}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-gray-800 hover:text-gray-900 transition-colors"
|
||
aria-label="TikTok"
|
||
>
|
||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M19.59 6.69a4.83 4.83 0 0 1-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 0 1-5.2 1.74 2.89 2.89 0 0 1 2.31-4.64 2.93 2.93 0 0 1 .88.13V9.4a6.84 6.84 0 0 0-1-.05A6.33 6.33 0 0 0 5 20.1a6.34 6.34 0 0 0 10.86-4.43v-7a8.16 8.16 0 0 0 4.77 1.52v-3.4a4.85 4.85 0 0 1-1-.1z"/>
|
||
</svg>
|
||
</a>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default App;
|