defmodule BerrypodWeb.ShopComponents.Content do @moduledoc false use Phoenix.Component import BerrypodWeb.ShopComponents.Base @default_social_links [ %{platform: :instagram, url: "https://instagram.com", label: "Instagram"}, %{platform: :bluesky, url: "https://bsky.app", label: "Bluesky"}, %{platform: :mastodon, url: "https://mastodon.social", label: "Mastodon"}, %{platform: :kofi, url: "https://ko-fi.com", label: "Ko-fi"}, %{platform: :github, url: "https://github.com", label: "GitHub"} ] @doc """ Renders a content body container for long-form content pages (about, etc.). ## Attributes * `image_src` - Optional. Base path to image (without size/extension), used with responsive_image. * `image_alt` - Optional. Alt text for the image. Defaults to "Page image". ## Slots * `inner_block` - Required. The content to render. ## Examples <.content_body image_src="/mockups/night-sky-blanket-3" image_alt="A cosy blanket">

Content here...

""" attr :image_src, :string, default: nil attr :image_alt, :string, default: "Page image" slot :inner_block, required: true def content_body(assigns) do ~H"""
<%= if @image_src do %>
<.responsive_image src={@image_src} source_width={1200} alt={@image_alt} sizes="(max-width: 800px) 100vw, 800px" class="content-hero-image" />
<% end %>
{render_slot(@inner_block)}
""" end @doc """ Renders a contact form card. ## Attributes * `title` - Optional. Form heading. Defaults to "Send a message". * `email` - Optional. If provided, displays "Email me: [email]" below the title. ## Examples <.contact_form /> <.contact_form title="Get in touch" /> <.contact_form email="hello@example.com" /> """ attr :title, :string, default: "Send a message" attr :email, :string, default: nil attr :response_time, :string, default: nil def contact_form(assigns) do ~H""" <.shop_card class="contact-form-card">

{@title}

<%= if @email || @response_time do %>
<%= if @email do %>

Email me: {@email}

<% end %> <%= if @response_time do %>

{@response_time}

<% end %>
<% else %>
<% end %>
<.shop_input type="text" placeholder="Your name" />
<.shop_input type="email" placeholder="your@email.com" />
<.shop_input type="text" placeholder="How can I help?" />
<.shop_textarea rows="5" placeholder="Your message..." />
<.shop_button type="submit" class="contact-form-submit"> Send Message
""" end @doc """ Renders the order tracking card. Submits `lookup_orders` to the parent LiveView. The `:sent` state shows a confirmation message; `:not_found` shows the form again with an error note. ## Attributes * `tracking_state` - Optional. `:idle | :sent | :not_found`. Defaults to `:idle`. ## Examples <.order_tracking_card /> <.order_tracking_card tracking_state={@tracking_state} /> """ attr :tracking_state, :atom, default: :idle def order_tracking_card(%{tracking_state: :sent} = assigns) do ~H""" <.shop_card class="card-section">

Check your inbox

We've sent a link to your email address. It'll expire after an hour.

""" end def order_tracking_card(%{tracking_state: :not_found} = assigns) do ~H""" <.shop_card class="card-section">

Track your order

No orders found for that address. Make sure you use the same email you checked out with.

<.shop_input type="email" name="email" placeholder="your@email.com" class="email-input" required /> <.shop_button type="submit">Try again
""" end def order_tracking_card(assigns) do ~H""" <.shop_card class="card-section">

Track your order

Enter the email address you used at checkout and we'll send you a link.

<.shop_input type="email" name="email" placeholder="your@email.com" class="email-input" required /> <.shop_button type="submit">Send link
""" end @doc """ Renders the info card with bullet points (e.g., "Handy to know" section). ## Attributes * `title` - Required. Card heading. * `items` - Required. List of maps with `label` and `value` keys. ## Examples <.info_card title="Handy to know" items={[ %{label: "Printing", value: "2-5 business days"}, %{label: "Delivery", value: "3-7 business days after printing"} ]} /> """ attr :title, :string, required: true attr :items, :list, required: true def info_card(assigns) do ~H""" <.shop_card class="card-section">

{@title}

""" end @doc """ Renders the contact info card with email link. ## Attributes * `title` - Optional. Card heading. Defaults to "Get in touch". * `email` - Required. Email address. * `response_text` - Optional. Response time text. Defaults to "We typically respond within 24 hours". ## Examples <.contact_info_card email="hello@example.com" /> """ attr :title, :string, default: "Get in touch" attr :email, :string, required: true attr :response_text, :string, default: "We typically respond within 24 hours" def contact_info_card(assigns) do ~H""" <.shop_card class="card-section">

{@title}

{@email}

{@response_text}

""" end @doc """ Renders a newsletter signup card. ## Attributes * `title` - Optional. Card heading. Defaults to "Stay in touch". * `description` - Optional. Card description. * `button_text` - Optional. Button text. Defaults to "Subscribe". * `variant` - Optional. Either `:card` (default, with border/background) or `:inline` (no card styling, for embedding in footer). ## Examples <.newsletter_card /> <.newsletter_card title="Studio news" description="Get updates on new products." /> <.newsletter_card variant={:inline} /> """ attr :title, :string, default: "Newsletter" attr :description, :string, default: "Sample newsletter signup. Replace with your own message to encourage subscribers." attr :button_text, :string, default: "Subscribe" attr :variant, :atom, default: :card def newsletter_card(%{variant: :inline} = assigns) do ~H"""

{@title}

{@description}

<.shop_input type="email" placeholder="your@email.com" class="email-input" /> <.shop_button type="submit">{@button_text}
""" end def newsletter_card(assigns) do ~H""" <.shop_card class="card-section">

{@title}

{@description}

<.shop_input type="email" placeholder="your@email.com" class="email-input" /> <.shop_button type="submit">{@button_text}
""" end @doc """ Renders social media links in a single card with a compact grid layout. ## Attributes * `title` - Optional. Card heading. Defaults to "Follow us". * `links` - Optional. List of maps with `platform`, `url`, and `label` keys. Supported platforms: :instagram, :pinterest, :facebook, :twitter, :tiktok, :patreon, :youtube ## Examples <.social_links_card /> <.social_links_card title="Elsewhere" links={[%{platform: :instagram, url: "https://instagram.com/example", label: "Instagram"}]} /> """ attr :title, :string, default: "Find me on" attr :links, :list, default: @default_social_links def social_links_card(assigns) do ~H""" <.shop_card class="card-section">

{@title}

""" end @doc """ Renders social media icon links. ## Attributes * `links` - Optional. List of maps with `platform`, `url`, and `label` keys. Supported platforms: :instagram, :pinterest ## Examples <.social_links /> <.social_links links={[%{platform: :instagram, url: "https://instagram.com/example", label: "Instagram"}]} /> """ attr :links, :list, default: @default_social_links def social_links(assigns) do ~H""" """ end # Renders a social media icon for the given platform. # # All icons are from Simple Icons (simpleicons.org), MIT licensed. # # ## Supported platforms # # **Commercial/Creative:** # :instagram, :pinterest, :tiktok, :facebook, :twitter, :youtube, :patreon, :kofi, :etsy, :gumroad, :bandcamp # # **Open Web/Federated:** # :mastodon, :pixelfed, :bluesky, :peertube, :lemmy, :matrix # # **Developer/Hacker:** # :github, :gitlab, :codeberg, :sourcehut # # **Communication:** # :discord, :telegram, :signal # # **Other:** # :substack, :rss, :website attr :platform, :atom, required: true # Commercial/Creative platforms defp social_icon(%{platform: :instagram} = assigns) do ~H""" """ end defp social_icon(%{platform: :pinterest} = assigns) do ~H""" """ end defp social_icon(%{platform: :tiktok} = assigns) do ~H""" """ end defp social_icon(%{platform: :facebook} = assigns) do ~H""" """ end defp social_icon(%{platform: :twitter} = assigns) do ~H""" """ end defp social_icon(%{platform: :youtube} = assigns) do ~H""" """ end defp social_icon(%{platform: :patreon} = assigns) do ~H""" """ end defp social_icon(%{platform: :kofi} = assigns) do ~H""" """ end defp social_icon(%{platform: :etsy} = assigns) do ~H""" """ end defp social_icon(%{platform: :gumroad} = assigns) do ~H""" """ end defp social_icon(%{platform: :bandcamp} = assigns) do ~H""" """ end # Open Web/Federated platforms defp social_icon(%{platform: :mastodon} = assigns) do ~H""" """ end defp social_icon(%{platform: :pixelfed} = assigns) do ~H""" """ end defp social_icon(%{platform: :bluesky} = assigns) do ~H""" """ end defp social_icon(%{platform: :peertube} = assigns) do ~H""" """ end defp social_icon(%{platform: :lemmy} = assigns) do ~H""" """ end defp social_icon(%{platform: :matrix} = assigns) do ~H""" """ end # Developer/Hacker platforms defp social_icon(%{platform: :github} = assigns) do ~H""" """ end defp social_icon(%{platform: :gitlab} = assigns) do ~H""" """ end defp social_icon(%{platform: :codeberg} = assigns) do ~H""" """ end defp social_icon(%{platform: :sourcehut} = assigns) do ~H""" """ end # Communication platforms defp social_icon(%{platform: :discord} = assigns) do ~H""" """ end defp social_icon(%{platform: :telegram} = assigns) do ~H""" """ end defp social_icon(%{platform: :signal} = assigns) do ~H""" """ end # Other platforms defp social_icon(%{platform: :substack} = assigns) do ~H""" """ end defp social_icon(%{platform: :rss} = assigns) do ~H""" """ end defp social_icon(%{platform: :website} = assigns) do ~H""" """ end # Fallback for unknown platforms defp social_icon(assigns) do ~H""" """ end @doc """ Renders a star rating display. ## Attributes * `rating` - Required. Number of filled stars (1-5). * `max` - Optional. Maximum stars to display. Defaults to 5. * `size` - Optional. Size variant: `:sm` (w-4), `:md` (w-5). Defaults to `:sm`. * `color` - Optional. Star color. Defaults to "#f59e0b" (amber). ## Examples <.star_rating rating={5} /> <.star_rating rating={4} size={:md} /> """ attr :rating, :integer, required: true attr :max, :integer, default: 5 attr :size, :atom, default: :sm attr :color, :string, default: "#f59e0b" def star_rating(assigns) do ~H"""
<%= for i <- 1..@max do %> <% end %>
""" end @doc """ Renders trust badges (e.g., free delivery, easy returns). ## Attributes * `items` - Optional. List of badge items. Each item is a map with: - `title` - Badge title (sentence case) - `description` - Badge description Defaults to "Made to order" and "Quality materials" badges. ## Examples <.trust_badges /> <.trust_badges items={[%{title: "Custom", description: "Badge text"}]} /> """ attr :items, :list, default: [ %{title: "Made to order", description: "Printed just for you"}, %{title: "Quality materials", description: "Premium inks and substrates"} ] def trust_badges(assigns) do ~H"""
<%= for item <- @items do %>

