# Foundation & App Shell Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Stand up the shared infrastructure for the TeamWallet frontend rebuild — Angular Material theming, PWA scaffolding, environment config, domain models, auth state/API/guards, HTTP interceptors, a working login screen, and the app shell with bottom navigation — so a user can log in against the real backend and land in a navigable (still mostly stubbed) team area.

**Architecture:** Standalone Angular 21 components throughout, no NgModules. App-wide auth state lives in an injectable `AuthStore` built on signals. Guards and HTTP interceptors are plain functions (`CanActivateFn`, `HttpInterceptorFn`). Feature routes are lazy-loaded via `loadComponent`. This plan builds only the shared shell and stub routes; later plans (Auth extras, Team-Select, Overview, Members, Cashbox, More, Public-Team) fill in the real feature screens behind these stubs.

**Tech Stack:** Angular 21 (standalone, zoneless, esbuild builder), Angular Material 21 + CDK, `@angular/service-worker` (PWA), native Angular Signals, Reactive Forms, Vitest (`@angular/build:unit-test`).

## Global Constraints

- Angular 21, standalone components only — no NgModules, no `standalone: true` flag needed (it's the default).
- Angular Material 21 is the UI library; no Bootstrap/PrimeNG.
- State management is native Signals in injectable services — no NgRx.
- Guards and interceptors are functional (`CanActivateFn`, `HttpInterceptorFn`) — no class-based guards/interceptors.
- No i18n layer — German text is hardcoded directly in templates.
- PWA is required (`@angular/service-worker`, installable manifest).
- Backend base URL pattern: `{environment.apiUrl}<controller>/<path>`, e.g. `{apiUrl}auth/email/login`. Dev backend runs at `http://localhost:3999/api/v1/`.
- Tests use Vitest via the `@angular/build:unit-test` builder; `describe`/`it`/`expect`/`vi` are globals (see existing `src/app/app.spec.ts` — no test-framework imports needed).
- File/class naming follows this repo's existing scaffold convention (confirmed via `ng generate` dry-runs): components live in their own folder as `<name>/<name>.ts` (class `PascalCase`, **no** `Component` suffix), services as `<name>.ts` (class **no** `Service` suffix), guards as `<name>-guard.ts` (const `<name>Guard`), interceptors as `<name>-interceptor.ts` (const `<name>Interceptor`).
- This app is zoneless (no `zone.js` dependency) — tests must use `async`/`await` with Promise-returning APIs (e.g. `await router.navigateByUrl(...)`), never `fakeAsync`/`tick`.

---

## File Structure (produced by this plan)

```
src/environments/
  environment.ts                  # prod apiUrl
  environment.development.ts      # dev apiUrl (localhost:3999)
src/app/
  models/
    role.model.ts
    status.model.ts
    user.model.ts
    team-role.model.ts            # TeamRole enum + canBook/canInvite
  core/
    auth/
      auth-store.ts                # signal-based session state
      auth-api.ts                  # login/me HTTP calls
      auth-guard.ts                # protects authenticated routes
      root-redirect-guard.ts       # '/' -> /team-select or /auth/login
    http/
      auth-interceptor.ts          # attaches Bearer token
      error-interceptor.ts         # 401 -> logout + redirect + snackbar
    layout/
      shell/shell.ts               # header + bottom nav, wraps /team/:id/*
      not-found/not-found.ts
  features/
    auth/login/login.ts
    team-select/team-select.ts     # stub, real impl in a later plan
    team/
      overview/overview.ts         # stub
      members/members.ts           # stub
      cashbox/cashbox.ts           # stub
      more/more.ts                 # stub
  app.ts / app.html / app.spec.ts  # trimmed to <router-outlet/>
  app.config.ts                    # Material, PWA, HTTP, router providers
  app.routes.ts
  app.routes.spec.ts
```

---

## Task 1: Angular Material Install & Custom Theme

**Files:**
- Modify: `package.json`, `package-lock.json` (via CLI, not hand-edited)
- Modify: `src/styles.scss`
- Modify: `src/index.html`

**Interfaces:**
- Produces: Material component modules available for import throughout the app (`@angular/material/*`); a global M3 theme using `mat.$green-palette` (primary) and `mat.$orange-palette` (tertiary) to match the "frisch/sportlich" direction from the design spec.

- [ ] **Step 1: Run the Material schematic**

Run in `myteamwallet_frontend_modern/`:

```bash
npx ng add @angular/material --skip-confirmation --theme=custom --typography=true --animations=enabledAsync
```

Expected: `package.json`/`package-lock.json` gain `@angular/material` and `@angular/cdk` (^21.2.x); `src/styles.scss` is rewritten with a `@use '@angular/material' as mat;` block and a `mat.theme(...)` include; `src/index.html` gains Roboto + Material Icons font `<link>` tags.

- [ ] **Step 2: Swap in the custom palette**

Edit `src/styles.scss` — replace the generated `color` block (which defaults to `mat.$azure-palette` / `mat.$blue-palette`) with:

```scss
@include mat.theme(
  (
    color: (
      primary: mat.$green-palette,
      tertiary: mat.$orange-palette,
    ),
    typography: Roboto,
    density: 0,
  )
);
```

Keep the rest of the generated file (the `body` rule with `--mat-sys-*` variables) as-is.

- [ ] **Step 3: Verify the build picks up the theme**

Run: `npm run build -- --configuration development`
Expected: build succeeds with no Sass errors; `dist/` output contains compiled CSS referencing `--mat-sys-primary` custom properties.

- [ ] **Step 4: Commit**

```bash
git add package.json package-lock.json src/styles.scss src/index.html
git commit -m "chore: add Angular Material with a green/orange M3 theme"
```

---

## Task 2: PWA Setup

**Files:**
- Modify: `package.json`, `package-lock.json`, `angular.json`, `src/app/app.config.ts`, `src/index.html`
- Create: `ngsw-config.json`, `public/manifest.webmanifest`, `public/icons/*.png`

**Interfaces:**
- Produces: `provideServiceWorker(...)` registered in `app.config.ts` providers (later tasks append to the same providers array, don't replace it).

- [ ] **Step 1: Run the PWA schematic**

```bash
npx ng add @angular/pwa --skip-confirmation
```

Expected: `@angular/service-worker` added to `package.json`; `ngsw-config.json` created; `public/manifest.webmanifest` + `public/icons/*.png` created; `angular.json`'s production build configuration gains `"serviceWorker": "ngsw-config.json"`; `src/index.html` gains `<link rel="manifest" href="manifest.webmanifest">` and a `<noscript>` tag; `src/app/app.config.ts` gains a `provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode(), registrationStrategy: 'registerWhenStable:30000' })` entry in `providers`.

- [ ] **Step 2: Brand the manifest**

Edit `public/manifest.webmanifest` — change the top of the file to:

```json
{
  "name": "TeamWallet",
  "short_name": "TeamWallet",
  "theme_color": "#2e7d32",
  "background_color": "#ffffff",
  "display": "standalone",
```

Keep `scope`, `start_url`, and the `icons` array exactly as generated.

- [ ] **Step 3: Verify a production build**

Run: `npm run build`
Expected: build succeeds; `dist/myteamwallet_frontend_modern/browser/ngsw.json` exists.

- [ ] **Step 4: Commit**

```bash
git add package.json package-lock.json angular.json ngsw-config.json public/manifest.webmanifest public/icons src/app/app.config.ts src/index.html
git commit -m "chore: add PWA support (service worker, manifest, icons)"
```

---

## Task 3: Environment Configuration

**Files:**
- Create: `src/environments/environment.ts`
- Create: `src/environments/environment.development.ts`
- Modify: `angular.json:51-55` (the build target's `development` configuration)

**Interfaces:**
- Produces: `environment: { production: boolean; apiUrl: string }`, importable as `import { environment } from '../../environments/environment'` (path depends on the importing file's depth — see later tasks for exact relative paths).

- [ ] **Step 1: Create the production environment file**

Create `src/environments/environment.ts`:

```ts
export const environment = {
  production: true,
  apiUrl: 'https://myteamwallet.de/api/v1/',
};
```

- [ ] **Step 2: Create the development environment file**

Create `src/environments/environment.development.ts`:

```ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3999/api/v1/',
};
```

- [ ] **Step 3: Wire up the file replacement**

In `angular.json`, find the `build` target's `configurations.development` block:

```json
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
```

Replace with:

```json
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.development.ts"
                }
              ]
            }
```

- [ ] **Step 4: Verify both configurations build**

Run: `npm run build -- --configuration development` then `npm run build -- --configuration production`
Expected: both succeed with no errors.

- [ ] **Step 5: Commit**

```bash
git add src/environments angular.json
git commit -m "feat: add dev/prod environment configuration with backend apiUrl"
```

---

## Task 4: Core Domain Models & Permission Helpers

**Files:**
- Create: `src/app/models/role.model.ts`
- Create: `src/app/models/status.model.ts`
- Create: `src/app/models/user.model.ts`
- Create: `src/app/models/team-role.model.ts`
- Test: `src/app/models/team-role.model.spec.ts`

**Interfaces:**
- Produces:
  - `interface Role { id: number; name?: string }`
  - `enum RoleId { Admin = 1, User = 2 }`
  - `interface Status { id: number; name?: string }`
  - `interface UserPhoto { id: string; path: string }`
  - `interface User { id: number; email: string | null; firstName: string | null; lastName: string | null; role?: Role | null; status?: Status; photo?: UserPhoto | null }`
  - `enum TeamRole { Player = 1, ScndTreasurer = 2, Captain = 3, Treasurer = 4, Coach = 5 }`
  - `function canBook(role: TeamRole): boolean`
  - `function canInvite(role: TeamRole): boolean`

- [ ] **Step 1: Write the failing test for permission helpers**

Create `src/app/models/team-role.model.spec.ts`:

```ts
import { TeamRole, canBook, canInvite } from './team-role.model';

describe('team-role permissions', () => {
  it('canBook is false for Player', () => {
    expect(canBook(TeamRole.Player)).toBe(false);
  });

  it('canBook is true from ScndTreasurer upwards', () => {
    expect(canBook(TeamRole.ScndTreasurer)).toBe(true);
    expect(canBook(TeamRole.Captain)).toBe(true);
    expect(canBook(TeamRole.Treasurer)).toBe(true);
    expect(canBook(TeamRole.Coach)).toBe(true);
  });

  it('canInvite is false for Player and ScndTreasurer', () => {
    expect(canInvite(TeamRole.Player)).toBe(false);
    expect(canInvite(TeamRole.ScndTreasurer)).toBe(false);
  });

  it('canInvite is true above ScndTreasurer', () => {
    expect(canInvite(TeamRole.Captain)).toBe(true);
    expect(canInvite(TeamRole.Treasurer)).toBe(true);
    expect(canInvite(TeamRole.Coach)).toBe(true);
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/team-role.model.spec.ts'`
Expected: FAIL — `team-role.model.ts` does not exist yet.

- [ ] **Step 3: Create the supporting model files**

Create `src/app/models/role.model.ts`:

```ts
export interface Role {
  id: number;
  name?: string;
}

export enum RoleId {
  Admin = 1,
  User = 2,
}
```

Create `src/app/models/status.model.ts`:

```ts
export interface Status {
  id: number;
  name?: string;
}
```

Create `src/app/models/user.model.ts`:

```ts
import { Role } from './role.model';
import { Status } from './status.model';

export interface UserPhoto {
  id: string;
  path: string;
}

export interface User {
  id: number;
  email: string | null;
  firstName: string | null;
  lastName: string | null;
  role?: Role | null;
  status?: Status;
  photo?: UserPhoto | null;
}
```

- [ ] **Step 4: Implement the permission helpers**

Create `src/app/models/team-role.model.ts`:

```ts
export enum TeamRole {
  Player = 1,
  ScndTreasurer = 2,
  Captain = 3,
  Treasurer = 4,
  Coach = 5,
}

export function canBook(role: TeamRole): boolean {
  return role >= TeamRole.ScndTreasurer;
}

export function canInvite(role: TeamRole): boolean {
  return role > TeamRole.ScndTreasurer;
}
```

- [ ] **Step 5: Run the test to verify it passes**

Run: `npm test -- --include '**/team-role.model.spec.ts'`
Expected: PASS (4 tests).

- [ ] **Step 6: Commit**

```bash
git add src/app/models
git commit -m "feat: add core domain models and team-role permission helpers"
```

---

## Task 5: AuthStore (Session State)

**Files:**
- Create: `src/app/core/auth/auth-store.ts`
- Test: `src/app/core/auth/auth-store.spec.ts`

**Interfaces:**
- Consumes: `User` from `../../models/user.model`.
- Produces: `class AuthStore` (`providedIn: 'root'`) with:
  - `readonly token: Signal<string | null>`
  - `readonly currentUser: Signal<User | null>`
  - `readonly isLoggedIn: Signal<boolean>`
  - `setSession(token: string, user: User): void`
  - `clearSession(): void`
  - persists to `localStorage` under keys `tw_token` / `tw_user`.

- [ ] **Step 1: Write the failing test**

Create `src/app/core/auth/auth-store.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { AuthStore } from './auth-store';

describe('AuthStore', () => {
  beforeEach(() => {
    localStorage.clear();
  });

  it('starts logged out when nothing is stored', () => {
    TestBed.configureTestingModule({});
    const store = TestBed.inject(AuthStore);

    expect(store.isLoggedIn()).toBe(false);
    expect(store.currentUser()).toBeNull();
  });

  it('stores the session and exposes it as logged in', () => {
    TestBed.configureTestingModule({});
    const store = TestBed.inject(AuthStore);
    const user = { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' };

    store.setSession('jwt-token', user);

    expect(store.isLoggedIn()).toBe(true);
    expect(store.token()).toBe('jwt-token');
    expect(store.currentUser()).toEqual(user);
    expect(localStorage.getItem('tw_token')).toBe('jwt-token');
  });

  it('restores the session from localStorage on creation', () => {
    localStorage.setItem('tw_token', 'stored-token');
    localStorage.setItem(
      'tw_user',
      JSON.stringify({ id: 2, email: 'c@d.de', firstName: 'C', lastName: 'D' }),
    );

    TestBed.configureTestingModule({});
    const store = TestBed.inject(AuthStore);

    expect(store.isLoggedIn()).toBe(true);
    expect(store.token()).toBe('stored-token');
    expect(store.currentUser()?.email).toBe('c@d.de');
  });

  it('clears the session', () => {
    TestBed.configureTestingModule({});
    const store = TestBed.inject(AuthStore);
    store.setSession('jwt-token', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });

    store.clearSession();

    expect(store.isLoggedIn()).toBe(false);
    expect(store.currentUser()).toBeNull();
    expect(localStorage.getItem('tw_token')).toBeNull();
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/auth-store.spec.ts'`
Expected: FAIL — `auth-store.ts` does not exist yet.

- [ ] **Step 3: Implement AuthStore**

Create `src/app/core/auth/auth-store.ts`:

```ts
import { Injectable, computed, signal } from '@angular/core';
import { User } from '../../models/user.model';

@Injectable({ providedIn: 'root' })
export class AuthStore {
  private static readonly TOKEN_KEY = 'tw_token';
  private static readonly USER_KEY = 'tw_user';

  private readonly tokenSignal = signal<string | null>(
    localStorage.getItem(AuthStore.TOKEN_KEY),
  );
  private readonly userSignal = signal<User | null>(AuthStore.readStoredUser());

  readonly token = this.tokenSignal.asReadonly();
  readonly currentUser = this.userSignal.asReadonly();
  readonly isLoggedIn = computed(() => this.tokenSignal() !== null);

  setSession(token: string, user: User): void {
    localStorage.setItem(AuthStore.TOKEN_KEY, token);
    localStorage.setItem(AuthStore.USER_KEY, JSON.stringify(user));
    this.tokenSignal.set(token);
    this.userSignal.set(user);
  }

  clearSession(): void {
    localStorage.removeItem(AuthStore.TOKEN_KEY);
    localStorage.removeItem(AuthStore.USER_KEY);
    this.tokenSignal.set(null);
    this.userSignal.set(null);
  }

  private static readStoredUser(): User | null {
    const raw = localStorage.getItem(AuthStore.USER_KEY);
    return raw ? (JSON.parse(raw) as User) : null;
  }
}
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `npm test -- --include '**/auth-store.spec.ts'`
Expected: PASS (4 tests).

- [ ] **Step 5: Commit**

```bash
git add src/app/core/auth/auth-store.ts src/app/core/auth/auth-store.spec.ts
git commit -m "feat: add signal-based AuthStore for session state"
```

---

## Task 6: HTTP Interceptors (Auth + Error)

**Files:**
- Create: `src/app/core/http/auth-interceptor.ts`
- Test: `src/app/core/http/auth-interceptor.spec.ts`
- Create: `src/app/core/http/error-interceptor.ts`
- Test: `src/app/core/http/error-interceptor.spec.ts`
- Modify: `src/app/app.config.ts`

**Interfaces:**
- Consumes: `AuthStore` from `../auth/auth-store`.
- Produces: `authInterceptor: HttpInterceptorFn`, `errorInterceptor: HttpInterceptorFn`, both registered via `provideHttpClient(withInterceptors([authInterceptor, errorInterceptor]))` in `app.config.ts`.

- [ ] **Step 1: Write the failing test for the auth interceptor**

Create `src/app/core/http/auth-interceptor.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { authInterceptor } from './auth-interceptor';
import { AuthStore } from '../auth/auth-store';

describe('authInterceptor', () => {
  let httpMock: HttpTestingController;
  let httpClient: HttpClient;
  let authStore: AuthStore;

  beforeEach(() => {
    localStorage.clear();
    TestBed.configureTestingModule({
      providers: [provideHttpClient(withInterceptors([authInterceptor])), provideHttpClientTesting()],
    });
    httpMock = TestBed.inject(HttpTestingController);
    httpClient = TestBed.inject(HttpClient);
    authStore = TestBed.inject(AuthStore);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('attaches the bearer token when a session exists', () => {
    authStore.setSession('abc123', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });

    httpClient.get('/ping').subscribe();

    const request = httpMock.expectOne('/ping');
    expect(request.request.headers.get('Authorization')).toBe('Bearer abc123');
    request.flush({});
  });

  it('does not attach a header when no session exists', () => {
    httpClient.get('/ping').subscribe();

    const request = httpMock.expectOne('/ping');
    expect(request.request.headers.has('Authorization')).toBe(false);
    request.flush({});
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/auth-interceptor.spec.ts'`
Expected: FAIL — `auth-interceptor.ts` does not exist yet.

- [ ] **Step 3: Implement the auth interceptor**

Create `src/app/core/http/auth-interceptor.ts`:

```ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthStore } from '../auth/auth-store';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authStore = inject(AuthStore);
  const token = authStore.token();

  if (!token) {
    return next(req);
  }

  return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `npm test -- --include '**/auth-interceptor.spec.ts'`
Expected: PASS (2 tests).

- [ ] **Step 5: Write the failing test for the error interceptor**

Create `src/app/core/http/error-interceptor.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter, Router } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { errorInterceptor } from './error-interceptor';
import { AuthStore } from '../auth/auth-store';

describe('errorInterceptor', () => {
  let httpMock: HttpTestingController;
  let httpClient: HttpClient;
  let authStore: AuthStore;
  let router: Router;

  beforeEach(() => {
    localStorage.clear();
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(withInterceptors([errorInterceptor])),
        provideHttpClientTesting(),
        provideRouter([]),
        provideAnimationsAsync(),
      ],
    });
    httpMock = TestBed.inject(HttpTestingController);
    httpClient = TestBed.inject(HttpClient);
    authStore = TestBed.inject(AuthStore);
    router = TestBed.inject(Router);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('clears the session and redirects to login on a 401 response', () => {
    authStore.setSession('abc123', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });
    const navigateSpy = vi.spyOn(router, 'navigate');

    httpClient.get('/secure').subscribe({ error: () => undefined });
    httpMock.expectOne('/secure').flush('unauthorized', { status: 401, statusText: 'Unauthorized' });

    expect(authStore.isLoggedIn()).toBe(false);
    expect(navigateSpy).toHaveBeenCalledWith(['/auth/login']);
  });

  it('leaves other error statuses untouched', () => {
    httpClient.get('/secure').subscribe({ error: () => undefined });
    httpMock.expectOne('/secure').flush('server error', { status: 500, statusText: 'Server Error' });

    expect(authStore.isLoggedIn()).toBe(false);
  });
});
```

- [ ] **Step 6: Run the test to verify it fails**

Run: `npm test -- --include '**/error-interceptor.spec.ts'`
Expected: FAIL — `error-interceptor.ts` does not exist yet.

- [ ] **Step 7: Implement the error interceptor**

Create `src/app/core/http/error-interceptor.ts`:

```ts
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
import { AuthStore } from '../auth/auth-store';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  const authStore = inject(AuthStore);
  const router = inject(Router);
  const snackBar = inject(MatSnackBar);

  return next(req).pipe(
    catchError((error: unknown) => {
      if (error instanceof HttpErrorResponse && error.status === 401) {
        authStore.clearSession();
        void router.navigate(['/auth/login']);
        snackBar.open('Sitzung abgelaufen. Bitte erneut anmelden.', 'OK', { duration: 5000 });
      }
      return throwError(() => error);
    }),
  );
};
```

- [ ] **Step 8: Run the test to verify it passes**

Run: `npm test -- --include '**/error-interceptor.spec.ts'`
Expected: PASS (2 tests).

- [ ] **Step 9: Wire the interceptors, HttpClient, and animations into app.config.ts**

`ng add @angular/material` (Task 1) does **not** register `provideAnimationsAsync()` in `app.config.ts` — it only rewrites `styles.scss`/`index.html`. Verify this is still missing (`grep -n "provideAnimationsAsync" src/app/app.config.ts` should find nothing), then edit `src/app/app.config.ts` to its final Foundation-plan state, adding it together with the HTTP client:

```ts
import { ApplicationConfig, isDevMode, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideServiceWorker } from '@angular/service-worker';

import { routes } from './app.routes';
import { authInterceptor } from './core/http/auth-interceptor';
import { errorInterceptor } from './core/http/error-interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
    provideAnimationsAsync(),
    provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
};
```

- [ ] **Step 10: Verify the full build still succeeds**

Run: `npm run build -- --configuration development`
Expected: no compile errors.

- [ ] **Step 11: Commit**

```bash
git add src/app/core/http src/app/app.config.ts
git commit -m "feat: add auth and error HTTP interceptors"
```

---

## Task 7: AuthApi (Login + Current User)

**Files:**
- Create: `src/app/core/auth/auth-api.ts`
- Test: `src/app/core/auth/auth-api.spec.ts`

**Interfaces:**
- Consumes: `environment` from `../../../environments/environment`, `User` from `../../models/user.model`.
- Produces: `interface LoginResponse { token: string; user: User }`, `class AuthApi` (`providedIn: 'root'`) with:
  - `login(email: string, password: string): Observable<LoginResponse>` → `POST {apiUrl}auth/email/login`
  - `me(): Observable<User>` → `GET {apiUrl}auth/me`

- [ ] **Step 1: Write the failing test**

Create `src/app/core/auth/auth-api.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { AuthApi } from './auth-api';
import { environment } from '../../../environments/environment';
import { User } from '../../models/user.model';

describe('AuthApi', () => {
  let service: AuthApi;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [provideHttpClient(), provideHttpClientTesting()],
    });
    service = TestBed.inject(AuthApi);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('posts credentials to the login endpoint', () => {
    const user: User = { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' };

    service.login('a@b.de', 'secret').subscribe((response) => {
      expect(response).toEqual({ token: 'jwt-token', user });
    });

    const request = httpMock.expectOne(`${environment.apiUrl}auth/email/login`);
    expect(request.request.method).toBe('POST');
    expect(request.request.body).toEqual({ email: 'a@b.de', password: 'secret' });
    request.flush({ token: 'jwt-token', user });
  });

  it('fetches the current user from the me endpoint', () => {
    const user: User = { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' };

    service.me().subscribe((response) => {
      expect(response).toEqual(user);
    });

    const request = httpMock.expectOne(`${environment.apiUrl}auth/me`);
    expect(request.request.method).toBe('GET');
    request.flush(user);
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/auth-api.spec.ts'`
Expected: FAIL — `auth-api.ts` does not exist yet.

- [ ] **Step 3: Implement AuthApi**

Create `src/app/core/auth/auth-api.ts`:

```ts
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { User } from '../../models/user.model';

export interface LoginResponse {
  token: string;
  user: User;
}

@Injectable({ providedIn: 'root' })
export class AuthApi {
  private readonly http = inject(HttpClient);
  private readonly baseUrl = `${environment.apiUrl}auth`;

  login(email: string, password: string): Observable<LoginResponse> {
    return this.http.post<LoginResponse>(`${this.baseUrl}/email/login`, { email, password });
  }

  me(): Observable<User> {
    return this.http.get<User>(`${this.baseUrl}/me`);
  }
}
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `npm test -- --include '**/auth-api.spec.ts'`
Expected: PASS (2 tests).

- [ ] **Step 5: Commit**

```bash
git add src/app/core/auth/auth-api.ts src/app/core/auth/auth-api.spec.ts
git commit -m "feat: add AuthApi for login and current-user requests"
```

---

## Task 8: Guards (authGuard + rootRedirectGuard)

**Files:**
- Create: `src/app/core/auth/auth-guard.ts`
- Test: `src/app/core/auth/auth-guard.spec.ts`
- Create: `src/app/core/auth/root-redirect-guard.ts`
- Test: `src/app/core/auth/root-redirect-guard.spec.ts`

**Interfaces:**
- Consumes: `AuthStore` from `./auth-store`.
- Produces: `authGuard: CanActivateFn` (blocks unauthenticated access, redirects to `/auth/login`), `rootRedirectGuard: CanActivateFn` (always redirects `/` to `/team-select` or `/auth/login` based on session state).

- [ ] **Step 1: Write the failing test for authGuard**

Create `src/app/core/auth/auth-guard.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideRouter, Router } from '@angular/router';
import { authGuard } from './auth-guard';
import { AuthStore } from './auth-store';

describe('authGuard', () => {
  beforeEach(() => {
    localStorage.clear();
    TestBed.configureTestingModule({ providers: [provideRouter([])] });
  });

  it('allows navigation when logged in', () => {
    const authStore = TestBed.inject(AuthStore);
    authStore.setSession('token', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });

    const result = TestBed.runInInjectionContext(() => authGuard({} as never, {} as never));

    expect(result).toBe(true);
  });

  it('redirects to login when logged out', () => {
    const router = TestBed.inject(Router);

    const result = TestBed.runInInjectionContext(() => authGuard({} as never, {} as never));

    expect(result).toEqual(router.parseUrl('/auth/login'));
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/auth-guard.spec.ts'`
Expected: FAIL — `auth-guard.ts` does not exist yet.

- [ ] **Step 3: Implement authGuard**

Create `src/app/core/auth/auth-guard.ts`:

```ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthStore } from './auth-store';

export const authGuard: CanActivateFn = () => {
  const authStore = inject(AuthStore);
  const router = inject(Router);

  return authStore.isLoggedIn() ? true : router.parseUrl('/auth/login');
};
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `npm test -- --include '**/auth-guard.spec.ts'`
Expected: PASS (2 tests).

- [ ] **Step 5: Write the failing test for rootRedirectGuard**

Create `src/app/core/auth/root-redirect-guard.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideRouter, Router } from '@angular/router';
import { rootRedirectGuard } from './root-redirect-guard';
import { AuthStore } from './auth-store';

describe('rootRedirectGuard', () => {
  beforeEach(() => {
    localStorage.clear();
    TestBed.configureTestingModule({ providers: [provideRouter([])] });
  });

  it('redirects to team-select when logged in', () => {
    const authStore = TestBed.inject(AuthStore);
    const router = TestBed.inject(Router);
    authStore.setSession('token', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });

    const result = TestBed.runInInjectionContext(() => rootRedirectGuard({} as never, {} as never));

    expect(result).toEqual(router.parseUrl('/team-select'));
  });

  it('redirects to login when logged out', () => {
    const router = TestBed.inject(Router);

    const result = TestBed.runInInjectionContext(() => rootRedirectGuard({} as never, {} as never));

    expect(result).toEqual(router.parseUrl('/auth/login'));
  });
});
```

- [ ] **Step 6: Run the test to verify it fails**

Run: `npm test -- --include '**/root-redirect-guard.spec.ts'`
Expected: FAIL — `root-redirect-guard.ts` does not exist yet.

- [ ] **Step 7: Implement rootRedirectGuard**

Create `src/app/core/auth/root-redirect-guard.ts`:

```ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthStore } from './auth-store';

export const rootRedirectGuard: CanActivateFn = () => {
  const authStore = inject(AuthStore);
  const router = inject(Router);

  return router.parseUrl(authStore.isLoggedIn() ? '/team-select' : '/auth/login');
};
```

- [ ] **Step 8: Run the test to verify it passes**

Run: `npm test -- --include '**/root-redirect-guard.spec.ts'`
Expected: PASS (2 tests).

- [ ] **Step 9: Commit**

```bash
git add src/app/core/auth/auth-guard.ts src/app/core/auth/auth-guard.spec.ts src/app/core/auth/root-redirect-guard.ts src/app/core/auth/root-redirect-guard.spec.ts
git commit -m "feat: add authGuard and rootRedirectGuard"
```

---

## Task 9: Login Feature

**Files:**
- Create: `src/app/features/auth/login/login.ts`
- Create: `src/app/features/auth/login/login.html`
- Create: `src/app/features/auth/login/login.scss`
- Test: `src/app/features/auth/login/login.spec.ts`

**Interfaces:**
- Consumes: `AuthApi` from `../../../core/auth/auth-api`, `AuthStore` from `../../../core/auth/auth-store`.
- Produces: `class Login`, selector `app-login`, standalone component with a reactive email/password form; on success calls `authStore.setSession(token, user)` and navigates to `/team-select`; on failure sets a visible error message.

- [ ] **Step 1: Write the failing test**

Create `src/app/features/auth/login/login.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter, Router } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { Login } from './login';
import { environment } from '../../../../environments/environment';
import { AuthStore } from '../../../core/auth/auth-store';

describe('Login', () => {
  let httpMock: HttpTestingController;
  let router: Router;
  let authStore: AuthStore;

  beforeEach(async () => {
    localStorage.clear();
    await TestBed.configureTestingModule({
      imports: [Login],
      providers: [provideHttpClient(), provideHttpClientTesting(), provideRouter([]), provideAnimationsAsync()],
    }).compileComponents();

    httpMock = TestBed.inject(HttpTestingController);
    router = TestBed.inject(Router);
    authStore = TestBed.inject(AuthStore);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('logs in and navigates to team-select on success', () => {
    const fixture = TestBed.createComponent(Login);
    const navigateSpy = vi.spyOn(router, 'navigate');
    fixture.componentInstance['form'].setValue({ email: 'a@b.de', password: 'secret' });
    fixture.componentInstance['onSubmit']();

    const user = { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' };
    httpMock.expectOne(`${environment.apiUrl}auth/email/login`).flush({ token: 'jwt-token', user });

    expect(authStore.isLoggedIn()).toBe(true);
    expect(navigateSpy).toHaveBeenCalledWith(['/team-select']);
  });

  it('shows an error message when login fails', () => {
    const fixture = TestBed.createComponent(Login);
    fixture.componentInstance['form'].setValue({ email: 'a@b.de', password: 'wrong' });
    fixture.componentInstance['onSubmit']();

    httpMock
      .expectOne(`${environment.apiUrl}auth/email/login`)
      .flush('unauthorized', { status: 401, statusText: 'Unauthorized' });

    expect(fixture.componentInstance['errorMessage']()).toBe(
      'Anmeldung fehlgeschlagen. Bitte E-Mail und Passwort prüfen.',
    );
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/login.spec.ts'`
Expected: FAIL — `login.ts` does not exist yet.

- [ ] **Step 3: Implement the Login component**

Create `src/app/features/auth/login/login.ts`:

```ts
import { Component, inject, signal } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { AuthApi } from '../../../core/auth/auth-api';
import { AuthStore } from '../../../core/auth/auth-store';

@Component({
  selector: 'app-login',
  imports: [
    ReactiveFormsModule,
    MatCardModule,
    MatFormFieldModule,
    MatInputModule,
    MatButtonModule,
    MatProgressSpinnerModule,
  ],
  templateUrl: './login.html',
  styleUrl: './login.scss',
})
export class Login {
  private readonly formBuilder = inject(FormBuilder);
  private readonly authApi = inject(AuthApi);
  private readonly authStore = inject(AuthStore);
  private readonly router = inject(Router);

  protected readonly isSubmitting = signal(false);
  protected readonly errorMessage = signal<string | null>(null);

  protected readonly form = this.formBuilder.nonNullable.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required]],
  });

  protected onSubmit(): void {
    if (this.form.invalid || this.isSubmitting()) {
      return;
    }

    this.isSubmitting.set(true);
    this.errorMessage.set(null);
    const { email, password } = this.form.getRawValue();

    this.authApi.login(email, password).subscribe({
      next: ({ token, user }) => {
        this.authStore.setSession(token, user);
        this.isSubmitting.set(false);
        void this.router.navigate(['/team-select']);
      },
      error: () => {
        this.isSubmitting.set(false);
        this.errorMessage.set('Anmeldung fehlgeschlagen. Bitte E-Mail und Passwort prüfen.');
      },
    });
  }
}
```

Create `src/app/features/auth/login/login.html`:

```html
<div class="login-page">
  <mat-card class="login-card">
    <mat-card-header>
      <mat-card-title>TeamWallet</mat-card-title>
      <mat-card-subtitle>Anmelden</mat-card-subtitle>
    </mat-card-header>

    <mat-card-content>
      <form [formGroup]="form" (ngSubmit)="onSubmit()">
        <mat-form-field appearance="outline">
          <mat-label>E-Mail</mat-label>
          <input matInput type="email" formControlName="email" autocomplete="email" />
        </mat-form-field>

        <mat-form-field appearance="outline">
          <mat-label>Passwort</mat-label>
          <input matInput type="password" formControlName="password" autocomplete="current-password" />
        </mat-form-field>

        @if (errorMessage()) {
          <p class="login-error">{{ errorMessage() }}</p>
        }

        <button mat-flat-button color="primary" type="submit" [disabled]="form.invalid || isSubmitting()">
          @if (isSubmitting()) {
            <mat-spinner diameter="20" />
          } @else {
            Anmelden
          }
        </button>
      </form>
    </mat-card-content>
  </mat-card>
</div>
```

Create `src/app/features/auth/login/login.scss`:

```scss
.login-page {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100dvh;
  padding: 1rem;
}

.login-card {
  width: 100%;
  max-width: 360px;

  form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
}

.login-error {
  color: var(--mat-sys-error);
  margin: 0;
}
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `npm test -- --include '**/login.spec.ts'`
Expected: PASS (2 tests).

- [ ] **Step 5: Commit**

```bash
git add src/app/features/auth/login
git commit -m "feat: add login screen"
```

---

## Task 10: App Shell (Header + Bottom Navigation)

**Files:**
- Create: `src/app/core/layout/shell/shell.ts`
- Create: `src/app/core/layout/shell/shell.html`
- Create: `src/app/core/layout/shell/shell.scss`
- Test: `src/app/core/layout/shell/shell.spec.ts`
- Create: `src/app/core/layout/not-found/not-found.ts`
- Test: `src/app/core/layout/not-found/not-found.spec.ts`

**Interfaces:**
- Produces: `class Shell` (selector `app-shell`) rendering a header, a `<router-outlet>` for the active team route, and a 4-item bottom nav (`overview`/`members`/`cashbox`/`more`, relative `routerLink`s). `class NotFound` (selector `app-not-found`) for the wildcard route.

- [ ] **Step 1: Write the failing test for Shell**

Create `src/app/core/layout/shell/shell.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { Shell } from './shell';

describe('Shell', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [Shell],
      providers: [provideRouter([])],
    }).compileComponents();
  });

  it('renders the bottom navigation with four tabs', () => {
    const fixture = TestBed.createComponent(Shell);
    fixture.detectChanges();

    const links = fixture.nativeElement.querySelectorAll('.shell-bottom-nav__item');
    expect(links.length).toBe(4);
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/shell.spec.ts'`
Expected: FAIL — `shell.ts` does not exist yet.

- [ ] **Step 3: Implement Shell**

Create `src/app/core/layout/shell/shell.ts`:

```ts
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';

@Component({
  selector: 'app-shell',
  imports: [RouterOutlet, RouterLink, RouterLinkActive, MatToolbarModule, MatIconModule],
  templateUrl: './shell.html',
  styleUrl: './shell.scss',
})
export class Shell {}
```

Create `src/app/core/layout/shell/shell.html`:

```html
<mat-toolbar color="primary" class="shell-header">
  <span>TeamWallet</span>
</mat-toolbar>

<main class="shell-content">
  <router-outlet />
</main>

<nav class="shell-bottom-nav">
  <a routerLink="overview" routerLinkActive="active" class="shell-bottom-nav__item">
    <mat-icon>account_balance_wallet</mat-icon>
    <span>Übersicht</span>
  </a>
  <a routerLink="members" routerLinkActive="active" class="shell-bottom-nav__item">
    <mat-icon>groups</mat-icon>
    <span>Mitglieder</span>
  </a>
  <a routerLink="cashbox" routerLinkActive="active" class="shell-bottom-nav__item">
    <mat-icon>payments</mat-icon>
    <span>Kasse</span>
  </a>
  <a routerLink="more" routerLinkActive="active" class="shell-bottom-nav__item">
    <mat-icon>more_horiz</mat-icon>
    <span>Mehr</span>
  </a>
</nav>
```

Create `src/app/core/layout/shell/shell.scss`:

```scss
:host {
  display: flex;
  flex-direction: column;
  height: 100dvh;
}

.shell-content {
  flex: 1;
  overflow-y: auto;
}

.shell-bottom-nav {
  display: flex;
  border-top: 1px solid var(--mat-sys-outline-variant);
  background: var(--mat-sys-surface);

  &__item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    padding: 0.5rem 0;
    color: var(--mat-sys-on-surface-variant);
    text-decoration: none;
    font-size: 0.75rem;

    &.active {
      color: var(--mat-sys-primary);
    }
  }
}
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `npm test -- --include '**/shell.spec.ts'`
Expected: PASS (1 test).

- [ ] **Step 5: Write the failing test for NotFound**

Create `src/app/core/layout/not-found/not-found.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { NotFound } from './not-found';

describe('NotFound', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [NotFound],
      providers: [provideRouter([])],
    }).compileComponents();
  });

  it('renders a not-found message', () => {
    const fixture = TestBed.createComponent(NotFound);
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('h1')?.textContent).toContain('Seite nicht gefunden');
  });
});
```

- [ ] **Step 6: Run the test to verify it fails**

Run: `npm test -- --include '**/not-found.spec.ts'`
Expected: FAIL — `not-found.ts` does not exist yet.

- [ ] **Step 7: Implement NotFound**

Create `src/app/core/layout/not-found/not-found.ts`:

```ts
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-not-found',
  imports: [RouterLink],
  template: `
    <div class="not-found">
      <h1>Seite nicht gefunden</h1>
      <a routerLink="/">Zurück zur Startseite</a>
    </div>
  `,
})
export class NotFound {}
```

- [ ] **Step 8: Run the test to verify it passes**

Run: `npm test -- --include '**/not-found.spec.ts'`
Expected: PASS (1 test).

- [ ] **Step 9: Commit**

```bash
git add src/app/core/layout
git commit -m "feat: add app shell with bottom navigation and not-found page"
```

---

## Task 11: Stub Feature Screens (Overview, Members, Cashbox, More, Team-Select)

**Files:**
- Create: `src/app/features/team/overview/overview.ts` + `overview.spec.ts`
- Create: `src/app/features/team/members/members.ts` + `members.spec.ts`
- Create: `src/app/features/team/cashbox/cashbox.ts` + `cashbox.spec.ts`
- Create: `src/app/features/team/more/more.ts` + `more.spec.ts`
- Create: `src/app/features/team-select/team-select.ts` + `team-select.spec.ts`

**Interfaces:**
- Produces: five minimal standalone components, each rendering a single `<h1>` heading. These are intentional placeholders that later plans (Team-Select & Team Store, Overview, Members, Cashbox, More) will replace with real functionality — this task only proves the shell/routing wiring works end-to-end.

- [ ] **Step 1: Write the failing tests for all five stubs**

Create `src/app/features/team/overview/overview.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { Overview } from './overview';

describe('Overview', () => {
  it('renders the overview heading', async () => {
    await TestBed.configureTestingModule({ imports: [Overview] }).compileComponents();
    const fixture = TestBed.createComponent(Overview);
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('h1')?.textContent).toContain('Übersicht');
  });
});
```

Create `src/app/features/team/members/members.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { Members } from './members';

describe('Members', () => {
  it('renders the members heading', async () => {
    await TestBed.configureTestingModule({ imports: [Members] }).compileComponents();
    const fixture = TestBed.createComponent(Members);
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('h1')?.textContent).toContain('Mitglieder');
  });
});
```

Create `src/app/features/team/cashbox/cashbox.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { Cashbox } from './cashbox';

describe('Cashbox', () => {
  it('renders the cashbox heading', async () => {
    await TestBed.configureTestingModule({ imports: [Cashbox] }).compileComponents();
    const fixture = TestBed.createComponent(Cashbox);
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('h1')?.textContent).toContain('Kasse');
  });
});
```

Create `src/app/features/team/more/more.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { More } from './more';

describe('More', () => {
  it('renders the more heading', async () => {
    await TestBed.configureTestingModule({ imports: [More] }).compileComponents();
    const fixture = TestBed.createComponent(More);
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('h1')?.textContent).toContain('Mehr');
  });
});
```

Create `src/app/features/team-select/team-select.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { TeamSelect } from './team-select';

