add Site context with social links editor and site-wide settings
Some checks failed
deploy / deploy (push) Has been cancelled

- Add Site context for managing site-wide content (social links, nav items,
  announcement bar, footer content)
- Add SocialLink schema with URL normalization and platform auto-detection
  supporting 40+ platforms via host and 25+ via URI scheme
- Add NavItem schema for header/footer navigation (editor UI coming next)
- Add SiteEditor component with collapsible sections for each content type
- Wire social links card block and footer to use database data
- Filter empty URLs from display in shop components
- Add DetailsPreserver hook to preserve collapsible section state
- Add comprehensive tests for Site context and SocialLink functions
- Remove unused helper functions from onboarding to fix compiler warnings
- Move sync_edit_url_param helper to group handle_editor_event clauses

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-28 10:09:33 +00:00
parent 0b86cd66ce
commit 638bb4fb70
24 changed files with 3121 additions and 195 deletions

View File

@@ -66,7 +66,6 @@ defmodule BerrypodWeb.Admin.Backup do
end
end
def handle_event("validate_upload", _params, socket) do
{:noreply, socket}
end
@@ -274,11 +273,10 @@ defmodule BerrypodWeb.Admin.Backup do
<% end %>
</div>
<p class="admin-section-desc">
{Backup.format_size(@stats.total_size)} total ·
{length(@stats.tables)} tables ·
{@stats.key_counts["products"] || 0} products ·
{@stats.key_counts["orders"] || 0} orders ·
{@stats.key_counts["images"] || 0} images
{Backup.format_size(@stats.total_size)} total · {length(@stats.tables)} tables · {@stats.key_counts[
"products"
] || 0} products · {@stats.key_counts["orders"] || 0} orders · {@stats.key_counts["images"] ||
0} images
</p>
<div class="admin-section-body">
<button
@@ -459,10 +457,22 @@ defmodule BerrypodWeb.Admin.Backup do
<div class="backup-comparison-col">
<h4 class="backup-comparison-label">Current</h4>
<dl class="backup-comparison-stats">
<div><dt>Size</dt><dd>{Backup.format_size(@stats.total_size)}</dd></div>
<div><dt>Products</dt><dd>{@stats.key_counts["products"] || 0}</dd></div>
<div><dt>Orders</dt><dd>{@stats.key_counts["orders"] || 0}</dd></div>
<div><dt>Images</dt><dd>{@stats.key_counts["images"] || 0}</dd></div>
<div>
<dt>Size</dt>
<dd>{Backup.format_size(@stats.total_size)}</dd>
</div>
<div>
<dt>Products</dt>
<dd>{@stats.key_counts["products"] || 0}</dd>
</div>
<div>
<dt>Orders</dt>
<dd>{@stats.key_counts["orders"] || 0}</dd>
</div>
<div>
<dt>Images</dt>
<dd>{@stats.key_counts["images"] || 0}</dd>
</div>
</dl>
</div>
<div class="backup-comparison-arrow">
@@ -471,10 +481,22 @@ defmodule BerrypodWeb.Admin.Backup do
<div class="backup-comparison-col">
<h4 class="backup-comparison-label">Uploaded</h4>
<dl class="backup-comparison-stats">
<div><dt>Size</dt><dd>{Backup.format_size(@uploaded_backup.stats.file_size)}</dd></div>
<div><dt>Products</dt><dd>{@uploaded_backup.stats.key_counts["products"] || 0}</dd></div>
<div><dt>Orders</dt><dd>{@uploaded_backup.stats.key_counts["orders"] || 0}</dd></div>
<div><dt>Images</dt><dd>{@uploaded_backup.stats.key_counts["images"] || 0}</dd></div>
<div>
<dt>Size</dt>
<dd>{Backup.format_size(@uploaded_backup.stats.file_size)}</dd>
</div>
<div>
<dt>Products</dt>
<dd>{@uploaded_backup.stats.key_counts["products"] || 0}</dd>
</div>
<div>
<dt>Orders</dt>
<dd>{@uploaded_backup.stats.key_counts["orders"] || 0}</dd>
</div>
<div>
<dt>Images</dt>
<dd>{@uploaded_backup.stats.key_counts["images"] || 0}</dd>
</div>
</dl>
</div>
</div>
@@ -482,7 +504,9 @@ defmodule BerrypodWeb.Admin.Backup do
<%= if @uploaded_backup.stats.latest_migration == @stats.schema_version do %>
<div class="backup-validation backup-validation-ok">
<.icon name="hero-check-circle-mini" class="size-4" />
<span>Backup validated · Schema version {@uploaded_backup.stats.latest_migration}</span>
<span>
Backup validated · Schema version {@uploaded_backup.stats.latest_migration}
</span>
</div>
<%= if @restoring do %>
@@ -496,22 +520,40 @@ defmodule BerrypodWeb.Admin.Backup do
<% else %>
<%= if @confirming_restore do %>
<div class="backup-warning">
<p>This will replace your current database. A backup will be saved automatically.</p>
<p>
This will replace your current database. A backup will be saved automatically.
</p>
<div class="backup-actions">
<button type="button" class="admin-btn admin-btn-danger admin-btn-sm" phx-click="execute_restore">
<button
type="button"
class="admin-btn admin-btn-danger admin-btn-sm"
phx-click="execute_restore"
>
Replace database
</button>
<button type="button" class="admin-btn admin-btn-outline admin-btn-sm" phx-click="cancel_restore">
<button
type="button"
class="admin-btn admin-btn-outline admin-btn-sm"
phx-click="cancel_restore"
>
Cancel
</button>
</div>
</div>
<% else %>
<div class="backup-actions">
<button type="button" class="admin-btn admin-btn-primary admin-btn-sm" phx-click="confirm_restore">
<button
type="button"
class="admin-btn admin-btn-primary admin-btn-sm"
phx-click="confirm_restore"
>
Restore this backup
</button>
<button type="button" class="admin-btn admin-btn-outline admin-btn-sm" phx-click="cancel_restore">
<button
type="button"
class="admin-btn admin-btn-outline admin-btn-sm"
phx-click="cancel_restore"
>
Cancel
</button>
</div>
@@ -526,7 +568,11 @@ defmodule BerrypodWeb.Admin.Backup do
</span>
</div>
<div class="backup-actions">
<button type="button" class="admin-btn admin-btn-outline admin-btn-sm" phx-click="cancel_restore">
<button
type="button"
class="admin-btn admin-btn-outline admin-btn-sm"
phx-click="cancel_restore"
>
Cancel
</button>
</div>

View File

@@ -851,36 +851,6 @@ defmodule BerrypodWeb.Setup.Onboarding do
# ── Helpers ──
defp account_summary(%{current_scope: %{user: user}}) when not is_nil(user) do
site_name = Settings.site_name()
if site_name != "Store Name" do
"#{site_name} · #{user.email}"
else
user.email
end
end
defp account_summary(_), do: "Account created"
defp provider_summary(%{setup: %{provider_type: type}}) when is_binary(type) do
case Provider.get(type) do
nil -> "Connected"
info -> "Connected to #{info.name}"
end
end
defp provider_summary(_), do: nil
defp stripe_summary(%{setup: %{stripe_connected: true}}) do
case Settings.secret_hint("stripe_api_key") do
nil -> "Connected"
hint -> "Connected · #{hint}"
end
end
defp stripe_summary(_), do: nil
defp provider_card_options(providers) do
Enum.map(providers, fn provider ->
option = %{