{item.title}

{item.description}

<% end %>
""" end @doc """ Renders a customer reviews section with collapsible header and review cards. ## Attributes * `reviews` - Required. List of review maps with: - `rating` - Star rating (1-5) - `title` - Review title - `body` - Review text - `author` - Reviewer name - `date` - Relative date string (e.g., "2 weeks ago") - `verified` - Boolean, if true shows "Verified purchase" badge * `average_rating` - Optional. Average rating to show in header. Defaults to 5. * `total_count` - Optional. Total number of reviews. Defaults to length of reviews list. * `open` - Optional. Whether section is expanded by default. Defaults to true. ## Examples <.reviews_section reviews={@product.reviews} average_rating={4.8} total_count={24} /> """ attr :reviews, :list, required: true attr :average_rating, :integer, default: 5 attr :total_count, :integer, default: nil attr :open, :boolean, default: true def reviews_section(assigns) do assigns = assign_new(assigns, :display_count, fn -> assigns.total_count || length(assigns.reviews) end) ~H"""

Customer reviews

<.star_rating rating={@average_rating} /> ({@display_count})
<%= for review <- @reviews do %> <.review_card review={review} /> <% end %>
<.shop_button_outline class="reviews-load-more"> Load more reviews
""" end @doc """ Renders a single review card. ## Attributes * `review` - Required. Map with `rating`, `title`, `body`, `author`, `date`, `verified`. ## Examples <.review_card review={%{rating: 5, title: "Great!", body: "...", author: "Jane", date: "1 week ago", verified: true}} /> """ attr :review, :map, required: true def review_card(assigns) do ~H"""
<.star_rating rating={@review.rating} /> {@review.date}

{@review.title}

{@review.body}

""" end @doc """ Renders a page title heading. ## Attributes * `text` - Required. The title text. * `class` - Optional. Additional CSS classes. ## Examples <.page_title text="Your basket" /> <.page_title text="Order History" class="mb-4" /> """ attr :text, :string, required: true def page_title(assigns) do ~H"""

{@text}

""" end @doc """ Renders rich text content with themed typography. This component renders structured content blocks (paragraphs, headings) with appropriate theme styling. ## Attributes * `blocks` - Required. List of content blocks. Each block is a map with: - `type` - Either `:paragraph`, `:heading`, or `:lead` - `text` - The text content - `level` - For headings, the level (2, 3, etc.). Defaults to 2. ## Examples <.rich_text blocks={[ %{type: :lead, text: "Introduction paragraph..."}, %{type: :paragraph, text: "Regular paragraph..."}, %{type: :heading, text: "Section Title"}, %{type: :paragraph, text: "More content..."} ]} /> """ attr :blocks, :list, required: true def rich_text(assigns) do ~H"""
<%= for block <- @blocks do %> <.rich_text_block block={block} /> <% end %>
""" end attr :block, :map, required: true defp rich_text_block(%{block: %{type: :lead}} = assigns) do ~H"""

{@block.text}

