summaryrefslogtreecommitdiff
path: root/apps/web/app/reader/settings/_components/danger-zone-settings.tsx
diff options
context:
space:
mode:
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.tsx156
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>
+ )
+}