From 74d8899eec61bc849a8b6cd31459e3d016da2a13 Mon Sep 17 00:00:00 2001 From: domrichardson <100129001+domrichardson@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:40:18 +0100 Subject: [PATCH] feat: Updates to dashboard and delete confirmations --- frontend/src/App.vue | 1482 +------------ .../styles/scoped/components/NoteEditor.css | 33 - .../styles/scoped/components/TaskBoard.css | 18 - .../src/assets/styles/shared/danger-zone.css | 32 + .../src/components/AdminProviderModal.vue | 26 +- frontend/src/components/AdminSpaceModal.vue | 115 +- .../src/components/ConfirmActionModal.vue | 62 + frontend/src/components/DangerZonePanel.vue | 24 + frontend/src/components/FileExplorer.vue | 46 +- frontend/src/components/NoteEditor.vue | 32 +- .../src/components/SpaceSettingsModal.vue | 112 +- frontend/src/components/TaskBoard.vue | 30 +- frontend/src/components/app/AppModalHost.vue | 145 ++ .../components/app/AppWorkspaceContent.vue | 169 ++ frontend/src/main.js | 1 + frontend/src/pages/Admin.vue | 136 +- frontend/src/pages/Dashboard.vue | 1881 +++++++++++++++++ frontend/src/pages/Home.vue | 7 - frontend/src/router/index.js | 16 +- 19 files changed, 2760 insertions(+), 1607 deletions(-) create mode 100644 frontend/src/assets/styles/shared/danger-zone.css create mode 100644 frontend/src/components/ConfirmActionModal.vue create mode 100644 frontend/src/components/DangerZonePanel.vue create mode 100644 frontend/src/components/app/AppModalHost.vue create mode 100644 frontend/src/components/app/AppWorkspaceContent.vue create mode 100644 frontend/src/pages/Dashboard.vue delete mode 100644 frontend/src/pages/Home.vue 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 @@ - - - - - - - - - - - Notely - - - - - - - - {{ currentSpace.name }} - Select Space - - - - - {{ space.name }} - - - - - - - New Space - - - - - - - - - - - - - - - - - - - {{ currentUser.username }} - - - Admin Panel - - Logout - - - - Login - - - - - - - - - - - - - - - - - - - - - {{ crumb.label }} - - {{ crumb.label }} - / - - - - - - - - Create - - - - - - New Note - - - - - - New Task List - - - - - - New Task - - - - - - - - - - - - - - - Edit Note - - - - {{ shareCopied ? "Copied" : "Share" }} - - - - - - - - - - - - - - - - - - - - No Spaces Yet - Create a space to start organizing your notes - - - Create Your First Space - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Unlock Note - - - - - - Enter the password to view {{ unlockTargetNote?.title }}. - - Password - - {{ unlockError }} - - - - - - - + + + - - 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. - - - Delete Provider - - - + + + + Delete Provider + + @@ -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 @@ - + @@ -85,24 +85,39 @@ {{ success }} - - Danger Zone - Permanently delete this space and all its notes, categories, and members. This cannot be undone. - + + + {{ deleting ? "Deleting..." : "Delete Space" }} - + + + - - - 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 @@ + + + + + + + + + {{ title }} + + + + + {{ message }} + + + + + + + + + + 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 @@ + + + {{ title }} + {{ description }} + + + + + 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. - Delete Task List - + + + + Delete Task List + + @@ -209,11 +215,16 @@ - - Danger Zone - Deleting this status is permanent and cannot be undone. + Delete Status - + @@ -105,7 +105,7 @@ Edit - Delete + Delete @@ -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 @@ + + + + + + + + + + + + Notely + + + + + + + + {{ currentSpace.name }} + Select Space + + + + + {{ space.name }} + + + + + + + New Space + + + + + + + + + + + + + + + + + + + {{ currentUser.username }} + + + Admin Panel + + Logout + + + + + + + + + + + + + + + + + + + + + + {{ crumb.label }} + + {{ crumb.label }} + / + + + + + + + + Create + + + + + + New Note + + + + + + New Task List + + + + + + New Task + + + + + + + + + + + + + + + Edit Note + + + + {{ shareCopied ? "Copied" : "Share" }} + + + + + + + + + + + + + No Spaces Yet + Create a space to start organizing your notes + + + Create Your First Space + + + + + + + + + + + + + + Unlock Note + + + + + + Enter the password to view {{ unlockTargetNote?.title }}. + + Password + + {{ unlockError }} + + + + + + + + + + + + + + + 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 }, }, {
Create a space to start organizing your notes
- Enter the password to view {{ unlockTargetNote?.title }}. -
Permanently delete this space and all its notes, categories, and members. This cannot be undone.
{{ message }}
{{ description }}
Delete this task list and all associated tasks permanently.
Deleting this status is permanent and cannot be undone.
+ Enter the password to view {{ unlockTargetNote?.title }}. +