diff --git a/backend/src/handlers/config.rs b/backend/src/handlers/config.rs index 379e47a..e737131 100644 --- a/backend/src/handlers/config.rs +++ b/backend/src/handlers/config.rs @@ -40,6 +40,8 @@ pub async fn update_config( if let Some(v) = patch.favicon { config.favicon = v; } if let Some(v) = patch.theme { config.theme = v; } if let Some(v) = patch.custom_css { config.custom_css = v; } + if let Some(v) = patch.contact_intro { config.contact_intro = v; } + if let Some(v) = patch.contact_links { config.contact_links = v; } let config_str = serde_json::to_string_pretty(&config).map_err(|e| { error!("Serialization error: {}", e); diff --git a/backend/src/models.rs b/backend/src/models.rs index 5dc2a51..be7de4e 100644 --- a/backend/src/models.rs +++ b/backend/src/models.rs @@ -1,6 +1,13 @@ use chrono::NaiveDate; use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, Clone)] +pub struct ContactLink { + pub kind: String, + pub label: String, + pub value: String, +} + #[derive(Serialize, Deserialize, Clone)] pub struct SiteConfig { pub title: String, @@ -11,6 +18,10 @@ pub struct SiteConfig { pub favicon: String, pub theme: String, pub custom_css: String, + #[serde(default)] + pub contact_intro: String, + #[serde(default)] + pub contact_links: Vec, } impl Default for SiteConfig { @@ -26,6 +37,8 @@ impl Default for SiteConfig { favicon: "/favicon.svg".to_string(), theme: "salon".to_string(), custom_css: "".to_string(), + contact_intro: "".to_string(), + contact_links: Vec::new(), } } } @@ -48,6 +61,10 @@ pub struct SiteConfigPatch { pub theme: Option, #[serde(default)] pub custom_css: Option, + #[serde(default)] + pub contact_intro: Option, + #[serde(default)] + pub contact_links: Option>, } #[derive(Serialize, Deserialize, Clone, Default)] diff --git a/frontend/src/components/react/admin/Settings.tsx b/frontend/src/components/react/admin/Settings.tsx index da81355..728a0ff 100644 --- a/frontend/src/components/react/admin/Settings.tsx +++ b/frontend/src/components/react/admin/Settings.tsx @@ -1,6 +1,15 @@ import { useState, useEffect } from 'react'; import { getConfig, updateConfig, ApiError } from '../../../lib/api'; -import type { SiteConfig } from '../../../lib/types'; +import type { SiteConfig, ContactLink } from '../../../lib/types'; + +const CONTACT_KINDS: { value: string; label: string; placeholder: string }[] = [ + { value: 'email', label: 'Email', placeholder: 'you@example.com' }, + { value: 'mastodon', label: 'Mastodon', placeholder: 'https://mastodon.social/@you' }, + { value: 'bluesky', label: 'Bluesky', placeholder: 'https://bsky.app/profile/you.bsky.social' }, + { value: 'github', label: 'GitHub', placeholder: 'https://github.com/you' }, + { value: 'instagram', label: 'Instagram', placeholder: 'https://instagram.com/you' }, + { value: 'url', label: 'Other link', placeholder: 'https://…' }, +]; export default function Settings() { const [config, setConfig] = useState>({}); @@ -17,10 +26,30 @@ export default function Settings() { setTimeout(() => setAlert(null), 5000); } - function update(key: keyof SiteConfig, value: string) { + function update(key: K, value: SiteConfig[K]) { setConfig(prev => ({ ...prev, [key]: value })); } + const contactLinks: ContactLink[] = config.contact_links ?? []; + + function updateContactLink(index: number, patch: Partial) { + const next = contactLinks.map((row, i) => (i === index ? { ...row, ...patch } : row)); + update('contact_links', next); + } + function addContactLink() { + update('contact_links', [...contactLinks, { kind: 'email', label: 'Email', value: '' }]); + } + function removeContactLink(index: number) { + update('contact_links', contactLinks.filter((_, i) => i !== index)); + } + function moveContactLink(index: number, dir: -1 | 1) { + const target = index + dir; + if (target < 0 || target >= contactLinks.length) return; + const next = [...contactLinks]; + [next[index], next[target]] = [next[target], next[index]]; + update('contact_links', next); + } + async function handleSubmit(e: React.SyntheticEvent) { e.preventDefault(); try { @@ -85,6 +114,101 @@ export default function Settings() { +
+

Contact

+
+ +