onboarding completato

This commit is contained in:
2026-04-22 11:05:01 +02:00
parent 35ccd1487b
commit 46058d96c8
13 changed files with 211 additions and 120 deletions

View File

@@ -63,4 +63,9 @@ class AuthCubit extends Cubit<AuthState> {
);
}
}
Future<void> requestLogout() async {
await _supabase.auth.signOut();
emit(state.copyWith(status: AuthStatus.initial));
}
}

View File

@@ -100,7 +100,7 @@ class _AuthScreenState extends State<AuthScreen> {
label: 'Email Aziendale',
icon: Icons.email_outlined,
controller: _emailController,
// TODO: Aggiungi nel tuo FluxTextField la gestione del keyboardType se non c'è già!
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 20),
FluxTextField(
@@ -108,7 +108,8 @@ class _AuthScreenState extends State<AuthScreen> {
icon: Icons.lock_outline,
isPassword: true, // Magia del FluxTextField!
controller: _passwordController,
// onSubmitted: (_) => _submit(), // Se lo supporti nel tuo widget custom
onSubmitted: (_) =>
_submit(), // Se lo supporti nel tuo widget custom
),
const SizedBox(height: 40),

View File

@@ -246,9 +246,7 @@ class _HomeScreenState extends State<HomeScreen> {
),
onPressed: () {
Navigator.pop(dialogContext); // Chiude la Dialog
/* context.read<AuthBloc>().add(
LogoutRequested(),
); // Esegue il logout */
context.read<AuthCubit>().requestLogout(); // Esegue il logout
},
child: const Text("Esci"),
),

View File

@@ -13,7 +13,11 @@ class OnboardingCubit extends Cubit<OnboardingState> {
final SessionCubit _sessionCubit;
OnboardingCubit(this._sessionCubit, this._repository)
: super(OnboardingState(step: _sessionCubit.state.onboardingStep));
: super(OnboardingState(
step: _sessionCubit.state.onboardingStep,
companyId: _sessionCubit.state.company?.id,
storeId: _sessionCubit.state.currentStore?.id,
));
// --- STEP 1: REGISTRAZIONE AZIENDA ---
Future<void> saveCompany(String companyName) async {
@@ -49,12 +53,14 @@ class OnboardingCubit extends Cubit<OnboardingState> {
// --- STEP 2: REGISTRAZIONE PRIMO NEGOZIO ---
Future<void> saveStore(StoreModel store) async {
if (state.companyId == null) return;
if (state.companyId == '') return;
emit(state.copyWith(isLoading: true));
try {
// Iniettiamo forzatamente il companyId ottenuto dallo step precedente
final storeToSave = store.copyWith(companyId: state.companyId);
final savedStore = await _repository.createStore(storeToSave);
_sessionCubit.changeStore(savedStore);
emit(
state.copyWith(
@@ -72,7 +78,8 @@ class OnboardingCubit extends Cubit<OnboardingState> {
// --- STEP 3: REGISTRAZIONE PROFILO STAFF (PAZIENTE ZERO) ---
Future<void> saveStaff(StaffMemberModel staff) async {
if (state.companyId == null || state.storeId == null) return;
if (state.companyId == null) return;
if (state.companyId == '') return;
emit(state.copyWith(isLoading: true));
try {

View File

@@ -50,6 +50,10 @@ class _CompanyOnboardingFormState extends State<CompanyOnboardingForm> {
label: 'Ragione Sociale / Nome Azienda',
controller: _nameCtrl,
validator: notEmptyValidator,
keyboardType: TextInputType.name,
textCapitalization: TextCapitalization.words,
autocorrect: false,
onSubmitted: (_) => _submit(),
),
const Spacer(),
@@ -60,13 +64,7 @@ class _CompanyOnboardingFormState extends State<CompanyOnboardingForm> {
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
if (_formKey.currentState!.validate()) {
context.read<OnboardingCubit>().saveCompany(
_nameCtrl.text.trim(),
);
}
},
onPressed: () => _submit(),
child: const Text(
"Salva e Prosegui",
style: TextStyle(fontSize: 16),
@@ -78,4 +76,10 @@ class _CompanyOnboardingFormState extends State<CompanyOnboardingForm> {
),
);
}
void _submit() {
if (_formKey.currentState!.validate()) {
context.read<OnboardingCubit>().saveCompany(_nameCtrl.text.trim());
}
}
}

View File

@@ -1,18 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/data/core_repository.dart';
import 'package:flux/core/utils/validators.dart';
import 'package:flux/features/master_data/store/models/store_model.dart';
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart';
// Sostituisci con il percorso corretto della tua FluxTextField
import 'package:flux/core/widgets/flux_text_field.dart';
import 'package:flux/features/onboarding/blocs/onboarding_state.dart';
import 'package:flux/features/onboarding/ui/company_onboarding_form.dart';
import 'package:flux/features/onboarding/ui/staff_onboarding_form.dart';
import 'package:flux/features/onboarding/ui/store_onboarding_form.dart';
import 'package:get_it/get_it.dart';
class OnboardingScreen extends StatefulWidget {
const OnboardingScreen({super.key});
@@ -24,15 +17,6 @@ class OnboardingScreen extends StatefulWidget {
class _OnboardingScreenState extends State<OnboardingScreen> {
late PageController _pageController;
// --- CHIAVI DEI FORM (Per la validazione indipendente di ogni step) ---
final _staffFormKey = GlobalKey<FormState>();
// --- CONTROLLERS: STEP 3 (Staff) ---
final _staffFirstNameCtrl = TextEditingController();
final _staffLastNameCtrl = TextEditingController();
final _staffJobTitleCtrl = TextEditingController();
@override
void initState() {
super.initState();
@@ -44,9 +28,6 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
@override
void dispose() {
_pageController.dispose();
_staffFirstNameCtrl.dispose();
_staffLastNameCtrl.dispose();
_staffJobTitleCtrl.dispose();
super.dispose();
}
@@ -112,9 +93,9 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
physics:
const NeverScrollableScrollPhysics(), // Vietato lo swipe manuale!
children: [
CompanyOnboardingForm(state: state), // Step 1: Company
CompanyOnboardingForm(state: state),
StoreOnboardingForm(state: state),
_buildStaffForm(context, state),
StaffOnboardingForm(),
],
),
@@ -131,73 +112,4 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
},
);
}
Widget _buildStaffForm(BuildContext context, OnboardingState state) {
return Padding(
padding: const EdgeInsets.all(32.0),
child: Form(
key: _staffFormKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
"Il tuo Profilo 👤",
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
"Ultimo step! Crea il tuo profilo operativo per iniziare a usare FLUX.",
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 48),
FluxTextField(
label: 'Nome',
controller: _staffFirstNameCtrl,
validator: notEmptyValidator,
),
const SizedBox(height: 16),
FluxTextField(
label: 'Cognome',
controller: _staffLastNameCtrl,
validator: notEmptyValidator,
),
const SizedBox(height: 16),
FluxTextField(
label: 'Etichetta Ruolo (es. Titolare, Manager)',
controller: _staffJobTitleCtrl,
// Il jobTitle può anche essere opzionale, decidi tu!
),
const Spacer(),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.black, // O il tuo context.accent
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
if (_staffFormKey.currentState!.validate()) {
final newStaff = StaffMemberModel.empty().copyWith(
name: _staffFirstNameCtrl.text.trim(),
jobTitle: _staffJobTitleCtrl.text.trim(),
);
context.read<OnboardingCubit>().saveStaff(newStaff);
}
},
child: const Text(
"Entra in FLUX",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 16),
],
),
),
);
}
}

View File

@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/utils/validators.dart';
import 'package:flux/core/widgets/flux_text_field.dart';
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
import 'package:flux/features/onboarding/blocs/onboarding_cubit.dart';
class StaffOnboardingForm extends StatefulWidget {
const StaffOnboardingForm({super.key});
@override
State<StaffOnboardingForm> createState() => _StaffOnboardingFormState();
}
class _StaffOnboardingFormState extends State<StaffOnboardingForm> {
final _formKey = GlobalKey<FormState>();
final _nameCtrl = TextEditingController();
final _emailCtrl = TextEditingController();
final _jobTitleCtrl = TextEditingController();
@override
void dispose() {
_nameCtrl.dispose();
_emailCtrl.dispose();
_jobTitleCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(32),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
"Il tuo Profilo 👤",
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
"Ultimo step! Crea il tuo profilo operativo per iniziare a usare FLUX.",
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 48),
FluxTextField(
label: 'Nome',
keyboardType: TextInputType.name,
controller: _nameCtrl,
validator: notEmptyValidator,
textCapitalization: TextCapitalization.words,
autocorrect: false,
),
const SizedBox(height: 16),
FluxTextField(
label: 'Email',
keyboardType: TextInputType.emailAddress,
controller: _emailCtrl,
textCapitalization: TextCapitalization.none,
),
const SizedBox(height: 16),
FluxTextField(
label: 'Etichetta Ruolo (es. Titolare, Manager)',
controller: _jobTitleCtrl,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.words,
onSubmitted: (_) => _submit(),
),
const Spacer(),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.black, // O il tuo context.accent
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () => _submit(),
child: const Text(
"Entra in FLUX",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 16),
],
),
),
);
}
void _submit() {
if (_formKey.currentState!.validate()) {
final newStaff = StaffMemberModel.empty().copyWith(
name: _nameCtrl.text.trim(),
email: _emailCtrl.text.trim(),
jobTitle: _jobTitleCtrl.text.trim(),
);
context.read<OnboardingCubit>().saveStaff(newStaff);
}
}
}

