Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ const nextConfig = {
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
{ key: "Content-Security-Policy", value: "frame-ancestors *" },
],
},
Expand Down
38 changes: 23 additions & 15 deletions src/app/docs/(article)/api-usage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export default function ApiUsagePage() {
<div className="space-y-4">
<p className="text-foreground/80">
Both{" "}
<Link href="/docs/places-api" className="font-medium text-primary hover:underline">
<Link
href="/docs/places-api"
className="font-medium text-primary hover:underline"
>
Places API
</Link>{" "}
and{" "}
Expand All @@ -35,22 +38,27 @@ export default function ApiUsagePage() {
Competitions API
</Link>{" "}
are public, unauthenticated, read-only endpoints. This page is the honest answer
to &quot;how much can I call this&quot; - what&apos;s actually enforced, not what
might be assumed.
to &quot;how much can I call this&quot; - what&apos;s actually enforced, not
what might be assumed.
</p>

<Card>
<CardHeader>
<CardTitle>No API key, no rate limiting</CardTitle>
<CardDescription>Nothing throttles requests by IP or client today</CardDescription>
<CardDescription>
Nothing throttles requests by IP or client today
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 text-sm text-foreground/80">
<p>
Neither endpoint requires a key, and neither currently enforces a
requests-per-minute style limit. What bounds them instead is pagination and
caching, below. If that ever changes (a key requirement, a 429 response), it
will show up on the{" "}
<Link href="/docs/changelog" className="font-medium text-primary hover:underline">
<Link
href="/docs/changelog"
className="font-medium text-primary hover:underline"
>
changelog
</Link>{" "}
and this page gets updated alongside it - don&apos;t build something that
Expand All @@ -62,7 +70,9 @@ export default function ApiUsagePage() {
<Card>
<CardHeader>
<CardTitle>Pagination caps</CardTitle>
<CardDescription>The real limit is response size, not request rate</CardDescription>
<CardDescription>
The real limit is response size, not request rate
</CardDescription>
</CardHeader>
<CardContent className="text-sm text-foreground/80">
<ul className="ml-4 list-disc space-y-1.5">
Expand Down Expand Up @@ -98,7 +108,9 @@ export default function ApiUsagePage() {
<Card>
<CardHeader>
<CardTitle>Response caching</CardTitle>
<CardDescription>6 hours - the real reason you rarely need to poll fast</CardDescription>
<CardDescription>
6 hours - the real reason you rarely need to poll fast
</CardDescription>
</CardHeader>
<CardContent className="text-sm text-foreground/80">
<p>
Expand All @@ -107,8 +119,8 @@ export default function ApiUsagePage() {
Cache-Control
</code>{" "}
header with a 6-hour max age. The underlying datasets change a few times a
week at most, so polling more often than that gets you the same data,
served from cache either way.
week at most, so polling more often than that gets you the same data, served
from cache either way.
</p>
</CardContent>
</Card>
Expand All @@ -122,13 +134,9 @@ export default function ApiUsagePage() {
<p>
Nothing stops a tight polling loop today, but nothing has to reward it either.
Respect the 6-hour cache window, page through large pulls with{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
offset
</code>{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">offset</code>{" "}
instead of raising{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
limit
</code>{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">limit</code>{" "}
past what you need, and if you&apos;re running something that calls either
endpoint thousands of times a day, say so in an issue - that&apos;s exactly the
kind of usage that would justify adding real rate limiting.
Expand Down
8 changes: 2 additions & 6 deletions src/app/docs/(article)/embedding-widget/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ export default function EmbeddingWidgetPage() {
<code className="rounded bg-muted px-1.5 py-0.5 font-mono">/embed</code> is a
second, chrome-free version of the map: no navbar, no sidebar, no sign-in - just
the pins, sized to fit wherever you put it. It&apos;s meant to be put inside an{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono">
&lt;iframe&gt;
</code>{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono">&lt;iframe&gt;</code>{" "}
on a school, library, or organization&apos;s own site.
</p>

Expand Down Expand Up @@ -67,9 +65,7 @@ export default function EmbeddingWidgetPage() {
<Card>
<CardHeader>
<CardTitle>Or build the URL yourself</CardTitle>
<CardDescription>
Same query parameters as a shared /map link
</CardDescription>
<CardDescription>Same query parameters as a shared /map link</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-foreground/80">
<ul className="ml-4 list-disc space-y-1.5">
Expand Down
10 changes: 6 additions & 4 deletions src/app/docs/(article)/privacy-data/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const TABLES: { name: string; stores: string; note?: string }[] = [
},
{
name: "user_home",
stores: "One lat/lng, if you've set a home location (powers \"Near me\" sorting).",
stores: 'One lat/lng, if you\'ve set a home location (powers "Near me" sorting).',
},
{
name: "user_events",
Expand All @@ -46,8 +46,8 @@ export default function PrivacyDataPage() {
<div className="space-y-4">
<p className="text-foreground/80">
The map, calendar, and competitions catalog are fully public and need no
account. Signing in only unlocks a handful of private, per-user features -
this page is what those store.
account. Signing in only unlocks a handful of private, per-user features - this
page is what those store.
</p>

<Card>
Expand Down Expand Up @@ -110,7 +110,9 @@ export default function PrivacyDataPage() {
<Card>
<CardHeader>
<CardTitle>Password resets</CardTitle>
<CardDescription>Attempts are rate-limited, not your email itself</CardDescription>
<CardDescription>
Attempts are rate-limited, not your email itself
</CardDescription>
</CardHeader>
<CardContent className="text-sm text-foreground/80">
<p>
Expand Down
14 changes: 8 additions & 6 deletions src/app/docs/(article)/roadmap/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ export default function RoadmapPage() {
<CardContent className="text-sm text-foreground/80">
<ul className="ml-4 list-disc space-y-1.5">
<li>
<span className="font-medium">Geographic depth.</span> Coverage is
deepest in India and a handful of other countries; most of the world has
at most a handful of entries per category, if any. Adding a real,
verifiable place in an under-covered city is one of the highest-value
single contributions anyone can make.
<span className="font-medium">Geographic depth.</span> Coverage is deepest
in India and a handful of other countries; most of the world has at most a
handful of entries per category, if any. Adding a real, verifiable place
in an under-covered city is one of the highest-value single contributions
anyone can make.
</li>
<li>
<span className="font-medium">Re-verification.</span> Most places have
Expand All @@ -102,7 +102,9 @@ export default function RoadmapPage() {
<Card>
<CardHeader>
<CardTitle>How direction actually gets decided</CardTitle>
<CardDescription>Issues and pull requests, not a backlog tool</CardDescription>
<CardDescription>
Issues and pull requests, not a backlog tool
</CardDescription>
</CardHeader>
<CardContent className="text-sm text-foreground/80">
<p>
Expand Down
17 changes: 10 additions & 7 deletions src/app/docs/(article)/verification-process/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export default function VerificationProcessPage() {
<Card>
<CardHeader>
<CardTitle>What the badge means</CardTitle>
<CardDescription>A green badge with the check date, next to the pin</CardDescription>
<CardDescription>
A green badge with the check date, next to the pin
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 text-sm text-foreground/80">
<p>
Expand Down Expand Up @@ -78,15 +80,15 @@ export default function VerificationProcessPage() {
>
<p>
Confirm the address and entrance still match what&apos;s on the map, and that
the place is still open and operating. If anything has actually changed - name,
address, permanently closed - that&apos;s a correction, not a verification;
say so in the issue instead of confirming stale data.
the place is still open and operating. If anything has actually changed -
name, address, permanently closed - that&apos;s a correction, not a
verification; say so in the issue instead of confirming stale data.
</p>
</StepCard>

<StepCard
step={3}
title="File a &quot;Verify a place&quot; issue"
title='File a "Verify a place" issue'
description="One small form, no pull request needed"
isLast
>
Expand All @@ -100,8 +102,9 @@ export default function VerificationProcessPage() {
>
Verify a place
</a>{" "}
issue with the place&apos;s id (from its dataset entry, or the map&apos;s share
link), the date you checked it, and what you found. A maintainer then adds the{" "}
issue with the place&apos;s id (from its dataset entry, or the map&apos;s
share link), the date you checked it, and what you found. A maintainer then
adds the{" "}
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
verified
</code>{" "}
Expand Down
7 changes: 6 additions & 1 deletion src/components/home/city-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ export function CitySwitcher({ cities, className }: CitySwitcherProps) {
}

return (
<Select open={open} onOpenChange={handleOpenChange} onValueChange={handleSelect} value="">
<Select
open={open}
onOpenChange={handleOpenChange}
onValueChange={handleSelect}
value=""
>
<SelectTrigger
className={cn("w-full sm:w-64", className)}
aria-label="Jump to a city"
Expand Down
5 changes: 1 addition & 4 deletions src/components/layout/app-main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ export function AppMain({ children }: { children: React.ReactNode }) {
const isEmbed = pathname.startsWith("/embed");

return (
<main
id="main-content"
className={cn("flex flex-1 flex-col", !isEmbed && "pt-14")}
>
<main id="main-content" className={cn("flex flex-1 flex-col", !isEmbed && "pt-14")}>
{children}
</main>
);
Expand Down
15 changes: 13 additions & 2 deletions src/components/map/category-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { GraduationCap, Landmark, Library, MapPin, Plane, type LucideIcon } from "lucide-react";
import {
GraduationCap,
Landmark,
Library,
MapPin,
Plane,
type LucideIcon,
} from "lucide-react";

import type { PlaceType } from "@/lib/types";
import { PLACE_TYPE_COLORS } from "@/lib/map";
Expand Down Expand Up @@ -29,6 +36,10 @@ export interface CategoryIconProps {
export function CategoryIcon({ type, className }: CategoryIconProps) {
const Icon = PLACE_TYPE_ICONS[type];
return (
<Icon aria-hidden className={cn("size-4", className)} style={{ color: PLACE_TYPE_COLORS[type] }} />
<Icon
aria-hidden
className={cn("size-4", className)}
style={{ color: PLACE_TYPE_COLORS[type] }}
/>
);
}
10 changes: 8 additions & 2 deletions src/components/map/compare-places-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ function pairDistances(places: Place[]): PairDistance[] {
* between each pair. Callers are expected to pass 2 or 3 places; fewer than
* 2 just renders the one card with no distances.
*/
export function ComparePlacesSheet({ places, open, onOpenChange }: ComparePlacesSheetProps) {
export function ComparePlacesSheet({
places,
open,
onOpenChange,
}: ComparePlacesSheetProps) {
const distances = places.length > 1 ? pairDistances(places) : [];

return (
Expand Down Expand Up @@ -76,7 +80,9 @@ export function ComparePlacesSheet({ places, open, onOpenChange }: ComparePlaces
/>
{PLACE_TYPE_LABELS[place.type]}
</div>
<p className="text-xs text-muted-foreground">{humanizeCity(place.city)}</p>
<p className="text-xs text-muted-foreground">
{humanizeCity(place.city)}
</p>
{place.address && (
<p className="text-xs leading-snug text-muted-foreground">
{place.address}
Expand Down
10 changes: 7 additions & 3 deletions src/components/map/embed-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export function EmbedMap({ places }: EmbedMapProps) {
);

const visible = React.useMemo(
() => filterPlaces(places, { types: state.types, city: state.city, query: state.query }),
() =>
filterPlaces(places, { types: state.types, city: state.city, query: state.query }),
[places, state.types, state.city, state.query],
);

Expand All @@ -53,7 +54,9 @@ export function EmbedMap({ places }: EmbedMapProps) {
focusId={state.placeId}
focusBounds={focusBounds}
closePopupTrigger={0}
center={state.lat !== null && state.lng !== null ? [state.lat, state.lng] : undefined}
center={
state.lat !== null && state.lng !== null ? [state.lat, state.lng] : undefined
}
zoom={state.zoom ?? studyMapConfig.defaultZoom}
/>
</div>
Expand All @@ -63,7 +66,8 @@ export function EmbedMap({ places }: EmbedMapProps) {
rel="noreferrer"
className="flex h-7 shrink-0 items-center justify-center bg-card text-xs font-medium text-muted-foreground hover:text-foreground"
>
{visible.length} {visible.length === 1 ? "place" : "places"} · View on {site.name} ↗
{visible.length} {visible.length === 1 ? "place" : "places"} · View on {site.name}{" "}
↗
</Link>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion src/components/map/heatmap-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ interface HeatmapToggleProps {
* layer to `map-view.tsx`, which is out of scope here. Styled to match
* `NearMeButton` so both sit naturally in the same map-controls stack.
*/
export function HeatmapToggle({ pressed, onPressedChange, className }: HeatmapToggleProps) {
export function HeatmapToggle({
pressed,
onPressedChange,
className,
}: HeatmapToggleProps) {
return (
<Button
type="button"
Expand Down
4 changes: 3 additions & 1 deletion src/components/map/place-detail-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ export function PlaceDetailDrawer({ place, open, onOpenChange }: PlaceDetailDraw

<p className="text-[11px] text-muted-foreground/75">
Added by{" "}
<span className="font-medium text-muted-foreground">{place.added_by}</span>
<span className="font-medium text-muted-foreground">
{place.added_by}
</span>
</p>
</div>
</>
Expand Down
6 changes: 3 additions & 3 deletions src/components/map/place-photo-gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export function PlacePhotoGallery({ place }: PlacePhotoGalleryProps) {
</div>
<p className="flex items-start gap-1.5 text-xs text-muted-foreground">
<MapPinOff className="mt-0.5 size-3.5 shrink-0" aria-hidden />
Street View not available here? Not every location has coverage -
a blank or grey preview above means Google hasn&apos;t mapped this spot yet,
not that something&apos;s broken.
Street View not available here? Not every location has coverage - a blank or
grey preview above means Google hasn&apos;t mapped this spot yet, not that
something&apos;s broken.
</p>
</CardContent>
</Card>
Expand Down
4 changes: 2 additions & 2 deletions src/components/map/report-issue-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ export function ReportIssueDialog({ place, open, onOpenChange }: ReportIssueDial
<DialogHeader>
<DialogTitle>Report an issue</DialogTitle>
<DialogDescription>
{place.name} - opens a pre-filled GitHub issue for a maintainer to review, no
account or Git knowledge needed beyond a GitHub sign-in to submit it.
{place.name} - opens a pre-filled GitHub issue for a maintainer to review,
no account or Git knowledge needed beyond a GitHub sign-in to submit it.
</DialogDescription>
</DialogHeader>

Expand Down
8 changes: 5 additions & 3 deletions src/components/map/route-eta-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ export function RouteEtaCard({ home, place }: RouteEtaCardProps) {
<div className="flex items-start gap-2 rounded-md border border-border bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground">
<Footprints className="mt-0.5 size-3.5 shrink-0" aria-hidden />
<p>
<span className="font-medium text-foreground">{formatDistance(distanceKm)} away</span>{" "}
from home, straight-line. Roughly {formatWalkingEta(distanceKm)} walking at a 5 km/h
pace, not a real route, so actual walking distance and time can differ.
<span className="font-medium text-foreground">
{formatDistance(distanceKm)} away
</span>{" "}
from home, straight-line. Roughly {formatWalkingEta(distanceKm)} walking at a 5
km/h pace, not a real route, so actual walking distance and time can differ.
</p>
</div>
);
Expand Down
Loading
Loading