> ## Documentation Index
> Fetch the complete documentation index at: https://docs.golansertifikasi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 03 Frontend Angular Developer Guide

# Frontend Developer Guide — Golan Sertifikasi Web

**Stack:** Angular\
**Repository:** `golan-web`\
**Primary consumers:** Students, instructors, admins, finance users, customer service\
**Backend:** versioned REST/JSON public API through KrakenD Community Edition (`golan-backend`)

The Angular application is a client of the backend. It must not become a second source of truth for authorization, payments, finance, course completion, or certificate eligibility.

***

## 1. Frontend Scope

Angular web is the MVP client for:

```text theme={null}
student-facing web
admin
instructor
finance
customer service / backoffice
```

MVP student capabilities include:

* Google login/session handling and onboarding.
* Catalog/course detail.
* Midtrans top-up return/status, wallet balance and wallet-only cart/checkout.
* Order/top-up/ledger history and wallet-refund request/status.
* Permanent regular-course access, learning, progress, quiz, assignment, and regular certificate.
* BNSP registration/fulfillment status and secure LSP access display.
* In-app notification center and web push integration where supported by the selected provider/browser strategy.

MVP backoffice capabilities include the operational screens required for course/learning administration, manual grading, BNSP fulfillment/SLA monitoring, refund review, notification operations, audit visibility, and basic reporting subject to RBAC.

The Angular application is a client of the backend. It must not become a second source of truth for authorization, payments, tax, refund eligibility, course completion, assessment results, certificate eligibility, BNSP delivery, or notification business state.

Referral, wallet, instructor earnings/withdrawal and separate super-admin realized-profit reporting/payout controls belong to MVP web workflows, subject to backend authorization and the SSOT §51 production gate. Wishlist, discussions/community, advanced CMS and advanced analytics are non-blocking Phase 2 features.

## 1.1 Public Client Architecture

Angular is a public API client. Its network boundary is:

```text theme={null}
Angular browser app
→ HTTPS REST/JSON
→ KrakenD Community Edition
→ REST/HTTP edge of the owning Go service
```

Angular must **not**:

* call internal gRPC ports;
* publish to or consume RabbitMQ directly;
* connect to PostgreSQL or Dragonfly;
* connect to ClamAV;
* construct internal service URLs or discover services;
* depend on private service database schemas or protobuf contracts;
* bypass KrakenD to call a service directly in production.

The canonical public contract is OpenAPI (`/openapi/openapi.yaml` in the backend repository) under `/api/v1/...`. Internal gRPC/protobuf and RabbitMQ event schemas are backend implementation contracts, not Angular contracts.

Angular may know **domain ownership** for diagnostics and UI semantics, but it must address public resources, not deployment units. A future backend service split/merge that preserves OpenAPI should not require a frontend architectural rewrite.

## 2. Recommended Angular Structure

Use feature-based boundaries.

```text theme={null}
src/app/
  core/
    auth/
    api/
    guards/
    interceptors/
    layout/
    config/

  shared/
    ui/
    pipes/
    directives/
    models/

  features/
    public/
    onboarding/
    catalog/
    course/
    learning/
    cart/
    checkout/
    orders/
    certificates/
    bnsp/
    reviews/
    wishlist/
    instructor/
    admin/
    finance/
    profile/

  app.routes.ts
```

Avoid one large `components/` directory containing unrelated features.

***

## 3. Routing

Example:

```text theme={null}
/
 /courses
 /courses/:slug

 /login
 /onboarding

 /app
 /app/my-courses
 /app/my-courses/:courseId
 /app/orders
 /app/certificates
 /app/bnsp
 /app/bnsp/:registrationId
 /app/profile

 /instructor/*
 /admin/*
 /finance/*

 /certificate/verify/:verificationCode
```

Use route-level lazy loading for large feature areas.

***

## 4. Authentication

Google login flow should be thin on the frontend:

```text theme={null}
click Continue with Google
→ Google auth flow
→ send required credential/code to backend
→ backend returns internal session
→ load /me
→ route based on onboarding state
```

Do not infer authentication from local UI state alone.

KrakenD may reject structurally invalid/expired access tokens at the edge, but authorization remains enforced by the owning backend service. A route guard or hidden button is never authorization.

Web security preference:

* If backend architecture supports same-site cookies, prefer secure HttpOnly session/refresh cookies.
* Do not store long-lived sensitive tokens in localStorage unless architecture requires it and risks are explicitly accepted.

***

## 5. Auth State

Central auth state should contain only what the UI needs.

Example:

```ts theme={null}
interface CurrentUser {
  id: string;
  email: string;
  avatarUrl?: string;
  displayName?: string;
  onboardingCompleted: boolean;
  roles: string[];
  permissions: string[];
}
```

UI permissions are for rendering/UX only.

The backend still enforces every permission.

***

