diff --git a/package.json b/package.json index df5dc4e..a8525f2 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "lucide-react": "^0.462.0", "ngeohash": "^0.6.3", "nostr-tools": "^2.13.0", - "npubcash-sdk": "^0.2.0", "qrcode": "^1.5.4", "react": "^18.3.1", "react-day-picker": "^8.10.1", @@ -85,7 +84,7 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.3", - "zod": "^3.25.71" + "zod": "^3.23.8" }, "devDependencies": { "@eslint/js": "^9.9.0", diff --git a/src/components/AppProvider.tsx b/src/components/AppProvider.tsx index 2da8a41..ea86a74 100644 --- a/src/components/AppProvider.tsx +++ b/src/components/AppProvider.tsx @@ -14,7 +14,7 @@ interface AppProviderProps { // Zod schema for RelayMetadata validation const RelayMetadataSchema = z.object({ relays: z.array(z.object({ - url: z.url(), + url: z.string().url(), read: z.boolean(), write: z.boolean(), })), @@ -23,7 +23,7 @@ const RelayMetadataSchema = z.object({ // Zod schema for BlossomServerMetadata validation const BlossomServerMetadataSchema = z.object({ - servers: z.array(z.url()), + servers: z.array(z.string().url()), updatedAt: z.number(), }) satisfies z.ZodType; diff --git a/src/src/App.test.tsx b/src/src/App.test.tsx deleted file mode 100644 index bedec00..0000000 --- a/src/src/App.test.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { render } from '@testing-library/react'; -import { test } from 'vitest'; - -import App from './App'; - -test('App', () => { - render(); -}) \ No newline at end of file diff --git a/src/src/App.tsx b/src/src/App.tsx deleted file mode 100644 index 76cc557..0000000 --- a/src/src/App.tsx +++ /dev/null @@ -1,79 +0,0 @@ -// NOTE: This file should normally not be modified unless you are adding a new provider. -// To add new routes, edit the AppRouter.tsx file. - -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { createHead, UnheadProvider } from '@unhead/react/client'; -import { InferSeoMetaPlugin } from '@unhead/addons'; -import { Suspense } from 'react'; -import NostrProvider from '@/components/NostrProvider'; -import { NostrSync } from '@/components/NostrSync'; -import { Toaster } from "@/components/ui/toaster"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { NostrLoginProvider } from '@nostrify/react/login'; -import { AppProvider } from '@/components/AppProvider'; -import { NWCProvider } from '@/contexts/NWCContext'; -import { LanguageProvider } from '@/contexts/LanguageContext'; -import { AppConfig } from '@/contexts/AppContext'; -import { PWAInstallPrompt } from '@/components/PWAInstallPrompt'; -import { AmberCallbackHandler } from '@/components/auth/AmberCallbackHandler'; -import AppRouter from './AppRouter'; - -const head = createHead({ - plugins: [ - InferSeoMetaPlugin(), - ], -}); - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - staleTime: 60000, // 1 minute - gcTime: Infinity, - }, - }, -}); - -const defaultConfig: AppConfig = { - theme: "dark", - relayMetadata: { - relays: [ - { url: 'wss://relay.ditto.pub', read: true, write: true }, - { url: 'wss://goskatespots.nostr1.com', read: true, write: true }, - { url: 'wss://wotr.relatr.xyz', read: true, write: true }, - { url: 'wss://purplepag.es', read: true, write: false }, // Profile aggregator - read only - ], - updatedAt: 0, - }, - useSkateRelay: true, // Use community relay by default for skate content -}; - -export function App() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - ); -} - -export default App; diff --git a/src/src/AppRouter.tsx b/src/src/AppRouter.tsx deleted file mode 100644 index 9ffc49a..0000000 --- a/src/src/AppRouter.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { BrowserRouter, Route, Routes } from "react-router-dom"; -import { ScrollToTop } from "./components/ScrollToTop"; - -import Index from "./pages/Index"; -import MapPage from "./pages/MapPage"; -import EventsPage from "./pages/EventsPage"; -import FoodPage from "./pages/FoodPage"; -import WalletPage from "./pages/WalletPage"; -import SettingsPage from "./pages/SettingsPage"; -import CreateSpotPage from "./pages/CreateSpotPage"; -import CreateShopPage from "./pages/CreateShopPage"; -import CreateEventPage from "./pages/CreateEventPage"; -import CreateListingPage from "./pages/CreateListingPage"; -import CreateTourPage from "./pages/CreateTourPage"; -import EditSpotPage from "./pages/EditSpotPage"; -import EditShopPage from "./pages/EditShopPage"; -import EditEventPage from "./pages/EditEventPage"; -import GearSwapPage from "./pages/GearSwapPage"; -import SpotDetailPage from "./pages/SpotDetailPage"; -import ShopDetailPage from "./pages/ShopDetailPage"; -import EventDetailPage from "./pages/EventDetailPage"; -import OnboardingPage from "./pages/OnboardingPage"; -import PyramidPage from "./pages/PyramidPage"; -import LabPage from "./pages/LabPage"; -import BecomeCoachPage from "./pages/BecomeCoachPage"; -import BecomeFilmerPage from "./pages/BecomeFilmerPage"; -import CoachDetailPage from "./pages/CoachDetailPage"; -import TourDetailPage from "./pages/TourDetailPage"; -import AboutPage from "./pages/AboutPage"; -import FAQPage from "./pages/FAQPage"; -import CommunityStatsPage from "./pages/CommunityStatsPage"; -import SpotCollectionsPage from "./pages/SpotCollectionsPage"; -import CollectionDetailPage from "./pages/CollectionDetailPage"; -import DirectMessagesPage from "./pages/DirectMessagesPage"; -import ConversationPage from "./pages/ConversationPage"; -import BountyLeaderboardPage from "./pages/BountyLeaderboardPage"; -import SignersPage from "./pages/SignersPage"; -import StashPage from "./pages/StashPage"; -import { NIP19Page } from "./pages/NIP19Page"; -import NotFound from "./pages/NotFound"; - -export function AppRouter() { - return ( - - - - } /> - } /> - } /> - } /> - {/* Legacy route redirect */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - {/* Bounty System */} - } /> - {/* Security & Signers */} - } /> - {/* Stash - Private Inventory */} - } /> - {/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */} - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - ); -} -export default AppRouter; diff --git a/src/src/components/AddPhotoToSpot.tsx b/src/src/components/AddPhotoToSpot.tsx deleted file mode 100644 index 6a3fc70..0000000 --- a/src/src/components/AddPhotoToSpot.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { useState } from 'react'; -import { Camera, Upload, Loader2, X } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Textarea } from '@/components/ui/textarea'; -import { Card, CardContent } from '@/components/ui/card'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog'; -import { useCurrentUser } from '@/hooks/useCurrentUser'; -import { useNostrPublish } from '@/hooks/useNostrPublish'; -import { useUploadFile } from '@/hooks/useUploadFile'; -import { useToast } from '@/hooks/useToast'; -import { useQueryClient } from '@tanstack/react-query'; -import { PHOTO_CONTRIBUTION_KIND } from '@/hooks/useSpotContributions'; -import { SKATE_SPOT_KIND } from '@/lib/types'; -import { LoginArea } from '@/components/auth/LoginArea'; - -interface AddPhotoToSpotProps { - spotPubkey: string; - spotIdentifier: string; - spotTitle: string; -} - -export function AddPhotoToSpot({ spotPubkey, spotIdentifier, spotTitle }: AddPhotoToSpotProps) { - const { user } = useCurrentUser(); - const { mutateAsync: publishEvent, isPending: isPublishing } = useNostrPublish(); - const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile(); - const { toast } = useToast(); - const queryClient = useQueryClient(); - - const [open, setOpen] = useState(false); - const [caption, setCaption] = useState(''); - const [uploadedImage, setUploadedImage] = useState<{ url: string; mimeType?: string } | null>(null); - - const spotRef = `${SKATE_SPOT_KIND}:${spotPubkey}:${spotIdentifier}`; - - const handleImageUpload = async (e: React.ChangeEvent) => { - const files = e.target.files; - if (!files || files.length === 0) return; - - try { - const file = files[0]; - const tags = await uploadFile(file); - const url = tags.find(([name]) => name === 'url')?.[1]; - const mimeType = tags.find(([name]) => name === 'm')?.[1]; - - if (url) { - setUploadedImage({ url, mimeType }); - toast({ - title: 'Photo uploaded', - description: 'Add a caption and submit to contribute!', - }); - } - } catch (error) { - toast({ - title: 'Upload failed', - description: error instanceof Error ? error.message : 'Failed to upload photo', - variant: 'destructive', - }); - } - - e.target.value = ''; - }; - - const handleSubmit = async () => { - if (!uploadedImage) { - toast({ - title: 'No photo', - description: 'Please upload a photo first.', - variant: 'destructive', - }); - return; - } - - try { - // Create kind 1063 file metadata event - const eventTags: string[][] = [ - ['url', uploadedImage.url], - ['a', spotRef], - ['alt', `Community photo for ${spotTitle}`], - ]; - - if (uploadedImage.mimeType) { - eventTags.push(['m', uploadedImage.mimeType]); - } - - await publishEvent({ - kind: PHOTO_CONTRIBUTION_KIND, - content: caption.trim(), - tags: eventTags, - }); - - toast({ - title: 'Photo contributed!', - description: 'Your photo has been added to this spot.', - }); - - // Reset and close - setCaption(''); - setUploadedImage(null); - setOpen(false); - - // Invalidate the photos query - queryClient.invalidateQueries({ queryKey: ['spot-photos', spotPubkey, spotIdentifier] }); - } catch (error) { - toast({ - title: 'Failed to contribute', - description: error instanceof Error ? error.message : 'Something went wrong', - variant: 'destructive', - }); - } - }; - - const handleClose = () => { - setOpen(false); - setCaption(''); - setUploadedImage(null); - }; - - return ( - - - - - - - - - Add Photo to Spot - - - Contribute a photo to help others discover this spot. - - - - {!user ? ( -
-

Log in to contribute photos

- -
- ) : ( -
- {/* Photo upload area */} - {uploadedImage ? ( -
- Uploaded - -
- ) : ( - - - - - - )} - - {/* Caption */} -
-