EventBuzz_Console/src/app/components/microsite-v2/UploadMicrositeV2TemplatePa...

176 lines
6.1 KiB
TypeScript

import React, { useState } from 'react';
import { Save } from 'lucide-react';
import { PanelShell, Label, Field, HelperText } from '../panels/PanelShell';
import { staticMicrositeApi, type MicrositeV2Template } from '../../../services/api';
const inputStyle: React.CSSProperties = {
width: '100%',
padding: '8px 10px',
borderRadius: 'var(--radius-button)',
border: '1px solid var(--border)',
background: 'var(--input-background)',
color: 'var(--foreground)',
fontSize: 'var(--text-sm)',
boxSizing: 'border-box',
};
export function UploadMicrositeV2TemplatePanel({
template,
onClose,
onSaved,
}: {
template: MicrositeV2Template | null;
onClose: () => void;
onSaved: () => void;
}) {
const isEdit = Boolean(template);
const [slug, setSlug] = useState(template?.slug || '');
const [name, setName] = useState(template?.name || '');
const [description, setDescription] = useState(template?.description || '');
const [templateHtml, setTemplateHtml] = useState<File | null>(null);
const [templateJson, setTemplateJson] = useState<File | null>(null);
const [thumbnail, setThumbnail] = useState<File | null>(null);
const [assetsZip, setAssetsZip] = useState<File | null>(null);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const handleSave = async () => {
setSaving(true);
setError('');
try {
let templateId = template?._id;
if (!isEdit) {
if (!slug.trim() || !name.trim()) {
throw new Error('Slug and name are required');
}
const createRes = await staticMicrositeApi.createTemplate({
slug: slug.trim(),
name: name.trim(),
description: description.trim() || undefined,
});
if (createRes.error || !createRes.data) throw new Error(createRes.error || 'Create failed');
templateId = createRes.data._id;
} else if (name !== template?.name || description !== template?.description) {
const updateRes = await staticMicrositeApi.updateTemplate(template!._id, {
name: name.trim(),
description: description.trim(),
});
if (updateRes.error) throw new Error(updateRes.error);
}
if (!templateId) throw new Error('Missing template id');
const hasFiles = templateHtml || templateJson || thumbnail || assetsZip;
if (!isEdit && (!templateHtml || !templateJson)) {
throw new Error('template.html and template.json are required for new templates');
}
if (hasFiles) {
const uploadRes = await staticMicrositeApi.uploadTemplateFiles(templateId, {
templateHtml: templateHtml || undefined,
templateJson: templateJson || undefined,
thumbnail: thumbnail || undefined,
assetsZip: assetsZip || undefined,
});
if (uploadRes.error) throw new Error(uploadRes.error);
}
onSaved();
} catch (e) {
setError(e instanceof Error ? e.message : 'Save failed');
} finally {
setSaving(false);
}
};
return (
<PanelShell
title={isEdit ? 'Edit template' : 'New microsite template'}
description="Upload template.html, template.json, thumbnail, and optional assets zip."
onClose={onClose}
panelWidth="min(520px, 100vw)"
ctaLabel={saving ? 'Saving…' : 'Save'}
ctaDisabled={saving}
onCtaClick={() => void handleSave()}
>
{error ? <p style={{ color: 'var(--destructive)', fontSize: 'var(--text-sm)', marginBottom: '12px' }}>{error}</p> : null}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div>
<Label text="Slug" />
<Field>
<input
type="text"
value={slug}
disabled={isEdit}
onChange={(e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ''))}
placeholder="event-up"
style={inputStyle}
/>
</Field>
<HelperText text="Unique id used in S3 paths. Cannot change after create." />
</div>
<div>
<Label text="Name" />
<Field>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} style={inputStyle} />
</Field>
</div>
<div>
<Label text="Description" />
<Field>
<textarea value={description} onChange={(e) => setDescription(e.target.value)} rows={2} style={{ ...inputStyle, resize: 'vertical' }} />
</Field>
</div>
<div>
<Label text={`template.html ${isEdit ? '(optional)' : '(required)'}`} />
<input type="file" accept=".html,text/html" onChange={(e) => setTemplateHtml(e.target.files?.[0] || null)} />
</div>
<div>
<Label text={`template.json ${isEdit ? '(optional)' : '(required)'}`} />
<input type="file" accept=".json,application/json" onChange={(e) => setTemplateJson(e.target.files?.[0] || null)} />
</div>
<div>
<Label text="Thumbnail (optional)" />
<input type="file" accept="image/*" onChange={(e) => setThumbnail(e.target.files?.[0] || null)} />
</div>
<div>
<Label text="Assets zip (optional)" />
<input type="file" accept=".zip,application/zip" onChange={(e) => setAssetsZip(e.target.files?.[0] || null)} />
<HelperText text="Zip contents are extracted to templates/{slug}/assets/" />
</div>
<button
type="button"
disabled={saving}
onClick={() => void handleSave()}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '6px',
padding: '8px 14px',
borderRadius: 'var(--radius-button)',
border: 'none',
background: 'var(--primary)',
color: 'var(--primary-foreground)',
cursor: saving ? 'wait' : 'pointer',
fontSize: 'var(--text-sm)',
alignSelf: 'flex-start',
}}
>
<Save size={14} />
{saving ? 'Saving…' : 'Save template'}
</button>
</div>
</PanelShell>
);
}