""" end defp rich_text_block(%{block: %{type: :updated_at}} = assigns) do ~H"""

Last updated: {@block.date}

""" end defp rich_text_block(%{block: %{type: :paragraph}} = assigns) do ~H"""

{@block.text}

""" end defp rich_text_block(%{block: %{type: :heading}} = assigns) do ~H"""

{@block.text}

""" end defp rich_text_block(%{block: %{type: :closing}} = assigns) do ~H"""

{@block.text}

""" end defp rich_text_block(%{block: %{type: :list}} = assigns) do ~H""" """ end defp rich_text_block(assigns) do ~H"""

{@block.text}

""" end @doc """ Renders a responsive `` element with AVIF, WebP, and JPEG sources. Computes available widths from `source_width` to avoid upscaling - only generates srcset entries for sizes smaller than or equal to the original image dimensions. The component renders: - `` for AVIF (best compression, modern browsers) - `` for WebP (good compression, broad support) - `` with JPEG srcset (fallback for legacy browsers) ## Attributes * `src` - Required. Base path to the image variants (without size/extension). * `alt` - Required. Alt text for accessibility. * `source_width` - Required. Original image width in pixels. * `sizes` - Optional. Responsive sizes attribute. Defaults to "100vw". * `class` - Optional. CSS classes to apply to the `` element. * `width` - Optional. Explicit width attribute. * `height` - Optional. Explicit height attribute. * `priority` - Optional. If true, sets eager loading and high fetchpriority. Defaults to false (lazy loading). ## Examples <.responsive_image src="/image_cache/abc123" source_width={1200} alt="Product image" /> <.responsive_image src="/image_cache/abc123" source_width={1200} alt="Hero banner" priority={true} sizes="(max-width: 768px) 100vw, 50vw" /> """ attr :src, :string, required: true, doc: "Base path without size/extension" attr :alt, :string, required: true attr :source_width, :integer, required: true, doc: "Original image width" attr :sizes, :string, default: "100vw" attr :class, :string, default: "" attr :width, :integer, default: nil attr :height, :integer, default: nil attr :priority, :boolean, default: false def responsive_image(assigns) do alias Berrypod.Images.Optimizer # Compute available widths from source dimensions (no upscaling) available = Optimizer.applicable_widths(assigns.source_width) default_width = Enum.max(available) assigns = assigns |> assign(:available_widths, available) |> assign(:default_width, default_width) ~H""" {@alt} """ end @doc """ Renders flash messages styled for the shop theme. """ attr :flash, :map, required: true def shop_flash_group(assigns) do ~H"""
<%= if msg = Phoenix.Flash.get(@flash, :info) do %> <% end %> <%= if msg = Phoenix.Flash.get(@flash, :error) do %> <% end %>
""" end defp build_srcset(base, widths, format) do widths |> Enum.sort() |> Enum.map_join(", ", &"#{base}-#{&1}.#{format} #{&1}w") end end