View File

@@ -56,6 +56,7 @@ class _StoreOnboardingFormState extends State<StoreOnboardingForm> {
FluxTextField(
controller: _nameCtrl,
label: "Nome del Negozio",
keyboardType: TextInputType.name,
validator: (value) =>
value == null || value.isEmpty ? "Obbligatorio" : null,
),
@@ -63,6 +64,7 @@ class _StoreOnboardingFormState extends State<StoreOnboardingForm> {
FluxTextField(
controller: _addressCtrl,
keyboardType: TextInputType.streetAddress,
label: "Indirizzo",
validator: (value) =>
value == null || value.isEmpty ? "Obbligatorio" : null,
@@ -115,20 +117,7 @@ class _StoreOnboardingFormState extends State<StoreOnboardingForm> {
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
if (_formKey.currentState!.validate()) {
// MIRACOLO DELLA FACTORY EMPTY!
final newStore = StoreModel.empty().copyWith(
nome: _nameCtrl.text.trim(),
indirizzo: _addressCtrl.text.trim(),
comune: _cityCtrl.text.trim(),
cap: _zipCodeCtrl.text.trim(),
// Formattiamo in maiuscolo qui, al momento del salvataggio!
provincia: _provinceCtrl.text.trim().toUpperCase(),
);
context.read<OnboardingCubit>().saveStore(newStore);
}
},
onPressed: () => _submit(),
child: const Text(
"Salva Negozio",
style: TextStyle(fontSize: 16),
@@ -141,6 +130,21 @@ class _StoreOnboardingFormState extends State<StoreOnboardingForm> {
);
}
void _submit() {
if (_formKey.currentState!.validate()) {
// MIRACOLO DELLA FACTORY EMPTY!
final newStore = StoreModel.empty().copyWith(
nome: _nameCtrl.text.trim(),
indirizzo: _addressCtrl.text.trim(),
comune: _cityCtrl.text.trim(),
cap: _zipCodeCtrl.text.trim(),
// Formattiamo in maiuscolo qui, al momento del salvataggio!
provincia: _provinceCtrl.text.trim().toUpperCase(),
);
context.read<OnboardingCubit>().saveStore(newStore);
}
}
// --- WIDGET ESTRATTI PER PULIZIA ---
Widget _buildCityField() {
@@ -169,6 +173,7 @@ class _StoreOnboardingFormState extends State<StoreOnboardingForm> {
// Rende la tastiera del telefono automaticamente maiuscola
textCapitalization: TextCapitalization.characters,
inputFormatters: [LengthLimitingTextInputFormatter(2)],
onSubmitted: (_) => _submit(),
);
}
}