Adds requirements and userstories. also updates diagrams to be mermaid diagrams instead of text variants
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
# User Stories — CMS Frontend
|
||||
|
||||
**Format**: "As a [persona], I want to [action], so that [benefit]"
|
||||
**Acceptance Criteria**: Detailed checklist — happy path, error states, edge cases, and security constraints
|
||||
|
||||
---
|
||||
|
||||
## Epic: Authentication & Session
|
||||
|
||||
### US-01: Login with email and password
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an Owner/Admin/User, I want to log in with my email address and password, so that I can access the CMS platform securely.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] A `/login` page is displayed for unauthenticated users
|
||||
- [ ] The form contains an email field, a password field, and a submit button
|
||||
- [ ] Email field validates format before submission (invalid format shows inline error)
|
||||
- [ ] Password field has a minimum length client-side hint (8 characters)
|
||||
- [ ] On valid credentials: access token is stored in memory, refresh token is set as httpOnly cookie, user is redirected to `/`
|
||||
- [ ] On invalid credentials: a generic error message is shown ("Invalid email or password") — no distinction between wrong email and wrong password
|
||||
- [ ] On network error: a user-friendly error message is shown ("Unable to connect. Please try again.")
|
||||
- [ ] The password field input is masked; a show/hide toggle is present
|
||||
- [ ] The submit button shows a loading state while the request is in progress
|
||||
- [ ] After successful login, the back button does NOT navigate back to the login page
|
||||
- [ ] The page is accessible when the system is initialized and the user is not logged in
|
||||
- [ ] **Security**: No token is stored in localStorage or sessionStorage
|
||||
- [ ] **Security**: The form does not autocomplete passwords in production (autocomplete="new-password" or "current-password" as appropriate)
|
||||
|
||||
---
|
||||
|
||||
### US-02: Stay logged in across tab switches (session persistence via refresh token)
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want my session to be restored when I return to the app after a page refresh or browser restart, so that I do not have to log in repeatedly.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] On app load, the app calls `POST /auth/refresh` using the httpOnly cookie before rendering protected routes
|
||||
- [ ] If the refresh succeeds: the new access token is stored in memory and the user proceeds to their intended route
|
||||
- [ ] If the refresh fails (expired or missing cookie): the user is redirected to `/login`
|
||||
- [ ] During the session restoration check, a loading/spinner state is shown — no flash of the protected content or login page
|
||||
- [ ] A user who has never logged in sees the login page immediately (no loading delay)
|
||||
- [ ] **Security**: The refresh endpoint call uses credentials (cookies) — `credentials: 'include'` in fetch or equivalent
|
||||
|
||||
---
|
||||
|
||||
### US-03: Logout and end session
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want to log out of the application, so that my session is terminated and no one else can use my account.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] A logout button/menu item is visible in the sidebar for all authenticated users
|
||||
- [ ] Clicking logout calls `POST /auth/revoke` with the current refresh token
|
||||
- [ ] After logout: in-memory access token is cleared, the browser is redirected to `/login`
|
||||
- [ ] If the revoke call fails (network error): the user is still redirected to `/login` and local state is cleared
|
||||
- [ ] After logout, navigating back (browser back button) to a protected page redirects to `/login`
|
||||
- [ ] **Security**: The httpOnly cookie is cleared upon logout (backend sets `Set-Cookie` with expired date)
|
||||
|
||||
---
|
||||
|
||||
### US-04: Redirect to login when not authenticated
|
||||
**Persona**: Anonymous Visitor
|
||||
|
||||
As an unauthenticated visitor, I want to be redirected to the login page when I try to access a protected route, so that I cannot access content I am not authorised to see.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Navigating to any protected route (e.g. `/`, `/users`, `/settings`) without a valid session redirects to `/login`
|
||||
- [ ] After redirect, the originally requested URL is preserved (e.g. as a `?redirect=/users` query param) so the user can be sent there after login
|
||||
- [ ] No protected page content is rendered, even briefly, before the redirect occurs
|
||||
- [ ] **Security**: The redirect happens client-side as a defence-in-depth measure; the API also enforces authorisation server-side
|
||||
|
||||
---
|
||||
|
||||
### US-05: Automatic access token refresh on expiry
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want my access token to be refreshed automatically when it expires during an active session, so that I am not abruptly logged out while working.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] When an API call returns a 401 (Unauthorised), the app automatically calls `POST /auth/refresh`
|
||||
- [ ] If the refresh succeeds: the original API call is retried with the new access token
|
||||
- [ ] If the refresh fails (refresh token expired): the user is redirected to `/login` with a notification ("Your session expired. Please log in again.")
|
||||
- [ ] The original user action is not lost if possible (e.g. form data is preserved)
|
||||
- [ ] Only one refresh attempt is made per 401 — infinite retry loops are prevented
|
||||
- [ ] **Security**: The token refresh logic is centralised in the API client, not duplicated across components
|
||||
|
||||
---
|
||||
|
||||
## Epic: System Initialization
|
||||
|
||||
### US-06: Initialize system as first Owner
|
||||
**Persona**: Anonymous Visitor
|
||||
|
||||
As the first visitor to a new CMS installation, I want to create an Owner account during system setup, so that the platform is ready for use.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] The `/setup` page contains a form with email and password fields
|
||||
- [ ] The form validates email format and password minimum length (8 characters)
|
||||
- [ ] On submit: `POST /setup/owner` is called with the provided credentials
|
||||
- [ ] On success: the user is redirected to `/login` with a success toast ("System initialized. Please log in.")
|
||||
- [ ] On failure (e.g. system already initialized): an appropriate error message is shown and the user is redirected to `/login`
|
||||
- [ ] **Edge case**: If the user navigates to `/setup` when the system is already initialized, they are redirected to `/login`
|
||||
- [ ] **Security**: The setup page is only reachable when `GET /setup/status` returns `{ initialized: false }`
|
||||
|
||||
---
|
||||
|
||||
### US-07: Redirect to setup when system is not initialized
|
||||
**Persona**: Anonymous Visitor
|
||||
|
||||
As a visitor to an uninitialized CMS, I want to be automatically redirected to the setup page, so that I know the system needs configuration before use.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] On app startup, `GET /setup/status` is called before rendering any page
|
||||
- [ ] If `initialized: false`: all routes redirect to `/setup`
|
||||
- [ ] If `initialized: true`: normal routing applies (login, protected routes, etc.)
|
||||
- [ ] During the status check, a loading state is shown
|
||||
- [ ] **Edge case**: If the `/setup/status` call fails, an error page is shown with a retry option
|
||||
- [ ] **Security**: The `/setup/owner` endpoint is disabled server-side once initialized; the frontend check is defence-in-depth only
|
||||
|
||||
---
|
||||
|
||||
## Epic: Dashboard
|
||||
|
||||
### US-08: View dashboard after login
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want to see a dashboard after logging in, so that I get an overview of the system's state and quick access to key functions.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] The `/` (or `/dashboard`) route renders the dashboard page for all authenticated users
|
||||
- [ ] The dashboard displays a welcome message with the user's name and role
|
||||
- [ ] The dashboard displays at least one summary widget (e.g. system status indicator)
|
||||
- [ ] The layout uses the sidebar navigation from the example app design
|
||||
- [ ] **Edge case**: If user data fails to load, a graceful error state is shown
|
||||
|
||||
---
|
||||
|
||||
### US-09: View system availability status on dashboard
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want to see the current system availability status on the dashboard, so that I am immediately aware of any maintenance or outage.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] The dashboard calls `GET /availability/status` on load
|
||||
- [ ] The status is displayed with a colour-coded indicator: Available (green), Maintenance (yellow), Unavailable (red)
|
||||
- [ ] The status message/reason is shown alongside the indicator if present
|
||||
- [ ] If the API call fails: the indicator shows "Unknown" with a retry option
|
||||
- [ ] The status is refreshed when the user navigates back to the dashboard
|
||||
|
||||
---
|
||||
|
||||
## Epic: User Management
|
||||
|
||||
### US-10: View list of users
|
||||
**Persona**: Owner, Admin
|
||||
|
||||
As an Owner or Admin, I want to see a list of all platform users, so that I can manage team membership and access.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] The `/users` page is accessible to Owner and Admin roles only
|
||||
- [ ] The page displays a table/list with each user's name, email, role, and active status
|
||||
- [ ] If the user list is empty, an empty state message is shown
|
||||
- [ ] If the API call fails, an error state is shown with a retry option
|
||||
- [ ] **Security**: Navigating to `/users` as a User role redirects to an "Access Denied" page or back to `/`
|
||||
|
||||
---
|
||||
|
||||
### US-11: Invite a new user
|
||||
**Persona**: Owner, Admin
|
||||
|
||||
As an Owner or Admin, I want to invite a new user by providing their email address and selecting their role, so that they can join the platform.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] An "Invite User" button is visible on the `/users` page for Owner and Admin roles
|
||||
- [ ] Clicking the button opens a dialog/modal with an email field and a role selector (Admin, User)
|
||||
- [ ] The email field validates format before submission
|
||||
- [ ] The role selector does NOT allow selecting Owner (Admins cannot create other Owners; Owner-role invites are also restricted unless the inviter is an Owner — consider showing Owner option only when logged in as Owner)
|
||||
- [ ] On submit: `POST /users/invite` is called
|
||||
- [ ] On success: the dialog remains open and transitions to the "share link" step (US-12)
|
||||
- [ ] On failure (e.g. email already registered): an appropriate inline error is shown
|
||||
- [ ] **Security**: The invite action is only available to Owner and Admin; role selection is validated server-side
|
||||
|
||||
---
|
||||
|
||||
### US-12: Share invite link after invitation is created
|
||||
**Persona**: Owner, Admin
|
||||
|
||||
As an Owner or Admin, I want to see and copy the generated invite link after creating an invitation, so that I can share it with the invited person.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] After a successful invitation (US-11), the dialog shows the generated invite link
|
||||
- [ ] A "Copy to clipboard" button is present; clicking it copies the link and shows a success toast
|
||||
- [ ] The link is displayed as readable text (not just a button)
|
||||
- [ ] A "Done" button closes the dialog
|
||||
- [ ] **Edge case**: If the clipboard API is unavailable (e.g. non-HTTPS context), a fallback allows manual selection of the text
|
||||
|
||||
---
|
||||
|
||||
### US-13: Complete account setup via invitation link
|
||||
**Persona**: New Invited User
|
||||
|
||||
As a person who received an invitation link, I want to complete my account setup by setting a password, so that I can log in to the platform.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Navigating to `/invite/complete?token={token}` triggers `GET /users/validate-invitation?token={token}`
|
||||
- [ ] If the token is valid: a form is shown with a display name field and a password field (with confirmation)
|
||||
- [ ] Password must meet minimum requirements (8+ characters); a strength indicator is shown
|
||||
- [ ] Password confirmation must match; mismatch shows an inline error
|
||||
- [ ] On submit: `POST /users/complete-setup` is called with the token, display name, and password
|
||||
- [ ] On success: a confirmation message is shown ("Your account is ready. You can now log in.") with a link to `/login`
|
||||
- [ ] The form shows a loading state during submission
|
||||
- [ ] **Security**: The token is sent to the backend for server-side validation; client-side token inspection is never used for access decisions
|
||||
|
||||
---
|
||||
|
||||
### US-14: Handle expired or invalid invitation token
|
||||
**Persona**: New Invited User
|
||||
|
||||
As a person with an expired or already-used invitation link, I want to see a clear error message, so that I understand why I cannot proceed and know what to do next.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] If `GET /users/validate-invitation` returns an error (expired, used, or invalid token): no setup form is shown
|
||||
- [ ] An error message is displayed: "This invitation link is invalid or has expired. Please contact your administrator for a new invite."
|
||||
- [ ] A link to `/login` is provided for users who already completed setup
|
||||
- [ ] **Edge case**: If the token query parameter is missing from the URL, the same error state is shown
|
||||
- [ ] **Security**: No partial form data is shown when token validation fails
|
||||
|
||||
---
|
||||
|
||||
## Epic: Profile
|
||||
|
||||
### US-15: View own profile information
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want to see my profile information, so that I can verify my account details.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] The `/profile` page is accessible to all authenticated users
|
||||
- [ ] The page displays the user's display name, email address, and role
|
||||
- [ ] All fields are read-only in v1
|
||||
- [ ] A placeholder/note indicates that profile editing will be available in a future version
|
||||
- [ ] **Edge case**: If the user data cannot be loaded, an error state is shown
|
||||
|
||||
---
|
||||
|
||||
## Epic: System Settings
|
||||
|
||||
### US-16: View system availability status in settings
|
||||
**Persona**: Owner
|
||||
|
||||
As an Owner, I want to view the current system availability status in the System Settings page, so that I have a central place for system-level information.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] The `/settings` page is accessible to Owner only
|
||||
- [ ] The page displays the current system status (Available / Maintenance / Unavailable) and reason from `GET /availability/status`
|
||||
- [ ] The initialized status of the system is also shown (always "Initialized" when this page is reachable)
|
||||
- [ ] Placeholder sections for future settings categories are visible (e.g. "Module Settings", "CMS Configuration")
|
||||
- [ ] **Edge case**: If the availability API call fails, the status shows "Unknown" with a retry option
|
||||
|
||||
---
|
||||
|
||||
### US-17: Access denied for System Settings for non-Owners
|
||||
**Persona**: Admin, User
|
||||
|
||||
As an Admin or User, I want to be prevented from accessing the System Settings page, so that system-level configuration is protected.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Navigating to `/settings` as an Admin or User redirects to an "Access Denied" page or to `/`
|
||||
- [ ] The System Settings item is NOT shown in the sidebar for Admin or User roles
|
||||
- [ ] **Security**: The redirect is enforced client-side as defence-in-depth; the backend also enforces authorisation
|
||||
|
||||
---
|
||||
|
||||
## Epic: Navigation & Layout
|
||||
|
||||
### US-18: See role-appropriate navigation items in sidebar
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want to see only the navigation items relevant to my role in the sidebar, so that the interface is uncluttered and I am not confused by inaccessible sections.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Dashboard: visible to all authenticated users
|
||||
- [ ] User Management: visible to Owner and Admin only
|
||||
- [ ] System Settings: visible to Owner only
|
||||
- [ ] CMS Management: visible to all authenticated users
|
||||
- [ ] Profile: visible to all authenticated users
|
||||
- [ ] Logout: visible to all authenticated users
|
||||
- [ ] The sidebar is responsive: collapses to an icon-only view or a hamburger menu on small screens
|
||||
- [ ] The currently active route is highlighted in the sidebar
|
||||
|
||||
---
|
||||
|
||||
### US-19: Toggle dark/light theme
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want to switch between dark and light themes, so that I can use the interface comfortably in different lighting conditions.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] A theme toggle button is present in the UI (sidebar footer or top bar)
|
||||
- [ ] Clicking the toggle switches between light and dark mode immediately
|
||||
- [ ] The chosen theme is persisted in `localStorage` under a well-named key (e.g. `cms-theme`)
|
||||
- [ ] On app load, the persisted theme preference is applied before the first render (no flash)
|
||||
- [ ] If no preference is stored, the system (OS) preference is used as default
|
||||
- [ ] **Security**: Only theme preference is stored in localStorage — no auth data
|
||||
|
||||
---
|
||||
|
||||
## Epic: CMS Management (Placeholder)
|
||||
|
||||
### US-20: View CMS management placeholder page
|
||||
**Persona**: Owner, Admin, User
|
||||
|
||||
As an authenticated user, I want to navigate to the CMS Management section, so that I know where CMS content features will be available in the future.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] The `/cms` route renders a CMS Management page for all authenticated users
|
||||
- [ ] The page displays a clear "Work in Progress" or "Coming Soon" message
|
||||
- [ ] A brief description explains that CMS content modules will appear here
|
||||
- [ ] The page uses the same layout as other pages (sidebar, header)
|
||||
|
||||
---
|
||||
|
||||
## Future Features (Brief Notes — Out of Scope for v1)
|
||||
|
||||
- **Profile editing** (`/profile`): Change display name and password — backend endpoints not yet confirmed
|
||||
- **Availability status management** (`/settings`): Allow Owner to set status to Available/Maintenance/Unavailable — requires CMS availability master module
|
||||
- **User deactivation/editing** (`/users`): Deactivate or edit existing users — backend endpoints not yet confirmed
|
||||
- **CMS content modules**: Actual content management pages — dependent on backend CMS modules being built
|
||||
Reference in New Issue
Block a user