18 KiB
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
/loginpage 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 enforces backend rules client-side: minimum 8 characters, at least 1 uppercase letter, 1 lowercase letter, 1 digit, and 1 non-alphanumeric character (e.g.
!@#$%) - 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/refreshusing 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/revokewith 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
/loginand 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-Cookiewith 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=/usersquery 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
/loginwith 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
/setuppage contains a form with email and password fields - The form validates email format and password minimum length (8 characters)
- On submit:
POST /setup/owneris called with the provided credentials - On success: the user is redirected to
/loginwith 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
/setupwhen the system is already initialized, they are redirected to/login - Security: The setup page is only reachable when
GET /setup/statusreturns{ 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/statusis 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/statuscall fails, an error page is shown with a retry option - Security: The
/setup/ownerendpoint 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/statuson 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
/userspage 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
/usersas 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
/userspage 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/inviteis 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}triggersGET /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 the backend requirements: minimum 8 characters, at least 1 uppercase letter, 1 lowercase letter, 1 digit, and 1 non-alphanumeric character — these rules are enforced client-side before submission and validated server-side
- Inline validation messages indicate which specific rule is not yet met (e.g. "Must contain at least 1 uppercase letter")
- Password confirmation must match; mismatch shows an inline error
- On submit:
POST /users/complete-setupis 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-invitationreturns 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
/loginis 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
/profilepage 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
/settingspage 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
/settingsas 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 Owner only (feature is for managing other client CMS instances; out of scope for v1 but access is restricted from the start)
- 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
localStorageunder 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
As an Owner, I want to navigate to the CMS Management section, so that I know where multi-CMS management features will be available in the future.
Acceptance Criteria:
- The
/cmsroute renders a CMS Management page for Owner only - Navigating to
/cmsas Admin or User redirects to an "Access Denied" page or back to/ - The CMS Management item is NOT shown in the sidebar for Admin or User roles
- The page displays a clear "Work in Progress" or "Coming Soon" message
- A brief description explains that this section will allow managing multiple client CMS instances
- The page uses the same layout as other pages (sidebar, header)
- Security: The access restriction is enforced client-side as defence-in-depth; the backend also enforces authorisation
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