Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions apps/app/app/[locale]/(dashboard)/bookings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { db } from "@repo/db";
import { auth } from "@/auth";
import { redirect } from "next/navigation";
import { format, startOfDay } from "date-fns";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { CalendarClock, User, Mail, AlignLeft } from "lucide-react";

export default async function BookingsPage() {
const session = await auth();

if (!session?.user?.id) {
redirect("/login");
}

const todayStart = startOfDay(new Date());

// 1. Fetch upcoming bookings for the logged-in Host
const upcomingBookings = await db.booking.findMany({
where: {
hostId: session.user.id,
startTime: {
gte: todayStart, // Only show meetings happening in the future
},
},
orderBy: {
startTime: "asc", // Show the soonest meetings first
},
include: {
eventType: {
select: {
title: true,
duration: true,
},
},
},
});

return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Upcoming Bookings</h1>
<p className="text-muted-foreground mt-2">
Manage your scheduled meetings and events.
</p>
</div>

{upcomingBookings.length === 0 ? (
<Card className="flex flex-col items-center justify-center p-12 text-center bg-muted/20 border-dashed">
<CalendarClock className="h-12 w-12 text-muted-foreground/50 mb-4" />
<CardTitle className="text-xl">No upcoming meetings</CardTitle>
<CardDescription className="mt-2 max-w-sm">
When guests schedule time with you, their bookings will appear here.
Share your event links to get started!
</CardDescription>
</Card>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{upcomingBookings.map((booking) => (
<Card key={booking.id} className="overflow-hidden flex flex-col">
<div className="h-2 w-full bg-primary" />
<CardHeader className="pb-3">
<div className="flex justify-between items-start">
<div>
<CardTitle className="text-lg">
{booking.eventType.title}
</CardTitle>
<CardDescription className="mt-1">
{booking.eventType.duration} minutes
</CardDescription>
</div>
<Badge
variant={
booking.status === "ACCEPTED" ? "default" : "secondary"
}
>
{booking.status}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4 flex-1">
{/* Time Context */}
<div className="bg-muted/40 p-3 rounded-md border border-border/50">
<p className="font-medium text-sm">
{/* Displaying time in UTC for the host for now - we can add timezone parsing later */}
{format(booking.startTime, "EEEE, MMMM d, yyyy")}
</p>
<p className="text-sm text-muted-foreground mt-0.5">
{format(booking.startTime, "h:mm a")} -{" "}
{format(booking.endTime, "h:mm a")}
</p>
</div>

{/* Guest Details */}
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<User className="h-4 w-4 text-muted-foreground shrink-0" />
<span className="font-medium">{booking.guestName}</span>
</div>
<div className="flex items-center gap-2">
<Mail className="h-4 w-4 text-muted-foreground shrink-0" />
<a
href={`mailto:${booking.guestEmail}`}
className="text-primary hover:underline truncate"
>
{booking.guestEmail}
</a>
</div>
{booking.guestNotes && (
<div className="flex items-start gap-2 pt-1">
<AlignLeft className="h-4 w-4 text-muted-foreground shrink-0 mt-0.5" />
<p className="text-muted-foreground italic line-clamp-2">
"{booking.guestNotes}"
</p>
</div>
)}
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
);
}
77 changes: 7 additions & 70 deletions apps/app/app/[locale]/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,17 @@
import Link from "next/link";
import { ReactNode } from "react";
import {
CalendarClock,
CalendarRange,
BookOpen,
BarChart2,
} from "lucide-react";

const NAV_ITEMS = [
{
label: "Event Types",
href: "/event-types",
icon: CalendarRange,
},
{
label: "Availability",
href: "/availability",
icon: CalendarClock,
},
{
label: "Bookings",
href: "/bookings",
icon: BookOpen,
},
{
label: "Polls",
href: "/polls",
icon: BarChart2,
},
];
import { AppSidebar } from "@/components/dashboard/app-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

export default function DashboardLayout({ children }: { children: ReactNode }) {
return (
<div className="flex min-h-screen bg-[#090909] text-foreground">
{/* Sidebar Navigation */}
<aside className="hidden md:flex flex-col w-60 border-r border-border/40 bg-[#111111] p-4 shrink-0">
{/* Brand Logo */}
<div className="flex items-center gap-2.5 px-3 py-3 mb-6">
<div className="h-7 w-7 rounded-lg bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm">
S
</div>
<span className="font-semibold text-base tracking-tight">
SlotSyncro
</span>
</div>

{/* Navigation Links */}
<nav className="space-y-1 flex-1">
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className="flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-[#1F1F1F] transition-colors"
>
<Icon className="h-4 w-4 shrink-0" />
<span>{item.label}</span>
</Link>
);
})}
</nav>

{/* Footer info */}
<div className="border-t border-border/40 pt-4 px-3 flex items-center justify-between text-xs text-muted-foreground">
<span>SlotSyncro App</span>
<span className="bg-muted/30 px-2 py-0.5 rounded font-mono text-[10px]">
v1.0
</span>
</div>
</aside>
<SidebarProvider>
{/* Client-side Sidebar Component */}
<AppSidebar />

{/* Main Page Workspace */}
<main className="flex-1 min-w-0 p-6 md:p-10 overflow-y-auto">
{children}
<div className="max-w-6xl mx-auto w-full">{children}</div>
</main>
</div>
</SidebarProvider>
);
}
21 changes: 17 additions & 4 deletions apps/app/app/[locale]/[username]/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { db } from "@repo/db";
import { notFound } from "next/navigation";
import { BookingView } from "@/components/booking/booking-view";
import { auth } from "@/auth";

interface PublicBookingPageProps {
params: Promise<{
Expand All @@ -15,7 +16,18 @@ export default async function PublicBookingPage({
}: PublicBookingPageProps) {
const { locale, username, slug } = await params;

// 🎯 Query host by username OR fallback to user ID if username is null
// 1. Fetch current user session to auto-populate the booking form
const session = await auth();

const loggedInUser = session?.user?.id
? {
id: session.user.id as string,
name: session.user.name || "",
email: session.user.email || "",
}
: null;

// 2. Query host by username OR fallback to user ID if username is null
const host = await db.user.findFirst({
where: {
OR: [{ username: username }, { id: username }],
Expand All @@ -29,7 +41,7 @@ export default async function PublicBookingPage({
notFound();
}

// Fetch the event type scoped to this host
// 3. Fetch the event type scoped to this host
const eventType = await db.eventType.findUnique({
where: {
userId_slug: {
Expand All @@ -43,12 +55,12 @@ export default async function PublicBookingPage({
notFound();
}

// Fetch existing bookings to calculate availability conflicts
// 4. Fetch existing bookings to calculate availability conflicts
const existingBookings = await db.booking.findMany({
where: {
hostId: host.id,
startTime: {
gte: new Date(),
gte: new Date(), // Only fetch future bookings to save memory
},
},
});
Expand All @@ -62,6 +74,7 @@ export default async function PublicBookingPage({
weeklyAvailability={host.weeklyAvailability}
existingBookings={existingBookings}
locale={locale}
loggedInUser={loggedInUser} // Type error resolved
/>
</div>
</main>
Expand Down
Loading
Loading