Loading README.md +17 −1 Changes for README.md: 17 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -10,12 +10,28 @@ Developer portal UI and deployment for the OOP platform. ## Run locally The backend needs PostgreSQL. Start one matching `POSTGRESQL_SETTINGS__URL` in `.env.example`: ```bash docker run -d --name portal-db -p 5432:5432 \ -e POSTGRES_USER=portal -e POSTGRES_PASSWORD=portal -e POSTGRES_DB=portal \ postgres:16 ``` Then: ```bash cp .env.example .env # then fill in the Keycloak client make dev # backend on :8000, GUI on :3000 ``` `make run` and `make gui-run` start either one alone. `make run` and `make gui-run` start either one alone. To work without Keycloak, set `AUTH__SECURITY_MODE="none"` in `.env`: there is no login page, and every request acts as a dev user with every role. If the GUI says it can't reach the portal, the backend is not running: look for its startup error in the `make dev` output. Open [http://localhost:3000](http://localhost:3000). The GUI dev server proxies `/api` to the backend, so both share one address and the login cookies work. That is why Loading portal-gui/eslint.config.js +13 −0 Changes for portal-gui/eslint.config.js: 13 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -18,6 +18,19 @@ export default defineConfig([ languageOptions: { ecmaVersion: 2020, globals: globals.browser, // Type information, which the promise rules below need. parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, rules: { '@typescript-eslint/no-floating-promises': 'error', // Async handlers in JSX (onClick, onSubmit) are normal React. '@typescript-eslint/no-misused-promises': [ 'error', { checksVoidReturn: { attributes: false } }, ], }, }, ]); portal-gui/src/app/guards/auth-pending.tsx 0 → 100644 +22 −0 Changes for portal-gui/src/app/guards/auth-pending.tsx: 22 added lines, 0 removed lines. Original line number Diff line number Diff line import { ErrorState } from '@/components/common/error-state'; import { LoadingState } from '@/components/common/loading-state'; import type { AuthStatus } from '@/types/authentication'; // Shown while we cannot tell yet whether anyone is logged in. export function AuthPending({ status, }: { status: Extract<AuthStatus, 'loading' | 'unavailable'>; }) { if (status === 'loading') { return <LoadingState message="Checking your session…" />; } return ( <ErrorState title="Can't reach the portal" description="The portal backend or the login service is not responding." onRetry={() => window.location.reload()} /> ); } portal-gui/src/app/guards/protected-route.tsx +5 −1 Changes for portal-gui/src/app/guards/protected-route.tsx: 5 added lines, 1 removed line. Original line number Diff line number Diff line import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from '@/features/auth/auth-context'; import { ROUTES } from '../config/route'; import { AuthPending } from './auth-pending'; export function ProtectedRoute() { const { isAuthenticated } = useAuth(); const { status, isAuthenticated } = useAuth(); if (status === 'loading' || status === 'unavailable') { return <AuthPending status={status} />; } if (!isAuthenticated) { return <Navigate to={ROUTES.LOGIN} replace />; } Loading portal-gui/src/app/guards/public-route.tsx +5 −1 Changes for portal-gui/src/app/guards/public-route.tsx: 5 added lines, 1 removed line. Original line number Diff line number Diff line import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from '@/features/auth/auth-context'; import { ROUTES } from '../config/route'; import { AuthPending } from './auth-pending'; export function PublicRoute() { const { isAuthenticated } = useAuth(); const { status, isAuthenticated } = useAuth(); if (status === 'loading' || status === 'unavailable') { return <AuthPending status={status} />; } if (isAuthenticated) { return <Navigate to={ROUTES.HOME} replace />; } Loading Loading
README.md +17 −1 Changes for README.md: 17 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -10,12 +10,28 @@ Developer portal UI and deployment for the OOP platform. ## Run locally The backend needs PostgreSQL. Start one matching `POSTGRESQL_SETTINGS__URL` in `.env.example`: ```bash docker run -d --name portal-db -p 5432:5432 \ -e POSTGRES_USER=portal -e POSTGRES_PASSWORD=portal -e POSTGRES_DB=portal \ postgres:16 ``` Then: ```bash cp .env.example .env # then fill in the Keycloak client make dev # backend on :8000, GUI on :3000 ``` `make run` and `make gui-run` start either one alone. `make run` and `make gui-run` start either one alone. To work without Keycloak, set `AUTH__SECURITY_MODE="none"` in `.env`: there is no login page, and every request acts as a dev user with every role. If the GUI says it can't reach the portal, the backend is not running: look for its startup error in the `make dev` output. Open [http://localhost:3000](http://localhost:3000). The GUI dev server proxies `/api` to the backend, so both share one address and the login cookies work. That is why Loading
portal-gui/eslint.config.js +13 −0 Changes for portal-gui/eslint.config.js: 13 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -18,6 +18,19 @@ export default defineConfig([ languageOptions: { ecmaVersion: 2020, globals: globals.browser, // Type information, which the promise rules below need. parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, rules: { '@typescript-eslint/no-floating-promises': 'error', // Async handlers in JSX (onClick, onSubmit) are normal React. '@typescript-eslint/no-misused-promises': [ 'error', { checksVoidReturn: { attributes: false } }, ], }, }, ]);
portal-gui/src/app/guards/auth-pending.tsx 0 → 100644 +22 −0 Changes for portal-gui/src/app/guards/auth-pending.tsx: 22 added lines, 0 removed lines. Original line number Diff line number Diff line import { ErrorState } from '@/components/common/error-state'; import { LoadingState } from '@/components/common/loading-state'; import type { AuthStatus } from '@/types/authentication'; // Shown while we cannot tell yet whether anyone is logged in. export function AuthPending({ status, }: { status: Extract<AuthStatus, 'loading' | 'unavailable'>; }) { if (status === 'loading') { return <LoadingState message="Checking your session…" />; } return ( <ErrorState title="Can't reach the portal" description="The portal backend or the login service is not responding." onRetry={() => window.location.reload()} /> ); }
portal-gui/src/app/guards/protected-route.tsx +5 −1 Changes for portal-gui/src/app/guards/protected-route.tsx: 5 added lines, 1 removed line. Original line number Diff line number Diff line import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from '@/features/auth/auth-context'; import { ROUTES } from '../config/route'; import { AuthPending } from './auth-pending'; export function ProtectedRoute() { const { isAuthenticated } = useAuth(); const { status, isAuthenticated } = useAuth(); if (status === 'loading' || status === 'unavailable') { return <AuthPending status={status} />; } if (!isAuthenticated) { return <Navigate to={ROUTES.LOGIN} replace />; } Loading
portal-gui/src/app/guards/public-route.tsx +5 −1 Changes for portal-gui/src/app/guards/public-route.tsx: 5 added lines, 1 removed line. Original line number Diff line number Diff line import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from '@/features/auth/auth-context'; import { ROUTES } from '../config/route'; import { AuthPending } from './auth-pending'; export function PublicRoute() { const { isAuthenticated } = useAuth(); const { status, isAuthenticated } = useAuth(); if (status === 'loading' || status === 'unavailable') { return <AuthPending status={status} />; } if (isAuthenticated) { return <Navigate to={ROUTES.HOME} replace />; } Loading