"use client"; import React from "react"; import Link from "next/link"; import { ActionButton } from "@/components/ui/action-button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { FullPageSpinner } from "@/components/ui/full-page-spinner"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { toast } from "@/components/ui/use-toast"; import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { ArrowDownToLine, CheckCircle, CircleDashed, CirclePlus, Edit, FlaskConical, Plus, Save, Trash2, XCircle, } from "lucide-react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { ZFeed, zNewFeedSchema, zUpdateFeedSchema, } from "@karakeep/shared/types/feeds"; import ActionConfirmingDialog from "../ui/action-confirming-dialog"; import { Button, buttonVariants } from "../ui/button"; import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "../ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../ui/table"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; export function FeedsEditorDialog() { const { t } = useTranslation(); const [open, setOpen] = React.useState(false); const apiUtils = api.useUtils(); const form = useForm>({ resolver: zodResolver(zNewFeedSchema), defaultValues: { name: "", url: "", enabled: true, }, }); React.useEffect(() => { if (open) { form.reset(); } }, [open]); const { mutateAsync: createFeed, isPending: isCreating } = api.feeds.create.useMutation({ onSuccess: () => { toast({ description: "Feed has been created!", }); apiUtils.feeds.list.invalidate(); setOpen(false); }, }); return ( Subscribe to a new Feed
{ await createFeed(value); form.resetField("name"); form.resetField("url"); })} > { return ( Name ); }} /> { return ( URL ); }} /> { await createFeed(value); })} loading={isCreating} variant="default" className="items-center" > Add
); } export function EditFeedDialog({ feed }: { feed: ZFeed }) { const { t } = useTranslation(); const apiUtils = api.useUtils(); const [open, setOpen] = React.useState(false); React.useEffect(() => { if (open) { form.reset({ feedId: feed.id, name: feed.name, url: feed.url, }); } }, [open]); const { mutateAsync: updateFeed, isPending: isUpdating } = api.feeds.update.useMutation({ onSuccess: () => { toast({ description: "Feed has been updated!", }); setOpen(false); apiUtils.feeds.list.invalidate(); }, }); const form = useForm>({ resolver: zodResolver(zUpdateFeedSchema), defaultValues: { feedId: feed.id, name: feed.name, url: feed.url, }, }); return ( {t("actions.edit")} Edit Feed
{ await updateFeed(value); })} > { return ( ); }} /> { return ( {t("common.name")} ); }} /> { return ( {t("common.url")} ); }} /> { await updateFeed(value); })} type="submit" className="items-center" > {t("actions.save")}
); } export function FeedRow({ feed }: { feed: ZFeed }) { const { t } = useTranslation(); const apiUtils = api.useUtils(); const { mutate: deleteFeed, isPending: isDeleting } = api.feeds.delete.useMutation({ onSuccess: () => { toast({ description: "Feed has been deleted!", }); apiUtils.feeds.list.invalidate(); }, }); const { mutate: fetchNow, isPending: isFetching } = api.feeds.fetchNow.useMutation({ onSuccess: () => { toast({ description: "Feed fetch has been enqueued!", }); apiUtils.feeds.list.invalidate(); }, }); const { mutate: updateFeedEnabled } = api.feeds.update.useMutation({ onSuccess: () => { toast({ description: feed.enabled ? t("settings.feeds.feed_disabled") : t("settings.feeds.feed_enabled"), }); apiUtils.feeds.list.invalidate(); }, onError: (error) => { toast({ description: `Error: ${error.message}`, variant: "destructive", }); }, }); const handleToggle = (checked: boolean) => { updateFeedEnabled({ feedId: feed.id, enabled: checked }); }; return ( {feed.name} {feed.url} {feed.lastFetchedAt?.toLocaleString()} {feed.lastFetchedStatus === "success" ? ( ) : feed.lastFetchedStatus === "failure" ? ( ) : ( )} fetchNow({ feedId: feed.id })} > {t("actions.fetch_now")} ( deleteFeed({ feedId: feed.id })} className="items-center" type="button" > {t("actions.delete")} )} > ); } export default function FeedSettings() { const { t } = useTranslation(); const { data: feeds, isLoading } = api.feeds.list.useQuery(); return ( <>
{t("settings.feeds.rss_subscriptions")} {t("common.experimental")}
{isLoading && } {feeds && feeds.feeds.length == 0 && (

You don't have any RSS subscriptions yet.

)} {feeds && feeds.feeds.length > 0 && ( {t("common.name")} {t("common.url")} Last Fetch Last Status {t("common.actions")} {feeds.feeds.map((feed) => ( ))}
)}
); }