import { useState } from 'react'; import { useSeoMeta } from '@unhead/react'; import { useNavigate, Link } from 'react-router-dom'; import ngeohash from 'ngeohash'; import { nip19 } from 'nostr-tools'; import { Layout } from '@/components/Layout'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { useNostrPublish } from '@/hooks/useNostrPublish'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useUploadFile } from '@/hooks/useUploadFile'; import { useToast } from '@/hooks/useToast'; import { useWallet } from '@/hooks/useWallet'; import { WalletModal } from '@/components/WalletModal'; import { LoginArea } from '@/components/auth/LoginArea'; import { SKATE_EDITOR_KIND, EDITOR_SERVICE_LABELS, EDITOR_SERVICE_DESCRIPTIONS, EDITOR_SKILL_LABELS, EDITOR_SKILL_DESCRIPTIONS, } from '@/lib/types'; import { cn } from '@/lib/utils'; import { Scissors, Upload, X, Loader2, ArrowLeft, MapPin, Wallet, ExternalLink, Monitor } from 'lucide-react'; const SERVICE_ENTRIES = Object.entries(EDITOR_SERVICE_LABELS); const SKILL_ENTRIES = Object.entries(EDITOR_SKILL_LABELS); export default function BecomeEditorPage() { useSeoMeta({ title: 'Become a Video Editor - SkateSpots', description: 'Create your skateboarding video editor profile on SkateSpots.', }); const navigate = useNavigate(); const { user } = useCurrentUser(); const author = useAuthor(user?.pubkey || ''); const { mutateAsync: createEvent, isPending: isPublishing } = useNostrPublish(); const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile(); const { toast } = useToast(); const { webln, activeNWC } = useWallet(); const hasWallet = !!webln || !!activeNWC; const [bio, setBio] = useState(''); const [location, setLocation] = useState(''); const [latitude, setLatitude] = useState(''); const [longitude, setLongitude] = useState(''); const [selectedServices, setSelectedServices] = useState([]); const [selectedSkills, setSelectedSkills] = useState([]); const [image, setImage] = useState(null); const [experience, setExperience] = useState(''); const [rate, setRate] = useState(''); const [phone, setPhone] = useState(''); const [email, setEmail] = useState(''); const [website, setWebsite] = useState(''); const [portfolio, setPortfolio] = useState(''); const [social, setSocial] = useState(''); const [hardware, setHardware] = useState(''); const [software, setSoftware] = useState(''); const [isGettingLocation, setIsGettingLocation] = useState(false); const getDisplayName = () => { if (author.data?.metadata?.name) return author.data.metadata.name; if (user?.pubkey) { const npub = nip19.npubEncode(user.pubkey); return `${npub.slice(0, 12)}...${npub.slice(-4)}`; } return 'Anonymous Editor'; }; const toggleService = (s: string) => setSelectedServices((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]); const toggleSkill = (s: string) => setSelectedSkills((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]); const getCurrentLocation = () => { if (!navigator.geolocation) { toast({ title: 'Geolocation not supported', variant: 'destructive' }); return; } setIsGettingLocation(true); navigator.geolocation.getCurrentPosition( (pos) => { setLatitude(pos.coords.latitude.toFixed(6)); setLongitude(pos.coords.longitude.toFixed(6)); setIsGettingLocation(false); toast({ title: 'Location found' }); }, (err) => { setIsGettingLocation(false); toast({ title: 'Location error', description: err.message, variant: 'destructive' }); } ); }; const handleImageUpload = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; try { const tags = await uploadFile(files[0]); const url = tags[0]?.[1]; if (url) { setImage(url); toast({ title: 'Image uploaded' }); } } catch (err) { toast({ title: 'Upload failed', description: err instanceof Error ? err.message : 'Error', variant: 'destructive' }); } e.target.value = ''; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (selectedServices.length === 0) { toast({ title: 'Services required', description: 'Select at least one edit type.', variant: 'destructive' }); return; } try { const identifier = 'editor-profile'; const displayName = getDisplayName(); const tags: string[][] = [ ['d', identifier], ['title', displayName], ['t', 'skateeditor'], ['alt', `Skate video editor: ${displayName}`], ]; if (location.trim()) tags.push(['location', location.trim()]); if (latitude && longitude) { const lat = parseFloat(latitude); const lng = parseFloat(longitude); if (!isNaN(lat) && !isNaN(lng)) tags.push(['g', ngeohash.encode(lat, lng, 7)]); } for (const s of selectedServices) tags.push(['t', s]); for (const s of selectedSkills) tags.push(['t', s]); if (image) tags.push(['image', image]); if (experience.trim()) tags.push(['experience', experience.trim()]); if (rate.trim()) tags.push(['rate', rate.trim()]); if (phone.trim()) tags.push(['phone', phone.trim()]); if (email.trim()) tags.push(['email', email.trim()]); if (website.trim()) tags.push(['website', website.trim()]); if (portfolio.trim()) tags.push(['portfolio', portfolio.trim()]); if (social.trim()) tags.push(['social', social.trim()]); if (hardware.trim()) tags.push(['hardware', hardware.trim()]); if (software.trim()) tags.push(['software', software.trim()]); await createEvent({ kind: SKATE_EDITOR_KIND, content: bio.trim(), tags }); toast({ title: 'Editor profile created!', description: 'Your profile has been published to SkateSpots.' }); navigate(`/coach/${user?.pubkey}/${identifier}`); } catch (err) { toast({ title: 'Failed to publish', description: err instanceof Error ? err.message : 'Error', variant: 'destructive' }); } }; if (!user) { return (

Login Required

Log in to create your video editor profile.

); } const hasNip05 = !!author.data?.metadata?.nip05; return (
Back to The Lab

Become a Video Editor

Offer your video editing services to the skate community

{!hasNip05 && (
⚠️

NIP-05 Verification Recommended

Verify your identity to build trust with clients.

)}

⚡ Lightning Wallet for Payments

Receive Bitcoin ⚡ payments directly from clients for your editing work.

{hasWallet ? (

Wallet Connected ✓

) : (
)}
Editor Profile Tell the community about your editing style and skills {/* Identity */}

{getDisplayName()}

From your Nostr profile

{/* Profile image */}
{image ? (
profile
) : ( )}
{/* Bio */}