diff --git a/apps/browser-extension/src/components/CSVImportModal.tsx b/apps/browser-extension/src/components/CSVImportModal.tsx index c6d8539..f5fa8fa 100644 --- a/apps/browser-extension/src/components/CSVImportModal.tsx +++ b/apps/browser-extension/src/components/CSVImportModal.tsx @@ -1,8 +1,10 @@ import { + ALL_QUOTE_CATEGORIES, type CSVParseResult, type CSVQuoteRow, generateQuoteCSVTemplate, parseQuotesCSV, + QUOTE_CATEGORIES, validateCSVFile, } from '@cuewise/shared'; import { cn } from '@cuewise/ui'; @@ -10,6 +12,8 @@ import { AlertCircle, AlertTriangle, CheckCircle2, + ClipboardPaste, + Copy, Download, FileSpreadsheet, FolderPlus, @@ -19,22 +23,30 @@ import { import type React from 'react'; import { useRef, useState } from 'react'; import { useQuoteStore } from '../stores/quote-store'; +import { useToastStore } from '../stores/toast-store'; interface CSVImportModalProps { onClose: () => void; } type CollectionMode = 'none' | 'new' | 'existing'; +type InputMode = 'file' | 'paste'; export const CSVImportModal: React.FC = ({ onClose }) => { const { collections, createCollection, bulkAddQuotes } = useQuoteStore(); const fileInputRef = useRef(null); + // Input mode state + const [inputMode, setInputMode] = useState('file'); + // File state const [selectedFile, setSelectedFile] = useState(null); const [parseResult, setParseResult] = useState(null); const [fileError, setFileError] = useState(null); + // Paste state + const [pastedText, setPastedText] = useState(''); + // Collection state const [collectionMode, setCollectionMode] = useState('none'); const [newCollectionName, setNewCollectionName] = useState(''); @@ -124,6 +136,67 @@ export const CSVImportModal: React.FC = ({ onClose }) => { } }; + // Handle pasted text parsing + const handleParsePastedText = () => { + if (!pastedText.trim()) { + setFileError('Please paste some CSV text'); + setParseResult(null); + return; + } + + setFileError(null); + setImportComplete(false); + + try { + const result = parseQuotesCSV(pastedText); + setParseResult(result); + + // If a source is common among quotes, suggest it as collection name + if (result.valid.length > 0) { + const sources = result.valid + .map((q) => q.source) + .filter((s): s is string => !!s && s.trim() !== ''); + if (sources.length > 0) { + const sourceCounts = sources.reduce( + (acc, s) => { + acc[s] = (acc[s] || 0) + 1; + return acc; + }, + {} as Record + ); + const mostCommon = Object.entries(sourceCounts).sort((a, b) => b[1] - a[1])[0]; + if (mostCommon && mostCommon[1] >= result.valid.length / 2) { + setNewCollectionName(mostCommon[0]); + } + } + } + } catch { + setFileError('Failed to parse CSV text'); + } + }; + + // Clear pasted text + const handleClearPaste = () => { + setPastedText(''); + setParseResult(null); + setFileError(null); + setImportComplete(false); + }; + + // Handle input mode switch + const handleInputModeChange = (mode: InputMode) => { + setInputMode(mode); + // Clear state when switching modes + setSelectedFile(null); + setPastedText(''); + setParseResult(null); + setFileError(null); + setImportComplete(false); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + // Download template const handleDownloadTemplate = () => { const template = generateQuoteCSVTemplate(); @@ -138,6 +211,48 @@ export const CSVImportModal: React.FC = ({ onClose }) => { URL.revokeObjectURL(url); }; + // Copy AI prompt to clipboard + const handleCopyAIPrompt = async () => { + const categoryList = ALL_QUOTE_CATEGORIES.map( + (cat) => ` - ${cat}: ${QUOTE_CATEGORIES[cat]}` + ).join('\n'); + + const prompt = `Generate a CSV file with motivational quotes for a personal quote collection app. + +## CSV Format +The CSV must have these columns: +- text (required): The quote text +- author (required): Who said/wrote the quote +- category (optional): One of the categories below +- source (optional): Book, speech, or reference where the quote is from +- notes (optional): Personal notes about the quote + +## Available Categories +${categoryList} + +## Requirements +1. Generate 20-30 high-quality, meaningful quotes +2. Include a mix of categories for variety +3. Use accurate attributions (don't make up authors) +4. Include the source when known (book title, speech name, etc.) +5. Output as valid CSV with proper escaping for quotes containing commas + +## Example Output +text,author,category,source,notes +"The only way to do great work is to love what you do.",Steve Jobs,success,Stanford Commencement Speech 2005, +"Be the change you wish to see in the world.",Mahatma Gandhi,inspiration,,Often misattributed +"The mind is everything. What you think you become.",Buddha,mindfulness,, + +Please generate the CSV now, focusing on [SPECIFY YOUR THEME OR TOPIC HERE - e.g., "stoic philosophy", "entrepreneurship", "mindfulness and meditation", "leadership wisdom"].`; + + try { + await navigator.clipboard.writeText(prompt); + useToastStore.getState().success('AI prompt copied to clipboard'); + } catch { + useToastStore.getState().error('Failed to copy to clipboard'); + } + }; + // Handle import const handleImport = async () => { if (!parseResult || parseResult.valid.length === 0) { @@ -252,57 +367,136 @@ export const CSVImportModal: React.FC = ({ onClose }) => { {/* Content */}
- {/* File Upload Area */} - {!selectedFile ? ( + {/* Input Mode Tabs */} +
+ + +
+ + {/* File Upload Mode */} + {inputMode === 'file' && + (!selectedFile ? ( +
+ +
+ +
+
+ ) : ( +
+
+ + {selectedFile.name} +
+ +
+ ))} + + {/* Paste Text Mode */} + {inputMode === 'paste' && (
-