profileShare

rasmusjy / tasteprint

Read-only snapshot

No repository description.

main default branch 169 files Expires Sep 13, 2026, 9:06 AM
PublicTasteprintPage.tsx 4,563 bytes
1 import { useQuery } from "@tanstack/react-query";
2 import { ArrowRight, MapPin, Utensils } from "lucide-react";
3 import { Link, useParams } from "react-router-dom";
4 import { Logo } from "../components/Logo";
5 import { Avatar, ErrorState, LoadingScreen } from "../components/Ui";
6 import { WorldMap } from "../components/WorldMap";
7 import { api } from "../lib/api";
8 import { formatDate } from "../lib/format";
9 import type { PublicTasteprint } from "../types";
10
11 export function PublicTasteprintPage() {
12 const { shareSlug } = useParams();
13 const tasteprint = useQuery({
14 queryKey: ["public-tasteprint", shareSlug],
15 queryFn: () => api<PublicTasteprint>(`/api/v1/public/tasteprints/${shareSlug}`),
16 enabled: Boolean(shareSlug),
17 retry: false
18 });
19
20 if (tasteprint.isLoading) return <div className="public-state"><LoadingScreen /></div>;
21 if (tasteprint.isError || !tasteprint.data) {
22 return <div className="public-state"><Logo /><ErrorState message="This Tasteprint is private or does not exist." /><Link className="button button-primary" to="/">Open Tasteprint</Link></div>;
23 }
24 const data = tasteprint.data;
25 const explored = data.tasteprint.destinations.filter(item => item.coverage > 0).sort((a, b) => b.coverage - a.coverage);
26
27 return (
28 <div className="public-page">
29 <header className="public-nav"><Logo /><Link className="button button-primary" to="/register">Make your own</Link></header>
30 <main className="public-main">
31 <section className="public-passport">
32 <div className="public-person">
33 <Avatar name={data.user.displayName} url={data.user.avatarUrl} size="large" />
34 <div><span className="eyebrow eyebrow-light">Culinary passport</span><h1>{data.user.displayName}'s Tasteprint</h1>
35 {(data.user.homeCity || data.user.homeCountryCode) && <p><MapPin size={15} /> {[data.user.homeCity, data.user.homeCountryCode].filter(Boolean).join(", ")}</p>}
36 </div>
37 </div>
38 {data.user.bio && <p className="public-bio">{data.user.bio}</p>}
39 <WorldMap destinations={data.tasteprint.destinations} interactive={false} className="public-map" />
40 <div className="ticket-stats public-stats">
41 <div><strong>{data.tasteprint.stats.uniqueDishes}</strong><span>Dishes</span></div>
42 <div><strong>{data.tasteprint.stats.countriesTasted}</strong><span>Countries</span></div>
43 <div><strong>{data.tasteprint.stats.regionsTasted}</strong><span>Regions</span></div>
44 <div><strong>{data.tasteprint.stats.worldCoverage}%</strong><span>Atlas</span></div>
45 </div>
46 </section>
47
48 <section className="public-section">
49 <div className="section-heading"><div><span className="eyebrow">Food cultures</span><h2>Strongest parts of the map</h2></div></div>
50 <div className="public-country-list">
51 {explored.slice(0, 6).map(item => (
52 <article key={item.destination.code}>
53 <span style={{ backgroundColor: item.destination.accentColor }}>{item.destination.code}</span>
54 <div><strong>{item.destination.name}</strong><small>{item.triedDishes} of {item.totalDishes} core dishes</small></div>
55 <b>{item.coverage}%</b>
56 </article>
57 ))}
58 </div>
59 </section>
60
61 {data.tasteprint.recentTastings.length > 0 && (
62 <section className="public-section">
63 <div className="section-heading"><div><span className="eyebrow">Recent marks</span><h2>Latest dishes remembered</h2></div></div>
64 <div className="public-tasting-grid">
65 {data.tasteprint.recentTastings.slice(0, 6).map(tasting => (
66 <article key={tasting.id}>
67 <div className="public-tasting-photo" style={{ backgroundColor: tasting.dish.destinationAccent }}>
68 {tasting.photoUrl ? <img src={tasting.photoUrl} alt="" /> : <Utensils size={24} />}
69 </div>
70 <div><span>{tasting.dish.destinationName}</span><h3>{tasting.dish.name}</h3><p>{tasting.city}, {formatDate(tasting.tastedOn)}</p></div>
71 </article>
72 ))}
73 </div>
74 </section>
75 )}
76
77 <section className="public-cta"><div><span className="eyebrow eyebrow-light">Your turn</span><h2>Which parts of the world have you actually tasted?</h2></div><Link className="button button-light" to="/register">Start your map <ArrowRight size={17} /></Link></section>
78 </main>
79 </div>
80 );
81 }
82