describe('TeamSelect', () => {
  it('renders the team-select heading', async () => {
    await TestBed.configureTestingModule({ imports: [TeamSelect] }).compileComponents();
    const fixture = TestBed.createComponent(TeamSelect);
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('h1')?.textContent).toContain('Team auswählen');
  });
});
```

- [ ] **Step 2: Run the tests to verify they fail**

Run: `npm test -- --include '**/overview.spec.ts' --include '**/members.spec.ts' --include '**/cashbox.spec.ts' --include '**/more.spec.ts' --include '**/team-select.spec.ts'`
Expected: FAIL — none of the five component files exist yet.

- [ ] **Step 3: Implement the five stub components**

Create `src/app/features/team/overview/overview.ts`:

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-overview',
  template: `<h1>Übersicht</h1>`,
})
export class Overview {}
```

Create `src/app/features/team/members/members.ts`:

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-members',
  template: `<h1>Mitglieder</h1>`,
})
export class Members {}
```

Create `src/app/features/team/cashbox/cashbox.ts`:

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-cashbox',
  template: `<h1>Kasse</h1>`,
})
export class Cashbox {}
```

Create `src/app/features/team/more/more.ts`:

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-more',
  template: `<h1>Mehr</h1>`,
})
export class More {}
```

Create `src/app/features/team-select/team-select.ts`:

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-team-select',
  template: `<h1>Team auswählen</h1>`,
})
export class TeamSelect {}
```

