implement create tender screen
This commit is contained in:
parent
12fbef19c5
commit
7e427237c3
@ -1,15 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:razorpay_flutter/razorpay_flutter.dart';
|
||||
import 'package:marco/helpers/services/payment_service.dart';
|
||||
import 'package:marco/helpers/services/tenant_service.dart';
|
||||
import 'package:marco/helpers/services/app_logger.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class PaymentController with ChangeNotifier {
|
||||
Razorpay? _razorpay;
|
||||
final PaymentService _paymentService = PaymentService();
|
||||
final TenantService _tenantService = TenantService();
|
||||
|
||||
bool isProcessing = false;
|
||||
//BuildContext? _context;
|
||||
|
||||
// Pending values to use after payment verification
|
||||
String? _pendingTenantEnquireId;
|
||||
String? _pendingPlanId;
|
||||
|
||||
/// ==============================
|
||||
/// START PAYMENT (Safe init)
|
||||
@ -18,11 +23,17 @@ class PaymentController with ChangeNotifier {
|
||||
required double amount,
|
||||
required String description,
|
||||
required BuildContext context,
|
||||
String? tenantEnquireId,
|
||||
String? planId,
|
||||
}) async {
|
||||
try {
|
||||
isProcessing = true;
|
||||
notifyListeners();
|
||||
|
||||
// Save pending ids for post-payment subscription call
|
||||
_pendingTenantEnquireId = tenantEnquireId;
|
||||
_pendingPlanId = planId;
|
||||
|
||||
// Step 1: Create payment order
|
||||
final result = await _paymentService.createOrder(amount);
|
||||
logSafe("🧩 Raw response in PaymentController: $result");
|
||||
@ -35,7 +46,7 @@ class PaymentController with ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Handle both wrapped and unwrapped formats
|
||||
// Step 3: Handle both wrapped and unwrapped formats
|
||||
final data = result['data'] ?? result;
|
||||
final orderId = data?['orderId'];
|
||||
final key = data?['key'];
|
||||
@ -48,18 +59,18 @@ class PaymentController with ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Initialize Razorpay if needed
|
||||
// Step 4: Initialize Razorpay if needed
|
||||
_razorpay ??= Razorpay();
|
||||
_razorpay!.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
|
||||
_razorpay!.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
|
||||
_razorpay!.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
|
||||
|
||||
// Step 4: Open Razorpay checkout
|
||||
// Step 5: Open Razorpay checkout
|
||||
final options = {
|
||||
'key': key,
|
||||
'amount': (amount * 100).toInt(), // Razorpay expects amount in paise
|
||||
'name': 'Marco',
|
||||
'description': 'Subscription Payment',
|
||||
'description': description,
|
||||
'order_id': orderId,
|
||||
'prefill': {'contact': '9999999999', 'email': 'test@marco.com'},
|
||||
};
|
||||
@ -94,6 +105,7 @@ class PaymentController with ChangeNotifier {
|
||||
signature: response.signature!,
|
||||
)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
logSafe("🧩 Verification result: $verificationResult");
|
||||
} catch (e) {
|
||||
logSafe("⏱️ Verification timeout/error: $e", level: LogLevel.error);
|
||||
}
|
||||
@ -101,25 +113,27 @@ class PaymentController with ChangeNotifier {
|
||||
isProcessing = false;
|
||||
notifyListeners();
|
||||
|
||||
// ✅ Handle backend verification response properly
|
||||
// Handle backend verification response properly
|
||||
if (verificationResult != null) {
|
||||
// Example backend response: { "verified": true, "message": "Payment verified" }
|
||||
final isVerified = verificationResult['verified'] == true;
|
||||
final msg = verificationResult['message'] ?? '';
|
||||
|
||||
if (isVerified) {
|
||||
// If we have pending tenant and plan IDs, call subscription API
|
||||
await _maybeSubscribeTenantAfterPayment(
|
||||
verificationResult: verificationResult,
|
||||
razorpayResponse: response,
|
||||
);
|
||||
|
||||
_showDialog(
|
||||
title: "Payment Successful 🎉",
|
||||
message:
|
||||
msg.isNotEmpty ? msg : "Your payment was verified successfully.",
|
||||
message: msg.isNotEmpty ? msg : "Your payment was verified successfully.",
|
||||
success: true,
|
||||
);
|
||||
} else {
|
||||
_showDialog(
|
||||
title: "Verification Failed ❌",
|
||||
message: msg.isNotEmpty
|
||||
? msg
|
||||
: "Payment completed but verification failed.",
|
||||
message: msg.isNotEmpty ? msg : "Payment completed but verification failed.",
|
||||
success: false,
|
||||
);
|
||||
}
|
||||
@ -134,6 +148,61 @@ class PaymentController with ChangeNotifier {
|
||||
_cleanup();
|
||||
}
|
||||
|
||||
Future<void> _maybeSubscribeTenantAfterPayment({
|
||||
required Map<String, dynamic> verificationResult,
|
||||
required PaymentSuccessResponse razorpayResponse,
|
||||
}) async {
|
||||
if (_pendingTenantEnquireId == null || _pendingPlanId == null) {
|
||||
logSafe("ℹ️ No pending tenant/plan id to subscribe.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine paymentDetailId — prefer backend value if provided
|
||||
final paymentDetailId = verificationResult['paymentDetailId'] ?? verificationResult['paymentId'] ?? razorpayResponse.paymentId;
|
||||
|
||||
final subscribePayload = {
|
||||
"tenantEnquireId": _pendingTenantEnquireId,
|
||||
"paymentDetailId": paymentDetailId,
|
||||
"planId": _pendingPlanId,
|
||||
};
|
||||
|
||||
logSafe("🟢 Subscribing tenant automatically: $subscribePayload");
|
||||
|
||||
final subResp = await _tenantService.subscribeTenant(subscribePayload);
|
||||
|
||||
// Clear pending values immediately to avoid double-invoke
|
||||
_pendingTenantEnquireId = null;
|
||||
_pendingPlanId = null;
|
||||
|
||||
if (subResp == null) {
|
||||
logSafe("❌ subscribeTenant returned null");
|
||||
_showDialog(
|
||||
title: "Subscription Failed",
|
||||
message: "Failed to call subscription API. Please contact support.",
|
||||
success: false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final data = subResp['data'] ?? subResp;
|
||||
// backend success flag might be in different places; check robustly
|
||||
final bool success = (data is Map && (data['success'] == true || subResp['success'] == true)) || (subResp['statusCode'] == 200);
|
||||
|
||||
if (success) {
|
||||
_showDialog(
|
||||
title: "Subscription Active ✅",
|
||||
message: data['message'] ?? "Tenant subscribed successfully.",
|
||||
success: true,
|
||||
);
|
||||
} else {
|
||||
_showDialog(
|
||||
title: "Subscription Failed",
|
||||
message: data['message'] ?? "Subscription API did not confirm success.",
|
||||
success: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handlePaymentError(PaymentFailureResponse response) {
|
||||
logSafe("❌ Payment Failed: ${response.message}");
|
||||
isProcessing = false;
|
||||
|
||||
@ -6,7 +6,7 @@ class SubscriptionController extends GetxController {
|
||||
var isLoading = true.obs;
|
||||
|
||||
// Frequency tabs
|
||||
final frequencies = ['monthly', 'quarterly', 'halfyearly', 'yearly'];
|
||||
final frequencies = ['monthly', 'quarterly', 'half-yearly', 'yearly'];
|
||||
var selectedFrequency = 'monthly'.obs;
|
||||
|
||||
@override
|
||||
|
||||
@ -104,4 +104,9 @@ class ApiEndpoints {
|
||||
// Payment Module API Endpoints
|
||||
static const String createOrder = "/payment/create-order";
|
||||
static const String verifyPayment = "/payment/verify-payment";
|
||||
|
||||
// Tenant endpoints
|
||||
static const String createTenantSelf = '/Tenant/self/create';
|
||||
static const String tenantSubscribe = '/Tenant/self/subscription';
|
||||
static const String tenantRenewSubscription = '/Tenant/renew/subscription';
|
||||
}
|
||||
|
||||
@ -283,16 +283,20 @@ class ApiService {
|
||||
Map<String, String>? additionalHeaders,
|
||||
Duration customTimeout = extendedTimeout,
|
||||
bool hasRetried = false,
|
||||
bool requireAuth = true, // ✅ added
|
||||
}) async {
|
||||
String? token = await _getToken();
|
||||
String? token;
|
||||
|
||||
if (requireAuth) {
|
||||
token = await _getToken();
|
||||
if (token == null) return null;
|
||||
}
|
||||
|
||||
final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
|
||||
logSafe(
|
||||
"PUT $uri\nHeaders: ${_headers(token)}\nBody: $body",
|
||||
);
|
||||
|
||||
final headers = {
|
||||
..._headers(token),
|
||||
'Content-Type': 'application/json',
|
||||
if (requireAuth && token != null) ..._headers(token),
|
||||
if (additionalHeaders != null) ...additionalHeaders,
|
||||
};
|
||||
|
||||
@ -305,19 +309,23 @@ class ApiService {
|
||||
.put(uri, headers: headers, body: jsonEncode(body))
|
||||
.timeout(customTimeout);
|
||||
|
||||
if (response.statusCode == 401 && !hasRetried) {
|
||||
logSafe("Unauthorized PUT. Attempting token refresh...");
|
||||
if (response.statusCode == 401 && requireAuth && !hasRetried) {
|
||||
logSafe("⚠️ Unauthorized PUT. Attempting token refresh...");
|
||||
if (await AuthService.refreshToken()) {
|
||||
return await _putRequest(endpoint, body,
|
||||
return await _putRequest(
|
||||
endpoint,
|
||||
body,
|
||||
additionalHeaders: additionalHeaders,
|
||||
customTimeout: customTimeout,
|
||||
hasRetried: true);
|
||||
hasRetried: true,
|
||||
requireAuth: requireAuth,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
logSafe("HTTP PUT Exception: $e", level: LogLevel.error);
|
||||
logSafe("❌ HTTP PUT Exception: $e", level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -2247,6 +2255,59 @@ class ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create tenant (self)
|
||||
static Future<Map<String, dynamic>?> createTenantSelf(
|
||||
Map<String, dynamic> payload) async {
|
||||
try {
|
||||
final response = await _postRequest(
|
||||
ApiEndpoints.createTenantSelf,
|
||||
payload,
|
||||
requireAuth: false, // likely public
|
||||
);
|
||||
if (response == null) return null;
|
||||
return _parseResponse(response, label: "Create Tenant Self");
|
||||
} catch (e) {
|
||||
logSafe("❌ Exception in createTenantSelf: $e", level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe tenant (after successful payment)
|
||||
static Future<Map<String, dynamic>?> subscribeTenant(
|
||||
Map<String, dynamic> payload) async {
|
||||
try {
|
||||
final response = await _postRequest(
|
||||
ApiEndpoints.tenantSubscribe,
|
||||
payload,
|
||||
requireAuth:
|
||||
true, // likely needs auth if user logged in; set according to backend
|
||||
);
|
||||
if (response == null) return null;
|
||||
return _parseResponse(response, label: "Tenant Subscribe");
|
||||
} catch (e) {
|
||||
logSafe("❌ Exception in subscribeTenant: $e", level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Renew tenant subscription (PUT)
|
||||
static Future<Map<String, dynamic>?> renewTenantSubscription(
|
||||
Map<String, dynamic> payload) async {
|
||||
try {
|
||||
final response = await _putRequest(
|
||||
ApiEndpoints.tenantRenewSubscription,
|
||||
payload,
|
||||
requireAuth: true,
|
||||
);
|
||||
if (response == null) return null;
|
||||
return _parseResponse(response, label: "Renew Tenant Subscription");
|
||||
} catch (e) {
|
||||
logSafe("❌ Exception in renewTenantSubscription: $e",
|
||||
level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// === Employee APIs ===
|
||||
/// Search employees by first name and last name only (not middle name)
|
||||
/// Returns a list of up to 10 employee records matching the search string.
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:marco/helpers/services/api_endpoints.dart';
|
||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||
import 'package:marco/helpers/services/app_logger.dart';
|
||||
import 'package:marco/helpers/services/auth_service.dart';
|
||||
import 'package:marco/helpers/services/api_service.dart';
|
||||
import 'package:marco/model/tenant/tenant_list_model.dart';
|
||||
|
||||
/// Abstract interface for tenant service functionality
|
||||
@ -160,4 +161,44 @@ class TenantService implements ITenantService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> createTenant(
|
||||
Map<String, dynamic> payload) async {
|
||||
try {
|
||||
logSafe("🟢 Creating tenant: $payload");
|
||||
final resp = await ApiService.createTenantSelf(payload);
|
||||
logSafe("🧩 createTenant response: $resp");
|
||||
return resp;
|
||||
} catch (e, s) {
|
||||
logSafe("❌ Exception in createTenant: $e\n$s", level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> subscribeTenant(
|
||||
Map<String, dynamic> payload) async {
|
||||
try {
|
||||
logSafe("🟢 Subscribing tenant: $payload");
|
||||
final resp = await ApiService.subscribeTenant(payload);
|
||||
logSafe("🧩 subscribeTenant response: $resp");
|
||||
return resp;
|
||||
} catch (e, s) {
|
||||
logSafe("❌ Exception in subscribeTenant: $e\n$s", level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> renewSubscription(
|
||||
Map<String, dynamic> payload) async {
|
||||
try {
|
||||
logSafe("🟢 Renewing subscription: $payload");
|
||||
final resp = await ApiService.renewTenantSubscription(payload);
|
||||
logSafe("🧩 renewSubscription response: $resp");
|
||||
return resp;
|
||||
} catch (e, s) {
|
||||
logSafe("❌ Exception in renewSubscription: $e\n$s",
|
||||
level: LogLevel.error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import 'package:marco/view/document/user_document_screen.dart';
|
||||
import 'package:marco/view/tenant/tenant_selection_screen.dart';
|
||||
import 'package:marco/view/payment/payment_screen.dart';
|
||||
import 'package:marco/view/subscriptions/subscriptions_screen.dart';
|
||||
import 'package:marco/view/tenant/tenant_create_screen.dart';
|
||||
|
||||
class AuthMiddleware extends GetMiddleware {
|
||||
@override
|
||||
@ -34,6 +35,7 @@ class AuthMiddleware extends GetMiddleware {
|
||||
'/subscription',
|
||||
'/payment',
|
||||
'/select-tenant',
|
||||
'/create-tenant',
|
||||
];
|
||||
|
||||
// ✅ Allow any route that starts with these public routes
|
||||
@ -110,6 +112,11 @@ getPageRoute() {
|
||||
middlewares: [AuthMiddleware()]),
|
||||
// Payment
|
||||
GetPage(name: '/payment', page: () => PaymentScreen()),
|
||||
GetPage(
|
||||
name: '/create-tenant',
|
||||
page: () => const TenantCreateScreen(),
|
||||
),
|
||||
|
||||
GetPage(
|
||||
name: '/subscription',
|
||||
page: () => SubscriptionScreen(),
|
||||
|
||||
@ -66,7 +66,10 @@ class _PaymentScreenState extends State<PaymentScreen> {
|
||||
final controller = _controller!;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Payment", style: TextStyle(color: Colors.black),),
|
||||
title: const Text(
|
||||
"Payment",
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
body: Padding(
|
||||
@ -90,10 +93,18 @@ class _PaymentScreenState extends State<PaymentScreen> {
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
// Extract optional IDs passed via Get.arguments
|
||||
final String tenantEnquireId =
|
||||
(args['tenantEnquireId'] ?? '') as String;
|
||||
final String planId = (args['planId'] ?? '') as String;
|
||||
|
||||
await controller.startPayment(
|
||||
amount: finalAmount,
|
||||
description: finalDescription,
|
||||
context: context,
|
||||
tenantEnquireId:
|
||||
tenantEnquireId.isNotEmpty ? tenantEnquireId : null,
|
||||
planId: planId.isNotEmpty ? planId : null,
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
||||
@ -106,11 +106,11 @@ class SubscriptionScreen extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.toNamed('/payment', arguments: {
|
||||
'amount': plan['price'] ?? 0,
|
||||
'description':
|
||||
plan['planName'] ?? 'Subscription',
|
||||
Get.toNamed('/create-tenant', arguments: {
|
||||
'planId': plan['id'] ?? '',
|
||||
'amount': plan['price'] ?? 0,
|
||||
'planName':
|
||||
plan['planName'] ?? 'Subscription',
|
||||
});
|
||||
},
|
||||
child: MyText.bodyMedium(
|
||||
|
||||
263
lib/view/tenant/tenant_create_screen.dart
Normal file
263
lib/view/tenant/tenant_create_screen.dart
Normal file
@ -0,0 +1,263 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:marco/helpers/services/tenant_service.dart';
|
||||
import 'package:marco/helpers/services/app_logger.dart';
|
||||
|
||||
class TenantCreateScreen extends StatefulWidget {
|
||||
const TenantCreateScreen({super.key});
|
||||
|
||||
@override
|
||||
State<TenantCreateScreen> createState() => _TenantCreateScreenState();
|
||||
}
|
||||
|
||||
class _TenantCreateScreenState extends State<TenantCreateScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _firstNameCtrl = TextEditingController();
|
||||
final _lastNameCtrl = TextEditingController();
|
||||
final _orgNameCtrl = TextEditingController();
|
||||
final _emailCtrl = TextEditingController();
|
||||
final _contactCtrl = TextEditingController();
|
||||
final _billingCtrl = TextEditingController();
|
||||
final _orgSizeCtrl = TextEditingController();
|
||||
final _industryIdCtrl = TextEditingController();
|
||||
final _referenceCtrl = TextEditingController();
|
||||
|
||||
final TenantService _tenantService = TenantService();
|
||||
bool _loading = false;
|
||||
|
||||
// Values from subscription screen
|
||||
late final String planId;
|
||||
late final double amount;
|
||||
late final String planName;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final args = (Get.arguments ?? {}) as Map<String, dynamic>;
|
||||
planId = args['planId'] ?? '';
|
||||
amount = (args['amount'] ?? 0).toDouble();
|
||||
planName = args['planName'] ?? 'Subscription';
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_firstNameCtrl.dispose();
|
||||
_lastNameCtrl.dispose();
|
||||
_orgNameCtrl.dispose();
|
||||
_emailCtrl.dispose();
|
||||
_contactCtrl.dispose();
|
||||
_billingCtrl.dispose();
|
||||
_orgSizeCtrl.dispose();
|
||||
_industryIdCtrl.dispose();
|
||||
_referenceCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
final payload = {
|
||||
"firstName": _firstNameCtrl.text.trim(),
|
||||
"lastName": _lastNameCtrl.text.trim(),
|
||||
"organizationName": _orgNameCtrl.text.trim(),
|
||||
"email": _emailCtrl.text.trim(),
|
||||
"contactNumber": _contactCtrl.text.trim(),
|
||||
"billingAddress": _billingCtrl.text.trim(),
|
||||
"organizationSize": _orgSizeCtrl.text.trim(),
|
||||
"industryId": _industryIdCtrl.text.trim(),
|
||||
"reference": _referenceCtrl.text.trim(),
|
||||
};
|
||||
|
||||
setState(() => _loading = true);
|
||||
final resp = await _tenantService.createTenant(payload);
|
||||
setState(() => _loading = false);
|
||||
|
||||
if (resp == null) {
|
||||
Get.snackbar("Error", "Failed to create tenant. Try again later.",
|
||||
backgroundColor: Colors.red, colorText: Colors.white);
|
||||
return;
|
||||
}
|
||||
|
||||
final data = resp['data'] ?? resp;
|
||||
final tenantEnquireId =
|
||||
data['tenantEnquireId'] ?? data['id'] ?? data['tenantId'];
|
||||
|
||||
if (tenantEnquireId == null) {
|
||||
logSafe("❌ Create tenant response missing id: $resp");
|
||||
Get.snackbar("Error", "Tenant created but server didn't return id.",
|
||||
backgroundColor: Colors.red, colorText: Colors.white);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Go to payment screen with all details
|
||||
Get.toNamed('/payment', arguments: {
|
||||
'amount': amount,
|
||||
'description': 'Subscription for ${_orgNameCtrl.text}',
|
||||
'tenantEnquireId': tenantEnquireId,
|
||||
'planId': planId,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final divider = Divider(color: Colors.grey.shade300, height: 32);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Create Tenant"),
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Personal Info",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _firstNameCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "First Name *",
|
||||
hintText: "e.g., John",
|
||||
prefixIcon: Icon(Icons.person_outline),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (v) => v == null || v.isEmpty ? "Required" : null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _lastNameCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Last Name *",
|
||||
hintText: "e.g., Doe",
|
||||
prefixIcon: Icon(Icons.person_outline),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (v) => v == null || v.isEmpty ? "Required" : null,
|
||||
),
|
||||
divider,
|
||||
const Text(
|
||||
"Organization Details",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _orgNameCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Organization Name *",
|
||||
hintText: "e.g., MarcoBMS",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (v) => v == null || v.isEmpty ? "Required" : null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _billingCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Billing Address",
|
||||
hintText: "e.g., 123 Main Street, Mumbai",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _orgSizeCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Organization Size",
|
||||
hintText: "e.g., 1-10, 11-50",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _industryIdCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Industry ID (UUID)",
|
||||
hintText: "e.g., 3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
divider,
|
||||
const Text(
|
||||
"Contact Info",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _emailCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Email",
|
||||
hintText: "e.g., john.doe@example.com",
|
||||
prefixIcon: Icon(Icons.email_outlined),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (v) =>
|
||||
v == null || !v.contains('@') ? "Invalid email" : null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _contactCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Contact Number",
|
||||
hintText: "e.g., +91 9876543210",
|
||||
prefixIcon: Icon(Icons.phone_outlined),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
divider,
|
||||
const Text(
|
||||
"Other Details",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _referenceCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Reference",
|
||||
hintText: "e.g., Referral Name or Code",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
onPressed: _loading ? null : _submit,
|
||||
child: _loading
|
||||
? const SizedBox(
|
||||
height: 22,
|
||||
width: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Text(
|
||||
"Create Tenant & Continue",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user