diff --git a/src/app/(customer)/settings/page.tsx b/src/app/(customer)/settings/page.tsx
index 4582f71..4eafa9f 100644
--- a/src/app/(customer)/settings/page.tsx
+++ b/src/app/(customer)/settings/page.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { Bell, Gift, LogIn } from "lucide-react";
+import { Bell, ChevronRight, Gift, LogIn, User } from "lucide-react";
import Link from "next/link";
import { auth } from "@/auth";
import { Button } from "@/components/ui/button";
@@ -13,6 +13,12 @@ import { InstallAppButton } from "@/features/pwa/components/install-app-button";
export const metadata: Metadata = { title: "Settings" };
export const dynamic = "force-dynamic";
+const SETTINGS_LINKS = [
+ { href: "/settings/preferred-barber", label: "Preferred Barber", icon: User },
+ { href: "/loyalty", label: "Loyalty Card", icon: Gift },
+ { href: "/settings/notifications", label: "Notifications", icon: Bell },
+] as const;
+
export default async function CustomerSettingsPage() {
const session = await auth();
@@ -34,8 +40,9 @@ export default async function CustomerSettingsPage() {
);
}
- const [customerProfile, branding] = await Promise.all([
+ const [customerProfile, user, branding] = await Promise.all([
prisma.customerProfile.findUnique({ where: { userId: session.user.id } }),
+ prisma.user.findUnique({ where: { id: session.user.id }, select: { image: true } }),
getBranding(),
]);
@@ -49,22 +56,20 @@ export default async function CustomerSettingsPage() {
: (session.user.name ?? "Signed-in customer")
}
meta={customerProfile?.email ?? session.user.email}
- avatarUrl={session.user.image ?? undefined}
+ avatarUrl={user?.image ?? undefined}
+ editHref="/settings/profile"
/>
-
-
- Loyalty card
-
-
- Notifications
-
-
+
+ {SETTINGS_LINKS.map(({ href, label, icon: Icon }) => (
+ -
+
+
+ {label}
+
+
+
+ ))}
+
Get the app
diff --git a/src/app/(customer)/settings/preferred-barber/page.tsx b/src/app/(customer)/settings/preferred-barber/page.tsx
new file mode 100644
index 0000000..9ac51e3
--- /dev/null
+++ b/src/app/(customer)/settings/preferred-barber/page.tsx
@@ -0,0 +1,38 @@
+import type { Metadata } from "next";
+import { redirect } from "next/navigation";
+import { auth } from "@/auth";
+import { TopNav } from "@/components/navigation/top-nav";
+import { getPrimaryBusiness } from "@/lib/business/get-primary-business";
+import { prisma } from "@/lib/db/prisma";
+import { PreferredBarberPicker } from "@/features/customer/components/preferred-barber-picker";
+
+export const metadata: Metadata = { title: "Preferred Barber" };
+export const dynamic = "force-dynamic";
+
+export default async function PreferredBarberPage() {
+ const session = await auth();
+ if (!session?.user) redirect("/sign-in?callbackUrl=/settings/preferred-barber");
+
+ const [business, customerProfile] = await Promise.all([
+ getPrimaryBusiness(),
+ prisma.customerProfile.findUnique({ where: { userId: session.user.id } }),
+ ]);
+
+ const staff = await prisma.staffProfile.findMany({
+ where: { businessId: business.id, status: "ACTIVE", isBookableOnline: true },
+ include: { user: { select: { name: true, image: true } } },
+ });
+
+ return (
+
+
+
+ Chosen for you by default when booking — you can still pick anyone else.
+ ({ id: s.id, name: s.user.name ?? "Barber", avatarUrl: s.user.image ?? undefined }))}
+ initialPreferredId={customerProfile?.preferredBarberId ?? null}
+ />
+
+
+ );
+}
diff --git a/src/app/(customer)/settings/profile/page.tsx b/src/app/(customer)/settings/profile/page.tsx
new file mode 100644
index 0000000..4a08312
--- /dev/null
+++ b/src/app/(customer)/settings/profile/page.tsx
@@ -0,0 +1,38 @@
+import type { Metadata } from "next";
+import { redirect } from "next/navigation";
+import { auth } from "@/auth";
+import { TopNav } from "@/components/navigation/top-nav";
+import { prisma } from "@/lib/db/prisma";
+import { CustomerProfileForm } from "@/features/customer/components/customer-profile-form";
+
+export const metadata: Metadata = { title: "Edit Profile" };
+export const dynamic = "force-dynamic";
+
+export default async function CustomerEditProfilePage() {
+ const session = await auth();
+ if (!session?.user) redirect("/sign-in?callbackUrl=/settings/profile");
+
+ const [customerProfile, user] = await Promise.all([
+ prisma.customerProfile.findUnique({ where: { userId: session.user.id } }),
+ prisma.user.findUnique({ where: { id: session.user.id }, select: { image: true } }),
+ ]);
+
+ const [sessionFirstName, ...sessionLastNameParts] = (session.user.name ?? "").trim().split(" ");
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ui/profile-card.tsx b/src/components/ui/profile-card.tsx
index dbbf7b2..984ba73 100644
--- a/src/components/ui/profile-card.tsx
+++ b/src/components/ui/profile-card.tsx
@@ -1,4 +1,5 @@
import Image from "next/image";
+import Link from "next/link";
import { Pencil } from "lucide-react";
import type { ReactNode } from "react";
import { Card } from "@/components/ui/card";
@@ -8,10 +9,14 @@ export interface ProfileCardProps {
meta?: ReactNode;
avatarUrl?: string;
onEdit?: () => void;
+ /** Renders the edit-pencil as a `
` instead of a button — lets a Server Component page use the edit affordance without becoming a Client Component. Ignored if `onEdit` is also passed. */
+ editHref?: string;
}
/** Avatar + name + meta + edit-pencil pattern reused across nearly every settings/profile screen. */
-export function ProfileCard({ name, meta, avatarUrl, onEdit }: ProfileCardProps) {
+export function ProfileCard({ name, meta, avatarUrl, onEdit, editHref }: ProfileCardProps) {
+ const editButtonClassName = "flex h-10 w-10 items-center justify-center rounded-full bg-stone text-ink";
+
return (
{avatarUrl ? (
@@ -31,15 +36,16 @@ export function ProfileCard({ name, meta, avatarUrl, onEdit }: ProfileCardProps)
{name}
{meta && {meta}
}
- {onEdit && (
-