diff --git a/src/components/NotificationBell.tsx b/src/components/NotificationBell.tsx index 2be5a04..b0c1934 100644 --- a/src/components/NotificationBell.tsx +++ b/src/components/NotificationBell.tsx @@ -225,7 +225,7 @@ export function NotificationBell({ requests: cordnJoinRequests, isLoading: cordnJoinLoading, dismiss: dismissCordnJoin, - } = useCordnJoinNotifications(); + } = useCordnJoinNotifications(open); const { lastRead, seenAt, markAllRead } = useNotificationsRead(); // Buying/selling steps: an offer on your listing, a counter it's your turn to diff --git a/src/hooks/useCordnJoinNotifications.ts b/src/hooks/useCordnJoinNotifications.ts index a30b436..8c58125 100644 --- a/src/hooks/useCordnJoinNotifications.ts +++ b/src/hooks/useCordnJoinNotifications.ts @@ -1,8 +1,10 @@ import { useCallback, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; +import { useNostrLogin } from '@nostrify/react/login'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useSkateRelays } from '@/hooks/useSkateRelays'; +import { hasNip44, isInteractiveSigner } from '@/lib/signerCapabilities'; import { APP_DATA_KIND } from '@/lib/types'; import { CORDN_JOIN_TAG, @@ -17,11 +19,15 @@ const DISMISSED_KEY = 'skatespots:cordn-join-dismissed'; * background watcher as encrypted NIP-78 events. The watcher also sends the * same event as a NIP-17 DM, so the OS push path works while the app is closed. */ -export function useCordnJoinNotifications() { +export function useCordnJoinNotifications(opened = false) { const { user } = useCurrentUser(); + const { logins } = useNostrLogin(); + const interactive = isInteractiveSigner(user, logins); const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const [dismissed, setDismissed] = useLocalStorage(DISMISSED_KEY, []); - const canRead = !!user?.pubkey && !!user.signer.nip44; + // Amber prompts once per decrypt. Load these only when the bell is opened; + // passive signers can fetch them quietly in the background. + const canRead = !!user?.pubkey && hasNip44(user, logins) && (!interactive || opened); const query = useQuery({ queryKey: ['cordn-join-notifications', user?.pubkey, getQueryKeySuffix()], diff --git a/zapstore.yaml b/zapstore.yaml index fa1891a..6c31321 100644 --- a/zapstore.yaml +++ b/zapstore.yaml @@ -1,104 +1,98 @@ # Zapstore publish config for SkateSpots # https://zapstore.dev # -# HOW TO PUBLISH -# ────────────── -# 1. Build the APK locally: -# npm run android:build -# -# 2. Publish to Zapstore: -# SIGN_WITH=nsec1... zsp publish zapstore.yaml -# -# Or with browser extension (NIP-07): -# SIGN_WITH=browser zsp publish zapstore.yaml -# -# zsp will: -# - Parse the APK (version, cert, permissions, icon) -# - Upload APK + icon to cdn.zapstore.dev (Blossom) -# - Sign and publish events to relay.zapstore.dev +# Publish: scripts/release.sh patch --yes (reads ~/.config/skatespots/zsp.env) +# or: SIGN_WITH=... zsp publish --quiet zapstore.yaml # ═══════════════════════════════════════════════════════════════════ # SOURCE CONFIGURATION # ═══════════════════════════════════════════════════════════════════ # Source code, shown as the "Source" link in the Zapstore listing. -# This points at the NIP-34 git repository announcement (kind 30617), -# rendered as a browsable web page by nostrhub.io. A plain HTTPS URL is -# used (rather than a bare naddr) so Zapstore renders it as a clickable -# Source link — a bare naddr coordinate was published correctly but not -# surfaced in the UI. repository: https://nostrhub.io/naddr1qvzqqqrhnypzqg88e9f588nx7wyz4wt0ura23w3yd4hmztqrkr95utwyvgachjepqyd8wumn8ghj7emfwsh8x6rpddjhxur9v9ex2tnyd9uj7qq22d4kzar92dcx7arnhyhdwn # Local APK built by: npm run android:build release_source: ./android/app/build/outputs/apk/release/app-release.apk +# Guard: refuse to publish if the configured signer is not the app identity. +pubkey: npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx + # ═══════════════════════════════════════════════════════════════════ # APP METADATA # ═══════════════════════════════════════════════════════════════════ name: SkateSpots -summary: The decentralized skate map & community, built on Nostr +summary: The decentralized skate map, crews, private chat and gear — on Nostr. description: | - Discover skate spots worldwide, start your own Crew, find coaches, - photographers, builders and filmers, join events, buy/sell gear, and - connect with the global skateboarding community. + Discover skate spots worldwide, start a crew, find coaches, filmers, + photographers, builders and editors, join events, trade gear, and connect + with the global skateboarding community. - Built on Nostr — decentralized, censorship-resistant, no ads, no tracking. - Powered by Bitcoin Lightning zaps. + Built on Nostr: decentralized, censorship-resistant, no ads, no tracking, + no central account. Lightning zaps and Cashu are supported where available. Features: - - Interactive world map with a "Near Me" locator - - Live spot status reports — active, bust, skate-stopped, closed or gone - - Crews: build your local skate community, map-anchored - - The Lab: coaches, filmers, photographers, builders & video editors - - Events calendar - - Buy/sell used skate gear - - Private encrypted gear stash - - Local tours — guides show you the hidden spots - - Lightning zaps to content creators - - Works offline (PWA) + - Interactive world map with a Near Me locator and spot status reports + - Crews with members, roles, events and invites + - Private chat: NIP-17 DMs, plus MLS end-to-end encrypted crew rooms + - The Lab: coaches, filmers, photographers, builders and other crafts + - Events calendar, tours and local guides + - Marketplace for used gear, with barter and Lightning payments + - Private encrypted gear Stash + - On-device message translation in 24 languages (text never leaves the phone) + - Weekly newsletter and a public Cordn community room + - Works offline; web push and local Android notifications website: https://skate-spots.org license: AGPL-3.0-only -# App icon (local path or URL). YAML config ALWAYS wins over the icon -# extracted from the APK or pulled from external metadata sources. -# This is the orange SkateSpots logo used everywhere else. +# Launcher / listing icon. icon: ./public/images/icon-1024.png -# Tags for Zapstore discovery +# Listing screenshots (uploaded by zsp to the Zapstore CDN). +images: + - ./public/images/screenshots/map.png + - ./public/images/screenshots/spot.png + - ./public/images/screenshots/marketplace.png + +# Discovery tags. tags: - sports - social - nostr - bitcoin - maps + - chat + - privacy + - marketplace -# Nostr NIPs supported by this app +# Nostr NIPs used by the app. supported_nips: - "01" + - "05" - "07" + - "13" + - "17" + - "19" + - "23" + - "25" + - "44" - "46" - "47" + - "51" - "55" + - "56" - "57" + - "59" - "65" + - "75" + - "78" - "99" -# ═══════════════════════════════════════════════════════════════════ -# METADATA SOURCES -# ═══════════════════════════════════════════════════════════════════ - -# Do NOT enrich icon/metadata from external sources — we set everything -# explicitly here so the correct orange logo is always used. Play Store -# enrichment was overriding our icon with the wrong (blue launcher) image. +# Keep metadata source-free: the YAML is the source of truth for the icon, +# screenshots and copy. metadata_sources: [] -# ═══════════════════════════════════════════════════════════════════ -# RELEASE NOTES -# ═══════════════════════════════════════════════════════════════════ - -# Path to a plain-text file with release notes for this version. -# zsp reads this file and publishes its contents to Zapstore. +# Release notes for this version. release_notes: ./release-notes.txt