diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 15e1374..9ef2ae3 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,1482 +1,10 @@ + - - diff --git a/frontend/src/assets/styles/scoped/components/NoteEditor.css b/frontend/src/assets/styles/scoped/components/NoteEditor.css index bd75e10..b6218c4 100644 --- a/frontend/src/assets/styles/scoped/components/NoteEditor.css +++ b/frontend/src/assets/styles/scoped/components/NoteEditor.css @@ -66,24 +66,6 @@ max-height: 600px; } -.danger-zone { - padding: 1rem; - border: 1px solid #f3b5b5; - border-radius: 0.75rem; - background: var(--color-surface)5f5; -} - -.danger-zone-title { - color: #9f1c1c; - font-size: 1rem; - font-weight: 700; -} - -.danger-zone-copy { - color: #7a2727; - font-size: 0.9rem; -} - .task-mention-panel { margin-top: 0.45rem; border: 1px solid #dbe4f0; @@ -225,19 +207,6 @@ background-color: var(--color-surface); } -:root[data-bs-theme="dark"] .danger-zone { - background: #2d1a1a; - border-color: #7a3030; -} - -:root[data-bs-theme="dark"] .danger-zone-title { - color: #fc8181; -} - -:root[data-bs-theme="dark"] .danger-zone-copy { - color: #fca5a5; -} - :root[data-bs-theme="dark"] .task-mention-panel { border-color: #3a4558; background: #1f2733; @@ -296,5 +265,3 @@ background: color-mix(in srgb, var(--task-status-color, #7aa2f7) 26%, #111827 74%); color: color-mix(in srgb, var(--task-status-color, #7aa2f7) 70%, #dbeafe 30%); } - - diff --git a/frontend/src/assets/styles/scoped/components/TaskBoard.css b/frontend/src/assets/styles/scoped/components/TaskBoard.css index e8cf942..723b43a 100644 --- a/frontend/src/assets/styles/scoped/components/TaskBoard.css +++ b/frontend/src/assets/styles/scoped/components/TaskBoard.css @@ -289,24 +289,6 @@ align-items: center; } -.danger-zone { - border: 1px solid #f3b5b5; - border-radius: 0.75rem; - background: var(--color-surface) 5f5; - padding: 0.75rem; -} - -.danger-zone-title { - color: #9f1c1c; - margin: 0; - font-weight: 700; -} - -.danger-zone-copy { - color: #7a2727; - font-size: 0.9rem; -} - @media (max-width: 900px) { .task-filters { grid-template-columns: 1fr; diff --git a/frontend/src/assets/styles/shared/danger-zone.css b/frontend/src/assets/styles/shared/danger-zone.css new file mode 100644 index 0000000..82274e0 --- /dev/null +++ b/frontend/src/assets/styles/shared/danger-zone.css @@ -0,0 +1,32 @@ +.danger-zone { + padding: 1rem; + border: 1px solid #f3b5b5; + border-radius: 0.75rem; + background: #fff5f5; +} + +.danger-zone-title { + color: #9f1c1c; + font-size: 1rem; + font-weight: 700; + margin: 0; +} + +.danger-zone-copy { + color: #7a2727; + font-size: 0.9rem; + margin-bottom: 0; +} + +:root[data-bs-theme="dark"] .danger-zone { + background: #2d1a1a; + border-color: #7a3030; +} + +:root[data-bs-theme="dark"] .danger-zone-title { + color: #fc8181; +} + +:root[data-bs-theme="dark"] .danger-zone-copy { + color: #fca5a5; +} diff --git a/frontend/src/components/AdminProviderModal.vue b/frontend/src/components/AdminProviderModal.vue index 3879c84..1bc2f6f 100644 --- a/frontend/src/components/AdminProviderModal.vue +++ b/frontend/src/components/AdminProviderModal.vue @@ -62,17 +62,17 @@
-
-
-
-
Danger Zone
-
Permanently delete this provider configuration.
-
- -
-
+ + +
@@ -92,6 +92,7 @@ - - - diff --git a/frontend/src/components/AdminSpaceModal.vue b/frontend/src/components/AdminSpaceModal.vue index eba8322..39d95ae 100644 --- a/frontend/src/components/AdminSpaceModal.vue +++ b/frontend/src/components/AdminSpaceModal.vue @@ -1,5 +1,5 @@ - - - diff --git a/frontend/src/components/ConfirmActionModal.vue b/frontend/src/components/ConfirmActionModal.vue new file mode 100644 index 0000000..4b94de4 --- /dev/null +++ b/frontend/src/components/ConfirmActionModal.vue @@ -0,0 +1,62 @@ + + + diff --git a/frontend/src/components/DangerZonePanel.vue b/frontend/src/components/DangerZonePanel.vue new file mode 100644 index 0000000..be9eae1 --- /dev/null +++ b/frontend/src/components/DangerZonePanel.vue @@ -0,0 +1,24 @@ + + + diff --git a/frontend/src/components/FileExplorer.vue b/frontend/src/components/FileExplorer.vue index 20e846f..76e5151 100644 --- a/frontend/src/components/FileExplorer.vue +++ b/frontend/src/components/FileExplorer.vue @@ -78,7 +78,7 @@ {{ displayName(obj) }} {{ formatSize(obj.size) }} - @@ -87,11 +87,14 @@ + + diff --git a/frontend/src/components/TaskBoard.vue b/frontend/src/components/TaskBoard.vue index 4c1ca74..c7f84dc 100644 --- a/frontend/src/components/TaskBoard.vue +++ b/frontend/src/components/TaskBoard.vue @@ -185,11 +185,17 @@ -
-
Danger Zone
-

Delete this task list and all associated tasks permanently.

- -
+ + + -
-
Danger Zone
-

Deleting this status is permanent and cannot be undone.

+ -
+ @@ -105,7 +105,7 @@
- +
@@ -286,7 +286,16 @@ :deleting="deletingProviderModal" @close="closeProviderModal" @submit="submitProviderModal" - @delete="deleteProviderFromModal" + @delete="requestDeleteProvider" + /> + + @@ -298,6 +307,7 @@ import AdminSpaceModal from "../components/AdminSpaceModal.vue"; import AdminGroupModal from "../components/AdminGroupModal.vue"; import AdminUserModal from "../components/AdminUserModal.vue"; import AdminProviderModal from "../components/AdminProviderModal.vue"; +import ConfirmActionModal from "../components/ConfirmActionModal.vue"; const router = useRouter(); const activeTab = ref("users"); @@ -344,6 +354,12 @@ const providerModalMode = ref("create"); const selectedProvider = ref(null); const submittingProviderModal = ref(false); const deletingProviderModal = ref(false); +const showDeleteConfirmModal = ref(false); +const deleteConfirmBusy = ref(false); +const deleteConfirmIntent = ref({ + type: "", + payload: null, +}); const loadingFeatureFlags = ref(false); const savingFeatureFlags = ref(false); @@ -365,6 +381,47 @@ const clearMessages = () => { successMessage.value = ""; }; +const deleteConfirmTitle = computed(() => { + if (deleteConfirmIntent.value.type === "user") { + return "Delete User"; + } + if (deleteConfirmIntent.value.type === "group") { + return "Delete Group"; + } + if (deleteConfirmIntent.value.type === "provider") { + return "Delete Identity Provider"; + } + return "Confirm Deletion"; +}); + +const deleteConfirmMessage = computed(() => { + if (deleteConfirmIntent.value.type === "user") { + const username = deleteConfirmIntent.value.payload?.username || "this user"; + return `Delete user "${username}"? This action cannot be undone.`; + } + if (deleteConfirmIntent.value.type === "group") { + const name = deleteConfirmIntent.value.payload?.name || "this group"; + return `Delete group "${name}"? This action cannot be undone.`; + } + if (deleteConfirmIntent.value.type === "provider") { + const name = deleteConfirmIntent.value.payload?.name || "this identity provider"; + return `Delete identity provider "${name}"? This action cannot be undone.`; + } + return "Are you sure you want to continue?"; +}); + +const closeDeleteConfirmModal = () => { + if (deleteConfirmBusy.value) { + return; + } + + showDeleteConfirmModal.value = false; + deleteConfirmIntent.value = { + type: "", + payload: null, + }; +}; + const formatDate = (iso) => { if (!iso) return ""; return new Date(iso).toLocaleDateString(); @@ -438,8 +495,20 @@ const submitUserModal = async ({ group_ids }) => { } }; +const requestDeleteUser = (user) => { + if (!user?.id) { + return; + } + + deleteConfirmIntent.value = { + type: "user", + payload: user, + }; + showDeleteConfirmModal.value = true; +}; + const deleteUser = async (user) => { - if (!confirm(`Delete user "${user.username}"? This action cannot be undone.`)) { + if (!user?.id) { return; } @@ -530,7 +599,7 @@ const deleteGroup = async (group) => { if (group.is_system) { return; } - if (!confirm(`Delete group "${group.name}"? This action cannot be undone.`)) { + if (!group?.id) { return; } @@ -541,9 +610,22 @@ const deleteGroup = async (group) => { await Promise.all([loadGroups(), loadUsers()]); } catch (e) { error.value = e.response?.data || "Failed to delete group."; + throw e; } }; +const requestDeleteGroup = (group) => { + if (!group?.id || group.is_system) { + return; + } + + deleteConfirmIntent.value = { + type: "group", + payload: group, + }; + showDeleteConfirmModal.value = true; +}; + const loadSpaces = async () => { loadingSpaces.value = true; clearMessages(); @@ -631,12 +713,21 @@ const loadProviders = async () => { } }; -const deleteProviderFromModal = async (provider) => { +const requestDeleteProvider = (provider) => { if (!provider?.id) { return; } - if (!confirm(`Delete identity provider "${provider.name}"? This action cannot be undone.`)) { + closeProviderModal(); + deleteConfirmIntent.value = { + type: "provider", + payload: { ...provider }, + }; + showDeleteConfirmModal.value = true; +}; + +const deleteProviderFromModal = async (provider) => { + if (!provider?.id) { return; } @@ -649,11 +740,42 @@ const deleteProviderFromModal = async (provider) => { closeProviderModal(); } catch (e) { error.value = e.response?.data || "Failed to delete provider."; + throw e; } finally { deletingProviderModal.value = false; } }; +const confirmDeleteAction = async () => { + if (deleteConfirmBusy.value) { + return; + } + + const { type, payload } = deleteConfirmIntent.value; + if (!type || !payload) { + return; + } + + deleteConfirmBusy.value = true; + try { + if (type === "user") { + await deleteUser(payload); + } else if (type === "group") { + await deleteGroup(payload); + } else if (type === "provider") { + await deleteProviderFromModal(payload); + } + + showDeleteConfirmModal.value = false; + deleteConfirmIntent.value = { + type: "", + payload: null, + }; + } finally { + deleteConfirmBusy.value = false; + } +}; + const loadFeatureFlags = async () => { loadingFeatureFlags.value = true; clearMessages(); diff --git a/frontend/src/pages/Dashboard.vue b/frontend/src/pages/Dashboard.vue new file mode 100644 index 0000000..ab2ac17 --- /dev/null +++ b/frontend/src/pages/Dashboard.vue @@ -0,0 +1,1881 @@ + + + + + diff --git a/frontend/src/pages/Home.vue b/frontend/src/pages/Home.vue deleted file mode 100644 index c891e86..0000000 --- a/frontend/src/pages/Home.vue +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 93cd1bf..6f7e73b 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -18,13 +18,25 @@ const routes = [ { path: "/", name: "Home", - component: () => import("../pages/Home.vue"), + component: () => import("../pages/Dashboard.vue"), + meta: { requiresAuth: true }, + }, + { + path: "/dashboard/s/:spaceId/n/:noteId?", + name: "DashboardNote", + component: () => import("../pages/Dashboard.vue"), + meta: { requiresAuth: true }, + }, + { + path: "/dashboard/s/:spaceId/t/:taskListId", + name: "DashboardTaskList", + component: () => import("../pages/Dashboard.vue"), meta: { requiresAuth: true }, }, { path: "/search", name: "Search", - component: () => import("../pages/Home.vue"), + component: () => import("../pages/Dashboard.vue"), meta: { requiresAuth: true }, }, {