import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { Slider } from '@/components/ui/slider' import { Hash, X, Users, Code, Coins, Newspaper, BookOpen, Scroll, Cpu, Trophy, Film, Heart, TrendingUp, Utensils, MapPin, Home, PawPrint, Shirt, Image, Zap, Settings, Book, Network, Car } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useNostr } from '@/providers/NostrProvider' import { TDraftEvent } from '@/types' import dayjs from 'dayjs' // Utility functions for thread creation function extractImagesFromContent(content: string): string[] { const imageRegex = /(https?:\/\/[^\s]+\.(jpg|jpeg|png|gif|webp|svg)(\?[^\s]*)?)/gi return content.match(imageRegex) || [] } function generateImetaTags(imageUrls: string[]): string[][] { return imageUrls.map(url => ['imeta', 'url', url]) } function buildNsfwTag(): string[] { return ['content-warning', ''] } function buildClientTag(): string[] { return ['client', 'jumble'] } interface CreateThreadDialogProps { topic: string availableRelays: string[] selectedRelay?: string | null onClose: () => void onThreadCreated: () => void } export const DISCUSSION_TOPICS = [ { id: 'general', label: 'General', icon: Hash }, { id: 'meetups', label: 'Meetups', icon: Users }, { id: 'devs', label: 'Developers', icon: Code }, { id: 'finance', label: 'Bitcoin, Finance & Economics', icon: Coins }, { id: 'politics', label: 'Politics & Breaking News', icon: Newspaper }, { id: 'literature', label: 'Literature & Art', icon: BookOpen }, { id: 'philosophy', label: 'Philosophy & Theology', icon: Scroll }, { id: 'tech', label: 'Technology & Science', icon: Cpu }, { id: 'nostr', label: 'Nostr', icon: Network }, { id: 'automotive', label: 'Automotive', icon: Car }, { id: 'sports', label: 'Sports and Gaming', icon: Trophy }, { id: 'entertainment', label: 'Entertainment & Pop Culture', icon: Film }, { id: 'health', label: 'Health & Wellness', icon: Heart }, { id: 'lifestyle', label: 'Lifestyle & Personal Development', icon: TrendingUp }, { id: 'food', label: 'Food & Cooking', icon: Utensils }, { id: 'travel', label: 'Travel & Adventure', icon: MapPin }, { id: 'home', label: 'Home & Garden', icon: Home }, { id: 'pets', label: 'Pets & Animals', icon: PawPrint }, { id: 'fashion', label: 'Fashion & Beauty', icon: Shirt } ] export default function CreateThreadDialog({ topic: initialTopic, availableRelays, selectedRelay: initialRelay, onClose, onThreadCreated }: CreateThreadDialogProps) { const { t } = useTranslation() const { pubkey, publish } = useNostr() const [title, setTitle] = useState('') const [content, setContent] = useState('') const [selectedTopic] = useState(initialTopic) const [selectedRelay, setSelectedRelay] = useState(initialRelay || '') const [isSubmitting, setIsSubmitting] = useState(false) const [errors, setErrors] = useState<{ title?: string; content?: string; relay?: string; author?: string; subject?: string }>({}) const [isNsfw, setIsNsfw] = useState(false) const [addClientTag, setAddClientTag] = useState(true) const [minPow, setMinPow] = useState(0) const [showAdvancedOptions, setShowAdvancedOptions] = useState(false) // Readings options state const [isReadingGroup, setIsReadingGroup] = useState(false) const [author, setAuthor] = useState('') const [subject, setSubject] = useState('') const [showReadingsPanel, setShowReadingsPanel] = useState(false) const validateForm = () => { const newErrors: { title?: string; content?: string; relay?: string; author?: string; subject?: string } = {} if (!title.trim()) { newErrors.title = t('Title is required') } else if (title.length > 100) { newErrors.title = t('Title must be 100 characters or less') } if (!content.trim()) { newErrors.content = t('Content is required') } else if (content.length > 5000) { newErrors.content = t('Content must be 5000 characters or less') } if (!selectedRelay) { newErrors.relay = t('Please select a relay') } // Validate readings fields if reading group is enabled if (isReadingGroup) { if (!author.trim()) { newErrors.author = t('Author is required for reading groups') } if (!subject.trim()) { newErrors.subject = t('Subject (book title) is required for reading groups') } } setErrors(newErrors) return Object.keys(newErrors).length === 0 } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!pubkey) { alert(t('You must be logged in to create a thread')) return } if (!validateForm()) { return } setIsSubmitting(true) try { // Extract images from content const images = extractImagesFromContent(content.trim()) // Build tags array const tags = [ ['title', title.trim()], ['t', selectedTopic], ['-'] // Required tag for relay privacy ] // Add readings tags if this is a reading group if (isReadingGroup) { tags.push(['t', 'readings']) tags.push(['author', author.trim()]) tags.push(['subject', subject.trim()]) } // Add image metadata tags if images are found if (images && images.length > 0) { tags.push(...generateImetaTags(images)) } // Add NSFW tag if enabled if (isNsfw) { tags.push(buildNsfwTag()) } // Add client tag if enabled if (addClientTag) { tags.push(buildClientTag()) } // Create the thread event (kind 11) const threadEvent: TDraftEvent = { kind: 11, content: content.trim(), tags, created_at: dayjs().unix() } // Publish to the selected relay only const publishedEvent = await publish(threadEvent, { specifiedRelayUrls: [selectedRelay], minPow }) if (publishedEvent) { onThreadCreated() onClose() } else { throw new Error(t('Failed to publish thread')) } } catch (error) { console.error('Error creating thread:', error) console.error('Error details:', { name: error instanceof Error ? error.name : 'Unknown', message: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }) let errorMessage = t('Failed to create thread') if (error instanceof Error) { if (error.message.includes('timeout')) { errorMessage = t('Thread creation timed out. Please try again.') } else if (error.message.includes('auth-required') || error.message.includes('auth required')) { errorMessage = t('Relay requires authentication for write access. Please try a different relay or contact the relay operator.') } else if (error.message.includes('blocked')) { errorMessage = t('Your account is blocked from posting to this relay.') } else if (error.message.includes('rate limit')) { errorMessage = t('Rate limited. Please wait before trying again.') } else if (error.message.includes('writes disabled')) { errorMessage = t('Some relays have temporarily disabled writes.') } else if (error.message && error.message.trim()) { errorMessage = `${t('Failed to create thread')}: ${error.message}` } else { errorMessage = t('Failed to create thread. Please try a different relay.') } } else if (error instanceof AggregateError) { errorMessage = t('Failed to publish to some relays. Please try again or use different relays.') } alert(errorMessage) } finally { setIsSubmitting(false) } } const selectedTopicInfo = DISCUSSION_TOPICS.find(t => t.id === selectedTopic) || DISCUSSION_TOPICS[0] return (
{t('Create New Thread')}
{/* Topic Selection */}
{selectedTopicInfo.label}

{t('Threads are organized by topics. You can change this after creation.')}

{/* Title Input */}
setTitle(e.target.value)} placeholder={t('Enter a descriptive title for your thread')} maxLength={100} className={errors.title ? 'border-destructive' : ''} /> {errors.title && (

{errors.title}

)}

{title.length}/100 {t('characters')}

{/* Content Input */}