## 6. Guards

Examples:

```text theme={null}
AuthGuard
OnboardingGuard
PermissionGuard
GuestGuard
```

Expected behavior:

* Authenticated but onboarding incomplete → `/onboarding`.
* Guest accessing private route → `/login`.
* User lacking frontend permission → render 403/access denied.
* Never assume route guard equals backend authorization.

***

## 7. HTTP Layer

Use one consistent public API client layer whose base URL points to **KrakenD**, not to individual services.

```text theme={null}
component
→ feature service/store
→ generated/central API client
→ KrakenD /api/v1/...
```

Cross-cutting responsibilities:

```text theme={null}
authentication/session credentials
request/correlation ID propagation when supported
standard response-envelope mapping
stable backend error-code mapping
401/403 handling
network timeout handling
safe retry policy
observability/redaction
```

Canonical success envelopes:

```json theme={null}
{
  "data": {},
  "meta": {}
}
```

Canonical list envelope:

```json theme={null}
{
  "data": [],
  "meta": {
    "page": 1,
    "page_size": 20,
    "total": 128,
    "total_pages": 7
  }
}
```

Canonical error shape:

```json theme={null}
{
  "error": {
    "code": "STABLE_MACHINE_CODE",
    "message": "Human-readable message",
    "fields": null,
    "request_id": "..."
  }
}
```

Rules:

* UI logic may branch on stable `error.code`; do not branch on arbitrary message text.
* Surface `fields` for form validation when supplied.
* Preserve `request_id` for support/diagnostics.
* Do not expose raw gateway/service stack traces.
* Do not scatter raw `HttpClient` calls through components.
* Do not blindly retry mutation requests. A retryable mutation requires an explicit idempotency contract/key or a backend-provided safe recovery flow.
* `502`, `503`, timeout, and network errors mean canonical state may be unknown; refetch authoritative resources before claiming failure/success for critical workflows.

## 8. API Models

Do not manually invent slightly different models per screen.

Use:

* Shared generated API types from OpenAPI, or
* A disciplined API model layer.

Backend enums are canonical.

Public DTOs describe resources; they do not expose private service tables. Cross-domain IDs in DTOs must be treated as opaque identifiers. Never attempt client-side joins by assuming database relationships across services.

Examples:

```text theme={null}
CourseType = regular | bnsp
OrderStatus = pending | paid | expired | cancelled | refunded | partially_refunded
BnspRegistrationStatus = pending_access | access_ready | access_delivered | cancelled
CertificateStatus = active | revoked
QuizAttemptStatus = in_progress | submitted | pending_review | graded | passed | failed | expired
AssignmentSubmissionStatus = submitted | under_review | passed | failed
```

***

## 9. State Management

Do not add a heavy global store by default for every screen.

Recommended approach:

* Local component state for small isolated UI.
* Signals/services for feature state.
* Introduce NgRx only where state complexity justifies it.

Good global/long-lived candidates:

```text theme={null}
current user
permissions
cart summary
global notifications
app configuration
```

Avoid treating API response cache as permanent business state.

***

## 10. Public Catalog

Course cards should clearly distinguish:

```text theme={null}
Regular Course
BNSP Registration
```

This distinction must be visible before purchase.

BNSP product copy must not imply:

* Golan issues the BNSP certificate.
* Assessment happens inside Golan.
* Passing is guaranteed by purchase.

Recommended BNSP disclosure:

```text theme={null}
After payment, Golan Sertifikasi will provide access information for the designated LSP website. The official BNSP certification process and certificate are managed by the LSP.
```

***

## 11. Course Detail

### Regular course

May show:

```text theme={null}
description
instructors[]
primary instructor when supplied
category
level
duration
modules
lesson preview
certificate availability
rating when review domain is enabled
price
```

### BNSP product

Should show:

```text theme={null}
program information
registration fee
what user receives from Golan
expected LSP handoff process
LSP access provisioning explanation
important disclaimer that certification is handled by LSP
price
```

Do not show an internal "Golan BNSP certificate" badge.

***

## 12. Onboarding

Three steps:

### Step 1 — Profile

```text theme={null}
legal/full name
birth date
gender
```

### Step 2 — Interests

Multi-select course categories.

### Step 3 — Acquisition

Single-select source + `Other` text when needed.

Requirements:

* Preserve entered values between steps.
* Load categories from API.
* Validate client-side for UX.
* Backend remains authoritative.
* Completion request only after required steps are saved.

***

## 13. Cart

Cart must support both course types.

Each item should show type:

```text theme={null}
Regular
BNSP
```

Do not compute final payable totals as authoritative client values.

Backend checkout response is canonical for:

```text theme={null}
subtotal
discount
tax
grand_total
```

Customer-facing prices are tax-inclusive. Frontend may render previews returned by the backend, but it must not add PPN itself or derive a payable total from a hardcoded tax rate. The final breakdown and amount always come from backend checkout.

***

## 14. Top-up and Wallet Checkout

Suggested flow:

```text theme={null}
Refetch backend available/held wallet balance
→ If insufficient, create top-up intent with Finance
→ Open Midtrans UI for top-up only
→ On return show "verifying top-up" and refetch top-up/wallet status
→ Review cart and POST wallet checkout when balance is sufficient
→ Render pending while Commerce reconciles reserve/capture
→ Refetch backend order until paid, then render fulfillment status
```

Critical rule:

> A successful Midtrans browser callback is not proof of a wallet credit or course purchase. Midtrans never pays a course order directly.

The UI must wait for backend canonical status.

States:

```text theme={null}
creating_order
awaiting_topup
verifying_topup
awaiting_wallet_capture
paid
expired
failed/cancelled
```

***

## 15. Payment Result UX

After Midtrans top-up UI closes/returns:

* Do not instantly create local enrollment state.
* Refetch top-up and wallet; never create or mark a course order paid from the redirect.
* If pending, show top-up verification state.
* Offer manual "Check top-up status".
* After wallet credit, checkout via Commerce and refetch its order until it reports paid:
  * Regular course → link to My Courses.
  * BNSP → link to BNSP Registration status.

***

## 16. Student Regular Learning

Suggested layout:

```text theme={null}
Course sidebar
  modules
  lessons
  quiz
  assignments

Main content
  lesson content
  player/document
  next/previous
```

Progress displayed from backend.

Avoid client-only progress calculations that may diverge from server rules.

***

## 17. Quiz UX

Support:

```text theme={null}
multiple choice
multiple answer
true/false
essay
```

Requirements:

* Timer only when the backend/API returns an effective timed-attempt policy.
* Warn before leaving an active attempt.
* Submit through backend with double-submit protection.
* Show score/status according to backend response.
* Never expose correct-answer metadata before submission unless backend review policy explicitly permits it.
* Essay submission must render `pending_review` or equivalent when manual grading is outstanding.
* Do not show `passed` merely because the HTTP submission request succeeded.
* Manual grading screens are available only to actors with grading permission and must show grading actor/result history where the API exposes it.
* After grading mutation, refetch/reconcile the canonical attempt rather than locally manufacturing final course-completion state.

## 18. Assignment UX

Support:

```text theme={null}
text response
file upload
submission status
attempt/submission history
score
feedback
resubmission when backend permits
```

Files should upload through approved backend/object-storage flow.

Rules:

* The current MVP default is three submissions, but Angular must **never** embed `3` as the business authority.
* Render `max_submissions`, `submissions_used`, `can_resubmit`, and any deadline/reason from the backend response.
* Every submission is shown as its own historical record where UX requires history; a resubmission must not visually overwrite the prior attempt as though it never existed.
* Disable/enable resubmit controls from canonical backend capability fields and still handle server rejection because policy may change between render and click.
* Grading success is not inferred from upload/submission success.

## 19. Regular Certificates

Student area:

```text theme={null}
/app/certificates
```

Show:

```text theme={null}
course
certificate number
issue date
status
view/download
verification link
```

Current certificate status semantics:

```text theme={null}
active
revoked
```

Regular certificates do **not** expire. Do not show an expiration date, countdown, or client-generated expiry state.

Public verification:

```text theme={null}
/certificate/verify/:verificationCode
```

Verification page should reveal only approved public data. Certificate availability/status is always taken from backend; course completion UI alone is not proof of issuance.

## 20. BNSP Student Experience

Dedicated area:

```text theme={null}
/app/bnsp
```

List states:

```text theme={null}
Pending LSP Access
Access Ready
Access Delivered
Cancelled
```

The API should provide operational fields such as:

```text theme={null}
fulfillment_due_at
is_overdue
access_ready_at
access_delivered_at
```

Angular renders these fields; it does not calculate the SLA by adding a hardcoded 24 hours. The current product default is 1 × 24 hours on the configured business-day calendar, but backend policy remains authoritative.

Detail page may show:

```text theme={null}
Program
Order / registration reference
Status
Fulfillment target/deadline supplied by backend
LSP name
LSP website URL
Account identifier
Password/access secret
Instructions
```

### Credential UX

Credentials are sensitive.

Requirements:

* Password hidden by default.
* Explicit "Show" action.
* Copy button available.
* No secret in browser URL.
* No secret in analytics payload.
* No secret in error logging.
* Avoid persisting secret in application state longer than necessary.
* API response containing secret should not be cached.
* Clear secret state when navigating away if practical.

Recommended status copy for `pending_access` should communicate that payment is confirmed and admin is preparing LSP access without promising a client-computed timestamp.

`access_delivered` means authorized staff have recorded delivery. Student opening/viewing credentials must not trigger that status transition. If a future acknowledgement feature exists, render it as a separate concept.

This is not an internal BNSP assessment screen.

## 21. Admin BNSP Fulfillment

Admin screen:

```text theme={null}
/admin/bnsp-registrations
```

Filters should support canonical status plus SLA operations, for example:

```text theme={null}
pending_access
access_ready
access_delivered
cancelled
overdue / SLA breached   # backend-derived operational filter, not a client-created fulfillment status
```

Detail should show:

```text theme={null}
LSP name
LSP website URL
account identifier
account secret input/replacement flow
instructions
fulfillment_due_at
is_overdue
access_ready_at
access_delivered_at
access_delivered_by
```

Rules:

* Creating/updating credentials and marking delivery are distinct mutations.
* "Mark Delivered" is an explicit authorized action; merely viewing the admin screen or student screen does not set delivery state.
* After mark-delivered succeeds, refetch the backend record before showing final state.
* SLA overdue styling/badges are derived from backend fields/filter results, not from a locally embedded policy duration.
* Credential mutation forms must avoid accidental resubmission and must not write secrets to logs, analytics, URL state, or persistent client caches.
* Monitoring/escalation queues should be operationally sortable/filterable without inventing a new certification state.

## 22. Admin Course Management

Course editor should conditionally render sections.

For `regular`:

```text theme={null}
learning modules
quizzes
assignments
completion rules
certificate settings
```

For `bnsp`:

```text theme={null}
product/registration information
pricing
LSP-related public information if desired
```

Do not expose regular certificate configuration for BNSP.

***

## 23. RBAC UX

Navigation visibility can use permissions.

Examples:

```text theme={null}
course.publish
bnsp_registration.manage_access
refund.approve
withdrawal.approve
```

However:

* Hidden button ≠ authorization.
* Handle backend 403 cleanly.
* Provide a generic access denied page.

***

## 24. Reviews and Wishlist

`wishlist` is **Phase 2** and must not block MVP delivery. If its existing UI/domain code remains, keep it isolated from core checkout/catalog paths.

`reviews/ratings` have not been explicitly assigned to MVP or Phase 2 in the current product decision. Treat release scope for reviews as an `Open Product Decision`; do not silently make reviews a release blocker.

For any enabled review UI, backend remains authoritative for eligibility and moderation state.

## 24.1 Media Upload and Scan Contract

Assignments, course media, certificate files, and other binary objects are governed by Media Service behind the public API. Angular must not construct arbitrary S3 object keys or treat a successful upload transfer as proof that a file is usable.

Expected untrusted-upload lifecycle:

```text theme={null}
request upload authorization
→ upload to authorized target
→ media state pending_scan / scanning
→ ClamAV processing
→ clean | infected | scan_failed
```

Rules:

* Only `clean` media can be treated as trusted/usable.
* `pending_scan`, `scanning`, and `scan_failed` require a waiting/error state; do not silently attach them as completed assignment content.
* `infected` must be rejected and never offered for normal download.
* Private downloads use authorized temporary URLs or another backend-controlled access mechanism.
* Original filenames are presentation metadata, not trusted object identifiers.
* BNSP credentials and auth tokens must never be sent through media metadata.

## 25. Errors

Map backend error codes to useful UI.

Examples:

```text theme={null}
VALIDATION_ERROR
AUTH_REQUIRED
FORBIDDEN
COURSE_NOT_FOUND
ORDER_NOT_PAYABLE
PAYMENT_PENDING
PAYMENT_ALREADY_PROCESSED
BNsp_ACCESS_NOT_READY
CERTIFICATE_NOT_ELIGIBLE
```

Prefer error-code mapping over parsing backend message strings.

***

## 26. Loading and Empty States

Every data screen needs:

```text theme={null}
loading
success
empty
error
```

High-value screens that require careful states:

```text theme={null}
checkout
payment verification
my courses
BNSP access
certificate verification
withdrawals
refunds
```

***

## 27. Analytics

Track approved UI events only.

Examples:

```text theme={null}
course_view
category_view
search
wishlist_add
cart_add
checkout_start
bnsp_access_view
```

Never send:

```text theme={null}
password
LSP credential
Google token
bank account data
certificate private data
```

UTM parameters should be captured on first relevant landing and sent through the supported backend attribution flow.

***

## 28. Accessibility

Minimum:

* Keyboard navigable controls.
* Form labels.
* Semantic headings.
* Error messages associated with fields.
* Accessible dialogs.
* Color is not the only status indicator.
* Video content should support accessibility improvements when content production allows.

***

## 29. Responsive Design

Primary breakpoints should support:

```text theme={null}
desktop admin/dashboard
tablet
mobile browser
```

Even with a Flutter mobile app, public web and authenticated web should remain responsive.

***

## 30. Performance

Use:

* Lazy routes.
* Image optimization.
* Pagination/infinite load where appropriate.
* Avoid loading complete course content in catalog payload.
* Avoid repeated `/me` calls.
* Cache only non-sensitive data appropriately.
* Use trackBy/equivalent stable identity for lists.

Never cache BNSP secret responses in persistent browser storage.

***

## 31. Environment and Runtime Configuration

Typical environments:

```text theme={null}
local
development
staging
production
```

Frontend runtime/build config may include deployment values such as:

```text theme={null}
API_BASE_URL              # KrakenD/public API origin only
GOOGLE_CLIENT_ID
MIDTRANS_CLIENT_KEY
MIDTRANS_ENV
APP_BASE_URL
PUSH_PUBLIC_CONFIG where applicable
```

Do **not** configure Angular with internal addresses or credentials such as:

```text theme={null}
IDENTITY_SERVICE_URL
COMMERCE_SERVICE_URL
GRPC_*
RABBITMQ_URL
DATABASE_URL
DRAGONFLY_ADDR
S3_SECRET_KEY
SMTP_PASSWORD
MIDTRANS_SERVER_KEY
JWT_SIGNING_PRIVATE_KEY
```

The browser talks to KrakenD/public endpoints only. Never put backend secrets in Angular builds.

### Business-policy rule

Do not add frontend environment variables such as:

```text theme={null}
PPN_RATE
REFUND_DAYS
BNSP_SLA_HOURS
MAX_ASSIGNMENT_SUBMISSIONS
```

as authoritative product policy.

Angular should consume derived API fields such as:

```text theme={null}
price.tax
price.grand_total
refund_request_deadline_at
can_request_refund
assignment.max_submissions
assignment.can_resubmit
bnsp.fulfillment_due_at
bnsp.is_overdue
```

Client-side use of these fields is for presentation and validation hints only; every mutation remains backend-validated.

## 31.1 Distributed Backend Workflow Semantics

The backend uses service-owned transactions plus asynchronous RabbitMQ processing for many cross-domain effects. Angular must not assume that one HTTP response means every downstream service has converged.

Examples:

```text theme={null}
payment settled
→ Commerce order update
→ Learning enrollment OR BNSP registration
→ Finance effects when applicable
→ Notification
→ Analytics
```

These effects may converge asynchronously. UX rules:

* After Midtrans top-up return, fetch canonical top-up and wallet status; never infer wallet credit or paid order from redirect parameters.
* If Commerce confirms wallet capture and the order is paid but enrollment/BNSP registration is not yet visible, show a neutral processing state and refetch with bounded backoff.
* A timeout on a mutation is an **unknown outcome** until the resource is refetched or the idempotent command result is resolved.
* Duplicate notifications/events must not trigger duplicate UI mutations.
* Do not build UI logic around RabbitMQ event names or queue timing.
* Use backend-exposed business statuses, timestamps, capabilities, and reason codes.

## 32. Testing

### Unit

Focus on:

```text theme={null}
guards
permission rendering
forms
formatters
feature stores
API mapping
```

### Component

Focus on:

```text theme={null}
onboarding
checkout summary
BNSP credential display
certificate verification
admin BNSP form
```

### E2E

Critical web flows:

```text theme={null}
Google login / session bootstrap
onboarding
regular course purchase
payment verification
learning
regular certificate
BNSP purchase
admin credential provisioning
student BNSP credential view
```

Use test accounts and synthetic credentials only.

***

## 33. Frontend Definition of Done

A feature is complete when:

1. Success/loading/empty/error states exist.
2. Backend validation errors are rendered correctly.
3. Permissions affect UX without pretending to provide backend security.
4. Sensitive data is not persisted unnecessarily.
5. BNSP and regular product semantics are clearly separated.
6. Payment success waits for backend canonical status.
7. Mobile-responsive behavior is acceptable.
8. Tests cover critical interactions.
9. API contract matches current OpenAPI/SSOT.
10. All production API traffic uses the configured KrakenD/public API origin; no feature calls internal service hosts/gRPC.
11. Distributed-workflow screens tolerate eventual convergence and unknown mutation outcomes.
12. Upload-dependent flows respect Media Service scan state before considering a file usable.

***

## 34. Frontend Engineering Baseline

The web repository should define a small set of non-negotiable conventions so feature teams do not create incompatible patterns.

Recommended baseline:

```text theme={null}
Angular standalone architecture
strict TypeScript
strict template type checking
feature-level lazy loading
reactive forms for non-trivial forms
signals/services for ordinary feature state
OpenAPI-generated or centrally defined API DTOs
ESLint + formatter enforced in CI
```

Avoid introducing a global state framework merely because the project has many screens. Add one only when actual cross-feature state complexity requires it.

Each feature should expose a narrow public surface. A feature should not import another feature's internal components/services directly unless that dependency is intentional and stable.

Suggested dependency direction:

```text theme={null}
app shell
  ↓
features
  ↓
shared/core abstractions

feature A ─X→ feature B internals
```

***

## 35. Route and Access Contract

Routes should encode presentation/navigation, while the backend remains authoritative for access.

Recommended route metadata concept:

```ts theme={null}
interface RouteAccessData {
  auth?: 'guest' | 'authenticated';
  onboarding?: 'required' | 'incomplete';
  permissions?: string[];
}
```

Expected route behavior:

```text theme={null}
public catalog
→ no auth required

/login
→ guest-oriented; authenticated users can be redirected appropriately

/onboarding
→ authenticated + onboarding incomplete

/app/*
→ authenticated + onboarding complete

/instructor/*
→ authenticated + required instructor permissions

/admin/*
→ authenticated + explicit permissions

/finance/*
→ authenticated + explicit finance permissions
```

Do not authorize solely by checking role strings such as `admin`. Route access should use backend-supplied permissions where practical.

When a protected API returns 403 despite the UI believing access exists, the UI must accept the backend result, show access denied, and refresh permissions if appropriate.

***

## 36. Session and Authentication Lifecycle

The web app must handle more than the initial Google button.

Canonical client states:

```text theme={null}
unknown
unauthenticated
authenticating
authenticated
refreshing
expired
```

Bootstrap sequence:

```text theme={null}
App start
→ determine whether an application session may exist
→ call session refresh/bootstrap according to backend contract
→ GET /me
→ populate CurrentUser
→ route according to authentication + onboarding + permissions
```

Requirements:

* Prevent multiple simultaneous refresh attempts from producing a refresh storm.
* Queue or fail requests consistently while a refresh is in progress.
* A failed refresh that definitively means session expiry must clear authenticated state.
* A temporary network error must not be confused with explicit logout/session invalidation.
* Logout must clear client auth state and sensitive feature state.
* Never log OAuth credentials, access tokens, refresh tokens, or raw auth responses.

If cookies are used:

```text theme={null}
HttpOnly
Secure in production
appropriate SameSite policy
CSRF defenses where required
```

If bearer tokens are used, storage and refresh behavior must be documented as part of the backend contract.

***

## 37. DTO, View Model, and Enum Discipline

API DTOs and UI view models solve different problems and should not be conflated.

Recommended pattern:

```text theme={null}
API DTO
→ mapper
→ feature view model
→ component
```

Use a mapper when the UI needs derived presentation data such as:

```text theme={null}
formatted price
display status
display duration
button capability flags
safe public URL representation
```

Do not rewrite canonical enums inside components.

Bad:

```ts theme={null}
if (order.status === 'success') { ... }
```

when backend canonical state is `paid`.

Prefer exhaustive handling of known states so a new backend enum causes a visible development/test failure rather than silent incorrect UI.

***

## 38. Form Architecture and Validation

Use reactive forms for onboarding, checkout-adjacent forms, admin forms, refund/withdrawal forms, and other multi-field workflows.

Validation layers:

```text theme={null}
client synchronous validation
→ immediate UX feedback

client asynchronous validation
→ only where useful, e.g. code availability

backend validation
→ authoritative business validation
```

Field errors returned by backend should map to the relevant form control where possible.

Example normalized error handling:

```ts theme={null}
interface ApiValidationError {
  code: 'VALIDATION_ERROR';
  fields: Record<string, string[]>;
}
```

Rules:

* Preserve form values after server validation failure.
* Disable duplicate submit while the same mutation is in flight.
* Do not permanently disable the button after recoverable network failures.
* Dirty-form navigation warnings should be used for high-loss forms such as course editing and admin BNSP credential entry.

***

## 39. Query, Pagination, Filter, and URL State

Catalog and admin lists should have predictable URL-addressable state.

Recommended URL parameters:

```text theme={null}
?q=
&category=
&type=
&level=
&sort=
&page=
&page_size=
```

For admin list pages, include status/filter parameters where relevant.

Requirements:

* Refreshing the page should preserve meaningful list/filter state.
* Back/forward navigation should behave correctly.
* Do not fetch on every keystroke without debounce for search.
* Cancel or ignore stale requests when new query state supersedes them.
* Pagination metadata comes from backend.
* Do not infer total pages from current page length.

***

## 40. Mutation and Idempotency UX

Client mutation state should distinguish:

```text theme={null}
idle
submitting
success
validation_error
conflict
network_error
unknown_result
```

`unknown_result` matters for operations where the request may have reached the server but the response was lost.

For checkout/payment/financial mutations:

* Prefer backend-supported idempotency keys where defined.
* Do not automatically replay a request merely because a timeout occurred.
* Provide a safe reconciliation action such as refetching order status.

For harmless idempotent actions such as wishlist state, optimistic UI may be used with rollback.

***

## 41. Detailed BNSP Credential Retrieval Contract

The student credential screen should be deliberately isolated from ordinary cached feature state.

Suggested UI flow:

```text theme={null}
Open BNSP registration detail
→ load non-sensitive registration metadata
→ if status permits, show "View LSP access"
→ explicit user action
→ fetch sensitive access payload
→ render secret masked
→ explicit reveal/copy action
→ clear sensitive object on route destroy/logout
```

Do not preload secrets in:

```text theme={null}
BNSP registration list
route resolver shared cache
service worker cache
browser localStorage/sessionStorage
analytics context
error monitoring breadcrumbs
```

When the credential endpoint returns 403/404/not-ready, display a state-specific message instead of exposing raw backend text.

Admin credential form rules:

* Password/secret is never shown in list rows.
* Edit behavior must not accidentally blank an existing encrypted secret when the secret field is left untouched.
* If the backend uses explicit replace semantics, the form must distinguish `unchanged`, `replace`, and `clear` where clearing is permitted.
* After saving, scrub the plaintext secret from the form model as soon as practical.

***

## 42. Admin and Backoffice Interaction Standards

Backoffice screens are operational tools and should prioritize correctness over decorative UI.

Every table/list that can trigger consequential actions should expose enough context to avoid acting on the wrong entity.

Examples:

```text theme={null}
order: invoice + user + date + total + status
refund: request reference + order + amount + reason + status
BNSP: registration reference + user + program + payment state + access state
withdrawal: request reference + user + amount + destination summary + status
```

High-impact actions should use explicit confirmation:

```text theme={null}
certificate revoke
refund approve/process
withdrawal approve/reject
role/permission changes
course archive after purchases
BNSP credential replacement
```

Confirmation dialogs must describe the actual effect. Avoid generic `Are you sure?` for financial or irreversible actions.

***

## 43. Design System and UI State Contract

Create shared primitives for repeated behavior rather than one-off implementations.

Minimum reusable components/patterns:

```text theme={null}
button
input/select/textarea
form field + validation message
modal/dialog
alert/banner
badge/status chip
skeleton/loading state
empty state
error state
pagination
confirmation dialog
table shell
price display
course type badge
```

Status colors/icons are presentation only. Always include text labels so state is not communicated by color alone.

Canonical client state labels should map from backend enums through one centralized mapping layer.

Example:

```text theme={null}
pending_access → Pending LSP Access
access_ready → Access Ready
paid → Paid
expired → Expired
revoked → Revoked
```

***

## 44. Frontend Observability and Privacy

Production frontend telemetry should help diagnose failures without collecting secrets.

Useful context:

```text theme={null}
release/version
route name
request ID/correlation ID
API error code
browser/device class
feature name
```

Never include:

```text theme={null}
OAuth tokens
LSP account secrets
Midtrans secret/server credentials
full payment payloads
bank account numbers
private uploaded documents
raw assignment content unless explicitly required
```

Global error handling must distinguish application bugs from expected domain errors.

Expected 4xx outcomes should not create noisy fatal alerts when they are already handled by the feature.

***

## 45. Expanded Frontend Test Matrix

Testing should be organized by risk and locked business semantics, not just component count.

### Contract tests

Verify that generated/central API models compile against the current OpenAPI contract and that enum/field changes are surfaced, including derived policy fields.

### Auth tests

```text theme={null}
guest → protected route redirects
new user → onboarding
completed user → app
expired session → refresh/logout path
403 → access denied, not fake success
```

### Commerce + refund tests

```text theme={null}
tax-inclusive backend breakdown rendered exactly
no client-side PPN addition
checkout duplicate-click protection
Midtrans returns but backend still pending
pending later becomes paid
expired/cancelled order display
refund button driven by backend can_request_refund/deadline
refund request success ≠ refund approved
approved/rejected/processing/processed states
```

### Learning tests

```text theme={null}
permanent enrollment remains accessible after completion
lesson status transitions
objective quiz submit validation
essay submit → pending_review
manual grading result refresh
assignment upload failure/retry
assignment resubmit creates/shows new attempt history
max submissions driven by backend field
course completion refresh
certificate appears only after backend issuance
certificate has active/revoked only; no expiry UI
```

### BNSP tests

```text theme={null}
pending_access
access_ready
fulfillment_due_at rendered from API
overdue badge/filter driven by backend field
credential fetch forbidden
secret masked by default
reveal/copy
secret absent from telemetry mocks
admin create/update credential
admin mark-delivered is separate action
student view does not mark delivered
actor/timestamp rendered after delivery
regular certificate UI absent for BNSP
```

### Notification tests

```text theme={null}
in-app notification refresh
push click routes then refetches canonical resource
duplicate push does not duplicate mutation
permission denied/unsupported browser is handled
logout/revoked session cleans push association according to backend/provider flow
sensitive values absent from push payload mocks
```

### Backoffice tests

```text theme={null}
permission-based navigation
manual essay/assignment grading permissions
high-impact confirmation
refund approve/reject requires reason where API requires it
BNSP SLA queue filtering
validation errors preserve inputs
audit-sensitive actions do not echo secrets
```

### Accessibility tests

Critical flows should be usable by keyboard and expose labels/roles for automated accessibility checks.

## 46. Build, Delivery, and Runtime Configuration

The built Angular artifact must not contain backend-only secrets.

CI stages should include at minimum:

```text theme={null}
install with lockfile
lint
type check/build
unit tests
critical component tests
optional E2E on staging/sandbox
artifact build
```

Deployment should make the application version discoverable for support/observability without exposing sensitive build metadata.

When runtime configuration is used, validate required variables during startup and fail clearly if configuration is invalid rather than silently targeting the wrong backend/environment.

***

## 47. Frontend Feature Acceptance Template

Every substantial feature should be reviewed against this template before merge/release.

```text theme={null}
[ ] Route/access rules defined
[ ] API contract identified through public OpenAPI/KrakenD boundary
[ ] No direct internal service/gRPC/RabbitMQ dependency introduced
[ ] Backend-derived business limits/deadlines/capabilities identified
[ ] No PPN/refund/SLA/attempt-limit business constant hardcoded in Angular
[ ] Loading state implemented
[ ] Empty state implemented where applicable
[ ] Domain error states mapped
[ ] Backend validation errors handled
[ ] Duplicate mutation protection handled
[ ] Ambiguous HTTP/network success reconciles canonical business state where needed
[ ] Sensitive data handling reviewed
[ ] Media upload/scan lifecycle handled when files are involved
[ ] Notification/push duplicate behavior reviewed where applicable
[ ] Analytics events defined and scrubbed
[ ] Responsive behavior checked
[ ] Keyboard/accessibility basics checked
[ ] Unit/component tests added
[ ] Critical E2E path updated if applicable
[ ] No business rule duplicated as a client-only source of truth
```

For payment, refund, BNSP, manual grading, certificate, and notification features, reconciliation/security semantics are release blockers rather than polish items.

## 48. Refund Workflow UX

Refund is MVP and is manual/admin reviewed.

Student flow:

```text theme={null}
order detail
→ backend says can_request_refund?
→ show request form when allowed
→ submit once/idempotently
→ show requested / under review / approved / rejected / processing / processed state from backend
```

Rules:

* Current default request window is seven days after payment, but the UI must never calculate eligibility from `payment_date + 7 days` as authority.
* Prefer rendering `refund_request_deadline_at`, `can_request_refund`, and reason codes supplied by backend.
* Request success does not mean refund approval or completed financial reversal.
* Do not revoke/hide course/BNSP access client-side merely because a request was submitted.
* BNSP with `access_delivered` is non-refundable by default; render backend decision/reason rather than reproducing this logic locally.

Admin/backoffice flow:

```text theme={null}
refund queue
→ view order/payment + usage/fulfillment evidence
→ approve or reject with decision reason
→ if approved, follow backend processing status
→ reconcile final order/refund/entitlement state
```

Approval/rejection controls require explicit permissions. UI should display audit metadata returned by backend and prevent accidental double action.

***

## 49. Push + In-App Notification Contract

The locked product SSOT and backend guide require push in MVP alongside in-app and email. Angular must not compensate for notification delivery delays or failures by inventing notification business state.

Rules:

* Backend notification/event state is canonical.
* A browser push payload is a navigation/refetch hint, not the final business state.
* Opening a notification should route using a safe reference and fetch the current resource through KrakenD.
* Duplicate push events must not cause duplicate mutations.
* Handle permission denied, unsupported browser, subscription rotation, logout, and revoked-session cleanup without breaking the in-app notification center.
* Never put LSP credentials, auth tokens, payment secrets, private document URLs, or sensitive financial values in push payloads.
* Do not require WebSocket for MVP notification correctness. Persistent in-app notification state plus refetch/push is sufficient; if one-way live UI updates are later required, evaluate an explicit backend-supported mechanism rather than opening arbitrary service sockets.

## 50. MVP Scope and Open Product Decisions

Web MVP includes student web plus admin/instructor/finance/customer-service operational workflows. Phase 2 features listed in the SSOT are non-blocking.

If a product behavior is not specified by the SSOT/API, Angular must not invent a durable rule. Important unresolved examples currently include:

```text theme={null}
significant course usage threshold for refund decisions
partial-refund business policy
BNSP business-day/holiday calendar mechanics
review release phase
coupon/promotion release phase
optional student BNSP acknowledgement
certificate reissue policy
```

Treat these as `Open Product Decision` until the SSOT is revised. UI can show generic backend-provided capability/reason fields without defining the missing policy itself.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.