diff --git a/client/components/GamifiedBanner.tsx b/client/components/GamifiedBanner.tsx index 4684f3e0..45abcf24 100644 --- a/client/components/GamifiedBanner.tsx +++ b/client/components/GamifiedBanner.tsx @@ -8,12 +8,15 @@ type Props = { style?: string | null; }; -const ACCENTS: Record = { +const ACCENTS: Record< + string, + { + grad: string; + glowRing: string; + pill: string; + icon: any; + } +> = { quest: { grad: "from-emerald-500/20 via-aethex-500/15 to-neon-blue/20", glowRing: @@ -58,7 +61,9 @@ export function GamifiedBanner({ text, enabled, style }: Props) { const parts = useMemo(() => { // If user prefixed an emoji, use it - const m = /^([\p{Emoji}\p{Extended_Pictographic}]+)\s*(.*)$/u.exec(text || ""); + const m = /^([\p{Emoji}\p{Extended_Pictographic}]+)\s*(.*)$/u.exec( + text || "", + ); return { emoji: m?.[1] || "🎮", body: (m?.[2] || text || "").trim(), diff --git a/client/components/SEO.tsx b/client/components/SEO.tsx index 404e8e85..a3cecbee 100644 --- a/client/components/SEO.tsx +++ b/client/components/SEO.tsx @@ -11,40 +11,83 @@ export type SEOProps = { function upsertMeta(selector: string, attrs: Record) { let el = document.querySelector(selector) as HTMLElement | null; if (!el) { - const tag = selector.startsWith('meta[') ? 'meta' : selector.startsWith('link[') ? 'link' : 'meta'; + const tag = selector.startsWith("meta[") + ? "meta" + : selector.startsWith("link[") + ? "link" + : "meta"; el = document.createElement(tag); document.head.appendChild(el); } Object.entries(attrs).forEach(([k, v]) => (el as any).setAttribute(k, v)); } -export default function SEO({ pageTitle, description, image, canonical, noIndex }: SEOProps) { +export default function SEO({ + pageTitle, + description, + image, + canonical, + noIndex, +}: SEOProps) { useEffect(() => { const title = `AeThex | ${pageTitle}`; document.title = title; if (canonical) { - upsertMeta('link[rel="canonical"]', { rel: 'canonical', href: canonical }); - upsertMeta('meta[property="og:url"]', { property: 'og:url', content: canonical }); + upsertMeta('link[rel="canonical"]', { + rel: "canonical", + href: canonical, + }); + upsertMeta('meta[property="og:url"]', { + property: "og:url", + content: canonical, + }); } if (description) { - upsertMeta('meta[name="description"]', { name: 'description', content: description }); - upsertMeta('meta[property="og:description"]', { property: 'og:description', content: description }); - upsertMeta('meta[name="twitter:description"]', { name: 'twitter:description', content: description }); + upsertMeta('meta[name="description"]', { + name: "description", + content: description, + }); + upsertMeta('meta[property="og:description"]', { + property: "og:description", + content: description, + }); + upsertMeta('meta[name="twitter:description"]', { + name: "twitter:description", + content: description, + }); } - upsertMeta('meta[property="og:title"]', { property: 'og:title', content: title }); - upsertMeta('meta[name="twitter:title"]', { name: 'twitter:title', content: title }); + upsertMeta('meta[property="og:title"]', { + property: "og:title", + content: title, + }); + upsertMeta('meta[name="twitter:title"]', { + name: "twitter:title", + content: title, + }); if (image) { - upsertMeta('meta[property="og:image"]', { property: 'og:image', content: image }); - upsertMeta('meta[name="twitter:image"]', { name: 'twitter:image', content: image }); + upsertMeta('meta[property="og:image"]', { + property: "og:image", + content: image, + }); + upsertMeta('meta[name="twitter:image"]', { + name: "twitter:image", + content: image, + }); } if (noIndex) { - upsertMeta('meta[name="robots"]', { name: 'robots', content: 'noindex, nofollow' }); - upsertMeta('meta[name="googlebot"]', { name: 'googlebot', content: 'noindex, nofollow' }); + upsertMeta('meta[name="robots"]', { + name: "robots", + content: "noindex, nofollow", + }); + upsertMeta('meta[name="googlebot"]', { + name: "googlebot", + content: "noindex, nofollow", + }); } }, [pageTitle, description, image, canonical, noIndex]); diff --git a/client/components/admin/BannerSettings.tsx b/client/components/admin/BannerSettings.tsx index 64df8352..424a6c33 100644 --- a/client/components/admin/BannerSettings.tsx +++ b/client/components/admin/BannerSettings.tsx @@ -27,7 +27,10 @@ export default function BannerSettings() { const resp = await fetch("/api/site-settings", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ key: "home_banner", value: { text, enabled, style } }), + body: JSON.stringify({ + key: "home_banner", + value: { text, enabled, style }, + }), }); if (!resp.ok) throw new Error("Save failed"); } finally { @@ -38,13 +41,17 @@ export default function BannerSettings() { return (
- +
- +
- +
{ + const next = blogPosts.slice(); + next[i] = { ...next[i], title: e.target.value }; + setBlogPosts(next); + }} + /> + { + const next = blogPosts.slice(); + next[i] = { ...next[i], slug: e.target.value }; + setBlogPosts(next); + }} + /> +
+
+ { + const n = blogPosts.slice(); + n[i] = { ...n[i], author: e.target.value }; + setBlogPosts(n); + }} + /> + { + const n = blogPosts.slice(); + n[i] = { ...n[i], date: e.target.value }; + setBlogPosts(n); + }} + /> +
+
+ { + const n = blogPosts.slice(); + n[i] = { ...n[i], read_time: e.target.value }; + setBlogPosts(n); + }} + /> + { + const n = blogPosts.slice(); + n[i] = { ...n[i], category: e.target.value }; + setBlogPosts(n); + }} + /> + { + const n = blogPosts.slice(); + n[i] = { ...n[i], image: e.target.value }; + setBlogPosts(n); + }} + /> +
+