feat: added /cellar
190ea561
4 file(s) · +184 −0
| 1 | + | --- |
|
| 2 | + | const { wine, cellarApiUrl } = Astro.props; |
|
| 3 | + | --- |
|
| 4 | + | ||
| 5 | + | <a |
|
| 6 | + | href={`/cellar/${wine.short_id}`} |
|
| 7 | + | class="flex items-center gap-4 py-3 border-b border-[#333] no-underline hover:opacity-70 transition-opacity" |
|
| 8 | + | > |
|
| 9 | + | <div class="flex-shrink-0 w-20 h-20"> |
|
| 10 | + | <img |
|
| 11 | + | src={`${cellarApiUrl}/api/wines/${wine.short_id}/pentagon.svg`} |
|
| 12 | + | alt={`${wine.name} taste profile`} |
|
| 13 | + | width="80" |
|
| 14 | + | height="80" |
|
| 15 | + | style="width:80px;height:80px;object-fit:contain;" |
|
| 16 | + | loading="lazy" |
|
| 17 | + | /> |
|
| 18 | + | </div> |
|
| 19 | + | <div class="flex flex-col gap-0.5"> |
|
| 20 | + | <span class="text-base">{wine.name}</span> |
|
| 21 | + | <span class="text-xs opacity-50">{wine.origin}{wine.grape ? ` · ${wine.grape}` : ""}</span> |
|
| 22 | + | </div> |
|
| 23 | + | </a> |
| 28 | 28 | path: "/now", |
|
| 29 | 29 | }, |
|
| 30 | 30 | { |
|
| 31 | + | title: "Cellar", |
|
| 32 | + | path: "/cellar", |
|
| 33 | + | }, |
|
| 34 | + | { |
|
| 31 | 35 | title: "Feeds", |
|
| 32 | 36 | path: "/feeds", |
|
| 33 | 37 | }, |
| 1 | + | --- |
|
| 2 | + | export const prerender = false; |
|
| 3 | + | ||
| 4 | + | import PageLayout from "@/layouts/Base.astro"; |
|
| 5 | + | ||
| 6 | + | const { short_id } = Astro.params; |
|
| 7 | + | const CELLAR_API_URL = import.meta.env.CELLAR_API_URL ?? "https://cellar.stevedylan.dev"; |
|
| 8 | + | ||
| 9 | + | let wine: any = null; |
|
| 10 | + | let fetchError: string | null = null; |
|
| 11 | + | try { |
|
| 12 | + | const res = await fetch(`${CELLAR_API_URL}/api/wines/${short_id}`); |
|
| 13 | + | if (res.status === 404) { |
|
| 14 | + | return Astro.redirect("/404"); |
|
| 15 | + | } else if (!res.ok) { |
|
| 16 | + | fetchError = `API returned ${res.status}`; |
|
| 17 | + | } else { |
|
| 18 | + | wine = await res.json(); |
|
| 19 | + | } |
|
| 20 | + | } catch (e) { |
|
| 21 | + | fetchError = e instanceof Error ? e.message : "Failed to reach cellar API"; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | const meta = { |
|
| 25 | + | title: wine?.name ?? "Wine", |
|
| 26 | + | description: wine ? `${wine.origin} · ${wine.grape}` : "", |
|
| 27 | + | }; |
|
| 28 | + | --- |
|
| 29 | + | ||
| 30 | + | <PageLayout meta={meta}> |
|
| 31 | + | <div class="flex flex-col gap-6 w-full pb-16"> |
|
| 32 | + | ||
| 33 | + | {fetchError ? ( |
|
| 34 | + | <p class="text-red-400 text-sm">Could not load wine: {fetchError}</p> |
|
| 35 | + | ) : wine && ( |
|
| 36 | + | <> |
|
| 37 | + | <h1 class="text-2xl font-bold" style="letter-spacing:-0.5px">{wine.name}</h1> |
|
| 38 | + | ||
| 39 | + | <div class="grid grid-cols-2 gap-6 items-center wine-detail-top"> |
|
| 40 | + | {wine.has_image && ( |
|
| 41 | + | <div class="w-full"> |
|
| 42 | + | <img |
|
| 43 | + | src={`${CELLAR_API_URL}/wines/${wine.short_id}/image`} |
|
| 44 | + | alt={wine.name} |
|
| 45 | + | class="w-full object-cover rounded wine-image" |
|
| 46 | + | loading="eager" |
|
| 47 | + | /> |
|
| 48 | + | </div> |
|
| 49 | + | )} |
|
| 50 | + | {!wine.wishlist && ( |
|
| 51 | + | <div class="flex flex-col items-center gap-4 p-3"> |
|
| 52 | + | <img |
|
| 53 | + | src={`${CELLAR_API_URL}/api/wines/${wine.short_id}/pentagon.svg`} |
|
| 54 | + | alt="Taste profile" |
|
| 55 | + | width="250" |
|
| 56 | + | height="250" |
|
| 57 | + | loading="eager" |
|
| 58 | + | /> |
|
| 59 | + | <img |
|
| 60 | + | src={`${CELLAR_API_URL}/api/wines/${wine.short_id}/bars.svg`} |
|
| 61 | + | alt="Appearance and nose" |
|
| 62 | + | width="250" |
|
| 63 | + | loading="eager" |
|
| 64 | + | /> |
|
| 65 | + | </div> |
|
| 66 | + | )} |
|
| 67 | + | </div> |
|
| 68 | + | ||
| 69 | + | <div class="flex flex-col gap-1"> |
|
| 70 | + | {wine.origin && ( |
|
| 71 | + | <div class="flex gap-3 text-sm"> |
|
| 72 | + | <span class="text-xs opacity-50">origin</span> |
|
| 73 | + | <span>{wine.origin}</span> |
|
| 74 | + | </div> |
|
| 75 | + | )} |
|
| 76 | + | {wine.grape && ( |
|
| 77 | + | <div class="flex gap-3 text-sm"> |
|
| 78 | + | <span class="text-xs opacity-50">grape</span> |
|
| 79 | + | <span>{wine.grape}</span> |
|
| 80 | + | </div> |
|
| 81 | + | )} |
|
| 82 | + | </div> |
|
| 83 | + | ||
| 84 | + | {wine.notes && ( |
|
| 85 | + | <div class="flex flex-col gap-1"> |
|
| 86 | + | <span class="text-xs opacity-50">notes</span> |
|
| 87 | + | <p class="whitespace-pre-wrap">{wine.notes}</p> |
|
| 88 | + | </div> |
|
| 89 | + | )} |
|
| 90 | + | ||
| 91 | + | {wine.background && ( |
|
| 92 | + | <div class="flex flex-col gap-1"> |
|
| 93 | + | <span class="text-xs opacity-50">background</span> |
|
| 94 | + | <p class="whitespace-pre-wrap">{wine.background}</p> |
|
| 95 | + | </div> |
|
| 96 | + | )} |
|
| 97 | + | </> |
|
| 98 | + | )} |
|
| 99 | + | </div> |
|
| 100 | + | <a href="/cellar" class="text-sm">← cellar</a> |
|
| 101 | + | ||
| 102 | + | </PageLayout> |
|
| 103 | + | ||
| 104 | + | <style> |
|
| 105 | + | @media (max-width: 480px) { |
|
| 106 | + | .wine-detail-top { |
|
| 107 | + | grid-template-columns: 1fr; |
|
| 108 | + | } |
|
| 109 | + | .wine-image { |
|
| 110 | + | max-height: none; |
|
| 111 | + | width: 100%; |
|
| 112 | + | } |
|
| 113 | + | } |
|
| 114 | + | </style> |
| 1 | + | --- |
|
| 2 | + | export const prerender = false; |
|
| 3 | + | ||
| 4 | + | import PageLayout from "@/layouts/Base.astro"; |
|
| 5 | + | import WineCard from "@/components/page/WineCard.astro"; |
|
| 6 | + | ||
| 7 | + | const meta = { |
|
| 8 | + | title: "Cellar", |
|
| 9 | + | description: "My wine tasting log", |
|
| 10 | + | }; |
|
| 11 | + | ||
| 12 | + | const CELLAR_API_URL = import.meta.env.CELLAR_API_URL ?? "https://cellar.stevedylan.dev"; |
|
| 13 | + | ||
| 14 | + | let wines: any[] = []; |
|
| 15 | + | let error: string | null = null; |
|
| 16 | + | try { |
|
| 17 | + | const res = await fetch(`${CELLAR_API_URL}/api/wines`); |
|
| 18 | + | if (res.ok) { |
|
| 19 | + | wines = await res.json(); |
|
| 20 | + | } else { |
|
| 21 | + | error = `API returned ${res.status}`; |
|
| 22 | + | } |
|
| 23 | + | } catch (e) { |
|
| 24 | + | error = e instanceof Error ? e.message : "Failed to reach cellar API"; |
|
| 25 | + | } |
|
| 26 | + | --- |
|
| 27 | + | ||
| 28 | + | <PageLayout meta={meta}> |
|
| 29 | + | <div class="flex min-h-screen flex-col items-start justify-start gap-6"> |
|
| 30 | + | <h1 class="title">Cellar</h1> |
|
| 31 | + | {error ? ( |
|
| 32 | + | <p class="text-red-400 text-sm">Could not load wines: {error}</p> |
|
| 33 | + | ) : wines.length === 0 ? ( |
|
| 34 | + | <p class="text-gray-400 text-sm">no wines yet</p> |
|
| 35 | + | ) : ( |
|
| 36 | + | <div class="flex flex-col w-full"> |
|
| 37 | + | {wines.map((wine: any) => ( |
|
| 38 | + | <WineCard wine={wine} cellarApiUrl={CELLAR_API_URL} /> |
|
| 39 | + | ))} |
|
| 40 | + | </div> |
|
| 41 | + | )} |
|
| 42 | + | </div> |
|
| 43 | + | </PageLayout> |