add SEO enhancements: OG images, meta robots, FAQ block, image sitemap
All checks were successful
deploy / deploy (push) Successful in 4m59s

- Per-page SEO controls: meta robots directives, focus keyword, OG image
- Site-wide default OG image in admin settings
- FAQ block type with FAQPage JSON-LD schema
- Enhanced Organization JSON-LD with business info, contact, address
- Image sitemap with product images
- SEO preview panel with Google/social card mockups
- SEO checklist with real-time scoring
- Business info section in site editor
- GSC integration scaffolding (OAuth, client, cache)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-04-17 16:47:43 +01:00
parent 9facfd926e
commit 4aa7dece0c
42 changed files with 3881 additions and 41 deletions

View File

@@ -64,6 +64,7 @@ defmodule BerrypodWeb.ShopComponents.SiteEditor do
|> assign(:header_nav, state.header_nav)
|> assign(:footer_nav, state.footer_nav)
|> assign(:social_links, state.social_links)
|> assign(:business_info, state.business_info)
~H"""
<div class="editor-site-content">
@@ -109,6 +110,10 @@ defmodule BerrypodWeb.ShopComponents.SiteEditor do
<.site_section title="Social links" icon="hero-link">
<.social_links_editor links={@social_links} event_prefix={@event_prefix} />
</.site_section>
<.site_section title="Business info" icon="hero-building-storefront">
<.business_info_editor info={@business_info} event_prefix={@event_prefix} />
</.site_section>
</div>
"""
end
@@ -856,6 +861,144 @@ defmodule BerrypodWeb.ShopComponents.SiteEditor do
"""
end
# ── Business Info Editor ────────────────────────────────────────────
attr :info, :map, required: true
attr :event_prefix, :string, default: "site_"
defp business_info_editor(assigns) do
~H"""
<form class="site-editor-form" phx-change={@event_prefix <> "update_business_info"}>
<p class="admin-text-secondary admin-text-sm" style="margin-bottom: 1rem;">
Used for rich snippets in search results and business schema data.
</p>
<div class="theme-section">
<label class="theme-section-label">Business type</label>
<div class="site-editor-radio-group">
<label class="admin-radio-label">
<input
type="radio"
name="business_info[business_type]"
value="Organization"
checked={@info["business_type"] != "LocalBusiness"}
/>
<span>Organisation</span>
</label>
<label class="admin-radio-label">
<input
type="radio"
name="business_info[business_type]"
value="LocalBusiness"
checked={@info["business_type"] == "LocalBusiness"}
/>
<span>Local business</span>
</label>
</div>
</div>
<div class="theme-section">
<label class="theme-section-label" for="business-phone">Phone</label>
<input
type="tel"
id="business-phone"
name="business_info[business_phone]"
value={@info["business_phone"]}
class="admin-input"
placeholder="+44 7123 456789"
phx-debounce="500"
/>
</div>
<div class="theme-section">
<label class="theme-section-label" for="business-email">Email</label>
<input
type="email"
id="business-email"
name="business_info[business_email]"
value={@info["business_email"]}
class="admin-input"
placeholder="hello@example.com"
phx-debounce="500"
/>
</div>
<%!-- Address fields shown for LocalBusiness --%>
<div
class="business-address-fields"
style={if @info["business_type"] != "LocalBusiness", do: "display: none;"}
>
<div class="theme-section">
<label class="theme-section-label" for="business-street">Street address</label>
<input
type="text"
id="business-street"
name="business_info[address_street]"
value={@info["address_street"]}
class="admin-input"
placeholder="123 High Street"
phx-debounce="500"
/>
</div>
<div class="theme-section">
<label class="theme-section-label" for="business-city">City</label>
<input
type="text"
id="business-city"
name="business_info[address_city]"
value={@info["address_city"]}
class="admin-input"
placeholder="London"
phx-debounce="500"
/>
</div>
<div class="admin-row admin-row-sm">
<div class="theme-section admin-fill">
<label class="theme-section-label" for="business-region">County / region</label>
<input
type="text"
id="business-region"
name="business_info[address_region]"
value={@info["address_region"]}
class="admin-input"
placeholder="Greater London"
phx-debounce="500"
/>
</div>
<div class="theme-section" style="flex: 0 0 8rem;">
<label class="theme-section-label" for="business-postcode">Postcode</label>
<input
type="text"
id="business-postcode"
name="business_info[address_postal_code]"
value={@info["address_postal_code"]}
class="admin-input"
placeholder="SW1A 1AA"
phx-debounce="500"
/>
</div>
</div>
<div class="theme-section">
<label class="theme-section-label" for="business-country">Country</label>
<input
type="text"
id="business-country"
name="business_info[address_country]"
value={@info["address_country"]}
class="admin-input"
placeholder="United Kingdom"
phx-debounce="500"
/>
</div>
</div>
</form>
"""
end
# ── Helpers ─────────────────────────────────────────────────────────
# Social