diff --git a/src/client/app.tsx b/src/client/app.tsx index 8352c09..ae385d3 100644 --- a/src/client/app.tsx +++ b/src/client/app.tsx @@ -1,16 +1,39 @@ import { useState } from "react"; +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 { InitialDataLoader } from "@/client/components/initial-data-loader"; function App() { - const [activeTab, setActiveTab] = useState<"booking" | "admin-treatments" | "admin-bookings">("booking"); + const { user, isLoading, isOwner } = useAuth(); + const [activeTab, setActiveTab] = useState<"booking" | "admin-treatments" | "admin-bookings" | "profile">("booking"); + + // Show loading spinner while checking authentication + if (isLoading) { + return ( +
+
+
đź’…
+
Lade...
+
+
+ ); + } + + // Show login form if user is not authenticated and trying to access admin features + const needsAuth = !user && (activeTab === "admin-treatments" || activeTab === "admin-bookings" || activeTab === "profile"); + if (needsAuth) { + return ; + } const tabs = [ - { id: "booking", label: "Book Appointment", icon: "📅" }, - { id: "admin-treatments", label: "Manage Treatments", icon: "💅" }, - { id: "admin-bookings", label: "Manage Bookings", icon: "📋" }, + { id: "booking", label: "Termin buchen", icon: "📅", requiresAuth: false }, + { id: "admin-treatments", label: "Behandlungen verwalten", icon: "💅", requiresAuth: true }, + { id: "admin-bookings", label: "Buchungen verwalten", icon: "📋", requiresAuth: true }, + ...(user ? [{ id: "profile", label: "Profil", icon: "👤", requiresAuth: true }] : []), ] as const; return ( @@ -28,6 +51,19 @@ function App() {

Professional Nail Design & Care

+ + {user && ( +
+ + Willkommen, {user.username} + + {isOwner && ( + + Inhaber + + )} +
+ )} @@ -36,20 +72,44 @@ function App() { @@ -60,44 +120,58 @@ function App() {

- Book Your Perfect Nail Treatment + Buchen Sie Ihre perfekte Nagelbehandlung

- Experience professional nail care with our expert technicians. - Choose from our wide range of treatments and book your appointment today. + Erleben Sie professionelle Nagelpflege mit unseren Experten. + Wählen Sie aus unserem breiten Angebot an Behandlungen und buchen Sie noch heute Ihren Termin.

)} - {activeTab === "admin-treatments" && ( + {activeTab === "admin-treatments" && isOwner && (

- Treatment Management + Behandlungen verwalten

- Add, edit, and manage your nail treatment services. + HinzufĂĽgen, bearbeiten und verwalten Sie Ihre Nagelbehandlungen.

)} - {activeTab === "admin-bookings" && ( + {activeTab === "admin-bookings" && isOwner && (

- Booking Management + Buchungen verwalten

- View and manage customer appointments and bookings. + Sehen und verwalten Sie Kundentermine und Buchungen.

)} + + {activeTab === "profile" && user && ( +
+
+

+ Benutzerprofil +

+

+ Verwalten Sie Ihre Kontoinformationen und Einstellungen. +

+
+ +
+ )} {/* Footer */} diff --git a/src/client/components/auth-provider.tsx b/src/client/components/auth-provider.tsx new file mode 100644 index 0000000..bdd38a7 --- /dev/null +++ b/src/client/components/auth-provider.tsx @@ -0,0 +1,114 @@ +import { createContext, useContext, useState, useEffect, ReactNode } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { queryClient } from "@/client/rpc-client"; + +interface User { + id: string; + username: string; + email: string; + role: "customer" | "owner"; +} + +interface AuthContextType { + user: User | null; + sessionId: string | null; + isLoading: boolean; + login: (username: string, password: string) => Promise; + logout: () => void; + isOwner: boolean; +} + +const AuthContext = createContext(null); + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +} + +interface AuthProviderProps { + children: ReactNode; +} + +export function AuthProvider({ children }: AuthProviderProps) { + const [user, setUser] = useState(null); + const [sessionId, setSessionId] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const { mutateAsync: loginMutation } = useMutation( + queryClient.auth.login.mutationOptions() + ); + + const { mutateAsync: logoutMutation } = useMutation( + queryClient.auth.logout.mutationOptions() + ); + + const { mutateAsync: verifySessionMutation } = useMutation( + queryClient.auth.verifySession.mutationOptions() + ); + + useEffect(() => { + // Check for existing session on app load + const storedSessionId = localStorage.getItem("sessionId"); + if (storedSessionId) { + verifySessionMutation(storedSessionId) + .then((result) => { + setUser(result.user); + setSessionId(storedSessionId); + }) + .catch(() => { + localStorage.removeItem("sessionId"); + }) + .finally(() => { + setIsLoading(false); + }); + } else { + setIsLoading(false); + } + }, [verifySessionMutation]); + + const login = async (username: string, password: string) => { + try { + const result = await loginMutation({ username, password }); + setUser(result.user); + setSessionId(result.sessionId); + localStorage.setItem("sessionId", result.sessionId); + } catch (error) { + throw error; + } + }; + + const logout = async () => { + if (sessionId) { + try { + await logoutMutation(sessionId); + } catch (error) { + // Continue with logout even if server call fails + console.error("Logout error:", error); + } + } + + setUser(null); + setSessionId(null); + localStorage.removeItem("sessionId"); + }; + + const isOwner = user?.role === "owner"; + + const value: AuthContextType = { + user, + sessionId, + isLoading, + login, + logout, + isOwner, + }; + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/src/client/components/booking-form.tsx b/src/client/components/booking-form.tsx index 5983c6b..e29ebc0 100644 --- a/src/client/components/booking-form.tsx +++ b/src/client/components/booking-form.tsx @@ -64,7 +64,7 @@ export function BookingForm() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!selectedTreatment || !customerName || !customerEmail || !customerPhone || !appointmentDate || !appointmentTime) { - alert("Please fill in all required fields"); + alert("Bitte füllen Sie alle erforderlichen Felder aus"); return; } @@ -85,7 +85,7 @@ export function BookingForm() { setAppointmentDate(""); setAppointmentTime(""); setNotes(""); - alert("Booking created successfully! We'll contact you to confirm your appointment."); + alert("Buchung erfolgreich erstellt! Wir werden Sie kontaktieren, um Ihren Termin zu bestätigen."); } }); }; @@ -95,13 +95,13 @@ export function BookingForm() { return (
-

Book Your Nail Treatment

+

Buchen Sie Ihre Nagelbehandlung

{/* Treatment Selection */}