Skip to content

Commit 6d23fca

Browse files
committed
new admin tiles create for customise home pages
1 parent 233b7c3 commit 6d23fca

File tree

15 files changed

+903
-60
lines changed

15 files changed

+903
-60
lines changed

backend/src/database/init.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,19 @@ export const initializeDatabase = (): Promise<void> => {
360360
)
361361
`);
362362

363+
// Create HOME_SECTIONS table for dynamic Home page content
364+
db.run(`
365+
CREATE TABLE IF NOT EXISTS home_sections (
366+
id INTEGER PRIMARY KEY AUTOINCREMENT,
367+
sectionKey TEXT NOT NULL,
368+
title TEXT,
369+
body TEXT,
370+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
371+
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
372+
UNIQUE(sectionKey)
373+
)
374+
`);
375+
363376
// Ensure startHour/endHour columns exist (safe migration for existing DBs)
364377

365378
db.all(`PRAGMA table_info('settings')`, (err, rows: any[]) => {

backend/src/database/queries.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,4 +669,48 @@ export class DatabaseQueries {
669669
});
670670
}
671671

672+
// HOME SECTIONS
673+
static getHomeSections(): Promise<any[]> {
674+
return new Promise((resolve, reject) => {
675+
db.all(`SELECT * FROM home_sections ORDER BY sectionKey`, (err, rows) => {
676+
if (err) reject(err);
677+
else resolve(rows as any[]);
678+
});
679+
});
680+
}
681+
682+
static getHomeSection(sectionKey: string): Promise<any | null> {
683+
return new Promise((resolve, reject) => {
684+
db.get(`SELECT * FROM home_sections WHERE sectionKey = ?`, [sectionKey], (err, row) => {
685+
if (err) reject(err);
686+
else resolve(row || null);
687+
});
688+
});
689+
}
690+
691+
static upsertHomeSection(section: { sectionKey: string; title: string; body: string }): Promise<number> {
692+
return new Promise((resolve, reject) => {
693+
db.run(`
694+
INSERT INTO home_sections (sectionKey, title, body)
695+
VALUES (?, ?, ?)
696+
ON CONFLICT(sectionKey) DO UPDATE SET
697+
title = excluded.title,
698+
body = excluded.body,
699+
updatedAt = CURRENT_TIMESTAMP
700+
`, [section.sectionKey, section.title, section.body], function(err) {
701+
if (err) reject(err);
702+
else resolve(this.lastID || this.changes);
703+
});
704+
});
705+
}
706+
707+
static deleteHomeSection(id: number): Promise<void> {
708+
return new Promise((resolve, reject) => {
709+
db.run(`DELETE FROM home_sections WHERE id = ?`, [id], (err) => {
710+
if (err) reject(err);
711+
else resolve();
712+
});
713+
});
714+
}
715+
672716
}

backend/src/routes/homecontent.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import express from 'express';
2+
import { DatabaseQueries } from '../database';
3+
import { ApiResponse } from '../types';
4+
import { authenticateAdmin } from '../middleware/auth';
5+
6+
const router = express.Router();
7+
8+
// GET /api/homecontent - Get all home sections - PUBLIC
9+
router.get('/', async (req, res) => {
10+
try {
11+
const sections = await DatabaseQueries.getHomeSections();
12+
13+
// Transform array into object keyed by sectionKey for easier frontend consumption
14+
const sectionsMap: Record<string, { title: string; body: string }> = {};
15+
sections.forEach((section: any) => {
16+
sectionsMap[section.sectionKey] = {
17+
title: section.title,
18+
body: section.body
19+
};
20+
});
21+
22+
const response: ApiResponse = {
23+
success: true,
24+
data: sectionsMap,
25+
message: `Found ${sections.length} home sections`
26+
};
27+
28+
res.json(response);
29+
} catch (error) {
30+
console.error('Error fetching home sections:', error);
31+
res.status(500).json({
32+
success: false,
33+
error: 'Failed to fetch home sections',
34+
message: error instanceof Error ? error.message : 'Unknown error'
35+
});
36+
}
37+
});
38+
39+
// PUT /api/homecontent - Create or update home section - PROTECTED
40+
router.put('/', authenticateAdmin, async (req, res) => {
41+
try {
42+
const { sectionKey, title, body } = req.body;
43+
44+
if (!sectionKey) {
45+
return res.status(400).json({
46+
success: false,
47+
error: 'sectionKey is required'
48+
});
49+
}
50+
51+
const validSections = ['hero', 'servicesHeading', 'serviceIndividual', 'serviceCouples', 'serviceGroup', 'whyChooseUs'];
52+
if (!validSections.includes(sectionKey)) {
53+
return res.status(400).json({
54+
success: false,
55+
error: `Invalid sectionKey. Supported: ${validSections.join(', ')}`
56+
});
57+
}
58+
59+
await DatabaseQueries.upsertHomeSection({
60+
sectionKey,
61+
title: title || '',
62+
body: body || ''
63+
});
64+
65+
res.json({
66+
success: true,
67+
message: `Home section '${sectionKey}' saved successfully`
68+
});
69+
} catch (error) {
70+
console.error('Error saving home section:', error);
71+
res.status(500).json({
72+
success: false,
73+
error: 'Failed to save home section',
74+
message: error instanceof Error ? error.message : 'Unknown error'
75+
});
76+
}
77+
});
78+
79+
// PUT /api/homecontent/bulk - Bulk update multiple sections - PROTECTED
80+
router.put('/bulk', authenticateAdmin, async (req, res) => {
81+
try {
82+
const { sections } = req.body;
83+
84+
if (!Array.isArray(sections) || sections.length === 0) {
85+
return res.status(400).json({
86+
success: false,
87+
error: 'sections array is required'
88+
});
89+
}
90+
91+
const validSections = ['hero', 'servicesHeading', 'serviceIndividual', 'serviceCouples', 'serviceGroup', 'whyChooseUs'];
92+
93+
for (const section of sections) {
94+
if (!section.sectionKey) {
95+
return res.status(400).json({
96+
success: false,
97+
error: 'Each section must have sectionKey'
98+
});
99+
}
100+
101+
if (!validSections.includes(section.sectionKey)) {
102+
return res.status(400).json({
103+
success: false,
104+
error: `Invalid sectionKey '${section.sectionKey}'. Supported: ${validSections.join(', ')}`
105+
});
106+
}
107+
}
108+
109+
// Save all sections
110+
for (const section of sections) {
111+
await DatabaseQueries.upsertHomeSection({
112+
sectionKey: section.sectionKey,
113+
title: section.title || '',
114+
body: section.body || ''
115+
});
116+
}
117+
118+
res.json({
119+
success: true,
120+
message: `Successfully saved ${sections.length} home sections`
121+
});
122+
} catch (error) {
123+
console.error('Error bulk saving home sections:', error);
124+
res.status(500).json({
125+
success: false,
126+
error: 'Failed to save home sections',
127+
message: error instanceof Error ? error.message : 'Unknown error'
128+
});
129+
}
130+
});
131+
132+
// DELETE /api/homecontent/:id - Delete a home section - PROTECTED
133+
router.delete('/:id', authenticateAdmin, async (req, res) => {
134+
try {
135+
const id = parseInt(req.params.id, 10);
136+
137+
if (isNaN(id)) {
138+
return res.status(400).json({
139+
success: false,
140+
error: 'Invalid section ID'
141+
});
142+
}
143+
144+
await DatabaseQueries.deleteHomeSection(id);
145+
146+
res.json({
147+
success: true,
148+
message: 'Home section deleted successfully'
149+
});
150+
} catch (error) {
151+
console.error('Error deleting home section:', error);
152+
res.status(500).json({
153+
success: false,
154+
error: 'Failed to delete home section',
155+
message: error instanceof Error ? error.message : 'Unknown error'
156+
});
157+
}
158+
});
159+
160+
export default router;

backend/src/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import gumroadRoutes from './routes/gumroad';
1818
import newslettersRoutes from './routes/newsletters';
1919
import userDataRoutes from './routes/userData';
2020
import aboutRoutes from './routes/about';
21+
import homeContentRoutes from './routes/homecontent';
2122

2223
// Load environment variables from root .env file (if exists)
2324
// In Docker, env vars are passed directly; in dev, load from .env file
@@ -71,6 +72,7 @@ app.use('/api/gumroad', gumroadRoutes);
7172
app.use('/api/newsletters', newslettersRoutes);
7273
app.use('/api/user-data', userDataRoutes);
7374
app.use('/api/about', aboutRoutes);
75+
app.use('/api/homecontent', homeContentRoutes);
7476

7577
// API Status endpoint
7678
app.get('/api/status', async (req, res) => {

frontend/public/locales/en/admin.json

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,11 @@
8888
"description": "Control how many days of past and future appointments to show in admin lists"
8989
},
9090
"appointmentTypes": {
91-
"title": "Appointment Details",
91+
"title": "Appointment Page Content",
9292
"description": "Create and manage appointment types with names and prices"
9393
},
9494
"headerMessage": {
95-
"title": "Header Message",
95+
"title": "Home Page Header",
9696
"description": "Customize the calendar header message displayed to users"
9797
},
9898
"siteTheme": {
@@ -110,6 +110,10 @@
110110
"aboutDetails": {
111111
"title": "About Page Content",
112112
"description": "Customize the About page sections and content"
113+
},
114+
"homeDetails": {
115+
"title": "Home Page Content",
116+
"description": "Customize the Home page sections and content"
113117
}
114118
},
115119
"configure": "Configure",
@@ -482,13 +486,44 @@
482486
}
483487
},
484488
"appointmentTypes": {
485-
"modalTitle": "📋 Appointment Details",
489+
"modalTitle": "📋 Appointment Page Content",
486490
"infoBanner": "Define appointment types with names, tags, and prices for your booking system.",
491+
"listTitle": "Appointment Types",
487492
"currency": "Currency",
488493
"namePlaceholder": "Name (e.g., Consultation)",
489494
"tagPlaceholder": "Tag (e.g., consultation)",
490495
"pricePlaceholder": "Price",
491-
"addType": "Add Appointment Type"
496+
"addType": "+ Add Appointment Type",
497+
"newAppointment": "New Appointment Type",
498+
"editAppointment": "Edit Appointment Type",
499+
"fields": {
500+
"name": "Name",
501+
"language": "Language",
502+
"price": "Price",
503+
"minutes": "Minutes",
504+
"description": "Description",
505+
"features": "Features (one per line)"
506+
},
507+
"placeholders": {
508+
"name": "e.g., Individual Therapy Session",
509+
"description": "Brief description of this appointment type...",
510+
"features": "Personalized treatment plan\nConfidential environment\nEvidence-based techniques"
511+
},
512+
"buttons": {
513+
"save": "Save",
514+
"cancel": "Cancel",
515+
"close": "Close",
516+
"edit": "Edit",
517+
"delete": "Delete"
518+
},
519+
"noPriceSet": "No price set",
520+
"loading": "Loading...",
521+
"errors": {
522+
"nameRequired": "Name is required",
523+
"saveFailed": "Failed to save appointment type",
524+
"deleteFailed": "Failed to delete appointment type"
525+
},
526+
"confirmDelete": "Are you sure you want to delete this appointment type?"
492527
},
493528
"emailSettings": {
494529
"modalTitle": "📧 Email Configuration",
@@ -569,6 +604,7 @@
569604
},
570605
"newsletterModal": {
571606
"modalTitle": "📰 Create Newsletter",
607+
"infoBanner": "HTML is supported. Use tags like <p>, <ul>, <li>, <strong>, <em> for formatting.",
572608
"title": "Title",
573609
"subtitle": "Subtitle",
574610
"messagePart1": "Message Part 1",
@@ -635,6 +671,35 @@
635671
},
636672
"helpText": "HTML is supported. Use tags like &lt;p&gt;, &lt;ul&gt;, &lt;li&gt;, &lt;strong&gt;, &lt;em&gt; for formatting."
637673
},
674+
"homeDetails": {
675+
"modalTitle": "🏠 Home Page Content",
676+
"sections": {
677+
"hero": "Hero Section",
678+
"servicesHeading": "Services Heading",
679+
"serviceIndividual": "Tile 1",
680+
"serviceCouples": "Tile 2",
681+
"serviceGroup": "Tile 3",
682+
"whyChooseUs": "Why Choose Us"
683+
},
684+
"fields": {
685+
"title": "Title",
686+
"body": "Content (HTML allowed)"
687+
},
688+
"placeholders": {
689+
"title": "Enter section title...",
690+
"body": "Enter content (HTML tags like <p>, <ul>, <li> are supported)..."
691+
},
692+
"saveButton": "Save All Sections",
693+
"saving": "Saving...",
694+
"toast": {
695+
"saved": "Home page content saved successfully!",
696+
"failed": "Failed to save home page content",
697+
"error": "Error saving home page content",
698+
"loaded": "Home page content loaded",
699+
"loadFailed": "Failed to load home page content"
700+
},
701+
"helpText": "HTML is supported. Use tags like &lt;p&gt;, &lt;ul&gt;, &lt;li&gt;, &lt;strong&gt;, &lt;em&gt; for formatting."
702+
},
638703
"licenseInfo": {
639704
"modalTitle": "🔓 Premium License Details",
640705
"loading": "Loading license information...",

0 commit comments

Comments
 (0)