defmodule BerrypodWeb.Admin.Dashboard do use BerrypodWeb, :live_view alias Berrypod.{Cart, Orders, Products, Settings} @checklist_items [ %{key: :products_synced, label: "Sync your products", href: "/admin/providers"}, %{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() 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(: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("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)} 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 navigate={~p"/admin/theme"} class="admin-btn admin-btn-secondary"> <.icon name="hero-paint-brush-mini" class="size-4" /> Customise theme
<%!-- Launch checklist --%> <.launch_checklist :if={@show_checklist and !@just_went_live} setup={@setup} /> <%!-- 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 --%>

Recent orders

<.link navigate={~p"/admin/orders"} style="font-size: 0.875rem; color: var(--color-base-content-60, rgba(0 0 0 / 0.6));" > View all →
<%= 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 defp launch_checklist(assigns) do items = Enum.map(@checklist_items, fn item -> Map.put(item, :done, Map.get(assigns.setup, item.key, false)) end) done_count = Enum.count(items, & &1.done) total = length(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) ~H"""

Launch checklist

{@done_count} of {@total}
""" 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" style="display: block; text-decoration: none;" >
<.icon name={@icon} class="size-5" />

{@value}

{@label}

""" end defp fulfilment_pill(assigns) do {color, label} = case assigns.status do "unfulfilled" -> {"var(--color-base-200, #e5e5e5)", "unfulfilled"} "submitted" -> {"#dbeafe", "submitted"} "processing" -> {"#fef3c7", "processing"} "shipped" -> {"#f3e8ff", "shipped"} "delivered" -> {"#dcfce7", "delivered"} "failed" -> {"#fee2e2", "failed"} _ -> {"var(--color-base-200, #e5e5e5)", assigns.status || "—"} end assigns = assign(assigns, color: color, label: label) ~H""" {@label} """ end # ========================================================================== # Helpers # ========================================================================== defp show_checklist?(setup) do not setup.site_live and not setup.checklist_dismissed 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