Base app architecture:

    Route groups for customer, staff and owner.
* Reusable design system with around 20 UI components.
* Complete Prisma schema.
* Authentication with email/password and Google.
* Role and permission model.
* Protected routes.
* Clean separation between features, services, components and shared utilities.
This commit is contained in:
Stefan Lazic committed 2026-08-02 17:47:23 +01:00
1 parent 468d163299
commit 090646fdfd
201 files changed
+31984

No files matched your search

+251
View File
@@ -0,0 +1,251 @@
/**
* Dev/demo fixtures. Run with `npm run prisma:seed` (requires a reachable
* DATABASE_URL — see .env.example). Intentionally small: enough to exercise
* every relation in the schema once, not a full demo dataset.
*/
import "dotenv/config";
import { PrismaClient, Permission, Weekday, RewardType } from "../generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import bcrypt from "bcryptjs";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
const OWNER_PERMISSIONS: Permission[] = [
Permission.MANAGE_BUSINESS_PROFILE,
Permission.MANAGE_BOOKING_RULES,
Permission.MANAGE_SERVICES,
Permission.MANAGE_STAFF,
Permission.MANAGE_LOYALTY_PROGRAMME,
Permission.MANAGE_NOTIFICATION_SETTINGS,
Permission.VIEW_ALL_STAFF_APPOINTMENTS,
Permission.MANAGE_OWN_APPOINTMENTS,
Permission.CHECK_IN_CUSTOMERS,
Permission.MANAGE_LOYALTY_STAMPS,
Permission.MANAGE_OWN_AVAILABILITY,
Permission.VIEW_CUSTOMER_DIRECTORY,
Permission.BOOK_ON_BEHALF_OF_CUSTOMER,
];
const BARBER_PERMISSIONS: Permission[] = [
Permission.MANAGE_OWN_APPOINTMENTS,
Permission.CHECK_IN_CUSTOMERS,
Permission.MANAGE_LOYALTY_STAMPS,
Permission.MANAGE_OWN_AVAILABILITY,
Permission.VIEW_CUSTOMER_DIRECTORY,
Permission.BOOK_ON_BEHALF_OF_CUSTOMER,
];
const WEEKDAYS: Weekday[] = [
Weekday.MONDAY,
Weekday.TUESDAY,
Weekday.WEDNESDAY,
Weekday.THURSDAY,
Weekday.FRIDAY,
Weekday.SATURDAY,
];
async function main() {
const business = await prisma.business.upsert({
where: { slug: "blake-heritage-barbershop" },
update: {},
create: {
slug: "blake-heritage-barbershop",
name: "Blake Heritage Barbershop",
description:
"Blake Heritage has come to Northwood! Check our Instagram @Blake.Heritage.Barbershop for more info.",
phone: "+44 1923 000000",
email: "hello@blakeheritage.co.uk",
timezone: "Europe/London",
currency: "GBP",
locale: "en-GB",
locations: {
create: {
addressLine1: "79 Joel Street",
city: "Northwood",
postalCode: "HA6 1LW",
country: "GB",
},
},
settings: {
create: {
cancellationPolicy: "Free cancellation up to 24 hours before your appointment.",
lateArrivalPolicy: "Arrivals more than 10 minutes late may need to be rescheduled.",
},
},
bookingRule: { create: {} }, // all defaults from the schema
hours: {
create: [
...WEEKDAYS.map((dayOfWeek) => ({
dayOfWeek,
opensAtMinutes: 10 * 60,
closesAtMinutes: 19 * 60,
})),
{ dayOfWeek: Weekday.SUNDAY, isClosed: true },
],
},
loyaltyProgramme: {
create: {
isEnabled: true,
stampsRequired: 10,
rewardType: RewardType.PERCENTAGE,
rewardValue: 50,
rewardLabel: "50% Off Any Service",
termsLine: "Valid for 12 months. One reward per visit.",
},
},
},
});
const ownerRole = await prisma.role.upsert({
where: { businessId_name: { businessId: business.id, name: "Owner" } },
update: {},
create: {
businessId: business.id,
name: "Owner",
isSystem: true,
permissions: { create: OWNER_PERMISSIONS.map((permission) => ({ permission })) },
},
});
const barberRole = await prisma.role.upsert({
where: { businessId_name: { businessId: business.id, name: "Barber" } },
update: {},
create: {
businessId: business.id,
name: "Barber",
isSystem: true,
permissions: { create: BARBER_PERMISSIONS.map((permission) => ({ permission })) },
},
});
const category = await prisma.serviceCategory.upsert({
where: { id: `${business.id}-senior-barber` },
update: {},
create: {
id: `${business.id}-senior-barber`,
businessId: business.id,
name: "Hair & Beard Services — Senior Barber",
},
});
const [haircut, skinfade] = await Promise.all([
prisma.service.upsert({
where: { id: `${business.id}-haircut-style` },
update: {},
create: {
id: `${business.id}-haircut-style`,
businessId: business.id,
categoryId: category.id,
name: "Haircut & Style",
durationMinutes: 30,
price: 22,
},
}),
prisma.service.upsert({
where: { id: `${business.id}-skinfade-style` },
update: {},
create: {
id: `${business.id}-skinfade-style`,
businessId: business.id,
categoryId: category.id,
name: "Skinfade / Taper & Style",
durationMinutes: 30,
price: 25,
},
}),
]);
const ownerPasswordHash = await bcrypt.hash("password123", 12);
const ownerUser = await prisma.user.upsert({
where: { email: "marcus@blakeheritage.co.uk" },
update: {},
create: {
email: "marcus@blakeheritage.co.uk",
name: "Marcus Reed",
passwordHash: ownerPasswordHash,
emailVerified: new Date(),
staffProfile: {
create: {
businessId: business.id,
roleId: ownerRole.id,
bio: "Master barber, owner.",
workingHours: {
create: WEEKDAYS.map((dayOfWeek) => ({
dayOfWeek,
startMinutes: 10 * 60,
endMinutes: 19 * 60,
})),
},
services: {
create: [{ serviceId: haircut.id }, { serviceId: skinfade.id }],
},
},
},
},
include: { staffProfile: true },
});
const barberPasswordHash = await bcrypt.hash("password123", 12);
await prisma.user.upsert({
where: { email: "leo@blakeheritage.co.uk" },
update: {},
create: {
email: "leo@blakeheritage.co.uk",
name: "Leo Vance",
passwordHash: barberPasswordHash,
emailVerified: new Date(),
staffProfile: {
create: {
businessId: business.id,
roleId: barberRole.id,
bio: "Senior specialist.",
workingHours: {
create: WEEKDAYS.map((dayOfWeek) => ({
dayOfWeek,
startMinutes: 10 * 60,
endMinutes: 19 * 60,
})),
},
services: { create: [{ serviceId: haircut.id }] },
},
},
},
});
const customerPasswordHash = await bcrypt.hash("password123", 12);
await prisma.user.upsert({
where: { email: "alex.chen@example.com" },
update: {},
create: {
email: "alex.chen@example.com",
name: "Alex Chen",
passwordHash: customerPasswordHash,
emailVerified: new Date(),
customerProfile: {
create: {
firstName: "Alex",
lastName: "Chen",
email: "alex.chen@example.com",
isGuest: false,
preferredBarberId: ownerUser.staffProfile?.id,
},
},
},
});
console.log("Seed complete:", {
business: business.name,
roles: [ownerRole.name, barberRole.name],
services: [haircut.name, skinfade.name],
});
}
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});