diff --git a/src/components/SkaterDirectoryCard.tsx b/src/components/SkaterDirectoryCard.tsx
new file mode 100644
index 0000000..1baa178
--- /dev/null
+++ b/src/components/SkaterDirectoryCard.tsx
@@ -0,0 +1,84 @@
+import { Link } from 'react-router-dom';
+import { PersonStanding, ChevronRight, Sparkles, Check } from 'lucide-react';
+import { useCurrentUser } from '@/hooks/useCurrentUser';
+import { useUserSkaterProfile } from '@/hooks/useSkaters';
+import { SKATER_STYLES, SKATER_LEVELS } from '@/lib/types';
+
+/**
+ * Settings/profile companion card that surfaces the user's SkateSpots "skater
+ * layer" (kind 35000) right next to their kind-0 profile — the reverse of the
+ * cross-link on the Become-Skater page. Shows status + a link to edit, or a
+ * nudge to join the directory if they haven't yet.
+ */
+export function SkaterDirectoryCard() {
+ const { user } = useCurrentUser();
+ const { data: skater, isLoading } = useUserSkaterProfile(user?.pubkey);
+
+ if (!user || isLoading) return null;
+
+ // Not in the directory yet — gentle nudge.
+ if (!skater) {
+ return (
+
+
+
+
+
+
Join the Skater Directory
+
+ Add your stance, styles & level so skaters can find you.
+
+
+
+
+ );
+ }
+
+ // Already in the directory — show a compact summary + edit affordance.
+ const level = skater.level ? SKATER_LEVELS[skater.level] : undefined;
+
+ return (
+
+
+
+ Edit
+
+
+
+ );
+}
diff --git a/src/pages/BecomeSkaterPage.tsx b/src/pages/BecomeSkaterPage.tsx
index 237ce8d..ea2d8d4 100644
--- a/src/pages/BecomeSkaterPage.tsx
+++ b/src/pages/BecomeSkaterPage.tsx
@@ -28,7 +28,7 @@ import {
} from '@/lib/types';
import { genUserName } from '@/lib/genUserName';
import { cn } from '@/lib/utils';
-import { Upload, X, Loader2, ArrowLeft, MapPin, PersonStanding, Sparkles } from 'lucide-react';
+import { Upload, X, Loader2, ArrowLeft, MapPin, PersonStanding, Sparkles, UserRound } from 'lucide-react';
export default function BecomeSkaterPage() {
useSeoMeta({
@@ -47,6 +47,9 @@ export default function BecomeSkaterPage() {
const metadata = author.data?.metadata;
const displayName = metadata?.name ?? (user ? genUserName(user.pubkey) : 'Skater');
const avatar = metadata?.picture;
+ // kind-0 values we borrow from so we never re-ask for what the profile has.
+ const kind0About = metadata?.about ?? '';
+ const kind0Website = metadata?.website ?? '';
const [bio, setBio] = useState('');
const [location, setLocation] = useState('');
@@ -59,11 +62,16 @@ export default function BecomeSkaterPage() {
const [homeSpot, setHomeSpot] = useState('');
const [instagram, setInstagram] = useState('');
const [website, setWebsite] = useState('');
+ // `image` is an explicit skate-specific override; when null we fall back to
+ // the kind-0 avatar (shown in the UI as "using your profile photo").
const [image, setImage] = useState(null);
const [isGettingLocation, setIsGettingLocation] = useState(false);
+ // Ensures we only auto-prefill from kind-0 once, and never clobber typing.
+ const [prefilled, setPrefilled] = useState(false);
- // Prefill from an existing profile when editing.
+ // Prefill: existing 35000 wins; otherwise borrow shared fields from kind-0.
useEffect(() => {
+ if (prefilled) return;
if (existing) {
setBio(existing.bio || '');
setLocation(existing.location || '');
@@ -73,10 +81,16 @@ export default function BecomeSkaterPage() {
setYears(existing.yearsSkating || '');
setHomeSpot(existing.homeSpot || '');
setInstagram(existing.instagram || '');
- setWebsite(existing.website || '');
+ setWebsite(existing.website || kind0Website);
if (existing.image) setImage(existing.image);
+ setPrefilled(true);
+ } else if (author.data) {
+ // New skater — seed the shared fields from their Nostr profile.
+ setBio(kind0About);
+ setWebsite(kind0Website);
+ setPrefilled(true);
}
- }, [existing]);
+ }, [existing, author.data, kind0About, kind0Website, prefilled]);
const toggleStyle = (s: SkaterStyle) =>
setStyles((prev) => (prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]));
@@ -147,10 +161,18 @@ export default function BecomeSkaterPage() {
if (years.trim()) tags.push(['years', years.trim()]);
if (homeSpot.trim()) tags.push(['home_spot', homeSpot.trim()]);
if (instagram.trim()) tags.push(['instagram', instagram.trim()]);
- if (website.trim()) tags.push(['website', website.trim()]);
- if (image) tags.push(['image', image]);
+ // Store shared fields ONLY as overrides — if they match kind-0, leave them
+ // out so kind-0 stays the single source of truth and nothing goes stale.
+ const websiteTrim = website.trim();
+ if (websiteTrim && websiteTrim !== kind0Website.trim()) {
+ tags.push(['website', websiteTrim]);
+ }
+ if (image && image !== avatar) tags.push(['image', image]);
- await createEvent({ kind: SKATE_SKATER_KIND, content: bio.trim(), tags });
+ const bioTrim = bio.trim();
+ const content = bioTrim === kind0About.trim() ? '' : bioTrim;
+
+ await createEvent({ kind: SKATE_SKATER_KIND, content, tags });
toast({
title: existing ? 'Profile updated!' : "You're on the map! 🛹",
@@ -214,6 +236,18 @@ export default function BecomeSkaterPage() {
+ {/* Linked-to-your-profile notice */}
+
+
+
+ This is your skate layer, built on your Nostr profile.
+ Your name, photo & bio come from there — edit your full profile to change them everywhere.
+
+
+
{loadingExisting ? (
Loading your profile…
@@ -224,7 +258,14 @@ export default function BecomeSkaterPage() {
-
+
+
+ {bio.trim() && bio.trim() === kind0About.trim() && (
+
+ From your profile
+
+ )}
+
+
+ {kind0About.trim()
+ ? 'Pulled from your profile — tweak it for your skater card, or leave it as is.'
+ : 'A short intro for your skater card.'}
+
{/* Photo */}
-
- {image ? (
-
-
-
+
+
+ {image ? (
+
+
+
+
+ ) : avatar ? (
+ // Falling back to the kind-0 avatar — no upload needed.
+
+
+
+ Your profile pic
+
+
+ ) : (
+
+ )}
+
+
+ {image
+ ? 'A skate-specific shot for your card.'
+ : avatar
+ ? 'Using your profile photo. Upload an action shot to override it here.'
+ : 'Add an action shot for your skater card (optional).'}
+