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" /> -
- -
+

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 && ( - + ) : ( + editHref && ( + +