@deneb-ui/create-template 2.0.70 → 2.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +191 -7
- package/package.json +1 -1
- package/template/fivora-template.json +13 -24
- package/template/package.json +2 -2
- package/template/scripts/merge-platform-contract.js +82 -5
- package/template/src/app/about_us/page.tsx +105 -19
- package/template/src/app/contact/page.tsx +124 -39
- package/template/src/app/globals.css +13 -83
- package/template/src/app/layout.tsx +7 -1
- package/template/src/app/page.tsx +118 -33
- package/template/src/app/products/detail/page.tsx +13 -1
- package/template/src/app/services/page.tsx +67 -9
- package/template/src/components/SiteChrome.tsx +4 -0
- package/template/src/components/ThemeToggle.tsx +89 -0
- package/template/src/data/site-data.json +99 -74
- package/template/src/lib/siteDataContext.tsx +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, type FormEvent } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { EditableText, EditableBox } from '@deneb-ui/ui';
|
|
5
5
|
import { contentObject, contentText, useSiteData } from '@/lib/siteDataContext';
|
|
6
6
|
|
|
7
7
|
export default function ContactPage() {
|
|
@@ -15,6 +15,10 @@ export default function ContactPage() {
|
|
|
15
15
|
contentText(contact.mapUrl) ||
|
|
16
16
|
contentText(contact.addressUrl);
|
|
17
17
|
const address = contentText(contact.address);
|
|
18
|
+
const phone = contentText(contact.phone);
|
|
19
|
+
const email = contentText(contact.email);
|
|
20
|
+
const whatsapp = contentText(contact.whatsapp);
|
|
21
|
+
const directMessageText = contentText(contact.directMessageText) || 'Chat on WhatsApp';
|
|
18
22
|
|
|
19
23
|
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
20
24
|
event.preventDefault();
|
|
@@ -49,47 +53,128 @@ export default function ContactPage() {
|
|
|
49
53
|
return (
|
|
50
54
|
<div data-preview-page-key="contact">
|
|
51
55
|
<section className="page-section page-heading" data-design-section="contact-heading">
|
|
52
|
-
<
|
|
53
|
-
|
|
56
|
+
<EditableText
|
|
57
|
+
variant="h1"
|
|
58
|
+
size="4xl"
|
|
59
|
+
weight="bold"
|
|
60
|
+
color="heading"
|
|
61
|
+
data-preview-field-path="contact.heading"
|
|
62
|
+
defaultValue={contentText(contact.heading)}
|
|
63
|
+
/>
|
|
64
|
+
<EditableText
|
|
65
|
+
variant="lead"
|
|
66
|
+
color="muted"
|
|
67
|
+
data-preview-field-path="contact.intro"
|
|
68
|
+
defaultValue={contentText(contact.intro)}
|
|
69
|
+
/>
|
|
54
70
|
</section>
|
|
71
|
+
|
|
55
72
|
<section className="page-section contact-grid" data-design-section="contact-details">
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
labelFieldPath="contact.directionsLabel"
|
|
65
|
-
urlFieldPath="contact.directionsUrl"
|
|
66
|
-
variant="outline"
|
|
67
|
-
size="md"
|
|
73
|
+
<EditableBox className="card contact-details" balance radius="xl">
|
|
74
|
+
<EditableText
|
|
75
|
+
variant="h3"
|
|
76
|
+
size="xl"
|
|
77
|
+
weight="bold"
|
|
78
|
+
color="heading"
|
|
79
|
+
data-preview-static="contact-heading"
|
|
80
|
+
defaultValue="Direct Information"
|
|
68
81
|
/>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginTop: '0.5rem' }}>
|
|
83
|
+
<a href={phone ? `tel:${phone}` : ''} data-preview-field-path="contact.phone">
|
|
84
|
+
{phone}
|
|
85
|
+
</a>
|
|
86
|
+
<a href={email ? `mailto:${email}` : ''} data-preview-field-path="contact.email">
|
|
87
|
+
{email}
|
|
88
|
+
</a>
|
|
89
|
+
<EditableText
|
|
90
|
+
variant="p"
|
|
91
|
+
color="muted"
|
|
92
|
+
data-preview-field-path="contact.address"
|
|
93
|
+
defaultValue={address}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
<div style={{ marginTop: '1.5rem', display: 'flex', flexWrap: 'wrap', gap: '0.75rem' }}>
|
|
97
|
+
<a
|
|
98
|
+
href={directionsUrl || '#'}
|
|
99
|
+
data-preview-field-path="contact.directionsUrl"
|
|
100
|
+
target="_blank"
|
|
101
|
+
rel="noopener noreferrer"
|
|
102
|
+
className="deneb-map-link"
|
|
103
|
+
style={{
|
|
104
|
+
display: 'inline-flex',
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
justifyContent: 'center',
|
|
107
|
+
gap: '0.5rem',
|
|
108
|
+
fontWeight: 500,
|
|
109
|
+
textDecoration: 'none',
|
|
110
|
+
transition: 'all 0.2s ease',
|
|
111
|
+
cursor: 'pointer',
|
|
112
|
+
borderRadius: '0.5rem',
|
|
113
|
+
fontSize: '0.95rem',
|
|
114
|
+
padding: '0.5rem 1rem',
|
|
115
|
+
backgroundColor: 'transparent',
|
|
116
|
+
border: '1px solid var(--color-border, #e2e8f0)',
|
|
117
|
+
color: 'var(--color-text, #0f172a)',
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
<svg data-preview-static="map-link-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
121
|
+
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
|
|
122
|
+
<circle cx="12" cy="10" r="3" />
|
|
123
|
+
</svg>
|
|
124
|
+
<span data-preview-field-path="contact.directionsLabel">{directionsLabel}</span>
|
|
125
|
+
</a>
|
|
126
|
+
<a
|
|
127
|
+
href={whatsapp ? `https://wa.me/${whatsapp.replace(/[^0-9]/g, '')}` : '#'}
|
|
128
|
+
target="_blank"
|
|
129
|
+
rel="noopener noreferrer"
|
|
130
|
+
className="button-secondary"
|
|
131
|
+
data-preview-field-path="contact.whatsapp"
|
|
132
|
+
>
|
|
133
|
+
<span data-preview-field-path="contact.directMessageText">
|
|
134
|
+
{directMessageText}
|
|
135
|
+
</span>
|
|
136
|
+
</a>
|
|
137
|
+
</div>
|
|
138
|
+
</EditableBox>
|
|
139
|
+
|
|
140
|
+
<EditableBox className="card" balance radius="xl">
|
|
141
|
+
<form
|
|
142
|
+
className="contact-form"
|
|
143
|
+
onSubmit={handleSubmit}
|
|
144
|
+
data-fivora-contact-disabled
|
|
145
|
+
>
|
|
146
|
+
<EditableText
|
|
147
|
+
variant="h2"
|
|
148
|
+
size="2xl"
|
|
149
|
+
weight="bold"
|
|
150
|
+
color="heading"
|
|
151
|
+
data-preview-field-path="contact.formTitle"
|
|
152
|
+
defaultValue={contentText(contact.formTitle)}
|
|
153
|
+
/>
|
|
154
|
+
<label data-preview-static>
|
|
155
|
+
Name
|
|
156
|
+
<input type="text" name="name" autoComplete="name" required />
|
|
157
|
+
</label>
|
|
158
|
+
<label data-preview-static>
|
|
159
|
+
Email
|
|
160
|
+
<input type="email" name="email" autoComplete="email" required />
|
|
161
|
+
</label>
|
|
162
|
+
<label data-preview-static>
|
|
163
|
+
Message
|
|
164
|
+
<textarea name="message" rows={4} required />
|
|
165
|
+
</label>
|
|
166
|
+
<button type="submit" className="button-primary" style={{ marginTop: '0.5rem' }}>
|
|
167
|
+
<span data-preview-field-path="contact.submitLabel">
|
|
168
|
+
{contentText(contact.submitLabel)}
|
|
169
|
+
</span>
|
|
170
|
+
</button>
|
|
171
|
+
{status && (
|
|
172
|
+
<p role="status" aria-live="polite" style={{ marginTop: '0.75rem', fontSize: '0.9rem' }}>
|
|
173
|
+
{status}
|
|
174
|
+
</p>
|
|
175
|
+
)}
|
|
176
|
+
</form>
|
|
177
|
+
</EditableBox>
|
|
93
178
|
</section>
|
|
94
179
|
</div>
|
|
95
180
|
);
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
color-scheme: light;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
html.dark,
|
|
10
|
+
.dark {
|
|
11
|
+
color-scheme: dark;
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
* {
|
|
10
15
|
box-sizing: border-box;
|
|
11
16
|
}
|
|
@@ -18,6 +23,7 @@ body {
|
|
|
18
23
|
margin: 0;
|
|
19
24
|
background: var(--page-background, #ffffff);
|
|
20
25
|
color: var(--page-text, #0f172a);
|
|
26
|
+
transition: background-color 0.25s ease, color 0.25s ease;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
button,
|
|
@@ -252,10 +258,11 @@ h3 {
|
|
|
252
258
|
|
|
253
259
|
.card {
|
|
254
260
|
padding: 1.4rem;
|
|
255
|
-
border: 1px solid color-mix(in srgb, var(--page-text) 12%, transparent);
|
|
261
|
+
border: 1px solid var(--card-border, color-mix(in srgb, var(--page-text) 12%, transparent));
|
|
256
262
|
border-radius: 1.35rem;
|
|
257
|
-
background: var(--page-background);
|
|
263
|
+
background: var(--card-bg, var(--page-background));
|
|
258
264
|
box-shadow: 0 1rem 2.5rem color-mix(in srgb, var(--brand-secondary) 8%, transparent);
|
|
265
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.25s ease, border-color 0.25s ease;
|
|
259
266
|
}
|
|
260
267
|
|
|
261
268
|
.card h3 {
|
|
@@ -347,10 +354,11 @@ h3 {
|
|
|
347
354
|
.contact-form textarea {
|
|
348
355
|
width: 100%;
|
|
349
356
|
padding: 0.85rem 1rem;
|
|
350
|
-
border: 1px solid color-mix(in srgb, var(--page-text) 20%, transparent);
|
|
357
|
+
border: 1px solid var(--input-border, color-mix(in srgb, var(--page-text) 20%, transparent));
|
|
351
358
|
border-radius: 0.8rem;
|
|
352
|
-
background: var(--page-background);
|
|
353
|
-
color: var(--page-text);
|
|
359
|
+
background: var(--input-bg, var(--page-background));
|
|
360
|
+
color: var(--input-color, var(--page-text));
|
|
361
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
354
362
|
}
|
|
355
363
|
|
|
356
364
|
.site-footer {
|
|
@@ -442,81 +450,3 @@ h3 {
|
|
|
442
450
|
transform: scale(1.06) translateY(-4px);
|
|
443
451
|
}
|
|
444
452
|
|
|
445
|
-
/* Developer Quickstart Guide Section */
|
|
446
|
-
.dev-guide-section {
|
|
447
|
-
border-top: 1px solid color-mix(in srgb, var(--page-text) 10%, transparent);
|
|
448
|
-
border-bottom: 1px solid color-mix(in srgb, var(--page-text) 10%, transparent);
|
|
449
|
-
background: color-mix(in srgb, var(--brand-color) 4%, var(--page-background));
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
.dev-guide-header {
|
|
453
|
-
text-align: center;
|
|
454
|
-
max-width: 44rem;
|
|
455
|
-
margin: 0 auto 2.5rem;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
.dev-badge {
|
|
459
|
-
display: inline-block;
|
|
460
|
-
padding: 0.35rem 0.9rem;
|
|
461
|
-
border-radius: 999px;
|
|
462
|
-
font-size: 0.8rem;
|
|
463
|
-
font-weight: 700;
|
|
464
|
-
letter-spacing: 0.05em;
|
|
465
|
-
background: color-mix(in srgb, var(--brand-color) 15%, transparent);
|
|
466
|
-
color: var(--brand-color);
|
|
467
|
-
margin-bottom: 0.85rem;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
.dev-command-grid {
|
|
471
|
-
display: grid;
|
|
472
|
-
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
473
|
-
gap: 1.5rem;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
.dev-cmd-card {
|
|
477
|
-
padding: 1.5rem;
|
|
478
|
-
border-radius: 1rem;
|
|
479
|
-
background: var(--page-background);
|
|
480
|
-
border: 1px solid color-mix(in srgb, var(--page-text) 12%, transparent);
|
|
481
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
|
482
|
-
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
.dev-cmd-card:hover {
|
|
486
|
-
transform: translateY(-3px);
|
|
487
|
-
border-color: var(--brand-accent);
|
|
488
|
-
box-shadow: 0 8px 24px color-mix(in srgb, var(--brand-color) 15%, transparent);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
.dev-cmd-card .cmd-header {
|
|
492
|
-
display: flex;
|
|
493
|
-
align-items: center;
|
|
494
|
-
justify-content: space-between;
|
|
495
|
-
margin-bottom: 0.75rem;
|
|
496
|
-
gap: 0.5rem;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
.dev-cmd-card .cmd-tag {
|
|
500
|
-
font-size: 0.75rem;
|
|
501
|
-
font-weight: 700;
|
|
502
|
-
text-transform: uppercase;
|
|
503
|
-
letter-spacing: 0.08em;
|
|
504
|
-
color: var(--brand-color);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
.dev-cmd-card code {
|
|
508
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
509
|
-
font-size: 0.85rem;
|
|
510
|
-
font-weight: 700;
|
|
511
|
-
padding: 0.2rem 0.5rem;
|
|
512
|
-
border-radius: 0.35rem;
|
|
513
|
-
background: color-mix(in srgb, var(--page-text) 8%, transparent);
|
|
514
|
-
color: var(--page-text);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
.dev-cmd-card p {
|
|
518
|
-
margin: 0;
|
|
519
|
-
font-size: 0.9rem;
|
|
520
|
-
line-height: 1.5;
|
|
521
|
-
color: color-mix(in srgb, var(--page-text) 70%, transparent);
|
|
522
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Metadata } from 'next';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import siteData from '@/data/site-data.json';
|
|
4
|
-
import { SiteDataProvider } from '@/lib/siteDataContext';
|
|
4
|
+
import { SiteDataProvider, ThemeStyles } from '@/lib/siteDataContext';
|
|
5
5
|
import SiteChrome from '@/components/SiteChrome';
|
|
6
6
|
import './globals.css';
|
|
7
7
|
import '../fonts/deneb-fonts.css';
|
|
@@ -15,8 +15,14 @@ export const metadata: Metadata = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
18
|
+
const initialTheme =
|
|
19
|
+
(siteData as any)?.template?.structure?.theme || (siteData as any)?.theme;
|
|
20
|
+
|
|
18
21
|
return (
|
|
19
22
|
<html lang="en" suppressHydrationWarning>
|
|
23
|
+
<head>
|
|
24
|
+
<ThemeStyles theme={initialTheme} enableDualMode />
|
|
25
|
+
</head>
|
|
20
26
|
<body suppressHydrationWarning>
|
|
21
27
|
<SiteDataProvider initialSiteData={siteData}>
|
|
22
28
|
<SiteChrome>{children}</SiteChrome>
|
|
@@ -13,8 +13,7 @@ import {
|
|
|
13
13
|
EditableText,
|
|
14
14
|
EditableList,
|
|
15
15
|
EditableCard,
|
|
16
|
-
|
|
17
|
-
ListActionCta,
|
|
16
|
+
EditableImage,
|
|
18
17
|
} from '@deneb-ui/ui';
|
|
19
18
|
|
|
20
19
|
|
|
@@ -140,14 +139,28 @@ export default function HomePage() {
|
|
|
140
139
|
data-preview-field-path="home.introBody"
|
|
141
140
|
defaultValue={contentText(home.introBody)}
|
|
142
141
|
/>
|
|
143
|
-
<div
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
<div
|
|
143
|
+
style={{ display: 'flex', justifyContent: 'center', gap: '0.75rem', marginTop: '1.5rem', flexWrap: 'wrap' }}
|
|
144
|
+
data-preview-list-path="home.demoPreOrderCta"
|
|
145
|
+
>
|
|
146
|
+
{contentList(home.demoPreOrderCta).map((rawCta, index) => {
|
|
147
|
+
const cta = contentObject(rawCta);
|
|
148
|
+
const label = contentText(cta.buttonLabel) || 'Book a Consultation';
|
|
149
|
+
const url = contentText(cta.buttonUrl) || '#';
|
|
150
|
+
return (
|
|
151
|
+
<a
|
|
152
|
+
key={index}
|
|
153
|
+
href={url}
|
|
154
|
+
className="button-primary"
|
|
155
|
+
data-preview-item-path={`home.demoPreOrderCta[${index}]`}
|
|
156
|
+
data-preview-field-path={`home.demoPreOrderCta[${index}].buttonUrl`}
|
|
157
|
+
>
|
|
158
|
+
<span data-preview-field-path={`home.demoPreOrderCta[${index}].buttonLabel`}>
|
|
159
|
+
{label}
|
|
160
|
+
</span>
|
|
161
|
+
</a>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
151
164
|
</div>
|
|
152
165
|
</div>
|
|
153
166
|
</section>
|
|
@@ -229,38 +242,110 @@ export default function HomePage() {
|
|
|
229
242
|
/>
|
|
230
243
|
</div>
|
|
231
244
|
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
items={products}
|
|
235
|
-
className="card-grid"
|
|
236
|
-
itemAs={false}
|
|
237
|
-
>
|
|
238
|
-
{(rawProduct, index, itemPath) => {
|
|
245
|
+
<div className="card-grid" data-preview-list-path="products">
|
|
246
|
+
{products.map((rawProduct, index) => {
|
|
239
247
|
const product = contentObject(rawProduct);
|
|
240
248
|
const productId =
|
|
241
249
|
typeof product.id === 'string' || typeof product.id === 'number'
|
|
242
250
|
? product.id
|
|
243
251
|
: null;
|
|
252
|
+
const price = typeof product.price === 'number' ? product.price : parseFloat(String(product.price || 0));
|
|
253
|
+
const compareAt = typeof product.compareAtPrice === 'number' ? product.compareAtPrice : parseFloat(String(product.compareAtPrice || 0));
|
|
254
|
+
const currency = contentText(product.currency) || 'LKR';
|
|
255
|
+
const badge = contentText(product.badge);
|
|
256
|
+
const category = contentText(product.category);
|
|
244
257
|
|
|
245
258
|
return (
|
|
246
|
-
<
|
|
259
|
+
<EditableCard
|
|
247
260
|
key={productId ?? index}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
className="card product-card"
|
|
262
|
+
item={product as any}
|
|
263
|
+
itemPath={`products[${index}]`}
|
|
264
|
+
data-preview-item-path={`products[${index}]`}
|
|
265
|
+
balance
|
|
266
|
+
radius="xl"
|
|
267
|
+
style={{ display: 'flex', flexDirection: 'column' }}
|
|
268
|
+
>
|
|
269
|
+
<div style={{ position: 'relative', overflow: 'hidden', borderRadius: '12px', marginBottom: '0.75rem' }}>
|
|
270
|
+
<EditableImage
|
|
271
|
+
src={contentText(product.imageUrl) || '/placeholder.svg'}
|
|
272
|
+
alt={contentText(product.name)}
|
|
273
|
+
data-preview-field-path={`products[${index}].imageUrl`}
|
|
274
|
+
aspectRatio="4/3"
|
|
275
|
+
fit="cover"
|
|
276
|
+
radius="lg"
|
|
277
|
+
/>
|
|
278
|
+
{badge && (
|
|
279
|
+
<span
|
|
280
|
+
className="badge-primary"
|
|
281
|
+
data-preview-field-path={`products[${index}].badge`}
|
|
282
|
+
style={{ position: 'absolute', top: '10px', left: '10px', zIndex: 10 }}
|
|
283
|
+
>
|
|
284
|
+
{badge}
|
|
285
|
+
</span>
|
|
286
|
+
)}
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, gap: '0.5rem' }}>
|
|
290
|
+
{category && (
|
|
291
|
+
<span
|
|
292
|
+
className="tag"
|
|
293
|
+
data-preview-field-path={`products[${index}].category`}
|
|
294
|
+
style={{ fontSize: '0.75rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em' }}
|
|
295
|
+
>
|
|
296
|
+
{category}
|
|
297
|
+
</span>
|
|
298
|
+
)}
|
|
299
|
+
<EditableText
|
|
300
|
+
variant="h3"
|
|
301
|
+
size="lg"
|
|
302
|
+
weight="bold"
|
|
303
|
+
color="heading"
|
|
304
|
+
data-preview-field-path={`products[${index}].name`}
|
|
305
|
+
defaultValue={contentText(product.name)}
|
|
306
|
+
/>
|
|
307
|
+
<EditableText
|
|
308
|
+
variant="p"
|
|
309
|
+
color="muted"
|
|
310
|
+
data-preview-field-path={`products[${index}].description`}
|
|
311
|
+
defaultValue={contentText(product.description)}
|
|
312
|
+
/>
|
|
313
|
+
|
|
314
|
+
<div style={{ display: 'flex', alignItems: 'baseline', gap: '0.5rem', marginTop: 'auto', paddingTop: '0.75rem' }}>
|
|
315
|
+
<EditableText
|
|
316
|
+
as="span"
|
|
317
|
+
weight="bold"
|
|
318
|
+
size="xl"
|
|
319
|
+
color="primary"
|
|
320
|
+
data-preview-field-path={`products[${index}].price`}
|
|
321
|
+
defaultValue={`${currency} ${price}`}
|
|
322
|
+
/>
|
|
323
|
+
{compareAt > 0 && (
|
|
324
|
+
<EditableText
|
|
325
|
+
as="span"
|
|
326
|
+
color="muted"
|
|
327
|
+
style={{ textDecoration: 'line-through', fontSize: '0.875rem' }}
|
|
328
|
+
data-preview-field-path={`products[${index}].compareAtPrice`}
|
|
329
|
+
defaultValue={`${currency} ${compareAt}`}
|
|
330
|
+
/>
|
|
331
|
+
)}
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<div style={{ marginTop: '0.75rem' }}>
|
|
335
|
+
<a
|
|
336
|
+
href={platformProductDetailHref(productId)}
|
|
337
|
+
className="button-secondary"
|
|
338
|
+
style={{ width: '100%', textAlign: 'center' }}
|
|
339
|
+
data-preview-static="Open the stable live product detail page"
|
|
340
|
+
>
|
|
341
|
+
View details
|
|
342
|
+
</a>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</EditableCard>
|
|
261
346
|
);
|
|
262
|
-
}}
|
|
263
|
-
</
|
|
347
|
+
})}
|
|
348
|
+
</div>
|
|
264
349
|
</section>
|
|
265
350
|
</div>
|
|
266
351
|
);
|
|
@@ -3,5 +3,17 @@
|
|
|
3
3
|
import { PlatformProductDetail } from '@deneb-ui/ui';
|
|
4
4
|
|
|
5
5
|
export default function ProductDetailPage() {
|
|
6
|
-
return
|
|
6
|
+
return (
|
|
7
|
+
<PlatformProductDetail
|
|
8
|
+
backHref="/"
|
|
9
|
+
loadingFallback={
|
|
10
|
+
<div
|
|
11
|
+
role="status"
|
|
12
|
+
className="mx-auto flex min-h-[40vh] max-w-7xl items-center justify-center px-4 py-16 text-sm text-slate-400"
|
|
13
|
+
>
|
|
14
|
+
<span data-preview-static="product-detail-loading">Loading product…</span>
|
|
15
|
+
</div>
|
|
16
|
+
}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
7
19
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { contentObject, contentText, useServices, useSiteData } from '@/lib/siteDataContext';
|
|
4
|
-
import {
|
|
3
|
+
import { contentList, contentObject, contentText, useServices, useSiteData } from '@/lib/siteDataContext';
|
|
4
|
+
import { EditableCard, EditableImage, EditableText } from '@deneb-ui/ui';
|
|
5
5
|
|
|
6
6
|
export default function ServicesPage() {
|
|
7
7
|
const siteData = useSiteData();
|
|
@@ -31,13 +31,71 @@ export default function ServicesPage() {
|
|
|
31
31
|
</section>
|
|
32
32
|
<section className="page-section alt" data-design-section="services-list">
|
|
33
33
|
<div className="card-grid" data-preview-list-path="services">
|
|
34
|
-
{services.map((
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
{services.map((rawService, index) => {
|
|
35
|
+
const service = contentObject(rawService);
|
|
36
|
+
const features = contentList(service.features);
|
|
37
|
+
const imageUrl = contentText(service.imageUrl);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<EditableCard
|
|
41
|
+
key={contentText(service.id) || index}
|
|
42
|
+
className="card service-card"
|
|
43
|
+
item={service as any}
|
|
44
|
+
itemPath={`services[${index}]`}
|
|
45
|
+
data-preview-item-path={`services[${index}]`}
|
|
46
|
+
balance
|
|
47
|
+
radius="xl"
|
|
48
|
+
style={{ display: 'flex', flexDirection: 'column' }}
|
|
49
|
+
>
|
|
50
|
+
{imageUrl && (
|
|
51
|
+
<div style={{ position: 'relative', overflow: 'hidden', borderRadius: '12px', marginBottom: '0.75rem' }}>
|
|
52
|
+
<EditableImage
|
|
53
|
+
src={imageUrl}
|
|
54
|
+
alt={contentText(service.name)}
|
|
55
|
+
data-preview-field-path={`services[${index}].imageUrl`}
|
|
56
|
+
aspectRatio="16/9"
|
|
57
|
+
fit="cover"
|
|
58
|
+
radius="lg"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
<EditableText
|
|
63
|
+
variant="h3"
|
|
64
|
+
size="xl"
|
|
65
|
+
weight="bold"
|
|
66
|
+
color="heading"
|
|
67
|
+
data-preview-field-path={`services[${index}].name`}
|
|
68
|
+
defaultValue={contentText(service.name)}
|
|
69
|
+
/>
|
|
70
|
+
<EditableText
|
|
71
|
+
variant="p"
|
|
72
|
+
color="muted"
|
|
73
|
+
data-preview-field-path={`services[${index}].description`}
|
|
74
|
+
defaultValue={contentText(service.description)}
|
|
75
|
+
style={{ marginTop: '0.5rem' }}
|
|
76
|
+
/>
|
|
77
|
+
{features.length > 0 && (
|
|
78
|
+
<ul
|
|
79
|
+
className="service-features-list"
|
|
80
|
+
data-preview-list-path={`services[${index}].features`}
|
|
81
|
+
style={{ marginTop: '1rem', display: 'flex', flexDirection: 'column', gap: '0.375rem', paddingLeft: '1.25rem' }}
|
|
82
|
+
>
|
|
83
|
+
{features.map((feature, fIndex) => (
|
|
84
|
+
<li
|
|
85
|
+
key={fIndex}
|
|
86
|
+
data-preview-item-path={`services[${index}].features[${fIndex}]`}
|
|
87
|
+
style={{ fontSize: '0.875rem' }}
|
|
88
|
+
>
|
|
89
|
+
<span data-preview-field-path={`services[${index}].features[${fIndex}]`}>
|
|
90
|
+
{contentText(feature)}
|
|
91
|
+
</span>
|
|
92
|
+
</li>
|
|
93
|
+
))}
|
|
94
|
+
</ul>
|
|
95
|
+
)}
|
|
96
|
+
</EditableCard>
|
|
97
|
+
);
|
|
98
|
+
})}
|
|
41
99
|
</div>
|
|
42
100
|
</section>
|
|
43
101
|
</div>
|
|
@@ -4,6 +4,7 @@ import type { ReactNode } from 'react';
|
|
|
4
4
|
import { usePathname } from 'next/navigation';
|
|
5
5
|
import { useSiteData } from '@/lib/siteDataContext';
|
|
6
6
|
import { Navbar, Footer, AnnouncementBar, FloatingContactWidget, getThemeCssProperties } from '@deneb-ui/ui';
|
|
7
|
+
import { ThemeToggle } from './ThemeToggle';
|
|
7
8
|
|
|
8
9
|
export default function SiteChrome({ children }: { children: ReactNode }) {
|
|
9
10
|
const siteData = useSiteData();
|
|
@@ -19,6 +20,9 @@ export default function SiteChrome({ children }: { children: ReactNode }) {
|
|
|
19
20
|
<main>{children}</main>
|
|
20
21
|
<Footer />
|
|
21
22
|
<FloatingContactWidget />
|
|
23
|
+
<div className="fixed bottom-6 left-6 z-40">
|
|
24
|
+
<ThemeToggle showLabel className="shadow-lg backdrop-blur-md" />
|
|
25
|
+
</div>
|
|
22
26
|
</div>
|
|
23
27
|
);
|
|
24
28
|
}
|