diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf72d3..544172d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Added + +- Settings → Organisation has a "Google Search Console verification" field. Paste the HTML tag from Search Console and the site outputs it on every page, so each deployment can verify its own domain without uploading a file. + ## [1.31.0] - 2026-10-22 ### Added diff --git a/backend/src/controllers/settingsController.js b/backend/src/controllers/settingsController.js index 6855a74..c8adeb1 100644 --- a/backend/src/controllers/settingsController.js +++ b/backend/src/controllers/settingsController.js @@ -21,6 +21,8 @@ const PUBLIC_KEYS = [ // the admin re-saves the Branding tab). 'primary_color', 'secondary_color', 'accent_color', 'logo_url', 'favicon_url', 'setup_complete', 'app_base_url', + // Google Search Console "HTML tag" code, output as a meta tag on every page + 'google_site_verification', // Legal pages 'legal_operator_name', 'legal_io_name', 'legal_io_email', 'legal_website_url', 'legal_effective_date', diff --git a/frontend/src/app/dashboard/admin/settings/page.tsx b/frontend/src/app/dashboard/admin/settings/page.tsx index a2369c9..b5dec2f 100644 --- a/frontend/src/app/dashboard/admin/settings/page.tsx +++ b/frontend/src/app/dashboard/admin/settings/page.tsx @@ -32,6 +32,12 @@ const TABS: { id: TabId; label: string; icon: LucideIcon }[] = [ const inputCls = "w-full border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400"; +/** The code from Search Console's verification meta tag; accepts the whole tag or just the code. */ +function googleVerificationCode(input: string): string { + const m = input.match(/content\s*=\s*["']([^"']*)["']/i); + return (m ? m[1] : input).trim(); +} + function Field({ label, hint, required, children, }: { @@ -104,6 +110,7 @@ function SiteSettingsPageInner() { const [orgPhone, setOrgPhone] = useState(""); const [orgAddress, setOrgAddress] = useState(""); const [appBaseUrl, setAppBaseUrl] = useState(""); + const [googleVerification, setGoogleVerification] = useState(""); // ── Branding ── const [primaryColor, setPrimaryColor] = useState("#4F46E5"); @@ -153,6 +160,7 @@ function SiteSettingsPageInner() { setOrgPhone(s.org_phone || ""); setOrgAddress(s.org_address || ""); setAppBaseUrl(s.app_base_url || ""); + setGoogleVerification(s.google_site_verification || ""); // primary_color falls back to the legacy accent_color value (which used // to be "the one brand color" before this 3-color system existed). // accent_color is only trusted as the NEW tertiary color once @@ -209,6 +217,7 @@ function SiteSettingsPageInner() { org_phone: orgPhone.trim(), org_address: orgAddress.trim(), app_base_url: appBaseUrl.trim(), + google_site_verification: googleVerificationCode(googleVerification), }); const uploadBrandingAsset = async (file: File, kind: "logo" | "favicon"): Promise => { @@ -432,6 +441,10 @@ function SiteSettingsPageInner() { setAppBaseUrl(e.target.value)} /> + + setGoogleVerification(e.target.value)} /> + )} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 4be538b..d9d638d 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -42,6 +42,8 @@ export async function generateMetadata(): Promise { description, ...(logoUrl ? { images: [logoUrl] } : {}), }, + // Per-deployment Search Console ownership check (Settings → Organisation). + ...(settings.google_site_verification ? { verification: { google: settings.google_site_verification } } : {}), }; } diff --git a/frontend/src/content/help/site-settings.tsx b/frontend/src/content/help/site-settings.tsx index 58d5374..a43e644 100644 --- a/frontend/src/content/help/site-settings.tsx +++ b/frontend/src/content/help/site-settings.tsx @@ -25,6 +25,9 @@ export const siteSettingsHelpContent: HelpContent = { The public URL of this site, used in email links like password resets and ticket delivery. + + Proves to Google that you own this site, so you can see search traffic and submit /sitemap.xml in Search Console. Add the site there as a URL-prefix property (e.g. https://events.yourchurch.org/), pick the HTML tag method, paste the tag here and save, then click Verify. Each site has its own code. Google finds your events without this; it only unlocks the Search Console reports. + ), diff --git a/frontend/src/contexts/SiteSettingsContext.tsx b/frontend/src/contexts/SiteSettingsContext.tsx index f6922b2..ff677ee 100644 --- a/frontend/src/contexts/SiteSettingsContext.tsx +++ b/frontend/src/contexts/SiteSettingsContext.tsx @@ -18,6 +18,7 @@ export type SiteSettings = { logo_url?: string; favicon_url?: string; setup_complete?: string; + google_site_verification?: string; // Legal pages legal_operator_name?: string; legal_io_name?: string;