feat: Add configurable deposit percentage, FAQ management, and fix PayID copy button

This commit is contained in:
Gemini CLI committed 2026-06-03 20:05:21 +10:00
1 parent d34950a91b
commit 27f3d73d01
12 files changed
+1111 -334

No files matched your search

-1
View File
@@ -1813,7 +1813,6 @@
"integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/config": "6.19.3",
"@prisma/engines": "6.19.3"
@@ -0,0 +1,42 @@
-- AlterTable
ALTER TABLE "StockItem" ADD COLUMN "maxStock" REAL;
-- CreateTable
CREATE TABLE "FAQ" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"question" TEXT NOT NULL,
"answer" TEXT NOT NULL,
"order" INTEGER NOT NULL DEFAULT 0
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Config" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT DEFAULT 1,
"adminCode" TEXT NOT NULL DEFAULT '1234',
"finalDepositDate" TEXT,
"cookDay" TEXT,
"payIdInfo" TEXT DEFAULT 'Your PayID Here',
"termsOfService" TEXT DEFAULT 'To secure your order, a 30% non-refundable deposit is required upfront before smoking begins. Payments can be made via PayID or Cash. The remaining balance is payable upon pickup.',
"orderingPolicy" TEXT DEFAULT 'Deposits cover material costs and are final. PayID details provided after ordering.'
);
INSERT INTO "new_Config" ("adminCode", "id") SELECT "adminCode", "id" FROM "Config";
DROP TABLE "Config";
ALTER TABLE "new_Config" RENAME TO "Config";
CREATE TABLE "new_Order" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"orderNumber" TEXT,
"name" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"items" TEXT NOT NULL,
"totalWeight" REAL NOT NULL,
"estimatedTotal" REAL NOT NULL,
"status" TEXT NOT NULL DEFAULT 'Pending',
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO "new_Order" ("estimatedTotal", "id", "items", "name", "phone", "timestamp", "totalWeight") SELECT "estimatedTotal", "id", "items", "name", "phone", "timestamp", "totalWeight" FROM "Order";
DROP TABLE "Order";
ALTER TABLE "new_Order" RENAME TO "Order";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
@@ -0,0 +1,18 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Config" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT DEFAULT 1,
"adminCode" TEXT NOT NULL DEFAULT '1234',
"finalDepositDate" TEXT,
"cookDay" TEXT,
"payIdInfo" TEXT DEFAULT 'Your PayID Here',
"termsOfService" TEXT DEFAULT 'To secure your order, a 30% non-refundable deposit is required upfront before smoking begins. Payments can be made via PayID or Cash. The remaining balance is payable upon pickup.',
"orderingPolicy" TEXT DEFAULT 'Deposits cover material costs and are final. PayID details provided after ordering.',
"depositPercentage" INTEGER NOT NULL DEFAULT 30
);
INSERT INTO "new_Config" ("adminCode", "cookDay", "finalDepositDate", "id", "orderingPolicy", "payIdInfo", "termsOfService") SELECT "adminCode", "cookDay", "finalDepositDate", "id", "orderingPolicy", "payIdInfo", "termsOfService" FROM "Config";
DROP TABLE "Config";
ALTER TABLE "new_Config" RENAME TO "Config";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
+10
View File
@@ -43,4 +43,14 @@ model Config {
finalDepositDate String?
cookDay String?
payIdInfo String? @default("Your PayID Here")
termsOfService String? @default("To secure your order, a 30% non-refundable deposit is required upfront before smoking begins. Payments can be made via PayID or Cash. The remaining balance is payable upon pickup.")
orderingPolicy String? @default("Deposits cover material costs and are final. PayID details provided after ordering.")
depositPercentage Int @default(30)
}
model FAQ {
id Int @id @default(autoincrement())
question String
answer String
order Int @default(0)
}
+72 -5
View File
@@ -13,12 +13,13 @@ app.use(express.json());
// API Routes
// Fetch all data (Stock, Orders, Config)
// Fetch all data (Stock, Orders, Config, FAQs)
app.get('/api/data', async (req, res) => {
try {
let stock = await prisma.stockItem.findMany();
const orders = await prisma.order.findMany();
const config = await prisma.config.findFirst();
const faqs = await prisma.fAQ.findMany({ orderBy: { order: 'asc' } });
// Parse order items from string to JSON
const parsedOrders = orders.map(order => ({
@@ -64,7 +65,7 @@ app.get('/api/data', async (req, res) => {
// Security: Don't send the admin code to the client
const configResponse = config ? { ...config, adminCode: undefined } : null;
res.json({ stock: stockWithRemaining, orders: parsedOrders, config: configResponse });
res.json({ stock: stockWithRemaining, orders: parsedOrders, config: configResponse, faqs });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to fetch data' });
@@ -75,13 +76,17 @@ app.get('/api/data', async (req, res) => {
app.post('/api/login', async (req, res) => {
try {
const { code } = req.body;
console.log(`Login attempt with code: ${code}`);
const config = await prisma.config.findFirst();
if (config && config.adminCode === code) {
console.log('Login successful');
res.json({ success: true });
} else {
console.log(`Login failed. Expected: ${config?.adminCode}, Got: ${code}`);
res.status(401).json({ error: 'Invalid admin code' });
}
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Login failed' });
}
});
@@ -236,12 +241,15 @@ app.delete('/api/stock/:id', async (req, res) => {
// Update Config
app.post('/api/config', async (req, res) => {
try {
const { adminCode, finalDepositDate, cookDay, payIdInfo } = req.body;
const { adminCode, finalDepositDate, cookDay, payIdInfo, termsOfService, orderingPolicy, depositPercentage } = req.body;
const data: any = {};
if (adminCode !== undefined) data.adminCode = adminCode;
if (finalDepositDate !== undefined) data.finalDepositDate = finalDepositDate;
if (cookDay !== undefined) data.cookDay = cookDay;
if (payIdInfo !== undefined) data.payIdInfo = payIdInfo;
if (termsOfService !== undefined) data.termsOfService = termsOfService;
if (orderingPolicy !== undefined) data.orderingPolicy = orderingPolicy;
if (depositPercentage !== undefined) data.depositPercentage = parseInt(depositPercentage);
// Use upsert to ensure we always use ID 1
await prisma.config.upsert({
@@ -257,19 +265,58 @@ app.post('/api/config', async (req, res) => {
}
});
// Update or Create FAQ Item
app.post('/api/faqs', async (req, res) => {
try {
const item = req.body;
if (item.id) {
const updated = await prisma.fAQ.update({
where: { id: item.id },
data: {
...item,
id: undefined // Don't update ID
}
});
res.json(updated);
} else {
const created = await prisma.fAQ.create({ data: item });
res.json(created);
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to update FAQ' });
}
});
// Delete FAQ Item
app.delete('/api/faqs/:id', async (req, res) => {
try {
const { id } = req.params;
await prisma.fAQ.delete({ where: { id: parseInt(id) } });
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to delete FAQ item' });
}
});
// Reset Database to defaults
app.post('/api/reset', async (req, res) => {
try {
await prisma.order.deleteMany();
await prisma.stockItem.deleteMany();
await prisma.config.deleteMany();
await prisma.fAQ.deleteMany();
// Seed default admin code and limits with ID 1
await prisma.config.create({
data: {
id: 1,
adminCode: '1234',
payIdInfo: 'Your PayID Here'
payIdInfo: 'Your PayID Here',
termsOfService: 'To secure your order, a 30% non-refundable deposit is required upfront before smoking begins. Payments can be made via PayID or Cash. The remaining balance is payable upon pickup.',
orderingPolicy: 'Deposits cover material costs and are final. PayID details provided after ordering.',
depositPercentage: 30
}
});
@@ -280,6 +327,14 @@ app.post('/api/reset', async (req, res) => {
{ name: 'Pork Shoulder', category: 'Pork', price: 35, unit: 'kg', description: 'Pulled pork perfection', available: true }
]
});
// Seed some default FAQs
await prisma.fAQ.createMany({
data: [
{ question: 'When is pickup?', answer: 'Pickups are usually scheduled for the afternoon of the Cook Day. We will contact you to confirm the exact time.', order: 1 },
{ question: 'How do I pay the deposit?', answer: 'Once you submit your order, you can pay the 30% deposit via PayID or Cash.', order: 2 }
]
});
res.sendStatus(200);
} catch (error) {
@@ -299,6 +354,18 @@ app.get(/^\/(?!api).*/, (req, res) => {
res.sendFile(path.join(frontendPath, 'index.html'));
});
app.listen(PORT, () => {
app.listen(PORT, async () => {
// Ensure a default config exists
const config = await prisma.config.findFirst();
if (!config) {
await prisma.config.create({
data: {
id: 1,
adminCode: '1234',
payIdInfo: 'Your PayID Here'
}
});
console.log('Default config seeded');
}
console.log(`Server is running on http://localhost:${PORT}`);
});