add setup foundations: site gate, registration lockdown, coming soon page
- Settings.site_live?/0 and set_site_live/1 for shop visibility control - Accounts.has_admin?/0 to detect single-tenant admin existence - Registration lockdown: /users/register redirects when admin exists - Setup.setup_status/0 aggregates provider, product, and stripe checks - Coming soon page at /coming-soon with themed styling - ThemeHook :require_site_live gate on all public shop routes - Site live → everyone through - Authenticated → admin preview through - No admin → fresh install demo through - Otherwise → redirect to coming soon - Go live / take offline toggle on /admin/settings - 648 tests, 0 failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,11 @@ defmodule SimpleshopThemeWeb.AdminLive.Settings do
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket |> assign(:page_title, "Credentials") |> assign_stripe_state()}
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, "Settings")
|
||||
|> assign(:site_live, Settings.site_live?())
|
||||
|> assign_stripe_state()}
|
||||
end
|
||||
|
||||
defp assign_stripe_state(socket) do
|
||||
@@ -96,6 +100,18 @@ defmodule SimpleshopThemeWeb.AdminLive.Settings do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("toggle_site_live", _params, socket) do
|
||||
new_value = !socket.assigns.site_live
|
||||
{:ok, _} = Settings.set_site_live(new_value)
|
||||
|
||||
message = if new_value, do: "Shop is now live", else: "Shop taken offline"
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:site_live, new_value)
|
||||
|> put_flash(:info, message)}
|
||||
end
|
||||
|
||||
def handle_event("toggle_advanced", _params, socket) do
|
||||
{:noreply, assign(socket, :advanced_open, !socket.assigns.advanced_open)}
|
||||
end
|
||||
@@ -106,10 +122,50 @@ defmodule SimpleshopThemeWeb.AdminLive.Settings do
|
||||
<Layouts.app flash={@flash} current_scope={@current_scope}>
|
||||
<div class="max-w-2xl">
|
||||
<.header>
|
||||
Credentials
|
||||
<:subtitle>Connect payment providers and manage API keys</:subtitle>
|
||||
Settings
|
||||
<:subtitle>Shop status, payment providers, and API keys</:subtitle>
|
||||
</.header>
|
||||
|
||||
<section class="mt-10">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-lg font-semibold">Shop status</h2>
|
||||
<%= if @site_live do %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-green-600/20 ring-inset">
|
||||
<.icon name="hero-check-circle-mini" class="size-3" /> Live
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-zinc-50 px-2 py-1 text-xs font-medium text-zinc-600 ring-1 ring-zinc-500/10 ring-inset">
|
||||
Offline
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-zinc-600">
|
||||
<%= if @site_live do %>
|
||||
Your shop is visible to the public.
|
||||
<% else %>
|
||||
Your shop is offline. Visitors see a "coming soon" page.
|
||||
<% end %>
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
<button
|
||||
phx-click="toggle_site_live"
|
||||
class={[
|
||||
"inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-semibold shadow-xs",
|
||||
if(@site_live,
|
||||
do: "bg-zinc-100 text-zinc-700 hover:bg-zinc-200 ring-1 ring-zinc-300 ring-inset",
|
||||
else: "bg-green-600 text-white hover:bg-green-500"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= if @site_live do %>
|
||||
<.icon name="hero-eye-slash-mini" class="size-4" /> Take offline
|
||||
<% else %>
|
||||
<.icon name="hero-eye-mini" class="size-4" /> Go live
|
||||
<% end %>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mt-10">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-lg font-semibold">Stripe</h2>
|
||||
|
||||
22
lib/simpleshop_theme_web/live/shop_live/coming_soon.ex
Normal file
22
lib/simpleshop_theme_web/live/shop_live/coming_soon.ex
Normal file
@@ -0,0 +1,22 @@
|
||||
defmodule SimpleshopThemeWeb.ShopLive.ComingSoon do
|
||||
use SimpleshopThemeWeb, :live_view
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, assign(socket, :page_title, "Coming soon")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<main class="flex min-h-screen items-center justify-center px-6 text-center" role="main">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight sm:text-4xl">{@theme_settings.site_name}</h1>
|
||||
<p class="mt-4 text-lg text-[var(--t-text-muted)]">
|
||||
We're getting things ready. Check back soon.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -48,9 +48,15 @@ defmodule SimpleshopThemeWeb.UserLive.Registration do
|
||||
end
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
changeset = Accounts.change_user_email(%User{}, %{}, validate_unique: false)
|
||||
|
||||
{:ok, assign_form(socket, changeset), temporary_assigns: [form: nil]}
|
||||
if Accounts.has_admin?() do
|
||||
{:ok,
|
||||
socket
|
||||
|> put_flash(:error, "Registration is closed")
|
||||
|> redirect(to: ~p"/users/log-in")}
|
||||
else
|
||||
changeset = Accounts.change_user_email(%User{}, %{}, validate_unique: false)
|
||||
{:ok, assign_form(socket, changeset), temporary_assigns: [form: nil]}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
||||
@@ -32,10 +32,19 @@ defmodule SimpleshopThemeWeb.Router do
|
||||
scope "/", SimpleshopThemeWeb do
|
||||
pipe_through [:browser, :shop]
|
||||
|
||||
live_session :coming_soon,
|
||||
layout: {SimpleshopThemeWeb.Layouts, :shop},
|
||||
on_mount: [
|
||||
{SimpleshopThemeWeb.ThemeHook, :mount_theme}
|
||||
] do
|
||||
live "/coming-soon", ShopLive.ComingSoon, :index
|
||||
end
|
||||
|
||||
live_session :public_shop,
|
||||
layout: {SimpleshopThemeWeb.Layouts, :shop},
|
||||
on_mount: [
|
||||
{SimpleshopThemeWeb.ThemeHook, :mount_theme},
|
||||
{SimpleshopThemeWeb.ThemeHook, :require_site_live},
|
||||
{SimpleshopThemeWeb.CartHook, :mount_cart}
|
||||
] do
|
||||
live "/", ShopLive.Home, :index
|
||||
|
||||
@@ -4,6 +4,12 @@ defmodule SimpleshopThemeWeb.ThemeHook do
|
||||
|
||||
Mounted in the public_shop live_session alongside CartHook.
|
||||
Eliminates the identical theme-loading boilerplate from every shop LiveView.
|
||||
|
||||
## Actions
|
||||
|
||||
- `:mount_theme` — loads theme settings, CSS, and media assigns
|
||||
- `:require_site_live` — redirects unauthenticated visitors to /coming-soon
|
||||
when the shop is not live
|
||||
"""
|
||||
|
||||
import Phoenix.Component, only: [assign: 3]
|
||||
@@ -35,4 +41,21 @@ defmodule SimpleshopThemeWeb.ThemeHook do
|
||||
|
||||
{:cont, socket}
|
||||
end
|
||||
|
||||
def on_mount(:require_site_live, _params, session, socket) do
|
||||
cond do
|
||||
Settings.site_live?() ->
|
||||
{:cont, socket}
|
||||
|
||||
session["user_token"] ->
|
||||
{:cont, socket}
|
||||
|
||||
not SimpleshopTheme.Accounts.has_admin?() ->
|
||||
# Fresh install — no admin yet, show the demo shop
|
||||
{:cont, socket}
|
||||
|
||||
true ->
|
||||
{:halt, Phoenix.LiveView.redirect(socket, to: "/coming-soon")}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user