Profile access, real gig messaging, desktop Local Chat removal + audit fixes

#0 Crew band gig booking:
- Rename "Book them for a gig" -> "Set up a gig" (honest: it creates an event)
- Add "Message about a gig" -> pre-filled encrypted DM (NIP-17) to the crew
  founder via existing /messages/:npub?message= flow (real communication)

#1 Profile access (was missing/mislabeled everywhere):
- Desktop header: add "View my profile" icon linking to /npub
- Mobile user menu: split mislabeled "My Profile"(->Settings) into
  "View Profile" (-> /npub) + "Settings"
- Burger menu: add "View Profile" entry
- Settings > Edit Profile: add "View my public profile" quick link

#2 Remove standalone Local Chat button on desktop map toolbar (kept on mobile)

Audit fixes:
- Mobile menu "Wallet & Stash" -> "Wallet" (Stash is already its own item)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-30 21:16:15 +07:00
parent 8056b02ad1
commit ee9e04d3e0
4 changed files with 63 additions and 10 deletions

View file

@ -1,5 +1,6 @@
import { useState, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import { cn } from '@/lib/utils';
import { CustomizeNavSheet } from '@/components/CustomizeNavSheet';
import { useNavPreferences, useDesktopNavPreferences, type NavTab } from '@/hooks/useNavPreferences';
@ -193,9 +194,15 @@ function MobileHeaderUserMenu({
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/settings" className="flex items-center gap-2 cursor-pointer">
<Link to={`/${nip19.npubEncode(pubkey)}`} className="flex items-center gap-2 cursor-pointer">
<User className="h-4 w-4" />
My Profile
View Profile
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/settings" className="flex items-center gap-2 cursor-pointer">
<Settings className="h-4 w-4" />
Settings
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
@ -213,7 +220,7 @@ function MobileHeaderUserMenu({
<DropdownMenuItem asChild>
<Link to="/wallet" className="flex items-center gap-2 cursor-pointer">
<Wallet className="h-4 w-4 text-orange-400" />
<span className="text-orange-400">Wallet &amp; Stash</span>
<span className="text-orange-400">Wallet</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
@ -569,8 +576,21 @@ export function Layout({ children, noPadding }: LayoutProps) {
<OfflineIndicator />
{/* Language Toggle */}
<LanguageToggle compact className="text-gray-400 hover:text-white" />
{/* View own public profile */}
{user && (
<Link to={`/${nip19.npubEncode(user.pubkey)}`}>
<Button
variant="ghost"
size="icon"
className="text-gray-400 hover:text-[#ff0040] hover:bg-[#ff0040]/10"
title="View my profile"
>
<User className="h-5 w-5" />
</Button>
</Link>
)}
<Link to="/settings">
<Button variant="ghost" size="icon">
<Button variant="ghost" size="icon" title="Settings">
<Settings className="h-5 w-5" />
</Button>
</Link>
@ -661,6 +681,15 @@ export function Layout({ children, noPadding }: LayoutProps) {
onClick={() => setMobileMenuOpen(false)}
/>
))}
{user && (
<NavLink
href={`/${nip19.npubEncode(user.pubkey)}`}
label="View Profile"
icon={User}
isActive={false}
onClick={() => setMobileMenuOpen(false)}
/>
)}
{user && (
<NavLink
href="/messages"

View file

@ -44,6 +44,7 @@ import { NoteContent } from '@/components/NoteContent';
import { useCrew } from '@/hooks/useCrews';
import { useCrewFeed } from '@/hooks/useCrewFeed';
import { useAuthor } from '@/hooks/useAuthor';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { genUserName } from '@/lib/genUserName';
import { SKATE_CREW_KIND, SKATE_SPOT_KIND, CREW_VIBE_LABELS, SHOW_CREW_TREASURY } from '@/lib/types';
import { sanitizeUrl, urlDomain } from '@/lib/sanitizeUrl';
@ -130,6 +131,7 @@ export default function CrewDetailPage() {
const { pubkey, identifier } = useParams<{ pubkey: string; identifier: string }>();
const { data: crew, isLoading, error } = useCrew(identifier || '', pubkey);
const author = useAuthor(crew?.pubkey);
const { user } = useCurrentUser();
const { isFounder, isAdmin, role, canManage } = useCrewRole(crew);
const { toast } = useToast();
const [agentOpen, setAgentOpen] = useState(false);
@ -560,11 +562,20 @@ export default function CrewDetailPage() {
to={`/create-event?crew=${crew.pubkey}:${crew.identifier}&type=concert&title=${encodeURIComponent(`${crew.bandName} live`)}`}
className="flex items-center justify-center gap-2 rounded-lg bg-purple-500 px-3 py-2 text-sm font-black uppercase text-white transition-colors hover:bg-purple-600"
>
<Calendar className="h-4 w-4" /> Book them for a gig
<Calendar className="h-4 w-4" /> Set up a gig
</Link>
{user && user.pubkey !== crew.pubkey && (
<Link
to={`/messages/${npub}?message=${encodeURIComponent(`Hey ${crew.name} — interested in booking ${crew.bandName} for a gig. Are you around to talk dates?`)}`}
className="flex items-center justify-center gap-2 rounded-lg border border-purple-500/30 bg-purple-500/10 px-3 py-2 text-sm font-bold text-purple-200 transition-colors hover:bg-purple-500/20"
>
<MessageSquare className="h-4 w-4" /> Message about a gig
</Link>
)}
<p className="text-center text-[11px] leading-snug text-gray-500">
Sets up a music event hosted by this crew on the Events calendar —
sort the date &amp; venue and it's booked.
<strong className="text-gray-400">Set up a gig</strong> adds a music event to the
calendar hosted by this crew. <strong className="text-gray-400">Message</strong> opens
an encrypted DM to the crew to sort dates &amp; details.
</p>
</CardContent>
</Card>

View file

@ -1437,16 +1437,17 @@ export default function MapPage() {
{/* Spacer */}
<div className="flex-1 min-w-2" />
{/* Local Chat - live geohash channel for wherever the map is centered */}
{/* Local Chat - live geohash channel for wherever the map is centered.
Mobile only: on desktop the toolbar is dense and DMs/crew chat cover
messaging, so we hide the standalone Local Chat trigger at md+. */}
<Sheet>
<SheetTrigger asChild>
<Button
size="sm"
variant="outline"
className="gap-1.5 border-2 border-purple-500/50 hover:bg-purple-500/10 text-purple-400 font-bold h-8 px-2 sm:px-3 flex-shrink-0"
className="md:hidden gap-1.5 border-2 border-purple-500/50 hover:bg-purple-500/10 text-purple-400 font-bold h-8 px-2 flex-shrink-0"
>
<MessageCircle className="h-4 w-4" />
<span className="hidden md:inline">Local Chat</span>
</Button>
</SheetTrigger>
<SheetContent

View file

@ -15,6 +15,7 @@ import { useTranslation } from '@/contexts/LanguageContext';
import { useQueryClient } from '@tanstack/react-query';
import { useNostr } from '@nostrify/react';
import { NSchema as n } from '@nostrify/nostrify';
import { nip19 } from 'nostr-tools';
import { useState, useEffect, useRef, useCallback } from 'react';
import { LoginArea } from '@/components/auth/LoginArea';
import { Button } from '@/components/ui/button';
@ -183,6 +184,17 @@ function ProfileScreen({ onBack }: { onBack: () => void }) {
</div>
) : (
<div className="space-y-4">
{/* Quick link to the user's own public profile page */}
<Link
to={`/${nip19.npubEncode(user.pubkey)}`}
className="flex items-center justify-between gap-3 p-3 rounded-2xl bg-muted/50 border border-border hover:bg-muted transition-colors"
>
<span className="flex items-center gap-2 text-sm font-medium">
<User className="h-4 w-4 text-[#ff0040]" />
View my public profile
</span>
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</Link>
{!metadata?.name && (
<div className="flex items-center gap-3 p-3 rounded-2xl bg-yellow-950/30 border border-yellow-500/40">
<AlertCircle className="h-4 w-4 text-yellow-500 flex-shrink-0" />