From bc1987f78c425e3e02a0089ff39803a8b13a309d Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Thu, 29 Oct 2026 20:20:49 +0700 Subject: [PATCH] feat: sponsor-ready profiles with follow, events and audience stats --- src/hooks/useFollowing.ts | 4 +- src/hooks/useSkateEvents.ts | 36 +++++++++ src/i18n/ca/batch6.ts | 10 +++ src/i18n/cs/batch4.ts | 10 +++ src/i18n/de/batch4.ts | 10 +++ src/i18n/el/batch4.ts | 10 +++ src/i18n/en.ts | 10 +++ src/i18n/es.ts | 10 +++ src/i18n/eu/batch6.ts | 10 +++ src/i18n/fr/batch4.ts | 10 +++ src/i18n/gl/batch6.ts | 10 +++ src/i18n/hi/batch6.ts | 10 +++ src/i18n/id/batch4.ts | 10 +++ src/i18n/it/batch4.ts | 10 +++ src/i18n/ja/batch7.ts | 10 +++ src/i18n/ko/batch4.ts | 10 +++ src/i18n/nl/batch4.ts | 10 +++ src/i18n/pl/batch4.ts | 10 +++ src/i18n/pt/batch4.ts | 10 +++ src/i18n/ro/batch4.ts | 10 +++ src/i18n/ru/batch4.ts | 10 +++ src/i18n/sv/batch4.ts | 10 +++ src/i18n/th/batch9.ts | 10 +++ src/i18n/tr/batch4.ts | 10 +++ src/i18n/uk/batch4.ts | 10 +++ src/i18n/vi/batch4.ts | 10 +++ src/i18n/zh-CN/batch4.ts | 10 +++ src/i18n/zh-TW/batch4.ts | 10 +++ src/pages/NIP19Page.tsx | 148 +++++++++++++++++++++++++++++++++--- 29 files changed, 438 insertions(+), 10 deletions(-) diff --git a/src/hooks/useFollowing.ts b/src/hooks/useFollowing.ts index 794ec22..21e7741 100644 --- a/src/hooks/useFollowing.ts +++ b/src/hooks/useFollowing.ts @@ -119,6 +119,7 @@ export function useFollowActions() { onSuccess: (_targetPubkey) => { // Invalidate queries to refetch queryClient.invalidateQueries({ queryKey: ['following', user?.pubkey] }); + queryClient.invalidateQueries({ queryKey: ['followers', _targetPubkey] }); queryClient.invalidateQueries({ queryKey: ['trust-score'] }); toast({ @@ -165,9 +166,10 @@ export function useFollowActions() { return targetPubkey; }, - onSuccess: () => { + onSuccess: (targetPubkey) => { // Invalidate queries queryClient.invalidateQueries({ queryKey: ['following', user?.pubkey] }); + queryClient.invalidateQueries({ queryKey: ['followers', targetPubkey] }); queryClient.invalidateQueries({ queryKey: ['trust-score'] }); toast({ diff --git a/src/hooks/useSkateEvents.ts b/src/hooks/useSkateEvents.ts index d3c1436..a9cac27 100644 --- a/src/hooks/useSkateEvents.ts +++ b/src/hooks/useSkateEvents.ts @@ -105,6 +105,42 @@ export function useUpcomingSkateEvents(limit = 20, options: { enabled?: boolean }); } +/** Events authored by one pubkey (profile page). Upcoming first, then recent. */ +export function useUserEvents(pubkey: string | undefined) { + const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); + const hidden = useAdminModeration(); + + return useQuery({ + queryKey: ['user-skate-events', pubkey, getQueryKeySuffix()], + enabled: !!pubkey, + select: (events: SkateEvent[]) => filterAdminHidden(events, CALENDAR_EVENT_KIND, hidden), + queryFn: async () => { + if (!pubkey) return []; + const signal = AbortSignal.timeout(12000); + const events = await querySkateContent( + [{ + kinds: [CALENDAR_EVENT_KIND], + authors: [pubkey], + '#t': ['skateboarding'], + limit: 50, + }], + { signal }, + ); + const now = Math.floor(Date.now() / 1000); + return events + .map(parseSkateEvent) + .filter((event): event is SkateEvent => !!event) + .sort((a, b) => { + const aUpcoming = a.start >= now; + const bUpcoming = b.start >= now; + if (aUpcoming !== bUpcoming) return aUpcoming ? -1 : 1; + return aUpcoming ? a.start - b.start : b.start - a.start; + }); + }, + staleTime: 60_000, + }); +} + export function useSkateEvent(identifier: string, pubkey?: string) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const hidden = useAdminModeration(); diff --git a/src/i18n/ca/batch6.ts b/src/i18n/ca/batch6.ts index 3458204..387ab7e 100644 --- a/src/i18n/ca/batch6.ts +++ b/src/i18n/ca/batch6.ts @@ -962,6 +962,16 @@ const batch6 = { nip05Verified: "Verificat amb NIP-05", nip05NotVerified: "NIP-05 no verificat amb aquesta clau", message: "Missatge", + eventsHosted: "Esdeveniments organitzats", + forSponsors: "Per a patrocinadors i col·laboracions", + sponsorPitch: "Patrocini, col·laboració, foto/vídeo o esdeveniment? Comença una conversa.", + startCollab: "Inicia un xat de col·laboració", + viewRiderCard: "Veure la fitxa del patinador", + collabMessage: "Hola {{name}}, voldria parlar d'un patrocini o d'una col·laboració.", + followers: "Seguidors", + following: "Seguint", + follow: "Segueix", + unfollow: "Deixa de seguir", zapsReceived: "zaps rebuts", memberSince: "Membre des de", badges: "Insígnies", diff --git a/src/i18n/cs/batch4.ts b/src/i18n/cs/batch4.ts index 95f592e..dcd3ebf 100644 --- a/src/i18n/cs/batch4.ts +++ b/src/i18n/cs/batch4.ts @@ -936,6 +936,16 @@ export const csBatch4 = { nip05Verified: "NIP-05 ověřeno", nip05NotVerified: "NIP-05 neověřeno vůči tomuto klíči", message: "Zpráva", + eventsHosted: "Uspořádané akce", + forSponsors: "Pro sponzory a spolupráce", + sponsorPitch: "Sponzorství, spolupráce, foto/video nebo akce? Začni konverzaci.", + startCollab: "Začít chat o spolupráci", + viewRiderCard: "Zobrazit kartu jezdce", + collabMessage: "Ahoj {{name}}, chtěl bych mluvit o sponzorství nebo spolupráci.", + followers: "Sledující", + following: "Sleduji", + follow: "Sledovat", + unfollow: "Přestat sledovat", zapsReceived: "přijatých zaps", memberSince: "Členem od", badges: "Odznaky", diff --git a/src/i18n/de/batch4.ts b/src/i18n/de/batch4.ts index 10a94ba..d7b3727 100644 --- a/src/i18n/de/batch4.ts +++ b/src/i18n/de/batch4.ts @@ -936,6 +936,16 @@ const deBatch4 = { nip05Verified: "NIP-05 verifiziert", nip05NotVerified: "NIP-05 nicht gegen diesen Schlüssel verifiziert", message: "Nachricht", + eventsHosted: "Organisierte Events", + forSponsors: "Für Sponsoren & Kooperationen", + sponsorPitch: "Sponsoring, Kooperation, Foto/Video oder Event? Starte ein Gespräch.", + startCollab: "Kooperations-Chat starten", + viewRiderCard: "Skateboarder-Karte ansehen", + collabMessage: "Hi {{name}}, ich würde gern über ein Sponsoring oder eine Kooperation sprechen.", + followers: "Follower", + following: "Folge ich", + follow: "Folgen", + unfollow: "Entfolgen", zapsReceived: "erhaltene Zaps", memberSince: "Mitglied seit", badges: "Abzeichen", diff --git a/src/i18n/el/batch4.ts b/src/i18n/el/batch4.ts index f692491..c182344 100644 --- a/src/i18n/el/batch4.ts +++ b/src/i18n/el/batch4.ts @@ -936,6 +936,16 @@ const elBatch4 = { nip05Verified: "Επαληθευμένο NIP-05", nip05NotVerified: "Το NIP-05 δεν επαληθεύτηκε με αυτό το κλειδί", message: "Μήνυμα", + eventsHosted: "Εκδηλώσεις που διοργάνωσες", + forSponsors: "Για χορηγούς και συνεργασίες", + sponsorPitch: "Χορηγία, συνεργασία, φωτογραφία/βίντεο ή εκδήλωση; Ξεκίνα μια συζήτηση.", + startCollab: "Έναρξη συνομιλίας συνεργασίας", + viewRiderCard: "Προβολή κάρτας σκέιτερ", + collabMessage: "Γεια σου {{name}}, θα ήθελα να μιλήσουμε για χορηγία ή συνεργασία.", + followers: "Ακόλουθοι", + following: "Ακολουθείς", + follow: "Ακολούθησε", + unfollow: "Σταμάτα να ακολουθείς", zapsReceived: "zaps που ελήφθησαν", memberSince: "Μέλος από", badges: "Badges", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 8fa4a9a..9da8539 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -6744,6 +6744,16 @@ walletUi: { nip05Verified: 'NIP-05 verified', nip05NotVerified: 'NIP-05 not verified against this key', message: 'Message', + eventsHosted: 'Events hosted', + forSponsors: 'For sponsors & collabs', + sponsorPitch: 'Sponsorship, collab, photo/video or event? Start a conversation.', + startCollab: 'Start a collab chat', + viewRiderCard: 'View rider card', + collabMessage: "Hi {{name}}, I'd like to talk about a sponsorship or collaboration.", + followers: 'Followers', + following: 'Following', + follow: 'Follow', + unfollow: 'Unfollow', zapsReceived: 'zaps received', memberSince: 'Member since', badges: 'Badges', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index c23e7b5..9a7efd2 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -6744,6 +6744,16 @@ walletUi: { nip05Verified: 'NIP-05 verificado', nip05NotVerified: 'NIP-05 no verificado con esta clave', message: 'Mensaje', + eventsHosted: 'Eventos organizados', + forSponsors: 'Para patrocinadores y colaboraciones', + sponsorPitch: '¿Patrocinio, colaboración, foto/vídeo o evento? Empieza una conversación.', + startCollab: 'Iniciar chat de colaboración', + viewRiderCard: 'Ver tarjeta de rider', + collabMessage: 'Hola {{name}}, me gustaría hablar sobre un patrocinio o una colaboración.', + followers: 'Seguidores', + following: 'Siguiendo', + follow: 'Seguir', + unfollow: 'Dejar de seguir', zapsReceived: 'zaps recibidos', memberSince: 'Miembro desde', badges: 'Insignias', diff --git a/src/i18n/eu/batch6.ts b/src/i18n/eu/batch6.ts index 322a4a7..5eddf2f 100644 --- a/src/i18n/eu/batch6.ts +++ b/src/i18n/eu/batch6.ts @@ -962,6 +962,16 @@ const batch6 = { nip05Verified: "NIP-05 egiaztatua", nip05NotVerified: "NIP-05 ez da gako honekin egiaztatu", message: "Mezua", + eventsHosted: "Antolatutako ekitaldiak", + forSponsors: "Babesleentzat eta lankidetzetarako", + sponsorPitch: "Babesa, lankidetza, argazki/bideoa edo ekitaldia? Hasi solasaldia.", + startCollab: "Hasi lankidetza-txata", + viewRiderCard: "Ikusi patinatzailearen txartela", + collabMessage: "Kaixo {{name}}, babes edo lankidetza bati buruz hitz egin nahi nuke.", + followers: "Jarraitzaileak", + following: "Jarraitzen", + follow: "Jarraitu", + unfollow: "Utzi jarraitzeari", zapsReceived: "jasotako zapak", memberSince: "Kidea noiztik", badges: "Dominak", diff --git a/src/i18n/fr/batch4.ts b/src/i18n/fr/batch4.ts index c42656b..df95192 100644 --- a/src/i18n/fr/batch4.ts +++ b/src/i18n/fr/batch4.ts @@ -936,6 +936,16 @@ const frBatch4 = { nip05Verified: "NIP-05 vérifié", nip05NotVerified: "NIP-05 non vérifié pour cette clé", message: "Message", + eventsHosted: "Événements organisés", + forSponsors: "Pour les sponsors et collaborations", + sponsorPitch: "Sponsoring, collaboration, photo/vidéo ou événement ? Lance la conversation.", + startCollab: "Démarrer une discussion de collaboration", + viewRiderCard: "Voir la fiche du skateur", + collabMessage: "Salut {{name}}, j'aimerais parler d'un sponsoring ou d'une collaboration.", + followers: "Abonnés", + following: "Abonnements", + follow: "Suivre", + unfollow: "Ne plus suivre", zapsReceived: "zaps reçus", memberSince: "Membre depuis", badges: "Badges", diff --git a/src/i18n/gl/batch6.ts b/src/i18n/gl/batch6.ts index ab99777..108b4f0 100644 --- a/src/i18n/gl/batch6.ts +++ b/src/i18n/gl/batch6.ts @@ -962,6 +962,16 @@ const batch6 = { "nip05Verified": "Verificado por NIP-05", "nip05NotVerified": "NIP-05 non verificado con esta chave", "message": "Mensaxe", + "eventsHosted": "Eventos organizados", + "forSponsors": "Para patrocinadores e colaboracións", + "sponsorPitch": "Patrocinio, colaboración, foto/vídeo ou evento? Comeza unha conversa.", + "startCollab": "Iniciar chat de colaboración", + "viewRiderCard": "Ver a ficha do patinador", + "collabMessage": "Ola {{name}}, gustaríame falar dun patrocinio ou dunha colaboración.", + "followers": "Seguidores", + "following": "Seguindo", + "follow": "Seguir", + "unfollow": "Deixar de seguir", "zapsReceived": "zaps recibidos", "memberSince": "Membro desde", "badges": "Insignias", diff --git a/src/i18n/hi/batch6.ts b/src/i18n/hi/batch6.ts index da48249..7305bd2 100644 --- a/src/i18n/hi/batch6.ts +++ b/src/i18n/hi/batch6.ts @@ -962,6 +962,16 @@ const batch6 = { "nip05Verified": "NIP-05 सत्यापित", "nip05NotVerified": "NIP-05 इस कुंजी से सत्यापित नहीं", "message": "संदेश", + "eventsHosted": "आयोजित कार्यक्रम", + "forSponsors": "प्रायोजकों और सहयोगों के लिए", + "sponsorPitch": "प्रायोजन, सहयोग, फ़ोटो/वीडियो या कार्यक्रम? बातचीत शुरू करें।", + "startCollab": "सहयोग बातचीत शुरू करें", + "viewRiderCard": "स्केटर कार्ड देखें", + "collabMessage": "नमस्ते {{name}}, क्या हम प्रायोजन या सहयोग पर बात कर सकते हैं?", + "followers": "फ़ॉलोअर", + "following": "फ़ॉलो कर रहे", + "follow": "फ़ॉलो करें", + "unfollow": "फ़ॉलो हटाएँ", "zapsReceived": "zaps प्राप्त", "memberSince": "सदस्यता से", "badges": "बैज", diff --git a/src/i18n/id/batch4.ts b/src/i18n/id/batch4.ts index 0215384..ea28995 100644 --- a/src/i18n/id/batch4.ts +++ b/src/i18n/id/batch4.ts @@ -942,6 +942,16 @@ const idBatch4 = { nip05Verified: 'Terverifikasi NIP-05', nip05NotVerified: 'NIP-05 tidak terverifikasi terhadap kunci ini', message: 'Pesan', + eventsHosted: 'Acara yang diselenggarakan', + forSponsors: 'Untuk sponsor & kolaborasi', + sponsorPitch: 'Sponsorship, kolaborasi, foto/video, atau acara? Mulai percakapan.', + startCollab: 'Mulai chat kolaborasi', + viewRiderCard: 'Lihat kartu peseluncur', + collabMessage: 'Hai {{name}}, saya ingin membahas sponsorship atau kolaborasi.', + followers: 'Pengikut', + following: 'Mengikuti', + follow: 'Ikuti', + unfollow: 'Berhenti Ikuti', zapsReceived: 'zap diterima', memberSince: 'Anggota sejak', badges: 'Lencana', diff --git a/src/i18n/it/batch4.ts b/src/i18n/it/batch4.ts index 4c1819e..15a0b22 100644 --- a/src/i18n/it/batch4.ts +++ b/src/i18n/it/batch4.ts @@ -936,6 +936,16 @@ const itBatch4 = { nip05Verified: "Verificato NIP-05", nip05NotVerified: "NIP-05 non verificato con questa chiave", message: "Messaggio", + eventsHosted: "Eventi organizzati", + forSponsors: "Per sponsor e collaborazioni", + sponsorPitch: "Sponsorizzazione, collaborazione, foto/video o evento? Inizia una conversazione.", + startCollab: "Avvia una chat di collaborazione", + viewRiderCard: "Vedi la scheda dello skater", + collabMessage: "Ciao {{name}}, vorrei parlare di una sponsorizzazione o di una collaborazione.", + followers: "Follower", + following: "Seguiti", + follow: "Segui", + unfollow: "Smetti di seguire", zapsReceived: "zap ricevuti", memberSince: "Membro dal", badges: "Distintivi", diff --git a/src/i18n/ja/batch7.ts b/src/i18n/ja/batch7.ts index f401d33..dbce5bc 100644 --- a/src/i18n/ja/batch7.ts +++ b/src/i18n/ja/batch7.ts @@ -945,6 +945,16 @@ const jaBatch7 = { nip05Verified: 'NIP-05認証済み', nip05NotVerified: 'NIP-05がこの鍵で認証されていません', message: 'メッセージ', + eventsHosted: '主催イベント', + forSponsors: 'スポンサー・コラボ募集', + sponsorPitch: 'スポンサー、コラボ、写真/動画、イベントの依頼はありませんか?まずはご相談ください。', + startCollab: 'コラボチャットを始める', + viewRiderCard: 'ライダーカードを見る', + collabMessage: 'こんにちは{{name}}さん、スポンサーやコラボについてお話ししたいです。', + followers: 'フォロワー', + following: 'フォロー中', + follow: 'フォロー', + unfollow: 'フォロー解除', zapsReceived: '受信したzaps', memberSince: '登録日', badges: 'バッジ', diff --git a/src/i18n/ko/batch4.ts b/src/i18n/ko/batch4.ts index 4aa87d4..b7fdab0 100644 --- a/src/i18n/ko/batch4.ts +++ b/src/i18n/ko/batch4.ts @@ -944,6 +944,16 @@ const koBatch4 = { nip05Verified: 'NIP-05 인증됨', nip05NotVerified: '이 키에 대해 NIP-05가 인증되지 않음', message: '메시지', + eventsHosted: '주최한 이벤트', + forSponsors: '스폰서 및 협업 문의', + sponsorPitch: '스폰서십, 협업, 사진/영상 또는 이벤트? 대화를 시작해 보세요.', + startCollab: '협업 채팅 시작', + viewRiderCard: '라이더 카드 보기', + collabMessage: '안녕하세요 {{name}}님, 스폰서십이나 협업에 대해 이야기하고 싶어요.', + followers: '팔로워', + following: '팔로잉', + follow: '팔로우', + unfollow: '언팔로우', zapsReceived: '받은 Zap', memberSince: '가입일', badges: '배지', diff --git a/src/i18n/nl/batch4.ts b/src/i18n/nl/batch4.ts index 962419d..20571fe 100644 --- a/src/i18n/nl/batch4.ts +++ b/src/i18n/nl/batch4.ts @@ -944,6 +944,16 @@ const nlBatch4 = { nip05Verified: 'NIP-05 geverifieerd', nip05NotVerified: 'NIP-05 niet geverifieerd tegen deze sleutel', message: 'Bericht', + eventsHosted: 'Georganiseerde evenementen', + forSponsors: 'Voor sponsors & samenwerkingen', + sponsorPitch: 'Sponsoring, samenwerking, foto/video of evenement? Start een gesprek.', + startCollab: 'Start een samenwerkingschat', + viewRiderCard: 'Bekijk de skaterkaart', + collabMessage: 'Hoi {{name}}, ik wil graag praten over een sponsoring of samenwerking.', + followers: 'Volgers', + following: 'Volgend', + follow: 'Volgen', + unfollow: 'Ontvolgen', zapsReceived: 'zaps ontvangen', memberSince: 'Lid sinds', badges: 'Badges', diff --git a/src/i18n/pl/batch4.ts b/src/i18n/pl/batch4.ts index a8aaeff..b0be6a4 100644 --- a/src/i18n/pl/batch4.ts +++ b/src/i18n/pl/batch4.ts @@ -936,6 +936,16 @@ const plBatch4 = { nip05Verified: "Zweryfikowano NIP-05", nip05NotVerified: "NIP-05 niezweryfikowany względem tego klucza", message: "Wiadomość", + eventsHosted: "Organizowane wydarzenia", + forSponsors: "Dla sponsorów i współprac", + sponsorPitch: "Sponsoring, współpraca, foto/wideo lub wydarzenie? Rozpocznij rozmowę.", + startCollab: "Rozpocznij czat o współpracy", + viewRiderCard: "Zobacz kartę deskorolkarza", + collabMessage: "Cześć {{name}}, chciałbym porozmawiać o sponsoringu lub współpracy.", + followers: "Obserwujący", + following: "Obserwowani", + follow: "Obserwuj", + unfollow: "Przestań obserwować", zapsReceived: "otrzymanych zapów", memberSince: "Członek od", badges: "Odznaki", diff --git a/src/i18n/pt/batch4.ts b/src/i18n/pt/batch4.ts index 6625576..2fe7355 100644 --- a/src/i18n/pt/batch4.ts +++ b/src/i18n/pt/batch4.ts @@ -1071,6 +1071,16 @@ const ptBatch4 = { nip05Verified: 'Verificado por NIP-05', nip05NotVerified: 'NIP-05 não verificado para esta chave', message: 'Mensagem', + eventsHosted: 'Eventos organizados', + forSponsors: 'Para patrocinadores e colaborações', + sponsorPitch: 'Patrocínio, colaboração, foto/vídeo ou evento? Comece uma conversa.', + startCollab: 'Iniciar chat de colaboração', + viewRiderCard: 'Ver cartão do skatista', + collabMessage: 'Oi {{name}}, quero falar sobre um patrocínio ou uma colaboração.', + followers: 'Seguidores', + following: 'Seguindo', + follow: 'Seguir', + unfollow: 'Deixar de seguir', zapsReceived: 'zaps recebidos', memberSince: 'Membro desde', badges: 'Selos', diff --git a/src/i18n/ro/batch4.ts b/src/i18n/ro/batch4.ts index 2dcdb51..40985b2 100644 --- a/src/i18n/ro/batch4.ts +++ b/src/i18n/ro/batch4.ts @@ -936,6 +936,16 @@ const roBatch4 = { nip05Verified: 'Verificat NIP-05', nip05NotVerified: 'NIP-05 neverificat pentru această cheie', message: 'Mesaj', + eventsHosted: 'Evenimente organizate', + forSponsors: 'Pentru sponsori și colaborări', + sponsorPitch: 'Sponsorizare, colaborare, foto/video sau eveniment? Începe o conversație.', + startCollab: 'Începe un chat de colaborare', + viewRiderCard: 'Vezi cardul riderului', + collabMessage: 'Salut {{name}}, aș vrea să vorbim despre o sponsorizare sau o colaborare.', + followers: 'Urmăritori', + following: 'Urmărești', + follow: 'Urmărește', + unfollow: 'Nu mai urmări', zapsReceived: 'zap-uri primite', memberSince: 'Membru din', badges: 'Insigne', diff --git a/src/i18n/ru/batch4.ts b/src/i18n/ru/batch4.ts index e085142..b29b6d2 100644 --- a/src/i18n/ru/batch4.ts +++ b/src/i18n/ru/batch4.ts @@ -936,6 +936,16 @@ const ruBatch4 = { nip05Verified: 'NIP-05 подтверждён', nip05NotVerified: 'NIP-05 не подтверждён для этого ключа', message: 'Сообщение', + eventsHosted: 'Проведённые мероприятия', + forSponsors: 'Для спонсоров и коллабораций', + sponsorPitch: 'Спонсорство, коллаборация, фото/видео или мероприятие? Начните разговор.', + startCollab: 'Начать чат о коллаборации', + viewRiderCard: 'Посмотреть карточку райдера', + collabMessage: 'Привет, {{name}}! Хочу обсудить спонсорство или коллаборацию.', + followers: 'Подписчики', + following: 'Подписки', + follow: 'Подписаться', + unfollow: 'Отписаться', zapsReceived: 'zaps получено', memberSince: 'Участник с', badges: 'Значки', diff --git a/src/i18n/sv/batch4.ts b/src/i18n/sv/batch4.ts index 8ea3056..b9d4294 100644 --- a/src/i18n/sv/batch4.ts +++ b/src/i18n/sv/batch4.ts @@ -936,6 +936,16 @@ const svBatch4 = { nip05Verified: "NIP-05 verifierad", nip05NotVerified: "NIP-05 inte verifierad mot den här nyckeln", message: "Meddelande", + eventsHosted: "Arrangerade evenemang", + forSponsors: "För sponsorer & samarbeten", + sponsorPitch: "Sponsring, samarbete, foto/video eller evenemang? Starta ett samtal.", + startCollab: "Starta en samarbetschatt", + viewRiderCard: "Visa skejtarkortet", + collabMessage: "Hej {{name}}, jag skulle vilja prata om sponsring eller samarbete.", + followers: "Följare", + following: "Följer", + follow: "Följ", + unfollow: "Sluta följa", zapsReceived: "zaps mottagna", memberSince: "Medlem sedan", badges: "Märken", diff --git a/src/i18n/th/batch9.ts b/src/i18n/th/batch9.ts index 8bc7e32..4829e53 100644 --- a/src/i18n/th/batch9.ts +++ b/src/i18n/th/batch9.ts @@ -1045,6 +1045,16 @@ const thBatch9 = { nip05Verified: 'ยืนยัน NIP-05 แล้ว', nip05NotVerified: 'NIP-05 ยังไม่ได้รับการยืนยันกับกุญแจนี้', message: 'ข้อความ', + eventsHosted: 'กิจกรรมที่จัดขึ้น', + forSponsors: 'สำหรับสปอนเซอร์และความร่วมมือ', + sponsorPitch: 'การสนับสนุน ความร่วมมือ ภาพ/วิดีโอ หรืออีเวนต์? เริ่มคุยกันได้เลย', + startCollab: 'เริ่มแชตความร่วมมือ', + viewRiderCard: 'ดูการ์ดนักสเก็ต', + collabMessage: 'สวัสดี {{name}} ฉันอยากคุยเรื่องการสนับสนุนหรือความร่วมมือ', + followers: 'ผู้ติดตาม', + following: 'กำลังติดตาม', + follow: 'ติดตาม', + unfollow: 'เลิกติดตาม', zapsReceived: 'Zap ที่ได้รับ', memberSince: 'สมาชิกตั้งแต่', badges: 'ป้าย', diff --git a/src/i18n/tr/batch4.ts b/src/i18n/tr/batch4.ts index e63d3c7..0c57cb2 100644 --- a/src/i18n/tr/batch4.ts +++ b/src/i18n/tr/batch4.ts @@ -936,6 +936,16 @@ const trBatch4 = { nip05Verified: "NIP-05 doğrulandı", nip05NotVerified: "NIP-05 bu anahtara karşı doğrulanmadı", message: "Mesaj", + eventsHosted: "Düzenlenen etkinlikler", + forSponsors: "Sponsorlar ve iş birlikleri için", + sponsorPitch: "Sponsorluk, iş birliği, fotoğraf/video veya etkinlik mi? Hemen konuşmaya başla.", + startCollab: "İş birliği sohbeti başlat", + viewRiderCard: "Rider kartını görüntüle", + collabMessage: "Merhaba {{name}}, bir sponsorluk veya iş birliği hakkında konuşmak istiyorum.", + followers: "Takipçiler", + following: "Takip Edilen", + follow: "Takip Et", + unfollow: "Takibi Bırak", zapsReceived: "zap alındı", memberSince: "Üyelik tarihi", badges: "Rozetler", diff --git a/src/i18n/uk/batch4.ts b/src/i18n/uk/batch4.ts index d902ace..5893bff 100644 --- a/src/i18n/uk/batch4.ts +++ b/src/i18n/uk/batch4.ts @@ -936,6 +936,16 @@ const ukBatch4 = { nip05Verified: "Підтверджено NIP-05", nip05NotVerified: "NIP-05 не підтверджено для цього ключа", message: "Повідомлення", + eventsHosted: "Проведені події", + forSponsors: "Для спонсорів і колаборацій", + sponsorPitch: "Спонсорство, колаборація, фото/відео чи подія? Почніть розмову.", + startCollab: "Почати чат про колаборацію", + viewRiderCard: "Переглянути картку райдера", + collabMessage: "Привіт, {{name}}! Хочу обговорити спонсорство або колаборацію.", + followers: "Підписники", + following: "Підписки", + follow: "Підписатися", + unfollow: "Відписатися", zapsReceived: "отримано zaps", memberSince: "Учасник з", badges: "Значки", diff --git a/src/i18n/vi/batch4.ts b/src/i18n/vi/batch4.ts index 3b0d86a..e572739 100644 --- a/src/i18n/vi/batch4.ts +++ b/src/i18n/vi/batch4.ts @@ -944,6 +944,16 @@ const viBatch4 = { nip05Verified: 'Đã xác minh NIP-05', nip05NotVerified: 'NIP-05 chưa được xác minh với khóa này', message: 'Nhắn tin', + eventsHosted: 'Sự kiện đã tổ chức', + forSponsors: 'Dành cho nhà tài trợ & hợp tác', + sponsorPitch: 'Tài trợ, hợp tác, ảnh/video hay sự kiện? Bắt đầu trò chuyện nhé.', + startCollab: 'Bắt đầu trò chuyện hợp tác', + viewRiderCard: 'Xem thẻ người trượt ván', + collabMessage: 'Chào {{name}}, tôi muốn trao đổi về tài trợ hoặc hợp tác.', + followers: 'Người theo dõi', + following: 'Đang theo dõi', + follow: 'Theo dõi', + unfollow: 'Bỏ theo dõi', zapsReceived: 'zap đã nhận', memberSince: 'Thành viên từ', badges: 'Huy hiệu', diff --git a/src/i18n/zh-CN/batch4.ts b/src/i18n/zh-CN/batch4.ts index 0c95f17..9264038 100644 --- a/src/i18n/zh-CN/batch4.ts +++ b/src/i18n/zh-CN/batch4.ts @@ -941,6 +941,16 @@ const zhCNBatch4 = { nip05Verified: "NIP-05 已验证", nip05NotVerified: "NIP-05 与此密钥未通过验证", message: "私信", + eventsHosted: "主办的活动", + forSponsors: "赞助与合作", + sponsorPitch: "赞助、合作、拍摄照片/视频还是办活动?来聊聊吧。", + startCollab: "发起合作聊天", + viewRiderCard: "查看滑手名片", + collabMessage: "你好 {{name}},我想聊聊赞助或合作的事。", + followers: "粉丝", + following: "关注中", + follow: "关注", + unfollow: "取消关注", zapsReceived: "收到的 zaps", memberSince: "加入于", badges: "徽章", diff --git a/src/i18n/zh-TW/batch4.ts b/src/i18n/zh-TW/batch4.ts index 339cced..c4a0557 100644 --- a/src/i18n/zh-TW/batch4.ts +++ b/src/i18n/zh-TW/batch4.ts @@ -941,6 +941,16 @@ const zhTWBatch4 = { nip05Verified: "NIP-05 已驗證", nip05NotVerified: "NIP-05 與此金鑰未透過驗證", message: "私信", + eventsHosted: "主辦的活動", + forSponsors: "贊助與合作", + sponsorPitch: "贊助、合作、拍攝照片/影片還是辦活動?來聊聊吧。", + startCollab: "發起合作聊天", + viewRiderCard: "查看滑手名片", + collabMessage: "你好 {{name}},我想聊聊贊助或合作的事。", + followers: "粉絲", + following: "追蹤中", + follow: "關注", + unfollow: "取消關注", zapsReceived: "收到的 zaps", memberSince: "加入於", badges: "徽章", diff --git a/src/pages/NIP19Page.tsx b/src/pages/NIP19Page.tsx index 63238d7..aa85d92 100644 --- a/src/pages/NIP19Page.tsx +++ b/src/pages/NIP19Page.tsx @@ -16,6 +16,7 @@ import { ClinkPayButton } from '@/components/ClinkPayButton'; import { useAuthor } from '@/hooks/useAuthor'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useUserSkateSpots } from '@/hooks/useSkateSpots'; +import { useUserEvents } from '@/hooks/useSkateEvents'; import { useUserShops } from '@/hooks/useShops'; import { useUserCrews } from '@/hooks/useCrews'; import { useUserPublicStash } from '@/hooks/useStashShowcase'; @@ -28,19 +29,21 @@ import { useCoordinator } from '@/hooks/useCoordinator'; import { coordinatorAddUrl, parseCoordinatorInput, shortNpub, CORDN_RELAY_URL } from '@/lib/coordinator'; import { toast } from '@/hooks/useToast'; import { SpotCard, SpotCardSkeleton } from '@/components/SpotCard'; +import { EventCard } from '@/components/EventCard'; import { genUserName } from '@/lib/genUserName'; import { sanitizeUrl } from '@/lib/sanitizeUrl'; import { cn } from '@/lib/utils'; import { SKATER_STANCES, SKATER_STYLES, SKATER_LEVELS, NEWSLETTER_KIND } from '@/lib/types'; import { NewsletterIssueView } from '@/components/NewsletterIssueView'; import NotFound from './NotFound'; -import { MapPin, Store, MessageCircle, Globe, Zap, ExternalLink, Copy, Check, ShieldCheck, Instagram, Share2, Users, Crown, Bot, Pencil, Package, Tag, ArrowRightLeft, Bitcoin, Award, MessageSquareLock, PersonStanding, Sparkles, Home, Clock, Server, Plus } from 'lucide-react'; +import { MapPin, Store, MessageCircle, Globe, Zap, ExternalLink, Copy, Check, ShieldCheck, ShieldAlert, Loader2, Instagram, Share2, Users, Crown, Bot, Pencil, Package, Tag, ArrowRightLeft, Bitcoin, Award, MessageSquareLock, PersonStanding, Sparkles, Home, Clock, Server, Plus, UserPlus, UserCheck, Handshake } from 'lucide-react'; import { useState, type ReactNode } from 'react'; import { UserStatusIndicator } from '@/components/UserStatusIndicator'; import { ShareProfileDialog } from '@/components/ShareProfileDialog'; import { ProfileBadges } from '@/components/ProfileBadges'; import { SKATESPOTS_BADGE, isSkateSpotsNip05, parseBadges } from '@/lib/badges'; import { useRiderStats } from '@/hooks/useRiderStats'; +import { useFollowers, useFollowing, useFollowActions, useIsFollowing } from '@/hooks/useFollowing'; import { useTranslation } from '@/contexts/language'; /** Format a "member since" year from a kind-0 event timestamp (seconds). */ @@ -122,6 +125,11 @@ function ProfilePage({ pubkey }: { pubkey: string }) { const { data: shops, isLoading: shopsLoading } = useUserShops(pubkey); const { memberships: crews, isLoading: crewsLoading } = useUserCrews(pubkey); const { data: publicStash, isLoading: publicStashLoading } = useUserPublicStash(pubkey); + const { data: events = [], isLoading: eventsLoading } = useUserEvents(pubkey); + const { data: followers = [], isLoading: followersLoading } = useFollowers(pubkey); + const { data: following = [] } = useFollowing(pubkey); + const { follow, unfollow, isFollowing: isFollowPending, isUnfollowing } = useFollowActions(); + const isFollowingProfile = useIsFollowing(pubkey); const { data: skater } = useUserSkaterProfile(pubkey); const [copied, setCopied] = useState(false); @@ -136,7 +144,7 @@ function ProfilePage({ pubkey }: { pubkey: string }) { const lud16 = metadata?.lud16; const isBot = metadata?.bot === true; // Soft NIP-05 verification — confirms the handle resolves to THIS pubkey. - const { data: nip05Result } = useCrewNip05(nip05, pubkey); + const { data: nip05Result, isLoading: nip05Loading } = useCrewNip05(nip05, pubkey); const nip05Verified = nip05Result?.status === 'verified'; // Custom CLINK offer code stored in the kind 0 content JSON. const raw = metadata as Record | undefined; @@ -174,6 +182,9 @@ function ProfilePage({ pubkey }: { pubkey: string }) { !!stanceMeta || !!levelMeta || !!skater.customLevel || skater.styles.length > 0 || !!skater.yearsSkating || !!skater.homeSpot || !!skater.location || !!skater.instagram ); + // Only invite sponsor/collab conversations on profiles that actually show + // skate activity — an empty kind-0 shouldn't read as a media kit. + const sponsorReady = hasSkateLayer || crews.length > 0 || (spots?.length ?? 0) > 0 || events.length > 0; const handleCopyNpub = () => { navigator.clipboard.writeText(npub); @@ -259,13 +270,23 @@ function ProfilePage({ pubkey }: { pubkey: string }) { )} title={nip05Verified ? t('walletUi.profile.nip05Verified') : t('walletUi.profile.nip05NotVerified')} > - {nip05Verified ? : } + {nip05Loading ? ( + + ) : nip05Verified ? ( + + ) : ( + + )} {nip05} - {nip05Verified && ( + {nip05Loading ? ( + + {t('auth.verifying')} + + ) : nip05Verified ? ( {t('common.verified')} - )} + ) : null}

)} {/* Extra verified handles (separate record) — a skater on several domains. */} @@ -309,7 +330,29 @@ function ProfilePage({ pubkey }: { pubkey: string }) { {/* Actions */} -
+
+ {!isOwnProfile && user && ( + + )} {/* Share — available on every profile */}
)} + {/* Events hosted by this skater — sponsor/community signal */} + {(eventsLoading || events.length > 0) && ( +
+
+

+ + {t('walletUi.profile.eventsHosted')} + {events.length > 0 && ( + + {events.length} + + )} +

+
+ {eventsLoading ? ( +
+ {Array.from({ length: 2 }).map((_, i) => ( + + ))} +
+ ) : ( +
+ {events.slice(0, 4).map((event) => ( + + ))} +
+ )} +
+ )} + {/* Public Stash — gear this skater chose to show publicly */} {(publicStashLoading || (publicStash && publicStash.length > 0)) && (
@@ -657,11 +738,11 @@ function ProfilePage({ pubkey }: { pubkey: string }) { >
{g.images[0] ? ( - {g.title} ) : ( @@ -711,6 +792,55 @@ function ProfilePage({ pubkey }: { pubkey: string }) { + {/* Sponsor / collab card — neutral, no assumed opt-in */} + {!isOwnProfile && sponsorReady && ( + +
+ +
+
+ +
+
+

+ {t('walletUi.profile.forSponsors')} +

+

{t('walletUi.profile.sponsorPitch')}

+
+
+ +
+ {[ + { label: t('walletUi.profile.followers'), value: followersLoading ? '—' : followers.length }, + { label: t('nav.events'), value: eventsLoading ? '—' : events.length }, + { label: t('walletUi.stats.spots'), value: spotsLoading ? '—' : (spots?.length ?? 0) }, + { label: t('walletUi.profile.zapsReceived'), value: riderStats ? riderStats.zapsReceived : '—' }, + ].map((stat) => ( +
+

{stat.value}

+

{stat.label}

+
+ ))} +
+ +
+ + +
+
+ + )} + {/* Quick Info Card */}