First cut
This commit is contained in:
134
src/pages/editor.astro
Normal file
134
src/pages/editor.astro
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { loadSiteData } from '../lib/site-data.ts';
|
||||
|
||||
const { siteContext } = loadSiteData();
|
||||
|
||||
const secret = import.meta.env.EDITOR_SESSION_SECRET || process.env.EDITOR_SESSION_SECRET || 'dev-secret';
|
||||
const sessionCookie = Astro.cookies.get('editor_session')?.value;
|
||||
let isAuthed = sessionCookie === secret;
|
||||
|
||||
if (!isAuthed && Astro.request.method === 'POST') {
|
||||
const formData = await Astro.request.formData();
|
||||
const password = formData.get('password') as string;
|
||||
const editSecret = import.meta.env.API_EDIT_SECRET || process.env.API_EDIT_SECRET || '';
|
||||
|
||||
if (password === editSecret) {
|
||||
Astro.cookies.set('editor_session', secret, {
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
maxAge: 8 * 60 * 60,
|
||||
path: '/',
|
||||
});
|
||||
isAuthed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const orchestratorUrl = import.meta.env.PUBLIC_ORCHESTRATOR_URL || process.env.PUBLIC_ORCHESTRATOR_URL || 'http://localhost:3001';
|
||||
const apiSecret = import.meta.env.API_EDIT_SECRET || process.env.API_EDIT_SECRET || '';
|
||||
---
|
||||
<BaseLayout title={`Editor — ${siteContext.businessName}`} primaryColor={siteContext.primaryColor}>
|
||||
<Fragment slot="logo">{siteContext.businessName}</Fragment>
|
||||
<Fragment slot="tagline">Content Editor</Fragment>
|
||||
|
||||
{!isAuthed ? (
|
||||
<section class="login-section">
|
||||
<div class="container">
|
||||
<div class="login-box">
|
||||
<h2>Editor Login</h2>
|
||||
<p>Enter the site edit password to continue.</p>
|
||||
<form method="POST">
|
||||
<input type="password" name="password" placeholder="Password" required />
|
||||
<button type="submit">Log In</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section class="editor-section">
|
||||
<div class="container">
|
||||
<div id="editor-root"
|
||||
data-orchestrator-url={orchestratorUrl}
|
||||
data-api-secret={apiSecret}
|
||||
></div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<Fragment slot="footer">
|
||||
© {new Date().getFullYear()} {siteContext.businessName} · Editor
|
||||
</Fragment>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.login-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 50vh;
|
||||
}
|
||||
.login-box {
|
||||
max-width: 360px;
|
||||
padding: 2rem;
|
||||
background: white;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.login-box h2 {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.4rem;
|
||||
color: var(--color-primary-dark);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.login-box p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.login-box input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 1rem;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
.login-box button {
|
||||
width: 100%;
|
||||
padding: 0.65rem;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 500;
|
||||
}
|
||||
.login-box button:hover {
|
||||
background: var(--color-primary-dark);
|
||||
}
|
||||
.editor-section {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
{isAuthed && (
|
||||
<script>
|
||||
import { createElement } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { VisualEditorIsland } from '../components/editor/VisualEditorIsland';
|
||||
|
||||
const el = document.getElementById('editor-root');
|
||||
if (el) {
|
||||
const root = createRoot(el);
|
||||
root.render(createElement(VisualEditorIsland, {
|
||||
orchestratorUrl: el.dataset.orchestratorUrl || '',
|
||||
apiSecret: el.dataset.apiSecret || '',
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
)}
|
||||
43
src/pages/index.astro
Normal file
43
src/pages/index.astro
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import HeroSection from '../components/sections/HeroSection.astro';
|
||||
import AboutSection from '../components/sections/AboutSection.astro';
|
||||
import FeaturesSection from '../components/sections/FeaturesSection.astro';
|
||||
import TestimonialsSection from '../components/sections/TestimonialsSection.astro';
|
||||
import TextSection from '../components/sections/TextSection.astro';
|
||||
import EventsList from '../components/sections/EventsList.astro';
|
||||
import { loadSiteData } from '../lib/site-data.ts';
|
||||
|
||||
const { siteContext, sections, events } = loadSiteData();
|
||||
---
|
||||
<BaseLayout title={siteContext.businessName} primaryColor={siteContext.primaryColor}>
|
||||
<Fragment slot="logo">{siteContext.businessName}</Fragment>
|
||||
<Fragment slot="tagline">{siteContext.tagline}</Fragment>
|
||||
|
||||
{sections.map((section) => {
|
||||
switch (section.type) {
|
||||
case 'hero':
|
||||
return <HeroSection
|
||||
headline={section.headline}
|
||||
subheading={section.subheading}
|
||||
ctaText={section.ctaText}
|
||||
ctaLink={section.ctaLink}
|
||||
/>;
|
||||
case 'about':
|
||||
return <AboutSection title={section.title} content={section.content} />;
|
||||
case 'features':
|
||||
return <FeaturesSection title={section.title} items={section.items} />;
|
||||
case 'testimonials':
|
||||
return <TestimonialsSection title={section.title} items={section.items} />;
|
||||
case 'text':
|
||||
return <TextSection heading={section.heading} content={section.content} id={section.id} />;
|
||||
}
|
||||
})}
|
||||
|
||||
<EventsList events={events.events} />
|
||||
|
||||
<Fragment slot="footer">
|
||||
© {new Date().getFullYear()} {siteContext.businessName}
|
||||
{siteContext.contactEmail && <span> · {siteContext.contactEmail}</span>}
|
||||
</Fragment>
|
||||
</BaseLayout>
|
||||
Reference in New Issue
Block a user