localspot/lib/localspot_web/live/business_live/show.ex
Kevin Sivic 9ed897a60e Add LiveView pages for business directory
- CategoryLive.Index: browse categories with business counts
- CategoryLive.Show: view businesses filtered by category
- BusinessLive.Index: search/filter businesses with geolocation
- BusinessLive.Show: detailed business profile with hours
- BusinessLive.New: submission form for new businesses
- BusinessLive.Map: interactive Leaflet.js map view
- Seed data with sample Columbus, OH businesses

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 00:11:36 -05:00

167 lines
5.7 KiB
Elixir

defmodule LocalspotWeb.BusinessLive.Show do
use LocalspotWeb, :live_view
alias Localspot.Businesses
@impl true
def mount(%{"slug" => slug}, _session, socket) do
case Businesses.get_business_by_slug(slug) do
nil ->
{:ok,
socket
|> put_flash(:error, "Business not found")
|> push_navigate(to: ~p"/businesses")}
business ->
{:ok,
socket
|> assign(:page_title, business.name)
|> assign(:business, business)
|> assign(:is_open, Businesses.currently_open?(business.hours))}
end
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<div class="max-w-4xl mx-auto">
<%!-- Back link --%>
<.link navigate={~p"/businesses"} class="btn btn-ghost btn-sm mb-4">
<.icon name="hero-arrow-left" class="w-4 h-4" /> Back to Directory
</.link>
<%!-- Photo gallery --%>
<div
:if={@business.photos != []}
class="carousel w-full rounded-lg mb-6 h-64 md:h-96 bg-base-300"
>
<div
:for={{photo, idx} <- Enum.with_index(@business.photos)}
id={"photo-#{idx}"}
class="carousel-item w-full"
>
<img
src={photo.url}
alt={photo.alt_text || "#{@business.name} photo #{idx + 1}"}
class="w-full object-cover"
/>
</div>
</div>
<%!-- Business header --%>
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<div>
<h1 class="text-3xl font-bold flex items-center gap-3 flex-wrap">
{@business.name}
<span
:if={@business.locally_owned}
class="badge badge-success"
title="Locally Owned"
>
Locally Owned
</span>
</h1>
<.link
navigate={~p"/categories/#{@business.category.slug}"}
class="text-base-content/60 hover:underline"
>
{@business.category.name}
</.link>
</div>
<div class={[
"badge badge-lg",
if(@is_open, do: "badge-success", else: "badge-error")
]}>
{if @is_open, do: "Open Now", else: "Closed"}
</div>
</div>
<%!-- Description --%>
<p :if={@business.description} class="text-lg mb-6">{@business.description}</p>
<%!-- Local ownership info --%>
<div :if={@business.locally_owned} class="alert alert-success mb-6">
<.icon name="hero-check-badge" class="w-6 h-6" />
<div>
<h3 class="font-bold">Locally Owned Business</h3>
<p class="text-sm">
This business has confirmed they are locally owned and operated, not a chain or franchise.
</p>
</div>
</div>
<div class="grid md:grid-cols-2 gap-6">
<%!-- Contact info --%>
<div class="card bg-base-200">
<div class="card-body">
<h2 class="card-title">Contact Information</h2>
<div class="space-y-3">
<div class="flex items-start gap-3">
<.icon name="hero-map-pin" class="w-5 h-5 mt-0.5 text-primary" />
<div>
<p>{@business.street_address}</p>
<p>{@business.city}, {@business.state} {@business.zip_code}</p>
</div>
</div>
<div :if={@business.phone} class="flex items-center gap-3">
<.icon name="hero-phone" class="w-5 h-5 text-primary" />
<a href={"tel:#{@business.phone}"} class="link link-hover">
{Businesses.format_phone(@business.phone)}
</a>
</div>
<div :if={@business.email} class="flex items-center gap-3">
<.icon name="hero-envelope" class="w-5 h-5 text-primary" />
<a href={"mailto:#{@business.email}"} class="link link-hover">
{@business.email}
</a>
</div>
<div :if={@business.website} class="flex items-center gap-3">
<.icon name="hero-globe-alt" class="w-5 h-5 text-primary" />
<a href={@business.website} target="_blank" rel="noopener" class="link link-hover">
Visit Website
<.icon name="hero-arrow-top-right-on-square" class="w-4 h-4 inline" />
</a>
</div>
</div>
</div>
</div>
<%!-- Hours --%>
<div class="card bg-base-200">
<div class="card-body">
<h2 class="card-title">Business Hours</h2>
<%= if @business.hours == [] do %>
<p class="text-base-content/60">Hours not available</p>
<% else %>
<div class="space-y-2">
<div
:for={hour <- Enum.sort_by(@business.hours, & &1.day_of_week)}
class="flex justify-between"
>
<span class="font-medium">{Businesses.day_name(hour.day_of_week)}</span>
<span :if={hour.closed} class="text-base-content/60">Closed</span>
<span :if={!hour.closed && hour.opens_at && hour.closes_at}>
{format_time(hour.opens_at)} - {format_time(hour.closes_at)}
</span>
</div>
</div>
<% end %>
</div>
</div>
</div>
</div>
</Layouts.app>
"""
end
defp format_time(time) do
Calendar.strftime(time, "%I:%M %p")
end
end