From 0bb4e5c1cd94eac9063ad961d6c438b543e41854 Mon Sep 17 00:00:00 2001 From: x3 Date: Tue, 22 Sep 2026 14:27:33 +0200 Subject: [PATCH] feat(frontend): bell, chat, uploads, glass, teams UI, fixes - Notification bell with invites (accept/decline refreshes workspaces) and unread badge (polling + socket push); team chat view realtime - Avatar/background uploads in settings + glassmorphism widgets toggle; background layer resolves /uploads URLs; full-bleed blurred backdrop - Sidebar: visible creation errors, working sub-pages (parent_id), TEAM badge workspaces, refresh button, chat nav; admin creates users - Tasks: per-team boards, assignee select with avatars; admin tabs for users/teams/groups (team workspaces, permission checkboxes) - Register view removed (admin-only accounts); login localized; prettier emoji set; scaling/overflow + mobile fixes --- README.md | 9 +- docs/API.md | 20 +- frontend/src/App.vue | 21 +- frontend/src/assets/style.css | 81 ++++++ frontend/src/components/Avatar.vue | 11 +- frontend/src/components/Bell.vue | 107 +++++++ frontend/src/components/GreetingHeader.vue | 8 +- frontend/src/components/InviteModal.vue | 2 +- frontend/src/components/SettingsWindow.vue | 38 ++- frontend/src/components/Sidebar.vue | 106 ++++--- frontend/src/files.js | 21 ++ frontend/src/i18n/de.js | 36 ++- frontend/src/i18n/en.js | 36 ++- frontend/src/i18n/zh.js | 36 ++- frontend/src/router/index.js | 4 +- frontend/src/views/Admin.vue | 306 +++++++++++++++++---- frontend/src/views/Chat.vue | 96 +++++++ frontend/src/views/Dashboard.vue | 3 +- frontend/src/views/Login.vue | 15 +- frontend/src/views/PageEditor.vue | 2 +- frontend/src/views/Register.vue | 37 --- frontend/src/views/Tasks.vue | 60 +++- 22 files changed, 898 insertions(+), 157 deletions(-) create mode 100644 frontend/src/components/Bell.vue create mode 100644 frontend/src/files.js create mode 100644 frontend/src/views/Chat.vue delete mode 100644 frontend/src/views/Register.vue diff --git a/README.md b/README.md index c919df5..8073964 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,14 @@ light/dark mode. Self-hostable via Node.js or Docker Compose. - **Pages** — Nested pages (`parent_id`) in workspaces or **books**, icon + cover, ordering, TipTap rich-text with **tables + checklists**, stored as JSON + HTML. - **Books** 📚 — Independent containers for pages, collapsible in the sidebar. -- **Tasks** ✅ — Kanban (todo/doing/done), priorities, deadlines, links to books/pages. +- **Tasks** ✅ — Kanban (todo/doing/doing→done), priorities, deadlines, **assignees**, + links to books/pages, **per-team boards**. +- **Teams** 👥 — Teams with managers/members, **own workspaces** (`TEAM_"name"` badge), + permission **groups** (e.g. Team Creators: teams.create/edit/invite, workspace.invite). +- **Invites + notifications** 🔔 — Pending invites with accept/decline, bell with unread + badge (polling + realtime push), notifications for invites and chat messages. +- **Team chat** 💬 — Realtime workspace channel via Socket.IO. +- **Uploads** 📤 — Avatar/background images uploadable (served from `/uploads`). - **Calendar** 🗓️ — Deadlines from tasks appear automatically. - **Time tracking** ⏱️ — Top-right timer, entries linkable to books, history list. - **Settings window** ⚙️ — Profile (name, email, avatar, password), appearance diff --git a/docs/API.md b/docs/API.md index e879885..821926c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -84,7 +84,25 @@ Content fields: `content_json` (TipTap JSON), `content_html` (rendered HTML). ## Admin (requires `is_admin`) - `GET /admin/overview` → counts + recent users -- `GET /admin/users`, `PATCH /admin/users/:id {is_admin}`, `DELETE /admin/users/:id` +- `GET /admin/users`, `POST /admin/users {name,email,password,is_admin?}` (admins create + accounts; public registration is disabled once installed), `PATCH /admin/users/:id`, + `DELETE /admin/users/:id` + +## Invites & notifications + +- `POST /workspaces/:id/members {email,role}` → pending invite + notification + (workspace admin+ or `workspace.invite` permission) +- `GET /invites/mine`, `POST /invites/:id/accept`, `POST /invites/:id/decline` +- `GET /notifications/mine?unread=1` → `{ notifications, unread }` +- `PATCH /notifications/:id/read`, `POST /notifications/read-all` + +## Chat / teams / groups / upload + +- `GET /chat/:workspaceId`, `POST /chat/:workspaceId {body}`, `DELETE /chat/message/:id` +- `GET /teams/by-workspace/:id`, `POST /teams`, `PATCH/DELETE /teams/:id`, + team members CRUD, `POST /teams/:id/workspace {name}` (team workspace + badge) +- `GET /groups/by-workspace/:id` (seeds `Team Creators`), groups CRUD + members +- `POST /upload` (multipart image ≤5MB) → `{ url: /uploads/... }` ## Health diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6eddb08..1565a97 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,21 +4,29 @@ import { useAuthStore } from './stores/auth'; import { useThemeStore } from './stores/theme'; import { useLocaleStore } from './stores/locale'; import { useUiStore } from './stores/ui'; +import { fileUrl } from './files'; import SettingsWindow from './components/SettingsWindow.vue'; import TimeTrackerButton from './components/TimeTrackerButton.vue'; +import Bell from './components/Bell.vue'; const auth = useAuthStore(); const theme = useThemeStore(); const locale = useLocaleStore(); const ui = useUiStore(); +const bgRaw = computed(() => auth.user?.bgImage || ''); +const bgIsImage = computed(() => /^(https?:\/\/|\/uploads|data:)/.test(bgRaw.value)); const bgStyle = computed(() => { - const bg = auth.user?.bgImage || ''; - if (!bg) return {}; - const style = { backgroundImage: bg.startsWith('http') ? `url("${bg}")` : bg }; - if (auth.user?.bgBlur !== false) style.filter = 'blur(14px) brightness(0.85)'; + if (!bgRaw.value) return {}; + const style = { + backgroundImage: bgIsImage.value ? `url("${fileUrl(bgRaw.value)}")` : bgRaw.value, + backgroundSize: 'cover', + backgroundPosition: 'center', + }; + if (auth.user?.bgBlur !== false) style.filter = 'blur(16px) brightness(0.9)'; return style; }); +const glassOn = computed(() => !!bgRaw.value && auth.user?.bgGlass !== false); onMounted(() => { locale.set(locale.locale); @@ -28,9 +36,10 @@ onMounted(() => {