fix content image double-suffix, clean up page defaults and editor UX
All checks were successful
deploy / deploy (push) Successful in 1m22s
All checks were successful
deploy / deploy (push) Successful in 1m22s
- Fix resolve_content_image returning base path (not full URL) so responsive_image doesn't double-append width/extension - Remove legacy image fields (image_src, image_alt, image_url) from block settings schemas - Remove demo/mockup fallbacks from renderer and defaults — blank fields stay blank instead of showing preview content - Replace demo text in defaults with instructional placeholders that guide new shop owners - Remove redundant X button from editor sidebar, add unsaved-changes confirmation to Done button - Fix block card name overflow on mobile (display: block, flex-wrap) - Add onboarding UX improvement plan (10 tasks) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
61772c26ae
commit
8ea77e5992
16
PROGRESS.md
16
PROGRESS.md
@ -51,6 +51,21 @@ All 4 phases done. Tailwind utility clone deleted, all templates migrated to sem
|
|||||||
| 70 | Margin guard on sales (prevent discounts that breach minimum profit threshold) | 69 | 1h | planned |
|
| 70 | Margin guard on sales (prevent discounts that breach minimum profit threshold) | 69 | 1h | planned |
|
||||||
| 71 | Announcement bar (dismissable shop banner for active sales) | 69 | 1.5h | planned |
|
| 71 | Announcement bar (dismissable shop banner for active sales) | 69 | 1.5h | planned |
|
||||||
|
|
||||||
|
### Onboarding UX improvements ([plan](docs/plans/onboarding-ux.md))
|
||||||
|
|
||||||
|
| # | Task | Priority | Est | Status |
|
||||||
|
|---|------|----------|-----|--------|
|
||||||
|
| 1 | Redirect to dashboard after wizard completion with welcome flash | High | 30m | planned |
|
||||||
|
| 2 | Add shipping setup to checklist, gate "Go live" on shipping existing | High | 1h | planned |
|
||||||
|
| 3 | Add shop settings to checklist (name, currency) | High | 45m | planned |
|
||||||
|
| 4 | Make checklist collapsible instead of dismissable | Medium | 15m | planned |
|
||||||
|
| 5 | Add test order guidance (test card number, what to expect) | Medium | 45m | planned |
|
||||||
|
| 6 | Skip completed wizard steps on revisit | Medium | 1h | planned |
|
||||||
|
| 7 | Better provider connection error messages with help links | Medium | 30m | planned |
|
||||||
|
| 8 | Add theme customisation tips to checklist | Low | 15m | planned |
|
||||||
|
| 9 | Smarter "Sync products" checklist link | Low | 15m | planned |
|
||||||
|
| 10 | Add email setup as optional checklist item | Low | 30m | planned |
|
||||||
|
|
||||||
### Platform site
|
### Platform site
|
||||||
|
|
||||||
| # | Task | Depends on | Est | Status |
|
| # | Task | Depends on | Est | Status |
|
||||||
@ -93,4 +108,5 @@ All plans in [docs/plans/](docs/plans/). Completed plans are kept as architectur
|
|||||||
| [favicon.md](docs/plans/favicon.md) | Complete |
|
| [favicon.md](docs/plans/favicon.md) | Complete |
|
||||||
| [legal-page-generator.md](docs/plans/legal-page-generator.md) | Complete |
|
| [legal-page-generator.md](docs/plans/legal-page-generator.md) | Complete |
|
||||||
| [url-redirects.md](docs/plans/url-redirects.md) | Complete |
|
| [url-redirects.md](docs/plans/url-redirects.md) | Complete |
|
||||||
|
| [onboarding-ux.md](docs/plans/onboarding-ux.md) | Planned |
|
||||||
| [profit-aware-pricing.md](docs/plans/profit-aware-pricing.md) | Planned |
|
| [profit-aware-pricing.md](docs/plans/profit-aware-pricing.md) | Planned |
|
||||||
|
|||||||
@ -1958,10 +1958,11 @@
|
|||||||
|
|
||||||
.block-card-info {
|
.block-card-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-card-name {
|
.block-card-name {
|
||||||
|
display: block;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -1983,6 +1984,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.125rem;
|
gap: 0.125rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-remove-btn {
|
.block-remove-btn {
|
||||||
@ -2119,7 +2121,8 @@
|
|||||||
.block-card-header {
|
.block-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.25rem 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
docs/plans/onboarding-ux.md
Normal file
109
docs/plans/onboarding-ux.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# Onboarding UX improvements
|
||||||
|
|
||||||
|
Status: Planned
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The setup wizard and dashboard launch checklist work, but there are gaps that leave new users guessing. Goal: make onboarding totally fool-proof and guided so a non-technical seller can go from zero to live store without confusion.
|
||||||
|
|
||||||
|
## Current flow
|
||||||
|
|
||||||
|
1. **Setup wizard** (`lib/berrypod_web/live/setup/onboarding.ex`): 3 cards — admin account → provider API key → Stripe connection. Gated by secret in prod, auto-login after account creation.
|
||||||
|
2. **Dashboard checklist** (`lib/berrypod_web/live/admin/dashboard.ex`): 5 items with progress bar — sync products, connect Stripe, customise theme, place test order, go live. Visible when `site_live` is false and not dismissed.
|
||||||
|
3. **Coming soon page**: blocks public access before launch.
|
||||||
|
4. **Setup status** (`lib/berrypod/setup.ex`): `setup_status/0` returns booleans for each milestone.
|
||||||
|
|
||||||
|
## Gaps identified
|
||||||
|
|
||||||
|
### High priority
|
||||||
|
|
||||||
|
#### 1. Dead end after setup wizard
|
||||||
|
The wizard ends at Stripe connection with no redirect or next-steps message. User has to figure out where to go.
|
||||||
|
|
||||||
|
**Fix:** After completing the final wizard step, redirect to `/admin` with a flash message like "You're in! Here's your launch checklist." (~30m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/setup/onboarding.ex`
|
||||||
|
|
||||||
|
#### 2. No shipping rate setup in checklist
|
||||||
|
Checkout requires shipping rates. There's no checklist item for configuring shipping, and no warning that checkout will fail without it. A user could "Go live" with no shipping rates.
|
||||||
|
|
||||||
|
**Fix:** Add "Set up shipping" checklist item that links to `/admin/settings` (shipping section). Gate "Go live" on at least one shipping profile existing. (~1h)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/admin/dashboard.ex`, `lib/berrypod/setup.ex`
|
||||||
|
|
||||||
|
#### 3. No shop settings in checklist
|
||||||
|
Shop name, currency, country are critical but not in the checklist. User could go live with "My Shop" as the name.
|
||||||
|
|
||||||
|
**Fix:** Add "Configure your shop" as the first checklist item, linking to `/admin/settings`. Mark complete when shop name differs from default. (~45m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/admin/dashboard.ex`, `lib/berrypod/setup.ex`
|
||||||
|
|
||||||
|
### Medium priority
|
||||||
|
|
||||||
|
#### 4. Checklist dismissable and gone forever
|
||||||
|
Once dismissed, no way to get it back. Stored as `checklist_dismissed` setting.
|
||||||
|
|
||||||
|
**Fix:** Replace dismiss with a collapse/expand toggle. Or add a "Show launch checklist" link in the dashboard sidebar when dismissed but site isn't live yet. (~15m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/admin/dashboard.ex`, possibly `lib/berrypod_web/components/layouts/admin.html.heex`
|
||||||
|
|
||||||
|
#### 5. "Place a test order" has zero guidance
|
||||||
|
Links to the shop home page. No mention of Stripe test mode, test card numbers, or what a successful test looks like.
|
||||||
|
|
||||||
|
**Fix:** Add inline help text under the checklist item: "Use card number 4242 4242 4242 4242 with any future expiry and CVC. You'll see the order appear in Orders when it works." (~45m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/admin/dashboard.ex`
|
||||||
|
|
||||||
|
#### 6. Skip completed wizard steps on revisit
|
||||||
|
If a user closes their browser mid-setup and comes back, the wizard restarts from scratch (session-based). Should detect existing state and skip completed steps.
|
||||||
|
|
||||||
|
**Fix:** In `mount/3`, call `Setup.setup_status/0` and set `current_step` to the first incomplete step. (~1h)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/setup/onboarding.ex`
|
||||||
|
|
||||||
|
#### 7. Better provider connection error messages
|
||||||
|
If the API key is wrong, the error is generic. No link to where to find the key, no troubleshooting.
|
||||||
|
|
||||||
|
**Fix:** Add provider-specific help text: "Find your Printify API key at Printify → Settings → Connections" (with link). Show the actual API error when it's user-facing (auth failure vs server error). (~30m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/setup/onboarding.ex`, `lib/berrypod_web/live/admin/providers.ex`
|
||||||
|
|
||||||
|
### Low priority
|
||||||
|
|
||||||
|
#### 8. Guide theme customisation
|
||||||
|
`theme_customised` is true if any setting differs from defaults. Changing one colour counts. No guidance on what to customise.
|
||||||
|
|
||||||
|
**Fix:** Add a brief tip under the checklist item: "Upload your logo, pick your colours, and choose a font that matches your brand." (~15m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/admin/dashboard.ex`
|
||||||
|
|
||||||
|
#### 9. Clarify "Sync your products" checklist step
|
||||||
|
Links to `/admin/providers` but user needs to click "Sync" on an already-connected provider. Confusing if they connected during the wizard.
|
||||||
|
|
||||||
|
**Fix:** If provider is connected but no products synced, link directly to the provider detail page with a prompt to sync. If products exist, mark as complete. (~15m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/admin/dashboard.ex`
|
||||||
|
|
||||||
|
#### 10. Move email setup into checklist as optional step
|
||||||
|
The orange "email not configured" banner appears on every page but isn't in the checklist. Email is needed for abandoned cart, order confirmations, contact form.
|
||||||
|
|
||||||
|
**Fix:** Add as a non-blocking checklist item (doesn't gate "Go live" but shows as recommended). (~30m)
|
||||||
|
|
||||||
|
**Files:** `lib/berrypod_web/live/admin/dashboard.ex`, `lib/berrypod/setup.ex`
|
||||||
|
|
||||||
|
## Task breakdown
|
||||||
|
|
||||||
|
| # | Task | Priority | Est | Status |
|
||||||
|
|---|------|----------|-----|--------|
|
||||||
|
| 1 | Redirect to dashboard after wizard completion with welcome flash | High | 30m | planned |
|
||||||
|
| 2 | Add shipping setup to checklist, gate "Go live" on shipping existing | High | 1h | planned |
|
||||||
|
| 3 | Add shop settings to checklist (name, currency) | High | 45m | planned |
|
||||||
|
| 4 | Make checklist collapsible instead of dismissable | Medium | 15m | planned |
|
||||||
|
| 5 | Add test order guidance (test card number, what to expect) | Medium | 45m | planned |
|
||||||
|
| 6 | Skip completed wizard steps on revisit | Medium | 1h | planned |
|
||||||
|
| 7 | Better provider connection error messages with help links | Medium | 30m | planned |
|
||||||
|
| 8 | Add theme customisation tips to checklist | Low | 15m | planned |
|
||||||
|
| 9 | Smarter "Sync products" checklist link | Low | 15m | planned |
|
||||||
|
| 10 | Add email setup as optional checklist item | Low | 30m | planned |
|
||||||
|
|
||||||
|
Total estimate: ~5.5h
|
||||||
@ -96,7 +96,6 @@ defmodule Berrypod.Pages.BlockTypes do
|
|||||||
%SettingsField{key: "title", label: "Title", type: :text, default: ""},
|
%SettingsField{key: "title", label: "Title", type: :text, default: ""},
|
||||||
%SettingsField{key: "description", label: "Description", type: :textarea, default: ""},
|
%SettingsField{key: "description", label: "Description", type: :textarea, default: ""},
|
||||||
%SettingsField{key: "image_id", label: "Image", type: :image, default: nil},
|
%SettingsField{key: "image_id", label: "Image", type: :image, default: nil},
|
||||||
%SettingsField{key: "image_url", label: "Image URL (legacy)", type: :text, default: ""},
|
|
||||||
%SettingsField{key: "link_text", label: "Link text", type: :text, default: ""},
|
%SettingsField{key: "link_text", label: "Link text", type: :text, default: ""},
|
||||||
%SettingsField{key: "link_href", label: "Link URL", type: :text, default: ""}
|
%SettingsField{key: "link_href", label: "Link URL", type: :text, default: ""}
|
||||||
]
|
]
|
||||||
@ -344,14 +343,7 @@ defmodule Berrypod.Pages.BlockTypes do
|
|||||||
allowed_on: :all,
|
allowed_on: :all,
|
||||||
settings_schema: [
|
settings_schema: [
|
||||||
%SettingsField{key: "content", label: "Content", type: :textarea, default: ""},
|
%SettingsField{key: "content", label: "Content", type: :textarea, default: ""},
|
||||||
%SettingsField{key: "image_id", label: "Image", type: :image, default: nil},
|
%SettingsField{key: "image_id", label: "Image", type: :image, default: nil}
|
||||||
%SettingsField{key: "image_src", label: "Image URL (legacy)", type: :text, default: ""},
|
|
||||||
%SettingsField{
|
|
||||||
key: "image_alt",
|
|
||||||
label: "Image alt text (legacy)",
|
|
||||||
type: :text,
|
|
||||||
default: ""
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -42,9 +42,9 @@ defmodule Berrypod.Pages.Defaults do
|
|||||||
defp blocks("home") do
|
defp blocks("home") do
|
||||||
[
|
[
|
||||||
block("hero", %{
|
block("hero", %{
|
||||||
"title" => "Original designs, printed on demand",
|
"title" => "Your headline goes here",
|
||||||
"description" =>
|
"description" =>
|
||||||
"Welcome to the Berrypod demo store. This is where your hero text goes \u2013 something short and punchy about what makes your shop worth a browse.",
|
"Write something short and punchy about your shop \u2013 what you sell and why it's worth a look.",
|
||||||
"cta_text" => "Shop the collection",
|
"cta_text" => "Shop the collection",
|
||||||
"cta_href" => "/collections/all",
|
"cta_href" => "/collections/all",
|
||||||
"variant" => "default"
|
"variant" => "default"
|
||||||
@ -55,11 +55,10 @@ defmodule Berrypod.Pages.Defaults do
|
|||||||
"product_count" => 8
|
"product_count" => 8
|
||||||
}),
|
}),
|
||||||
block("image_text", %{
|
block("image_text", %{
|
||||||
"title" => "Made with passion, printed with care",
|
"title" => "Your story in a nutshell",
|
||||||
"description" =>
|
"description" =>
|
||||||
"This is an example content section. Use it to share your story, highlight what makes your products special, or link to your about page.",
|
"Use this section to share what makes your products special, your creative process, or a bit about you. Pick an image from your media library to go alongside it.",
|
||||||
"image_url" => "/mockups/mountain-sunrise-print-3-800.webp",
|
"link_text" => "Read more about us \u2192",
|
||||||
"link_text" => "Learn more about the studio \u2192",
|
|
||||||
"link_href" => "/about"
|
"link_href" => "/about"
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
@ -68,13 +67,14 @@ defmodule Berrypod.Pages.Defaults do
|
|||||||
defp blocks("about") do
|
defp blocks("about") do
|
||||||
[
|
[
|
||||||
block("hero", %{
|
block("hero", %{
|
||||||
"title" => "About the studio",
|
"title" => "About us",
|
||||||
"description" => "Your story goes here \u2013 this is sample content for the demo shop",
|
"description" => "Share a bit about who you are and what you do",
|
||||||
"variant" => "sunken"
|
"variant" => "sunken"
|
||||||
}),
|
}),
|
||||||
block("content_body", %{
|
block("content_body", %{
|
||||||
"image_src" => "/mockups/night-sky-blanket-3",
|
"content" =>
|
||||||
"image_alt" => "Night sky blanket draped over a chair"
|
"Tell your customers who you are and what inspired your shop. What's the story behind your designs?\n\n" <>
|
||||||
|
"This is placeholder text \u2013 edit it to make it your own. You can also add an image from your media library using the block settings."
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@ -116,8 +116,7 @@ defmodule Berrypod.Pages.Defaults do
|
|||||||
[
|
[
|
||||||
block("hero", %{
|
block("hero", %{
|
||||||
"title" => "Get in touch",
|
"title" => "Get in touch",
|
||||||
"description" =>
|
"description" => "Add a friendly message about how customers can reach you",
|
||||||
"Sample contact page for the demo store. Add your own message here \u2013 something friendly about how customers can reach you.",
|
|
||||||
"variant" => "page"
|
"variant" => "page"
|
||||||
}),
|
}),
|
||||||
block("contact_form", %{"email" => "hello@example.com"}),
|
block("contact_form", %{"email" => "hello@example.com"}),
|
||||||
@ -125,9 +124,9 @@ defmodule Berrypod.Pages.Defaults do
|
|||||||
block("info_card", %{
|
block("info_card", %{
|
||||||
"title" => "Handy to know",
|
"title" => "Handy to know",
|
||||||
"items" => [
|
"items" => [
|
||||||
%{"label" => "Printing", "value" => "Example: 2-5 business days"},
|
%{"label" => "Printing", "value" => "Update with your printing times"},
|
||||||
%{"label" => "Delivery", "value" => "Example: 3-7 business days after printing"},
|
%{"label" => "Delivery", "value" => "Update with your delivery times"},
|
||||||
%{"label" => "Issues", "value" => "Example: Reprints for any defects"}
|
%{"label" => "Issues", "value" => "Update with your returns policy"}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
block("newsletter_card"),
|
block("newsletter_card"),
|
||||||
|
|||||||
@ -112,15 +112,12 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<button phx-click="editor_done" class="admin-btn admin-btn-sm admin-btn-ghost">
|
|
||||||
Done
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
phx-click="editor_toggle_sidebar"
|
phx-click="editor_done"
|
||||||
class="admin-btn admin-btn-sm admin-btn-ghost"
|
class="admin-btn admin-btn-sm admin-btn-ghost"
|
||||||
aria-label="Close sidebar"
|
data-confirm={@editor_dirty && "You have unsaved changes. Leave without saving?"}
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="size-5" />
|
Done
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -280,7 +277,7 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
|
|
||||||
defp render_block(%{block: %{"type" => "image_text"}} = assigns) do
|
defp render_block(%{block: %{"type" => "image_text"}} = assigns) do
|
||||||
settings = assigns.block["settings"] || %{}
|
settings = assigns.block["settings"] || %{}
|
||||||
image_url = resolve_block_image_url(settings["image_id"], settings["image_url"])
|
image_url = resolve_block_image_url(settings["image_id"])
|
||||||
|
|
||||||
assigns =
|
assigns =
|
||||||
assigns
|
assigns
|
||||||
@ -634,11 +631,12 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
defp render_block(%{block: %{"type" => "content_body"}} = assigns) do
|
defp render_block(%{block: %{"type" => "content_body"}} = assigns) do
|
||||||
settings = assigns.block["settings"] || %{}
|
settings = assigns.block["settings"] || %{}
|
||||||
content = settings["content"] || ""
|
content = settings["content"] || ""
|
||||||
{image_src, image_alt} = resolve_content_image(settings)
|
{image_src, source_width, image_alt} = resolve_content_image(settings)
|
||||||
|
|
||||||
assigns =
|
assigns =
|
||||||
assigns
|
assigns
|
||||||
|> assign(:image_src, image_src)
|
|> assign(:image_src, image_src)
|
||||||
|
|> assign(:image_source_width, source_width || 1200)
|
||||||
|> assign(:image_alt, image_alt)
|
|> assign(:image_alt, image_alt)
|
||||||
|> assign(:content, content)
|
|> assign(:content, content)
|
||||||
|
|
||||||
@ -648,7 +646,7 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
<div class="content-image">
|
<div class="content-image">
|
||||||
<.responsive_image
|
<.responsive_image
|
||||||
src={@image_src}
|
src={@image_src}
|
||||||
source_width={1200}
|
source_width={@image_source_width}
|
||||||
alt={@image_alt}
|
alt={@image_alt}
|
||||||
sizes="(max-width: 800px) 100vw, 800px"
|
sizes="(max-width: 800px) 100vw, 800px"
|
||||||
class="content-hero-image"
|
class="content-hero-image"
|
||||||
@ -1154,47 +1152,41 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
defp collection_path(slug, "featured"), do: ~p"/collections/#{slug}"
|
defp collection_path(slug, "featured"), do: ~p"/collections/#{slug}"
|
||||||
defp collection_path(slug, sort), do: ~p"/collections/#{slug}?sort=#{sort}"
|
defp collection_path(slug, sort), do: ~p"/collections/#{slug}?sort=#{sort}"
|
||||||
|
|
||||||
# Resolves an image_id to a URL, falling back to a legacy URL string
|
# Resolves an image_id to a full URL for blocks that need a single URL (e.g. background-image).
|
||||||
defp resolve_block_image_url(image_id, fallback_url) do
|
defp resolve_block_image_url(image_id) do
|
||||||
case resolve_image(image_id) do
|
case resolve_image(image_id) do
|
||||||
{url, _alt} -> url
|
nil ->
|
||||||
nil -> fallback_url || ""
|
""
|
||||||
|
|
||||||
|
image ->
|
||||||
|
if image.is_svg do
|
||||||
|
"/image_cache/#{image.id}.webp"
|
||||||
|
else
|
||||||
|
width =
|
||||||
|
image.source_width
|
||||||
|
|> Berrypod.Images.Optimizer.applicable_widths()
|
||||||
|
|> List.last()
|
||||||
|
|
||||||
|
"/image_cache/#{image.id}-#{width || 400}.webp"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Resolves image_id for content_body blocks, returning {src, alt}
|
# Resolves image_id for content_body blocks, returning {base_path, source_width, alt}.
|
||||||
|
# base_path has no width/extension suffix — responsive_image adds those.
|
||||||
defp resolve_content_image(settings) do
|
defp resolve_content_image(settings) do
|
||||||
case resolve_image(settings["image_id"]) do
|
case resolve_image(settings["image_id"]) do
|
||||||
{src, alt} -> {src, alt}
|
nil ->
|
||||||
nil -> {settings["image_src"], settings["image_alt"] || ""}
|
{nil, nil, ""}
|
||||||
|
|
||||||
|
image ->
|
||||||
|
{"/image_cache/#{image.id}", image.source_width, image.alt || image.filename}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp resolve_image(nil), do: nil
|
defp resolve_image(nil), do: nil
|
||||||
defp resolve_image(""), do: nil
|
defp resolve_image(""), do: nil
|
||||||
|
defp resolve_image(image_id), do: Berrypod.Media.get_image(image_id)
|
||||||
defp resolve_image(image_id) do
|
|
||||||
case Berrypod.Media.get_image(image_id) do
|
|
||||||
nil ->
|
|
||||||
nil
|
|
||||||
|
|
||||||
image ->
|
|
||||||
url =
|
|
||||||
if image.is_svg do
|
|
||||||
"/image_cache/#{image.id}.webp"
|
|
||||||
else
|
|
||||||
# Pick the largest variant that was actually generated
|
|
||||||
width =
|
|
||||||
image.source_width
|
|
||||||
|> Berrypod.Images.Optimizer.applicable_widths()
|
|
||||||
|> List.last()
|
|
||||||
|
|
||||||
"/image_cache/#{image.id}-#{width || 400}.webp"
|
|
||||||
end
|
|
||||||
|
|
||||||
{url, image.alt || image.filename}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@youtube_re ~r/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/
|
@youtube_re ~r/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/
|
||||||
@vimeo_re ~r/vimeo\.com\/(?:video\/)?(\d+)/
|
@vimeo_re ~r/vimeo\.com\/(?:video\/)?(\d+)/
|
||||||
|
|||||||
@ -11,6 +11,6 @@ defmodule BerrypodWeb.PageControllerTest do
|
|||||||
|
|
||||||
test "GET / renders the shop home page", %{conn: conn} do
|
test "GET / renders the shop home page", %{conn: conn} do
|
||||||
conn = get(conn, ~p"/")
|
conn = get(conn, ~p"/")
|
||||||
assert html_response(conn, 200) =~ "Original designs, printed on demand"
|
assert html_response(conn, 200) =~ "Your headline goes here"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -14,14 +14,14 @@ defmodule BerrypodWeb.Shop.ContentTest do
|
|||||||
test "renders about page", %{conn: conn} do
|
test "renders about page", %{conn: conn} do
|
||||||
{:ok, _view, html} = live(conn, ~p"/about")
|
{:ok, _view, html} = live(conn, ~p"/about")
|
||||||
|
|
||||||
assert html =~ "About the studio"
|
assert html =~ "About us"
|
||||||
assert html =~ "sample about page"
|
assert html =~ "Tell your customers who you are"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "displays about image", %{conn: conn} do
|
test "displays about content body", %{conn: conn} do
|
||||||
{:ok, _view, html} = live(conn, ~p"/about")
|
{:ok, _view, html} = live(conn, ~p"/about")
|
||||||
|
|
||||||
assert html =~ "night-sky-blanket"
|
assert html =~ "placeholder text"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ defmodule BerrypodWeb.Shop.HomeTest do
|
|||||||
test "renders the home page", %{conn: conn} do
|
test "renders the home page", %{conn: conn} do
|
||||||
{:ok, _view, html} = live(conn, ~p"/")
|
{:ok, _view, html} = live(conn, ~p"/")
|
||||||
|
|
||||||
assert html =~ "Original designs, printed on demand"
|
assert html =~ "Your headline goes here"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "renders hero section with CTA", %{conn: conn} do
|
test "renders hero section with CTA", %{conn: conn} do
|
||||||
@ -55,8 +55,8 @@ defmodule BerrypodWeb.Shop.HomeTest do
|
|||||||
test "renders image and text section", %{conn: conn} do
|
test "renders image and text section", %{conn: conn} do
|
||||||
{:ok, _view, html} = live(conn, ~p"/")
|
{:ok, _view, html} = live(conn, ~p"/")
|
||||||
|
|
||||||
assert html =~ "Made with passion, printed with care"
|
assert html =~ "Your story in a nutshell"
|
||||||
assert html =~ "Learn more about the studio"
|
assert html =~ "Read more about us"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "renders header with shop name", %{conn: conn} do
|
test "renders header with shop name", %{conn: conn} do
|
||||||
|
|||||||
@ -90,10 +90,8 @@ defmodule BerrypodWeb.PageEditorHookTest do
|
|||||||
"button[phx-click='editor_toggle_sidebar'][aria-label='Show editor sidebar']"
|
"button[phx-click='editor_toggle_sidebar'][aria-label='Show editor sidebar']"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Close the sidebar via the X button
|
# Close the sidebar via the backdrop
|
||||||
view
|
view |> element(".page-editor-backdrop") |> render_click()
|
||||||
|> element("button[phx-click='editor_toggle_sidebar'][aria-label='Close sidebar']")
|
|
||||||
|> render_click()
|
|
||||||
|
|
||||||
assert has_element?(view, "[data-sidebar-open='false']")
|
assert has_element?(view, "[data-sidebar-open='false']")
|
||||||
# Pencil button appears in header to re-open
|
# Pencil button appears in header to re-open
|
||||||
|
|||||||
@ -49,17 +49,17 @@ defmodule BerrypodWeb.PageRendererTest do
|
|||||||
test "home page renders hero and featured products" do
|
test "home page renders hero and featured products" do
|
||||||
html = render_page("home", %{products: []})
|
html = render_page("home", %{products: []})
|
||||||
|
|
||||||
assert html =~ "Original designs, printed on demand"
|
assert html =~ "Your headline goes here"
|
||||||
assert html =~ "Shop the collection"
|
assert html =~ "Shop the collection"
|
||||||
assert html =~ "Featured products"
|
assert html =~ "Featured products"
|
||||||
assert html =~ "Made with passion, printed with care"
|
assert html =~ "Your story in a nutshell"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "about page renders hero and content area" do
|
test "about page renders hero and content area" do
|
||||||
html =
|
html =
|
||||||
render_page("about", %{content_blocks: [%{type: :paragraph, text: "Test about text"}]})
|
render_page("about", %{content_blocks: [%{type: :paragraph, text: "Test about text"}]})
|
||||||
|
|
||||||
assert html =~ "About the studio"
|
assert html =~ "About us"
|
||||||
assert html =~ "content-body"
|
assert html =~ "content-body"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user