improve setup UX: password field, setup hook, checklist banners, theme tweaks
All checks were successful
deploy / deploy (push) Successful in 1m31s

- add password field and required shop name to setup wizard
- extract SetupHook for DRY redirect to /setup when no admin exists
- add ?from=checklist param to checklist hrefs with contextual banner on
  email settings and theme pages for easy return to dashboard
- remove email warning banner from admin layout (checklist covers it)
- make email a required checklist item (no longer optional)
- add DevReset module for wiping dev data without restart
- rename "Theme Studio" to "Theme", drop subtitle
- lower theme editor side-by-side breakpoint from 64em to 48em
- clean up login/registration pages (remove dead registration_open code)
- fix settings.put_secret to invalidate cache after write

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-03 17:41:08 +00:00
parent 0853b6f528
commit 64f083d271
23 changed files with 309 additions and 118 deletions

View File

@@ -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

78
lib/berrypod/dev_reset.ex Normal file
View File

@@ -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

View File

@@ -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 """