// DEPLOY_CONFIG: {"triggers": [{"name": "notify_owner_on_new_signup", "on": "collection.add", "collection": "subscribers", "actions": [{"type": "email", "to": "{{site_owner_email}}", "subject": "New subscriber signup: {{name || email}}", "body": "A new subscriber has signed up.\n\nName: {{name}}\nEmail: {{email}}\nCreated At: {{created_at}}\n\nFull record:\n{{json}}"}]}], "secrets": [{"name": "site_owner_email", "description": "Email address of the site owner or team inbox that receives new subscriber notifications."}, {"name": "email_delivery_provider", "description": "Configured email delivery mechanism/credential (e.g. SMTP/API credentials) used to send notification emails."}]}
import { useState, useEffect, useMemo } from 'react';
import { useCollection, renderChart } from '@deplixo/sdk';
import { EditorPanel } from './components/EditorPanel.jsx';
import { PreviewPanel } from './components/PreviewPanel.jsx';
// PROGRESS:sc_001:complete:Setting up landing page builder structure
const THEME_COLLECTION = 'landing-page-theme';
const DEFAULT_THEME = {
primary: '#BF360C',
secondary: '#E64A19',
accent: '#FF8F00',
light: '#FFB74D',
bg: '#FFF3E0',
white: '#FFFFFF',
text: '#3E2723',
textLight: '#6D4C41',
textMuted: '#8D6E63',
border: '#FFCCBC',
borderLight: '#FFE0B2',
cardBg: '#FFFFFF',
shadowColor: '191, 54, 12'
};
const PRESET_THEMES = [
{
id: 'warm',
name: 'Warm Ember',
preview: ['#BF360C', '#E64A19', '#FF8F00'],
values: {
primary: '#BF360C',
secondary: '#E64A19',
accent: '#FF8F00',
light: '#FFB74D',
bg: '#FFF3E0',
white: '#FFFFFF',
text: '#3E2723',
textLight: '#6D4C41',
textMuted: '#8D6E63',
border: '#FFCCBC',
borderLight: '#FFE0B2',
cardBg: '#FFFFFF',
shadowColor: '191, 54, 12'
}
},
{
id: 'forest',
name: 'Forest',
preview: ['#1B5E20', '#388E3C', '#81C784'],
values: {
primary: '#1B5E20',
secondary: '#388E3C',
accent: '#66BB6A',
light: '#A5D6A7',
bg: '#F1F8E9',
white: '#FFFFFF',
text: '#1B2E1F',
textLight: '#4E6B57',
textMuted: '#6C8A74',
border: '#C8E6C9',
borderLight: '#E8F5E9',
cardBg: '#FFFFFF',
shadowColor: '27, 94, 32'
}
},
{
id: 'ocean',
name: 'Ocean',
preview: ['#01579B', '#0288D1', '#4FC3F7'],
values: {
primary: '#01579B',
secondary: '#0288D1',
accent: '#4FC3F7',
light: '#81D4FA',
bg: '#E1F5FE',
white: '#FFFFFF',
text: '#102A43',
textLight: '#486581',
textMuted: '#627D98',
border: '#B3E5FC',
borderLight: '#E0F7FA',
cardBg: '#FFFFFF',
shadowColor: '1, 87, 155'
}
},
{
id: 'violet',
name: 'Violet',
preview: ['#4A148C', '#7B1FA2', '#BA68C8'],
values: {
primary: '#4A148C',
secondary: '#7B1FA2',
accent: '#BA68C8',
light: '#CE93D8',
bg: '#F3E5F5',
white: '#FFFFFF',
text: '#2E1A47',
textLight: '#5E4B7C',
textMuted: '#7B6D9A',
border: '#E1BEE7',
borderLight: '#F3E5F5',
cardBg: '#FFFFFF',
shadowColor: '74, 20, 140'
}
}
];
function buildThemeVars(theme) {
return {
'--primary': theme.primary,
'--secondary': theme.secondary,
'--accent': theme.accent,
'--light': theme.light,
'--bg': theme.bg,
'--white': theme.white,
'--text': theme.text,
'--text-light': theme.textLight,
'--text-muted': theme.textMuted,
'--border': theme.border,
'--border-light': theme.borderLight,
'--card-bg': theme.cardBg,
'--shadow-sm': `0 1px 3px rgba(${theme.shadowColor}, 0.06)`,
'--shadow-md': `0 4px 12px rgba(${theme.shadowColor}, 0.08)`,
'--shadow-lg': `0 8px 30px rgba(${theme.shadowColor}, 0.12)`
};
}
function applyThemeToDocument(theme) {
if (typeof document === 'undefined') return;
const root = document.documentElement;
const vars = buildThemeVars(theme);
Object.entries(vars).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
}
function App() {
const [activeTab, setActiveTab] = useState('editor');
const { items, loading, add, update } = useCollection('landing-page', { personal: true });
const { items: signupItems, loading: signupLoading } = useCollection('email-signups', { personal: true });
const { items: themeItems, loading: themeLoading, add: addTheme, update: updateTheme } = useCollection(THEME_COLLECTION, { personal: true });
const pageData = items.length > 0 ? items[0].value : null;
const pageId = items.length > 0 ? items[0].id : null;
const defaultData = {
headline: '',
subheadline: '',
description: '',
features: [],
ctaText: 'Get Started',
ctaUrl: '#',
heroImageUrl: ''
};
const defaultThemeRecord = {
name: 'Current Theme',
themeId: 'warm',
values: PRESET_THEMES[0].values,
custom: false,
updatedAt: new Date().toISOString()
};
useEffect(() => {
if (!loading && items.length === 0) {
add(defaultData);
}
}, [loading, items.length]);
useEffect(() => {
if (!themeLoading && themeItems.length === 0) {
addTheme(defaultThemeRecord);
}
}, [themeLoading, themeItems.length]);
const themeRecord = themeItems.length > 0 ? themeItems[0].value : defaultThemeRecord;
const selectedThemeId = themeRecord?.themeId || 'warm';
const selectedThemeValues = themeRecord?.values || PRESET_THEMES[0].values;
const selectedTheme = useMemo(() => ({ ...DEFAULT_THEME, ...selectedThemeValues }), [selectedThemeValues]);
useEffect(() => {
applyThemeToDocument(selectedTheme);
}, [selectedTheme]);
const handleUpdate = (newData) => {
if (pageId) {
update(pageId, { ...pageData, ...newData });
}
};
const handleThemeChange = (themeId) => {
const preset = PRESET_THEMES.find((theme) => theme.id === themeId);
if (!preset) return;
const nextTheme = {
...defaultThemeRecord,
themeId: preset.id,
name: preset.name,
values: preset.values,
custom: false,
updatedAt: new Date().toISOString()
};
if (themeItems.length > 0) {
updateTheme(themeItems[0].id, nextTheme);
} else {
addTheme(nextTheme);
}
};
const handleCustomThemeChange = (key, value) => {
const nextValues = { ...selectedThemeValues, [key]: value };
const nextTheme = {
...themeRecord,
themeId: 'custom',
name: 'Custom Theme',
values: nextValues,
custom: true,
updatedAt: new Date().toISOString()
};
if (themeItems.length > 0) {
updateTheme(themeItems[0].id, nextTheme);
} else {
addTheme(nextTheme);
}
};
const signupChartData = useMemo(() => {
const countsByDay = signupItems.reduce((acc, item) => {
const value = item?.value || {};
const rawDate = value.createdAt || item.createdAt || item.updatedAt || item.id;
const date = rawDate ? new Date(rawDate) : null;
const key = date && !Number.isNaN(date.getTime()) ? date.toISOString().slice(0, 10) : 'Unknown';
acc[key] = (acc[key] || 0) + 1;
return acc;
}, {});
return Object.entries(countsByDay)
.filter(([label]) => label !== 'Unknown')
.sort(([a], [b]) => a.localeCompare(b))
.map(([label, value]) => ({ label, value }));
}, [signupItems]);
if (loading || !pageData || themeLoading) {
return (
);
}
return (
Pick a preset or fine-tune your own colors. Changes apply instantly across the editor and preview.
{[
['primary', 'Primary'],
['secondary', 'Secondary'],
['accent', 'Accent'],
['bg', 'Background'],
['text', 'Text'],
['border', 'Border']
].map(([key, label]) => (
))}
{activeTab === 'editor' && }
{activeTab === 'preview' && }
{activeTab === 'admin' && (
Signup activity over time
{signupLoading
? 'Loading signup data...'
: signupChartData.length > 0
? `${signupChartData.reduce((sum, point) => sum + point.value, 0)} total signups across ${signupChartData.length} day${signupChartData.length === 1 ? '' : 's'}.`
: 'No signup data yet.'}
{signupLoading ? (
Preparing chart...
) : signupChartData.length > 0 ? (
) : (
📈
No signups to chart yet
When email signups are stored in your collection, they will appear here over time.
)}
)}
);
}
function SignupChart({ data }) {
const canvasRef = useState(null)[0];
const chartRef = useState(null)[0];
useEffect(() => {
if (canvasRef && data.length > 0) {
renderChart(canvasRef, {
type: 'bar',
data: {
labels: data.map((d) => d.label),
datasets: [
{
label: 'Signups',
data: data.map((d) => d.value)
}
]
}
});
}
}, [data, canvasRef, chartRef]);
return