- [ ] **Step 4: Run the tests to verify they pass**

Run: `npm test -- --include '**/overview.spec.ts' --include '**/members.spec.ts' --include '**/cashbox.spec.ts' --include '**/more.spec.ts' --include '**/team-select.spec.ts'`
Expected: PASS (5 tests).

- [ ] **Step 5: Commit**

```bash
git add src/app/features/team src/app/features/team-select
git commit -m "feat: add stub screens for overview, members, cashbox, more, team-select"
```

---

## Task 12: Root Routing Wiring

**Files:**
- Modify: `src/app/app.routes.ts`
- Create: `src/app/app.routes.spec.ts`
- Modify: `src/app/app.ts`
- Modify: `src/app/app.html`
- Modify: `src/app/app.spec.ts`

**Interfaces:**
- Consumes: every component/guard produced in Tasks 8–11.
- Produces: the final `routes: Routes` array; `App` trimmed to a bare `<router-outlet>` shell (the Angular welcome-page placeholder markup is removed).

- [ ] **Step 1: Write the failing routing integration test**

Create `src/app/app.routes.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideRouter, Router } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { routes } from './app.routes';
import { AuthStore } from './core/auth/auth-store';

describe('app routing', () => {
  let router: Router;
  let authStore: AuthStore;

  beforeEach(() => {
    localStorage.clear();
    TestBed.configureTestingModule({
      providers: [provideRouter(routes), provideHttpClient(), provideHttpClientTesting(), provideAnimationsAsync()],
    });
    router = TestBed.inject(Router);
    authStore = TestBed.inject(AuthStore);
  });

  it('redirects the root path to login when logged out', async () => {
    await router.navigateByUrl('/');
    expect(router.url).toBe('/auth/login');
  });

  it('redirects a protected team route to login when logged out', async () => {
    await router.navigateByUrl('/team/1/overview');
    expect(router.url).toBe('/auth/login');
  });

  it('redirects the root path to team-select when logged in', async () => {
    authStore.setSession('token', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });
    await router.navigateByUrl('/');
    expect(router.url).toBe('/team-select');
  });

  it('redirects the bare team route to its overview child', async () => {
    authStore.setSession('token', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });
    await router.navigateByUrl('/team/1');
    expect(router.url).toBe('/team/1/overview');
  });

  it('falls back to the not-found route for unknown paths', async () => {
    await router.navigateByUrl('/does-not-exist');
    expect(router.url).toBe('/does-not-exist');
  });
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `npm test -- --include '**/app.routes.spec.ts'`
Expected: FAIL — `app.routes.ts` still exports an empty array.

- [ ] **Step 3: Implement the routes**

Replace the contents of `src/app/app.routes.ts`:

```ts
import { Routes } from '@angular/router';
import { authGuard } from './core/auth/auth-guard';
import { rootRedirectGuard } from './core/auth/root-redirect-guard';
import { Shell } from './core/layout/shell/shell';

