Customer Profile
Customer profile update with image upload.
This commit is contained in:
11 files changed
+761
-25
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user