Theme Studio
-- One theme, infinite possibilities. Every combination is designed to work beautifully. -
+diff --git a/assets/css/admin/components.css b/assets/css/admin/components.css index 8f8854f..0859d2b 100644 --- a/assets/css/admin/components.css +++ b/assets/css/admin/components.css @@ -934,6 +934,25 @@ border: 1px solid var(--t-border-default); } +/* ── Checklist banner (shown when arriving from the launch checklist) ── */ + +.admin-checklist-banner { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + margin-bottom: 1.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.5rem; + background-color: color-mix(in oklch, var(--t-status-info) 10%, var(--t-surface-base)); + border: 1px solid color-mix(in oklch, var(--t-status-info) 20%, var(--t-surface-base)); + + & .admin-checklist-banner-icon { color: var(--t-status-info); flex-shrink: 0; } + & .admin-checklist-banner-text { flex: 1; color: var(--t-text-secondary); } + & .admin-checklist-banner-link { font-weight: 500; white-space: nowrap; } +} + /* ── Dashboard stats grid ── */ @@ -3422,7 +3441,7 @@ min-height: 100vh; background: var(--t-surface-sunken); - @media (min-width: 64em) { + @media (min-width: 48em) { flex-direction: row; height: 100vh; } @@ -3434,7 +3453,7 @@ flex-shrink: 0; transition: width 0.3s, padding 0.3s; - @media (min-width: 64em) { + @media (min-width: 48em) { height: 100vh; } } @@ -3444,7 +3463,7 @@ overflow-y: auto; padding: 1.5rem; - @media (min-width: 64em) { + @media (min-width: 48em) { width: 380px; } } @@ -4168,6 +4187,10 @@ display: flex; flex-direction: column; gap: 1rem; + + &[hidden] { + display: none; + } } .admin-adapter-link { diff --git a/assets/css/admin/icons.css b/assets/css/admin/icons.css index 904b49d..a588df3 100644 --- a/assets/css/admin/icons.css +++ b/assets/css/admin/icons.css @@ -386,6 +386,18 @@ height: 1.5rem; } +.hero-clipboard-document-check { + --hero-clipboard-document-check: url('data:image/svg+xml;utf8,'); + -webkit-mask: var(--hero-clipboard-document-check); + mask: var(--hero-clipboard-document-check); + mask-repeat: no-repeat; + background-color: currentColor; + vertical-align: middle; + display: inline-block; + width: 1.5rem; + height: 1.5rem; +} + .hero-clipboard-document-list { --hero-clipboard-document-list: url('data:image/svg+xml;utf8,'); -webkit-mask: var(--hero-clipboard-document-list); diff --git a/lib/berrypod/accounts.ex b/lib/berrypod/accounts.ex index af06383..4f22d20 100644 --- a/lib/berrypod/accounts.ex +++ b/lib/berrypod/accounts.ex @@ -109,6 +109,7 @@ defmodule Berrypod.Accounts do else %User{} |> User.email_changeset(attrs) + |> User.password_changeset(attrs) |> Ecto.Changeset.put_change(:confirmed_at, DateTime.utc_now(:second)) |> Repo.insert() end diff --git a/lib/berrypod/dev_reset.ex b/lib/berrypod/dev_reset.ex new file mode 100644 index 0000000..cc46f09 --- /dev/null +++ b/lib/berrypod/dev_reset.ex @@ -0,0 +1,78 @@ +if Mix.env() == :dev do + defmodule Berrypod.DevReset do + @moduledoc """ + Dev-only helper to wipe all data and flush caches without restarting. + + Usage from IEx or Tidewave eval: + + Berrypod.DevReset.run() + """ + + alias Berrypod.Repo + + # Tables in deletion order (children before parents) + @tables ~w( + order_items + orders + abandoned_carts + product_images + product_variants + products + provider_connections + favicon_variants + images + users_tokens + users + pages + analytics_events + newsletter_subscribers + newsletter_campaigns + redirects + broken_urls + dead_links + activity_log + email_suppressions + settings + ) + + def run do + # Reconnect the Repo in case mix ecto.reset replaced the DB file + # while the server was running (old connections would point to a ghost file) + Supervisor.terminate_child(Berrypod.Supervisor, Repo) + Supervisor.restart_child(Berrypod.Supervisor, Repo) + Process.sleep(100) + + IO.puts("Wiping all data...") + + Repo.query!("PRAGMA foreign_keys = OFF") + + for table <- @tables do + Repo.query!("DELETE FROM \"#{table}\"") + end + + # Clear Oban jobs too + Repo.query!("DELETE FROM oban_jobs") + + Repo.query!("PRAGMA foreign_keys = ON") + + IO.puts("Flushing caches and runtime config...") + + # ETS caches (invalidate_all clears computed/cached values too) + Berrypod.Settings.SettingsCache.invalidate_all() + Berrypod.Theme.CSSCache.invalidate() + Berrypod.Pages.PageCache.invalidate_all() + + # Redirects ETS table + if :ets.whereis(:redirects_cache) != :undefined do + :ets.delete_all_objects(:redirects_cache) + end + + # Runtime Application env + Application.put_env(:berrypod, Berrypod.Mailer, adapter: Swoosh.Adapters.Local) + Application.put_env(:swoosh, :api_client, false) + + IO.puts("Done — visit /setup to start fresh") + :ok + end + end +end diff --git a/lib/berrypod/settings.ex b/lib/berrypod/settings.ex index ba3d3c0..8233d7d 100644 --- a/lib/berrypod/settings.ex +++ b/lib/berrypod/settings.ex @@ -202,23 +202,29 @@ defmodule Berrypod.Settings do The plaintext is encrypted via Vault before storage. """ def put_secret(key, plaintext) when is_binary(plaintext) do - case Vault.encrypt(plaintext) do - {:ok, encrypted} -> - %Setting{key: key} - |> Setting.changeset(%{ - key: key, - value: "[encrypted]", - value_type: "encrypted", - encrypted_value: encrypted - }) - |> Repo.insert( - on_conflict: {:replace, [:value, :encrypted_value, :value_type, :updated_at]}, - conflict_target: :key - ) + result = + case Vault.encrypt(plaintext) do + {:ok, encrypted} -> + %Setting{key: key} + |> Setting.changeset(%{ + key: key, + value: "[encrypted]", + value_type: "encrypted", + encrypted_value: encrypted + }) + |> Repo.insert( + on_conflict: {:replace, [:value, :encrypted_value, :value_type, :updated_at]}, + conflict_target: :key + ) - {:error, reason} -> - {:error, reason} - end + {:error, reason} -> + {:error, reason} + end + + SettingsCache.invalidate() + SettingsCache.warm() + + result end @doc """ diff --git a/lib/berrypod_web/admin_layout_hook.ex b/lib/berrypod_web/admin_layout_hook.ex index ce1926f..dc67d42 100644 --- a/lib/berrypod_web/admin_layout_hook.ex +++ b/lib/berrypod_web/admin_layout_hook.ex @@ -28,7 +28,6 @@ defmodule BerrypodWeb.AdminLayoutHook do socket |> assign(:current_path, "") |> assign(:site_live, Settings.site_live?()) - |> assign(:email_configured, Berrypod.Mailer.email_configured?()) |> assign(:theme_settings, theme_settings) |> assign(:site_name, Settings.site_name()) |> assign(:site_description, Settings.site_description()) diff --git a/lib/berrypod_web/components/layouts/admin.html.heex b/lib/berrypod_web/components/layouts/admin.html.heex index 0c20eac..3246914 100644 --- a/lib/berrypod_web/components/layouts/admin.html.heex +++ b/lib/berrypod_web/components/layouts/admin.html.heex @@ -18,17 +18,6 @@ - <%!-- email warning banner --%> -
- <%!-- page content --%>- One theme, infinite possibilities. Every combination is designed to work beautifully. -
+