feat: Add configurable deposit percentage, FAQ management, and fix PayID copy button
This commit is contained in:
1 parent
d34950a91b
commit
27f3d73d01
12 files changed
+1111
-334
No files matched your search
Generated
-1
@@ -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;
|
||||
@@ -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
@@ -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}`);
|
||||
});
|
||||
Reference in new issue
Block a user