feat(sprint-6): Phase 6 — Notifications (WebSocket) + PWA
- WebSocket: Spring STOMP + SockJS, NotificationService, persistent notifications table - NotificationController: GET/PUT endpoints for notification management - Frontend: notification bell with unread badge, dropdown panel, real-time via STOMP - PWA: manifest.json, service worker (manual sw.js), offline page, install prompt - PWA icons (192+512), dark theme colors, standalone display - Full i18n (de/en) for notifications and PWA - Flyway V10 migration for notifications table - spring-boot-starter-websocket dependency added
This commit is contained in:
@@ -20,15 +20,27 @@ const SCREENSHOT_DIR = path.join(__dirname, "..", "docs", "screenshots")
|
||||
// Pages accessible without auth
|
||||
const PUBLIC_PAGES = [
|
||||
{ route: "/login", name: "01-login", title: "Admin Login" },
|
||||
{ route: "/portal-login", name: "02-portal-login", title: "Member Portal Login" },
|
||||
{
|
||||
route: "/portal-login",
|
||||
name: "02-portal-login",
|
||||
title: "Member Portal Login",
|
||||
},
|
||||
]
|
||||
|
||||
// Admin pages (require auth session)
|
||||
const ADMIN_PAGES = [
|
||||
{ route: "/dashboard", name: "03-dashboard", title: "Club Dashboard" },
|
||||
{ route: "/members", name: "04-members", title: "Member Management" },
|
||||
{ route: "/distributions", name: "05-distributions", title: "Distribution History" },
|
||||
{ route: "/distributions/new", name: "06-distribution-new", title: "New Distribution (Multi-Step)" },
|
||||
{
|
||||
route: "/distributions",
|
||||
name: "05-distributions",
|
||||
title: "Distribution History",
|
||||
},
|
||||
{
|
||||
route: "/distributions/new",
|
||||
name: "06-distribution-new",
|
||||
title: "New Distribution (Multi-Step)",
|
||||
},
|
||||
{ route: "/stock", name: "07-stock", title: "Stock & Batch Management" },
|
||||
{ route: "/stock/new", name: "08-stock-new", title: "Add New Batch" },
|
||||
{ route: "/reports", name: "09-reports", title: "Compliance Reports" },
|
||||
@@ -36,9 +48,21 @@ const ADMIN_PAGES = [
|
||||
|
||||
// Portal pages (no admin auth needed per middleware)
|
||||
const PORTAL_PAGES = [
|
||||
{ route: "/portal/dashboard", name: "10-portal-dashboard", title: "Member Quota Overview" },
|
||||
{ route: "/portal/history", name: "11-portal-history", title: "My Distribution History" },
|
||||
{ route: "/portal/profile", name: "12-portal-profile", title: "Profile & Settings" },
|
||||
{
|
||||
route: "/portal/dashboard",
|
||||
name: "10-portal-dashboard",
|
||||
title: "Member Quota Overview",
|
||||
},
|
||||
{
|
||||
route: "/portal/history",
|
||||
name: "11-portal-history",
|
||||
title: "My Distribution History",
|
||||
},
|
||||
{
|
||||
route: "/portal/profile",
|
||||
name: "12-portal-profile",
|
||||
title: "Profile & Settings",
|
||||
},
|
||||
]
|
||||
|
||||
async function setTheme(page: Page, theme: "dark" | "light") {
|
||||
@@ -76,7 +100,12 @@ test.describe("CannaManage Screenshot Tour", () => {
|
||||
test.setTimeout(120_000)
|
||||
|
||||
test("capture all pages in dark and light mode", async ({ page }) => {
|
||||
const results: { name: string; title: string; dark: string; light: string }[] = []
|
||||
const results: {
|
||||
name: string
|
||||
title: string
|
||||
dark: string
|
||||
light: string
|
||||
}[] = []
|
||||
|
||||
// --- PUBLIC PAGES ---
|
||||
for (const p of PUBLIC_PAGES) {
|
||||
@@ -102,13 +131,19 @@ test.describe("CannaManage Screenshot Tour", () => {
|
||||
}
|
||||
|
||||
// Check if we got authenticated (redirected to dashboard)
|
||||
const isAuthenticated = page.url().includes("/dashboard") || page.url().includes("/login")
|
||||
const isAuthenticated =
|
||||
page.url().includes("/dashboard") || page.url().includes("/login")
|
||||
|
||||
if (page.url().includes("/dashboard")) {
|
||||
// --- ADMIN PAGES (authenticated) ---
|
||||
for (const p of ADMIN_PAGES) {
|
||||
const dark = await capturePageScreenshot(page, p.route, p.name, "dark")
|
||||
const light = await capturePageScreenshot(page, p.route, p.name, "light")
|
||||
const light = await capturePageScreenshot(
|
||||
page,
|
||||
p.route,
|
||||
p.name,
|
||||
"light"
|
||||
)
|
||||
results.push({ name: p.name, title: p.title, dark, light })
|
||||
}
|
||||
} else {
|
||||
@@ -116,7 +151,12 @@ test.describe("CannaManage Screenshot Tour", () => {
|
||||
console.log("⚠️ Auth failed — admin pages will show login redirect")
|
||||
for (const p of ADMIN_PAGES) {
|
||||
const dark = await capturePageScreenshot(page, p.route, p.name, "dark")
|
||||
results.push({ name: p.name, title: `${p.title} (auth required)`, dark, light: dark })
|
||||
results.push({
|
||||
name: p.name,
|
||||
title: `${p.title} (auth required)`,
|
||||
dark,
|
||||
light: dark,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +187,9 @@ test.describe("CannaManage Screenshot Tour", () => {
|
||||
}
|
||||
fs.writeFileSync(path.join(docsDir, "visual-tour.md"), md)
|
||||
|
||||
console.log(`\n✅ Screenshot tour complete! ${results.length} pages captured.`)
|
||||
console.log(
|
||||
`\n✅ Screenshot tour complete! ${results.length} pages captured.`
|
||||
)
|
||||
console.log(`📄 Markdown: docs/visual-tour.md`)
|
||||
console.log(`📸 Screenshots: docs/screenshots/`)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user