LandingPage.tsx
3,725 bytes
| 1 | import { useQuery } from "@tanstack/react-query"; |
|---|---|
| 2 | import { ArrowRight, Camera, Check, MapPinned, Plane } from "lucide-react"; |
| 3 | import { Link, Navigate } from "react-router-dom"; |
| 4 | import { Logo } from "../components/Logo"; |
| 5 | import { WorldMap } from "../components/WorldMap"; |
| 6 | import { useAuth } from "../lib/auth"; |
| 7 | import { api } from "../lib/api"; |
| 8 | import type { Destination, DestinationProgress } from "../types"; |
| 9 | |
| 10 | const sampleCoverage: Record<string, number> = { |
| 11 | EE: 82, JP: 44, MX: 35, PT: 68, GE: 19, VN: 52 |
| 12 | }; |
| 13 | |
| 14 | export function LandingPage() { |
| 15 | const { user, loading } = useAuth(); |
| 16 | const destinations = useQuery({ |
| 17 | queryKey: ["destinations"], |
| 18 | queryFn: () => api<Destination[]>("/api/v1/catalog/destinations") |
| 19 | }); |
| 20 | |
| 21 | if (!loading && user) { |
| 22 | return <Navigate to="/app" replace />; |
| 23 | } |
| 24 | |
| 25 | const mapData: DestinationProgress[] = (destinations.data ?? []).map(destination => ({ |
| 26 | destination, |
| 27 | coverage: sampleCoverage[destination.code] ?? 0, |
| 28 | triedDishes: 0, |
| 29 | totalDishes: destination.dishCount |
| 30 | })); |
| 31 | |
| 32 | return ( |
| 33 | <div className="landing-page"> |
| 34 | <header className="landing-nav"> |
| 35 | <Logo /> |
| 36 | <div> |
| 37 | <Link className="button button-ghost" to="/login">Sign in</Link> |
| 38 | <Link className="button button-primary" to="/register">Start your map</Link> |
| 39 | </div> |
| 40 | </header> |
| 41 | |
| 42 | <main> |
| 43 | <section className="landing-hero"> |
| 44 | <div className="hero-copy"> |
| 45 | <span className="eyebrow">A food passport for actual trips</span> |
| 46 | <h1>You visited it.<br /><em>Did you taste it?</em></h1> |
| 47 | <p> |
| 48 | Tasteprint remembers the dishes behind every journey, shows the important experiences you missed, |
| 49 | and turns your next destination into a five-bite mission. |
| 50 | </p> |
| 51 | <div className="hero-actions"> |
| 52 | <Link className="button button-primary button-large" to="/register"> |
| 53 | Make your Tasteprint <ArrowRight size={18} /> |
| 54 | </Link> |
| 55 | <Link className="text-link" to="/login?demo=true">Open the demo passport</Link> |
| 56 | </div> |
| 57 | </div> |
| 58 | |
| 59 | <div className="hero-ticket" aria-label="Example culinary passport"> |
| 60 | <div className="ticket-route"> |
| 61 | <span>TLL</span> |
| 62 | <i /> |
| 63 | <Plane size={17} /> |
| 64 | <i /> |
| 65 | <span>WORLD</span> |
| 66 | </div> |
| 67 | <div className="ticket-score"> |
| 68 | <span>Culinary coverage</span> |
| 69 | <strong>41%</strong> |
| 70 | <small>6 food cultures started</small> |
| 71 | </div> |
| 72 | <WorldMap destinations={mapData} interactive={false} className="hero-world-map" /> |
| 73 | <div className="ticket-stamp">TASTED<br />06</div> |
| 74 | </div> |
| 75 | </section> |
| 76 | |
| 77 | <section className="landing-proof" aria-label="How Tasteprint works"> |
| 78 | <article> |
| 79 | <Camera size={22} /> |
| 80 | <span>At the table</span> |
| 81 | <h2>Log the dish, not only the restaurant.</h2> |
| 82 | <p>A photo, place, date, and one note are enough.</p> |
| 83 | </article> |
| 84 | <article> |
| 85 | <MapPinned size={22} /> |
| 86 | <span>Before the trip</span> |
| 87 | <h2>Get five bites that cover more than the obvious.</h2> |
| 88 | <p>Every mission mixes daily food, street bites, mornings, sweets, drinks, and a defining dish.</p> |
| 89 | </article> |
| 90 | <article> |
| 91 | <Check size={22} /> |
| 92 | <span>After the trip</span> |
| 93 | <h2>See what the destination added to your map.</h2> |
| 94 | <p>Share a clear Tasteprint instead of another photo dump.</p> |
| 95 | </article> |
| 96 | </section> |
| 97 | </main> |
| 98 | </div> |
| 99 | ); |
| 100 | } |
| 101 | |