diff options
Diffstat (limited to 'apps/web/app/reader/settings/_components/danger-zone-settings.tsx')
| -rw-r--r-- | apps/web/app/reader/settings/_components/danger-zone-settings.tsx | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/apps/web/app/reader/settings/_components/danger-zone-settings.tsx b/apps/web/app/reader/settings/_components/danger-zone-settings.tsx new file mode 100644 index 0000000..76c48d4 --- /dev/null +++ b/apps/web/app/reader/settings/_components/danger-zone-settings.tsx @@ -0,0 +1,156 @@ +"use client" + +import { useState } from "react" +import { useRouter } from "next/navigation" +import { useMutation } from "@tanstack/react-query" +import { useUnsubscribeAll } from "@/lib/queries/use-subscription-mutations" +import { useDeleteAllFolders } from "@/lib/queries/use-folder-mutations" +import { notify } from "@/lib/notify" + +export function DangerZoneSettings() { + const router = useRouter() + const unsubscribeAll = useUnsubscribeAll() + const deleteAllFolders = useDeleteAllFolders() + const [showDeleteSubsConfirm, setShowDeleteSubsConfirm] = useState(false) + const [showDeleteFoldersConfirm, setShowDeleteFoldersConfirm] = useState(false) + const [showDeleteAccountConfirm, setShowDeleteAccountConfirm] = useState(false) + const [deleteConfirmText, setDeleteConfirmText] = useState("") + + const deleteAccount = useMutation({ + mutationFn: async () => { + const response = await fetch("/api/account", { method: "DELETE" }) + if (!response.ok) throw new Error("Failed to delete account") + }, + onSuccess: () => { + router.push("/sign-in") + }, + onError: (error: Error) => { + notify("failed to delete account: " + error.message) + }, + }) + + return ( + <div className="px-4 py-3"> + <p className="mb-6 text-text-dim"> + these actions are irreversible. proceed with caution. + </p> + + <div className="mb-6"> + <h3 className="mb-2 text-text-primary">remove all subscriptions</h3> + <p className="mb-2 text-text-dim"> + unsubscribe from every feed. entries will remain but no new ones will be fetched. + </p> + {showDeleteSubsConfirm ? ( + <div className="flex items-center gap-2"> + <span className="text-status-error">are you sure?</span> + <button + onClick={() => { + unsubscribeAll.mutate() + setShowDeleteSubsConfirm(false) + }} + disabled={unsubscribeAll.isPending} + className="border border-status-error px-3 py-1 text-status-error transition-colors hover:bg-status-error hover:text-background-primary disabled:opacity-50" + > + yes, remove all + </button> + <button + onClick={() => setShowDeleteSubsConfirm(false)} + className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary" + > + cancel + </button> + </div> + ) : ( + <button + onClick={() => setShowDeleteSubsConfirm(true)} + className="border border-border px-3 py-1 text-text-secondary transition-colors hover:border-status-error hover:text-status-error" + > + remove all subscriptions + </button> + )} + </div> + + <div className="mb-6"> + <h3 className="mb-2 text-text-primary">delete all folders</h3> + <p className="mb-2 text-text-dim"> + remove all folders. feeds will be ungrouped but not unsubscribed. + </p> + {showDeleteFoldersConfirm ? ( + <div className="flex items-center gap-2"> + <span className="text-status-error">are you sure?</span> + <button + onClick={() => { + deleteAllFolders.mutate() + setShowDeleteFoldersConfirm(false) + }} + disabled={deleteAllFolders.isPending} + className="border border-status-error px-3 py-1 text-status-error transition-colors hover:bg-status-error hover:text-background-primary disabled:opacity-50" + > + yes, delete all + </button> + <button + onClick={() => setShowDeleteFoldersConfirm(false)} + className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary" + > + cancel + </button> + </div> + ) : ( + <button + onClick={() => setShowDeleteFoldersConfirm(true)} + className="border border-border px-3 py-1 text-text-secondary transition-colors hover:border-status-error hover:text-status-error" + > + delete all folders + </button> + )} + </div> + + <div> + <h3 className="mb-2 text-text-primary">delete account</h3> + <p className="mb-2 text-text-dim"> + permanently delete your account and all associated data. this cannot be undone. + </p> + {showDeleteAccountConfirm ? ( + <div> + <p className="mb-2 text-status-error"> + type DELETE to confirm account deletion. + </p> + <div className="flex items-center gap-2"> + <input + type="text" + value={deleteConfirmText} + onChange={(event) => setDeleteConfirmText(event.target.value)} + placeholder="type DELETE" + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none placeholder:text-text-dim focus:border-status-error" + autoFocus + /> + <button + onClick={() => deleteAccount.mutate()} + disabled={deleteConfirmText !== "DELETE" || deleteAccount.isPending} + className="border border-status-error px-4 py-2 text-status-error transition-colors hover:bg-status-error hover:text-background-primary disabled:opacity-50" + > + {deleteAccount.isPending ? "deleting ..." : "confirm delete"} + </button> + <button + onClick={() => { + setShowDeleteAccountConfirm(false) + setDeleteConfirmText("") + }} + className="px-2 py-1 text-text-secondary transition-colors hover:text-text-primary" + > + cancel + </button> + </div> + </div> + ) : ( + <button + onClick={() => setShowDeleteAccountConfirm(true)} + className="border border-border px-3 py-1 text-text-secondary transition-colors hover:border-status-error hover:text-status-error" + > + delete account and all data + </button> + )} + </div> + </div> + ) +} |