marco.pms.mobileapp/lib/view/auth/request_demo_bottom_sheet.dart

363 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/services/auth_service.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
class OrganizationFormBottomSheet {
static void show(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) => DraggableScrollableSheet(
expand: false,
initialChildSize: 0.85,
minChildSize: 0.5,
maxChildSize: 0.95,
builder: (context, scrollController) {
return _OrganizationForm(scrollController: scrollController);
},
),
);
}
}
class _OrganizationForm extends StatefulWidget {
final ScrollController scrollController;
const _OrganizationForm({required this.scrollController});
@override
State<_OrganizationForm> createState() => _OrganizationFormState();
}
class _OrganizationFormState extends State<_OrganizationForm> {
final MyFormValidator validator = MyFormValidator();
bool _loading = false;
List<Map<String, dynamic>> _industries = [];
String? _selectedIndustryId;
final List<String> _sizes = [
'1-10',
'11-50',
'51-200',
'201-1000',
'1000+',
];
final Map<String, String> _sizeApiMap = {
'1-10': 'less than 10',
'11-50': '11 to 50',
'51-200': '51 to 200',
'201-1000': 'more than 200',
'1000+': 'more than 1000',
};
String? _selectedSize;
bool _agreed = false;
@override
void initState() {
super.initState();
_loadIndustries();
validator.addField<String>('organizationName',
required: true, controller: TextEditingController());
validator.addField<String>('email',
required: true, controller: TextEditingController());
validator.addField<String>('contactPerson',
required: true, controller: TextEditingController());
validator.addField<String>('contactNumber',
required: true, controller: TextEditingController());
validator.addField<String>('about', controller: TextEditingController());
validator.addField<String>('address',
required: true, controller: TextEditingController());
}
Future<void> _loadIndustries() async {
final industries = await AuthService.getIndustries();
if (industries != null) {
setState(() {
_industries = industries;
});
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
padding: const EdgeInsets.all(20),
child: SingleChildScrollView(
controller: widget.scrollController,
child: Form(
key: validator.formKey,
child: Column(
children: [
Container(
width: 40,
height: 5,
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
Text(
'Adventure starts here 🚀',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 4),
const Text("Make your app management easy and fun!"),
const SizedBox(height: 20),
_buildTextField('organizationName', 'Organization Name'),
_buildTextField('email', 'Email',
keyboardType: TextInputType.emailAddress),
_buildTextField('contactPerson', 'Contact Person'),
_buildTextField('contactNumber', 'Contact Number',
keyboardType: TextInputType.phone),
_buildTextField('about', 'About Organization'),
_buildTextField('address', 'Current Address'),
const SizedBox(height: 10),
_buildPopupMenuField(
'Organization Size',
_sizes,
_selectedSize,
(val) => setState(() => _selectedSize = val),
'Please select organization size',
),
_buildPopupMenuField(
'Industry',
_industries.map((e) => e['name'] as String).toList(),
_selectedIndustryId != null
? _industries.firstWhere(
(e) => e['id'] == _selectedIndustryId)['name']
: null,
(val) {
setState(() {
final selectedIndustry = _industries.firstWhere(
(element) => element['name'] == val,
orElse: () => {});
_selectedIndustryId = selectedIndustry['id'];
});
},
'Please select industry',
),
const SizedBox(height: 10),
GestureDetector(
onTap: () => setState(() => _agreed = !_agreed),
child: Row(
children: [
Checkbox(
value: _agreed,
onChanged: (val) =>
setState(() => _agreed = val ?? false),
fillColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) {
return Colors.blueAccent;
}
return Colors.white;
}),
checkColor: Colors.white,
side:
const BorderSide(color: Colors.blueAccent, width: 2),
),
const Expanded(
child: Text('I agree to privacy policy & terms'),
),
],
),
),
const SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
),
onPressed: _loading ? null : _submitForm,
child: _loading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Text("Submit"),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Back to login"),
),
],
),
),
),
);
}
Widget _buildPopupMenuField(
String label,
List<String> items,
String? selectedValue,
ValueChanged<String?> onSelected,
String errorText,
) {
final bool hasError = selectedValue == null;
final GlobalKey _key = GlobalKey();
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: FormField<String>(
validator: (value) => hasError ? errorText : null,
builder: (fieldState) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: TextStyle(fontSize: 12, color: Colors.grey[700])),
const SizedBox(height: 6),
GestureDetector(
key: _key,
onTap: () async {
final RenderBox renderBox =
_key.currentContext!.findRenderObject() as RenderBox;
final Offset offset = renderBox.localToGlobal(Offset.zero);
final Size size = renderBox.size;
final selected = await showMenu<String>(
context: fieldState.context,
position: RelativeRect.fromLTRB(
offset.dx,
offset.dy + size.height,
offset.dx + size.width,
offset.dy,
),
items: items
.map((item) => PopupMenuItem<String>(
value: item,
child: Text(item),
))
.toList(),
);
if (selected != null) {
onSelected(selected);
fieldState.didChange(selected);
}
},
child: InputDecorator(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: fieldState.errorText,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 14),
),
child: Text(
selectedValue ?? 'Select $label',
style: TextStyle(
color: selectedValue == null ? Colors.grey : Colors.black,
fontSize: 16,
),
),
),
),
],
);
},
),
);
}
Widget _buildTextField(String fieldName, String label,
{TextInputType keyboardType = TextInputType.text}) {
final controller = validator.getController(fieldName);
final defaultValidator = validator.getValidation<String>(fieldName);
// Custom logic for contact number
String? Function(String?)? validatorFunc = defaultValidator;
if (fieldName == 'contactNumber') {
validatorFunc = (value) {
if (value == null || value.isEmpty) {
return 'Contact number is required';
}
if (!RegExp(r'^\d{10}$').hasMatch(value)) {
return 'Enter a valid 10-digit contact number';
}
return null;
};
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
),
validator: validatorFunc,
),
);
}
void _submitForm() async {
bool isValid = validator.validateForm();
if (_selectedSize == null || _selectedIndustryId == null) {
isValid = false;
setState(() {});
}
if (!_agreed) {
isValid = false;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please agree to the privacy policy & terms')),
);
}
if (isValid) {
setState(() => _loading = true);
final formData = validator.getData();
final Map<String, dynamic> requestBody = {
'organizatioinName': formData['organizationName'],
'email': formData['email'],
'about': formData['about'],
'contactNumber': formData['contactNumber'],
'contactPerson': formData['contactPerson'],
'industryId': _selectedIndustryId ?? '',
'oragnizationSize': _sizeApiMap[_selectedSize] ?? '',
'terms': _agreed,
'address': formData['address'],
};
final error = await AuthService.requestDemo(requestBody);
setState(() => _loading = false);
if (error == null) {
showAppSnackbar(
title: "Success",
message: "Demo request submitted successfully!.",
type: SnackbarType.success,
);
Navigator.pop(context);
} else {
showAppSnackbar(
title: "Error",
message: (error['error'] ?? 'Unknown error'),
type: SnackbarType.error,
);
}
}
}
}