import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../app/theme.dart';
import '../../../core/auth/auth_store.dart';
import '../../../core/auth/industry_profiles.dart';
import '../../../shared/widgets/brand_mark.dart';
import '../../dashboard/presentation/app_shell.dart';
class WelcomeScreen extends StatefulWidget {
const WelcomeScreen({super.key, this.authStore, this.invitationToken});
final AuthStore? authStore;
final String? invitationToken;
@override
State<WelcomeScreen> createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen> {
final email = TextEditingController();
final password = TextEditingController();
final tenant = TextEditingController();
bool register = false;
bool showLogin = false;
bool submitting = false;
final selectedIndustryProfiles = <String>{};
String? formError;
Map<String, dynamic>? invitation;
bool invitationLoading = false;
@override
void initState() {
super.initState();
if (widget.invitationToken != null) {
showLogin = true;
_inspectInvitation();
}
}
Future<void> _inspectInvitation() async {
setState(() => invitationLoading = true);
try {
invitation = await (widget.authStore ?? AuthStore.instance)
.inspectInvitation(widget.invitationToken!);
email.text = invitation?['email'] as String? ?? '';
} on Object {
formError = 'Die Einladung ist ungültig oder abgelaufen.';
} finally {
if (mounted) setState(() => invitationLoading = false);
}
}
@override
void dispose() {
email.dispose();
password.dispose();
tenant.dispose();
super.dispose();
}
Future<void> _submit(AuthStore auth) async {
if (submitting) return;
if (widget.invitationToken != null && invitation == null) {
setState(
() => formError = invitationLoading
? 'Die Einladung wird geprüft …'
: 'Die Einladung ist ungültig oder abgelaufen.',
);
return;
}
if (register && selectedIndustryProfiles.isEmpty) {
setState(() => formError = 'Wähle mindestens ein Branchenprofil aus.');
return;
}
FocusManager.instance.primaryFocus?.unfocus();
setState(() {
submitting = true;
formError = null;
});
// Render a visible response to the tap before starting network work.
await WidgetsBinding.instance.endOfFrame;
try {
if (widget.invitationToken != null) {
await auth.acceptInvitation(widget.invitationToken!, password.text);
} else if (register) {
await auth.registerOwner(
tenant.text.trim(),
email.text.trim(),
password.text,
industryProfileOptions
.where((option) => selectedIndustryProfiles.contains(option.code))
.map((option) => option.code)
.toList(),
);
} else {
await auth.login(email.text.trim(), password.text);
}
} finally {
if (mounted) setState(() => submitting = false);
}
}
@override
Widget build(BuildContext context) {
final auth = widget.authStore ?? AuthStore.instance;
if (auth.status == AuthStatus.loading) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
if (!showLogin) {
return Scaffold(
backgroundColor: AppColors.forest,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const BrandMark(light: true),
const SizedBox(height: 32),
const Text(
'Arbeit erledigt.\nNachweis fertig.',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 28),
FilledButton(
onPressed: () => setState(() => showLogin = true),
child: const Text('Demo starten'),
),
],
),
),
);
}
return Scaffold(
backgroundColor: AppColors.forest,
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Card(
child: Padding(
padding: const EdgeInsets.all(28),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const BrandMark(),
const SizedBox(height: 28),
Text(
register ? 'Betrieb anlegen' : 'Willkommen zurück',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 8),
Text(
widget.invitationToken != null
? 'Du wurdest zu einem MACHWEIS-Betrieb eingeladen.'
: register
? 'Lege deinen Betrieb und den ersten Inhaber-Zugang an.'
: 'Melde dich mit deinem MACHWEIS-Zugang an.',
),
const SizedBox(height: 22),
if (register) ...[
_field(
tenant,
'Name des Betriebs',
Icons.business_outlined,
),
const SizedBox(height: 14),
const Text(
'Branchenprofile',
style: TextStyle(fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
const Text(
'Mehrfachauswahl – sie steuert später passende Vorlagen und Abläufe.',
),
const SizedBox(height: 10),
Wrap(
spacing: 8,
runSpacing: 8,
children: industryProfileOptions
.map(
(option) => FilterChip(
label: Text(option.label),
selected: selectedIndustryProfiles.contains(
option.code,
),
onSelected: (selected) => setState(() {
if (selected) {
selectedIndustryProfiles.add(option.code);
} else {
selectedIndustryProfiles.remove(
option.code,
);
}
formError = null;
}),
),
)
.toList(),
),
const SizedBox(height: 14),
],
_field(
email,
'E-Mail',
Icons.mail_outline_rounded,
enabled: widget.invitationToken == null,
),
const SizedBox(height: 14),
_field(
password,
'Passwort',
Icons.lock_outline_rounded,
obscure: true,
),
if (formError != null || auth.error != null)
Padding(
padding: const EdgeInsets.only(top: 14),
child: Text(
formError ?? auth.error!,
style: const TextStyle(color: Colors.red),
),
),
const SizedBox(height: 20),
FilledButton(
onPressed: submitting || invitationLoading
? null
: () => _submit(auth),
child: Text(
submitting
? 'Anmeldung läuft …'
: widget.invitationToken != null
? 'Einladung annehmen'
: register
? 'Betrieb anlegen'
: 'Anmelden',
),
),
if (widget.invitationToken == null)
TextButton(
onPressed: () =>
Navigator.of(context).pushReplacement(
MaterialPageRoute<void>(
builder: (_) => const AppShell(),
),
),
child: const Text('Als Demo-Nutzer anmelden'),
),
if (!kIsWeb && widget.invitationToken == null)
TextButton(
onPressed: () => setState(() {
register = !register;
formError = null;
}),
child: Text(
register
? 'Ich habe bereits einen Zugang'
: 'Neuen Betrieb anlegen',
),
),
],
),
),
),
),
),
),
),
);
}
Widget _field(
TextEditingController controller,
String label,
IconData icon, {
bool obscure = false,
bool enabled = true,
}) => TextField(
controller: controller,
enabled: enabled,
obscureText: obscure,
keyboardType: label == 'E-Mail' ? TextInputType.emailAddress : null,
decoration: InputDecoration(
labelText: label,
prefixIcon: Icon(icon),
border: const OutlineInputBorder(),
),
);
}
