From 1cb7c109e739940578b8ff275522c8fe942cb76e Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Mon, 7 Sep 2026 22:06:49 +0700 Subject: [PATCH] =?UTF-8?q?The=20Lab:=20warn=20before=20a=20"Become=20a=20?= =?UTF-8?q?=E2=80=A6"=20form=20silently=20overwrites=20an=20existing=20pro?= =?UTF-8?q?file=20(1.2.28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lab provider profiles (coach, filmer, editor, photographer, builder) are addressable events with a fixed per-type `d` identifier, so re-publishing REPLACES the existing profile. Unlike BecomeSkaterPage — which pre-fills from the existing profile so re-submitting edits rather than clobbers — these five forms start blank and reconstruct nothing. A returning provider who opened the form again and submitted would silently wipe every field they'd previously set (bio, rate, spots, contact, links) down to whatever they happened to retype. Rather than fragile per-page reconstruction of the mixed services/skills tag sets (high regression risk), added a small honest guardrail: - New ExistingLabProfileNotice component: appears only when the current user already has a profile of that type (looked up via useLabProfile by their pubkey + the known identifier). It states plainly that publishing will REPLACE the current profile and links to view the existing one first. - Wired into all five Become* pages with the correct identifier/label. Also confirmed (not a bug): all five navigate to /coach/:pubkey/:identifier after publish — CoachDetailPage is a generic Lab-profile renderer that resolves any type by the distinct `d` identifier, so this is correct despite the name. Investigation of the shared submit handlers found them otherwise consistent and well-formed; left untouched. Co-authored-by: shakespeare.diy --- package.json | 2 +- src/components/ExistingLabProfileNotice.tsx | 60 +++++++++++++++++++++ src/pages/BecomeBuilderPage.tsx | 3 ++ src/pages/BecomeCoachPage.tsx | 3 ++ src/pages/BecomeEditorPage.tsx | 3 ++ src/pages/BecomeFilmerPage.tsx | 3 ++ src/pages/BecomePhotographerPage.tsx | 3 ++ 7 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/components/ExistingLabProfileNotice.tsx diff --git a/package.json b/package.json index d5fe35c..e98ed89 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.27", + "version": "1.2.28", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/components/ExistingLabProfileNotice.tsx b/src/components/ExistingLabProfileNotice.tsx new file mode 100644 index 0000000..331ebac --- /dev/null +++ b/src/components/ExistingLabProfileNotice.tsx @@ -0,0 +1,60 @@ +import { Link } from 'react-router-dom'; +import { AlertTriangle, Eye } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; +import { useLabProfile } from '@/hooks/useLabProfiles'; + +/** + * Lab provider profiles (coach, filmer, editor, photographer, builder…) are + * addressable events keyed by a fixed `d` identifier per type + * (e.g. "coach-profile"). That means re-publishing REPLACES the existing one. + * + * The "Become a …" forms start blank and don't reconstruct prior state, so a + * returning provider who submits again would silently wipe every field they'd + * set before (bio, rate, spots, contact…). This notice makes that consequence + * explicit: it appears only when the current user already has a profile of this + * type, warns that submitting will overwrite it, and links them to view it. + */ +export function ExistingLabProfileNotice({ + identifier, + label, +}: { + /** The fixed `d` identifier for this Lab profile type, e.g. "coach-profile". */ + identifier: string; + /** Human label for the profile type, e.g. "coach". */ + label: string; +}) { + const { user } = useCurrentUser(); + const { data: existing } = useLabProfile(identifier, user?.pubkey); + + if (!user || !existing) return null; + + return ( +
+
+ +
+

+ You already have a {label} profile +

+

+ Publishing this form replaces your + current {label} profile — anything you don't re-enter here will be lost. Review your + existing profile first so you don't drop details you already set. +

+ +
+
+
+ ); +} diff --git a/src/pages/BecomeBuilderPage.tsx b/src/pages/BecomeBuilderPage.tsx index efd3267..2a0b449 100644 --- a/src/pages/BecomeBuilderPage.tsx +++ b/src/pages/BecomeBuilderPage.tsx @@ -22,6 +22,7 @@ import { SpotPicker } from '@/components/SpotPicker'; import { NofferField } from '@/components/NofferField'; import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; +import { ExistingLabProfileNotice } from '@/components/ExistingLabProfileNotice'; import { CrewLinkField } from '@/components/CrewLinkField'; import { SKATE_BUILDER_KIND, @@ -354,6 +355,8 @@ export default function BecomeBuilderPage() { Tell the scene what you build and how you work + +
diff --git a/src/pages/BecomeCoachPage.tsx b/src/pages/BecomeCoachPage.tsx index a0effef..126b505 100644 --- a/src/pages/BecomeCoachPage.tsx +++ b/src/pages/BecomeCoachPage.tsx @@ -22,6 +22,7 @@ import { NofferField } from '@/components/NofferField'; import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; import { CrewLinkField } from '@/components/CrewLinkField'; +import { ExistingLabProfileNotice } from '@/components/ExistingLabProfileNotice'; import { SKATE_COACH_KIND, SKATE_SPOT_KIND, COACH_SKILL_LABELS, COACH_SKILL_DESCRIPTIONS, isValidNoffer, type EntityType } from '@/lib/types'; import { cn } from '@/lib/utils'; import { GraduationCap, Upload, X, Loader2, ArrowLeft, MapPin, Wallet, ExternalLink } from 'lucide-react'; @@ -313,6 +314,8 @@ export default function BecomeCoachPage() { Tell students about your experience + +
diff --git a/src/pages/BecomeEditorPage.tsx b/src/pages/BecomeEditorPage.tsx index 2bf3653..26cac98 100644 --- a/src/pages/BecomeEditorPage.tsx +++ b/src/pages/BecomeEditorPage.tsx @@ -29,6 +29,7 @@ import { type EntityType, } from '@/lib/types'; import { EntityTypeField } from '@/components/EntityTypeField'; +import { ExistingLabProfileNotice } from '@/components/ExistingLabProfileNotice'; import { CrewLinkField } from '@/components/CrewLinkField'; import { cn } from '@/lib/utils'; import { Scissors, Upload, X, Loader2, ArrowLeft, MapPin, Wallet, ExternalLink, Monitor } from 'lucide-react'; @@ -283,6 +284,8 @@ export default function BecomeEditorPage() { Tell the community about your editing style and skills + + {/* Identity */}
diff --git a/src/pages/BecomeFilmerPage.tsx b/src/pages/BecomeFilmerPage.tsx index 9e92cda..b59fa27 100644 --- a/src/pages/BecomeFilmerPage.tsx +++ b/src/pages/BecomeFilmerPage.tsx @@ -21,6 +21,7 @@ import { SpotPicker } from '@/components/SpotPicker'; import { NofferField } from '@/components/NofferField'; import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; +import { ExistingLabProfileNotice } from '@/components/ExistingLabProfileNotice'; 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'; import { cn } from '@/lib/utils'; @@ -329,6 +330,8 @@ export default function BecomeFilmerPage() { Tell clients about your filming expertise + +
diff --git a/src/pages/BecomePhotographerPage.tsx b/src/pages/BecomePhotographerPage.tsx index e373a75..b121728 100644 --- a/src/pages/BecomePhotographerPage.tsx +++ b/src/pages/BecomePhotographerPage.tsx @@ -21,6 +21,7 @@ import { SpotPicker } from '@/components/SpotPicker'; import { NofferField } from '@/components/NofferField'; import { PaymentReadinessNote } from '@/components/PaymentReadinessNote'; import { EntityTypeField } from '@/components/EntityTypeField'; +import { ExistingLabProfileNotice } from '@/components/ExistingLabProfileNotice'; import { CrewLinkField } from '@/components/CrewLinkField'; import type { EntityType } from '@/lib/types'; import { @@ -371,6 +372,8 @@ export default function BecomePhotographerPage() { Tell riders about your eye, your kit and your services + +