-
-
Today
+
-
-
-
Unavailable
+
-
+
- Hover over dates for details
+ Hover for details
diff --git a/src/components/admin/admin-sidebar.tsx b/src/components/admin/admin-sidebar.tsx
new file mode 100644
index 0000000..587b30e
--- /dev/null
+++ b/src/components/admin/admin-sidebar.tsx
@@ -0,0 +1,143 @@
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import {
+ ChartArea,
+ // Calendar,
+ // Clock,
+ LayoutDashboard,
+ LogOut,
+ Menu,
+ Settings,
+ X,
+} from "lucide-react";
+import { useState } from "react";
+import { Link, useLocation } from "@tanstack/react-router";
+import kdCutsLogo from "@/assets/KD Cuts Logo.png";
+
+const navItems = [
+ {
+ label: "Dashboard",
+ href: "/dashboard",
+ icon: LayoutDashboard,
+ },
+ // {
+ // label: "Availability",
+ // href: "/dashboard/availability",
+ // icon: Clock,
+ // },
+ // {
+ // label: "Appointments",
+ // href: "/dashboard/appointments",
+ // icon: Calendar,
+ // },
+ {
+ label: "Analytics",
+ href: "/dashboard/metrics",
+ icon: ChartArea,
+ },
+ {
+ label: "Settings",
+ href: "/dashboard/settings",
+ icon: Settings,
+ },
+];
+
+export function AdminSidebar() {
+ const { pathname } = useLocation();
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ return (
+ <>
+ {/* Mobile header */}
+
+
+

+
Admin Panel
+
+
+
+
+ {/* Mobile overlay */}
+ {mobileOpen && (
+
setMobileOpen(false)}
+ />
+ )}
+
+ {/* Sidebar */}
+
+
+ {/* Spacer for mobile header */}
+
+ >
+ );
+}
diff --git a/src/components/admin/bookings-kanban.tsx b/src/components/admin/bookings-kanban.tsx
new file mode 100644
index 0000000..e4b35a8
--- /dev/null
+++ b/src/components/admin/bookings-kanban.tsx
@@ -0,0 +1,231 @@
+import { useEffect, useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import {
+ ChevronDown,
+ ChevronUp,
+ Clock,
+ Mail,
+ Phone,
+ User,
+ Check,
+ X,
+} from "lucide-react";
+
+import { getBulkBookings, updateBookingStatus, type Booking, type BookingClientData, type BookingStatus } from "@/lib/booking";
+
+interface FullBooking extends Booking, BookingClientData { }
+
+const columns: { id: BookingStatus; title: string; color: string }[] = [
+ { id: "pending", title: "Pending", color: "bg-amber-500" },
+ { id: "completed", title: "Completed", color: "bg-emerald-500" },
+ { id: "cancelled", title: "Cancelled", color: "bg-red-400" },
+];
+
+function BookingCard({
+ booking,
+ onStatusChange,
+}: {
+ booking: FullBooking;
+ onStatusChange: (id: number, status: BookingStatus) => void;
+}) {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const formatDate = (dateStr: string) => {
+ const date = new Date(dateStr);
+ return date.toLocaleDateString("en-US", {
+ weekday: "short",
+ month: "short",
+ day: "numeric",
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+ {booking.name}
+
+
+ {booking.service}
+
+
+
+ {isOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {formatDate(booking.date)} at {booking.start_time}
+
+
+
+
+
+ {/* Contact Info */}
+
+
+
+ {booking.name}
+
+
+
+ {booking.email}
+
+
+
+
+
+ Time
+
+ {booking.start_time}
+
+
+
+ {/* Notes */}
+ {/* {booking.notes && ( */}
+ {/*
*/}
+ {/*
*/}
+ {/* Note: {booking.} */}
+ {/*
*/}
+ {/*
*/}
+ {/* )} */}
+
+ {/* Actions */}
+
+ {booking.status === "pending" && (
+ <>
+
+
+ >
+ )}
+ {(booking.status === "completed" ||
+ booking.status === "cancelled") && (
+
+ No actions available
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+export function BookingsKanban() {
+ const [bookings, setBookings] = useState
([]);
+
+ useEffect(() => {
+ getBulkBookings().then((bookings) => {
+ if (bookings) {
+ setBookings(bookings);
+ }
+ }).catch((error) => {
+ console.error(error);
+ })
+ }, [])
+
+
+ const handleStatusChange = async (id: number, newStatus: BookingStatus) => {
+ // TODO: update booking detail
+ const res = await updateBookingStatus(id, newStatus);
+
+ if (!res) return;
+
+ setBookings((prev) =>
+ prev.map((booking) =>
+ booking.id === id ? { ...booking, status: newStatus } : booking
+ )
+ );
+ };
+
+ const getBookingsByStatus = (status: BookingStatus) =>
+ bookings.filter((b) => b.status === status);
+
+ return (
+
+ {columns.map((column) => {
+ const columnBookings = getBookingsByStatus(column.id);
+ return (
+
+ {/* Column Header */}
+
+
+
+ {column.title}
+
+
+ {columnBookings.length}
+
+
+
+ {/* Column Content */}
+
+ {columnBookings.length === 0 ? (
+
+ ) : (
+ columnBookings.map((booking) => (
+
+ ))
+ )}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/src/components/admin/calendar-config.tsx b/src/components/admin/calendar-config.tsx
new file mode 100644
index 0000000..4a1e498
--- /dev/null
+++ b/src/components/admin/calendar-config.tsx
@@ -0,0 +1,724 @@
+import { useEffect, useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Calendar as C } from "@/components/ui/calendar";
+import { Badge } from "@/components/ui/badge";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Plus,
+ Trash2,
+ Clock,
+ CalendarDays,
+ Repeat,
+ Loader2,
+} from "lucide-react";
+import { addCalendarEntry, deleteCalenderEntry, getCalenderEntries, getSlotGenerationConfig, upsertSlotConfig, type Calendar, type SlotGenerationConfig } from "@/lib/calender";
+import { getUserSession, type MetaData } from "@/lib/db";
+
+const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+const fullDayNames = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+];
+
+// Client-side wrapper to add id
+interface CalendarWithId extends Calendar, MetaData {
+}
+
+const initialConfig: SlotGenerationConfig = {
+ slotDuration: 30,
+ bufferMinutes: 15,
+ minAdvanceDays: 2,
+ maxAdvanceMonths: 2,
+ businessStartHour: 9,
+ businessEndHour: 18,
+};
+
+function TimeSelect({
+ value,
+ onChange,
+ label,
+}: {
+ value: string;
+ onChange: (val: string) => void;
+ label: string;
+}) {
+ const hours = Array.from({ length: 24 }, (_, i) => i);
+
+ return (
+
+
+
+
+ );
+}
+
+function CalendarEntryCard({
+ entry,
+ onDelete,
+}: {
+ entry: CalendarWithId;
+ onDelete: (id: string) => void;
+}) {
+ const formatTime = (time: string) => {
+ const [hours, minutes] = time.split(":");
+ const hour = parseInt(hours);
+ const ampm = hour >= 12 ? "PM" : "AM";
+ const hour12 = hour % 12 || 12;
+ return `${hour12}:${minutes} ${ampm}`;
+ };
+
+ const formatDateRange = () => {
+ if (!entry.end_date || entry.start_date === entry.end_date) {
+ return new Date(entry.start_date).toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ year: "numeric",
+ });
+ }
+ return `${new Date(entry.start_date).toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${new Date(entry.end_date).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
+ };
+
+ return (
+
+
+
+
+
+
+ Availability Slot
+
+
+
+
+
+
+ {formatDateRange()}
+
+
+
+
+
+ {formatTime(entry.start_time)} - {formatTime(entry.end_time)}
+
+
+
+ {entry.days_of_week.length > 0 && (
+
+
+
+ {entry.days_of_week.map((d) => dayNames[d]).join(", ")}
+
+
+ )}
+
+ {entry.frequency && (
+
+ {entry.frequency}
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+function AddEntryDialog({
+ onAdd,
+}: {
+ onAdd: (entry: Omit) => void;
+}) {
+ const [open, setOpen] = useState(false);
+ const [startDate, setStartDate] = useState(new Date());
+ const [endDate, setEndDate] = useState(new Date());
+ const [startTime, setStartTime] = useState("09:00:00");
+ const [endTime, setEndTime] = useState("17:00:00");
+ const [selectedDays, setSelectedDays] = useState([1, 2, 3, 4, 5]);
+ const [bufferMinutes, setBufferMinutes] = useState(15);
+
+ // Add state for validation and feedback
+ const [errors, setErrors] = useState>({});
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [submitSuccess, setSubmitSuccess] = useState(false);
+
+ const toggleDay = (day: number) => {
+ setSelectedDays((prev) =>
+ prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day].sort()
+ );
+ };
+
+ const validateForm = (): boolean => {
+ const newErrors: Record = {};
+
+ if (!startDate) {
+ newErrors.startDate = "Start date is required";
+ }
+
+ if (endDate && startDate && endDate < startDate) {
+ newErrors.endDate = "End date cannot be before start date";
+ }
+
+ // Validate times
+ if (startTime && endTime) {
+ const start = new Date(`1970-01-01T${startTime}`);
+ const end = new Date(`1970-01-01T${endTime}`);
+ if (start >= end) {
+ newErrors.time = "End time must be after start time";
+ }
+ }
+
+ if (selectedDays.length === 0) {
+ newErrors.days = "At least one day must be selected";
+ }
+
+ if (bufferMinutes < 0 || bufferMinutes > 240) {
+ newErrors.buffer = "Buffer must be between 0 and 240 minutes";
+ }
+
+ setErrors(newErrors);
+ return Object.keys(newErrors).length === 0;
+ };
+
+ const handleSubmit = async () => {
+ // Reset states
+ setErrors({});
+ setSubmitSuccess(false);
+
+ // Validate form
+ if (!validateForm()) {
+ return; // Don't proceed if validation fails
+ }
+
+ if (!startDate) {
+ setErrors({ startDate: "Start date is required" });
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ try {
+ // Call the onAdd function
+ await Promise.resolve(onAdd({
+ start_date: startDate.toISOString().split("T")[0],
+ end_date: endDate?.toISOString().split("T")[0],
+ start_time: startTime,
+ end_time: endTime,
+ days_of_week: selectedDays,
+ frequency: "weekly",
+ buffer_minutes: bufferMinutes,
+ }));
+
+ // Show success feedback
+ setSubmitSuccess(true);
+
+ // Reset form after successful submission
+ setTimeout(() => {
+ setOpen(false);
+ resetForm();
+ }, 1500);
+
+ } catch (error) {
+ // Handle API/network errors
+ setErrors({
+ submit: error instanceof Error
+ ? error.message
+ : "Failed to add calendar entry. Please try again."
+ });
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ const resetForm = () => {
+ setStartDate(new Date());
+ setEndDate(new Date());
+ setStartTime("09:00:00");
+ setEndTime("17:00:00");
+ setSelectedDays([1, 2, 3, 4, 5]);
+ setBufferMinutes(15);
+ setErrors({});
+ setSubmitSuccess(false);
+ };
+
+ return (
+
+ );
+}
+
+export function CalendarConfig() {
+ const [_entriesIsLoading, setEntriesIsLoading] = useState(false);
+ const [entries, setEntries] = useState([]);
+
+ const [configIsLoading, setConfigIsLoading] = useState(false);
+ const [config, setConfig] = useState(initialConfig);
+
+ // Gets calender entries
+ useEffect(() => {
+ (async () => {
+ try {
+ setEntriesIsLoading(true);
+
+ const entires = await getCalenderEntries();
+
+ if (entires) {
+ setEntries(entires);
+ }
+ } catch (error) {
+ console.error(error);
+
+ } finally {
+ setEntriesIsLoading(false);
+ }
+ })()
+ }, [])
+ // Gets config
+ useEffect(() => {
+ (async () => {
+ try {
+ setConfigIsLoading(true);
+
+ const result = await getSlotGenerationConfig();
+
+ if (result) {
+ setConfig(prev => ({
+ ...prev,
+ bufferMinutes: result.bufferminutes,
+ businessEndHour: result.businessendhour,
+ businessStartHour: result.businessstarthour,
+ maxAdvanceMonths: result.maxadvancemonths,
+ minAdvanceDays: result.minadvancedays,
+ slotDuration: result.slotduration
+ }));
+ }
+
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setConfigIsLoading(false);
+ }
+ })()
+
+ }, [])
+
+ const handleConfigSubmit = async () => {
+ if (configIsLoading) return;
+ try {
+ setConfigIsLoading(true);
+
+ const res = await upsertSlotConfig(config);
+
+ if (res) {
+ console.log("Success");
+ } else {
+ setConfig(initialConfig);
+ throw new Error("Failed to update config");
+ }
+
+ } catch (error) {
+ console.error(error);
+ // TODO: add better error handling
+ } finally {
+ setConfigIsLoading(false);
+ }
+ }
+
+ const handleAddEntry = async (entry: Omit) => {
+ const data = await getUserSession();
+
+ if (!data) return;
+
+ const res = await addCalendarEntry({
+ ...entry,
+ user_id: data.user.id
+ })
+
+ console.log(res);
+ // setEntries((prev) => [...prev, newEntry]);
+ };
+
+ const handleDeleteEntry = async (id: string) => {
+ const res = await deleteCalenderEntry(Number(id));
+ if (!res) return;
+ setEntries((prev) => prev.filter((e) => e.id !== Number(id)));
+ };
+
+ return (
+
+
+
+
+ Slot Configuration
+
+
+
+
+
+
+
+
+
+
+
+ setConfig((prev) => ({
+ ...prev,
+ bufferMinutes: parseInt(e.target.value) || 0,
+ }))
+ }
+ />
+
+
+
+
+
+ setConfig((prev) => ({
+ ...prev,
+ minAdvanceDays: parseInt(e.target.value) || 0,
+ }))
+ }
+ />
+
+
+
+
+
+ setConfig((prev) => ({
+ ...prev,
+ maxAdvanceMonths: parseInt(e.target.value) || 1,
+ }))
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Calendar Entries
+
+
+ Manage your availability
+
+
+
+
+
+
+
+
+ Available Times
+
+ {entries.length}
+
+
+ {entries.length === 0 ? (
+
+
+
+ No availability entries yet. Add your working hours above.
+
+
+
+ ) : (
+
+ {entries.map((entry) => (
+
+ ))}
+
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/booking-section.tsx b/src/components/booking-section.tsx
index d74dc10..299d5c5 100644
--- a/src/components/booking-section.tsx
+++ b/src/components/booking-section.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from "react";
+import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -9,31 +9,86 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { Clock, ArrowRight, Calendar as Cal, X } from "lucide-react";
+import { Clock, ArrowRight, Calendar as Cal, X, Check } from "lucide-react";
import { generateAvailableSlots, getCalenderEntries, getSlotGenerationConfig, validateBookingTime, type AvailableSlot, type SlotGenerationConfig } from "@/lib/calender";
import { addDays, addMonths, format, parseISO } from "date-fns";
import { addBooking, getBookings, type Booking } from "@/lib/booking";
import TimeSlotSelector from "./TimeSlotSelector";
-import { useAuthSession } from "@/lib/auth";
-
-const services = [
- { id: "classic-cut", name: "Classic Haircut", price: 100, duration: 40 },
- { id: "beard-trim", name: "Beard Trim & Shape", price: 100, duration: 40 },
- { id: "full-service", name: "The Full Experience", price: 100, duration: 40 },
+import useFetch from "@/hooks/useFetch";
+import { getUserSession, } from "@/lib/db";
+import { getServices, type ServiceResponse } from "@/lib/settings";
+
+const initialServices: ServiceResponse[] = [
+ { id: 1, service: "Classic Haircut", price: 100, created_at: "2026-02-02T10:00:00Z", description: "This is a description" },
+ { id: 2, service: "Beard Trim & Shape", price: 100, created_at: "2026-02-02T10:00:00Z", description: "This is a description" },
+ { id: 3, service: "The Full Experience", price: 100, created_at: "2026-02-02T10:00:00Z", description: "This is a description" },
];
+
+const fetchAndGenerateAvalibleSlots = async () => {
+ const calendars = await getCalenderEntries();
+
+ if (!calendars) throw new Error("Failed to fetch calender entries");
+
+ const bookings = await getBookings() as Booking[] | null;
+ //
+ if (!bookings) throw new Error("Failed to fetch bookings");
+
+ const slotGenerationConfig = await getSlotGenerationConfig();
+
+ if (!slotGenerationConfig) throw new Error("Failed to fetch slot generation config");
+
+
+ const today = new Date();
+ const startDate = format(addDays(today, Number(slotGenerationConfig.minadvancedays || 2)), 'yyyy-MM-dd');
+ const endDate = format(addMonths(today, Number(slotGenerationConfig.maxadvancemonths || 1)), 'yyyy-MM-dd');
+
+ // Generate available slots with constraints
+ const config: SlotGenerationConfig = {
+ slotDuration: slotGenerationConfig.slotduration || 40,
+ bufferMinutes: slotGenerationConfig.bufferminutes || 15, // 15-minute buffer
+ minAdvanceDays: slotGenerationConfig.minadvancedays || 2, // Book at least 2 days in advance
+ maxAdvanceMonths: slotGenerationConfig.maxadvancemonths || 2, // Book up to 2 months in advance
+ businessStartHour: slotGenerationConfig.businessstarthour || 8,
+ businessEndHour: slotGenerationConfig.businessendhour || 18
+ };
+
+ const availableSlots = generateAvailableSlots(
+ calendars,
+ bookings,
+ startDate,
+ endDate,
+ config
+ );
+
+ // Validate a specific booking
+ const validation = validateBookingTime(
+ "2026-01-15",
+ "11:15",
+ 40,
+ calendars,
+ bookings,
+ config
+ );
+
+ console.log(validation);
+
+ return availableSlots
+};
+
// Simulated barber availability (in real app, this would come from a database/API)
export function BookingSection() {
- const { user } = useAuthSession();
// page state
// TODO: tied page state to loading of slots and handle submition
- const [error, setError] = useState(null);
+ const [_error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
+ const [isSuccess, setIsSuccess] = useState(false);
//
const [selectedSlot, setSelectedSlot] = useState(null);
- const [availableSlots, setAvailableSlots] = useState([]);
+ const { data: availableSlots, loading: _isLoadingSlots, error: _slotsError } = useFetch(fetchAndGenerateAvalibleSlots);
+ const [services, setServices] = useState([]);
const [selectedService, setSelectedService] = useState("");
const [formData, setFormData] = useState({
name: "",
@@ -41,71 +96,21 @@ export function BookingSection() {
phone: "",
});
- const selectedServiceData = services.find((s) => s.id === selectedService);
-
useEffect(() => {
- (async () => {
- try {
- setIsLoading(true);
-
- const calendars = await getCalenderEntries();
-
- if (!calendars) throw new Error("Failed to fetch calender entries");
-
- const bookings = await getBookings() as Booking[] | null;
- //
- if (!bookings) throw new Error("Failed to fetch bookings");
-
- const slotGenerationConfig = await getSlotGenerationConfig();
-
- if (!slotGenerationConfig) throw new Error("Failed to fetch slot generation config");
-
-
- const today = new Date();
- const startDate = format(addDays(today, Number(slotGenerationConfig.minAdvanceDays || 2)), 'yyyy-MM-dd');
- const endDate = format(addMonths(today, Number(slotGenerationConfig.maxAdvanceMonths || 1)), 'yyyy-MM-dd');
-
- // Generate available slots with constraints
- const config: SlotGenerationConfig = {
- slotDuration: slotGenerationConfig.slotDuration || 40,
- bufferMinutes: slotGenerationConfig.bufferMinutes || 15, // 15-minute buffer
- minAdvanceDays: slotGenerationConfig.minAdvanceDays || 2, // Book at least 2 days in advance
- maxAdvanceMonths: slotGenerationConfig.maxAdvanceMonths || 2, // Book up to 2 months in advance
- businessStartHour: slotGenerationConfig.businessStartHour || 8,
- businessEndHour: slotGenerationConfig.businessEndHour || 18
- };
-
- const availableSlots = generateAvailableSlots(
- calendars,
- bookings,
- startDate,
- endDate,
- config
- );
-
- // Validate a specific booking
- const validation = validateBookingTime(
- "2026-01-15",
- "11:15",
- 40,
- calendars,
- bookings,
- config
- );
-
- console.log(availableSlots);
-
- setAvailableSlots(availableSlots);
- } catch (error) {
- console.error(error);
- setError(`Something went wrong: ${error}`);
- } finally {
- setIsLoading(false);
+ getServices().then(services => {
+ if (services) {
+ setServices(services);
+ } else {
+ setServices(initialServices);
}
- }
- )()
+ }).catch((err) => {
+ console.error(err);
+ setServices(initialServices);
+ });
}, [])
+ const selectedServiceData = services.find((s) => s.service === selectedService);
+
// TODO: check of time slot selected
const canSubmit = selectedSlot && selectedServiceData;
@@ -113,20 +118,31 @@ export function BookingSection() {
if (canSubmit && selectedSlot) {
setIsLoading(true);
try {
+ const data = await getUserSession();
+
// TODO: add feed back if failed bool red
const res = await addBooking({
date: selectedSlot.date,
start_time: selectedSlot.start_time + ':00', // Add seconds
end_time: selectedSlot.end_time + ':00', // Add seconds
duration: Number(selectedSlot.duration),
- user_id: user?.user.id || null
+ user_id: data?.user.id || null
}, {
email: formData.email,
name: formData.name,
phone: formData.phone,
- service: selectedServiceData?.name
+ service: selectedServiceData?.service
})
-
+ if (res) {
+ setIsSuccess(true);
+ //Auto-close modal on successful submission and reset form after 3 seconds
+ setTimeout(() => {
+ setIsSuccess(false);
+ setSelectedSlot(null);
+ setSelectedService("");
+ setFormData({ name: "", email: "", phone: "" });
+ }, 5000)
+ }
} catch (error) {
console.error(error);
@@ -163,7 +179,7 @@ export function BookingSection() {
{/* Calendar */}
- setSelectedSlot(slot)} />
+ setSelectedSlot(slot)} />
{/* Time Slots & Service Selection */}
@@ -185,11 +201,11 @@ export function BookingSection() {
{services.map((service) => (
-
+
- {service.name}
+ {service.service}
- ${service.price} • {service.duration}min
+ R{service.price}
@@ -277,10 +293,10 @@ export function BookingSection() {
@@ -288,6 +304,70 @@ export function BookingSection() {
+
+ {/* Success Modal */}
+ {isSuccess && selectedSlot && selectedServiceData && (
+
+ )}
);
}
diff --git a/src/components/footer.tsx b/src/components/footer.tsx
index 53726d1..dd24c0c 100644
--- a/src/components/footer.tsx
+++ b/src/components/footer.tsx
@@ -1,6 +1,14 @@
-import { Scissors, MapPin, Phone, Clock, Instagram, Facebook } from "lucide-react";
-
+import { MapPin, Phone, Clock, Instagram, Facebook} from "lucide-react";
+import { useLocation } from "@tanstack/react-router";
+import kdCutsLogo from "@/assets/KD Cuts Logo.png";
export function Footer() {
+ const location = useLocation();
+ const isDashboard = location.pathname.startsWith('/dashboard');
+
+ //footer not visible on dashboard
+ if (isDashboard) return null;
+
+
return (