profileShare

rasmusjy / tasteprint

Read-only snapshot

No repository description.

main default branch 169 files Expires Sep 13, 2026, 9:06 AM
DestinationPage.tsx 3,423 bytes
1 import type { CSSProperties } from "react";
2 import { useQuery } from "@tanstack/react-query";
3 import { ArrowLeft, Check, Info, Plus } from "lucide-react";
4 import { Link, useParams } from "react-router-dom";
5 import { DishCard } from "../components/DishCard";
6 import { ProgressRing } from "../components/ProgressRing";
7 import { useQuickTaste } from "../components/QuickTaste";
8 import { ErrorState, LoadingScreen } from "../components/Ui";
9 import { api } from "../lib/api";
10 import type { DestinationProgressDetails } from "../types";
11
12 export function DestinationPage() {
13 const { code = "" } = useParams();
14 const { openTaste } = useQuickTaste();
15 const progress = useQuery({
16 queryKey: ["destination-progress", code],
17 queryFn: () => api<DestinationProgressDetails>(`/api/v1/progress/destinations/${code}`),
18 enabled: Boolean(code)
19 });
20
21 if (progress.isLoading) return <LoadingScreen label="Opening this food culture" />;
22 if (progress.isError || !progress.data) return <ErrorState message="This destination could not be loaded." />;
23
24 const data = progress.data;
25 const nextDish = data.dishes.find(item => !item.tried)?.dish;
26
27 return (
28 <div className="page destination-page">
29 <Link className="back-link" to="/app/explore"><ArrowLeft size={16} /> Food atlas</Link>
30
31 <section className="destination-hero" style={{ "--destination-accent": data.progress.destination.accentColor } as CSSProperties}>
32 <div className="destination-hero-code">{data.progress.destination.code}</div>
33 <div className="destination-hero-main">
34 <span className="eyebrow eyebrow-light">{data.progress.destination.regionName}</span>
35 <h1>{data.progress.destination.name}</h1>
36 <p className="destination-local-name">{data.progress.destination.localName}</p>
37 <p>{data.progress.destination.summary}</p>
38 {nextDish && (
39 <button className="button button-paper" type="button" onClick={() => openTaste({ dish: nextDish })}>
40 <Plus size={17} /> Log your next {data.progress.destination.name} dish
41 </button>
42 )}
43 </div>
44 <div className="destination-coverage-block">
45 <ProgressRing value={data.progress.coverage} size="large" label={`${data.progress.destination.name} coverage`} />
46 <span>{data.progress.triedDishes}/{data.progress.totalDishes} core experiences</span>
47 </div>
48 <div className="destination-stamp-outline" aria-hidden="true" />
49 </section>
50
51 <section className="culture-note">
52 <Info size={20} />
53 <div>
54 <strong>Coverage means breadth, not authority.</strong>
55 <p>The six starting points cover daily food, street eating, mornings, sweets, drinks, and a defining dish. No short list can represent every regional kitchen.</p>
56 </div>
57 </section>
58
59 <section className="destination-dishes">
60 <div className="section-heading">
61 <div>
62 <span className="eyebrow">Culinary core</span>
63 <h2>Six ways into the culture</h2>
64 </div>
65 <span className="section-note"><Check size={15} /> Tried dishes count wherever you tasted them</span>
66 </div>
67 <div className="dish-grid dish-grid-two">
68 {data.dishes.map(item => <DishCard key={item.dish.slug} dish={item.dish} tried={item.tried} />)}
69 </div>
70 </section>
71 </div>
72 );
73 }
74