admin: Superforms + Zod validation, three-address-defaults schema, profiles toast fix

- Add Superforms + Zod 4 for all admin form validation
- Replace address isDefault with three separate boolean defaults (Mailing, Billing, Shipping)
- Update profiles page with three-checkbox address form and toast improvements
- Convert all admin page server actions to pass request directly to crud
- Export superFormErrorsToFieldErrors helper for profiles server actions
- Regenerate Prisma client for new schema fields
This commit is contained in:
NeilClack
2026-07-07 13:36:23 -05:00
parent 38347f5f24
commit 38d1752f5b
98 changed files with 3408 additions and 1758 deletions
@@ -0,0 +1,5 @@
-- AlterTable: Remove price from Proclamation
ALTER TABLE "Proclamation" DROP COLUMN "price";
-- AlterTable: Change SolfeggioFrequency value from Float to Int
ALTER TABLE "SolfeggioFrequency" ALTER COLUMN "value" SET DATA TYPE INTEGER;
@@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `isDefault` on the `Address` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Address" DROP COLUMN "isDefault",
ADD COLUMN "isDefaultBilling" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "isDefaultMailing" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "isDefaultShipping" BOOLEAN NOT NULL DEFAULT false;
+12 -10
View File
@@ -47,16 +47,18 @@ model Profile {
}
model Address {
id String @id @default(uuid())
profileId String
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)
street String
city String
state String
postalCode String
country String
type String @default("Mailing")
isDefault Boolean @default(false)
id String @id @default(uuid())
profileId String
profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade)
street String
city String
state String
postalCode String
country String
type String @default("Mailing")
isDefaultMailing Boolean @default(false)
isDefaultBilling Boolean @default(false)
isDefaultShipping Boolean @default(false)
}
model Session {
+111
View File
@@ -0,0 +1,111 @@
import 'dotenv/config';
import pg from 'pg';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../src/lib/generated/prisma/client';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
const userRoles = [
{ id: 1, name: 'Super' },
{ id: 2, name: 'Admin' },
{ id: 3, name: 'Staff' },
{ id: 4, name: 'User' }
];
const solfeggioFrequencies = [
{ id: 1, name: 'Crown', value: 936 },
{ id: 2, name: '3rd Eye', value: 852 },
{ id: 3, name: 'Throat', value: 741 },
{ id: 4, name: 'Heart', value: 639 },
{ id: 5, name: 'Solar Plexus', value: 528 },
{ id: 6, name: 'Sacral', value: 417 },
{ id: 7, name: 'Root', value: 396 }
];
const subscriptionLevels = [
{ id: 1, name: 'Basic', description: 'General only' },
{ id: 2, name: 'Advanced', description: 'General + Specific' },
{ id: 3, name: 'Elite', description: 'General + Specific + Precise' }
];
const proclamationCategories = [
{ id: 4, name: 'Spiritual', parentId: null },
{ id: 5, name: 'General', parentId: 4 },
{ id: 9, name: 'Specific', parentId: 4 },
{ id: 40, name: 'Precise', parentId: 4 },
{ id: 2, name: 'Physical', parentId: null },
{ id: 3, name: 'General', parentId: 2 },
{ id: 10, name: 'Specific', parentId: 2 },
{ id: 32, name: 'Precise', parentId: 2 },
{ id: 16, name: 'Mental', parentId: null },
{ id: 30, name: 'General', parentId: 16 },
{ id: 31, name: 'Specific', parentId: 16 },
{ id: 38, name: 'Precise', parentId: 16 },
{ id: 17, name: 'Custom (Client Specific)', parentId: null },
{ id: 19, name: 'Financial', parentId: null },
{ id: 28, name: 'General', parentId: 19 },
{ id: 29, name: 'Specific', parentId: 19 },
{ id: 37, name: 'Precise', parentId: 19 },
{ id: 20, name: 'Emotional', parentId: null },
{ id: 24, name: 'General', parentId: 20 },
{ id: 26, name: 'Specific', parentId: 20 },
{ id: 45, name: 'Precise', parentId: 20 },
{ id: 21, name: 'Social', parentId: null },
{ id: 22, name: 'General', parentId: 21 },
{ id: 23, name: 'Specific', parentId: 21 },
{ id: 39, name: 'Precise', parentId: 21 },
{ id: 41, name: 'Biblical', parentId: null },
{ id: 42, name: 'Personal Growth & Development', parentId: null },
{ id: 43, name: 'Prayers', parentId: 41 },
{ id: 44, name: 'Scripture', parentId: 41 },
{ id: 46, name: 'Sports', parentId: null }
];
async function main() {
console.log('Seeding UserRole...');
for (const role of userRoles) {
await prisma.userRole.upsert({
where: { id: role.id },
create: role,
update: role
});
}
console.log('Seeding SolfeggioFrequency...');
for (const freq of solfeggioFrequencies) {
await prisma.solfeggioFrequency.upsert({
where: { id: freq.id },
create: freq,
update: freq
});
}
console.log('Seeding ProclamationCategory...');
for (const cat of proclamationCategories) {
await prisma.proclamationCategory.upsert({
where: { id: cat.id },
create: cat,
update: cat
});
}
console.log('Seeding SubscriptionLevel...');
for (const level of subscriptionLevels) {
await prisma.subscriptionLevel.upsert({
where: { id: level.id },
create: level,
update: level
});
}
console.log('Seed complete.');
}
main()
.catch((e) => {
console.error('Seed failed:', e);
process.exit(1);
})
.finally(() => prisma.$disconnect());