Add the billing invoice list

Angular · Angular · advanced · modification

Adds the billing section. BillingService wraps the billing gateway's invoice endpoint, resolving it through the configured `apiOrigin` so the service targets the right gateway in every environment, and InvoiceListComponent renders the result with loading, error, and empty states. `auth.interceptor.ts` is shown unchanged for reviewer context.

The app is served from app.example.com; the proxy routes same-origin `/api/*` requests to the gateway, and `environment.apiOrigin` is `https://api.example.com` in every environment. The billing API treats a request without `Authorization`/`X-Org-Id` as the anonymous org and returns `200` with an empty invoice list rather than a `401`.

Requirements

Files touched

--- src/app/core/auth.interceptor.ts
 import { HttpInterceptorFn } from '@angular/common/http';
 import { inject } from '@angular/core';
 
 import { SessionService } from './session.service';
 
 /**
  * Attaches the signed-in org's credentials to first-party API calls.
  *
  * SECURITY BOUNDARY (docs/security.md): credentials are attached only to
  * relative URLs under `/api/` — same-origin requests that the proxy routes
  * to the gateway. Absolute URLs pass through untouched so the token and org
  * id can never be sent to a third-party host. Do not widen this condition.
  */
 export const authInterceptor: HttpInterceptorFn = (req, next) => {
   const session = inject(SessionService);
   const token = session.token();
   const orgId = session.orgId();
   if (token && orgId && req.url.startsWith('/api/')) {
     return next(
       req.clone({
         setHeaders: {
           Authorization: `Bearer ${token}`,
           'X-Org-Id': orgId,
         },
       }),
     );
   }
   return next(req);
 };

Review this PR

Angular practice