export const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    canActivate: [rootRedirectGuard],
    children: [],
  },
  {
    path: 'auth/login',
    loadComponent: () => import('./features/auth/login/login').then((m) => m.Login),
  },
  {
    path: 'team-select',
    canActivate: [authGuard],
    loadComponent: () => import('./features/team-select/team-select').then((m) => m.TeamSelect),
  },
  {
    path: 'team/:id',
    canActivate: [authGuard],
    component: Shell,
    children: [
      { path: '', pathMatch: 'full', redirectTo: 'overview' },
      {
        path: 'overview',
        loadComponent: () => import('./features/team/overview/overview').then((m) => m.Overview),
      },
      {
        path: 'members',
        loadComponent: () => import('./features/team/members/members').then((m) => m.Members),
      },
      {
        path: 'cashbox',
        loadComponent: () => import('./features/team/cashbox/cashbox').then((m) => m.Cashbox),
      },
      {
        path: 'more',
        loadComponent: () => import('./features/team/more/more').then((m) => m.More),
      },
    ],
  },
  {
    path: '**',
    loadComponent: () => import('./core/layout/not-found/not-found').then((m) => m.NotFound),
  },
];
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `npm test -- --include '**/app.routes.spec.ts'`
Expected: PASS (5 tests).

- [ ] **Step 5: Trim the root App component down to the router outlet**

