diff --git a/lib/simpleshop_theme_web/components/layouts/root.html.heex b/lib/simpleshop_theme_web/components/layouts/root.html.heex index a575990..5f32636 100644 --- a/lib/simpleshop_theme_web/components/layouts/root.html.heex +++ b/lib/simpleshop_theme_web/components/layouts/root.html.heex @@ -36,6 +36,9 @@
  • {@current_scope.user.email}
  • +
  • + <.link href={~p"/admin/theme"}>Theme +
  • <.link href={~p"/users/settings"}>Settings
  • diff --git a/lib/simpleshop_theme_web/live/theme_live/index.ex b/lib/simpleshop_theme_web/live/theme_live/index.ex new file mode 100644 index 0000000..ee1fad9 --- /dev/null +++ b/lib/simpleshop_theme_web/live/theme_live/index.ex @@ -0,0 +1,54 @@ +defmodule SimpleshopThemeWeb.ThemeLive.Index do + use SimpleshopThemeWeb, :live_view + + alias SimpleshopTheme.Settings + alias SimpleshopTheme.Theme.{CSSGenerator, Presets} + + @impl true + def mount(_params, _session, socket) do + theme_settings = Settings.get_theme_settings() + generated_css = CSSGenerator.generate(theme_settings) + + socket = + socket + |> assign(:theme_settings, theme_settings) + |> assign(:generated_css, generated_css) + |> assign(:preview_page, :home) + |> assign(:preset_names, Presets.list_names()) + + {:ok, socket} + end + + @impl true + def handle_event("apply_preset", %{"preset" => preset_name}, socket) do + preset_atom = String.to_existing_atom(preset_name) + + case Settings.apply_preset(preset_atom) do + {:ok, theme_settings} -> + generated_css = CSSGenerator.generate(theme_settings) + + socket = + socket + |> assign(:theme_settings, theme_settings) + |> assign(:generated_css, generated_css) + |> put_flash(:info, "Applied #{preset_name} preset") + + {:noreply, socket} + + {:error, _} -> + {:noreply, put_flash(socket, :error, "Failed to apply preset")} + end + end + + @impl true + def handle_event("change_preview_page", %{"page" => page_name}, socket) do + page_atom = String.to_existing_atom(page_name) + {:noreply, assign(socket, :preview_page, page_atom)} + end + + @impl true + def handle_event("save_theme", _params, socket) do + socket = put_flash(socket, :info, "Theme saved successfully") + {:noreply, socket} + end +end diff --git a/lib/simpleshop_theme_web/live/theme_live/index.html.heex b/lib/simpleshop_theme_web/live/theme_live/index.html.heex new file mode 100644 index 0000000..8d8775a --- /dev/null +++ b/lib/simpleshop_theme_web/live/theme_live/index.html.heex @@ -0,0 +1,160 @@ +
    + + +
    + +
    +
    + +
    +

    Presets

    +
    + <%= for preset_name <- @preset_names do %> + + <% end %> +
    +
    + + +
    +
    +

    Current Settings

    +
    +
    + Mood: + <%= @theme_settings.mood %> +
    +
    + Typography: + <%= @theme_settings.typography %> +
    +
    + Shape: + <%= @theme_settings.shape %> +
    +
    + Density: + <%= @theme_settings.density %> +
    +
    + Accent Color: + + + + <%= @theme_settings.accent_color %> + +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    + <%= for {page_name, label} <- [ + {:home, "Home"}, + {:collection, "Collection"}, + {:pdp, "Product"}, + {:cart, "Cart"}, + {:about, "About"}, + {:contact, "Contact"}, + {:error, "404"} + ] do %> + + <% end %> +
    +
    + + +
    + + +
    +

    + Preview: <%= String.capitalize(to_string(@preview_page)) %> +

    + +
    +

    + This is a preview of the <%= @preview_page %> page with your current theme settings. +

    + +
    + + +
    + +
    +

    + Card Example +

    +

    + This card demonstrates the current surface, border, and text colors with the selected shape style. +

    +
    + +
    + Detailed preview pages coming in Phase 5 +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/lib/simpleshop_theme_web/router.ex b/lib/simpleshop_theme_web/router.ex index 7c00be0..77496d0 100644 --- a/lib/simpleshop_theme_web/router.ex +++ b/lib/simpleshop_theme_web/router.ex @@ -54,6 +54,7 @@ defmodule SimpleshopThemeWeb.Router do on_mount: [{SimpleshopThemeWeb.UserAuth, :require_authenticated}] do live "/users/settings", UserLive.Settings, :edit live "/users/settings/confirm-email/:token", UserLive.Settings, :confirm_email + live "/admin/theme", ThemeLive.Index, :index end post "/users/update-password", UserSessionController, :update_password diff --git a/test/simpleshop_theme_web/live/theme_live_test.exs b/test/simpleshop_theme_web/live/theme_live_test.exs new file mode 100644 index 0000000..bb839c0 --- /dev/null +++ b/test/simpleshop_theme_web/live/theme_live_test.exs @@ -0,0 +1,118 @@ +defmodule SimpleshopThemeWeb.ThemeLiveTest do + use SimpleshopThemeWeb.ConnCase, async: false + + import Phoenix.LiveViewTest + import SimpleshopTheme.AccountsFixtures + + alias SimpleshopTheme.Settings + + setup do + user = user_fixture() + %{user: user} + end + + describe "Index (unauthenticated)" do + test "redirects to login when not authenticated", %{conn: conn} do + {:error, redirect} = live(conn, ~p"/admin/theme") + + assert {:redirect, %{to: path}} = redirect + assert path == ~p"/users/log-in" + end + end + + describe "Index (authenticated)" do + setup %{conn: conn, user: user} do + conn = log_in_user(conn, user) + %{conn: conn} + end + + test "renders theme editor page", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/admin/theme") + + assert html =~ "Theme Editor" + assert html =~ "Save Theme" + assert html =~ "Presets" + end + + test "displays all 9 presets", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/admin/theme") + + assert html =~ "gallery" + assert html =~ "studio" + assert html =~ "boutique" + assert html =~ "bold" + assert html =~ "playful" + assert html =~ "minimal" + assert html =~ "night" + assert html =~ "classic" + assert html =~ "impulse" + end + + test "displays current theme settings", %{conn: conn} do + {:ok, _settings} = Settings.apply_preset(:gallery) + {:ok, _view, html} = live(conn, ~p"/admin/theme") + + assert html =~ "warm" + assert html =~ "editorial" + assert html =~ "soft" + assert html =~ "spacious" + end + + test "displays generated CSS in preview", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/admin/theme") + + assert html =~ ".preview-frame, .shop-root" + assert html =~ "--t-accent-h:" + assert html =~ "--t-surface-base:" + assert html =~ "--t-font-heading:" + end + + test "applies preset and updates preview", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/admin/theme") + + html = + view + |> element("button", "gallery") + |> render_click() + + theme_settings = Settings.get_theme_settings() + assert theme_settings.mood == "warm" + assert theme_settings.typography == "editorial" + assert html =~ "warm" + assert html =~ "editorial" + end + + test "switches preview page", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/admin/theme") + + html = + view + |> element("button", "Collection") + |> render_click() + + assert html =~ "Preview: Collection" + end + + test "save theme button works", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/admin/theme") + + view + |> element("button", "Save Theme") + |> render_click() + + assert view |> element("button", "Save Theme") |> has_element?() + end + + test "all preview page buttons are present", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/admin/theme") + + assert html =~ "Home" + assert html =~ "Collection" + assert html =~ "Product" + assert html =~ "Cart" + assert html =~ "About" + assert html =~ "Contact" + assert html =~ "404" + end + end +end