458 lines
16 KiB
Dart
458 lines
16 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';
|
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
|
import 'package:marco/helpers/widgets/my_text.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) =>
|
|
_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> with UIMixin {
|
|
final MyFormValidator validator = MyFormValidator();
|
|
bool _loading = false;
|
|
bool _agreed = false;
|
|
|
|
List<Map<String, dynamic>> _industries = [];
|
|
String? _selectedIndustryId;
|
|
String? _selectedSize;
|
|
|
|
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',
|
|
};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadIndustries();
|
|
_registerFields();
|
|
}
|
|
|
|
void _registerFields() {
|
|
validator.addField('organizationName',
|
|
required: true, controller: TextEditingController());
|
|
validator.addField('email',
|
|
required: true, controller: TextEditingController());
|
|
validator.addField('contactPerson',
|
|
required: true, controller: TextEditingController());
|
|
validator.addField('contactNumber',
|
|
required: true, controller: TextEditingController());
|
|
validator.addField('about', controller: TextEditingController());
|
|
validator.addField('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.fromLTRB(20, 20, 20, 40),
|
|
child: SingleChildScrollView(
|
|
controller: widget.scrollController,
|
|
child: Form(
|
|
key: validator.formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Center(
|
|
child: Container(
|
|
width: 40,
|
|
height: 5,
|
|
margin: const EdgeInsets.only(bottom: 20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[300],
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
),
|
|
Center(
|
|
child: Column(
|
|
children: [
|
|
MyText.titleLarge(
|
|
'Adventure starts here 🚀',
|
|
fontWeight: 600,
|
|
color: Colors.black87,
|
|
),
|
|
const SizedBox(height: 4),
|
|
MyText.bodySmall(
|
|
"Make your app management easy and fun!",
|
|
color: Colors.grey,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
_sectionHeader('Organization Info'),
|
|
_buildTextField('organizationName', 'Organization Name'),
|
|
_buildTextField('email', 'Email',
|
|
keyboardType: TextInputType.emailAddress),
|
|
_buildTextField('about', 'About Organization'),
|
|
_sectionHeader('Contact Details'),
|
|
_buildTextField('contactPerson', 'Contact Person'),
|
|
_buildTextField('contactNumber', 'Contact Number',
|
|
keyboardType: TextInputType.phone),
|
|
_buildTextField('address', 'Current Address'),
|
|
_sectionHeader('Additional Details'),
|
|
_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: 12),
|
|
Row(
|
|
children: [
|
|
Checkbox(
|
|
value: _agreed,
|
|
onChanged: (val) => setState(() => _agreed = val ?? false),
|
|
fillColor: MaterialStateProperty.resolveWith((states) =>
|
|
states.contains(MaterialState.selected)
|
|
? contentTheme.primary
|
|
: Colors.white),
|
|
checkColor: Colors.white,
|
|
),
|
|
Row(
|
|
children: [
|
|
MyText(
|
|
'I agree to the ',
|
|
color: Colors.black87,
|
|
),
|
|
MyText(
|
|
'privacy policy & terms',
|
|
color: contentTheme.primary,
|
|
fontWeight: 600,
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon: const Icon(Icons.close, color: Colors.white),
|
|
label: MyText.bodyMedium(
|
|
"Cancel",
|
|
color: Colors.white,
|
|
fontWeight: 600,
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.grey,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: _loading ? null : _submitForm,
|
|
icon:
|
|
Icon(Icons.check_circle_outline, color: Colors.white),
|
|
label: MyText.bodyMedium(
|
|
_loading ? "Submitting..." : "Submit",
|
|
color: Colors.white,
|
|
fontWeight: 600,
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: contentTheme
|
|
.primary,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Center(
|
|
child: TextButton.icon(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon:
|
|
Icon(Icons.arrow_back, size: 18, color: contentTheme.primary),
|
|
label: MyText.bodySmall(
|
|
'Back to log in',
|
|
fontWeight: 600,
|
|
color: contentTheme.primary,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _sectionHeader(String title) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(top: 20, bottom: 8),
|
|
child: MyText.titleSmall(
|
|
title,
|
|
fontWeight: 600,
|
|
color: Colors.grey[800],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTextField(String fieldName, String label,
|
|
{TextInputType keyboardType = TextInputType.text}) {
|
|
final controller = validator.getController(fieldName);
|
|
final defaultValidator = validator.getValidation<String>(fieldName);
|
|
|
|
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,
|
|
validator: validatorFunc,
|
|
style: const TextStyle(fontSize: 15, color: Colors.black87),
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
labelStyle: TextStyle(color: Colors.grey[700], fontSize: 14),
|
|
filled: true,
|
|
fillColor: Colors.grey[100],
|
|
contentPadding:
|
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(5),
|
|
borderSide: BorderSide(color: Colors.grey[400]!),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(5),
|
|
borderSide: BorderSide(color: Colors.grey[300]!),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(5),
|
|
borderSide: BorderSide(color: contentTheme.brandRed, width: 1.5),
|
|
),
|
|
errorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(5),
|
|
borderSide: const BorderSide(color: Colors.red),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
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: [
|
|
MyText.bodySmall(label, color: Colors.grey[700]),
|
|
const SizedBox(height: 6),
|
|
GestureDetector(
|
|
key: _key,
|
|
onTap: () async {
|
|
final renderBox =
|
|
_key.currentContext!.findRenderObject() as RenderBox;
|
|
final offset = renderBox.localToGlobal(Offset.zero);
|
|
final 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: MyText.bodyMedium(item),
|
|
))
|
|
.toList(),
|
|
);
|
|
|
|
if (selected != null) {
|
|
onSelected(selected);
|
|
fieldState.didChange(selected);
|
|
}
|
|
},
|
|
child: InputDecorator(
|
|
decoration: InputDecoration(
|
|
filled: true,
|
|
fillColor: Colors.grey[100],
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16, vertical: 16),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(5),
|
|
borderSide: BorderSide(color: Colors.grey[400]!),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(5),
|
|
borderSide: BorderSide(color: Colors.grey[300]!),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(5),
|
|
borderSide:
|
|
BorderSide(color: contentTheme.brandRed, width: 1.5),
|
|
),
|
|
errorText: fieldState.errorText,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
MyText.bodyMedium(
|
|
selectedValue ?? 'Select $label',
|
|
color:
|
|
selectedValue == null ? Colors.grey : Colors.black,
|
|
),
|
|
const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _submitForm() async {
|
|
bool isValid = validator.validateForm();
|
|
|
|
if (_selectedSize == null || _selectedIndustryId == null) {
|
|
isValid = false;
|
|
setState(() {});
|
|
}
|
|
|
|
if (!_agreed) {
|
|
isValid = false;
|
|
showAppSnackbar(
|
|
title: "Agreement Required",
|
|
message: "Please agree to the privacy policy & terms",
|
|
type: SnackbarType.warning,
|
|
);
|
|
}
|
|
|
|
if (!isValid) return;
|
|
|
|
setState(() => _loading = true);
|
|
|
|
final formData = validator.getData();
|
|
|
|
final 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 occurred',
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
}
|
|
}
|