add block previews, picker thumbnails and newsletter settings
All checks were successful
deploy / deploy (push) Successful in 1m30s

Block cards now show a one-line content summary below the name.
Block picker items include SVG wireframe thumbnails. Newsletter
block marked as decorative with configurable title/description
and form submission prevented on the shop side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-02-28 20:34:52 +00:00
parent 0a7982dfe8
commit 8f989d892d
6 changed files with 636 additions and 12 deletions

View File

@ -1453,11 +1453,23 @@
flex-shrink: 0; flex-shrink: 0;
} }
.block-card-name { .block-card-info {
flex: 1; flex: 1;
min-width: 0;
}
.block-card-name {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
min-width: 0; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.block-card-preview {
display: block;
font-size: 0.75rem;
color: color-mix(in oklch, var(--t-text-primary) 50%, transparent);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -1567,13 +1579,25 @@
} }
} }
.block-picker-thumb {
grid-column: 1 / -1;
width: 100%;
height: auto;
border-radius: 0.25rem;
background: color-mix(in oklch, var(--t-text-primary) 3%, transparent);
margin-bottom: 0.25rem;
}
.block-picker-item > .size-5 { .block-picker-item > .size-5 {
grid-row: 1 / -1;
margin-top: 0.0625rem; margin-top: 0.0625rem;
} }
.block-picker-item-desc { .block-picker-item-name {
grid-column: 2; grid-column: 2;
}
.block-picker-item-desc {
grid-column: 1 / -1;
font-size: 0.6875rem; font-size: 0.6875rem;
line-height: 1.3; line-height: 1.3;
color: color-mix(in oklch, var(--t-text-primary) 55%, transparent); color: color-mix(in oklch, var(--t-text-primary) 55%, transparent);
@ -1606,6 +1630,26 @@
/* Block settings panel */ /* Block settings panel */
.block-settings-hint {
display: flex;
align-items: flex-start;
gap: 0.375rem;
font-size: 0.75rem;
color: color-mix(in oklch, var(--t-text-primary) 60%, transparent);
background: color-mix(in oklch, var(--t-accent) 8%, transparent);
border: 1px solid color-mix(in oklch, var(--t-accent) 20%, transparent);
border-radius: 0.375rem;
padding: 0.5rem 0.625rem;
margin-bottom: 0.5rem;
line-height: 1.4;
}
.block-settings-hint svg {
flex-shrink: 0;
color: var(--t-accent);
margin-top: 0.05rem;
}
.block-card-settings { .block-card-settings {
padding: 0.75rem 0.75rem 0.25rem; padding: 0.75rem 0.75rem 0.25rem;
padding-left: 2.75rem; padding-left: 2.75rem;

View File

@ -110,10 +110,27 @@ defmodule Berrypod.Pages.BlockTypes do
}, },
"newsletter_card" => %{ "newsletter_card" => %{
name: "Newsletter signup", name: "Newsletter signup",
description: "Email signup form for collecting subscriber addresses", description: "Email signup form — currently decorative, does not collect emails",
icon: "hero-envelope", icon: "hero-envelope",
allowed_on: :all, allowed_on: :all,
settings_schema: [] hint:
"This block is decorative — form submissions aren't collected yet. Use it as a placeholder or remove it.",
settings_schema: [
%SettingsField{key: "title", label: "Title", type: :text, default: "Newsletter"},
%SettingsField{
key: "description",
label: "Description",
type: :textarea,
default:
"Sample newsletter signup. Replace with your own message to encourage subscribers."
},
%SettingsField{
key: "button_text",
label: "Button text",
type: :text,
default: "Subscribe"
}
]
}, },
"social_links_card" => %{ "social_links_card" => %{
name: "Social links", name: "Social links",

View File

@ -15,6 +15,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
import BerrypodWeb.CoreComponents, only: [icon: 1] import BerrypodWeb.CoreComponents, only: [icon: 1]
alias Berrypod.Pages.{BlockEditor, BlockTypes} alias Berrypod.Pages.{BlockEditor, BlockTypes}
alias BerrypodWeb.BlockThumbnails
# ── Block card ───────────────────────────────────────────────── # ── Block card ─────────────────────────────────────────────────
@ -28,12 +29,14 @@ defmodule BerrypodWeb.BlockEditorComponents do
block_type = BlockTypes.get(assigns.block["type"]) block_type = BlockTypes.get(assigns.block["type"])
has_settings = BlockEditor.has_settings?(assigns.block) has_settings = BlockEditor.has_settings?(assigns.block)
expanded = MapSet.member?(assigns.expanded, assigns.block["id"]) expanded = MapSet.member?(assigns.expanded, assigns.block["id"])
preview = block_preview(assigns.block, block_type)
assigns = assigns =
assigns assigns
|> assign(:block_type, block_type) |> assign(:block_type, block_type)
|> assign(:has_settings, has_settings) |> assign(:has_settings, has_settings)
|> assign(:is_expanded, expanded) |> assign(:is_expanded, expanded)
|> assign(:preview, preview)
~H""" ~H"""
<div <div
@ -49,9 +52,12 @@ defmodule BerrypodWeb.BlockEditorComponents do
<.icon name={(@block_type && @block_type.icon) || "hero-puzzle-piece"} class="size-5" /> <.icon name={(@block_type && @block_type.icon) || "hero-puzzle-piece"} class="size-5" />
</span> </span>
<span class="block-card-name"> <div class="block-card-info">
{(@block_type && @block_type.name) || @block["type"]} <span class="block-card-name">
</span> {(@block_type && @block_type.name) || @block["type"]}
</span>
<span :if={@preview} class="block-card-preview">{@preview}</span>
</div>
<span class="block-card-controls"> <span class="block-card-controls">
<button <button
@ -111,6 +117,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
:if={@is_expanded} :if={@is_expanded}
block={@block} block={@block}
schema={@block_type.settings_schema} schema={@block_type.settings_schema}
hint={@block_type[:hint]}
event_prefix={@event_prefix} event_prefix={@event_prefix}
/> />
</div> </div>
@ -121,6 +128,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
attr :block, :map, required: true attr :block, :map, required: true
attr :schema, :list, required: true attr :schema, :list, required: true
attr :hint, :string, default: nil
attr :event_prefix, :string, default: "" attr :event_prefix, :string, default: ""
def block_settings_form(assigns) do def block_settings_form(assigns) do
@ -129,6 +137,9 @@ defmodule BerrypodWeb.BlockEditorComponents do
~H""" ~H"""
<div class="block-card-settings" id={"block-settings-#{@block["id"]}"}> <div class="block-card-settings" id={"block-settings-#{@block["id"]}"}>
<p :if={@hint} class="block-settings-hint">
<.icon name="hero-information-circle-mini" class="size-4" /> {@hint}
</p>
<form phx-change={"#{@event_prefix}update_block_settings"}> <form phx-change={"#{@event_prefix}update_block_settings"}>
<input type="hidden" name="block_id" value={@block["id"]} /> <input type="hidden" name="block_id" value={@block["id"]} />
@ -454,6 +465,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
phx-value-type={type} phx-value-type={type}
class="block-picker-item" class="block-picker-item"
> >
<BlockThumbnails.block_thumbnail type={type} />
<.icon name={def.icon} class="size-5" /> <.icon name={def.icon} class="size-5" />
<span class="block-picker-item-name">{def.name}</span> <span class="block-picker-item-name">{def.name}</span>
<span :if={def[:description]} class="block-picker-item-desc"> <span :if={def[:description]} class="block-picker-item-desc">
@ -564,4 +576,69 @@ defmodule BerrypodWeb.BlockEditorComponents do
</div> </div>
""" """
end end
# ── Block preview ──────────────────────────────────────────────
# Extracts a short one-line preview from block settings for display in the card
defp block_preview(_block, nil), do: nil
defp block_preview(block, block_type) do
settings = block["settings"] || %{}
schema = block_type.settings_schema
# Try text/textarea fields first, then selects, then repeaters
find_text_preview(settings, schema) ||
find_select_preview(settings, schema) ||
find_repeater_preview(settings, schema)
end
defp find_text_preview(settings, schema) do
schema
|> Enum.filter(&(&1.type in [:text, :textarea]))
|> Enum.find_value(fn field ->
case settings[field.key] do
val when is_binary(val) and val != "" ->
truncate(val, 60)
_ ->
nil
end
end)
end
defp find_select_preview(settings, schema) do
schema
|> Enum.filter(&(&1.type == :select))
|> Enum.find_value(fn field ->
case settings[field.key] do
val when is_binary(val) and val != "" -> "#{field.label}: #{val}"
_ -> nil
end
end)
end
defp find_repeater_preview(settings, schema) do
schema
|> Enum.filter(&(&1.type == :repeater))
|> Enum.find_value(fn field ->
case settings[field.key] do
items when is_list(items) and items != [] ->
count = length(items)
"#{count} #{if count == 1, do: "item", else: "items"}"
_ ->
nil
end
end)
end
defp truncate(str, max) do
str = String.replace(str, ~r/\s+/, " ") |> String.trim()
if String.length(str) > max do
String.slice(str, 0, max) <> "..."
else
str
end
end
end end

View File

@ -0,0 +1,478 @@
defmodule BerrypodWeb.BlockThumbnails do
@moduledoc """
Tiny SVG wireframe thumbnails for each block type.
Used in the block picker to give a visual hint of what each block looks like.
Each thumbnail is an 80x48 SVG with simple geometric shapes.
"""
use Phoenix.Component
attr :type, :string, required: true
def block_thumbnail(assigns) do
~H"""
<svg
viewBox="0 0 80 48"
class="block-picker-thumb"
role="img"
aria-hidden="true"
>
<.thumb_shapes type={@type} />
</svg>
"""
end
# ── Hero banner ──────────────────────────────────────────────────
defp thumb_shapes(%{type: "hero"} = assigns) do
~H"""
<rect x="0" y="0" width="80" height="48" rx="2" fill="currentColor" opacity="0.08" />
<rect x="20" y="10" width="40" height="4" rx="1" fill="currentColor" opacity="0.35" />
<rect x="24" y="18" width="32" height="3" rx="1" fill="currentColor" opacity="0.2" />
<rect x="28" y="30" width="24" height="7" rx="2" fill="currentColor" opacity="0.25" />
"""
end
# ── Featured products ────────────────────────────────────────────
defp thumb_shapes(%{type: "featured_products"} = assigns) do
~H"""
<rect x="2" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
<rect x="22" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
<rect x="42" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
<rect x="62" y="2" width="17" height="20" rx="2" fill="currentColor" opacity="0.12" />
<rect x="4" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
<rect x="24" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
<rect x="44" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
<rect x="64" y="25" width="13" height="2" rx="1" fill="currentColor" opacity="0.25" />
<rect x="4" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="24" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="44" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="64" y="30" width="8" height="2" rx="1" fill="currentColor" opacity="0.15" />
"""
end
# ── Image + text ─────────────────────────────────────────────────
defp thumb_shapes(%{type: "image_text"} = assigns) do
~H"""
<rect x="2" y="4" width="34" height="40" rx="2" fill="currentColor" opacity="0.12" />
<rect x="42" y="8" width="32" height="4" rx="1" fill="currentColor" opacity="0.3" />
<rect x="42" y="16" width="28" height="3" rx="1" fill="currentColor" opacity="0.15" />
<rect x="42" y="22" width="30" height="3" rx="1" fill="currentColor" opacity="0.15" />
<rect x="42" y="28" width="20" height="3" rx="1" fill="currentColor" opacity="0.15" />
<rect x="42" y="36" width="16" height="5" rx="2" fill="currentColor" opacity="0.2" />
"""
end
# ── Category navigation ──────────────────────────────────────────
defp thumb_shapes(%{type: "category_nav"} = assigns) do
~H"""
<rect x="4" y="18" width="16" height="12" rx="6" fill="currentColor" opacity="0.15" />
<rect x="23" y="18" width="18" height="12" rx="6" fill="currentColor" opacity="0.15" />
<rect x="44" y="18" width="14" height="12" rx="6" fill="currentColor" opacity="0.15" />
<rect x="61" y="18" width="16" height="12" rx="6" fill="currentColor" opacity="0.15" />
"""
end
# ── Newsletter signup ────────────────────────────────────────────
defp thumb_shapes(%{type: "newsletter_card"} = assigns) do
~H"""
<rect
x="4"
y="4"
width="72"
height="40"
rx="3"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.12"
stroke-width="1"
/>
<rect x="20" y="10" width="40" height="4" rx="1" fill="currentColor" opacity="0.3" />
<rect x="24" y="18" width="32" height="3" rx="1" fill="currentColor" opacity="0.15" />
<rect
x="14"
y="28"
width="36"
height="8"
rx="2"
fill="currentColor"
opacity="0.1"
stroke="currentColor"
stroke-opacity="0.15"
stroke-width="0.5"
/>
<rect x="52" y="28" width="16" height="8" rx="2" fill="currentColor" opacity="0.2" />
"""
end
# ── Social links ─────────────────────────────────────────────────
defp thumb_shapes(%{type: "social_links_card"} = assigns) do
~H"""
<rect
x="4"
y="4"
width="72"
height="40"
rx="3"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.12"
stroke-width="1"
/>
<rect x="20" y="10" width="40" height="4" rx="1" fill="currentColor" opacity="0.25" />
<circle cx="24" cy="30" r="5" fill="currentColor" opacity="0.15" />
<circle cx="36" cy="30" r="5" fill="currentColor" opacity="0.15" />
<circle cx="48" cy="30" r="5" fill="currentColor" opacity="0.15" />
<circle cx="60" cy="30" r="5" fill="currentColor" opacity="0.15" />
"""
end
# ── Info card ────────────────────────────────────────────────────
defp thumb_shapes(%{type: "info_card"} = assigns) do
~H"""
<rect
x="4"
y="4"
width="72"
height="40"
rx="3"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.12"
stroke-width="1"
/>
<rect x="10" y="10" width="20" height="3" rx="1" fill="currentColor" opacity="0.25" />
<rect x="10" y="18" width="14" height="2" rx="1" fill="currentColor" opacity="0.2" />
<rect x="40" y="18" width="28" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="10" y="24" width="14" height="2" rx="1" fill="currentColor" opacity="0.2" />
<rect x="40" y="24" width="22" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="10" y="30" width="14" height="2" rx="1" fill="currentColor" opacity="0.2" />
<rect x="40" y="30" width="26" height="2" rx="1" fill="currentColor" opacity="0.12" />
"""
end
# ── Trust badges ─────────────────────────────────────────────────
defp thumb_shapes(%{type: "trust_badges"} = assigns) do
~H"""
<circle cx="16" cy="18" r="7" fill="currentColor" opacity="0.1" />
<circle cx="40" cy="18" r="7" fill="currentColor" opacity="0.1" />
<circle cx="64" cy="18" r="7" fill="currentColor" opacity="0.1" />
<rect x="8" y="30" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="32" y="30" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="56" y="30" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
"""
end
# ── Customer reviews ─────────────────────────────────────────────
defp thumb_shapes(%{type: "reviews_section"} = assigns) do
~H"""
<rect
x="2"
y="2"
width="24"
height="44"
rx="2"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect
x="28"
y="2"
width="24"
height="44"
rx="2"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect
x="54"
y="2"
width="24"
height="44"
rx="2"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect x="6" y="8" width="16" height="2" rx="1" fill="currentColor" opacity="0.25" />
<rect x="32" y="8" width="16" height="2" rx="1" fill="currentColor" opacity="0.25" />
<rect x="58" y="8" width="16" height="2" rx="1" fill="currentColor" opacity="0.25" />
<rect x="6" y="14" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="32" y="14" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="58" y="14" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
"""
end
# ── Spacer ───────────────────────────────────────────────────────
defp thumb_shapes(%{type: "spacer"} = assigns) do
~H"""
<line
x1="40"
y1="8"
x2="40"
y2="16"
stroke="currentColor"
stroke-opacity="0.2"
stroke-width="1"
stroke-dasharray="2 2"
/>
<rect
x="30"
y="18"
width="20"
height="12"
rx="1"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.15"
stroke-width="0.5"
stroke-dasharray="2 2"
/>
<line
x1="40"
y1="32"
x2="40"
y2="40"
stroke="currentColor"
stroke-opacity="0.2"
stroke-width="1"
stroke-dasharray="2 2"
/>
"""
end
# ── Divider ──────────────────────────────────────────────────────
defp thumb_shapes(%{type: "divider"} = assigns) do
~H"""
<line
x1="8"
y1="24"
x2="72"
y2="24"
stroke="currentColor"
stroke-opacity="0.25"
stroke-width="1.5"
/>
"""
end
# ── Button ───────────────────────────────────────────────────────
defp thumb_shapes(%{type: "button"} = assigns) do
~H"""
<rect x="22" y="16" width="36" height="16" rx="4" fill="currentColor" opacity="0.2" />
<rect x="28" y="22" width="24" height="4" rx="1" fill="currentColor" opacity="0.3" />
"""
end
# ── Video embed ──────────────────────────────────────────────────
defp thumb_shapes(%{type: "video_embed"} = assigns) do
~H"""
<rect x="4" y="4" width="72" height="40" rx="2" fill="currentColor" opacity="0.08" />
<polygon points="34,18 34,32 48,25" fill="currentColor" opacity="0.25" />
"""
end
# ── Product hero ─────────────────────────────────────────────────
defp thumb_shapes(%{type: "product_hero"} = assigns) do
~H"""
<rect x="2" y="4" width="38" height="40" rx="2" fill="currentColor" opacity="0.1" />
<rect x="46" y="6" width="28" height="4" rx="1" fill="currentColor" opacity="0.3" />
<rect x="46" y="14" width="16" height="3" rx="1" fill="currentColor" opacity="0.2" />
<rect x="46" y="24" width="28" height="8" rx="2" fill="currentColor" opacity="0.2" />
"""
end
# ── Breadcrumb ───────────────────────────────────────────────────
defp thumb_shapes(%{type: "breadcrumb"} = assigns) do
~H"""
<rect x="6" y="20" width="14" height="3" rx="1" fill="currentColor" opacity="0.2" />
<rect x="24" y="21" width="4" height="1.5" rx="0.5" fill="currentColor" opacity="0.15" />
<rect x="32" y="20" width="18" height="3" rx="1" fill="currentColor" opacity="0.2" />
<rect x="54" y="21" width="4" height="1.5" rx="0.5" fill="currentColor" opacity="0.15" />
<rect x="62" y="20" width="12" height="3" rx="1" fill="currentColor" opacity="0.3" />
"""
end
# ── Product grid ─────────────────────────────────────────────────
defp thumb_shapes(%{type: t} = assigns) when t in ~w(product_grid related_products) do
~H"""
<rect x="2" y="2" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
<rect x="28" y="2" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
<rect x="54" y="2" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
<rect x="2" y="26" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
<rect x="28" y="26" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
<rect x="54" y="26" width="24" height="20" rx="2" fill="currentColor" opacity="0.1" />
"""
end
# ── Collection header ────────────────────────────────────────────
defp thumb_shapes(%{type: "collection_header"} = assigns) do
~H"""
<rect x="16" y="14" width="48" height="6" rx="1" fill="currentColor" opacity="0.3" />
<rect x="28" y="26" width="24" height="3" rx="1" fill="currentColor" opacity="0.15" />
"""
end
# ── Filter bar ───────────────────────────────────────────────────
defp thumb_shapes(%{type: "filter_bar"} = assigns) do
~H"""
<rect x="4" y="18" width="14" height="12" rx="6" fill="currentColor" opacity="0.2" />
<rect x="21" y="18" width="16" height="12" rx="6" fill="currentColor" opacity="0.1" />
<rect x="40" y="18" width="12" height="12" rx="6" fill="currentColor" opacity="0.1" />
<rect
x="58"
y="18"
width="18"
height="12"
rx="2"
fill="currentColor"
opacity="0.08"
stroke="currentColor"
stroke-opacity="0.15"
stroke-width="0.5"
/>
"""
end
# ── Cart items ───────────────────────────────────────────────────
defp thumb_shapes(%{type: "cart_items"} = assigns) do
~H"""
<rect x="4" y="4" width="12" height="12" rx="2" fill="currentColor" opacity="0.12" />
<rect x="20" y="6" width="30" height="3" rx="1" fill="currentColor" opacity="0.2" />
<rect x="20" y="12" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
<line
x1="4"
y1="20"
x2="76"
y2="20"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect x="4" y="24" width="12" height="12" rx="2" fill="currentColor" opacity="0.12" />
<rect x="20" y="26" width="24" height="3" rx="1" fill="currentColor" opacity="0.2" />
<rect x="20" y="32" width="16" height="2" rx="1" fill="currentColor" opacity="0.12" />
"""
end
# ── Order summary ────────────────────────────────────────────────
defp thumb_shapes(%{type: "order_summary"} = assigns) do
~H"""
<rect
x="4"
y="4"
width="72"
height="40"
rx="3"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect x="10" y="12" width="20" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="50" y="12" width="20" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="10" y="18" width="20" height="2" rx="1" fill="currentColor" opacity="0.15" />
<rect x="50" y="18" width="16" height="2" rx="1" fill="currentColor" opacity="0.15" />
<line
x1="10"
y1="24"
x2="70"
y2="24"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect x="10" y="28" width="14" height="3" rx="1" fill="currentColor" opacity="0.25" />
<rect x="50" y="28" width="20" height="3" rx="1" fill="currentColor" opacity="0.25" />
<rect x="20" y="36" width="40" height="6" rx="2" fill="currentColor" opacity="0.2" />
"""
end
# ── Contact form ─────────────────────────────────────────────────
defp thumb_shapes(%{type: "contact_form"} = assigns) do
~H"""
<rect
x="8"
y="4"
width="64"
height="8"
rx="2"
fill="currentColor"
opacity="0.08"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect
x="8"
y="16"
width="64"
height="8"
rx="2"
fill="currentColor"
opacity="0.08"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect
x="8"
y="28"
width="64"
height="12"
rx="2"
fill="currentColor"
opacity="0.08"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect x="8" y="43" width="20" height="5" rx="2" fill="currentColor" opacity="0.2" />
"""
end
# ── Content body ─────────────────────────────────────────────────
defp thumb_shapes(%{type: "content_body"} = assigns) do
~H"""
<rect x="4" y="6" width="50" height="4" rx="1" fill="currentColor" opacity="0.25" />
<rect x="4" y="14" width="72" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="4" y="20" width="68" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="4" y="26" width="72" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="4" y="32" width="60" height="2" rx="1" fill="currentColor" opacity="0.12" />
<rect x="4" y="38" width="66" height="2" rx="1" fill="currentColor" opacity="0.12" />
"""
end
# ── Fallback for any unmatched type ──────────────────────────────
defp thumb_shapes(assigns) do
~H"""
<rect
x="4"
y="4"
width="72"
height="40"
rx="3"
fill="currentColor"
opacity="0.06"
stroke="currentColor"
stroke-opacity="0.1"
stroke-width="0.5"
/>
<rect x="20" y="16" width="40" height="4" rx="1" fill="currentColor" opacity="0.2" />
<rect x="26" y="24" width="28" height="3" rx="1" fill="currentColor" opacity="0.12" />
"""
end
end

View File

@ -314,7 +314,7 @@ defmodule BerrypodWeb.ShopComponents.Content do
<p class="card-text card-text--spaced"> <p class="card-text card-text--spaced">
{@description} {@description}
</p> </p>
<form class="card-inline-form"> <form class="card-inline-form" onsubmit="return false">
<.shop_input type="email" placeholder="your@email.com" class="email-input" /> <.shop_input type="email" placeholder="your@email.com" class="email-input" />
<.shop_button type="submit">{@button_text}</.shop_button> <.shop_button type="submit">{@button_text}</.shop_button>
</form> </form>
@ -329,7 +329,7 @@ defmodule BerrypodWeb.ShopComponents.Content do
<p class="card-text card-text--spaced"> <p class="card-text card-text--spaced">
{@description} {@description}
</p> </p>
<form class="card-inline-form"> <form class="card-inline-form" onsubmit="return false">
<.shop_input type="email" placeholder="your@email.com" class="email-input" /> <.shop_input type="email" placeholder="your@email.com" class="email-input" />
<.shop_button type="submit">{@button_text}</.shop_button> <.shop_button type="submit">{@button_text}</.shop_button>
</form> </form>

View File

@ -303,7 +303,15 @@ defmodule BerrypodWeb.PageRenderer do
end end
defp render_block(%{block: %{"type" => "newsletter_card"}} = assigns) do defp render_block(%{block: %{"type" => "newsletter_card"}} = assigns) do
~H"<.newsletter_card />" settings = assigns.block["settings"] || %{}
assigns =
assigns
|> assign(:title, settings["title"] || "Newsletter")
|> assign(:description, settings["description"] || "")
|> assign(:button_text, settings["button_text"] || "Subscribe")
~H"<.newsletter_card title={@title} description={@description} button_text={@button_text} />"
end end
defp render_block(%{block: %{"type" => "social_links_card"}} = assigns) do defp render_block(%{block: %{"type" => "social_links_card"}} = assigns) do