import 'package:flutter/material.dart'; import '../api.dart'; import '../main.dart'; class LoginScreen extends StatelessWidget { const LoginScreen({super.key}); @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( title: const Text('KC-App'), bottom: const TabBar( tabs: [ Tab(text: 'Konfi / Gast'), Tab(text: 'Team-Login'), Tab(text: 'Einladung'), ], ), ), body: SafeArea( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: Padding( padding: const EdgeInsets.all(24), child: TabBarView( children: const [ _GuestForm(), _TeamForm(), _InviteForm(), ], ), ), ), ), ), ), ); } } /// Shared submit-button + error handling for the three little forms. class _FormShell extends StatefulWidget { const _FormShell({required this.title, required this.fields, required this.onSubmit}); final String title; final List fields; final Future Function() onSubmit; @override State<_FormShell> createState() => _FormShellState(); } class _FormShellState extends State<_FormShell> { bool _busy = false; String? _error; Future _run() async { setState(() { _busy = true; _error = null; }); try { await widget.onSubmit(); } on ApiException catch (e) { setState(() => _error = e.message); } catch (e) { setState(() => _error = e.toString()); } finally { if (mounted) setState(() => _busy = false); } } @override Widget build(BuildContext context) { return ListView( shrinkWrap: true, children: [ if (widget.title.isNotEmpty) ...[ Text(widget.title, style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), ], ...widget.fields, const SizedBox(height: 20), if (_error != null) ...[ Text(_error!, style: TextStyle(color: Theme.of(context).colorScheme.error)), const SizedBox(height: 12), ], FilledButton( onPressed: _busy ? null : _run, child: _busy ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2)) : const Text('Weiter'), ), ], ); } } TextField _field(TextEditingController c, String label, {bool obscure = false}) => TextField( controller: c, obscureText: obscure, decoration: InputDecoration(labelText: label, border: const OutlineInputBorder()), ); class _GuestForm extends StatefulWidget { const _GuestForm(); @override State<_GuestForm> createState() => _GuestFormState(); } class _GuestFormState extends State<_GuestForm> { final _code = TextEditingController(); final _first = TextEditingController(); final _last = TextEditingController(); @override Widget build(BuildContext context) { final state = AppScope.of(context); return _FormShell( title: 'Mit Einladungscode beitreten', fields: [ _field(_code, 'Einladungscode'), const SizedBox(height: 12), _field(_first, 'Vorname'), const SizedBox(height: 12), _field(_last, 'Nachname'), ], onSubmit: () => state.guestLogin(_code.text.trim(), _first.text.trim(), _last.text.trim()), ); } } class _TeamForm extends StatefulWidget { const _TeamForm(); @override State<_TeamForm> createState() => _TeamFormState(); } class _TeamFormState extends State<_TeamForm> { final _email = TextEditingController(); final _password = TextEditingController(); @override Widget build(BuildContext context) { final state = AppScope.of(context); return ListView( shrinkWrap: true, children: [ Text('Leitungsteam / Verantwortliche', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 12), if (state.authError != null) ...[ Text(state.authError!, style: TextStyle(color: Theme.of(context).colorScheme.error)), const SizedBox(height: 12), ], FilledButton.icon( onPressed: () => state.beginOidcLogin(), icon: const Icon(Icons.login), label: const Text('Mit Konfi-Castle-ID anmelden'), ), const SizedBox(height: 8), const Text( 'Öffnet die Konfi-Castle-ID (Authentik). Leitungsteam-Rechte kommen ' 'aus deiner Authentik-Gruppe.', style: TextStyle(fontSize: 12), ), const Divider(height: 40), Text('Lokaler Teamer:in-Login', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 12), _TeamPasswordForm(email: _email, password: _password), ], ); } } class _TeamPasswordForm extends StatelessWidget { const _TeamPasswordForm({required this.email, required this.password}); final TextEditingController email; final TextEditingController password; @override Widget build(BuildContext context) { final state = AppScope.of(context); return _FormShell( title: '', fields: [ _field(email, 'E-Mail'), const SizedBox(height: 12), _field(password, 'Passwort', obscure: true), ], onSubmit: () => state.teamLogin(email.text.trim(), password.text), ); } } class _InviteForm extends StatefulWidget { const _InviteForm(); @override State<_InviteForm> createState() => _InviteFormState(); } class _InviteFormState extends State<_InviteForm> { final _token = TextEditingController(); final _first = TextEditingController(); final _last = TextEditingController(); final _email = TextEditingController(); final _password = TextEditingController(); @override Widget build(BuildContext context) { final state = AppScope.of(context); return _FormShell( title: 'Teamer:in-Einladung einlösen', fields: [ _field(_token, 'Einladungscode / Token'), const SizedBox(height: 12), _field(_first, 'Vorname'), const SizedBox(height: 12), _field(_last, 'Nachname'), const SizedBox(height: 12), _field(_email, 'E-Mail (bei Gruppen-Link nötig)'), const SizedBox(height: 12), _field(_password, 'Passwort wählen (min. 8 Zeichen)', obscure: true), ], onSubmit: () => state.redeemInvite( token: _token.text.trim(), first: _first.text.trim(), last: _last.text.trim(), password: _password.text, email: _email.text.trim(), ), ); } }