Customer Profile

Customer profile update with image upload.
This commit is contained in:
Stefan Lazic committed 2026-08-06 12:11:36 +01:00
1 parent f07c30da0d
commit 973f18f64c
11 files changed
+761 -25

No files matched your search

+22 -17
View File
@@ -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"
/>
<div className="space-y-2">
<Link
href="/loyalty"
className="border-line flex items-center gap-3 rounded-xl border bg-white px-4 py-3 text-sm font-bold"
>
<Gift className="h-4 w-4" aria-hidden="true" /> Loyalty card
</Link>
<Link
href="/settings/notifications"
className="border-line flex items-center gap-3 rounded-xl border bg-white px-4 py-3 text-sm font-bold"
>
<Bell className="h-4 w-4" aria-hidden="true" /> Notifications
</Link>
</div>
<ul className="divide-line divide-y rounded-md border border-line bg-white">
{SETTINGS_LINKS.map(({ href, label, icon: Icon }) => (
<li key={href}>
<Link href={href} className="hover:bg-stone flex items-center gap-3 px-5 py-4 text-sm font-bold">
<Icon className="text-muted h-5 w-5" aria-hidden="true" />
<span className="flex-1">{label}</span>
<ChevronRight className="text-muted h-4 w-4" aria-hidden="true" />
</Link>
</li>
))}
</ul>
<div className="border-line rounded-md border bg-white p-5">
<p className="mb-3 text-sm font-bold">Get the app</p>
<InstallAppButton businessName={branding.name} />
@@ -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 (
<div className="pb-12">
<TopNav title="Preferred Barber" onBack sticky className="bg-stone" />
<main className="mx-auto max-w-2xl space-y-4 px-6 py-6">
<p className="text-muted text-sm">Chosen for you by default when booking — you can still pick anyone else.</p>
<PreferredBarberPicker
staff={staff.map((s) => ({ id: s.id, name: s.user.name ?? "Barber", avatarUrl: s.user.image ?? undefined }))}
initialPreferredId={customerProfile?.preferredBarberId ?? null}
/>
</main>
</div>
);
}
@@ -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 (
<div className="pb-12">
<TopNav title="Edit Profile" onBack sticky className="bg-stone" />
<main className="mx-auto max-w-2xl space-y-6 px-6 py-6">
<CustomerProfileForm
initial={{
firstName: customerProfile?.firstName ?? sessionFirstName ?? "",
lastName: customerProfile?.lastName ?? sessionLastNameParts.join(" "),
email: customerProfile?.email ?? session.user.email ?? "",
phone: customerProfile?.phone ?? "",
image: user?.image ?? "",
}}
/>
</main>
</div>
);
}