feat: frictionless first-run tip and inline mute actions
This commit is contained in:
parent
db6d420d26
commit
7a797c0fc6
33 changed files with 100 additions and 43 deletions
|
|
@ -23,6 +23,7 @@ import { TrustBadge } from '@/components/TrustBadge';
|
||||||
import { OfficialBadge } from '@/components/OfficialBadge';
|
import { OfficialBadge } from '@/components/OfficialBadge';
|
||||||
import { ProBadge } from '@/components/ProBadge';
|
import { ProBadge } from '@/components/ProBadge';
|
||||||
import { ReportDialog } from '@/components/ReportDialog';
|
import { ReportDialog } from '@/components/ReportDialog';
|
||||||
|
import { MuteMenuItem } from '@/components/MuteMenuItem';
|
||||||
import { useTranslation } from '@/contexts/language';
|
import { useTranslation } from '@/contexts/language';
|
||||||
import type { SkateEvent } from '@/lib/types';
|
import type { SkateEvent } from '@/lib/types';
|
||||||
import { EVENT_TYPE_LABELS } from '@/lib/types';
|
import { EVENT_TYPE_LABELS } from '@/lib/types';
|
||||||
|
|
@ -291,6 +292,7 @@ export function EventCard({ event, compact = false, goingCount: goingCountProp }
|
||||||
{t('placesUi.eventCard.report')}
|
{t('placesUi.eventCard.report')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
<MuteMenuItem pubkey={event.pubkey} />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,31 @@
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||||
import { useCrews } from '@/hooks/useCrews';
|
|
||||||
import { useTranslation } from '@/contexts/language';
|
import { useTranslation } from '@/contexts/language';
|
||||||
|
|
||||||
const DISMISS_KEY = 'skatespots:first-run-dismissed';
|
const DISMISS_KEY = 'skatespots:first-run-dismissed';
|
||||||
/** Set once, the first time the checklist has actually been shown. */
|
/** Set once, the first time the tip has actually been shown. */
|
||||||
const SEEN_KEY = 'skatespots:first-run-seen';
|
const SEEN_KEY = 'skatespots:first-run-seen';
|
||||||
/** The signup flow stamps this (ms). Only a *recent* signup counts as "first run". */
|
/** The signup flow stamps this (ms). Only a *recent* signup counts as "first run". */
|
||||||
const SIGNUP_KEY = 'signup_completed';
|
const SIGNUP_KEY = 'signup_completed';
|
||||||
const FRESH_WINDOW_MS = 2 * 24 * 60 * 60 * 1000; // 2 days
|
const FRESH_WINDOW_MS = 2 * 24 * 60 * 60 * 1000; // 2 days
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One-shot "get started" nudge shown on the map for freshly signed-up users.
|
* One-shot, frictionless first-run tip.
|
||||||
*
|
*
|
||||||
* The crew step is dynamic: when the community already has crews the CTA is
|
* It deliberately does NOT ask a new skater to create a spot, crew or wallet.
|
||||||
* "Join a crew" (→ /crews); when there are none it's "Start your crew"
|
* The map is already populated; the only action is an optional "Near Me" (one
|
||||||
* (→ /create-crew). Logged-out visitors see the welcome banner instead.
|
* location prompt) and a dismiss. Any content creation is left to the user's
|
||||||
*
|
* own intent, not onboarding.
|
||||||
* ⚠️ Gating (fixed 2026-10-12): this used to show to ANY logged-in user until
|
|
||||||
* they tapped ✕, so returning skaters saw it forever. It now shows only when
|
|
||||||
* (a) the account has a **recent `signup_completed` stamp** (set by the signup
|
|
||||||
* flow — an imported key or an older account never matches) and (b) it has not
|
|
||||||
* been shown before (`first-run-seen`). It stays visible for that one session
|
|
||||||
* and never returns.
|
|
||||||
*/
|
*/
|
||||||
export function FirstRunChecklist() {
|
export function FirstRunChecklist({ onFindNearby }: { onFindNearby?: () => void }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
const [dismissed, setDismissed] = useLocalStorage<boolean>(DISMISS_KEY, false);
|
const [dismissed, setDismissed] = useLocalStorage<boolean>(DISMISS_KEY, false);
|
||||||
const [seen, setSeen] = useLocalStorage<boolean>(SEEN_KEY, false);
|
const [seen, setSeen] = useLocalStorage<boolean>(SEEN_KEY, false);
|
||||||
const crews = useCrews(1);
|
|
||||||
|
|
||||||
// Was this a fresh in-app signup? Read once (a stable local fact).
|
|
||||||
const [fresh] = useState(() => {
|
const [fresh] = useState(() => {
|
||||||
try {
|
try {
|
||||||
const at = Number(localStorage.getItem(SIGNUP_KEY) ?? 0);
|
const at = Number(localStorage.getItem(SIGNUP_KEY) ?? 0);
|
||||||
|
|
@ -45,8 +35,7 @@ export function FirstRunChecklist() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Capture `seen` at mount so marking it doesn't hide the checklist in the same
|
// Capture at mount so marking it seen doesn't hide the tip in this session.
|
||||||
// session — only the next one.
|
|
||||||
const seenAtMount = useRef(seen);
|
const seenAtMount = useRef(seen);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && fresh && !dismissed && !seenAtMount.current) setSeen(true);
|
if (user && fresh && !dismissed && !seenAtMount.current) setSeen(true);
|
||||||
|
|
@ -54,10 +43,6 @@ export function FirstRunChecklist() {
|
||||||
|
|
||||||
if (!user || dismissed || !fresh || seenAtMount.current) return null;
|
if (!user || dismissed || !fresh || seenAtMount.current) return null;
|
||||||
|
|
||||||
const hasCrews = (crews.data?.length ?? 0) > 0;
|
|
||||||
const crewTo = hasCrews ? '/crews' : '/create-crew';
|
|
||||||
const crewLabel = hasCrews ? t('mapUi.firstRunJoinCrew') : t('mapUi.firstRunStartCrew');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pointer-events-auto relative z-10 mx-3 mt-2 rounded-2xl border border-white/12 bg-black/85 p-3 backdrop-blur md:mx-4">
|
<div className="pointer-events-auto relative z-10 mx-3 mt-2 rounded-2xl border border-white/12 bg-black/85 p-3 backdrop-blur md:mx-4">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
|
@ -71,27 +56,28 @@ export function FirstRunChecklist() {
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="mt-1 text-xs leading-relaxed text-gray-300">{t('mapUi.firstRunTip')}</p>
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
<Link to="/create-spot">
|
{onFindNearby && (
|
||||||
<Button size="sm" className="h-8 bg-[#ccff00] font-bold text-black hover:bg-[#ccff00]/90">
|
<Button
|
||||||
{t('mapUi.firstRunAddSpot')}
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
onFindNearby();
|
||||||
|
setDismissed(true);
|
||||||
|
}}
|
||||||
|
className="h-8 bg-[#ccff00] font-bold text-black hover:bg-[#ccff00]/90"
|
||||||
|
>
|
||||||
|
{t('mapPage.nearMe')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
)}
|
||||||
<Link to={crewTo}>
|
<Button
|
||||||
<Button size="sm" variant="outline" className="h-8 border-white/20 font-bold">
|
size="sm"
|
||||||
{crewLabel}
|
variant="outline"
|
||||||
</Button>
|
onClick={() => setDismissed(true)}
|
||||||
</Link>
|
className="h-8 border-white/20 font-bold"
|
||||||
<Link to="/wallet">
|
>
|
||||||
<Button size="sm" variant="outline" className="h-8 border-white/20 font-bold">
|
{t('mapUi.gotIt')}
|
||||||
{t('mapUi.firstRunWallet')}
|
</Button>
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link to="/community">
|
|
||||||
<Button size="sm" variant="outline" className="h-8 border-white/20 font-bold">
|
|
||||||
{t('communityUi.link')}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { genUserName } from '@/lib/genUserName';
|
||||||
import { getLastLocation } from '@/lib/lastLocation';
|
import { getLastLocation } from '@/lib/lastLocation';
|
||||||
import { itemDistanceKm } from '@/lib/sortOptions';
|
import { itemDistanceKm } from '@/lib/sortOptions';
|
||||||
import { TrustBadge } from '@/components/TrustBadge';
|
import { TrustBadge } from '@/components/TrustBadge';
|
||||||
|
import { MuteMenuItem } from '@/components/MuteMenuItem';
|
||||||
import { Clock, Search, Tag, Flag, MoreVertical, ShieldCheck, Calendar, Package, AlertTriangle, CheckCircle, RotateCcw, Trash2, Loader2 } from 'lucide-react';
|
import { Clock, Search, Tag, Flag, MoreVertical, ShieldCheck, Calendar, Package, AlertTriangle, CheckCircle, RotateCcw, Trash2, Loader2 } from 'lucide-react';
|
||||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||||
import type { MarketplaceItem } from '@/lib/types';
|
import type { MarketplaceItem } from '@/lib/types';
|
||||||
|
|
@ -288,6 +289,7 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
|
||||||
<Flag className="h-4 w-4 mr-2" />
|
<Flag className="h-4 w-4 mr-2" />
|
||||||
{t('marketUi.card.reportListing')}
|
{t('marketUi.card.reportListing')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<MuteMenuItem pubkey={item.pubkey} />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
37
src/components/MuteMenuItem.tsx
Normal file
37
src/components/MuteMenuItem.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { VolumeX } from 'lucide-react';
|
||||||
|
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
|
||||||
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
|
import { useMuteList } from '@/hooks/useMuteList';
|
||||||
|
import { useMuteUser } from '@/hooks/useMuteUser';
|
||||||
|
import { useTranslation } from '@/contexts/language';
|
||||||
|
|
||||||
|
/** Inline mute action for content cards. Renders nothing when logged out. */
|
||||||
|
export function MuteMenuItem({
|
||||||
|
pubkey,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
pubkey: string;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { user } = useCurrentUser();
|
||||||
|
const { data: muted } = useMuteList();
|
||||||
|
const mute = useMuteUser();
|
||||||
|
if (!user || user.pubkey === pubkey) return null;
|
||||||
|
|
||||||
|
const isMuted = !!muted?.has(pubkey);
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
disabled={isMuted || mute.isPending}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!isMuted) mute.mutate(pubkey);
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<VolumeX className="mr-2 h-4 w-4" />
|
||||||
|
{isMuted ? t('walletUi.trust.muted') : t('walletUi.trust.mute')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ import { TrustBadge } from '@/components/TrustBadge';
|
||||||
import { OfficialBadge } from '@/components/OfficialBadge';
|
import { OfficialBadge } from '@/components/OfficialBadge';
|
||||||
import { AddToCollectionDialog } from '@/components/AddToCollectionDialog';
|
import { AddToCollectionDialog } from '@/components/AddToCollectionDialog';
|
||||||
import { ReportDialog } from '@/components/ReportDialog';
|
import { ReportDialog } from '@/components/ReportDialog';
|
||||||
|
import { MuteMenuItem } from '@/components/MuteMenuItem';
|
||||||
import { SpotStatusBadge } from '@/components/SpotStatusBadge';
|
import { SpotStatusBadge } from '@/components/SpotStatusBadge';
|
||||||
import type { SkateSpot } from '@/lib/types';
|
import type { SkateSpot } from '@/lib/types';
|
||||||
import { ACCESS_TYPE_INFO, SHELTER_INFO, SKATE_SHOP_KIND } from '@/lib/types';
|
import { ACCESS_TYPE_INFO, SHELTER_INFO, SKATE_SHOP_KIND } from '@/lib/types';
|
||||||
|
|
@ -258,6 +259,7 @@ export function SpotCard({ spot, status, community }: SpotCardProps) {
|
||||||
{t('mapUi.report')}
|
{t('mapUi.report')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
<MuteMenuItem pubkey={spot.pubkey} className="text-gray-300" />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { ZapButton } from '@/components/ZapButton';
|
||||||
import { TrustBadge } from '@/components/TrustBadge';
|
import { TrustBadge } from '@/components/TrustBadge';
|
||||||
import { ProBadge } from '@/components/ProBadge';
|
import { ProBadge } from '@/components/ProBadge';
|
||||||
import { ReportDialog } from '@/components/ReportDialog';
|
import { ReportDialog } from '@/components/ReportDialog';
|
||||||
|
import { MuteMenuItem } from '@/components/MuteMenuItem';
|
||||||
|
|
||||||
import type { SkateTour, TourCategory } from '@/lib/types';
|
import type { SkateTour, TourCategory } from '@/lib/types';
|
||||||
import { TOUR_CATEGORIES, TOUR_SKILL_LEVELS } from '@/lib/types';
|
import { TOUR_CATEGORIES, TOUR_SKILL_LEVELS } from '@/lib/types';
|
||||||
|
|
@ -474,6 +475,7 @@ export function TourCard({ tour, compact = false }: TourCardProps) {
|
||||||
{t('placesUi.eventCard.report')}
|
{t('placesUi.eventCard.report')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
<MuteMenuItem pubkey={tour.pubkey} />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@ const batch1 = {
|
||||||
"newHere": "Ets nou aquí?",
|
"newHere": "Ets nou aquí?",
|
||||||
"welcomeDesc": "Descobreix spots de skate, esdeveniments i la comunitat.",
|
"welcomeDesc": "Descobreix spots de skate, esdeveniments i la comunitat.",
|
||||||
"firstRunTitle": "Comença",
|
"firstRunTitle": "Comença",
|
||||||
|
"firstRunTip": "Toca qualsevol marcador per explorar. No cal configurar res — els spots a prop són a un toc.",
|
||||||
"firstRunAddSpot": "Afegeix el teu primer spot",
|
"firstRunAddSpot": "Afegeix el teu primer spot",
|
||||||
"firstRunJoinCrew": "Uneix-te a una colla",
|
"firstRunJoinCrew": "Uneix-te a una colla",
|
||||||
"firstRunStartCrew": "Crea la teva colla",
|
"firstRunStartCrew": "Crea la teva colla",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
"mapUi": {
|
"mapUi": {
|
||||||
"firstRunTitle": "Začít",
|
"firstRunTitle": "Začít",
|
||||||
|
"firstRunTip": "Klepni na jakýkoli pin a objevuj. Nic není třeba nastavovat — okolní spoty máš na jeden dotek.",
|
||||||
"firstRunAddSpot": "Přidej svůj první spot",
|
"firstRunAddSpot": "Přidej svůj první spot",
|
||||||
"firstRunJoinCrew": "Přidej se k crew",
|
"firstRunJoinCrew": "Přidej se k crew",
|
||||||
"firstRunStartCrew": "Založ si crew",
|
"firstRunStartCrew": "Založ si crew",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Loslegen",
|
firstRunTitle: "Loslegen",
|
||||||
|
firstRunTip: "Tippe auf eine Markierung und leg los. Nichts einzurichten — Spots in der Nähe sind nur einen Tipp entfernt.",
|
||||||
firstRunAddSpot: "Füge deinen ersten Spot hinzu",
|
firstRunAddSpot: "Füge deinen ersten Spot hinzu",
|
||||||
firstRunJoinCrew: "Einer Crew beitreten",
|
firstRunJoinCrew: "Einer Crew beitreten",
|
||||||
firstRunStartCrew: "Starte deine Crew",
|
firstRunStartCrew: "Starte deine Crew",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Ξεκίνα",
|
firstRunTitle: "Ξεκίνα",
|
||||||
|
firstRunTip: "Πάτησε οποιαδήποτε πινέζα για εξερεύνηση. Δεν χρειάζεται καμία ρύθμιση — τα κοντινά spot είναι ένα άγγιγμα μακριά.",
|
||||||
firstRunAddSpot: "Πρόσθεσε το πρώτο spot",
|
firstRunAddSpot: "Πρόσθεσε το πρώτο spot",
|
||||||
firstRunJoinCrew: "Μπες σε ένα crew",
|
firstRunJoinCrew: "Μπες σε ένα crew",
|
||||||
firstRunStartCrew: "Ξεκίνα το δικό σου crew",
|
firstRunStartCrew: "Ξεκίνα το δικό σου crew",
|
||||||
|
|
|
||||||
|
|
@ -918,6 +918,7 @@ mapUi: {
|
||||||
newHere: 'New here?',
|
newHere: 'New here?',
|
||||||
welcomeDesc: 'Discover skate spots, events & the community.',
|
welcomeDesc: 'Discover skate spots, events & the community.',
|
||||||
firstRunTitle: 'Get started',
|
firstRunTitle: 'Get started',
|
||||||
|
firstRunTip: 'Tap any pin to explore. Nothing to set up — nearby spots are one tap away.',
|
||||||
firstRunAddSpot: 'Add your first spot',
|
firstRunAddSpot: 'Add your first spot',
|
||||||
firstRunJoinCrew: 'Join a crew',
|
firstRunJoinCrew: 'Join a crew',
|
||||||
firstRunStartCrew: 'Start your crew',
|
firstRunStartCrew: 'Start your crew',
|
||||||
|
|
|
||||||
|
|
@ -918,6 +918,7 @@ mapUi: {
|
||||||
newHere: '¿Nuevo por aquí?',
|
newHere: '¿Nuevo por aquí?',
|
||||||
welcomeDesc: 'Descubre spots, eventos y la comunidad.',
|
welcomeDesc: 'Descubre spots, eventos y la comunidad.',
|
||||||
firstRunTitle: 'Empieza aquí',
|
firstRunTitle: 'Empieza aquí',
|
||||||
|
firstRunTip: 'Toca cualquier chincheta para explorar. No hay que configurar nada: los spots cercanos están a un toque.',
|
||||||
firstRunAddSpot: 'Añade tu primer spot',
|
firstRunAddSpot: 'Añade tu primer spot',
|
||||||
firstRunJoinCrew: 'Únete a una peña',
|
firstRunJoinCrew: 'Únete a una peña',
|
||||||
firstRunStartCrew: 'Crea tu peña',
|
firstRunStartCrew: 'Crea tu peña',
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@ const batch1 = {
|
||||||
newHere: "Berria zara hemen?",
|
newHere: "Berria zara hemen?",
|
||||||
welcomeDesc: "Aurkitu skate tokiak, ekitaldiak eta komunitatea.",
|
welcomeDesc: "Aurkitu skate tokiak, ekitaldiak eta komunitatea.",
|
||||||
firstRunTitle: "Hasi",
|
firstRunTitle: "Hasi",
|
||||||
|
firstRunTip: "Sakatu edozein markagailu esploratzeko. Ez dago ezer konfiguratu beharrik — inguruko tokiak ukitze bakarrera daude.",
|
||||||
firstRunAddSpot: "Gehitu zure lehen tokia",
|
firstRunAddSpot: "Gehitu zure lehen tokia",
|
||||||
firstRunJoinCrew: "Batu talde batera",
|
firstRunJoinCrew: "Batu talde batera",
|
||||||
firstRunStartCrew: "Sortu zure taldea",
|
firstRunStartCrew: "Sortu zure taldea",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Pour commencer",
|
firstRunTitle: "Pour commencer",
|
||||||
|
firstRunTip: "Touchez n'importe quel repère pour explorer. Rien à configurer — les spots à proximité sont à un toucher.",
|
||||||
firstRunAddSpot: "Ajoutez votre premier spot",
|
firstRunAddSpot: "Ajoutez votre premier spot",
|
||||||
firstRunJoinCrew: "Rejoindre une Crew",
|
firstRunJoinCrew: "Rejoindre une Crew",
|
||||||
firstRunStartCrew: "Lancer votre Crew",
|
firstRunStartCrew: "Lancer votre Crew",
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@ const batch1 = {
|
||||||
newHere: "Es novo por aquí?",
|
newHere: "Es novo por aquí?",
|
||||||
welcomeDesc: "Descubre spots de skate, eventos e a comunidade.",
|
welcomeDesc: "Descubre spots de skate, eventos e a comunidade.",
|
||||||
firstRunTitle: "Comeza",
|
firstRunTitle: "Comeza",
|
||||||
|
firstRunTip: "Toca calquera marcador para explorar. Non hai nada que configurar — os spots preto están a un toque.",
|
||||||
firstRunAddSpot: "Engade o teu primeiro spot",
|
firstRunAddSpot: "Engade o teu primeiro spot",
|
||||||
firstRunJoinCrew: "Únete a unha crew",
|
firstRunJoinCrew: "Únete a unha crew",
|
||||||
firstRunStartCrew: "Crea a túa crew",
|
firstRunStartCrew: "Crea a túa crew",
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@ const batch1 = {
|
||||||
"newHere": "नए हैं?",
|
"newHere": "नए हैं?",
|
||||||
"welcomeDesc": "स्केट स्पॉट, इवेंट और समुदाय खोजें।",
|
"welcomeDesc": "स्केट स्पॉट, इवेंट और समुदाय खोजें।",
|
||||||
"firstRunTitle": "शुरू करें",
|
"firstRunTitle": "शुरू करें",
|
||||||
|
"firstRunTip": "किसी भी पिन पर टैप करके एक्सप्लोर करें। कुछ सेट करने की ज़रूरत नहीं — आस-पास के स्पॉट बस एक टैप दूर हैं।",
|
||||||
"firstRunAddSpot": "अपना पहला स्पॉट जोड़ें",
|
"firstRunAddSpot": "अपना पहला स्पॉट जोड़ें",
|
||||||
"firstRunJoinCrew": "क्रू से जुड़ें",
|
"firstRunJoinCrew": "क्रू से जुड़ें",
|
||||||
"firstRunStartCrew": "अपना क्रू शुरू करें",
|
"firstRunStartCrew": "अपना क्रू शुरू करें",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const driftStrings = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: 'Mulai',
|
firstRunTitle: 'Mulai',
|
||||||
|
firstRunTip: 'Ketuk pin mana saja untuk menjelajah. Tanpa pengaturan — spot di sekitar tinggal sekali ketuk.',
|
||||||
firstRunAddSpot: 'Tambahkan spot pertamamu',
|
firstRunAddSpot: 'Tambahkan spot pertamamu',
|
||||||
firstRunJoinCrew: 'Gabung crew',
|
firstRunJoinCrew: 'Gabung crew',
|
||||||
firstRunStartCrew: 'Mulai crew-mu',
|
firstRunStartCrew: 'Mulai crew-mu',
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Inizia",
|
firstRunTitle: "Inizia",
|
||||||
|
firstRunTip: "Tocca qualsiasi segnaposto per esplorare. Niente da configurare — gli spot vicini sono a un tocco.",
|
||||||
firstRunAddSpot: "Aggiungi il tuo primo spot",
|
firstRunAddSpot: "Aggiungi il tuo primo spot",
|
||||||
firstRunJoinCrew: "Unisciti a una crew",
|
firstRunJoinCrew: "Unisciti a una crew",
|
||||||
firstRunStartCrew: "Avvia la tua crew",
|
firstRunStartCrew: "Avvia la tua crew",
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const drift: Dict = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: 'はじめかた',
|
firstRunTitle: 'はじめかた',
|
||||||
|
firstRunTip: 'ピンをタップして探索。設定は不要 — 近くのスポットはすぐそこ。',
|
||||||
firstRunAddSpot: '最初のスポットを追加',
|
firstRunAddSpot: '最初のスポットを追加',
|
||||||
firstRunJoinCrew: 'クルーに参加',
|
firstRunJoinCrew: 'クルーに参加',
|
||||||
firstRunStartCrew: 'クルーを始める',
|
firstRunStartCrew: 'クルーを始める',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const drift: Dict = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: '시작하기',
|
firstRunTitle: '시작하기',
|
||||||
|
firstRunTip: '핀을 탭해 둘러보세요. 설정은 필요 없어요 — 근처 스팟은 한 번의 탭이면 됩니다.',
|
||||||
firstRunAddSpot: '첫 스팟 추가',
|
firstRunAddSpot: '첫 스팟 추가',
|
||||||
firstRunJoinCrew: '크루 참여',
|
firstRunJoinCrew: '크루 참여',
|
||||||
firstRunStartCrew: '크루 시작',
|
firstRunStartCrew: '크루 시작',
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
"mapUi": {
|
"mapUi": {
|
||||||
"firstRunTitle": "Aan de slag",
|
"firstRunTitle": "Aan de slag",
|
||||||
|
"firstRunTip": "Tik op een pin om te verkennen. Niets in te stellen — spots in de buurt zijn één tik weg.",
|
||||||
"firstRunAddSpot": "Voeg je eerste spot toe",
|
"firstRunAddSpot": "Voeg je eerste spot toe",
|
||||||
"firstRunJoinCrew": "Sluit je aan bij een crew",
|
"firstRunJoinCrew": "Sluit je aan bij een crew",
|
||||||
"firstRunStartCrew": "Start je crew",
|
"firstRunStartCrew": "Start je crew",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
"mapUi": {
|
"mapUi": {
|
||||||
"firstRunTitle": "Zacznij",
|
"firstRunTitle": "Zacznij",
|
||||||
|
"firstRunTip": "Dotknij dowolnej pinezki, aby eksplorować. Niczego nie trzeba konfigurować — pobliskie spoty są o jedno dotknięcie.",
|
||||||
"firstRunAddSpot": "Dodaj swój pierwszy spot",
|
"firstRunAddSpot": "Dodaj swój pierwszy spot",
|
||||||
"firstRunJoinCrew": "Dołącz do crew",
|
"firstRunJoinCrew": "Dołącz do crew",
|
||||||
"firstRunStartCrew": "Załóż swoje crew",
|
"firstRunStartCrew": "Załóż swoje crew",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Comece agora",
|
firstRunTitle: "Comece agora",
|
||||||
|
firstRunTip: "Toque em qualquer pino para explorar. Nada para configurar — os spots por perto estão a um toque.",
|
||||||
firstRunAddSpot: "Adicione seu primeiro spot",
|
firstRunAddSpot: "Adicione seu primeiro spot",
|
||||||
firstRunJoinCrew: "Entrar em uma crew",
|
firstRunJoinCrew: "Entrar em uma crew",
|
||||||
firstRunStartCrew: "Comece sua crew",
|
firstRunStartCrew: "Comece sua crew",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
"mapUi": {
|
"mapUi": {
|
||||||
"firstRunTitle": "Începe",
|
"firstRunTitle": "Începe",
|
||||||
|
"firstRunTip": "Atinge orice pin pentru a explora. Nimic de configurat — spoturile din apropiere sunt la un tap.",
|
||||||
"firstRunAddSpot": "Adaugă primul tău spot",
|
"firstRunAddSpot": "Adaugă primul tău spot",
|
||||||
"firstRunJoinCrew": "Alătură-te unui crew",
|
"firstRunJoinCrew": "Alătură-te unui crew",
|
||||||
"firstRunStartCrew": "Începe-ți crew-ul",
|
"firstRunStartCrew": "Începe-ți crew-ul",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Начните",
|
firstRunTitle: "Начните",
|
||||||
|
firstRunTip: "Нажмите на любую метку, чтобы осмотреться. Ничего настраивать не нужно — ближайшие Spot в одном касании.",
|
||||||
firstRunAddSpot: "Добавьте первый Spot",
|
firstRunAddSpot: "Добавьте первый Spot",
|
||||||
firstRunJoinCrew: "Присоединиться к Crew",
|
firstRunJoinCrew: "Присоединиться к Crew",
|
||||||
firstRunStartCrew: "Создать свой Crew",
|
firstRunStartCrew: "Создать свой Crew",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Kom igång",
|
firstRunTitle: "Kom igång",
|
||||||
|
firstRunTip: "Tryck på valfri nål för att utforska. Inget att ställa in — spots i närheten är ett tryck bort.",
|
||||||
firstRunAddSpot: "Lägg till din första spot",
|
firstRunAddSpot: "Lägg till din första spot",
|
||||||
firstRunJoinCrew: "Gå med i ett crew",
|
firstRunJoinCrew: "Gå med i ett crew",
|
||||||
firstRunStartCrew: "Starta ditt crew",
|
firstRunStartCrew: "Starta ditt crew",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const driftStrings = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: 'เริ่มต้น',
|
firstRunTitle: 'เริ่มต้น',
|
||||||
|
firstRunTip: 'แตะหมุดใดก็ได้เพื่อสำรวจ ไม่ต้องตั้งค่าอะไร — spot ใกล้ ๆ อยู่แค่ปลายนิ้ว',
|
||||||
firstRunAddSpot: 'เพิ่ม spot แรกของคุณ',
|
firstRunAddSpot: 'เพิ่ม spot แรกของคุณ',
|
||||||
firstRunJoinCrew: 'เข้าร่วม crew',
|
firstRunJoinCrew: 'เข้าร่วม crew',
|
||||||
firstRunStartCrew: 'เริ่ม crew ของคุณ',
|
firstRunStartCrew: 'เริ่ม crew ของคุณ',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const driftStrings = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: 'Başla',
|
firstRunTitle: 'Başla',
|
||||||
|
firstRunTip: 'Keşfetmek için herhangi bir iğneye dokun. Kurulum yok — yakındaki spotlar bir dokunuş uzağında.',
|
||||||
firstRunAddSpot: 'İlk spotunu ekle',
|
firstRunAddSpot: 'İlk spotunu ekle',
|
||||||
firstRunJoinCrew: "Bir crew'e katıl",
|
firstRunJoinCrew: "Bir crew'e katıl",
|
||||||
firstRunStartCrew: "Crew'ini kur",
|
firstRunStartCrew: "Crew'ini kur",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const drift = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: "Почніть",
|
firstRunTitle: "Почніть",
|
||||||
|
firstRunTip: "Торкніться будь-якої мітки, щоб дослідити. Нічого налаштовувати не потрібно — найближчі Spot за один дотик.",
|
||||||
firstRunAddSpot: "Додайте перший Spot",
|
firstRunAddSpot: "Додайте перший Spot",
|
||||||
firstRunJoinCrew: "Приєднатися до команди",
|
firstRunJoinCrew: "Приєднатися до команди",
|
||||||
firstRunStartCrew: "Створіть свою команду",
|
firstRunStartCrew: "Створіть свою команду",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const driftStrings = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: 'Bắt đầu',
|
firstRunTitle: 'Bắt đầu',
|
||||||
|
firstRunTip: 'Chạm vào bất kỳ ghim nào để khám phá. Không cần thiết lập — spot gần đây chỉ cách một lần chạm.',
|
||||||
firstRunAddSpot: 'Thêm spot đầu tiên của bạn',
|
firstRunAddSpot: 'Thêm spot đầu tiên của bạn',
|
||||||
firstRunJoinCrew: 'Tham gia một crew',
|
firstRunJoinCrew: 'Tham gia một crew',
|
||||||
firstRunStartCrew: 'Bắt đầu crew của bạn',
|
firstRunStartCrew: 'Bắt đầu crew của bạn',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const drift: Dict = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: '开始使用',
|
firstRunTitle: '开始使用',
|
||||||
|
firstRunTip: '点击任意标记即可探索。无需设置——附近滑板点一触即达。',
|
||||||
firstRunAddSpot: '添加你的第一个滑板点',
|
firstRunAddSpot: '添加你的第一个滑板点',
|
||||||
firstRunJoinCrew: '加入一个 Crew',
|
firstRunJoinCrew: '加入一个 Crew',
|
||||||
firstRunStartCrew: '创建你的 Crew',
|
firstRunStartCrew: '创建你的 Crew',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const drift: Dict = {
|
||||||
},
|
},
|
||||||
mapUi: {
|
mapUi: {
|
||||||
firstRunTitle: '開始使用',
|
firstRunTitle: '開始使用',
|
||||||
|
firstRunTip: '點按任一標記即可探索。無需設定——附近滑板點一觸即達。',
|
||||||
firstRunAddSpot: '新增你的第一個滑板點',
|
firstRunAddSpot: '新增你的第一個滑板點',
|
||||||
firstRunJoinCrew: '加入一個 Crew',
|
firstRunJoinCrew: '加入一個 Crew',
|
||||||
firstRunStartCrew: '建立你的 Crew',
|
firstRunStartCrew: '建立你的 Crew',
|
||||||
|
|
|
||||||
|
|
@ -1150,7 +1150,7 @@ export default function MapPage() {
|
||||||
the floating toolbar would paint over it. */}
|
the floating toolbar would paint over it. */}
|
||||||
{/* First-run "get started" nudge for freshly signed-up users (hidden for
|
{/* First-run "get started" nudge for freshly signed-up users (hidden for
|
||||||
logged-out visitors, who get the welcome banner). */}
|
logged-out visitors, who get the welcome banner). */}
|
||||||
<FirstRunChecklist />
|
<FirstRunChecklist onFindNearby={() => void handleNearMe()} />
|
||||||
{showWelcomeBanner && (
|
{showWelcomeBanner && (
|
||||||
<div className="relative z-10 bg-gradient-to-r from-[#ff0040]/90 via-black/95 to-[#ccff00]/20 border-b border-[#ff0040]/40 backdrop-blur">
|
<div className="relative z-10 bg-gradient-to-r from-[#ff0040]/90 via-black/95 to-[#ccff00]/20 border-b border-[#ff0040]/40 backdrop-blur">
|
||||||
<div className="container px-4 py-2.5 flex items-center gap-3">
|
<div className="container px-4 py-2.5 flex items-center gap-3">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue