Add privacy transparency: dedicated /privacy page, onboarding privacy note, Sasquatch VPN/Tor Q&A, footer link
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
adc90caae9
commit
33cfe692ed
5 changed files with 258 additions and 1 deletions
|
|
@ -56,6 +56,7 @@ const SignersPage = lazy(() => import("./pages/SignersPage"));
|
||||||
const StashPage = lazy(() => import("./pages/StashPage"));
|
const StashPage = lazy(() => import("./pages/StashPage"));
|
||||||
const NIP19Page = lazy(() => import("./pages/NIP19Page").then(m => ({ default: m.NIP19Page })));
|
const NIP19Page = lazy(() => import("./pages/NIP19Page").then(m => ({ default: m.NIP19Page })));
|
||||||
const LicensePage = lazy(() => import("./pages/LicensePage"));
|
const LicensePage = lazy(() => import("./pages/LicensePage"));
|
||||||
|
const PrivacyPage = lazy(() => import("./pages/PrivacyPage"));
|
||||||
const SupportersPage = lazy(() => import("./pages/SupportersPage"));
|
const SupportersPage = lazy(() => import("./pages/SupportersPage"));
|
||||||
const RiderCardPage = lazy(() => import("./pages/RiderCardPage"));
|
const RiderCardPage = lazy(() => import("./pages/RiderCardPage"));
|
||||||
const AmberCallbackPage = lazy(() => import("./pages/AmberCallbackPage"));
|
const AmberCallbackPage = lazy(() => import("./pages/AmberCallbackPage"));
|
||||||
|
|
@ -140,6 +141,7 @@ export function AppRouter() {
|
||||||
{/* Info pages */}
|
{/* Info pages */}
|
||||||
<Route path="/card" element={<RiderCardPage />} />
|
<Route path="/card" element={<RiderCardPage />} />
|
||||||
<Route path="/license" element={<LicensePage />} />
|
<Route path="/license" element={<LicensePage />} />
|
||||||
|
<Route path="/privacy" element={<PrivacyPage />} />
|
||||||
<Route path="/supporters" element={<SupportersPage />} />
|
<Route path="/supporters" element={<SupportersPage />} />
|
||||||
{/* Amber NIP-55 callback — Amber redirects here after signing/login */}
|
{/* Amber NIP-55 callback — Amber redirects here after signing/login */}
|
||||||
<Route path="/amber-callback" element={<AmberCallbackPage />} />
|
<Route path="/amber-callback" element={<AmberCallbackPage />} />
|
||||||
|
|
|
||||||
|
|
@ -882,6 +882,12 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
||||||
>
|
>
|
||||||
License
|
License
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/privacy"
|
||||||
|
className="hover:text-[#ccff00] transition-colors font-medium"
|
||||||
|
>
|
||||||
|
Privacy
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/supporters"
|
to="/supporters"
|
||||||
className="hover:text-[#ccff00] transition-colors font-medium"
|
className="hover:text-[#ccff00] transition-colors font-medium"
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,16 @@ const STATIC_FAQ: Record<string, string> = {
|
||||||
'hey': "Hey skater! 🤙 Ask me about spots near you, upcoming events, coaches, or anything SkateSpots.",
|
'hey': "Hey skater! 🤙 Ask me about spots near you, upcoming events, coaches, or anything SkateSpots.",
|
||||||
'who are you': "I'm The Sasquatch 🦧 — the AI guide for SkateSpots. I can find skate spots worldwide, look up events, coaches, filmers, and gear listings. All powered by live Nostr data. Ask away!",
|
'who are you': "I'm The Sasquatch 🦧 — the AI guide for SkateSpots. I can find skate spots worldwide, look up events, coaches, filmers, and gear listings. All powered by live Nostr data. Ask away!",
|
||||||
'what can you do': "I can find skate spots by city or country, look up upcoming events, find coaches and filmers in The Lab, search gear listings, and answer questions about how the app works. Try: 'spots in Bangkok' or 'find a coach'! 🛹",
|
'what can you do': "I can find skate spots by city or country, look up upcoming events, find coaches and filmers in The Lab, search gear listings, and answer questions about how the app works. Try: 'spots in Bangkok' or 'find a coach'! 🛹",
|
||||||
'what is nostr': "Nostr is a simple, open protocol for decentralized social networks. Think Twitter but no one can ban you. You use cryptographic keys instead of usernames — one account works everywhere across all Nostr apps! 🔑",
|
'what is nostr': "Nostr is a simple, open protocol for decentralized social networks. Think Twitter, but no single company controls it — you use cryptographic keys instead of usernames, choose which relays store your data (or run your own), and one account works across every Nostr app! 🔑",
|
||||||
|
// ── Privacy — answered locally so it's always accurate ──
|
||||||
|
'is this private': "Here's the honest rundown 🛡️\n\n• Your **GPS location stays on your device** — 'Near Me' just centers the map. It's never sent to us or published.\n• **Spots you add are public** (that's the point of a shared map) — so don't drop a pin on your own front door.\n• **DMs & crew chats are end-to-end encrypted** — only members can read them.\n• Like any website, servers you connect to (map tiles, relays) can see your **IP**. Want to hide that too? Use a VPN or Tor.\n\nFull details on the [Privacy page](/privacy). 🛹",
|
||||||
|
'privacy': "Quick version 🛡️: your GPS never leaves your device, spots you publish are public by design, and DMs/crew chats are end-to-end encrypted. Servers can still see your IP (normal for any website) — a VPN or Tor hides that if you want. Full breakdown on the [Privacy page](/privacy).",
|
||||||
|
'am i tracked': "SkateSpots doesn't track you — no ads, no analytics profiling, no data selling, and there's no SkateSpots server collecting your location. Your GPS stays on your device. The one thing servers see (map tiles, relays) is your **IP** — same as any website. Use a VPN or Tor to hide that. More on the [Privacy page](/privacy). 🛡️",
|
||||||
|
'do you track': "Nope — no tracking, no ads, no data harvesting. Your GPS stays on your device and there's no central server logging you. Servers you connect to can see your IP (like any site); a VPN/Tor hides that. See the [Privacy page](/privacy). 🛹",
|
||||||
|
'vpn': "Totally optional, but solid if you want extra privacy 🛡️. A VPN hides your **IP** (rough location) from the servers SkateSpots talks to — map tiles, relays, etc. Pick a no-logs provider you trust (e.g. Mullvad); avoid sketchy free ones.\n\nHeads up: a VPN does **not** hide the spots you publish (those are public), and don't turn on browser GPS if you're trying to stay anonymous. Full details on the [Privacy page](/privacy).",
|
||||||
|
'tor': "Tor Browser gives you the strongest IP privacy — it routes you through several relays so servers can't see your real IP 🧅. Trade-off: it's slower, and maps + some Nostr relays may lag or block Tor exits. Great for the extra-cautious.\n\nRemember: Tor hides your IP, not the spots you publish, and don't enable browser GPS if anonymity is the goal. More on the [Privacy page](/privacy).",
|
||||||
|
'hide my ip': "Two free options 🛡️: a **reputable VPN** (easy, fast — hides your IP behind their servers) or **Tor Browser** (max privacy, slower). Either one stops the servers SkateSpots connects to from seeing your real IP. Note they don't hide spots you publish. See the [Privacy page](/privacy).",
|
||||||
|
'is my location': "Your location is safe 📍 — 'Near Me' reads your GPS on your device only, to center the map and sort nearby spots. It's never published to Nostr, never sent to us, never shared. Only spots you deliberately add are public. Details on the [Privacy page](/privacy).",
|
||||||
'what are zaps': "Zaps are instant Bitcoin Lightning tips sent over Nostr! Click ⚡ on any content, send sats directly to the creator's wallet. Like hitting 'like' but with real money. Instant, peer-to-peer, no middlemen. ⚡",
|
'what are zaps': "Zaps are instant Bitcoin Lightning tips sent over Nostr! Click ⚡ on any content, send sats directly to the creator's wallet. Like hitting 'like' but with real money. Instant, peer-to-peer, no middlemen. ⚡",
|
||||||
'how to login': "Two ways:\n1. **Browser extension** (Desktop): Install Alby or nos2x, then click 'Log in'\n2. **New user**: Click 'Sign up' to generate keys. Download your nsec (secret key) and SAVE IT — this is your account forever!",
|
'how to login': "Two ways:\n1. **Browser extension** (Desktop): Install Alby or nos2x, then click 'Log in'\n2. **New user**: Click 'Sign up' to generate keys. Download your nsec (secret key) and SAVE IT — this is your account forever!",
|
||||||
'what is cashu': "Built-in Bitcoin wallet! Cashu ecash tokens backed by Lightning — works offline at skate spots, privacy-focused (mints can't track payments). Go to Wallet in Settings to use it. 🥜",
|
'what is cashu': "Built-in Bitcoin wallet! Cashu ecash tokens backed by Lightning — works offline at skate spots, privacy-focused (mints can't track payments). Go to Wallet in Settings to use it. 🥜",
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,17 @@ export default function OnboardingPage() {
|
||||||
<BookOpen className="h-3.5 w-3.5" />
|
<BookOpen className="h-3.5 w-3.5" />
|
||||||
Deep dive: How Nostr works
|
Deep dive: How Nostr works
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* Calm, optional privacy note — links to the full Privacy page */}
|
||||||
|
<p className="text-[11px] text-gray-500 leading-relaxed mt-4 pt-3 border-t border-white/6">
|
||||||
|
<Shield className="h-3 w-3 inline-block mr-1 -mt-0.5 text-gray-500" />
|
||||||
|
Privacy tip: your GPS never leaves your device. Servers you connect to (like map tiles)
|
||||||
|
can still see your IP — if you want to hide that too, a VPN or Tor works. Totally optional.{' '}
|
||||||
|
<Link to="/privacy" className="text-purple-400/80 hover:text-purple-400 underline">
|
||||||
|
Read our privacy page
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Wallet (optional) ── */}
|
{/* ── Wallet (optional) ── */}
|
||||||
|
|
|
||||||
229
src/pages/PrivacyPage.tsx
Normal file
229
src/pages/PrivacyPage.tsx
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
import { useSeoMeta } from '@unhead/react';
|
||||||
|
import { Layout } from '@/components/Layout';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Shield,
|
||||||
|
MapPin,
|
||||||
|
Globe,
|
||||||
|
Eye,
|
||||||
|
Lock,
|
||||||
|
Wifi,
|
||||||
|
Key,
|
||||||
|
CheckCircle2,
|
||||||
|
AlertTriangle,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
// ─── Small building blocks ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function Section({
|
||||||
|
icon: Icon,
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
icon: React.ComponentType<{ className?: string }>;
|
||||||
|
color: string;
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="rounded-2xl border border-white/8 bg-white/3 p-5">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<div
|
||||||
|
className="w-9 h-9 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ background: `${color}1a`, border: `1px solid ${color}40` }}
|
||||||
|
>
|
||||||
|
<Icon className="h-5 w-5" style={{ color }} />
|
||||||
|
</div>
|
||||||
|
<h2 className="font-black text-white uppercase tracking-wide text-lg">{title}</h2>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-400 leading-relaxed space-y-3">{children}</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GoodPoint({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-start gap-2.5">
|
||||||
|
<CheckCircle2 className="h-4 w-4 text-green-400 flex-shrink-0 mt-0.5" />
|
||||||
|
<span>{children}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Caveat({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-start gap-2.5">
|
||||||
|
<AlertTriangle className="h-4 w-4 text-amber-400 flex-shrink-0 mt-0.5" />
|
||||||
|
<span>{children}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PrivacyPage() {
|
||||||
|
useSeoMeta({
|
||||||
|
title: 'Privacy — SkateSpots',
|
||||||
|
description:
|
||||||
|
'How SkateSpots handles your data: your GPS stays on your device, spots you publish are public, and how to hide your IP with a VPN or Tor if you want to.',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="container max-w-2xl px-4 py-10 md:py-16">
|
||||||
|
{/* Hero */}
|
||||||
|
<div className="text-center mb-10">
|
||||||
|
<div
|
||||||
|
className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-[#ccff00]/10 border-2 border-[#ccff00]/40 mb-5"
|
||||||
|
>
|
||||||
|
<Shield className="h-8 w-8 text-[#ccff00]" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl md:text-5xl font-black uppercase leading-none mb-4">
|
||||||
|
<span className="text-white">Your </span>
|
||||||
|
<span style={{ color: '#ccff00' }}>Privacy</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-400 text-base max-w-lg mx-auto leading-relaxed">
|
||||||
|
No tracking, no ads, no data selling. Here's exactly what stays on your device,
|
||||||
|
what's public, and how to lock things down further if you want to.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Location stays local */}
|
||||||
|
<Section icon={MapPin} color="#ff0040" title="Your location stays on your device">
|
||||||
|
<p>
|
||||||
|
When you tap <strong className="text-white">"Near Me,"</strong> your GPS location is
|
||||||
|
read on your device and used only to center the map and sort nearby spots.
|
||||||
|
</p>
|
||||||
|
<GoodPoint>It is <strong className="text-white">never</strong> published to Nostr.</GoodPoint>
|
||||||
|
<GoodPoint>It is <strong className="text-white">never</strong> sent to SkateSpots — there is no SkateSpots server collecting it.</GoodPoint>
|
||||||
|
<GoodPoint>It is <strong className="text-white">never</strong> shared with any third party.</GoodPoint>
|
||||||
|
<GoodPoint>
|
||||||
|
On the native app we use your phone's own location system directly and skip Google
|
||||||
|
Play Services — so it works on de-Googled phones (GrapheneOS, /e/OS) and Google
|
||||||
|
never sees it.
|
||||||
|
</GoodPoint>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
Weather is off by default. When you turn it on, it looks up conditions for the
|
||||||
|
<em> spot's </em> public location — not yours.
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Public by design */}
|
||||||
|
<Section icon={Eye} color="#ff6600" title="What's public by design">
|
||||||
|
<p>
|
||||||
|
SkateSpots is a shared map, so some things are meant to be seen by everyone. When you
|
||||||
|
choose to publish them, these are public on the Nostr network:
|
||||||
|
</p>
|
||||||
|
<Caveat>
|
||||||
|
<strong className="text-white">Spots you add</strong> — including their coordinates.
|
||||||
|
If you add a spot at your exact location, that location becomes public. Think before
|
||||||
|
you drop a pin on your own driveway.
|
||||||
|
</Caveat>
|
||||||
|
<Caveat><strong className="text-white">Reviews, comments, and your public profile</strong> (name, picture).</Caveat>
|
||||||
|
<Caveat>Who you follow, and public reactions/zaps.</Caveat>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
A VPN or Tor does <strong>not</strong> hide the content of what you publish — a spot's
|
||||||
|
coordinates are public regardless. Privacy tools hide your network identity (your IP),
|
||||||
|
not the things you choose to post.
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Encrypted */}
|
||||||
|
<Section icon={Lock} color="#14b8a6" title="What's private & encrypted">
|
||||||
|
<GoodPoint><strong className="text-white">Direct messages</strong> and <strong className="text-white">crew chats</strong> are end-to-end encrypted (NIP-17). Only the participants can read them.</GoodPoint>
|
||||||
|
<GoodPoint><strong className="text-white">My Stash</strong> — your private gear inventory — is encrypted to your own key.</GoodPoint>
|
||||||
|
<GoodPoint><strong className="text-white">Crew-only members</strong> can choose to be visible only to their crew-mates.</GoodPoint>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* IP / network */}
|
||||||
|
<Section icon={Wifi} color="#a855f7" title="What leaves your device: your IP">
|
||||||
|
<p>
|
||||||
|
Like every website, the servers SkateSpots talks to can see your{' '}
|
||||||
|
<strong className="text-white">IP address</strong> (a rough indicator of your region).
|
||||||
|
This is normal for any web or map app and isn't unique to us. Those servers are:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-5 space-y-1 text-gray-400">
|
||||||
|
<li><strong className="text-white">Map tiles</strong> (OpenStreetMap) — sees your IP and which area of the map you look at.</li>
|
||||||
|
<li><strong className="text-white">Nostr relays</strong> — see your IP when the app reads/publishes.</li>
|
||||||
|
<li><strong className="text-white">Weather</strong> (Open-Meteo, opt-in) and <strong className="text-white">image uploads</strong> (Blossom), when you use them.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
None of these receive your GPS location — but they do see your IP. If you'd rather hide
|
||||||
|
that too, read the next part.
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* VPN / Tor */}
|
||||||
|
<Section icon={Globe} color="#06b6d4" title="Want more privacy? VPN or Tor (optional)">
|
||||||
|
<p>
|
||||||
|
For most skaters this isn't necessary — but if you want to hide your IP and rough
|
||||||
|
location from the servers above, you have two free options:
|
||||||
|
</p>
|
||||||
|
<div className="grid sm:grid-cols-2 gap-3 my-1">
|
||||||
|
<div className="p-3.5 rounded-xl bg-white/4 border border-white/8">
|
||||||
|
<p className="font-bold text-white text-sm mb-1">🛡️ A reputable VPN</p>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
Hides your IP behind the VPN's servers. Easy, fast, works everywhere. Pick a
|
||||||
|
no-logs provider you trust (e.g. Mullvad). Avoid sketchy "free" VPNs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-3.5 rounded-xl bg-white/4 border border-white/8">
|
||||||
|
<p className="font-bold text-white text-sm mb-1">🧅 Tor Browser</p>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
Free, maximum IP privacy, routes through multiple relays. Slower — maps and some
|
||||||
|
Nostr relays may lag or block Tor exit nodes. Great for the extra-cautious.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl bg-amber-500/8 border border-amber-500/20 p-3.5 space-y-2">
|
||||||
|
<p className="text-xs font-black uppercase tracking-wide text-amber-400 flex items-center gap-1.5">
|
||||||
|
<AlertTriangle className="h-3.5 w-3.5" /> Read this so you're not fooled
|
||||||
|
</p>
|
||||||
|
<Caveat>
|
||||||
|
A VPN/Tor hides your <strong className="text-white">IP</strong> — not the spots you
|
||||||
|
publish. A pin at your home is public whether or not you use a VPN.
|
||||||
|
</Caveat>
|
||||||
|
<Caveat>
|
||||||
|
Don't enable <strong className="text-white">"Near Me" / browser GPS</strong> if your
|
||||||
|
goal is to stay anonymous — precise GPS overrides any IP hiding.
|
||||||
|
</Caveat>
|
||||||
|
<Caveat>
|
||||||
|
No tool makes you fully "anonymous." These reduce what servers can infer about your
|
||||||
|
network location — that's it.
|
||||||
|
</Caveat>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Keys */}
|
||||||
|
<Section icon={Key} color="#facc15" title="Your keys, your account">
|
||||||
|
<p>
|
||||||
|
Your account is a cryptographic key pair that you own. The safest way to log in is a{' '}
|
||||||
|
<strong className="text-white">signer</strong> (Alby, nos2x, or Amber on Android) so
|
||||||
|
your secret key never gets pasted into the app.
|
||||||
|
</p>
|
||||||
|
<Caveat>
|
||||||
|
If you paste your <code className="text-[#ccff00] text-xs bg-white/5 px-1 py-0.5 rounded">nsec</code>{' '}
|
||||||
|
directly, it's stored in your browser. Use a signer extension instead when you can, and
|
||||||
|
keep your nsec backup somewhere safe offline.
|
||||||
|
</Caveat>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="text-center pt-4 pb-2">
|
||||||
|
<p className="text-xs text-gray-600">
|
||||||
|
Questions? Ask{' '}
|
||||||
|
<span className="text-[#ccff00]">The Sasquatch</span> in-app, or read the{' '}
|
||||||
|
<Link to="/onboarding" className="text-[#ccff00]/80 hover:text-[#ccff00] underline">
|
||||||
|
getting started guide
|
||||||
|
</Link>
|
||||||
|
{' '}·{' '}
|
||||||
|
<Link to="/about" className="text-[#ccff00]/80 hover:text-[#ccff00] underline">
|
||||||
|
About
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue