feat: replace Geyser with Angor across the entire app
Remove all Geyser.fund references and replace with the SkateSpots Angor project (angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z). - SupportersPage: remove Geyser leaderboard + API calls entirely; replace Supporters section with an Angor CTA card including the on-chain wallet address (bc1q2hdfyqa7utrxrd95rlr27shsdr6gtk7dzdqres) - useGrowthStats: delete useGeyserStats hook + GEYSER_API_URL constant - CommunityStatsPage: remove Geyser stats widget, replace with Angor CTA - SasquatchGuide: update system prompt + FAQ (is it free, supporters) - NewsletterPage: update both Geyser links (body + footer) to Angor - AboutPage: remove Geyser row, update Angor entry with real project URL - README.md: update crowdfunding section + credits Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
d6b50de349
commit
ef9bbea465
7 changed files with 48 additions and 213 deletions
|
|
@ -24,7 +24,7 @@ A decentralized skateboarding community platform built on Nostr. Discover skate
|
||||||
- 🌤️ **Weather Integration** - Check conditions before you skate
|
- 🌤️ **Weather Integration** - Check conditions before you skate
|
||||||
- 🔐 **Nostr Authentication** - Decentralized login with your Nostr keys
|
- 🔐 **Nostr Authentication** - Decentralized login with your Nostr keys
|
||||||
- 📱 **PWA Support** - Install as a native app on any device
|
- 📱 **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
|
## 🛠️ 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.
|
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
|
- **Supporters page**: https://skate-spots.org/supporters
|
||||||
|
|
||||||
## 🙏 Credits
|
## 🙏 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
|
- **Built with**: [MKStack](https://soapbox.pub/mkstack) - Nostr client framework
|
||||||
- **Vibed with**: [Shakespeare](https://shakespeare.diy) - AI-powered development
|
- **Vibed with**: [Shakespeare](https://shakespeare.diy) - AI-powered development
|
||||||
- **Powered by**: [Nostr](https://nostr.com) - Decentralized protocol
|
- **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
|
## 🔗 Links
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
- 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.
|
- 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
|
- 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 ─
|
// ─── 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).
|
// These read the user's OWN data client-side (Stash is decrypted locally).
|
||||||
|
|
@ -418,7 +418,7 @@ const STATIC_FAQ: Record<string, string> = {
|
||||||
'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. 🥜",
|
||||||
'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. 🔌⚡",
|
'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 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 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'. 🤘",
|
'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<string, string> = {
|
||||||
'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 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. 🏪",
|
'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. 🛹💸",
|
'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. 🛡️",
|
'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.",
|
'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). 🛹",
|
'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). 🛹",
|
||||||
|
|
|
||||||
|
|
@ -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<GeyserStats>({
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -263,8 +263,7 @@ Version: v0.1.0`;
|
||||||
{ label: 'Built with', name: 'MKStack', url: 'https://soapbox.pub/mkstack', desc: 'Nostr client framework' },
|
{ 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: '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: '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/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: 'Decentralized Bitcoin crowdfunding' },
|
||||||
{ label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/', desc: 'Bitcoin crowdfunding platform' },
|
|
||||||
{ label: 'Distributed on', name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: 'Open-source app store' },
|
{ 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' },
|
{ label: 'AI powered by', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Nostr AI infrastructure' },
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { useAuthor } from '@/hooks/useAuthor';
|
import { useAuthor } from '@/hooks/useAuthor';
|
||||||
import { genUserName } from '@/lib/genUserName';
|
import { genUserName } from '@/lib/genUserName';
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
|
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 { SKATE_RELAY } from '@/lib/types';
|
||||||
import {
|
import {
|
||||||
AreaChart, Area,
|
AreaChart, Area,
|
||||||
|
|
@ -152,7 +154,7 @@ export default function CommunityStatsPage() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: stats, isLoading, isError } = useGrowthStats();
|
const { data: stats, isLoading, isError } = useGrowthStats();
|
||||||
const { data: geyser } = useGeyserStats();
|
|
||||||
|
|
||||||
// Content mix for donut chart
|
// Content mix for donut chart
|
||||||
const contentMix = useMemo(() => {
|
const contentMix = useMemo(() => {
|
||||||
|
|
@ -460,44 +462,27 @@ export default function CommunityStatsPage() {
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-black uppercase text-[#ccff00] mb-1">Funded by the Community</h3>
|
<h3 className="text-xl font-black uppercase text-[#ccff00] mb-1">Funded by the Community</h3>
|
||||||
<p className="text-sm text-gray-500 max-w-sm mb-4">
|
<p className="text-sm text-gray-500 max-w-sm mb-4">
|
||||||
Bitcoin Lightning supporters via Geyser Fund.<br />
|
Decentralized Bitcoin crowdfunding via Angor.<br />
|
||||||
No ads, no investors, no data selling.
|
No ads, no investors, no data selling.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
<a
|
<a
|
||||||
href="https://geyser.fund/project/skatespots"
|
href={ANGOR_PROJECT_URL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ff0040]/15 border border-[#ff0040]/40 text-[#ff0040] text-xs font-bold hover:bg-[#ff0040]/25 transition-colors"
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ff0040]/15 border border-[#ff0040]/40 text-[#ff0040] text-xs font-bold hover:bg-[#ff0040]/25 transition-colors"
|
||||||
>
|
>
|
||||||
<Zap className="h-3.5 w-3.5" /> Support on Geyser
|
<Zap className="h-3.5 w-3.5" /> Back us on Angor
|
||||||
<ExternalLink className="h-3 w-3 opacity-60" />
|
<ExternalLink className="h-3 w-3 opacity-60" />
|
||||||
</a>
|
</a>
|
||||||
<Link
|
<Link
|
||||||
to="/supporters"
|
to="/supporters"
|
||||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ccff00]/8 border border-[#ccff00]/30 text-[#ccff00] text-xs font-bold hover:bg-[#ccff00]/15 transition-colors"
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ccff00]/8 border border-[#ccff00]/30 text-[#ccff00] text-xs font-bold hover:bg-[#ccff00]/15 transition-colors"
|
||||||
>
|
>
|
||||||
<Users className="h-3.5 w-3.5" /> View all supporters →
|
<Users className="h-3.5 w-3.5" /> Community →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right flex-shrink-0">
|
|
||||||
{geyser ? (
|
|
||||||
<>
|
|
||||||
<p className="text-5xl font-black text-[#ccff00] leading-none">
|
|
||||||
{geyser.supporterCount}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-500 mt-1">supporters</p>
|
|
||||||
{geyser.totalSats > 0 && (
|
|
||||||
<p className="text-xs text-[#f7931a] font-bold mt-1">
|
|
||||||
⚡ {formatSats(geyser.totalSats)} sats raised
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Skeleton className="w-20 h-14" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -835,16 +835,16 @@ function IssueArticle({ issue }: { issue: NewsletterIssue }) {
|
||||||
<SectionHeader emoji="⚡" label="Support the Project" color="#f7931a" />
|
<SectionHeader emoji="⚡" label="Support the Project" color="#f7931a" />
|
||||||
<div className="mt-4 p-5 rounded-2xl border-2 border-[#f7931a]/30 bg-[#f7931a]/5">
|
<div className="mt-4 p-5 rounded-2xl border-2 border-[#f7931a]/30 bg-[#f7931a]/5">
|
||||||
<p className="text-sm text-gray-300 mb-3">
|
<p className="text-sm text-gray-300 mb-3">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href="https://geyser.fund/project/skatespots"
|
href="https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-2 text-[#f7931a] text-sm font-bold hover:opacity-80 transition-opacity"
|
className="inline-flex items-center gap-2 text-[#f7931a] text-sm font-bold hover:opacity-80 transition-opacity"
|
||||||
>
|
>
|
||||||
<Zap className="h-4 w-4" />
|
<Zap className="h-4 w-4" />
|
||||||
geyser.fund/project/skatespots
|
angor.io — SkateSpots
|
||||||
<ArrowRight className="h-3.5 w-3.5" />
|
<ArrowRight className="h-3.5 w-3.5" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -906,7 +906,7 @@ export default function NewsletterPage() {
|
||||||
Open source
|
Open source
|
||||||
</Link>
|
</Link>
|
||||||
{' · '}
|
{' · '}
|
||||||
<a href="https://geyser.fund/project/skatespots" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-[#ccff00] transition-colors">
|
<a href="https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-[#ccff00] transition-colors">
|
||||||
Support us
|
Support us
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { useSeoMeta } from '@unhead/react';
|
import { useSeoMeta } from '@unhead/react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Layout } from '@/components/Layout';
|
import { Layout } from '@/components/Layout';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react';
|
import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react';
|
||||||
|
|
||||||
const GEYSER_PROJECT_URL = 'https://geyser.fund/project/skatespots?hero=satsnfacts';
|
const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z';
|
||||||
const GEYSER_LEADERBOARD_URL = 'https://geyser.fund/project/skatespots/leaderboard';
|
|
||||||
const GEYSER_API_URL = 'https://api.geyser.fund/graphql';
|
|
||||||
|
|
||||||
// ─── Static data — edit these arrays to add/remove entries ──────────────────
|
// ─── Static data — edit these arrays to add/remove entries ──────────────────
|
||||||
|
|
||||||
|
|
@ -69,58 +66,6 @@ const FRIENDS: Friend[] = [
|
||||||
// Add more friends here
|
// 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<GeyserContributor[]>({
|
|
||||||
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 ───────────────────────────────────────────────────────────
|
// ─── Section header ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function SectionHeader({
|
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 = (
|
|
||||||
<div className="flex flex-col items-center gap-2 p-4 rounded-xl bg-black/60 border border-white/10 hover:border-[#ccff00]/40 transition-all group">
|
|
||||||
<div className="relative">
|
|
||||||
{contributor.user.imageUrl ? (
|
|
||||||
<img
|
|
||||||
src={contributor.user.imageUrl}
|
|
||||||
alt={contributor.user.username || 'Anon'}
|
|
||||||
className="w-12 h-12 rounded-full object-cover border-2 border-white/10 group-hover:border-[#ccff00]/50 transition-all"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-[#ff0040]/20 to-[#ccff00]/20 border-2 border-white/10 flex items-center justify-center text-xl">
|
|
||||||
🛹
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="absolute -bottom-1 -right-1 bg-[#f7931a] rounded-full w-4 h-4 flex items-center justify-center">
|
|
||||||
<Zap className="h-2 w-2 text-white" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs font-bold text-center text-white truncate max-w-full w-full text-center">
|
|
||||||
{isAnon ? 'Anon 🛹' : contributor.user.username}
|
|
||||||
</p>
|
|
||||||
<p className="text-[10px] font-bold text-[#f7931a]">
|
|
||||||
{formatSats(contributor.amount)} sats
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isAnon) return card;
|
|
||||||
return <a href={profileUrl} target="_blank" rel="noopener noreferrer">{card}</a>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Collaborator card ────────────────────────────────────────────────────────
|
// ─── Collaborator card ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function CollaboratorCard({ collab }: { collab: Collaborator }) {
|
function CollaboratorCard({ collab }: { collab: Collaborator }) {
|
||||||
|
|
@ -302,8 +210,6 @@ export default function SupportersPage() {
|
||||||
description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.',
|
description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: contributors = [], isLoading } = useGeyserLeaderboard();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="container max-w-4xl px-4 py-12 md:py-16 space-y-16">
|
<div className="container max-w-4xl px-4 py-12 md:py-16 space-y-16">
|
||||||
|
|
@ -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.
|
SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href={GEYSER_PROJECT_URL}
|
href={ANGOR_PROJECT_URL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-2 px-6 py-3 rounded-full bg-gradient-to-r from-[#ff0040] to-[#ff6600] text-white font-black uppercase text-sm hover:opacity-90 transition-all shadow-lg shadow-[#ff0040]/30"
|
className="inline-flex items-center gap-2 px-6 py-3 rounded-full bg-gradient-to-r from-[#ff0040] to-[#ff6600] text-white font-black uppercase text-sm hover:opacity-90 transition-all shadow-lg shadow-[#ff0040]/30"
|
||||||
>
|
>
|
||||||
<Zap className="h-4 w-4" />
|
<Zap className="h-4 w-4" />
|
||||||
Become a Supporter
|
Back us on Angor
|
||||||
<ExternalLink className="h-3.5 w-3.5 opacity-70" />
|
<ExternalLink className="h-3.5 w-3.5 opacity-70" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── 1. Supporters ── */}
|
{/* ── 1. Support ── */}
|
||||||
<section>
|
<section>
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
icon={Zap}
|
icon={Zap}
|
||||||
title="Supporters"
|
title="Support"
|
||||||
subtitle="Bitcoin contributors via Geyser — ordered by cumulative sats."
|
subtitle="Fund SkateSpots on Angor — decentralized, on-chain Bitcoin crowdfunding. No middleman, no KYC."
|
||||||
color="#f7931a"
|
color="#f7931a"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isLoading ? (
|
<div className="rounded-2xl border-2 border-[#f7931a]/30 bg-black/60 p-8 text-center">
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3">
|
<div className="text-5xl mb-4">🧡⚡</div>
|
||||||
{Array.from({ length: 12 }).map((_, i) => (
|
<h3 className="text-xl font-black uppercase mb-2 text-white">Back the project on Angor</h3>
|
||||||
<div key={i} className="flex flex-col items-center gap-2 p-4 rounded-xl bg-black/40 border border-white/5 animate-pulse">
|
<p className="text-sm text-gray-400 max-w-lg mx-auto mb-6 leading-relaxed">
|
||||||
<div className="w-12 h-12 rounded-full bg-white/10" />
|
Angor is a decentralized Bitcoin crowdfunding platform built on Nostr — your funds stay
|
||||||
<div className="h-3 w-14 bg-white/10 rounded" />
|
in your control, released in stages, with no custodian holding the pot. Every sat goes
|
||||||
<div className="h-3 w-10 bg-white/5 rounded" />
|
to development, relay hosting, and keeping SkateSpots free for every skater on earth.
|
||||||
</div>
|
</p>
|
||||||
))}
|
<p className="text-xs text-gray-600 font-mono mb-4 break-all">
|
||||||
</div>
|
⛓ bc1q2hdfyqa7utrxrd95rlr27shsdr6gtk7dzdqres
|
||||||
) : contributors.length > 0 ? (
|
</p>
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3">
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||||
{contributors.map((c, i) => (
|
<a
|
||||||
<SupporterCard key={`${c.user.id}-${i}`} contributor={c} />
|
href={ANGOR_PROJECT_URL}
|
||||||
))}
|
target="_blank"
|
||||||
</div>
|
rel="noopener noreferrer"
|
||||||
) : (
|
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border-2 border-[#f7931a]/40 text-[#f7931a] font-bold text-sm hover:bg-[#f7931a]/10 transition-all"
|
||||||
<div className="text-center py-12 rounded-2xl border border-dashed border-white/10">
|
>
|
||||||
<div className="text-4xl mb-3">🛹</div>
|
<ExternalLink className="h-4 w-4" />
|
||||||
<p className="text-gray-500 mb-2">Be the first supporter!</p>
|
Open Angor
|
||||||
<p className="text-sm text-gray-600">The leaderboard updates once contributions come in.</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-6 flex flex-wrap items-center justify-between gap-4">
|
|
||||||
<a
|
|
||||||
href={GEYSER_LEADERBOARD_URL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border-2 border-[#f7931a]/30 text-[#f7931a] font-bold text-sm hover:bg-[#f7931a]/10 transition-all"
|
|
||||||
>
|
|
||||||
<ExternalLink className="h-4 w-4" />
|
|
||||||
Full Leaderboard on Geyser
|
|
||||||
</a>
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
||||||
<span>Powered by</span>
|
|
||||||
<a href={GEYSER_PROJECT_URL} target="_blank" rel="noopener noreferrer"
|
|
||||||
className="font-bold text-gray-400 hover:text-white transition-colors">
|
|
||||||
Geyser 🧦
|
|
||||||
</a>
|
</a>
|
||||||
|
<span className="text-xs text-gray-500">
|
||||||
|
or ⚡ zap any spot, crew or creator directly in the app
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue