Phase 7
This commit is contained in:
189 files changed
+9873
-655
No files matched your search
@@ -13,6 +13,7 @@ import { computeOpeningStatus } from "@/lib/business/opening-status";
|
||||
import { getPrimaryBusiness } from "@/lib/business/get-primary-business";
|
||||
import { prisma } from "@/lib/db/prisma";
|
||||
import { getAvailableServices } from "@/services/scheduling/availability.service";
|
||||
import { getLoyaltySummary, type LoyaltySummary } from "@/services/loyalty/loyalty.service";
|
||||
import { getBusinessReviews } from "@/services/reviews/reviews.service";
|
||||
import type { AppointmentStatus } from "@/types/domain";
|
||||
|
||||
@@ -67,8 +68,7 @@ export default async function CustomerHomePage() {
|
||||
|
||||
let customerProfile: Awaited<ReturnType<typeof prisma.customerProfile.findUnique>> = null;
|
||||
let upcomingAppointment: UpcomingAppointment | null = null;
|
||||
let loyaltyAccount: Awaited<ReturnType<typeof prisma.loyaltyAccount.findFirst>> = null;
|
||||
let loyaltyProgramme: Awaited<ReturnType<typeof prisma.loyaltyProgramme.findUnique>> = null;
|
||||
let loyalty: LoyaltySummary | null = null;
|
||||
let preferredBarberName: string | null = null;
|
||||
let lastCompletedAppointment: Awaited<ReturnType<typeof prisma.appointment.findFirst>> = null;
|
||||
|
||||
@@ -78,7 +78,7 @@ export default async function CustomerHomePage() {
|
||||
});
|
||||
|
||||
if (customerProfile) {
|
||||
[upcomingAppointment, loyaltyProgramme, lastCompletedAppointment] = await Promise.all([
|
||||
[upcomingAppointment, loyalty, lastCompletedAppointment] = await Promise.all([
|
||||
prisma.appointment.findFirst({
|
||||
where: {
|
||||
customerId: customerProfile.id,
|
||||
@@ -88,24 +88,13 @@ export default async function CustomerHomePage() {
|
||||
orderBy: { startAt: "asc" },
|
||||
include: { services: { include: { service: true } }, staff: { include: { user: true } } },
|
||||
}),
|
||||
prisma.loyaltyProgramme.findUnique({ where: { businessId: business.id } }),
|
||||
getLoyaltySummary(prisma, { customerId: customerProfile.id, businessId: business.id }),
|
||||
prisma.appointment.findFirst({
|
||||
where: { customerId: customerProfile.id, status: "COMPLETED" },
|
||||
orderBy: { startAt: "desc" },
|
||||
}),
|
||||
]);
|
||||
|
||||
if (loyaltyProgramme?.isEnabled) {
|
||||
loyaltyAccount = await prisma.loyaltyAccount.findUnique({
|
||||
where: {
|
||||
customerId_loyaltyProgrammeId: {
|
||||
customerId: customerProfile.id,
|
||||
loyaltyProgrammeId: loyaltyProgramme.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (customerProfile.preferredBarberId) {
|
||||
const preferred = await prisma.staffProfile.findUnique({
|
||||
where: { id: customerProfile.preferredBarberId },
|
||||
@@ -145,7 +134,7 @@ export default async function CustomerHomePage() {
|
||||
</div>
|
||||
<span className="text-muted">· {reviews.totalReviews.toLocaleString()} reviews</span>
|
||||
<span
|
||||
className={openingStatus.isOpen ? "text-accent font-bold" : "text-danger font-bold"}
|
||||
className={openingStatus.isOpen ? "text-accent-foreground font-bold" : "text-danger font-bold"}
|
||||
>
|
||||
{openingStatus.label}
|
||||
</span>
|
||||
@@ -191,25 +180,38 @@ export default async function CustomerHomePage() {
|
||||
)}
|
||||
|
||||
{/* Loyalty summary */}
|
||||
{loyaltyProgramme?.isEnabled && loyaltyAccount && (
|
||||
{loyalty?.isEnabled && loyalty.accountId && (
|
||||
<Link
|
||||
href="/settings"
|
||||
className="rounded-jumbo border-line mb-6 block border bg-white p-5 shadow-sm"
|
||||
href="/loyalty"
|
||||
className={
|
||||
loyalty.availableReward
|
||||
? "rounded-jumbo border-accent-loyalty bg-accent-loyalty/10 mb-6 block border p-5 shadow-sm"
|
||||
: "rounded-jumbo border-line mb-6 block border bg-white p-5 shadow-sm"
|
||||
}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<p className="text-sm font-bold">Loyalty Progress</p>
|
||||
<span className="text-muted text-xs">
|
||||
{loyaltyAccount.currentStamps}/{loyaltyProgramme.stampsRequired} stamps
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-stone h-2 w-full overflow-hidden rounded-full">
|
||||
<div
|
||||
className="bg-accent-loyalty h-full rounded-full"
|
||||
style={{
|
||||
width: `${Math.min(100, (loyaltyAccount.currentStamps / loyaltyProgramme.stampsRequired) * 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{loyalty.availableReward ? (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-bold">Reward available — {loyalty.availableReward.label}</p>
|
||||
<span className="text-accent-loyalty-foreground text-xs font-bold uppercase tracking-widest">View</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<p className="text-sm font-bold">Loyalty Progress</p>
|
||||
<span className="text-muted text-xs">
|
||||
{loyalty.currentStamps}/{loyalty.stampsRequired} stamps
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-stone h-2 w-full overflow-hidden rounded-full">
|
||||
<div
|
||||
className="bg-accent-loyalty h-full rounded-full"
|
||||
style={{
|
||||
width: `${Math.min(100, (loyalty.currentStamps / loyalty.stampsRequired) * 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user