berrypod/lib/berrypod_web/live/admin/theme/index.html.heex

652 lines
22 KiB
Plaintext
Raw Normal View History

<div class="theme-layout">
<!-- Controls Sidebar -->
<div
id="theme-sidebar"
class={[
"theme-sidebar",
if(@sidebar_collapsed,
do: "theme-sidebar-collapsed",
else: "theme-sidebar-expanded"
)
]}
>
<!-- Collapsed state: just show expand button -->
<%= if @sidebar_collapsed do %>
<div class="theme-sidebar-collapsed-inner">
<button
type="button"
phx-click="toggle_sidebar"
class="theme-collapse-btn"
aria-label="Expand sidebar"
aria-expanded="false"
aria-controls="theme-sidebar"
>
<svg
class="theme-collapse-icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
aria-hidden="true"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</div>
<% else %>
<.link href={~p"/admin"} class="theme-back-link">
<.icon name="hero-arrow-left-mini" class="size-4" /> Admin
</.link>
<div :if={@from_checklist} class="admin-checklist-banner">
<.icon name="hero-clipboard-document-check" class="size-5 admin-checklist-banner-icon" />
<span class="admin-checklist-banner-text">
You're customising your theme.
</span>
<.link navigate={~p"/admin"} class="admin-link admin-checklist-banner-link">
&larr; Back to checklist
</.link>
</div>
<!-- Header -->
<div class="theme-header">
<div class="admin-fill">
<h1 class="theme-title">Theme</h1>
</div>
<button
type="button"
phx-click="toggle_sidebar"
class="theme-collapse-btn"
aria-label="Collapse sidebar"
aria-expanded="true"
aria-controls="theme-sidebar"
>
<svg
class="theme-collapse-icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
aria-hidden="true"
>
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
</div>
<!-- Site Name -->
<div class="theme-section">
<label class="theme-section-label">Shop name</label>
<form phx-change="update_setting" phx-value-field="site_name">
<input
type="text"
name="site_name"
value={@site_name}
placeholder="Your shop name"
class="admin-input admin-input-lg"
/>
</form>
</div>
<!-- Branding Section -->
<div class="theme-panel">
<span class="theme-section-label">Logo & header</span>
<div class="admin-stack admin-stack-sm theme-field">
<label class="admin-toggle-label">
<input
type="checkbox"
checked={@theme_settings.show_site_name}
phx-click="toggle_setting"
phx-value-field="show_site_name"
class="admin-toggle admin-toggle-sm"
/>
<span class="theme-slider-label">Show shop name</span>
</label>
<label class="admin-toggle-label">
<input
type="checkbox"
checked={@theme_settings.show_logo}
phx-click="toggle_setting"
phx-value-field="show_logo"
class="admin-toggle admin-toggle-sm"
/>
<span class="theme-slider-label">Show logo</span>
</label>
</div>
<!-- Logo Upload (when logo enabled) -->
<%= if @theme_settings.show_logo do %>
<div class="theme-subsection">
<span class="theme-slider-label theme-block-label">
Upload logo (SVG or PNG)
</span>
<div class="admin-row admin-row-lg">
<form phx-change="noop" phx-submit="noop" class="admin-fill">
<label class="theme-upload-label">
<span>Choose file...</span>
<.live_file_input upload={@uploads.logo_upload} class="hidden" />
</label>
</form>
<%= if @logo_image do %>
<div class="theme-thumb theme-thumb-logo">
<img
src={"/image_cache/#{@logo_image.id}.webp"}
alt={@site_name}
/>
<button
type="button"
phx-click="remove_logo"
class="theme-remove-btn"
title="Remove logo"
>
×
</button>
</div>
<% end %>
</div>
<%= for entry <- @uploads.logo_upload.entries do %>
<div class="theme-progress">
<div class="theme-progress-bar">
<div
class="theme-progress-fill"
style={"width: #{entry.progress}%"}
>
</div>
</div>
<span class="admin-text-secondary">{entry.progress}%</span>
<button
type="button"
phx-click="cancel_upload"
phx-value-ref={entry.ref}
phx-value-upload="logo_upload"
class="theme-upload-cancel"
>
×
</button>
</div>
<%= for err <- upload_errors(@uploads.logo_upload, entry) do %>
<p class="theme-error-text">{error_to_string(err)}</p>
<% end %>
<% end %>
<%= for err <- upload_errors(@uploads.logo_upload) do %>
<p class="theme-error-text">{error_to_string(err)}</p>
<% end %>
<!-- Logo Size Slider -->
<%= if @logo_image do %>
<form
phx-change="update_setting"
phx-value-field="logo_size"
class="theme-subfield"
>
<div class="theme-slider-header">
<span class="theme-slider-label">Logo size</span>
<span class="theme-slider-value">{@theme_settings.logo_size}px</span>
</div>
<input
type="range"
min="24"
max="120"
value={@theme_settings.logo_size}
name="logo_size"
class="admin-range"
/>
</form>
<!-- SVG Recolor Toggle (only for SVG logos) -->
<%= if @logo_image.is_svg do %>
<div class="theme-subfield">
<label class="admin-toggle-label">
<input
type="checkbox"
checked={@theme_settings.logo_recolor}
phx-click="update_setting"
phx-value-field="logo_recolor"
phx-value-setting_value={
if @theme_settings.logo_recolor, do: "false", else: "true"
}
class="admin-toggle admin-toggle-sm"
/>
<span class="theme-slider-label">Recolour logo</span>
</label>
<%= if @theme_settings.logo_recolor do %>
<form
id="logo-color-form"
phx-change="update_color"
phx-value-field="logo_color"
phx-hook="ColorSync"
class="theme-color-row theme-subfield-sm"
>
<input
type="color"
name="value"
value={@theme_settings.logo_color}
class="theme-color-swatch theme-color-swatch-sm"
/>
<span class="theme-color-value">{@theme_settings.logo_color}</span>
</form>
<% end %>
</div>
<% end %>
<% end %>
</div>
<% end %>
</div>
<!-- Site Icon / Favicon -->
<div class="theme-panel">
<label class="theme-section-label">Site icon</label>
<p class="admin-text-tertiary theme-field">
Your icon appears in browser tabs and on home screens.
</p>
<!-- Use logo as icon toggle -->
<label class="admin-toggle-label theme-field">
<input
type="checkbox"
checked={@theme_settings.use_logo_as_icon}
phx-click="toggle_setting"
phx-value-field="use_logo_as_icon"
class="admin-toggle admin-toggle-sm"
/>
<span class="theme-slider-label">Use logo as favicon</span>
</label>
<!-- Icon upload (only when not using logo) -->
<%= if !@theme_settings.use_logo_as_icon do %>
<div class="admin-separator">
<span class="theme-slider-label theme-block-label">
Upload icon (PNG or SVG, 512×512+)
</span>
<div class="admin-row admin-row-lg">
<form phx-change="noop" phx-submit="noop" class="admin-fill">
<label class="theme-upload-label">
<span>Choose file...</span>
<.live_file_input upload={@uploads.icon_upload} class="hidden" />
</label>
</form>
<%= if @icon_image do %>
<div class="theme-thumb theme-thumb-icon">
<%= if @icon_image.is_svg do %>
<img
src={"/images/#{@icon_image.id}/recolored/000000"}
alt="Current icon"
/>
<% else %>
<img
src={"/image_cache/#{@icon_image.id}.webp"}
alt="Current icon"
/>
<% end %>
<button
type="button"
phx-click="remove_icon"
class="theme-remove-btn"
title="Remove icon"
>
×
</button>
</div>
<% end %>
</div>
<%= for entry <- @uploads.icon_upload.entries do %>
<div class="theme-progress">
<div class="theme-progress-bar">
<div
class="theme-progress-fill"
style={"width: #{entry.progress}%"}
>
</div>
</div>
<span class="admin-text-secondary">{entry.progress}%</span>
<button
type="button"
phx-click="cancel_upload"
phx-value-ref={entry.ref}
phx-value-upload="icon_upload"
class="theme-upload-cancel"
>
×
</button>
</div>
<%= for err <- upload_errors(@uploads.icon_upload, entry) do %>
<p class="theme-error-text">{error_to_string(err)}</p>
<% end %>
<% end %>
<%= for err <- upload_errors(@uploads.icon_upload) do %>
<p class="theme-error-text">{error_to_string(err)}</p>
<% end %>
</div>
<% end %>
<!-- Short name -->
<div class="theme-subfield">
<form phx-change="update_setting" phx-value-field="favicon_short_name">
<label class="theme-slider-label theme-block-label">
Short name <span class="admin-text-tertiary">— appears under home screen icon</span>
</label>
<input
type="text"
name="favicon_short_name"
value={@theme_settings.favicon_short_name}
placeholder={String.slice(@site_name, 0, 12)}
maxlength="12"
class="admin-input admin-input-sm"
/>
</form>
</div>
<!-- Icon background colour -->
<div class="theme-subfield">
<form
id="icon-bg-color-form"
phx-change="update_color"
phx-value-field="icon_background_color"
phx-hook="ColorSync"
class="theme-color-row"
>
<input
type="color"
name="value"
value={@theme_settings.icon_background_color}
class="theme-color-swatch theme-color-swatch-sm"
/>
<div>
<span class="theme-slider-label theme-block-label">Icon background</span>
<span class="theme-slider-value">
{@theme_settings.icon_background_color}
</span>
</div>
</form>
</div>
</div>
<!-- Header Background Toggle -->
<div class="theme-section">
<label class="admin-toggle-label">
<input
type="checkbox"
checked={@theme_settings.header_background_enabled}
phx-click="update_setting"
phx-value-field="header_background_enabled"
phx-value-setting_value={
if @theme_settings.header_background_enabled, do: "false", else: "true"
}
class="admin-toggle admin-toggle-sm"
/>
<span class="theme-check-text">
Header background image
</span>
</label>
</div>
<!-- Header Image Upload (only when enabled) -->
<%= if @theme_settings.header_background_enabled do %>
<div class="theme-panel">
<span class="theme-slider-label theme-block-label">
Upload header image
</span>
<form phx-change="noop" phx-submit="noop">
<label class="theme-upload-label">
<span>Choose file...</span>
<.live_file_input upload={@uploads.header_upload} class="hidden" />
</label>
</form>
<%= if @header_image do %>
<div class="theme-thumb theme-thumb-cover theme-thumb-header">
<img
src={"/image_cache/#{@header_image.id}.webp"}
alt=""
/>
<button
type="button"
phx-click="remove_header"
class="theme-remove-btn"
title="Remove header background"
>
×
</button>
</div>
<%= if @header_contrast_warning != :ok do %>
<div class="theme-contrast-warning">
<.icon name="hero-exclamation-triangle" class="size-5" />
<div>
<strong>
<%= if @header_contrast_warning == :poor do %>
Text may be hard to read
<% else %>
Text contrast could be better
<% end %>
</strong>
<p>
The header text might blend into this background.
Try switching to a
<%= if @theme_settings.mood == "dark" do %>
lighter mood
<% else %>
dark mood
<% end %>
or choosing a different image.
</p>
</div>
</div>
<% end %>
<!-- Header Image Controls -->
<div class="admin-stack admin-stack-md theme-subfield">
<form phx-change="update_setting" phx-value-field="header_zoom">
<div class="theme-slider-header">
<span class="theme-slider-label">Zoom</span>
<span class="theme-slider-value">{@theme_settings.header_zoom}%</span>
</div>
<input
type="range"
min="100"
max="200"
value={@theme_settings.header_zoom}
name="header_zoom"
class="admin-range"
/>
</form>
<%= if @theme_settings.header_zoom > 100 do %>
<form phx-change="update_setting" phx-value-field="header_position_x">
<div class="theme-slider-header">
<span class="theme-slider-label">Horizontal position</span>
<span class="theme-slider-value">{@theme_settings.header_position_x}%</span>
</div>
<input
type="range"
min="0"
max="100"
value={@theme_settings.header_position_x}
name="header_position_x"
class="admin-range"
/>
</form>
<form phx-change="update_setting" phx-value-field="header_position_y">
<div class="theme-slider-header">
<span class="theme-slider-label">Vertical position</span>
<span class="theme-slider-value">{@theme_settings.header_position_y}%</span>
</div>
<input
type="range"
min="0"
max="100"
value={@theme_settings.header_position_y}
name="header_position_y"
class="admin-range"
/>
</form>
<% end %>
</div>
<% end %>
<%= for entry <- @uploads.header_upload.entries do %>
<div class="theme-progress">
<div class="theme-progress-bar">
<div
class="theme-progress-fill"
style={"width: #{entry.progress}%"}
>
</div>
</div>
<span class="admin-text-secondary">{entry.progress}%</span>
<button
type="button"
phx-click="cancel_upload"
phx-value-ref={entry.ref}
phx-value-upload="header_upload"
class="theme-upload-cancel"
>
×
</button>
</div>
<%= for err <- upload_errors(@uploads.header_upload, entry) do %>
<p class="theme-error-text">{error_to_string(err)}</p>
<% end %>
<% end %>
<%= for err <- upload_errors(@uploads.header_upload) do %>
<p class="theme-error-text">{error_to_string(err)}</p>
<% end %>
</div>
<% end %>
<!-- Presets Section -->
<.preset_grid
presets={@presets_with_descriptions}
active_preset={@active_preset}
event_prefix=""
label="Start with a preset"
/>
<!-- Accent Colors -->
<.color_picker
field="accent_color"
label="Accent colour"
value={@theme_settings.accent_color}
event_prefix=""
/>
<.color_picker
field="secondary_accent_color"
label="Hover colour"
value={@theme_settings.secondary_accent_color}
event_prefix=""
/>
<.color_picker
field="sale_color"
label="Sale colour"
value={@theme_settings.sale_color}
event_prefix=""
/>
<!-- Customise Section -->
<.customise_accordion
theme_settings={@theme_settings}
customise_open={@customise_open}
event_prefix=""
/>
<% end %>
</div>
<!-- Preview Area -->
<div class="theme-preview-area">
<div class="theme-preview-container">
<!-- Preview Page Switcher -->
<div class="theme-preview-tabs">
<%= for {page_name, label} <- [
{:home, "Home"},
{:collection, "Collection"},
{:pdp, "Product"},
{:cart, "Cart"},
{:about, "About"},
{:delivery, "Delivery"},
{:privacy, "Privacy"},
{:terms, "Terms"},
{:contact, "Contact"},
{:error, "404"}
] do %>
<button
type="button"
phx-click="change_preview_page"
phx-value-page={page_name}
class={[
"theme-preview-tab",
@preview_page == page_name && "theme-preview-tab-active"
]}
>
{label}
</button>
<% end %>
</div>
<!-- Browser Chrome -->
<div class="theme-browser-chrome">
<div class="theme-browser-dots">
<div class="theme-browser-dot theme-browser-dot-close"></div>
<div class="theme-browser-dot theme-browser-dot-min"></div>
<div class="theme-browser-dot theme-browser-dot-max"></div>
</div>
<div class="theme-browser-url">
<svg
class="theme-browser-url-icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<span class="theme-browser-url-text truncate">
{@site_name |> String.downcase() |> String.replace(" ", "")}.myshopify.com
</span>
</div>
</div>
<!-- Preview Frame -->
<div
class="themed theme-preview-frame"
data-mood={@theme_settings.mood}
data-typography={@theme_settings.typography}
data-shape={@theme_settings.shape}
data-density={@theme_settings.density}
data-grid={@theme_settings.grid_columns}
data-header={@theme_settings.header_layout}
data-sticky={to_string(@theme_settings.sticky_header)}
data-layout={@theme_settings.layout_width}
data-shadow={@theme_settings.card_shadow}
data-button-style={@theme_settings.button_style}
>
<style>
/* All font faces for theme switching */
<%= Phoenix.HTML.raw(Berrypod.Theme.Fonts.generate_all_font_faces(
&BerrypodWeb.Endpoint.static_path/1
)) %>
/* Generated theme CSS */
<%= Phoenix.HTML.raw(@generated_css) %>
</style>
<.preview_page
page={@preview_page}
preview_data={@preview_data}
theme_settings={@theme_settings}
site_name={@site_name}
logo_image={@logo_image}
header_image={@header_image}
cart_drawer_open={@cart_drawer_open}
/>
</div>
</div>
</div>
</div>