Merge pull request #13 from stevedylandev/chore/update-add-feed
06c0cf4b
chore: added category picker to AddFeedDialog
2 file(s) · +100 −16
chore: added category picker to AddFeedDialog
| 11 | 11 | } from "@/components/ui/dialog"; |
|
| 12 | 12 | import { Input } from "@/components/ui/input"; |
|
| 13 | 13 | import { Label } from "@/components/ui/label"; |
|
| 14 | + | import { |
|
| 15 | + | Select, |
|
| 16 | + | SelectContent, |
|
| 17 | + | SelectItem, |
|
| 18 | + | SelectTrigger, |
|
| 19 | + | SelectValue, |
|
| 20 | + | } from "@/components/ui/select"; |
|
| 14 | 21 | import { toast } from "sonner"; |
|
| 15 | 22 | import { useEvolu } from "@/lib/evolu"; |
|
| 16 | 23 | import { |
|
| 29 | 36 | interface AddFeedDialogProps { |
|
| 30 | 37 | open: boolean; |
|
| 31 | 38 | onOpenChange: (open: boolean) => void; |
|
| 39 | + | existingCategories: string[]; |
|
| 32 | 40 | } |
|
| 33 | 41 | ||
| 34 | - | export function AddFeedDialog({ open, onOpenChange }: AddFeedDialogProps) { |
|
| 42 | + | export function AddFeedDialog({ |
|
| 43 | + | open, |
|
| 44 | + | onOpenChange, |
|
| 45 | + | existingCategories, |
|
| 46 | + | }: AddFeedDialogProps) { |
|
| 35 | 47 | const [urlInput, setUrlInput] = React.useState(""); |
|
| 36 | - | const [categoryInput, setCategoryInput] = React.useState(""); |
|
| 48 | + | const [mode, setMode] = React.useState<"existing" | "new">("existing"); |
|
| 49 | + | const [selectedCategory, setSelectedCategory] = React.useState<string>(""); |
|
| 50 | + | const [newCategory, setNewCategory] = React.useState(""); |
|
| 37 | 51 | const [isAddingFeed, setIsAddingFeed] = React.useState(false); |
|
| 38 | 52 | const [statusMessage, setStatusMessage] = React.useState(""); |
|
| 39 | 53 | ||
| 40 | 54 | const evolu = useEvolu(); |
|
| 55 | + | ||
| 56 | + | React.useEffect(() => { |
|
| 57 | + | if (open) { |
|
| 58 | + | setUrlInput(""); |
|
| 59 | + | setMode("existing"); |
|
| 60 | + | setSelectedCategory(""); |
|
| 61 | + | setNewCategory(""); |
|
| 62 | + | setStatusMessage(""); |
|
| 63 | + | } |
|
| 64 | + | }, [open]); |
|
| 41 | 65 | ||
| 42 | 66 | async function addFeed() { |
|
| 43 | 67 | if (!urlInput.trim()) { |
|
| 111 | 135 | // Sanitize feed data to meet schema constraints |
|
| 112 | 136 | const sanitizedFeed = sanitizeFeedData(feedData); |
|
| 113 | 137 | ||
| 138 | + | // Determine the final category value |
|
| 139 | + | let finalCategory: string | null = null; |
|
| 140 | + | if (mode === "new") { |
|
| 141 | + | finalCategory = newCategory.trim() || null; |
|
| 142 | + | } else { |
|
| 143 | + | if (selectedCategory && selectedCategory !== "uncategorized") { |
|
| 144 | + | finalCategory = selectedCategory; |
|
| 145 | + | } |
|
| 146 | + | } |
|
| 147 | + | ||
| 114 | 148 | const result = evolu.insert("rssFeed", { |
|
| 115 | 149 | feedUrl: feedUrl, |
|
| 116 | 150 | title: sanitizedFeed.title, |
|
| 117 | 151 | description: sanitizedFeed.description || null, |
|
| 118 | - | category: categoryInput || "Uncategorized", |
|
| 152 | + | category: finalCategory as any, |
|
| 119 | 153 | dateUpdated: new Date().toISOString(), |
|
| 120 | 154 | }); |
|
| 121 | 155 | ||
| 150 | 184 | `Successfully added "${feedData.title}" with ${posts.length} post${posts.length !== 1 ? "s" : ""}`, |
|
| 151 | 185 | ); |
|
| 152 | 186 | ||
| 153 | - | setUrlInput(""); |
|
| 154 | - | setCategoryInput(""); |
|
| 155 | - | setStatusMessage(""); |
|
| 156 | 187 | onOpenChange(false); |
|
| 157 | 188 | } catch (error) { |
|
| 158 | 189 | setStatusMessage( |
|
| 189 | 220 | We'll automatically discover the RSS feed for you |
|
| 190 | 221 | </p> |
|
| 191 | 222 | </div> |
|
| 223 | + | ||
| 192 | 224 | <div className="grid gap-3"> |
|
| 193 | - | <Label htmlFor="category-input">Category</Label> |
|
| 194 | - | <Input |
|
| 195 | - | id="category-input" |
|
| 196 | - | name="category" |
|
| 197 | - | value={categoryInput} |
|
| 198 | - | onChange={(e) => setCategoryInput(e.target.value)} |
|
| 199 | - | placeholder="e.g., Tech, News, Blogs" |
|
| 200 | - | disabled={isAddingFeed} |
|
| 201 | - | /> |
|
| 225 | + | <Label>Category</Label> |
|
| 226 | + | <div className="flex gap-2"> |
|
| 227 | + | <Button |
|
| 228 | + | type="button" |
|
| 229 | + | variant={mode === "existing" ? "default" : "outline"} |
|
| 230 | + | onClick={() => setMode("existing")} |
|
| 231 | + | className="flex-1" |
|
| 232 | + | disabled={isAddingFeed} |
|
| 233 | + | > |
|
| 234 | + | Existing |
|
| 235 | + | </Button> |
|
| 236 | + | <Button |
|
| 237 | + | type="button" |
|
| 238 | + | variant={mode === "new" ? "default" : "outline"} |
|
| 239 | + | onClick={() => setMode("new")} |
|
| 240 | + | className="flex-1" |
|
| 241 | + | disabled={isAddingFeed} |
|
| 242 | + | > |
|
| 243 | + | New |
|
| 244 | + | </Button> |
|
| 245 | + | </div> |
|
| 202 | 246 | </div> |
|
| 247 | + | ||
| 248 | + | {mode === "existing" ? ( |
|
| 249 | + | <div className="grid gap-2"> |
|
| 250 | + | <Label htmlFor="category-select">Select Category</Label> |
|
| 251 | + | <Select |
|
| 252 | + | value={selectedCategory} |
|
| 253 | + | onValueChange={setSelectedCategory} |
|
| 254 | + | disabled={isAddingFeed} |
|
| 255 | + | > |
|
| 256 | + | <SelectTrigger id="category-select"> |
|
| 257 | + | <SelectValue placeholder="Select a category" /> |
|
| 258 | + | </SelectTrigger> |
|
| 259 | + | <SelectContent> |
|
| 260 | + | <SelectItem value="uncategorized">Uncategorized</SelectItem> |
|
| 261 | + | {existingCategories.map((cat) => ( |
|
| 262 | + | <SelectItem key={cat} value={cat}> |
|
| 263 | + | {cat} |
|
| 264 | + | </SelectItem> |
|
| 265 | + | ))} |
|
| 266 | + | </SelectContent> |
|
| 267 | + | </Select> |
|
| 268 | + | </div> |
|
| 269 | + | ) : ( |
|
| 270 | + | <div className="grid gap-2"> |
|
| 271 | + | <Label htmlFor="new-category">New Category Name</Label> |
|
| 272 | + | <Input |
|
| 273 | + | id="new-category" |
|
| 274 | + | value={newCategory} |
|
| 275 | + | onChange={(e) => setNewCategory(e.target.value)} |
|
| 276 | + | placeholder="Enter category name" |
|
| 277 | + | maxLength={50} |
|
| 278 | + | disabled={isAddingFeed} |
|
| 279 | + | /> |
|
| 280 | + | </div> |
|
| 281 | + | )} |
|
| 282 | + | ||
| 203 | 283 | {statusMessage && ( |
|
| 204 | 284 | <div className="text-sm text-primary">{statusMessage}</div> |
|
| 205 | 285 | )} |
|
| 505 | 505 | ||
| 506 | 506 | return ( |
|
| 507 | 507 | <> |
|
| 508 | - | <AddFeedDialog open={dialogOpen} onOpenChange={setDialogOpen} /> |
|
| 508 | + | <AddFeedDialog |
|
| 509 | + | open={dialogOpen} |
|
| 510 | + | onOpenChange={setDialogOpen} |
|
| 511 | + | existingCategories={existingCategories} |
|
| 512 | + | /> |
|
| 509 | 513 | <CategoryEditDialog |
|
| 510 | 514 | open={categoryEditDialogOpen} |
|
| 511 | 515 | onOpenChange={setCategoryEditDialogOpen} |