defmodule BerrypodWeb.Admin.Dashboard do
use BerrypodWeb, :live_view
alias Berrypod.{Cart, Orders, Products, Settings}
@impl true
def mount(_params, _session, socket) do
setup = Berrypod.Setup.setup_status()
status_counts = Orders.count_orders_by_status()
paid_count = Map.get(status_counts, "paid", 0)
recent_orders = Orders.list_orders(status: "paid") |> Enum.take(5)
{:ok,
socket
|> 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())
|> assign(:product_count, Products.count_products())
|> assign(:recent_orders, recent_orders)}
end
# ── Events ──
@impl true
def handle_event("go_live", _params, socket) do
{:ok, _} = Settings.set_site_live(true)
setup = %{socket.assigns.setup | site_live: true}
{:noreply,
socket
|> assign(:setup, setup)
|> assign(:just_went_live, true)}
end
def handle_event("toggle_checklist", _params, socket) do
{:noreply, assign(socket, :checklist_collapsed, !socket.assigns.checklist_collapsed)}
end
# ── Render ──
@impl true
def render(assigns) do
~H"""
<.header>
Dashboard
<%!-- Celebration after go-live --%>
<.icon name="hero-check-badge" class="setup-complete-icon" />
Your shop is live!
Customers can now browse and buy from your shop.
<.link href={~p"/"} class="admin-btn admin-btn-primary">
<.icon name="hero-arrow-top-right-on-square-mini" class="size-4" /> View your shop
<.link href="/?edit=theme" class="admin-btn admin-btn-secondary">
<.icon name="hero-paint-brush-mini" class="size-4" /> Customise theme
<%!-- Shop is live banner (not just now, but already live) --%>
<.icon name="hero-check-circle" class="size-5" />
Your shop is live
<%!-- Setup in progress message --%>
<.icon name="hero-wrench-screwdriver" class="size-5" />
Your admin account has been created. Continue the full setup below.
<%!-- Launch checklist --%>
<.launch_checklist
:if={@show_checklist and !@just_went_live}
setup={@setup}
collapsed={@checklist_collapsed}
/>
<%!-- Stats --%>
<.stat_card
label="Orders"
value={@paid_count}
icon="hero-shopping-bag"
href={~p"/admin/orders"}
/>
<.stat_card
label="Revenue"
value={format_revenue(@revenue)}
icon="hero-banknotes"
href={~p"/admin/orders"}
/>
<.stat_card
label="Products"
value={@product_count}
icon="hero-cube"
href={~p"/admin/products"}
/>
<%!-- Recent orders --%>
<%= if @recent_orders == [] do %>
<.icon name="hero-inbox" class="size-10" />
No orders yet
Orders will appear here once customers check out.
<% else %>
| Order |
Date |
Customer |
Total |
Fulfilment |
| {order.order_number} |
{format_date(order.inserted_at)} |
{order.customer_email || "—"} |
{Cart.format_price(order.total)} |
<.fulfilment_pill status={order.fulfilment_status} /> |
<% end %>
"""
end
# ==========================================================================
# Launch checklist component
# ==========================================================================
attr :setup, :map, required: true
attr :collapsed, :boolean, required: true
defp launch_checklist(assigns) do
items = checklist_items(assigns.setup)
done_count = Enum.count(items, & &1.done)
total = length(items)
progress_pct = round(done_count / total * 100)
assigns =
assigns
|> assign(:items, items)
|> assign(:done_count, done_count)
|> assign(:total, total)
|> assign(:progress_pct, progress_pct)
|> assign(:can_go_live, assigns.setup.can_go_live)
|> assign(:has_shipping, assigns.setup.has_shipping)
~H"""
-
<.icon :if={item.done} name="hero-check-mini" class="size-3" />
{item.label}
optional
<%= if item.key == :site_live do %>
<% else %>
<.link
:if={!item.done}
navigate={item.href}
class="admin-btn admin-btn-secondary admin-btn-sm"
>
Start →
<% end %>
{item.hint}
<.icon name="hero-question-mark-circle-mini" class="size-4" />
Help (opens in new window)
Shipping rates haven't synced yet. Try re-syncing your products from the <.link
navigate="/admin/providers"
class="admin-link"
>providers page.
"""
end
defp checklist_items(setup) do
[
# Setup wizard items (matches guided flow step names)
%{
key: :provider_connected,
label: "Connect a print provider",
href: "/admin/providers",
hint: "Link your Printify or Printful account to import products.",
help_url:
"https://help.printify.com/hc/en-us/articles/4483606633617-How-to-find-my-Printify-API-key"
},
%{
key: :stripe_connected,
label: "Connect Stripe for payments",
href: "/admin/settings",
hint: "Accept credit card payments from customers.",
help_url: "https://stripe.com/docs/keys"
},
%{
key: :email_configured,
label: "Set up email",
href: "/admin/settings/email",
hint: "Send order confirmations, shipping updates, and newsletters."
},
# Post-setup items
%{
key: :products_synced,
label: "Import products",
href:
if(setup.provider_connected,
do: "/admin/products",
else: "/admin/providers"
),
hint: "Sync products from your print provider."
},
%{
key: :has_shipping,
label: "Set up shipping",
href: "/admin/shipping",
hint: "Configure shipping rates for your products."
},
%{
key: :theme_customised,
label: "Customise your shop",
href: "/?edit=theme",
hint: "Upload your logo, pick colours, and choose fonts.",
optional: true
},
%{
key: :has_orders,
label: "Place a test order",
href: "/",
hint: "Use card 4242 4242 4242 4242 with any future expiry and CVC.",
optional: true
},
%{key: :site_live, label: "Go live"}
]
|> Enum.map(fn item ->
Map.put(item, :done, Map.get(setup, item.key, false))
end)
end
# ==========================================================================
# Components
# ==========================================================================
attr :label, :string, required: true
attr :value, :any, required: true
attr :icon, :string, required: true
attr :href, :string, required: true
defp stat_card(assigns) do
~H"""
<.link navigate={@href} class="admin-card dashboard-stat-link">
<.icon name={@icon} class="size-5" />
"""
end
defp fulfilment_pill(assigns) do
{color_class, label} =
case assigns.status do
"unfulfilled" -> {"admin-status-pill-zinc", "unfulfilled"}
"submitted" -> {"admin-status-pill-blue", "submitted"}
"processing" -> {"admin-status-pill-amber", "processing"}
"shipped" -> {"admin-status-pill-purple", "shipped"}
"delivered" -> {"admin-status-pill-green", "delivered"}
"failed" -> {"admin-status-pill-red", "failed"}
_ -> {"admin-status-pill-zinc", assigns.status || "—"}
end
assigns = assign(assigns, color_class: color_class, label: label)
~H"""
{@label}
"""
end
# ==========================================================================
# Helpers
# ==========================================================================
defp show_checklist?(setup) do
not setup.site_live
end
defp format_revenue(amount_pence) when is_integer(amount_pence) do
Cart.format_price(amount_pence)
end
defp format_revenue(_), do: "£0.00"
defp format_date(datetime) do
Calendar.strftime(datetime, "%d %b %Y")
end
end