extract site_name and site_description from theme settings into standalone settings
site_name and site_description are shop identity, not theme concerns. They now live in the Settings table as first-class settings with their own assigns (@site_name, @site_description) piped through hooks and plugs. The setup wizard writes site_name on account creation, and the theme editor reads/writes via Settings.put_setting. Removed the "configure your shop" checklist item since currency/country aren't built yet. Also adds shop name field to setup wizard step 1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,14 +3,6 @@ defmodule BerrypodWeb.Admin.Dashboard do
|
||||
|
||||
alias Berrypod.{Cart, Orders, Products, Settings}
|
||||
|
||||
@checklist_items [
|
||||
%{key: :products_synced, label: "Sync your products", href: "/admin/providers"},
|
||||
%{key: :stripe_connected, label: "Connect Stripe", href: "/admin/settings"},
|
||||
%{key: :theme_customised, label: "Customise your theme", href: "/admin/theme"},
|
||||
%{key: :has_orders, label: "Place a test order", href: "/"},
|
||||
%{key: :site_live, label: "Go live", href: nil}
|
||||
]
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
setup = Berrypod.Setup.setup_status()
|
||||
@@ -23,6 +15,7 @@ defmodule BerrypodWeb.Admin.Dashboard do
|
||||
|> assign(:page_title, "Dashboard")
|
||||
|> assign(:setup, setup)
|
||||
|> assign(:show_checklist, show_checklist?(setup))
|
||||
|> assign(:checklist_collapsed, false)
|
||||
|> assign(:just_went_live, false)
|
||||
|> assign(:paid_count, paid_count)
|
||||
|> assign(:revenue, Orders.total_revenue())
|
||||
@@ -43,14 +36,8 @@ defmodule BerrypodWeb.Admin.Dashboard do
|
||||
|> assign(:just_went_live, true)}
|
||||
end
|
||||
|
||||
def handle_event("dismiss_checklist", _params, socket) do
|
||||
{:ok, _} = Settings.put_setting("checklist_dismissed", true, "boolean")
|
||||
setup = %{socket.assigns.setup | checklist_dismissed: true}
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:setup, setup)
|
||||
|> assign(:show_checklist, false)}
|
||||
def handle_event("toggle_checklist", _params, socket) do
|
||||
{:noreply, assign(socket, :checklist_collapsed, !socket.assigns.checklist_collapsed)}
|
||||
end
|
||||
|
||||
# ── Render ──
|
||||
@@ -78,7 +65,11 @@ defmodule BerrypodWeb.Admin.Dashboard do
|
||||
</div>
|
||||
|
||||
<%!-- Launch checklist --%>
|
||||
<.launch_checklist :if={@show_checklist and !@just_went_live} setup={@setup} />
|
||||
<.launch_checklist
|
||||
:if={@show_checklist and !@just_went_live}
|
||||
setup={@setup}
|
||||
collapsed={@checklist_collapsed}
|
||||
/>
|
||||
|
||||
<%!-- Stats --%>
|
||||
<div class="admin-stats-grid">
|
||||
@@ -158,86 +149,147 @@ defmodule BerrypodWeb.Admin.Dashboard do
|
||||
# ==========================================================================
|
||||
|
||||
attr :setup, :map, required: true
|
||||
attr :collapsed, :boolean, required: true
|
||||
|
||||
defp launch_checklist(assigns) do
|
||||
items =
|
||||
Enum.map(@checklist_items, fn item ->
|
||||
Map.put(item, :done, Map.get(assigns.setup, item.key, false))
|
||||
end)
|
||||
items = checklist_items(assigns.setup)
|
||||
|
||||
done_count = Enum.count(items, & &1.done)
|
||||
total = length(items)
|
||||
# Email is optional — exclude from progress count
|
||||
required_items = Enum.reject(items, &(&1.key == :email_configured))
|
||||
done_count = Enum.count(required_items, & &1.done)
|
||||
total = length(required_items)
|
||||
progress_pct = round(done_count / total * 100)
|
||||
|
||||
can_go_live =
|
||||
assigns.setup.provider_connected and assigns.setup.products_synced and
|
||||
assigns.setup.stripe_connected
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:items, items)
|
||||
|> assign(:done_count, done_count)
|
||||
|> assign(:total, total)
|
||||
|> assign(:progress_pct, progress_pct)
|
||||
|> assign(:can_go_live, can_go_live)
|
||||
|> assign(:can_go_live, assigns.setup.can_go_live)
|
||||
|> assign(:has_shipping, assigns.setup.has_shipping)
|
||||
|
||||
~H"""
|
||||
<div class="admin-checklist admin-card-spaced">
|
||||
<div class="admin-checklist-header">
|
||||
<button type="button" phx-click="toggle_checklist" class="admin-checklist-header">
|
||||
<h2 class="admin-checklist-title">Launch checklist</h2>
|
||||
<div class="admin-checklist-progress">
|
||||
<span>{@done_count} of {@total}</span>
|
||||
<div class="admin-checklist-bar">
|
||||
<div class="admin-checklist-bar-fill" style={"width: #{@progress_pct}%"} />
|
||||
</div>
|
||||
<.icon
|
||||
name={if @collapsed, do: "hero-chevron-down-mini", else: "hero-chevron-up-mini"}
|
||||
class="size-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<ul class="admin-checklist-items">
|
||||
<ul :if={!@collapsed} class="admin-checklist-items">
|
||||
<li :for={item <- @items} class="admin-checklist-item">
|
||||
<span class={["admin-checklist-check", item.done && "admin-checklist-check-done"]}>
|
||||
<.icon :if={item.done} name="hero-check-mini" class="size-3" />
|
||||
</span>
|
||||
|
||||
<span class={["admin-checklist-label", item.done && "admin-checklist-label-done"]}>
|
||||
{item.label}
|
||||
</span>
|
||||
<div class="admin-checklist-content">
|
||||
<div class="admin-checklist-row">
|
||||
<span class={[
|
||||
"admin-checklist-label",
|
||||
item.done && "admin-checklist-label-done"
|
||||
]}>
|
||||
{item.label}
|
||||
<span :if={item[:optional]} class="admin-checklist-optional">optional</span>
|
||||
</span>
|
||||
|
||||
<span class="admin-checklist-action">
|
||||
<%= if item.key == :site_live do %>
|
||||
<button
|
||||
phx-click="go_live"
|
||||
disabled={!@can_go_live}
|
||||
class="admin-btn admin-btn-primary admin-btn-sm"
|
||||
>
|
||||
<.icon name="hero-rocket-launch-mini" class="size-4" /> Go live
|
||||
</button>
|
||||
<% else %>
|
||||
<.link
|
||||
:if={!item.done}
|
||||
navigate={item.href}
|
||||
class="admin-btn admin-btn-secondary admin-btn-sm"
|
||||
>
|
||||
{if item.done, do: "View", else: "Start"} →
|
||||
</.link>
|
||||
<% end %>
|
||||
</span>
|
||||
<span class="admin-checklist-action">
|
||||
<%= if item.key == :site_live do %>
|
||||
<button
|
||||
phx-click="go_live"
|
||||
disabled={!@can_go_live}
|
||||
class="admin-btn admin-btn-primary admin-btn-sm"
|
||||
>
|
||||
<.icon name="hero-rocket-launch-mini" class="size-4" /> Go live
|
||||
</button>
|
||||
<% else %>
|
||||
<.link
|
||||
:if={!item.done}
|
||||
navigate={item.href}
|
||||
class="admin-btn admin-btn-secondary admin-btn-sm"
|
||||
>
|
||||
Start →
|
||||
</.link>
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p :if={item[:hint] && !item.done} class="admin-checklist-hint">
|
||||
{item.hint}
|
||||
</p>
|
||||
|
||||
<p
|
||||
:if={item.key == :site_live && !@can_go_live && !@has_shipping}
|
||||
class="admin-checklist-hint"
|
||||
>
|
||||
Shipping rates haven't synced yet. Try re-syncing your products from the <.link
|
||||
navigate="/admin/providers"
|
||||
class="admin-link"
|
||||
>providers page</.link>.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="admin-checklist-footer">
|
||||
<button
|
||||
type="button"
|
||||
phx-click="dismiss_checklist"
|
||||
class="admin-btn admin-btn-ghost admin-btn-sm"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp checklist_items(setup) do
|
||||
[
|
||||
# Setup wizard items (done first during onboarding)
|
||||
%{
|
||||
key: :provider_connected,
|
||||
label: "Connect a print provider",
|
||||
href: "/admin/providers"
|
||||
},
|
||||
%{
|
||||
key: :stripe_connected,
|
||||
label: "Connect Stripe",
|
||||
href: "/admin/settings"
|
||||
},
|
||||
# Post-setup items
|
||||
%{
|
||||
key: :products_synced,
|
||||
label: "Sync your products",
|
||||
href: if(setup.provider_connected, do: "/admin/products", else: "/admin/providers"),
|
||||
hint: "Import products from your print provider."
|
||||
},
|
||||
%{
|
||||
key: :theme_customised,
|
||||
label: "Customise your theme",
|
||||
href: "/admin/theme",
|
||||
hint: "Upload your logo, pick your colours, and choose a font that matches your brand."
|
||||
},
|
||||
%{
|
||||
key: :email_configured,
|
||||
label: "Set up email",
|
||||
href: "/admin/settings",
|
||||
optional: true,
|
||||
hint: "Needed for order confirmations, abandoned cart emails, and the contact form."
|
||||
},
|
||||
%{
|
||||
key: :has_orders,
|
||||
label: "Place a test order",
|
||||
href: "/",
|
||||
hint:
|
||||
"Use card 4242 4242 4242 4242 with any future expiry and CVC. " <>
|
||||
"You'll see the order in Orders when it works."
|
||||
},
|
||||
%{key: :site_live, label: "Go live"}
|
||||
]
|
||||
|> Enum.map(fn item ->
|
||||
Map.put(item, :done, Map.get(setup, item.key, false))
|
||||
end)
|
||||
end
|
||||
|
||||
# ==========================================================================
|
||||
# Components
|
||||
# ==========================================================================
|
||||
@@ -287,7 +339,7 @@ defmodule BerrypodWeb.Admin.Dashboard do
|
||||
# ==========================================================================
|
||||
|
||||
defp show_checklist?(setup) do
|
||||
not setup.site_live and not setup.checklist_dismissed
|
||||
not setup.site_live
|
||||
end
|
||||
|
||||
defp format_revenue(amount_pence) when is_integer(amount_pence) do
|
||||
|
||||
@@ -688,6 +688,7 @@ defmodule BerrypodWeb.Admin.Pages.Editor do
|
||||
page_data={@page_data}
|
||||
preview_data={@preview_data}
|
||||
theme_settings={@theme_settings}
|
||||
site_name={@site_name}
|
||||
generated_css={@generated_css}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
|
||||
@@ -26,6 +26,8 @@ defmodule BerrypodWeb.Admin.Theme.Index do
|
||||
socket =
|
||||
socket
|
||||
|> assign(:theme_settings, theme_settings)
|
||||
|> assign(:site_name, Settings.site_name())
|
||||
|> assign(:site_description, Settings.site_description())
|
||||
|> assign(:generated_css, generated_css)
|
||||
|> assign(:preview_page, :home)
|
||||
|> assign(:presets_with_descriptions, Presets.all_with_descriptions())
|
||||
@@ -174,6 +176,16 @@ defmodule BerrypodWeb.Admin.Theme.Index do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
# Settings stored outside the theme JSON
|
||||
@standalone_settings ~w(site_name site_description)
|
||||
|
||||
@impl true
|
||||
def handle_event("update_setting", %{"field" => field, "setting_value" => value}, socket)
|
||||
when field in @standalone_settings do
|
||||
Settings.put_setting(field, value, "string")
|
||||
{:noreply, assign(socket, String.to_existing_atom(field), value)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("update_setting", %{"field" => field, "setting_value" => value}, socket) do
|
||||
field_atom = String.to_existing_atom(field)
|
||||
@@ -197,6 +209,19 @@ defmodule BerrypodWeb.Admin.Theme.Index do
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("update_setting", %{"field" => field} = params, socket)
|
||||
when field in @standalone_settings do
|
||||
value = params[field]
|
||||
|
||||
if value do
|
||||
Settings.put_setting(field, value, "string")
|
||||
{:noreply, assign(socket, String.to_existing_atom(field), value)}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("update_setting", %{"field" => field} = params, socket) do
|
||||
# For phx-change events from select/input elements, the value comes from the name attribute
|
||||
@@ -439,6 +464,7 @@ defmodule BerrypodWeb.Admin.Theme.Index do
|
||||
attr :page, :atom, required: true
|
||||
attr :preview_data, :map, required: true
|
||||
attr :theme_settings, :map, required: true
|
||||
attr :site_name, :string, required: true
|
||||
attr :logo_image, :any, required: true
|
||||
attr :header_image, :any, required: true
|
||||
attr :cart_drawer_open, :boolean, default: false
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<input
|
||||
type="text"
|
||||
name="site_name"
|
||||
value={@theme_settings.site_name}
|
||||
value={@site_name}
|
||||
placeholder="Your shop name"
|
||||
class="admin-input admin-input-lg"
|
||||
/>
|
||||
@@ -374,7 +374,7 @@
|
||||
type="text"
|
||||
name="favicon_short_name"
|
||||
value={@theme_settings.favicon_short_name}
|
||||
placeholder={String.slice(@theme_settings.site_name, 0, 12)}
|
||||
placeholder={String.slice(@site_name, 0, 12)}
|
||||
maxlength="12"
|
||||
class="admin-input admin-input-sm"
|
||||
/>
|
||||
@@ -1181,7 +1181,7 @@
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
<span class="theme-browser-url-text truncate">
|
||||
{@theme_settings.site_name |> String.downcase() |> String.replace(" ", "")}.myshopify.com
|
||||
{@site_name |> String.downcase() |> String.replace(" ", "")}.myshopify.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1213,6 +1213,7 @@
|
||||
page={@preview_page}
|
||||
preview_data={@preview_data}
|
||||
theme_settings={@theme_settings}
|
||||
site_name={@site_name}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
cart_drawer_open={@cart_drawer_open}
|
||||
|
||||
Reference in New Issue
Block a user