Add vendor detail page
This commit is contained in:
148
frontend/src/routes/vendors/$slug.tsx
vendored
148
frontend/src/routes/vendors/$slug.tsx
vendored
@@ -1,5 +1,149 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||||
|
import { createFileRoute, Link, notFound } from '@tanstack/react-router'
|
||||||
|
import { iso31661 } from 'iso-3166'
|
||||||
|
import { Facebook, Globe, Linkedin, Twitter } from 'lucide-react'
|
||||||
|
import { Helmet } from 'react-helmet-async'
|
||||||
|
|
||||||
|
import { Button } from '~/components/ui/button'
|
||||||
|
import { assetUrl } from '~/lib/directus'
|
||||||
|
import { vendorBySlugQuery } from '~/lib/queries'
|
||||||
|
|
||||||
export const Route = createFileRoute('/vendors/$slug')({
|
export const Route = createFileRoute('/vendors/$slug')({
|
||||||
component: () => null,
|
loader: async ({ context: { queryClient }, params: { slug } }) => {
|
||||||
|
const vendor = await queryClient.ensureQueryData(vendorBySlugQuery(slug))
|
||||||
|
if (!vendor) throw notFound()
|
||||||
|
},
|
||||||
|
component: VendorDetailPage,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function countryName(alpha3: string | null | undefined): string {
|
||||||
|
if (!alpha3) return ''
|
||||||
|
return iso31661.find((iso) => iso.alpha3 === alpha3)?.name ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function VendorDetailPage() {
|
||||||
|
const { slug } = Route.useParams()
|
||||||
|
const { data: vendor } = useSuspenseQuery(vendorBySlugQuery(slug))
|
||||||
|
if (!vendor) return null
|
||||||
|
|
||||||
|
const country = countryName(vendor.country)
|
||||||
|
const hasSocial = vendor.website || vendor.facebook || vendor.linkedin || vendor.twitter
|
||||||
|
const hasAddress = vendor.address_line_1 || vendor.address_line_2 || vendor.city || vendor.state || country
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="vendor">
|
||||||
|
<Helmet>
|
||||||
|
<title>{vendor.name}</title>
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-start justify-between gap-6 md:flex-row md:items-center">
|
||||||
|
<h1 className="text-3xl font-bold">{vendor.name}</h1>
|
||||||
|
{vendor.logo && (
|
||||||
|
<img
|
||||||
|
src={assetUrl(vendor.logo, 'logo-page')}
|
||||||
|
alt={vendor.name}
|
||||||
|
width={350}
|
||||||
|
height={150}
|
||||||
|
className="h-[150px] w-[350px] rounded-md bg-white object-contain"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{vendor.long_description && (
|
||||||
|
<div className="prose-cms mt-6" dangerouslySetInnerHTML={{ __html: vendor.long_description }} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-cols-1 gap-10 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{hasAddress && (
|
||||||
|
<section>
|
||||||
|
<h2 className="mt-6 mb-2 text-lg font-semibold">Address</h2>
|
||||||
|
<p className="text-sm">
|
||||||
|
{vendor.address_line_1 && (
|
||||||
|
<>
|
||||||
|
{vendor.address_line_1}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{vendor.address_line_2 && (
|
||||||
|
<>
|
||||||
|
{vendor.address_line_2}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{vendor.city && (
|
||||||
|
<>
|
||||||
|
{vendor.city}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{vendor.state && (
|
||||||
|
<>
|
||||||
|
{vendor.state}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{country && <>{country}</>}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasSocial && (
|
||||||
|
<section>
|
||||||
|
<h2 className="mt-6 mb-2 text-lg font-semibold">Social</h2>
|
||||||
|
<ul className="space-y-2 text-sm">
|
||||||
|
{vendor.website && (
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<Globe className="h-4 w-4" />
|
||||||
|
<a href={vendor.website} target="_blank" rel="noreferrer" className="hover:underline">
|
||||||
|
{vendor.website}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{vendor.linkedin && (
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<Linkedin className="h-4 w-4" />
|
||||||
|
<a href={vendor.linkedin} target="_blank" rel="noreferrer" className="hover:underline">
|
||||||
|
{vendor.linkedin}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{vendor.twitter && (
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<Twitter className="h-4 w-4" />
|
||||||
|
<a href={vendor.twitter} target="_blank" rel="noreferrer" className="hover:underline">
|
||||||
|
{vendor.twitter}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{vendor.facebook && (
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<Facebook className="h-4 w-4" />
|
||||||
|
<a href={vendor.facebook} target="_blank" rel="noreferrer" className="hover:underline">
|
||||||
|
{vendor.facebook}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{vendor.categories.length > 0 && (
|
||||||
|
<section>
|
||||||
|
<h2 className="mt-6 mb-2 text-lg font-semibold">Categories</h2>
|
||||||
|
<ul className="flex flex-col gap-2">
|
||||||
|
{vendor.categories.map((cat) => (
|
||||||
|
<li key={cat.categories_id.slug}>
|
||||||
|
<Button asChild variant="secondary" size="sm" className="h-6 px-2 text-[11px]">
|
||||||
|
<Link to="/vendors" search={{ category: cat.categories_id.slug }}>
|
||||||
|
{cat.categories_id.name}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user