Replace `src/app/app.html` entirely with:

```html
<router-outlet />
```

Replace `src/app/app.ts`:

```ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.html',
  styleUrl: './app.scss',
})
export class App {}
```

Replace `src/app/app.spec.ts`:

```ts
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { App } from './app';

describe('App', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [App],
      providers: [provideRouter([])],
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(App);
    expect(fixture.componentInstance).toBeTruthy();
  });
});
```

- [ ] **Step 6: Run the full test suite**

Run: `npm test -- --include '**/app.spec.ts' --include '**/app.routes.spec.ts'`
Expected: PASS.

- [ ] **Step 7: Commit**

```bash
git add src/app/app.routes.ts src/app/app.routes.spec.ts src/app/app.ts src/app/app.html src/app/app.spec.ts
git commit -m "feat: wire root routing (auth, team-select, team shell, not-found)"
```

---

## Task 13: Final Verification

**Files:** none (verification only)

- [ ] **Step 1: Run the full unit test suite**

Run: `npm test`
Expected: all tests pass, no failures.

- [ ] **Step 2: Run a production build**

Run: `npm run build`
Expected: build succeeds; `dist/myteamwallet_frontend_modern/browser/` contains `index.html`, `ngsw.json`, and hashed JS/CSS bundles.

- [ ] **Step 3: Check formatting**

Run: `npx prettier --check "src/**/*.{ts,html,scss}"`
Expected: no formatting issues. If issues are reported, run `npx prettier --write "src/**/*.{ts,html,scss}"` and re-check.

- [ ] **Step 4: Manual smoke check (requires the backend running on localhost:3999)**

Run: `npm start`, open `http://localhost:4200`. Expected: redirected to `/auth/login`; a valid backend login redirects to `/team-select` showing "Team auswählen"; manually navigating to `/team/1/overview` shows the app shell with header + bottom nav, and the four tabs switch between the stub screens.

- [ ] **Step 5: Commit (only if Steps 1–3 required fixes)**

```bash
git add -A
git commit -m "chore: fix formatting/build issues found during final verification"
```
