- New VerantwortlicheInvite model: LT-issued invites so a person can register as Gemeinde Verantwortliche(r) for a specific Gemeinde, skipping the self-registration approval step. - Wahl/Workshop/Teilnehmer gain phase support (phasenAnzahl, beschreibung), mirroring the WP plugin's multi-phase elections. Teilnehmer unique constraint now scoped per phase. - Auth: team login + guest auth adjustments, spec coverage. - sync.service.ts: register VerantwortlicheInvite as a synced model. - wahl.service.ts: submitTeilnehmer updated for the new phase-scoped unique key. - client: login/home screen rework, new theme.dart, FCM web tweaks. - .gitignore: ignore .DS_Store. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
97 lines
5.0 KiB
Markdown
97 lines
5.0 KiB
Markdown
# KC-App client (Flutter)
|
|
|
|
Single Flutter codebase for the KC-App platform. **Web** is the only target
|
|
enabled so far (`flutter config --enable-web`); Android/iOS/desktop can be
|
|
added later with `flutter create --platforms=...` in this directory — the
|
|
`lib/` code is platform-agnostic.
|
|
|
|
## Run
|
|
|
|
The Authentik redirect URI is `http://localhost:3000/v1/auth/callback`, so
|
|
the app must be reached on `:3000` — i.e. served by the backend, not `flutter
|
|
run`'s own dev server. Build it and let NestJS serve it:
|
|
|
|
```bash
|
|
flutter pub get
|
|
flutter build web # backend serves client/app/build/web at /
|
|
# then run the backend (npm run start:dev in ../../backend) and open :3000
|
|
```
|
|
|
|
For pure UI work without the OIDC flow, `flutter run -d chrome
|
|
--dart-define=API_BASE=http://localhost:3000/api` still works (guest / local
|
|
Teamer login only).
|
|
|
|
### Dart-defines
|
|
|
|
| define | default |
|
|
|---|---|
|
|
| `API_BASE` | `http://localhost:3000/api` |
|
|
| `OIDC_ISSUER` | `https://sso.konfi-castle.com/application/o/konfi-castle-app/` |
|
|
| `OIDC_CLIENT_ID` | the konfi-castle public client id |
|
|
| `OIDC_REDIRECT_URI` | `http://localhost:3000/v1/auth/callback` |
|
|
|
|
## What's implemented
|
|
|
|
- **Login** (`lib/screens/login_screen.dart`) — three tabs:
|
|
- *Konfi / Gast*: KC invite code + first/last name → `POST /auth/guest`.
|
|
- *Leitungsteam / Verantwortliche*: "Mit Konfi-Castle-ID anmelden" starts
|
|
the Authentik **Authorization Code + PKCE** flow (`lib/oidc.dart`);
|
|
below it, the local Gemeinde-Teamer password form
|
|
(`POST /auth/team-login`).
|
|
- *Einladung*: redeem a Teamer invite token → `POST /auth/teamer/register`.
|
|
- OIDC: discovery + S256 challenge, `?code=` handled on bootstrap, access +
|
|
refresh token persisted (`shared_preferences` / localStorage), expired
|
|
access token refreshed on restart. `GET /auth/me` resolves the role.
|
|
- **Home** (`lib/screens/home_screen.dart`) — identity card + navigation.
|
|
- **Verwaltung** (`lib/screens/admin_screen.dart`, Leitungsteam only) —
|
|
list/create KCs; per KC:
|
|
- Gemeinden (list/create); each opens **Teamer-Verwaltung**
|
|
(`teamer_admin_screen.dart`): local Teamer accounts + group-link / email
|
|
invites.
|
|
- **Workshop-Wahlen** (`wahl_admin_screen.dart`): create Wahlen, open/close
|
|
them, add workshops, list participants + Force-Zuteilung, run the
|
|
assignment, view the result table, export the CSV (browser download).
|
|
- **Dateien** (`files_admin_screen.dart`): upload with a visibility tier
|
|
(native `<input type=file>`), list. Needs a configured Nextcloud/S3 on
|
|
the backend or the upload returns 500.
|
|
- pending Verantwortlichen self-registrations (approve / reject).
|
|
- **Als Verantwortliche/r registrieren**
|
|
(`verantwortliche_register_screen.dart`) — shown on the home screen to a
|
|
logged-in Authentik user without a membership: enter a KC invite code,
|
|
pick a Gemeinde, submit; a Leitungsteam member then approves.
|
|
- **Push (web)** — `web/index.html` loads the Firebase compat SDK and
|
|
`web/firebase-messaging-sw.js` handles background messages. After login
|
|
`AppState` calls `window.kcGetPushToken()` and registers the token
|
|
(`POST /push/register`). Inert until `apiKey` / `appId` / `vapidKey` are
|
|
filled into both files (see the `REPLACE_ME` placeholders).
|
|
- **Nutzungsanalysen (web)** — `web/index.html` also initialises Google
|
|
Analytics for Firebase (`firebase.analytics()`) on every page load,
|
|
independent of login/push. Automatically logs `page_view` /
|
|
`session_start` / `first_visit`; visible in the Firebase Console under
|
|
**Analytics** (data can take a few hours to first appear, and won't show
|
|
on `localhost` — Analytics filters out non-public hostnames by default).
|
|
Screen-level events inside the Flutter SPA aren't tracked without further
|
|
instrumentation, but overall reach/users/sessions are.
|
|
- **Workshop-Wahl** (`lib/screens/wahl_screen.dart`, guests) — two tabs:
|
|
*Wünsche* (`GET /wahl/guest/overview`, tap workshops in order, max 3,
|
|
`POST /wahl/:id/teilnehmer`) and *Ergebnis* (`GET /wahl/guest/results` —
|
|
PENDING / ASSIGNED with workshop + wish rank / UNASSIGNED).
|
|
- **Dateien** (`lib/screens/files_screen.dart`) — `GET /files/:kcId`,
|
|
filtered server-side by the caller's visibility tier.
|
|
- **Chat** (`lib/screens/chat_screen.dart` + `lib/chat_socket.dart`) —
|
|
channel list, REST history, then a live `/chat` WebSocket connection
|
|
(`chat:join` / `chat:send` / `chat:message`) with a compose bar.
|
|
|
|
## Architecture
|
|
|
|
- `lib/api.dart` — `Api` (thin REST wrapper + models) and `AppState`
|
|
(`ChangeNotifier`: session, login/logout, token persistence).
|
|
- `lib/oidc.dart` — Authentik PKCE flow. Browser-only bits (sessionStorage,
|
|
redirect, `window.location`) sit behind a conditional import
|
|
(`browser.dart` → `browser_web.dart` / `browser_stub.dart`) so
|
|
`flutter test` compiles on the Dart VM.
|
|
- `lib/chat_socket.dart` — `/chat` WebSocket wrapper.
|
|
- `lib/main.dart` — `AppScope` (an `InheritedNotifier<AppState>`) exposes
|
|
`AppScope.of(context)`; `_AuthGate` switches Login/Home. No third-party
|
|
state-management package.
|