chore(lint): clear all no-unused-vars warnings (position-precise)
Removed/renamed the remaining 39 unused locals/dead code, file-by-file: - unused destructured bindings/params (remove the binding, keep the call) - unused single-line pure consts and dead functions/interfaces removed - deleted the dead _FeedItem component and its now-unused imports - amber _readChunks self-call fixed; SpotMap popup call site updated Warnings: 182 -> 7 (only react-refresh/only-export-components remain). no-explicit-any=0, exhaustive-deps=0, no-unused-vars=0. Gate: 101 tests, 0 errors, boot smoke green. AGENTS.md baseline + guidance updated.
This commit is contained in:
parent
721a5d4b67
commit
6def78fffe
27 changed files with 37 additions and 169 deletions
15
AGENTS.md
15
AGENTS.md
|
|
@ -79,15 +79,16 @@ session with **no other memory of this project**. So:
|
|||
- **Lazy routes**: every route uses `lazyWithRetry` (`src/lib/lazyWithRetry.ts`) —
|
||||
retries, then one full reload per session, so a chunk-load failure self-heals
|
||||
instead of showing the error boundary.
|
||||
- **Baselines** (verified 2026-09-26): `npm test` → 0 errors, **46 warnings**
|
||||
(39 `no-unused-vars`, 7 `react-refresh`), **101 tests**, build ok.
|
||||
- **Baselines** (verified 2026-09-26): `npm test` → 0 errors, **7 warnings**
|
||||
(all `react-refresh/only-export-components`), **101 tests**, build ok.
|
||||
`package.json` version **1.4.5**.
|
||||
- ✅ `no-explicit-any` = 0; ✅ `react-hooks/exhaustive-deps` = 0 (shared getters
|
||||
are `useCallback`-memoised; intentional mount-only effects carry a justified
|
||||
`eslint-disable-next-line` with a comment).
|
||||
- ✅ `no-explicit-any` = 0 · ✅ `react-hooks/exhaustive-deps` = 0 ·
|
||||
✅ `no-unused-vars` = 0. Remaining: `react-refresh` (dev-only DX — files
|
||||
exporting non-components next to components; fix by moving the export out).
|
||||
- ⚠️ Do **not** mass-fix `no-unused-vars` by name or blanket rename — shadowed
|
||||
names (`isUnfollowing`) and typed shorthand destructuring make it unsafe.
|
||||
Fix file-by-file, position-precise, opportunistically.
|
||||
names and typed shorthand destructuring make it unsafe. Fix position-precise.
|
||||
Renaming a component to `_Name` also trips `rules-of-hooks` — delete it or
|
||||
suppress instead.
|
||||
- **Untracked local junk — do NOT commit**: `assets/`, `resources/`,
|
||||
`cvm-server/`, `bun.lock`.
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export function FollowListManager() {
|
|||
const { user } = useCurrentUser();
|
||||
const { data: following = [], isLoading: followingLoading } = useFollowing();
|
||||
const { data: followers = [], isLoading: followersLoading } = useFollowers();
|
||||
const { unfollow, followByIdentifier, isFollowing, isUnfollowing } = useFollowActions();
|
||||
const { unfollow, followByIdentifier, isFollowing } = useFollowActions();
|
||||
|
||||
const [followInput, setFollowInput] = useState('');
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { useToast } from '@/hooks/useToast';
|
|||
import { isAdminPubkey } from '@/lib/admins';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { getSkateboardRating, getTrustLevel, SELLER_TRUST_BADGES } from '@/lib/skateboardRating';
|
||||
import { GEAR_CONDITIONS, GEAR_CATEGORIES, PRICE_TYPES, formatPrice, type MarketplaceItem } from '@/lib/types';
|
||||
import { GEAR_CONDITIONS, GEAR_CATEGORIES, formatPrice, type MarketplaceItem } from '@/lib/types';
|
||||
import { MessageCircle, MapPin, Search, CheckCircle, ShieldCheck, AlertTriangle, MoreVertical, Flag, Trash2, RotateCcw, Loader2 } from 'lucide-react';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
||||
|
|
@ -28,7 +28,7 @@ interface GearSwapItemCardProps {
|
|||
isFollowing?: boolean;
|
||||
}
|
||||
|
||||
export function GearSwapItemCard({ item, trustScore, salesCount, isFollowing }: GearSwapItemCardProps) {
|
||||
export function GearSwapItemCard({ item, trustScore, salesCount }: GearSwapItemCardProps) {
|
||||
const { user } = useCurrentUser();
|
||||
const navigate = useNavigate();
|
||||
const author = useAuthor(item.pubkey);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ interface LanguageToggleProps {
|
|||
* Language toggle dropdown for switching between English and Spanish
|
||||
*/
|
||||
export function LanguageToggle({ compact = false, className, variant = 'ghost' }: LanguageToggleProps) {
|
||||
const { language, setLanguage, t } = useTranslation();
|
||||
const { language, setLanguage } = useTranslation();
|
||||
|
||||
const currentLanguage = languages.find(l => l.code === language) || languages[0];
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ const NostrProvider: React.FC<NostrProviderProps> = (props) => {
|
|||
|
||||
return routes;
|
||||
},
|
||||
eventRouter(event: NostrEvent) {
|
||||
eventRouter(_event: NostrEvent) {
|
||||
// `resolveRelays` applies the blocklist and adds local relays.
|
||||
const resolved = resolveRelays(relayMetadata.current);
|
||||
const writeRelays = resolved.write.length > 0
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export function QualityMeter({
|
|||
return items;
|
||||
}, [type, hasLocation, hasPhotos, photoCount, hasDescription, descriptionLength, hasPhone, hasWebsite, hasHours, hasSpotType]);
|
||||
|
||||
const { score, maxScore, completedItems, totalItems } = useMemo(() => {
|
||||
const { score, maxScore } = useMemo(() => {
|
||||
const completed = checks.filter(c => c.completed);
|
||||
const total = checks.reduce((sum, c) => sum + c.points, 0);
|
||||
const current = completed.reduce((sum, c) => sum + c.points, 0);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ export function SortDropdown({
|
|||
onChange,
|
||||
className,
|
||||
showProximity = false,
|
||||
contentType = 'generic',
|
||||
}: SortDropdownProps) {
|
||||
const selectedOption = SORT_OPTIONS[value];
|
||||
const Icon = selectedOption.icon;
|
||||
|
|
|
|||
|
|
@ -661,8 +661,7 @@ function createPopupContent(
|
|||
function createEnhancedPopupContent(
|
||||
spot: SkateSpot,
|
||||
marketplaceItems: MarketplaceItem[],
|
||||
onViewDetails: () => void,
|
||||
weatherData?: SpotWeatherData
|
||||
onViewDetails: () => void
|
||||
): HTMLElement {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'p-2 min-w-[250px] max-w-[350px]';
|
||||
|
|
@ -1122,7 +1121,7 @@ export function SpotMap({
|
|||
|
||||
// Use enhanced popup if there are marketplace items, otherwise use standard
|
||||
const popupContent = hasGear
|
||||
? createEnhancedPopupContent(primarySpot, group.marketplaceItems, () => handleViewDetails(primarySpot), spotWeather)
|
||||
? createEnhancedPopupContent(primarySpot, group.marketplaceItems, () => handleViewDetails(primarySpot))
|
||||
: createPopupContent(primarySpot, () => handleViewDetails(primarySpot), spotWeather);
|
||||
|
||||
marker.bindPopup(popupContent, {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import {
|
|||
} from '@/components/ui/alert-dialog';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { SKATE_SPOT_KIND, type SkateSpot } from '@/lib/types';
|
||||
|
|
@ -47,7 +46,6 @@ export function SpotOwnership({ spot }: SpotOwnershipProps) {
|
|||
const { mutateAsync: publishEvent, isPending: isPublishing } = useNostrPublish();
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
const ownerProfile = useAuthor(spot.pubkey);
|
||||
|
||||
const [transferOpen, setTransferOpen] = useState(false);
|
||||
const [claimOpen, setClaimOpen] = useState(false);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ interface TourCardProps {
|
|||
compact?: boolean;
|
||||
}
|
||||
|
||||
function getCategoryIcon(category: TourCategory) {
|
||||
function _getCategoryIcon(category: TourCategory) {
|
||||
switch (category) {
|
||||
case 'local':
|
||||
return <Route className="h-4 w-4" />;
|
||||
|
|
|
|||
|
|
@ -86,7 +86,6 @@ const WalletContent = forwardRef<HTMLDivElement, {
|
|||
setAddDialogOpen: (open: boolean) => void;
|
||||
}>(({
|
||||
webln,
|
||||
hasNWC,
|
||||
connections,
|
||||
connectionInfo,
|
||||
activeConnection,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const MnemonicSignupDialog: React.FC<MnemonicSignupDialogProps> = ({
|
|||
const [showMnemonic, setShowMnemonic] = useState(true);
|
||||
const [verifyIndices, setVerifyIndices] = useState<number[]>([]);
|
||||
const [verifyInputs, setVerifyInputs] = useState<Record<number, string>>({});
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
const [, setIsVerified] = useState(false);
|
||||
const login = useLoginActions();
|
||||
|
||||
// Generate new mnemonic
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ function CashuWalletCardNative() {
|
|||
balances,
|
||||
mints,
|
||||
totalBalance,
|
||||
isInitialized,
|
||||
isLoading: walletLoading,
|
||||
error: walletError,
|
||||
mnemonic,
|
||||
|
|
@ -192,7 +191,7 @@ function CashuWalletCardNative() {
|
|||
sentTokenQRCode,
|
||||
} = useTokenOperations({ coco, activeMintUrl, generateQRCode, getEncodedToken });
|
||||
|
||||
const { history, isLoading: historyLoading } = useHistoryManager({
|
||||
const { history } = useHistoryManager({
|
||||
coco,
|
||||
repositories,
|
||||
mints,
|
||||
|
|
|
|||
|
|
@ -23,24 +23,6 @@ export interface BTCMapMerchant {
|
|||
};
|
||||
}
|
||||
|
||||
interface BTCMapResponse {
|
||||
type: string;
|
||||
features: Array<{
|
||||
type: string;
|
||||
geometry: {
|
||||
type: string;
|
||||
coordinates: [number, number]; // [lon, lat]
|
||||
};
|
||||
properties: {
|
||||
id: string;
|
||||
name?: string;
|
||||
'payment:lightning'?: string;
|
||||
'payment:onchain'?: string;
|
||||
[key: string]: string | undefined;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Bitcoin-accepting merchants from BTCMap API
|
||||
* Uses bounding box to get merchants in the visible map area
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export function useFollowActions() {
|
|||
|
||||
return targetPubkey;
|
||||
},
|
||||
onSuccess: (targetPubkey) => {
|
||||
onSuccess: (_targetPubkey) => {
|
||||
// Invalidate queries to refetch
|
||||
queryClient.invalidateQueries({ queryKey: ['following', user?.pubkey] });
|
||||
queryClient.invalidateQueries({ queryKey: ['trust-score'] });
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ interface LastListingRecord {
|
|||
export function useListingRestrictions(linkedSpotPubkey?: string): ListingRestrictions {
|
||||
const { nostr } = useNostr();
|
||||
const { user, metadata } = useCurrentUser();
|
||||
const [lastListingRecord, setLastListingRecord] = useLocalStorage<LastListingRecord | null>(
|
||||
const [lastListingRecord] = useLocalStorage<LastListingRecord | null>(
|
||||
'last-listing-record',
|
||||
null
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ interface UseHistoryManagerProps {
|
|||
export function useHistoryManager({
|
||||
coco,
|
||||
repositories,
|
||||
mints,
|
||||
}: UseHistoryManagerProps) {
|
||||
const [history, setHistory] = useState<HistoryEntry[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
|
|
|||
|
|
@ -649,7 +649,7 @@ export function decodeAmberResult(result: string): string {
|
|||
const reader = ds.readable.getReader();
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
const readChunks = async (): Promise<string> => {
|
||||
const _readChunks = async (): Promise<string> => {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
const combined = new Uint8Array(chunks.reduce((acc, c) => acc + c.length, 0));
|
||||
|
|
@ -661,7 +661,7 @@ export function decodeAmberResult(result: string): string {
|
|||
return new TextDecoder().decode(combined);
|
||||
}
|
||||
chunks.push(value);
|
||||
return readChunks();
|
||||
return _readChunks();
|
||||
};
|
||||
|
||||
// For sync usage, we'll just return the raw result
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { useAuthor } from '@/hooks/useAuthor';
|
|||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { DeleteEntityDialog } from '@/components/DeleteEntityDialog';
|
||||
import { isAdminPubkey } from '@/lib/admins';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { COACH_SKILL_LABELS, COACH_SKILL_DESCRIPTIONS, FILMING_SERVICE_LABELS, FILMING_SERVICE_DESCRIPTIONS, FILMING_SKILL_LABELS, FILMING_SKILL_DESCRIPTIONS, EDITOR_SERVICE_LABELS, EDITOR_SERVICE_DESCRIPTIONS, EDITOR_SKILL_LABELS, EDITOR_SKILL_DESCRIPTIONS, PHOTO_SERVICE_LABELS, PHOTO_SERVICE_DESCRIPTIONS, PHOTO_CAMERA_LABELS, PHOTO_LENS_LABELS, PHOTO_LIGHTING_LABELS, PHOTO_SUPPORT_LABELS, PHOTO_STYLE_LABELS, BUILDER_SERVICE_LABELS, BUILDER_SERVICE_DESCRIPTIONS, BUILDER_SKILL_LABELS, BUILDER_SCALE_LABELS, ARTIST_SERVICE_LABELS, ARTIST_SERVICE_DESCRIPTIONS, ARTIST_MEDIUM_LABELS, ARTIST_TOOL_LABELS, GRAFFITI_STYLE_LABELS, GRAFFITI_STYLE_DESCRIPTIONS, GRAFFITI_MEDIUM_LABELS, GRAFFITI_CONTEXT_LABELS, TATTOO_STYLE_LABELS, TATTOO_STYLE_DESCRIPTIONS, TATTOO_METHOD_LABELS, TRAINER_FOCUS_LABELS, TRAINER_FOCUS_DESCRIPTIONS, TRAINER_INJURY_LABELS, TRAINER_CREDENTIAL_LABELS, MUSICIAN_GENRE_LABELS, MUSICIAN_INSTRUMENT_LABELS, JUDGE_FORMAT_LABELS, JUDGE_SPECIALTY_LABELS, JUDGE_LEVEL_LABELS, SKATE_COACH_KIND, SKATE_FILMER_KIND, SKATE_EDITOR_KIND, SKATE_PHOTOGRAPHER_KIND, SKATE_BUILDER_KIND, SKATE_ARTIST_KIND, SKATE_GRAFFITI_KIND, SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND, SKATE_JUDGE_KIND } from '@/lib/types';
|
||||
import { sanitizeUrl, sanitizeTel, sanitizeEmail, urlDomain } from '@/lib/sanitizeUrl';
|
||||
import { OfficialBadge } from '@/components/OfficialBadge';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Layout } from '@/components/Layout';
|
||||
import { PageError } from '@/components/ErrorState';
|
||||
import { SpotCard, SpotCardSkeleton } from '@/components/SpotCard';
|
||||
|
|
@ -51,7 +51,6 @@ export default function CollectionDetailPage() {
|
|||
const { deleteCollection, isPending: isDeleting } = useDeleteSpotCollection();
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [removingSpotId, setRemovingSpotId] = useState<string | null>(null);
|
||||
|
|
|
|||
|
|
@ -31,12 +31,6 @@ function labCraftTotal(s: {
|
|||
);
|
||||
}
|
||||
|
||||
function formatSats(sats: number): string {
|
||||
if (sats >= 1_000_000) return `${(sats / 1_000_000).toFixed(1)}M`;
|
||||
if (sats >= 1_000) return `${Math.round(sats / 1_000)}k`;
|
||||
return sats.toLocaleString();
|
||||
}
|
||||
|
||||
const CHART_TOOLTIP_STYLE = {
|
||||
backgroundColor: '#0d0d0d',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export default function CreateListingPage() {
|
|||
// Anti-spam restrictions
|
||||
const restrictions = useListingRestrictions(linkedSpot?.pubkey);
|
||||
const [feePaid, setFeePaid] = useState(false);
|
||||
const [lastListingRecord, setLastListingRecord] = useLocalStorage<{ timestamp: number; spotOwnerPubkey?: string } | null>(
|
||||
const [, setLastListingRecord] = useLocalStorage<{ timestamp: number; spotOwnerPubkey?: string } | null>(
|
||||
'last-listing-record',
|
||||
null
|
||||
);
|
||||
|
|
|
|||
|
|
@ -33,14 +33,12 @@ import { ConfirmMembershipCard } from '@/components/ConfirmMembershipCard';
|
|||
import { MemberVisibilityControl } from '@/components/MemberVisibilityControl';
|
||||
import { LinkedLabCard } from '@/components/CrossLinkCards';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
||||
import { useCollectiveMembers } from '@/hooks/useCollectiveMembers';
|
||||
import { useCrewRole } from '@/hooks/useCrewRole';
|
||||
import { useCrewModeration, useHideCrewPost } from '@/hooks/useCrewModeration';
|
||||
import { useHideCrewPost } from '@/hooks/useCrewModeration';
|
||||
import { CrewInviteDialog } from '@/components/CrewInviteDialog';
|
||||
import { DeleteEntityDialog } from '@/components/DeleteEntityDialog';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { NoteContent } from '@/components/NoteContent';
|
||||
import { useCrew } from '@/hooks/useCrews';
|
||||
import { useCrewFeed } from '@/hooks/useCrewFeed';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
|
|
@ -49,83 +47,9 @@ import { genUserName } from '@/lib/genUserName';
|
|||
import { SKATE_CREW_KIND, SHOW_CREW_TREASURY, CREW_LOOKING_FOR_LABELS, type CrewRole } from '@/lib/types';
|
||||
import { sanitizeUrl, urlDomain } from '@/lib/sanitizeUrl';
|
||||
import NotFound from './NotFound';
|
||||
import { Users, MapPin, ArrowLeft, Calendar, Globe, Instagram, Edit, MessageSquare, Zap, ShieldCheck, EyeOff, Music } from 'lucide-react';
|
||||
import { Users, MapPin, ArrowLeft, Calendar, Globe, Instagram, Edit, MessageSquare, Zap, ShieldCheck, Music } from 'lucide-react';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
function FeedItem({
|
||||
event,
|
||||
canModerate,
|
||||
onHide,
|
||||
isHiding,
|
||||
}: {
|
||||
event: NostrEvent;
|
||||
canModerate?: boolean;
|
||||
onHide?: (event: NostrEvent) => void;
|
||||
isHiding?: boolean;
|
||||
}) {
|
||||
const author = useAuthor(event.pubkey);
|
||||
const metadata = author.data?.metadata;
|
||||
const name = metadata?.name ?? genUserName(event.pubkey);
|
||||
const npub = nip19.npubEncode(event.pubkey);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-white/5 bg-black/40 p-3">
|
||||
<div className="mb-1.5 flex items-center gap-2">
|
||||
<Link to={`/${npub}`}>
|
||||
<Avatar className="h-7 w-7">
|
||||
<AvatarImage src={metadata?.picture} alt={name} />
|
||||
<AvatarFallback className="text-[10px]">{name.slice(0, 2).toUpperCase()}</AvatarFallback>
|
||||
</Avatar>
|
||||
</Link>
|
||||
<Link to={`/${npub}`} className="text-sm font-bold text-gray-200 hover:text-white truncate">
|
||||
{name}
|
||||
</Link>
|
||||
<span className="ml-auto text-[11px] text-gray-500">
|
||||
{new Date(event.created_at * 1000).toLocaleDateString()}
|
||||
</span>
|
||||
{canModerate && onHide && (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 shrink-0 text-gray-500 hover:text-red-400"
|
||||
disabled={isHiding}
|
||||
aria-label="Hide this post"
|
||||
title="Hide from crew feed (admin)"
|
||||
>
|
||||
<EyeOff className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Hide this post?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
As a crew moderator you can hide this post from the crew feed for everyone.
|
||||
The original note stays on Nostr — this only removes it from this crew's feed.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => onHide(event)}
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
>
|
||||
Hide post
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)}
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap break-words text-sm text-gray-300">
|
||||
<NoteContent event={event} className="text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CrewDetailPage() {
|
||||
const { pubkey, identifier } = useParams<{ pubkey: string; identifier: string }>();
|
||||
const { data: crew, isLoading, error, refetch } = useCrew(identifier || '', pubkey);
|
||||
|
|
@ -143,27 +67,21 @@ export default function CrewDetailPage() {
|
|||
|
||||
// Feed = founder + listed members
|
||||
const feedAuthors = crew ? [crew.pubkey, ...crew.members] : [];
|
||||
const { data: feed, isLoading: feedLoading } = useCrewFeed(feedAuthors);
|
||||
const { data: feed } = useCrewFeed(feedAuthors);
|
||||
|
||||
// Crew-hosted events (jams, comps, meetups).
|
||||
const { data: crewEvents } = useCrewEvents(crew?.pubkey, crew?.identifier);
|
||||
|
||||
// Moderation — founder + admins can hide feed posts (NIP-32 labels).
|
||||
const crewCoord = crew ? `${SKATE_CREW_KIND}:${crew.pubkey}:${crew.identifier}` : '';
|
||||
const moderators = crew ? Array.from(new Set([crew.pubkey, ...crew.admins])) : [];
|
||||
const { data: hiddenIds } = useCrewModeration({
|
||||
crewCoord,
|
||||
moderators,
|
||||
enabled: !!crew,
|
||||
});
|
||||
const { mutateAsync: hidePost, isPending: isHiding } = useHideCrewPost(crewCoord);
|
||||
const { mutateAsync: hidePost } = useHideCrewPost(crewCoord);
|
||||
|
||||
// Unread dot on the Chat tab — cheap, member-only, no background decryption.
|
||||
const isCrewMember = !!user && !!crew &&
|
||||
(user.pubkey === crew.pubkey || crew.members.includes(user.pubkey));
|
||||
const { hasUnread: chatHasUnread } = useCrewChatUnread(crew ?? undefined, isCrewMember);
|
||||
|
||||
const handleHidePost = async (ev: NostrEvent) => {
|
||||
const _handleHidePost = async (ev: NostrEvent) => {
|
||||
try {
|
||||
await hidePost({ postId: ev.id, postAuthor: ev.pubkey });
|
||||
toast({ title: 'Post hidden', description: 'Removed from the crew feed for everyone.' });
|
||||
|
|
|
|||
|
|
@ -491,7 +491,6 @@ function LabProfileCard({ profile }: { profile: LabProfile }) {
|
|||
}
|
||||
|
||||
function NewToNostrSection() {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useCurrentUser();
|
||||
|
||||
// Don't show this section if user is already logged in
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ function MiniStashCard({ item, onDelete }: { item: StashItem; onDelete: () => vo
|
|||
// Stash drawer component
|
||||
function StashDrawer() {
|
||||
const { user } = useCurrentUser();
|
||||
const { items, stats, isLoading, addItem, removeItem, isEncryptionAvailable } = useStash();
|
||||
const { items, stats, isLoading, removeItem, isEncryptionAvailable } = useStash();
|
||||
const author = useAuthor(user?.pubkey || '');
|
||||
const metadata = author.data?.metadata;
|
||||
const displayName = metadata?.name ?? genUserName(user?.pubkey || '');
|
||||
|
|
@ -625,7 +625,7 @@ export default function MapPage() {
|
|||
const showWelcomeBanner = !user && !welcomeDismissed;
|
||||
|
||||
// Track Sasquatch panel open state so the Add Spot FAB can step aside
|
||||
const [sasquatchPanelOpen, setSasquatchPanelOpen] = useState(false);
|
||||
const [, setSasquatchPanelOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
const handler = (e: Event) => {
|
||||
setSasquatchPanelOpen((e as CustomEvent<{ open: boolean }>).detail.open);
|
||||
|
|
@ -771,7 +771,7 @@ export default function MapPage() {
|
|||
|
||||
|
||||
// Fetch BTC Map places when enabled
|
||||
const { data: btcMapPlaces, isLoading: btcMapLoading, error: btcMapError } = useBtcMapPlaces({
|
||||
const { data: btcMapPlaces, isLoading: btcMapLoading } = useBtcMapPlaces({
|
||||
lat: mapCenter.lat,
|
||||
lon: mapCenter.lon,
|
||||
radiusKm: 50, // 50km radius
|
||||
|
|
@ -881,7 +881,7 @@ export default function MapPage() {
|
|||
}, [allLocations]);
|
||||
|
||||
// Fetch weather data for all spots
|
||||
const { data: weatherData, isLoading: weatherLoading } = useMultipleSpotWeather(
|
||||
const { data: weatherData } = useMultipleSpotWeather(
|
||||
spotGeohashes,
|
||||
weatherEnabled
|
||||
);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ function fillTextFit(ctx: CanvasRenderingContext2D, text: string, x: number, y:
|
|||
}
|
||||
|
||||
// Draw a labeled row: small muted label above, value below
|
||||
function drawLabeledRow(
|
||||
function _drawLabeledRow(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
label: string, value: string,
|
||||
x: number, y: number, maxW: number,
|
||||
|
|
@ -553,7 +553,7 @@ function memberSince(createdAt: number) {
|
|||
}
|
||||
|
||||
// Verification URL — scanning this on a phone opens the profile on skate-spots.org
|
||||
function verifyUrl(pubkey: string) {
|
||||
function _verifyUrl(pubkey: string) {
|
||||
if (!pubkey) return 'https://skate-spots.org';
|
||||
return `https://skate-spots.org/${nip19.npubEncode(pubkey)}`;
|
||||
}
|
||||
|
|
@ -1490,7 +1490,7 @@ export default function RiderCardPage() {
|
|||
}
|
||||
|
||||
// ─── Inline award form (used on the Award tab) ────────────────────────────────
|
||||
function AwardBadgeInline({ issuerPubkey, onSuccess }: { issuerPubkey: string; onSuccess: () => void }) {
|
||||
function AwardBadgeInline({ issuerPubkey: _issuerPubkey, onSuccess }: { issuerPubkey: string; onSuccess: () => void }) {
|
||||
const { mutate: publishEvent, isPending } = useNostrPublish();
|
||||
const { toast } = useToast();
|
||||
const [recipientNpub, setRecipientNpub] = useState('');
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { CVMHealthCheck } from '@/components/CVMHealthCheck';
|
|||
import { LanguageSelector } from '@/components/LanguageToggle';
|
||||
import { DesktopNavManager } from '@/components/DesktopNavManager';
|
||||
import { MyDataTab } from '@/components/MyDataTab';
|
||||
import { useTranslation } from '@/contexts/LanguageContext';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { NSchema as n } from '@nostrify/nostrify';
|
||||
|
|
@ -544,7 +543,6 @@ function RelaysScreen({ onBack }: { onBack: () => void }) {
|
|||
// ─── Appearance sub-screen ────────────────────────────────────────────────────
|
||||
|
||||
function AppearanceScreen({ onBack }: { onBack: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<BackHeader title="Appearance" onBack={onBack} />
|
||||
|
|
@ -1148,21 +1146,6 @@ export default function SettingsPage() {
|
|||
const handleNavigate = (s: Section) => setSection(s);
|
||||
const handleBack = () => setSection(null);
|
||||
|
||||
const sectionTitles: Record<NonNullable<Section>, string> = {
|
||||
profile: 'Edit Profile',
|
||||
follows: 'Follows',
|
||||
nip05: 'NIP-05 Verification',
|
||||
security: 'Security',
|
||||
mydata: 'My Data',
|
||||
language: 'Language',
|
||||
appearance: 'Appearance',
|
||||
relays: 'Relays',
|
||||
crewnetwork: 'Crew Network',
|
||||
wallet: 'Wallet',
|
||||
stash: 'My Stash',
|
||||
advanced: 'Advanced',
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container px-4 py-5 max-w-lg mx-auto">
|
||||
|
|
|
|||
Loading…
Reference in a new issue