diff --git a/README.md b/README.md index a1890bf..9bd5826 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ A decentralized skateboarding community platform built on Nostr. Discover skate - 🌀️ **Weather Integration** - Check conditions before you skate - πŸ” **Nostr Authentication** - Decentralized login with your Nostr keys - πŸ“± **PWA Support** - Install as a native app on any device -- ❀️ **Supporters** - Community funding via Geyser (Bitcoin Lightning) +- ❀️ **Supporters** - Community funding via Angor (decentralized Bitcoin crowdfunding) ## πŸ› οΈ Technology Stack @@ -231,7 +231,7 @@ See `/license` in the app for the full license page. SkateSpots is 100% free, open-source, and community-funded. No ads, no data selling, no investors. -- **Geyser**: https://geyser.fund/project/skatespots +- **Angor**: https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z - **Supporters page**: https://skate-spots.org/supporters ## πŸ™ Credits @@ -239,7 +239,7 @@ SkateSpots is 100% free, open-source, and community-funded. No ads, no data sell - **Built with**: [MKStack](https://soapbox.pub/mkstack) - Nostr client framework - **Vibed with**: [Shakespeare](https://shakespeare.diy) - AI-powered development - **Powered by**: [Nostr](https://nostr.com) - Decentralized protocol -- **Funded by**: [Geyser](https://geyser.fund/project/skatespots) - Bitcoin Lightning crowdfunding +- **Funded by**: [Angor](https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z) - Decentralized Bitcoin crowdfunding ## πŸ”— Links diff --git a/src/components/SasquatchGuide.tsx b/src/components/SasquatchGuide.tsx index a7b192f..26b550a 100644 --- a/src/components/SasquatchGuide.tsx +++ b/src/components/SasquatchGuide.tsx @@ -213,7 +213,7 @@ Note: this returns a rich itinerary CARD β€” after calling it, add a short one-l - Nutzaps (NIP-61) = ecash tips sent over Nostr; you can set up nutzap RECEIVING in the wallet (sending full nutzaps is rolling out β€” use Lightning zaps for now). - CLINK (noffer) = an optional reusable Nostr-native payment code a shop/crew/Lab pro can add so you can pay them without a Lightning address. Shows a "Pay via CLINK" button. - goskatespots.nostr1.com = primary community relay -- 100% free, no ads, no tracking, community-funded via Geyser (see Supporters). πŸ›Ή`; +- 100% free, no ads, no tracking, community-funded via Angor (see Supporters). πŸ›Ή`; // ─── Personal tools (Sasquatch Pro) β€” only offered when the user is logged in ─ // These read the user's OWN data client-side (Stash is decrypted locally). @@ -418,7 +418,7 @@ const STATIC_FAQ: Record = { '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 nwc': "Nostr Wallet Connect β€” like USB-C for Lightning wallets. Connect Alby, Minibits, Zeus, or Coinos once, and zap with one click forever. Encrypted, you control spending limits. πŸ”Œβš‘", - 'is it free': "100% free. No ads, no subscriptions, no corporate BS. Built by skaters for skaters. Community-funded via Geyser β€” check the Supporters page. You can also earn Bitcoin through community zaps for contributing quality spots. πŸ›Ή", + 'is it free': "100% free. No ads, no subscriptions, no corporate BS. Built by skaters for skaters. Community-funded via Angor β€” check the Supporters page. You can also earn Bitcoin through community zaps for contributing quality spots. πŸ›Ή", 'what can i do': "Discover spots worldwide on the map, hire pros in The Lab (coaches, filmers, photographers, editors, ramp builders), roll with a crew, join events, take tours, buy/sell gear, track your private Stash, zap creators, check spot weather, and build the decentralized skate community!", 'what is the lab': "The Lab is where you hire skate-scene pros: Coaches, Filmers, Photographers, Video Editors, and ramp/park/DIY Builders. Each can be a solo person or a collective (crew/studio) β€” members confirm with their own keys so it's legit. All on Nostr, no fake accounts. πŸŽ“", 'what is a crew': "Crews are your map-anchored skate family β€” who you skate WITH, not who you hire. Each crew has home turf on the map (teal pins), home spots, members, vibes and a manifesto. Members confirm from their own key. Start one at Crews β†’ 'Start a Crew'. 🀘", @@ -433,7 +433,7 @@ const STATIC_FAQ: Record = { 'what are tours': "Tours are guided skate trips β€” local sessions, national road trips, or world destruction tours. Find them on the map or the Tours section, meet up, and skate new terrain with locals. πŸ—ΊοΈπŸ›Ή", 'what is a shop': "Skate Shops are local shops listed on the map. Discover and support real brick-and-mortar (or online) skate shops in your area β€” some even accept Bitcoin. πŸͺ", 'how to sell gear': "Head to Gear Swap / Marketplace and create a listing (NIP-99). Add photos, price in sats, condition, and optionally pin it to a spot so it shows on the map. Buyers zap or message you directly. πŸ›ΉπŸ’Έ", - 'supporters': "SkateSpots is community-funded via Geyser (Bitcoin Lightning crowdfunding) β€” no ads, no investors, no data selling. Check the Supporters page to chip in and keep the streets free. ❀️⚑", + 'supporters': "SkateSpots is community-funded via Angor (decentralized Bitcoin crowdfunding) β€” no ads, no investors, no data selling. Check the Supporters page to back the project and keep the streets free. ❀️⚑", 'trust score': "Trust scores (0-100) powered by Relatr. Analyzes your Nostr network: followers, activity, engagement, zaps. Higher = more trusted. Helps filter bots and spam. Check any profile to see their score. πŸ›‘οΈ", 'what relay': "SkateSpots uses: πŸ›Ή goskatespots.nostr1.com (primary, allowlisted for quality) + relay.ditto.pub (global) + purplepag.es (profile aggregator). Your data is replicated across all of them for redundancy.", 'trip planner': "Tell me a city and I'll build a day-by-day skate trip πŸ—ΊοΈ β€” spots, events, coaches and local crews, weather-aware (rainy day = crew tip for covered spots). Try 'plan me 3 days in Lisbon'. I can save it all to a Collection, and hand you off to flights/stays on the real sites (no KYC, no middleman). πŸ›Ή", diff --git a/src/hooks/useGrowthStats.ts b/src/hooks/useGrowthStats.ts index ae2bb30..7e5d3a7 100644 --- a/src/hooks/useGrowthStats.ts +++ b/src/hooks/useGrowthStats.ts @@ -291,43 +291,4 @@ export function useGrowthStats() { }); } -// ─── Geyser supporter count ────────────────────────────────────────────────── -const GEYSER_API_URL = 'https://api.geyser.fund/graphql'; - -export interface GeyserStats { - supporterCount: number; - totalSats: number; -} - -export function useGeyserStats() { - return useQuery({ - queryKey: ['geyser-stats'], - queryFn: async () => { - try { - const res = await fetch(GEYSER_API_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - query: `{ - projectGet(where: { name: "skatespots" }) { - fundersCount - balance - } - }`, - }), - }); - const json = await res.json(); - const project = json?.data?.projectGet; - return { - supporterCount: project?.fundersCount ?? 0, - totalSats: project?.balance ?? 0, - }; - } catch { - return { supporterCount: 0, totalSats: 0 }; - } - }, - staleTime: 15 * 60 * 1000, - retry: false, - }); -} diff --git a/src/pages/AboutPage.tsx b/src/pages/AboutPage.tsx index 5ce06c7..a1a35bf 100644 --- a/src/pages/AboutPage.tsx +++ b/src/pages/AboutPage.tsx @@ -263,8 +263,7 @@ Version: v0.1.0`; { label: 'Built with', name: 'MKStack', url: 'https://soapbox.pub/mkstack', desc: 'Nostr client framework' }, { label: 'Vibed with', name: 'Shakespeare', url: 'https://shakespeare.diy', desc: 'AI-powered development' }, { label: 'Powered by', name: 'Nostr', url: 'https://nostr.com', desc: 'Decentralized protocol' }, - { label: 'Crowdfunding', name: 'Geyser', url: 'https://geyser.fund/project/skatespots', desc: 'Centralized Bitcoin Lightning crowdfunding' }, - { label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/', desc: 'Bitcoin crowdfunding platform' }, + { label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: 'Decentralized Bitcoin crowdfunding' }, { label: 'Distributed on', name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: 'Open-source app store' }, { label: 'AI powered by', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Nostr AI infrastructure' }, ].map((item) => ( diff --git a/src/pages/CommunityStatsPage.tsx b/src/pages/CommunityStatsPage.tsx index 62642b1..197220e 100644 --- a/src/pages/CommunityStatsPage.tsx +++ b/src/pages/CommunityStatsPage.tsx @@ -7,7 +7,9 @@ import { Skeleton } from '@/components/ui/skeleton'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { useGrowthStats, useGeyserStats } from '@/hooks/useGrowthStats'; +import { useGrowthStats } from '@/hooks/useGrowthStats'; + +const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z'; import { SKATE_RELAY } from '@/lib/types'; import { AreaChart, Area, @@ -152,7 +154,7 @@ export default function CommunityStatsPage() { }); const { data: stats, isLoading, isError } = useGrowthStats(); - const { data: geyser } = useGeyserStats(); + // Content mix for donut chart const contentMix = useMemo(() => { @@ -460,44 +462,27 @@ export default function CommunityStatsPage() {

Funded by the Community

- Bitcoin Lightning supporters via Geyser Fund.
+ Decentralized Bitcoin crowdfunding via Angor.
No ads, no investors, no data selling.

- Support on Geyser + Back us on Angor - View all supporters β†’ + Community β†’
-
- {geyser ? ( - <> -

- {geyser.supporterCount} -

-

supporters

- {geyser.totalSats > 0 && ( -

- ⚑ {formatSats(geyser.totalSats)} sats raised -

- )} - - ) : ( - - )} -
diff --git a/src/pages/NewsletterPage.tsx b/src/pages/NewsletterPage.tsx index 7f95e7a..f9711f6 100644 --- a/src/pages/NewsletterPage.tsx +++ b/src/pages/NewsletterPage.tsx @@ -835,16 +835,16 @@ function IssueArticle({ issue }: { issue: NewsletterIssue }) {

- 100% open source, community funded, no ads, no investors. If it's useful zap it or back it on Geyser. + 100% open source, community funded, no ads, no investors. If it's useful zap it or back it on Angor.

- geyser.fund/project/skatespots + angor.io β€” SkateSpots
@@ -906,7 +906,7 @@ export default function NewsletterPage() { Open source {' Β· '} - + Support us

diff --git a/src/pages/SupportersPage.tsx b/src/pages/SupportersPage.tsx index b3dfd7a..507b6bc 100644 --- a/src/pages/SupportersPage.tsx +++ b/src/pages/SupportersPage.tsx @@ -1,12 +1,9 @@ import { useSeoMeta } from '@unhead/react'; -import { useQuery } from '@tanstack/react-query'; import { Layout } from '@/components/Layout'; import { Link } from 'react-router-dom'; import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react'; -const GEYSER_PROJECT_URL = 'https://geyser.fund/project/skatespots?hero=satsnfacts'; -const GEYSER_LEADERBOARD_URL = 'https://geyser.fund/project/skatespots/leaderboard'; -const GEYSER_API_URL = 'https://api.geyser.fund/graphql'; +const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z'; // ─── Static data β€” edit these arrays to add/remove entries ────────────────── @@ -69,58 +66,6 @@ const FRIENDS: Friend[] = [ // Add more friends here ]; -// ─── Helpers ───────────────────────────────────────────────────────────────── - -interface GeyserContributor { - user: { - id: string; - username: string; - imageUrl?: string; - heroLink?: string; - }; - amount: number; -} - -function formatSats(sats: number): string { - if (sats >= 1_000_000) return `${(sats / 1_000_000).toFixed(2)}M`; - if (sats >= 1_000) return `${Math.round(sats / 1_000).toLocaleString()}k`; - return sats.toLocaleString(); -} - -function useGeyserLeaderboard() { - return useQuery({ - queryKey: ['geyser-leaderboard'], - queryFn: async () => { - try { - const res = await fetch(GEYSER_API_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - query: `{ - projectGet(where: { name: "skatespots" }) { - funders(input: { orderBy: { amountFunded: desc }, pagination: { take: 100 } }) { - amountFunded - user { id username imageUrl heroLink } - } - } - }`, - }), - }); - const json = await res.json(); - const funders = json?.data?.projectGet?.funders ?? []; - return funders.map((f: { amountFunded: number; user: GeyserContributor['user'] }) => ({ - user: f.user, - amount: f.amountFunded, - })); - } catch { - return []; - } - }, - staleTime: 5 * 60 * 1000, - retry: false, - }); -} - // ─── Section header ─────────────────────────────────────────────────────────── function SectionHeader({ @@ -148,43 +93,6 @@ function SectionHeader({ ); } -// ─── Supporter card (Geyser contributors) ──────────────────────────────────── - -function SupporterCard({ contributor }: { contributor: GeyserContributor }) { - const isAnon = !contributor.user.username || contributor.user.username.toLowerCase().startsWith('anon'); - const profileUrl = contributor.user.heroLink || `https://geyser.fund/user/${contributor.user.id}`; - - const card = ( -
-
- {contributor.user.imageUrl ? ( - {contributor.user.username - ) : ( -
- πŸ›Ή -
- )} -
- -
-
-

- {isAnon ? 'Anon πŸ›Ή' : contributor.user.username} -

-

- {formatSats(contributor.amount)} sats -

-
- ); - - if (isAnon) return card; - return {card}; -} - // ─── Collaborator card ──────────────────────────────────────────────────────── function CollaboratorCard({ collab }: { collab: Collaborator }) { @@ -302,8 +210,6 @@ export default function SupportersPage() { description: 'The community keeping SkateSpots alive β€” supporters, collaborators, partners and friends.', }); - const { data: contributors = [], isLoading } = useGeyserLeaderboard(); - return (
@@ -319,66 +225,50 @@ export default function SupportersPage() { SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized.

- Become a Supporter + Back us on Angor
- {/* ── 1. Supporters ── */} + {/* ── 1. Support ── */}
- {isLoading ? ( -
- {Array.from({ length: 12 }).map((_, i) => ( -