Merge pull request #13 from stevedylandev/chore/update-add-feed 06c0cf4b
chore: added category picker to AddFeedDialog
Steve Simkins · 2025-12-05 19:43 2 file(s) · +100 −16
src/components/add-feed-dialog.tsx +95 −15
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
					)}
src/components/app-sidebar.tsx +5 −1
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}