diff --git a/PLAN_NEXT.md b/PLAN_NEXT.md index dc760ca..c80d058 100644 --- a/PLAN_NEXT.md +++ b/PLAN_NEXT.md @@ -103,6 +103,17 @@ auth` + the LFX/stack repair commit: `npm run build` green, routes + `/api` proxy return 200, served schema validates under JSON-Schema Draft 7. +## 0.3 Frontend design pass (Mantine) + +Plain-HTML UI replaced with a coherent Mantine v9 design system: +`@mantine/core` + `@mantine/hooks` + `@rjsf/mantine` (v6, matches core) ++ `@tabler/icons-react`. `src/theme.ts` (kirchenblau primary, system +fonts only, serif display headings), `AppShell` header/nav, per-page +`Card`/`Table`/`Alert`/`Badge` via shared `StatusBadge`, dark mode via +localStorage manager + toggle + inline `ColorSchemeScript` equivalent. +RJSF swap was import-only; SSR smoke test proved all 10 intake labels + +textarea/password/checkbox/slider widgets render. `npm run build` green. + ## 1. Locked decisions (do not re-ask)* Auth: SMTP magic-link, 15-min single-use tokens (`secrets.token_urlsafe`, store `sha256`, `used_at` set before session creation), session = opaque id in **DB table**, cookie `HttpOnly; Secure; SameSite=Lax; Path=/`. diff --git a/frontend/index.html b/frontend/index.html index 01b9d1c..831f366 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,26 @@ + + Kirche Einfach +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4719b52..a7f0deb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,8 +8,12 @@ "name": "kirche-einfach-frontend", "version": "0.1.0", "dependencies": { + "@mantine/core": "^9.6.1", + "@mantine/hooks": "^9.6.1", "@rjsf/core": "^6.10.0", + "@rjsf/mantine": "^6.10.0", "@rjsf/validator-ajv8": "^6.10.0", + "@tabler/icons-react": "^3.46.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.18.3" @@ -746,6 +750,59 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.20", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.20.tgz", + "integrity": "sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.9", + "@floating-ui/utils": "^0.2.12", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -796,6 +853,50 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mantine/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-9.6.1.tgz", + "integrity": "sha512-UkcPo8Zq0uSdodvyXV62HVmfIfHCcrjFrFrH0TggTy6Wtp8a78znGP8RJOz8zC9XSPpJmKjBQjg0M+nOvIzftA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.20", + "clsx": "^2.1.1", + "react-number-format": "^5.4.5", + "react-remove-scroll": "^2.7.2", + "type-fest": "^5.8.0" + }, + "peerDependencies": { + "@mantine/hooks": "9.6.1", + "react": "^19.2.0", + "react-dom": "^19.2.0" + } + }, + "node_modules/@mantine/dates": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-9.6.1.tgz", + "integrity": "sha512-3NxfqrcwMuYB9wvxIhGhEhhazK3wcA2wG4AuNVFGJDd97ujWWAy+jGHqBF9jOusp/Mk4bn8u+3SuDICtLPDf5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "9.6.1", + "@mantine/hooks": "9.6.1", + "dayjs": ">=1.0.0", + "react": "^19.2.0", + "react-dom": "^19.2.0" + } + }, + "node_modules/@mantine/hooks": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-9.6.1.tgz", + "integrity": "sha512-pfxM95WozngB0ZALw6h99QV1TQ0qniOkFhzi0+dmSnKpxkVSR0xeNsvPpGDj9Teu+M69fRoE4IkzyMnu5Xld3w==", + "license": "MIT", + "peerDependencies": { + "react": "^19.2.0" + } + }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", @@ -829,6 +930,26 @@ "react": ">=18" } }, + "node_modules/@rjsf/mantine": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@rjsf/mantine/-/mantine-6.10.0.tgz", + "integrity": "sha512-tqD2v7rCEaI5Dme66MiaIMem/zTuKJBn9+B1d5J8cVCOh77FmEyn1neVvB6rPK+jXNVY6jJD8DRW8vAzK6OPRQ==", + "license": "Apache-2.0", + "dependencies": { + "dayjs": "^1.11.21" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@mantine/core": ">=8", + "@mantine/dates": ">=8", + "@mantine/hooks": ">=8", + "@rjsf/core": "^6.10.0", + "@rjsf/utils": "^6.10.0", + "react": ">=18" + } + }, "node_modules/@rjsf/utils": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.10.0.tgz", @@ -1222,6 +1343,32 @@ "win32" ] }, + "node_modules/@tabler/icons": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.46.0.tgz", + "integrity": "sha512-f2RYFl3fzPwj5WO82x6en0dmkjefxEfOm16D1ByM6cj/McNiwOkL4VaPUoP9VVIrXAD9WnTSVFr70px703b//A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.46.0.tgz", + "integrity": "sha512-CCm7xJWhDT2PH4ZIFkP6AgYKtVhq0gpYkjUN+GVh1AzmIQaa77OW0bQPBPQiTE0PsXMR9oSxFqA3qBglzPyrVQ==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "3.46.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1285,7 +1432,7 @@ "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1449,6 +1596,15 @@ ], "license": "CC-BY-4.0" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1473,7 +1629,13 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", "license": "MIT" }, "node_modules/debug": { @@ -1494,6 +1656,12 @@ } } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.420", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", @@ -1629,6 +1797,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1829,6 +2006,16 @@ "license": "MIT", "peer": true }, + "node_modules/react-number-format": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.5.tgz", + "integrity": "sha512-y8O2yHHj3w0aE9XO8d2BCcUOOdQTRSVq+WIuMlLVucAm5XNjJAy+BoOJiuQMldVYVOKTMyvVNfnbl2Oqp+YxGw==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -1839,6 +2026,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", @@ -1877,6 +2111,28 @@ "react-dom": ">=18" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1964,6 +2220,24 @@ "node": ">=0.10.0" } }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -1981,6 +2255,27 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.9.0.tgz", + "integrity": "sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -2026,6 +2321,49 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/vite": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 32ac6c4..9189d2b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,8 +9,12 @@ "preview": "vite preview" }, "dependencies": { + "@mantine/core": "^9.6.1", + "@mantine/hooks": "^9.6.1", "@rjsf/core": "^6.10.0", + "@rjsf/mantine": "^6.10.0", "@rjsf/validator-ajv8": "^6.10.0", + "@tabler/icons-react": "^3.46.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.18.3" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7d2c87e..fbbb7bb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,17 @@ import { Link, Route, Routes, useNavigate } from "react-router-dom"; +import { + Anchor, + AppShell, + Button, + Center, + Container, + Group, + Loader, + Text, + Title, +} from "@mantine/core"; import { AuthProvider, useAuth } from "./auth"; +import SchemeToggle from "./components/SchemeToggle"; import StartPage from "./pages/StartPage"; import AccessPage from "./pages/AccessPage"; import LoginPage from "./pages/LoginPage"; @@ -7,15 +19,8 @@ import WorkflowsPage from "./pages/WorkflowsPage"; import TasksPage from "./pages/TasksPage"; import AdminPage from "./pages/AdminPage"; -const navStyle: React.CSSProperties = { - display: "flex", - gap: 12, - marginBottom: 16, - alignItems: "center", -}; - -function Shell() { - const { me, loading, logout } = useAuth(); +function Nav() { + const { me, logout } = useAuth(); const navigate = useNavigate(); async function onLogout() { @@ -24,36 +29,89 @@ function Shell() { } return ( -
-

Kirche Einfach

-

KI-gestützte Verwaltungsplattform für Kirche

- - {loading ? ( -

Wird geladen …

- ) : ( - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + + + Start + + {!me && ( + + Zugang + )} -
+ {me && ( + <> + + Workflows + + + Aufgaben + + + Verwaltung + + + )} + + {me && ( + <> + + {me.email} + + + + )} + + ); +} + +function Shell() { + const { loading } = useAuth(); + + return ( + + + + + Kirche Einfach + +
+
+
+
+ + + + KI-gestützte Verwaltungsplattform für Kirche + + {loading ? ( +
+ +
+ ) : ( + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + )} +
+
+
); } diff --git a/frontend/src/components/AccessQueue.tsx b/frontend/src/components/AccessQueue.tsx index 21c57c1..71daf2e 100644 --- a/frontend/src/components/AccessQueue.tsx +++ b/frontend/src/components/AccessQueue.tsx @@ -1,5 +1,19 @@ import { useCallback, useEffect, useState } from "react"; +import { + Alert, + Button, + Card, + Group, + Stack, + Text, + Title, +} from "@mantine/core"; +import { + IconAlertCircle, + IconCheck, +} from "@tabler/icons-react"; import { ApiError, api, type AccessRequest } from "../api"; +import StatusBadge from "./StatusBadge"; export default function AccessQueue() { const [requests, setRequests] = useState([]); @@ -43,46 +57,72 @@ export default function AccessQueue() { return (
-

Zugangsanfragen

- {error &&

{error}

} - {info &&

{info}

} - {pending.length === 0 &&

Keine offenen Anfragen.

} -
    + + Zugangsanfragen + + {error && ( + } mb="md"> + {error} + + )} + {info && ( + } mb="md"> + {info} + + )} + {pending.length === 0 && ( + + Keine offenen Anfragen. + + )} + {pending.map((r) => ( -
  • - {r.name} · {r.email} + + + + {r.name} + + {r.email} + + {r.reason && ( - <> -
    + {r.reason} - + )} -
    - {" "} - -
  • + + + + + ))} -
+ {done.length > 0 && ( <> -

Entschieden ({done.length})

-
    - {done.map((r) => ( -
  • - {r.status} · {r.name} · {r.email} -
  • - ))} -
+ + Entschieden ({done.length}) + + {done.map((r) => ( + + + + {r.name} · {r.email} + + + ))} )}
diff --git a/frontend/src/components/AdminTab.tsx b/frontend/src/components/AdminTab.tsx index 041ff96..9c068de 100644 --- a/frontend/src/components/AdminTab.tsx +++ b/frontend/src/components/AdminTab.tsx @@ -1,4 +1,22 @@ import { useCallback, useEffect, useState } from "react"; +import { + Alert, + Anchor, + Button, + Card, + Group, + Table, + Text, + TextInput, + Title, +} from "@mantine/core"; +import { + IconAlertCircle, + IconCheck, + IconExternalLink, + IconRefresh, + IconUpload, +} from "@tabler/icons-react"; import { ApiError, LANGFLOW_URL, @@ -6,6 +24,7 @@ import { type FlowDefinition, type FlowVersion, } from "../api"; +import StatusBadge from "./StatusBadge"; export default function AdminTab() { const [flows, setFlows] = useState([]); @@ -86,61 +105,110 @@ export default function AdminTab() { return (
-

Administration

- {error &&

{error}

} - {info &&

{info}

} -

- Flows werden visuell in Langflow entworfen:{" "} - - Langflow öffnen - -

-

Import aus Langflow

-
- setLangflowId(e.target.value)} - placeholder="Langflow Flow-ID (UUID)" - style={{ flex: 1, fontFamily: "monospace" }} - /> - setImportName(e.target.value)} - placeholder="Name (optional)" - /> - -
-

Flows + Versionen

-
- -
- {flows.length === 0 &&

Keine Flows vorhanden.

} -
    - {flows.map((f) => ( -
  • - {f.name}{" "} - - {f.source_path ? `(${f.source_path})` : "(API)"} - -
      - {(versions[f.id] ?? []).map((v) => ( -
    • - v{v.version_no} ·{" "} - {v.deployed_at ? `deployed (${v.lfx_flow_id})` : "nicht deployed"} ·{" "} - {v.author || "–"}{" "} - -
    • - ))} -
    -
  • - ))} -
+ + Flows & Versionen + + {error && ( + } mb="md"> + {error} + + )} + {info && ( + } mb="md"> + {info} + + )} + + + Flows werden visuell in Langflow entworfen:{" "} + + Langflow öffnen + + + + setLangflowId(e.target.value)} + style={{ flex: 1 }} + styles={{ input: { fontFamily: "monospace" } }} + /> + setImportName(e.target.value)} + /> + + + + + + + + + {flows.length === 0 ? ( + Keine Flows vorhanden. + ) : ( + + + + Flow + Version + Status + Autor + + + + + {flows.map((f) => + (versions[f.id] ?? []).map((v, i) => ( + + + {i === 0 && ( + <> + {f.name}{" "} + + {f.source_path ? `(${f.source_path})` : "(API)"} + + + )} + + v{v.version_no} + + {v.deployed_at ? ( + + ) : ( + + )} + + {v.author || "–"} + + + + + )), + )} + +
+ )}
); } diff --git a/frontend/src/components/ApprovalsTab.tsx b/frontend/src/components/ApprovalsTab.tsx index 8c9c57e..ac525d0 100644 --- a/frontend/src/components/ApprovalsTab.tsx +++ b/frontend/src/components/ApprovalsTab.tsx @@ -1,5 +1,18 @@ import { useCallback, useEffect, useState } from "react"; +import { + Alert, + Button, + Card, + Code, + Group, + Stack, + Text, + TextInput, + Title, +} from "@mantine/core"; +import { IconAlertCircle, IconCheck, IconRefresh, IconX } from "@tabler/icons-react"; import { ApiError, api, type ApprovalTask } from "../api"; +import StatusBadge from "./StatusBadge"; export default function ApprovalsTab() { const [tasks, setTasks] = useState([]); @@ -40,56 +53,89 @@ export default function ApprovalsTab() { return (
-

Freigaben

- {error &&

{error}

} -
- + Freigaben + + {error && ( + } mb="md"> + {error} + + )} + + setApprover(e.target.value)} - placeholder="Name (Bearbeiter)" /> - setNote(e.target.value)} - placeholder="Notiz" style={{ flex: 1 }} /> - -
-

Offen ({open.length})

- {open.length === 0 &&

Keine offenen Freigaben.

} -
    + + + + Offen ({open.length}) + + {open.length === 0 && ( + + Keine offenen Freigaben. + + )} + {open.map((t) => ( -
  • - {t.title || t.id} · {t.tenant_id} -
    - {JSON.stringify(t.payload)} -
    - {" "} - -
  • + + + + {t.title || t.id} + + {t.tenant_id} + + + + {JSON.stringify(t.payload)} + + + + + + ))} -
-

Entschieden ({done.length})

-
    - {done.map((t) => ( -
  • - {t.status} · {t.title || t.id} ·{" "} - {t.approver ?? "–"} + + + Entschieden ({done.length}) + + {done.map((t) => ( + + + + {t.title || t.id} · {t.approver ?? "–"} {t.decision_note ? ` · „${t.decision_note}“` : ""} -
  • - ))} -
+ + + ))}
); } diff --git a/frontend/src/components/SchemeToggle.tsx b/frontend/src/components/SchemeToggle.tsx new file mode 100644 index 0000000..4a326b3 --- /dev/null +++ b/frontend/src/components/SchemeToggle.tsx @@ -0,0 +1,18 @@ +import { ActionIcon, useMantineColorScheme } from "@mantine/core"; +import { IconMoon, IconSun } from "@tabler/icons-react"; + +export default function SchemeToggle() { + const { colorScheme, toggleColorScheme } = useMantineColorScheme(); + const dark = colorScheme === "dark"; + return ( + toggleColorScheme()} + aria-label={dark ? "Zum hellen Modus" : "Zum dunklen Modus"} + title={dark ? "Zum hellen Modus" : "Zum dunklen Modus"} + > + {dark ? : } + + ); +} diff --git a/frontend/src/components/StatusBadge.tsx b/frontend/src/components/StatusBadge.tsx new file mode 100644 index 0000000..645c325 --- /dev/null +++ b/frontend/src/components/StatusBadge.tsx @@ -0,0 +1,21 @@ +import { Badge } from "@mantine/core"; + +/* Single color language for every status in the app. */ + +const COLORS: Record = { + completed: "green", + approved: "green", + genehmigt: "green", + deployed: "green", + running: "blue", + open: "yellow", + pending: "yellow", + pending_approval: "yellow", + failed: "red", + rejected: "red", + abgelehnt: "red", +}; + +export default function StatusBadge({ status }: { status: string }) { + return {status}; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 350a0bf..41f6e8b 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,12 +1,21 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; +import { MantineProvider } from "@mantine/core"; +import "@mantine/core/styles.css"; import App from "./App"; +import { colorSchemeManager, theme } from "./theme"; createRoot(document.getElementById("root")!).render( - - - + + + + + , ); diff --git a/frontend/src/pages/AccessPage.tsx b/frontend/src/pages/AccessPage.tsx index 39c98cd..8248d6f 100644 --- a/frontend/src/pages/AccessPage.tsx +++ b/frontend/src/pages/AccessPage.tsx @@ -1,4 +1,15 @@ import { useState } from "react"; +import { + Alert, + Button, + Card, + Stack, + Text, + Textarea, + TextInput, + Title, +} from "@mantine/core"; +import { IconAlertCircle, IconCheck } from "@tabler/icons-react"; import { ApiError, api } from "../api"; export default function AccessPage() { @@ -29,60 +40,54 @@ export default function AccessPage() { } } - if (done) { - return ( -
-

Zugang

-

+ return ( + + + Zugang anfragen + + {done ? ( + } title="Angefragt"> Danke! Ihre Anfrage ist eingegangen. Sie erhalten eine E-Mail, sobald sie freigegeben wurde. -

-
- ); - } - - return ( -
-

Zugang anfragen

- {error &&

{error}

} -
void onSubmit(e)} - style={{ display: "grid", gap: 8, maxWidth: 480 }} - > - - -