From a89fca8e28dd993b49c43db0f8f5a6644cde66e2 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 3 Sep 2026 15:21:34 +0700 Subject: [PATCH] Lab: payment-readiness prompt on every provider form + drop downhill/longboard from judge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New PaymentReadinessNote component: checks the logged-in user's Nostr profile (kind 0) for a Lightning address / Silent Payment and either confirms "you can get paid" or links them to Edit Profile to add one. Wired into CraftProfileForm (coach, filmer, editor, photographer, builder, artist, graffiti, tattoo, trainer, musician, judge) and the standalone coach/filmer/editor/photographer/ builder forms, alongside the existing CLINK (noffer) field. Lightning address stays a single source of truth on kind 0 — never duplicated/stale per event. - become-judge: removed the "Downhill / Longboard" event format (not part of the skateboarding activities this app covers). Co-authored-by: shakespeare.diy --- src/components/CraftProfileForm.tsx | 9 +++- src/components/PaymentReadinessNote.tsx | 69 +++++++++++++++++++++++++ src/lib/types.ts | 1 - src/pages/BecomeBuilderPage.tsx | 2 + src/pages/BecomeCoachPage.tsx | 2 + src/pages/BecomeEditorPage.tsx | 2 + src/pages/BecomeFilmerPage.tsx | 2 + src/pages/BecomePhotographerPage.tsx | 2 + 8 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 src/components/PaymentReadinessNote.tsx diff --git a/src/components/CraftProfileForm.tsx b/src/components/CraftProfileForm.tsx index cfb9da9..ceb4ea5 100644 --- a/src/components/CraftProfileForm.tsx +++ b/src/components/CraftProfileForm.tsx @@ -16,6 +16,7 @@ import { useUploadFile } from '@/hooks/useUploadFile'; import { useToast } from '@/hooks/useToast'; import { LoginArea } from '@/components/auth/LoginArea'; import { NofferField } from '@/components/NofferField'; +import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; import { CrewLinkField } from '@/components/CrewLinkField'; import { LabProUpgrade } from '@/components/LabProUpgrade'; @@ -445,9 +446,13 @@ export function CraftProfileForm({ config }: { config: CraftFormConfig }) { - {/* CLINK tip jar */} + {/* Payments — Lightning address (from profile) + optional CLINK code */} - + + + diff --git a/src/components/PaymentReadinessNote.tsx b/src/components/PaymentReadinessNote.tsx new file mode 100644 index 0000000..a649340 --- /dev/null +++ b/src/components/PaymentReadinessNote.tsx @@ -0,0 +1,69 @@ +import { Link } from 'react-router-dom'; +import { Zap, CheckCircle2, AlertCircle } from 'lucide-react'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; +import { useAuthor } from '@/hooks/useAuthor'; +import { isValidLightningAddress, isValidSilentPayment } from '@/lib/types'; + +/** + * Payment-readiness note for provider / Lab / crew forms. + * + * Lightning address (lud16) and Bitcoin Silent Payment live on the user's Nostr + * profile (kind 0), NOT on each individual Lab event — so a single source of + * truth never goes stale across a person's many profiles. This note simply + * tells a provider whether that source is set up, and links them to Edit Profile + * if it isn't, so they always have a clear path to "get paid". + */ +export function PaymentReadinessNote() { + const { user } = useCurrentUser(); + const author = useAuthor(user?.pubkey || ''); + const metadata = author.data?.metadata; + + const lud16 = metadata?.lud16; + const hasLightning = isValidLightningAddress(lud16) || !!metadata?.lud06; + // Silent payment lives in a custom kind-0 field the app reads elsewhere. + const rawSp = (metadata as Record | undefined)?.bitcoin_silent_payment; + const hasSilentPayment = typeof rawSp === 'string' && isValidSilentPayment(rawSp); + + if (!user) return null; + + const ready = hasLightning; + + return ( +
+
+ + + {ready ? 'You can get paid' : 'Set up payments to get paid'} + +
+ + {ready ? ( +

+ + + Your Lightning address{lud16 ? <> ({lud16}) : ''} is on your + profile — people can zap and pay you directly. + {hasSilentPayment && ' A Bitcoin Silent Payment address is set too.'} + +

+ ) : ( +

+ + + Add a Lightning address (and optionally a Bitcoin Silent Payment address) to your{' '} + + Nostr profile + {' '} + so skaters can pay you. It's set once and works across every profile you have here. + +

+ )} +
+ ); +} diff --git a/src/lib/types.ts b/src/lib/types.ts index a4f5f75..361ab56 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1684,7 +1684,6 @@ export const JUDGE_FORMAT_LABELS: Record = { mini_ramp: 'Mini Ramp', park: 'Park', street: 'Street', - downhill: 'Downhill / Longboard', freestyle: 'Freestyle', world_tour: 'World Destruction Tour', am_series: 'Am Series / Qualifiers', diff --git a/src/pages/BecomeBuilderPage.tsx b/src/pages/BecomeBuilderPage.tsx index f69c820..1856d04 100644 --- a/src/pages/BecomeBuilderPage.tsx +++ b/src/pages/BecomeBuilderPage.tsx @@ -20,6 +20,7 @@ import { WalletModal } from '@/components/WalletModal'; import { LoginArea } from '@/components/auth/LoginArea'; import { SpotPicker } from '@/components/SpotPicker'; import { NofferField } from '@/components/NofferField'; +import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; import { CrewLinkField } from '@/components/CrewLinkField'; import { @@ -486,6 +487,7 @@ export default function BecomeBuilderPage() { setSocial(e.target.value)} className="bg-gray-800 border-gray-700" /> +

💬 Clients can message you directly through SkateSpots' encrypted messaging

diff --git a/src/pages/BecomeCoachPage.tsx b/src/pages/BecomeCoachPage.tsx index 008774e..73f4299 100644 --- a/src/pages/BecomeCoachPage.tsx +++ b/src/pages/BecomeCoachPage.tsx @@ -19,6 +19,7 @@ import { WalletModal } from '@/components/WalletModal'; import { LoginArea } from '@/components/auth/LoginArea'; import { SpotPicker } from '@/components/SpotPicker'; import { NofferField } from '@/components/NofferField'; +import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; import { CrewLinkField } from '@/components/CrewLinkField'; import { SKATE_COACH_KIND, SKATE_SPOT_KIND, COACH_SKILL_LABELS, COACH_SKILL_DESCRIPTIONS, isValidNoffer, type EntityType } from '@/lib/types'; @@ -401,6 +402,7 @@ export default function BecomeCoachPage() {
+
diff --git a/src/pages/BecomeEditorPage.tsx b/src/pages/BecomeEditorPage.tsx index acb51cb..2bf3653 100644 --- a/src/pages/BecomeEditorPage.tsx +++ b/src/pages/BecomeEditorPage.tsx @@ -18,6 +18,7 @@ import { useWallet } from '@/hooks/useWallet'; import { WalletModal } from '@/components/WalletModal'; import { LoginArea } from '@/components/auth/LoginArea'; import { NofferField } from '@/components/NofferField'; +import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { SKATE_EDITOR_KIND, EDITOR_SERVICE_LABELS, @@ -427,6 +428,7 @@ export default function BecomeEditorPage() { setWebsite(e.target.value)} className="bg-gray-800 border-gray-700" /> setSocial(e.target.value)} className="bg-gray-800 border-gray-700" />
+ diff --git a/src/pages/BecomeFilmerPage.tsx b/src/pages/BecomeFilmerPage.tsx index 47065e8..d930647 100644 --- a/src/pages/BecomeFilmerPage.tsx +++ b/src/pages/BecomeFilmerPage.tsx @@ -19,6 +19,7 @@ import { WalletModal } from '@/components/WalletModal'; import { LoginArea } from '@/components/auth/LoginArea'; import { SpotPicker } from '@/components/SpotPicker'; import { NofferField } from '@/components/NofferField'; +import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; import { CrewLinkField } from '@/components/CrewLinkField'; import { SKATE_FILMER_KIND, SKATE_SPOT_KIND, FILMING_SERVICE_LABELS, FILMING_SERVICE_DESCRIPTIONS, FILMING_SKILL_LABELS, FILMING_SKILL_DESCRIPTIONS, isValidNoffer, type EntityType } from '@/lib/types'; @@ -459,6 +460,7 @@ export default function BecomeFilmerPage() { +
diff --git a/src/pages/BecomePhotographerPage.tsx b/src/pages/BecomePhotographerPage.tsx index 8389cf4..f0aeaf8 100644 --- a/src/pages/BecomePhotographerPage.tsx +++ b/src/pages/BecomePhotographerPage.tsx @@ -19,6 +19,7 @@ import { WalletModal } from '@/components/WalletModal'; import { LoginArea } from '@/components/auth/LoginArea'; import { SpotPicker } from '@/components/SpotPicker'; import { NofferField } from '@/components/NofferField'; +import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; import { CrewLinkField } from '@/components/CrewLinkField'; import type { EntityType } from '@/lib/types'; @@ -508,6 +509,7 @@ export default function BecomePhotographerPage() { setSocial(e.target.value)} className="bg-gray-800 border-gray-700" />
+

💬 Riders can message you directly through SkateSpots' encrypted messaging