Base app architecture:

    Route groups for customer, staff and owner.
* Reusable design system with around 20 UI components.
* Complete Prisma schema.
* Authentication with email/password and Google.
* Role and permission model.
* Protected routes.
* Clean separation between features, services, components and shared utilities.
This commit is contained in:
Stefan Lazic committed 2026-08-02 17:47:23 +01:00
1 parent 468d163299
commit 090646fdfd
201 files changed
+31984

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
import Image from "next/image";
import type { ReactNode } from "react";
import { Card } from "@/components/ui/card";
import { StatusPill } from "@/components/ui/status-pill";
import type { AppointmentStatus, StaffSummary } from "@/types/domain";
export interface AppointmentCardProps {
status: AppointmentStatus;
serviceName: string;
staff: StaffSummary;
/** Pre-formatted for display, e.g. "Sat, Oct 12". */
formattedDate: string;
/** Pre-formatted for display, e.g. "2:00 PM". */
formattedTime: string;
/** Reschedule/Cancel/Book again buttons — left to the caller so this
* component stays agnostic of which actions apply to which status. */
actions?: ReactNode;
href?: string;
}
export function AppointmentCard({
status,
serviceName,
staff,
formattedDate,
formattedTime,
actions,
}: AppointmentCardProps) {
return (
<Card className="p-5">
<div className="mb-4 flex items-start justify-between">
<StatusPill status={status} />
<div className="text-right">
<p className="text-sm font-bold">{formattedDate}</p>
<p className="text-xs text-muted">{formattedTime}</p>
</div>
</div>
<div className="mb-4 flex items-center gap-3">
{staff.avatarUrl ? (
<Image
src={staff.avatarUrl}
alt=""
width={40}
height={40}
className="h-10 w-10 rounded-full object-cover"
/>
) : (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-stone text-xs font-bold text-muted">
{staff.name.charAt(0)}
</div>
)}
<div>
<h3 className="text-sm font-bold">{serviceName}</h3>
<p className="text-xs text-muted">with {staff.name}</p>
</div>
</div>
{actions && <div className="flex gap-2 border-t border-line pt-4">{actions}</div>}
</Card>
);
}