diff --git a/asset/images/file-type-pdf2.svg b/asset/images/file-type-pdf2.svg new file mode 100644 index 0000000..e10b671 --- /dev/null +++ b/asset/images/file-type-pdf2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/images/microsoft-word.svg b/asset/images/microsoft-word.svg new file mode 100644 index 0000000..dcfc1c3 --- /dev/null +++ b/asset/images/microsoft-word.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/common/widget/shared/input_text_field.dart b/lib/common/widget/shared/input_text_field.dart index 131314e..5a6cb7a 100644 --- a/lib/common/widget/shared/input_text_field.dart +++ b/lib/common/widget/shared/input_text_field.dart @@ -16,6 +16,10 @@ class InputTextField extends StatefulWidget { final FormFieldValidator? onVaildate; final Function(String)? onTextChange; final List? inputFormatters; + final Color cursorColor; + final Color focusedBorderColor; + final Color textColor; + final Color inactiveBorderColor; const InputTextField( {super.key, @@ -30,7 +34,11 @@ class InputTextField extends StatefulWidget { this.onTextChange, this.errorText, this.onVaildate, - this.inputFormatters}); + this.inputFormatters, + this.cursorColor = AppColors.focusedBorder, + this.focusedBorderColor = AppColors.focusedBorder, + this.inactiveBorderColor = AppColors.inActiveBorder, + this.textColor = AppColors.primaryTextColor}); @override State createState() => _InputTextFieldState(); @@ -65,6 +73,8 @@ class _InputTextFieldState extends State { TextFormField( key: widget.formKey, controller: widget.controller, + cursorColor: widget.cursorColor, + style: FontStyles.labelMedium.copyWith(color: widget.textColor), obscureText: widget.isObscureText ? obscureText : false, decoration: InputDecoration( labelText: widget.labelText, @@ -75,20 +85,25 @@ class _InputTextFieldState extends State { errorText: null, suffixIcon: widget.isObscureText ? InkWell( + splashColor: Colors.transparent, + canRequestFocus: false, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + highlightColor: Colors.transparent, onTap: () => changeVisiblity(), child: Container( padding: const EdgeInsets.only(right: 20), child: isPasswordVisible - ? const Icon(Icons.visibility) - : const Icon(Icons.visibility_off), + ? Icon(Icons.visibility,color: widget.textColor,) + : Icon(Icons.visibility_off,color: widget.textColor), ), ) : const SizedBox.shrink(), enabledBorder: OutlineInputBorder( borderSide: BorderSide( - color: _isHovered - ? AppColors.focusedBorder // Hover border color - : AppColors.inActiveBorder, // Default border color + color: (widget.errorText != null && widget.errorText!.isNotEmpty)? Colors.red : _isHovered + ? widget.focusedBorderColor // Hover border color + : widget.inactiveBorderColor, // Default border color ), ), focusedBorder: OutlineInputBorder( @@ -96,12 +111,12 @@ class _InputTextFieldState extends State { color: (widget.errorText != null && widget.errorText!.isNotEmpty) ? Colors.red - : AppColors.focusedBorder, // Active border color + : widget.focusedBorderColor, // Active border color ), ), - disabledBorder: const OutlineInputBorder( + disabledBorder: OutlineInputBorder( borderSide: BorderSide( - color: AppColors.inActiveBorder, // Disabled border color + color: widget.inactiveBorderColor, // Disabled border color ), ), focusedErrorBorder: const OutlineInputBorder( diff --git a/lib/constants/color_constants.dart b/lib/constants/color_constants.dart index 0783794..5055ddf 100644 --- a/lib/constants/color_constants.dart +++ b/lib/constants/color_constants.dart @@ -28,4 +28,10 @@ class AppColors{ static const Color sellerSignUpBg = Color(0xffe6e7ea); static const Color sellerButtonBg = Color(0xff101522); static const Color greyButtonBg = Color(0xff949698); + static const Color stepperInactiveBorder = Color(0xffdcdce5); + static const Color defaultIconColor = Color(0xff788295); + static const Color defaultTextColor = Color(0xff788295); + static const Color sellerTextFieldBorder = Color(0xffe2e4e9); + static const Color sellerTextFieldTextColor = Color(0xff848e9f); + static const Color greenColor = Color(0xff4a9d07); } \ No newline at end of file diff --git a/lib/data/config/dio_client.dart b/lib/data/config/dio_client.dart index 0fd660c..8a4fb38 100644 --- a/lib/data/config/dio_client.dart +++ b/lib/data/config/dio_client.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:frontend_ecommerce/config/endpoints_config.dart'; +import 'package:frontend_ecommerce/data/secured_storage/secured_storage.dart'; class ApiService { final Dio _dio = Dio(); @@ -15,9 +16,9 @@ class ApiService { // Add interceptors _dio.interceptors.add( InterceptorsWrapper( - onRequest: (options, handler) { + onRequest: (options, handler) async{ // Modify the request (e.g., add headers or tokens) - options.headers['Authorization'] = 'Bearer your_token'; + options.headers['authorization'] = await getAuthorizationHeader(); return handler.next(options); // Continue with the request }, onResponse: (response, handler) { @@ -81,4 +82,19 @@ class ApiService { rethrow; } } + + + + Future getAuthorizationHeader() async { + final String accessToken = 'Bearer ${await _getToken()}'; + return accessToken; + } + + + Future _getToken() async{ + final SecureStorage secureStorage = SecureStorage(); + return await secureStorage.getAccessToken(); + } + + } diff --git a/lib/data/data_source/seller/seller_api_endpoints.dart b/lib/data/data_source/seller/seller_api_endpoints.dart index ae25f23..2f08738 100644 --- a/lib/data/data_source/seller/seller_api_endpoints.dart +++ b/lib/data/data_source/seller/seller_api_endpoints.dart @@ -6,4 +6,6 @@ class SellerApiEndpoints { static const String sellerVerifyOTP = "${EndpointsConfig.backendUrl}seller/verify-otp"; static const String sellerRegisterSocial = "${EndpointsConfig.backendUrl}seller/register-social"; static const String sellerLogin = "${EndpointsConfig.backendUrl}seller/login"; + static const String sellerCreateContract = "${EndpointsConfig.backendUrl}seller/creating-contract"; + static const String getCreateContractDetails = "${EndpointsConfig.backendUrl}seller/get-contract-details"; } \ No newline at end of file diff --git a/lib/data/data_source/seller/seller_data_source.dart b/lib/data/data_source/seller/seller_data_source.dart index 0865985..ffe212a 100644 --- a/lib/data/data_source/seller/seller_data_source.dart +++ b/lib/data/data_source/seller/seller_data_source.dart @@ -1,6 +1,8 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:frontend_ecommerce/data/data_source/seller/seller_api_endpoints.dart'; +import 'package:frontend_ecommerce/features/seller/authentication/model/seller_create_contract_request_model.dart'; +import 'package:frontend_ecommerce/features/seller/authentication/model/seller_create_contract_response_model.dart'; import 'package:frontend_ecommerce/features/seller/authentication/model/seller_otp_verify_request_model.dart'; import 'package:frontend_ecommerce/features/seller/authentication/model/seller_otp_verify_response_model.dart'; @@ -16,6 +18,8 @@ abstract class BuyerDataSource { Future? sellerRegister(BuildContext context, SellerRegisterRequestModel data); Future? generateOtp(BuildContext context, SellerOTPRequestModel data); Future? verifyOtp(BuildContext context, SellerOTPVerifyRequestModel data); + Future? createContract(BuildContext context, SellerCreateContractRequestModel data); + Future? getContractDetails(BuildContext context); } class SellerDataSourceImpl implements BuyerDataSource{ @@ -82,4 +86,48 @@ class SellerDataSourceImpl implements BuyerDataSource{ } + @override + Future? createContract(BuildContext context, SellerCreateContractRequestModel data) async{ + try{ + final apiService = ApiService(context); + ErrorResponseModel? errorResponseModel; + SellerCreateContractModel? sellerCreateContractModel; + + Response response = await apiService.post(SellerApiEndpoints.sellerCreateContract, data.toJson()); + if(response.statusCode == 200){ + sellerCreateContractModel = SellerCreateContractModel.fromJson(response.data); + } else{ + errorResponseModel = ErrorResponseModel.fromJson(response.data); + } + + return SellerCreateContractResponseModel(sellerCreateContractModel: sellerCreateContractModel, errorResponseModel: errorResponseModel); + }catch(e){ + rethrow; + } + + } + + + @override + Future? getContractDetails(BuildContext context) async{ + try{ + final apiService = ApiService(context); + ErrorResponseModel? errorResponseModel; + SellerCreateContractModel? sellerCreateContractModel; + + Response response = await apiService.get(SellerApiEndpoints.getCreateContractDetails); + if(response.statusCode == 200){ + sellerCreateContractModel = SellerCreateContractModel.fromJson(response.data); + } else{ + errorResponseModel = ErrorResponseModel.fromJson(response.data); + } + + return SellerCreateContractResponseModel(sellerCreateContractModel: sellerCreateContractModel, errorResponseModel: errorResponseModel); + }catch(e){ + rethrow; + } + + } + + } \ No newline at end of file diff --git a/lib/features/seller/authentication/model/seller_create_contract_request_model.dart b/lib/features/seller/authentication/model/seller_create_contract_request_model.dart new file mode 100644 index 0000000..d3c90bf --- /dev/null +++ b/lib/features/seller/authentication/model/seller_create_contract_request_model.dart @@ -0,0 +1,80 @@ +class SellerCreateContractRequestModel { + final String? firstName; + final String? lastName; + final String? email; + final String? password; + final String? confirmPassword; + final int? stepIndex; + final String? panName; + final String? panNumber; + final String? gstNumber; + + SellerCreateContractRequestModel({ + this.firstName, + this.lastName, + this.email, + this.password, + this.confirmPassword, + this.stepIndex, + this.panName, + this.panNumber, + this.gstNumber + }); + + SellerCreateContractRequestModel copyWith({ + String? firstName, + String? lastName, + String? email, + String? password, + String? confirmPassword, + int? stepIndex, + String? panName, + String? panNumber, + String? gstNumber, + }) { + return SellerCreateContractRequestModel( + firstName: firstName ?? this.firstName, + lastName: lastName ?? this.lastName, + email: email ?? this.email, + password: password ?? this.password, + confirmPassword: confirmPassword ?? this.confirmPassword, + stepIndex: stepIndex ?? this.stepIndex, + gstNumber: gstNumber ?? this.gstNumber, + panName: panName ?? this.panName, + panNumber: panNumber ?? this.panNumber + ); + } + + factory SellerCreateContractRequestModel.fromJson(Map json){ + return SellerCreateContractRequestModel( + firstName: json["firstName"], + lastName: json["lastName"], + email: json["email"], + password: json["password"], + confirmPassword: json["confirm_password"], + stepIndex: json["stepIndex"], + gstNumber: json["gstNumber"], + panNumber: json["panNumber"], + panName: json["panName"] + ); + } + + Map toJson() { + return { + "firstName": firstName, + "lastName": lastName, + "email": email, + "password": password, + "confirm_password": confirmPassword, + "stepIndex": stepIndex, + "panNumber": panNumber, + "panName": panName, + "gstNumber": gstNumber + }; + } + + @override + String toString(){ + return "$firstName $lastName $email $password $confirmPassword $stepIndex"; + } +} \ No newline at end of file diff --git a/lib/features/seller/authentication/model/seller_create_contract_response_model.dart b/lib/features/seller/authentication/model/seller_create_contract_response_model.dart new file mode 100644 index 0000000..e0e3209 --- /dev/null +++ b/lib/features/seller/authentication/model/seller_create_contract_response_model.dart @@ -0,0 +1,51 @@ +import 'package:frontend_ecommerce/features/seller/authentication/model/seller_register_model.dart'; + +import '../../../shared/model/error_response.dart'; + +class SellerCreateContractModel { + SellerCreateContractModel({ + required this.status, + required this.data + }); + + final int? status; + final Data? data; + + SellerCreateContractModel copyWith({ + int? status, + Data? data, + String? token + }) { + return SellerCreateContractModel( + status: status ?? this.status, + data: data ?? this.data + ); + } + + factory SellerCreateContractModel.fromJson(Map json){ + return SellerCreateContractModel( + status: json["status"], + data: json["result"] == null ? null : Data.fromJson(json["result"]), + ); + } + + Map toJson() => { + "status": status, + "result": data?.toJson(), + }; + + @override + String toString(){ + return "$status, $data"; + } +} + +class SellerCreateContractResponseModel{ + final SellerCreateContractModel? sellerCreateContractModel; + final ErrorResponseModel? errorResponseModel; + + SellerCreateContractResponseModel({ + required this.sellerCreateContractModel, + required this.errorResponseModel + }); +} \ No newline at end of file diff --git a/lib/features/seller/authentication/model/seller_otp_verify_response_model.dart b/lib/features/seller/authentication/model/seller_otp_verify_response_model.dart index 546cc48..1e391a0 100644 --- a/lib/features/seller/authentication/model/seller_otp_verify_response_model.dart +++ b/lib/features/seller/authentication/model/seller_otp_verify_response_model.dart @@ -6,31 +6,37 @@ class SellerVerifyOTPRegisterModel { SellerVerifyOTPRegisterModel({ required this.status, required this.data, + required this.token }); final int? status; final Data? data; + final String? token; SellerVerifyOTPRegisterModel copyWith({ int? status, - Data? data + Data? data, + String? token }) { return SellerVerifyOTPRegisterModel( status: status ?? this.status, - data: data ?? this.data + data: data ?? this.data, + token: token ?? this.token ); } factory SellerVerifyOTPRegisterModel.fromJson(Map json){ return SellerVerifyOTPRegisterModel( status: json["status"], - data: json["result"] == null ? null : Data.fromJson(json["result"]) + data: json["result"] == null ? null : Data.fromJson(json["result"]), + token: json["token"] ); } Map toJson() => { "status": status, - "result": data?.toJson() + "result": data?.toJson(), + "token" : token }; @override diff --git a/lib/features/seller/authentication/model/seller_register_model.dart b/lib/features/seller/authentication/model/seller_register_model.dart index 7674037..66178e9 100644 --- a/lib/features/seller/authentication/model/seller_register_model.dart +++ b/lib/features/seller/authentication/model/seller_register_model.dart @@ -62,6 +62,7 @@ class Data { required this.shippingMethod, required this.shippingCharge, required this.authToken, + required this.stepIndex }); final int? id; @@ -74,13 +75,14 @@ class Data { final DateTime? createdAt; final String? phoneNumber; final bool? isOtpVerified; - final bool? gstNumber; + final String? gstNumber; final bool? gstOtpVerified; final String? storeName; final Pickup? pickUp; final String? shippingMethod; final String? shippingCharge; final String? authToken; + final int? stepIndex; Data copyWith({ int? id, @@ -93,13 +95,14 @@ class Data { DateTime? createdAt, String? phoneNumber, bool? isOtpVerified, - bool? gstNumber, + String? gstNumber, bool? gstOtpVerified, String? storeName, Pickup? pickUp, String? shippingMethod, String? shippingCharge, String? authToken, + int? stepIndex }) { return Data( id: id ?? this.id, @@ -119,6 +122,7 @@ class Data { shippingMethod: shippingMethod ?? this.shippingMethod, shippingCharge: shippingCharge ?? this.shippingCharge, authToken: authToken ?? this.authToken, + stepIndex : stepIndex ?? this.stepIndex ); } @@ -141,6 +145,7 @@ class Data { shippingMethod: json["shippingMethod"], shippingCharge: json["shippingCharge"], authToken: json["authToken"], + stepIndex: json["stepIndex"] ); } @@ -162,11 +167,12 @@ class Data { "shippingMethod": shippingMethod, "shippingCharge": shippingCharge, "authToken": authToken, + "stepIndex" : stepIndex }; @override String toString() { - return "$id, $firstName, $lastName, $email, $password, $confirmPassword, $updatedAt, $createdAt, $phoneNumber, $isOtpVerified, $gstNumber, $gstOtpVerified, $storeName, ${pickUp?.toString()}, $shippingMethod, $shippingCharge, $authToken"; + return "$id, $firstName, $lastName, $email, $password, $confirmPassword, $updatedAt, $createdAt, $phoneNumber, $isOtpVerified, $gstNumber, $gstOtpVerified, $storeName, ${pickUp?.toString()}, $shippingMethod, $shippingCharge, $authToken, $stepIndex"; } } diff --git a/lib/features/seller/authentication/view/create_contract_screen.dart b/lib/features/seller/authentication/view/create_contract_screen.dart new file mode 100644 index 0000000..f978be7 --- /dev/null +++ b/lib/features/seller/authentication/view/create_contract_screen.dart @@ -0,0 +1,747 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:desktop_drop/desktop_drop.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend_ecommerce/common/styles/font_style.dart'; +import 'package:frontend_ecommerce/constants/color_constants.dart'; +import 'package:frontend_ecommerce/features/shared/custom_stepper.dart'; +import 'package:frontend_ecommerce/features/shared/details_footer_section.dart'; +import 'package:frontend_ecommerce/features/shared/dotted_border.dart'; +import 'package:provider/provider.dart'; + +import '../../../../common/widget/shared/custom_button.dart'; +import '../../../../common/widget/shared/custom_snackbar.dart'; +import '../../../../common/widget/shared/input_text_field.dart'; +import '../../../../constants/screen_size_constants.dart'; +import '../../../../utils/utils.dart'; +import '../view_model/create_contract_view_model.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class CreateContractScreen extends StatefulWidget { + const CreateContractScreen({super.key, this.step = '1'}); + + final String? step; + + @override + State createState() => _CreateContractScreenState(); +} + +class _CreateContractScreenState extends State { + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final auth = Provider.of(context, listen: false); + auth.setInitialStepperData(context,currentStep: int.tryParse(widget.step ?? '1') ?? 1); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Material( + color: AppColors.white, + child: Scaffold( + backgroundColor: AppColors.white, + appBar: AppBar( + backgroundColor: AppColors.inActiveBorder, + surfaceTintColor: AppColors.inActiveBorder, + title: InkWell( + onTap: (){}, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 20), + child: Text('ChoiceUs INC',style: FontStyles.labelLarge.copyWith(fontWeight: FontWeight.bold,color: AppColors.primaryColor),), + ), + ), + titleSpacing: 0, + centerTitle: false, + actions: [ + InkWell( + onTap: (){ + }, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Padding( + padding: const EdgeInsetsDirectional.only(end: 20), + child: Text('Settings',style: FontStyles.labelMedium.copyWith(fontWeight: FontWeight.bold),), + ), + ) + ], + ), + body: Consumer(builder: (context, provider, child){ + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth < ScreenSizeConstants.mobileBreakPoint) { + return mobileLayout(context,provider); + } else if (constraints.maxWidth < ScreenSizeConstants.tabletBreakPoint) { + return tabletLayout(context,provider); + } else if (constraints.maxWidth < ScreenSizeConstants.desktopBreakPoint) { + return desktopOrTvLayout(context,provider); + } else { + return desktopOrTvLayout(context,provider); + } + } + );} + ), + bottomNavigationBar: const DetailsFooterSection(), + )); + } + + Widget mobileLayout(BuildContext context, CreateContractViewModel provider){ + int currentStepIndex = provider.stepperData.indexWhere((element) => element.isCurrentStep == true); + return Padding( + padding: const EdgeInsetsDirectional.only(start: 20, end: 20, top: 20), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Icon( + Icons.keyboard_backspace_outlined, + color: AppColors.defaultIconColor, + ), + const SizedBox(width: 50,), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: CustomStepper(data: provider.stepperData,onChanged: (stepIndex){ + provider.goToSelectedStepperIndex(stepIndex); + },), + ), + ], + ), + ) + ], + ), + const SizedBox(height: 30,), + Expanded( + child: SingleChildScrollView( + child: Align( + alignment: Alignment.center, + child: Container( + width: 400, + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: Column( + children: [ + buildInfoUI(provider,currentStepIndex), + const SizedBox(height: 20,), + bottomButton(false, provider, currentStepIndex), + const SizedBox(height: 50,), + ], + )), + ), + ), + ), + ], + ), + ); + } + + Widget tabletLayout(BuildContext context, CreateContractViewModel provider){ + int currentStepIndex = provider.stepperData.indexWhere((element) => element.isCurrentStep == true); + return Padding( + padding: const EdgeInsetsDirectional.only(start: 20, end: 20, top: 20), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + const Icon( + Icons.keyboard_backspace_outlined, + color: AppColors.defaultIconColor, + ), + const SizedBox(width: 50,), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: CustomStepper(data: provider.stepperData,onChanged: (stepIndex){ + provider.goToSelectedStepperIndex(stepIndex); + },), + ), + ], + ), + ) + ], + ), + const SizedBox(height: 30,), + Expanded( + child: SingleChildScrollView( + child: Align( + alignment: Alignment.center, + child: Container( + width: 400, + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: Column( + children: [ + buildInfoUI(provider,currentStepIndex), + const SizedBox(height: 20,), + bottomButton(false, provider, currentStepIndex), + const SizedBox(height: 50,), + ], + )), + ), + ), + ), + ], + ), + ); + } + + Widget desktopOrTvLayout(BuildContext context, CreateContractViewModel provider){ + int currentStepIndex = provider.stepperData.indexWhere((element) => element.isCurrentStep == true); + return Padding( + padding: const EdgeInsetsDirectional.only(start: 20, top: 50, end: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: CustomStepper(data: provider.stepperData,onChanged: (stepIndex){ + provider.goToSelectedStepperIndex(stepIndex); + },), + ), + ], + ), + ) + ], + ), + const SizedBox(height: 30,), + Expanded( + child: SingleChildScrollView( + child: Align( + alignment: Alignment.center, + child: Container( + width: 400, + constraints: const BoxConstraints( + maxWidth: 400 + ), + child: Column( + children: [ + buildInfoUI(provider,currentStepIndex), + const SizedBox(height: 20,), + bottomButton(false, provider, currentStepIndex), + const SizedBox(height: 50,), + ], + )), + ), + ), + ), + ], + ), + ); + } + + Widget buildInfoUI(CreateContractViewModel provider, int currentStepIndex){ + if(currentStepIndex != -1){ + switch(currentStepIndex){ + case 0: + return buildGeneralInfo(provider); + case 1: + return buildGSTInfo(provider); + case 2: + return buildStoreDetails(provider); + case 3: + return buildTaxAndAccountInfo(provider); + case 4: + return buildShippingInfo(provider); + } + } + return Container(); + } + + + Widget buildGeneralInfo(CreateContractViewModel provider){ + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center(child: Text(AppLocalizations.of(context).add_general_information, style: FontStyles.labelMedium.copyWith(fontWeight: FontWeight.bold, color: AppColors.primaryTextColor),)), + const SizedBox(height: 10,), + Center(child: Text(AppLocalizations.of(context).fill_out_the_basic_details_for_this_contract, style: FontStyles.labelSmall.copyWith(color: AppColors.defaultTextColor),)), + const SizedBox(height: 20,), + Text('${AppLocalizations.of(context).first_name} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.firstNameFormKey, + controller: provider.firstNameController, + errorText: provider.firstNameErrorText, + hintText: '', + labelText: '', + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + provider.validateName(context, value, true); + }, + ), + if(provider.firstNameErrorText?.isNotEmpty ?? false) + const SizedBox(height: 10,), + Text('${AppLocalizations.of(context).last_name} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.lastNameFormKey, + controller: provider.lastNameController, + errorText: provider.lastNameErrorText, + hintText: '', + labelText: '', + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + provider.validateName(context, value, false); + }, + ), + if(provider.lastNameErrorText?.isNotEmpty ?? false) + const SizedBox(height: 10,), + Text('${AppLocalizations.of(context).email} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.emailFormKey, + controller: provider.emailController, + errorText: provider.emailErrorText, + hintText: '', + labelText: '', + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + provider.validateEmail(context, value); + }, + ), + if(provider.emailErrorText?.isNotEmpty ?? false) + const SizedBox(height: 10,), + Text('${AppLocalizations.of(context).password} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.passwordFormKey, + controller: provider.passwordController, + errorText: provider.passwordErrorText, + hintText: '', + labelText: '', + isObscureText: true, + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + provider.validatePassword(context, value, true); + if(provider.confirmPasswordController.text.isNotEmpty){ + provider.validatePassword(context, provider.confirmPasswordController.text, false, password: value); + } + }, + ), + if(provider.passwordErrorText?.isNotEmpty ?? false) + const SizedBox(height: 10,), + Text('${AppLocalizations.of(context).confirm_password} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.confirmPasswordFormKey, + controller: provider.confirmPasswordController, + errorText: provider.confirmPasswordErrorText, + hintText: '', + labelText: '', + isObscureText: true, + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + provider.validatePassword(context, value, false, password: provider.passwordController.text); + }, + ), + ], + ); + } + + Widget buildGSTInfo(CreateContractViewModel provider){ + return ValueListenableBuilder( + valueListenable: provider.rxSellGstProducts, + builder: (context, value, child) { + if(value == 1){ + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center(child: Text(AppLocalizations.of(context).add_gst_information, style: FontStyles.labelMedium.copyWith(fontWeight: FontWeight.bold, color: AppColors.primaryTextColor),)), + const SizedBox(height: 10,), + Center(child: Text(AppLocalizations.of(context).gst_number_mandatory_text, style: FontStyles.labelSmall.copyWith(color: AppColors.defaultTextColor),)), + const SizedBox(height: 20,), + Text('${AppLocalizations.of(context).enter_15_digit_gst_number} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.gstNoFormKey, + controller: provider.gstNoTextField, + errorText: provider.gstNoErrorText, + hintText: '', + labelText: '', + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + int cursorPosition = provider.gstNoTextField.selection.base.offset; + provider.gstNoTextField.text = value.toUpperCase(); + if (cursorPosition <= provider.gstNoTextField.text.length) { + provider.gstNoTextField.selection = TextSelection.collapsed(offset: cursorPosition); + } else { + provider.gstNoTextField.selection = TextSelection.collapsed(offset: provider.gstNoTextField.text.length); + } + provider.validGstNumber(context, provider.gstNoTextField.text); + }, + ), + + Row( + children: [ + Radio(hoverColor: Colors.transparent, + value: 2, groupValue: provider.rxSellGstProducts.value, onChanged: (value){ + provider.rxSellGstProducts.value = value ?? 0; + if(provider.rxSellGstProducts.value == 1){ + provider.validGstNumber(context, provider.gstNoTextField.text); + }else{ + provider.validPanNumber(context, provider.panNoTextField.text); + } + provider.notifyListener(); + }), + Flexible(child: InkWell( + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + focusColor: Colors.transparent, + splashColor: Colors.transparent, + onTap: (){ + if(provider.rxSellGstProducts.value == 1){ + provider.rxSellGstProducts.value = 2; + }else{ + provider.rxSellGstProducts.value = 1; + } + + if(provider.rxSellGstProducts.value == 1){ + provider.validGstNumber(context, provider.gstNoTextField.text); + }else{ + provider.validPanNumber(context, provider.panNoTextField.text); + } + provider.notifyListener(); + }, + child: Text(AppLocalizations.of(context).i_sell_only_books))), + ], + ) + + ], + ); + }else{ + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center(child: Text(AppLocalizations.of(context).add_pan_card_details, style: FontStyles.labelMedium.copyWith(fontWeight: FontWeight.bold, color: AppColors.primaryTextColor),)), + const SizedBox(height: 10,), + Center(child: Text(AppLocalizations.of(context).pan_number_mandatory_info, style: FontStyles.labelSmall.copyWith(color: AppColors.defaultTextColor),)), + const SizedBox(height: 20,), + Text('${AppLocalizations.of(context).enter_10_digit_pan_number} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.panNoFormKey, + controller: provider.panNoTextField, + errorText: provider.panNoErrorText, + hintText: '', + labelText: '', + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + int cursorPosition = provider.panNoTextField.selection.base.offset; + provider.panNoTextField.text = value.toUpperCase(); + if (cursorPosition <= provider.panNoTextField.text.length) { + provider.panNoTextField.selection = TextSelection.collapsed(offset: cursorPosition); + } else { + provider.panNoTextField.selection = TextSelection.collapsed(offset: provider.panNoTextField.text.length); + } + provider.validPanNumber(context, provider.panNoTextField.text); + }, + ), + if(provider.rxSellGstProducts.value == 2 && provider.validPanNumber(context, provider.panNoTextField.text)) + panDetails(provider), + + Row( + children: [ + Radio(hoverColor: Colors.transparent, + value: 1, groupValue: provider.rxSellGstProducts.value, onChanged: (value){ + provider.rxSellGstProducts.value = value ?? 0; + if(provider.rxSellGstProducts.value == 1){ + provider.validGstNumber(context, provider.gstNoTextField.text); + }else{ + provider.validPanNumber(context, provider.panNoTextField.text); + } + provider.notifyListener(); + }), + Flexible(child: InkWell( + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + focusColor: Colors.transparent, + splashColor: Colors.transparent, + onTap: (){ + if(provider.rxSellGstProducts.value == 1){ + provider.rxSellGstProducts.value = 2; + }else{ + provider.rxSellGstProducts.value = 1; + } + + if(provider.rxSellGstProducts.value == 1){ + provider.validGstNumber(context, provider.gstNoTextField.text); + }else{ + provider.validPanNumber(context, provider.panNoTextField.text); + } + provider.notifyListener(); + + }, + child: Text(AppLocalizations.of(context).i_have_a_gst_number))), + ], + ) + ], + ); + } + } + ); + } + + + Widget panDetails(CreateContractViewModel provider){ + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('${AppLocalizations.of(context).pan_name} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor),), + const SizedBox(height: 10,), + InputTextField( + formKey: provider.panNameFormKey, + controller: provider.panNameTextField, + errorText: provider.panNameErrorText, + hintText: '', + labelText: '', + cursorColor: AppColors.sellerTextFieldTextColor, + textColor: AppColors.sellerTextFieldTextColor, + focusedBorderColor: AppColors.sellerTextFieldBorder, + inactiveBorderColor: AppColors.sellerTextFieldBorder, + onTextChange: (value) { + int cursorPosition = provider.panNameTextField.selection.base.offset; + provider.panNameTextField.text = value.toUpperCase(); + if (cursorPosition <= provider.panNameTextField.text.length) { + provider.panNameTextField.selection = TextSelection.collapsed(offset: cursorPosition); + } else { + provider.panNameTextField.selection = TextSelection.collapsed(offset: provider.panNameTextField.text.length); + } + provider.validatePanName(context, provider.panNameTextField.text); + }, + ), + if(provider.panNameErrorText?.isNotEmpty ?? false) + const SizedBox(height: 10,), + + Text('${AppLocalizations.of(context).upload_pan_document} *', style: FontStyles.labelSmall.copyWith(color: AppColors.primaryTextColor, fontWeight: FontWeight.bold),), + const SizedBox(height: 10,), + ValueListenableBuilder( + valueListenable: provider.isDragging, + builder: (context,isDragging, child) { + return DottedBorder( + borderType: BorderType.RRect, + dashPattern: const [6, 6], + strokeWidth: 1, + color: isDragging ? AppColors.primaryColor : AppColors.focusedBorder, + child: provider.filePickerResult.count <= 0 ? DropTarget( + onDragEntered: (value){ + provider.isDragging.value =true; + }, + onDragExited: (value){ + provider.isDragging.value =false; + }, + onDragDone: (value) async{ + if(Utils.getFileExtension(value.files[0].name ?? '') != '.png' && Utils.getFileExtension(value.files[0].name ?? '') != '.jpg' && Utils.getFileExtension(value.files[0].name ?? '') != '.pdf' && Utils.getFileExtension(value.files[0].name ?? '') != '.doc'){ + CustomSnackbar( + message: AppLocalizations.of(context).the_selected_file_is_not_allowed, + context: context) + .showSnackbar(); + }else{ + provider.filePickerResult = FilePickerResult([PlatformFile.fromMap({'name' : value.files[0].name, 'size' : await value.files[0].length(), 'path' : value.files[0].path, 'bytes': await value.files[0].readAsBytes()})]); + provider.notifyListener(); + } + }, child: Stack( + children: [ + Row( + children: [ + Flexible( + child: InkWell( + onTap: (){ + FilePicker.platform.pickFiles( + allowMultiple: false, + type: FileType.custom, + allowedExtensions: ['.pdf','.jpg','.png','.doc'] + ).then((value){ + if(value?.xFiles.where((test)=> Utils.getFileExtension(test.name) != '.png' && Utils.getFileExtension(test.name) != '.jpg' && Utils.getFileExtension(test.name) != '.pdf' && Utils.getFileExtension(test.name) != '.doc').isNotEmpty?? false){ + CustomSnackbar( + message: AppLocalizations.of(context).the_selected_file_is_not_allowed, + context: context) + .showSnackbar(); + }else{ + provider.filePickerResult = value ?? const FilePickerResult([]); + provider.notifyListener(); + } + + }); + }, + child: Container( + decoration: const BoxDecoration( + color: AppColors.primaryColor, + ), + padding: const EdgeInsetsDirectional.only(start: 20, end: 20, top: 10, bottom: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.upload, size: 20,), + Flexible(child: Text(AppLocalizations.of(context).upload)) + ], + ), + ), + ), + ), + const SizedBox(width: 10,), + Flexible(child: Text(AppLocalizations.of(context).drag_here_to_upload)) + ], + ), + if(isDragging)Positioned.fill( + child: Container( + decoration: BoxDecoration( + color: isDragging? AppColors.greenColor.withOpacity(0.5) : Colors.transparent + ), + ), + ) + ], + ), + ) : + Row( + children: [ + Expanded(child: Utils.getFileExtension(provider.filePickerResult.files[0].name ?? '') == '.png' || Utils.getFileExtension(provider.filePickerResult.files[0].name ?? '') == '.jpg'? Image.memory(Uint8List.fromList(provider.filePickerResult.files[0].bytes?.toList() ?? []),height: 300,) : + Utils.getFileExtension(provider.filePickerResult.files[0].name ?? '') == '.pdf'? Column( + children: [ + SvgPicture.asset(height: 100,'asset/images/file-type-pdf2.svg'), + const SizedBox(height: 5,), + Text(provider.filePickerResult.files[0].name, style: FontStyles.labelSmall,) + ], + ) : Column( + children: [ + SvgPicture.asset(height: 100,'asset/images/microsoft-word.svg'), + const SizedBox(height: 5,), + Text(provider.filePickerResult.files[0].name, style: FontStyles.labelSmall,) + ], + ) + ) + ], + ) + ); + } + ), + const SizedBox(height: 5,), + InkWell( + onTap: (){ + provider.filePickerResult = const FilePickerResult([]); + provider.notifyListener(); + }, + child: Text( + AppLocalizations.of(context).clear_file, + style: FontStyles.labelMedium.copyWith(color: AppColors.primaryColor), + ), + ), + const SizedBox(height: 10,), + RichText(text: TextSpan( + text: '${AppLocalizations.of(context).allowed_extension}: ', + style: FontStyles.labelMedium.copyWith(fontWeight: FontWeight.bold), + children: const [ + TextSpan( + text: 'pdf, jpg, png, doc', + style: FontStyles.labelSmall, + ) + ] + )), + const SizedBox(height: 20,), + ], + ); + } + + + + Widget buildStoreDetails(CreateContractViewModel provider){ + return Column( + children: [], + ); + } + Widget buildTaxAndAccountInfo(CreateContractViewModel provider){ + return Column( + children: [], + ); + } + Widget buildShippingInfo(CreateContractViewModel provider){ + return Column( + children: [], + ); + } + + Widget bottomButton(bool isLoading, CreateContractViewModel provider, int currentIndex,){ + return SizedBox( + width: MediaQuery.of(context).size.width, + child: ValueListenableBuilder(valueListenable: provider.isBottomButtonLoading, builder: (context, value, child){ + return CustomButton( + buttonText: AppLocalizations.of(context).next_button_text, + onPressed: () { + if(enableDisableButtonBg(currentIndex,provider)){ + provider.updateDetails(context,currentIndex); + } + }, + isLoading: value, + loadingColor: AppColors.white, + backgroundColor: enableDisableButtonBg(currentIndex, provider)? AppColors.primaryColor : AppColors.greyButtonBg, + textStyle: FontStyles.labelMedium + .copyWith(color: AppColors.white)); + }) + ); + } + + + bool enableDisableButtonBg(int currentIndex, CreateContractViewModel provider){ + if(currentIndex ==0){ + return provider.checkBasicDetails(context); + }else if (currentIndex == 1){ + if(provider.rxSellGstProducts.value == 1){ + return provider.validGstNumber(context, provider.gstNoTextField.text); + }else{ + return provider.validPanNumber(context, provider.panNoTextField.text) && provider.validatePanName(context, provider.panNameTextField.text, canShowError: false) && provider.validatePanDocument(context); + } + } + + return false; + } + + +} diff --git a/lib/features/seller/authentication/view_model/create_contract_view_model.dart b/lib/features/seller/authentication/view_model/create_contract_view_model.dart new file mode 100644 index 0000000..fbde551 --- /dev/null +++ b/lib/features/seller/authentication/view_model/create_contract_view_model.dart @@ -0,0 +1,347 @@ + + +import 'package:dio/dio.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:frontend_ecommerce/features/seller/authentication/model/seller_create_contract_request_model.dart'; +import 'package:frontend_ecommerce/route/router_constant.dart'; + +import '../../../../common/widget/shared/custom_snackbar.dart'; +import '../../../../data/data_source/seller/seller_data_source.dart'; +import '../../../../utils/utils.dart'; +import '../../../../utils/validators/pattern_validator.dart'; +import '../../../shared/model/stepper_data.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'dart:html' as html; + +class CreateContractViewModel extends ChangeNotifier{ + final SellerDataSourceImpl sellerDataSource = SellerDataSourceImpl(); + final int? steps = 5; + final List stepperData = []; + final GlobalKey firstNameFormKey = GlobalKey(); + final GlobalKey lastNameFormKey = GlobalKey(); + final GlobalKey emailFormKey = GlobalKey(); + final GlobalKey passwordFormKey = GlobalKey(); + final GlobalKey confirmPasswordFormKey = GlobalKey(); + final GlobalKey gstNoFormKey = GlobalKey(); + final GlobalKey panNoFormKey = GlobalKey(); + final GlobalKey panNameFormKey = GlobalKey(); + TextEditingController firstNameController = TextEditingController(text: ""); + TextEditingController lastNameController = TextEditingController(text: ""); + TextEditingController emailController = TextEditingController(text: ""); + TextEditingController passwordController = TextEditingController(text: ""); + TextEditingController confirmPasswordController = TextEditingController(text: ""); + TextEditingController gstNoTextField = TextEditingController(text: ""); + TextEditingController panNoTextField = TextEditingController(text: ""); + TextEditingController panNameTextField = TextEditingController(text: ""); + String? firstNameErrorText; + String? lastNameErrorText; + String? emailErrorText; + String? passwordErrorText; + String? confirmPasswordErrorText; + String? gstNoErrorText; + String? panNoErrorText; + String? panNameErrorText; + ValueNotifier rxSellGstProducts = ValueNotifier(1); + FilePickerResult filePickerResult = const FilePickerResult([]); + ValueNotifier isDragging = ValueNotifier(false); + ValueNotifier isBottomButtonLoading = ValueNotifier(false); + + setInitialStepperData(context, {int currentStep = -1}){ + stepperData.clear(); + stepperData.add(StepperData(headerTitle: AppLocalizations.of(context).personal_information,isCurrentStep: false, stepCompleted: false)); + stepperData.add(StepperData(headerTitle: AppLocalizations.of(context).gst_verification,isCurrentStep: false, stepCompleted: false)); + stepperData.add(StepperData(headerTitle: AppLocalizations.of(context).store_details,isCurrentStep: false, stepCompleted: false)); + stepperData.add(StepperData(headerTitle: AppLocalizations.of(context).tax_and_account_information,isCurrentStep: false, stepCompleted: false)); + stepperData.add(StepperData(headerTitle: AppLocalizations.of(context).shipping_information,isCurrentStep: false, stepCompleted: false)); + + + + getContractDetails(context); + } + + + T validateEmail(context, String? value) { + emailErrorText = PatternValidator.isValidEmail( + context, + value, + AppLocalizations.of(context).email_mandatory, + AppLocalizations.of(context).invalid_email); + if(T == dynamic){ + notifyListeners(); + } + return (emailErrorText?.isEmpty?? true) as T; + } + + T validateName(context, String? value, bool isFirstName) { + if (isFirstName) { + firstNameErrorText = PatternValidator.isValidName( + context, + value, + AppLocalizations.of(context).first_name_mandatory, + AppLocalizations.of(context).invalid_first_name); + if(T == dynamic){ + notifyListeners(); + } + return (firstNameErrorText?.isEmpty ?? true) as T; + } else { + lastNameErrorText = PatternValidator.isValidName( + context, + value, + AppLocalizations.of(context).last_name_mandatory, + AppLocalizations.of(context).invalid_last_name); + if(T == dynamic){ + notifyListeners(); + } + return (lastNameErrorText?.isEmpty ?? true) as T; + } + } + + T validatePassword(context, String? value, bool isPassword, + {String password = ""}) { + if (isPassword) { + passwordErrorText = PatternValidator.isValidPassword( + context, + value, + AppLocalizations.of(context).password_mandatory, + AppLocalizations.of(context).invalid_password); + if(T == dynamic){ + notifyListeners(); + } + return (passwordErrorText?.isEmpty ?? true) as T; + } else { + confirmPasswordErrorText = PatternValidator.isValidPassword( + context, + value, + AppLocalizations.of(context).confirm_password_mandatory, + AppLocalizations.of(context).invalid_password, + confirmPassword: password, + misMatchErrorText: + AppLocalizations.of(context).password_confrim_password_match); + if(T == dynamic){ + notifyListeners(); + } + return (confirmPasswordErrorText?.isEmpty ?? true) as T; + } + } + + T validGstNumber(context, String value){ + if(T == dynamic){ + if(PatternValidator.isValidGstNumber(context, value)?? false){ + gstNoErrorText = ''; + }else{ + gstNoErrorText = AppLocalizations.of(context).please_enter_a_valid_gst_number; + } + + notifyListeners(); + return (gstNoErrorText?.isEmpty ?? true) as T; + }else{ + String gstNoErrorText = ''; + if(PatternValidator.isValidGstNumber(context, value)?? false){ + gstNoErrorText = ''; + }else{ + gstNoErrorText = AppLocalizations.of(context).please_enter_a_valid_gst_number; + } + return (gstNoErrorText.isEmpty) as T; + } + } + + + T validPanNumber(context, String value){ + if(T == dynamic){ + if(PatternValidator.isValidPanNumber(context, value)?? false){ + panNoErrorText = ''; + }else{ + panNoErrorText = AppLocalizations.of(context).please_enter_a_valid_pan_card_number; + } + notifyListeners(); + return (panNoErrorText?.isEmpty ?? true) as T; + }else{ + String panNoErrorText = ''; + if(PatternValidator.isValidPanNumber(context, value)?? false){ + panNoErrorText = ''; + }else{ + panNoErrorText = AppLocalizations.of(context).please_enter_a_valid_pan_card_number; + } + return (panNoErrorText.isEmpty) as T; + } + } + + + bool validatePanName(context, String value, {bool canShowError = true}){ + if(value.isNotEmpty){ + panNameErrorText = ''; + if(canShowError){ + notifyListeners(); + } + return true; + } + if(canShowError){ + panNameErrorText = AppLocalizations.of(context).please_enter_pan_name; + notifyListeners(); + } + return false; + } + + bool validatePanDocument(context){ + if(filePickerResult.count > 0){ + return true; + } + return false; + } + + + setStepCompleted(int index, bool isCompleted){ + stepperData[index].stepCompleted = isCompleted; + } + + goToNextStep(int currentIndex){ + + if(currentIndex < (stepperData.length - 1)){ + for(int i = 0; i< stepperData.length; i++){ + stepperData[i].isCurrentStep = false; + } + stepperData[currentIndex + 1].isCurrentStep = true; + updateStepInUrl(currentIndex+2); + notifyListeners(); + }else{ + /// Todo: Go to next page after full completion of create contract; + } + } + + + void updateStepInUrl(int step){ + html.window.history.pushState({}, '', '/#${AppPages.auth}${AppPages.sellerCreateContract}?step=$step'); + } + + + goToSelectedStepperIndex(int index){ + for(int i = 0; i< stepperData.length; i++){ + stepperData[i].isCurrentStep = false; + } + stepperData[index].isCurrentStep = true; + updateStepInUrl(index + 1); + notifyListeners(); + } + + + bool checkBasicDetails(context){ + if(emailController.text.isEmpty || firstNameController.text.isEmpty || lastNameController.text.isEmpty || passwordController.text.isEmpty || confirmPasswordController.text.isEmpty){ + return false; + } + + if(validateEmail(context, emailController.text) && validateName(context, firstNameController.text, true) && validateName(context, lastNameController.text, false) && validatePassword(context, passwordController.text, true) && validatePassword(context, confirmPasswordController.text, false, password: passwordController.text)){ + + return true; + } + + return false; + } + + + void notifyListener(){ + notifyListeners(); + } + + + void getContractDetails(BuildContext context) async{ + try{ + + await sellerDataSource + .getContractDetails(context) + ?.then((onValue) async { + isBottomButtonLoading.value = false; + if (onValue.sellerCreateContractModel != null) { + if (onValue.sellerCreateContractModel?.status == 200) { + int currentStep = onValue.sellerCreateContractModel?.data?.stepIndex ?? 1; + updateStepInUrl(currentStep); + for(int i = 0; i <= currentStep; i++){ + if(i + 1 == currentStep){ + stepperData[i].isCurrentStep = true; + } + + if(i < (currentStep -1)){ + stepperData[i].stepCompleted = true; + } + } + notifyListeners(); + } else { + CustomSnackbar( + message: onValue.errorResponseModel!.message ?? "", + context: context) + .showSnackbar(); + } + } else { + CustomSnackbar( + message: onValue.errorResponseModel!.message ?? "", + context: context) + .showSnackbar(); + } + }); + + + }on DioException catch(e){ + Utils.showApiExceptionError(e, context); + } + } + + + + + + + void updateDetails(BuildContext context, int currentIndex) async { + try { + SellerCreateContractRequestModel sellerCreateContractRequestModel = + SellerCreateContractRequestModel(); + + if (currentIndex == 0) { + sellerCreateContractRequestModel = SellerCreateContractRequestModel( + firstName: firstNameController.text, + lastName: lastNameController.text, + email: emailController.text, + password: passwordController.text, + confirmPassword: confirmPasswordController.text, + stepIndex: currentIndex + 2); + } else if (currentIndex == 1) { + if (rxSellGstProducts.value == 1) { + sellerCreateContractRequestModel = SellerCreateContractRequestModel( + stepIndex: currentIndex + 2, gstNumber: gstNoTextField.text); + } else { + sellerCreateContractRequestModel = SellerCreateContractRequestModel( + stepIndex: currentIndex + 2, + panNumber: panNoTextField.text, + panName: panNameTextField.text); + } + } else if (currentIndex == 2) { + } else if (currentIndex == 3) { + } else if (currentIndex == 4) {} + + isBottomButtonLoading.value = true; + await sellerDataSource + .createContract(context, sellerCreateContractRequestModel) + ?.then((onValue) async { + isBottomButtonLoading.value = false; + if (onValue.sellerCreateContractModel != null) { + if (onValue.sellerCreateContractModel?.status == 200) { + setStepCompleted(currentIndex, true); + goToNextStep(currentIndex); + } else { + CustomSnackbar( + message: onValue.errorResponseModel!.message ?? "", + context: context) + .showSnackbar(); + } + } else { + CustomSnackbar( + message: onValue.errorResponseModel!.message ?? "", + context: context) + .showSnackbar(); + } + }); + } on DioException catch (e) { + isBottomButtonLoading.value = false; + Utils.showApiExceptionError(e, context); + } + } +} diff --git a/lib/features/seller/authentication/view_model/seller_register_view_model.dart b/lib/features/seller/authentication/view_model/seller_register_view_model.dart index ba5a9cc..9fdf8fd 100644 --- a/lib/features/seller/authentication/view_model/seller_register_view_model.dart +++ b/lib/features/seller/authentication/view_model/seller_register_view_model.dart @@ -3,12 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:frontend_ecommerce/features/seller/authentication/model/seller_otp_request_model.dart'; import 'package:frontend_ecommerce/features/seller/authentication/model/seller_otp_verify_request_model.dart'; +import 'package:frontend_ecommerce/features/seller/authentication/model/seller_otp_verify_response_model.dart'; import 'package:go_router/go_router.dart'; import '../../../../common/widget/shared/custom_snackbar.dart'; import '../../../../data/data_source/seller/seller_data_source.dart'; import '../../../../data/secured_storage/secured_storage.dart'; import '../../../../route/router_constant.dart'; +import '../../../../utils/utils.dart'; import '../../../../utils/validators/pattern_validator.dart'; import '../model/seller_register_model.dart'; import '../model/seller_register_request_model.dart'; @@ -210,7 +212,7 @@ class SellerRegisterViewModel extends ChangeNotifier{ }on DioException catch (e){ registerLoading = false; notifyListeners(); - showApiExceptionError(e, context); + Utils.showApiExceptionError(e, context); } } else { if(phoneNumber.isNotEmpty){ @@ -262,12 +264,12 @@ class SellerRegisterViewModel extends ChangeNotifier{ }on DioException catch (e) { registerLoading = false; notifyListeners(); - showApiExceptionError(e, context); + Utils.showApiExceptionError(e, context); } } - verifyOtp(context) async{ + verifyOtp(BuildContext context) async{ if(otp.length >= 6){ verifyOtpLoading = true; notifyListeners(); @@ -280,11 +282,8 @@ class SellerRegisterViewModel extends ChangeNotifier{ verifyOtpLoading = false; notifyListeners(); if(onValue.sellerVerifyOTPRegisterModel?.status == 200 ){ - // Move to Home page - CustomSnackbar( - message: 'OTP Verified', - context: context) - .showSnackbar(); + setStorageValues(onValue); + context.goNamed(NamedRoute.sellerContractCreate,queryParameters: {'step' : '1'}); }else{ CustomSnackbar( message: onValue.errorResponseModel!.message ?? "", @@ -303,7 +302,7 @@ class SellerRegisterViewModel extends ChangeNotifier{ }on DioException catch (e) { verifyOtpLoading = false; notifyListeners(); - showApiExceptionError(e, context); + Utils.showApiExceptionError(e, context); } }else{ CustomSnackbar( @@ -314,11 +313,11 @@ class SellerRegisterViewModel extends ChangeNotifier{ } - void setStorageValues(SellerRegisterModel? registerData) async{ - _secureStorage.setUserEmail(registerData?.data?.email ?? ''); - _secureStorage.setUserFirstName(registerData?.data?.firstName ?? ''); - _secureStorage.setUserLastName(registerData?.data?.lastName ?? ''); - _secureStorage.setAccessToken(registerData?.accessToken ?? ''); + void setStorageValues(SellerVerifyOTPResponseModel? registerData) async{ + _secureStorage.setUserEmail(registerData?.sellerVerifyOTPRegisterModel?.data?.email ?? ''); + _secureStorage.setUserFirstName(registerData?.sellerVerifyOTPRegisterModel?.data?.firstName ?? ''); + _secureStorage.setUserLastName(registerData?.sellerVerifyOTPRegisterModel?.data?.lastName ?? ''); + _secureStorage.setAccessToken(registerData?.sellerVerifyOTPRegisterModel?.token ?? ''); } void toggleOtpScreen(bool? openVerifyOtpScreen){ @@ -327,12 +326,5 @@ class SellerRegisterViewModel extends ChangeNotifier{ } - void showApiExceptionError(dynamic error, BuildContext context){ - CustomSnackbar( - message: error.response?.data != null ? (error.response?.data is Map && (error.response?.data as Map).containsKey('message') ? error.response?.data['message'] : 'Something went wrong') : 'Something went wrong', - context: context) - .showSnackbar(); - } - } \ No newline at end of file diff --git a/lib/features/shared/custom_stepper.dart b/lib/features/shared/custom_stepper.dart new file mode 100644 index 0000000..bbb48bf --- /dev/null +++ b/lib/features/shared/custom_stepper.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:frontend_ecommerce/common/styles/font_style.dart'; + +import '../../constants/color_constants.dart'; +import 'model/stepper_data.dart'; + +class CustomStepper extends StatelessWidget { + const CustomStepper({super.key, this.data,this.onChanged}); + + final List? data; + final Function(int stepIndex)? onChanged; + + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: stepperChildrenHeader(), + ), + ); + } + + List stepperChildrenHeader(){ + List items = []; + + for(int i =0 ; i< (data?.length ?? 0); i++){ + items.add( + IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + InkWell( + focusColor: Colors.transparent, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + onTap: (){ + if(data?[i].stepCompleted == null || data?[i].stepCompleted == false){ + if(i == 0){ + if(data?[i].isCurrentStep == false){ + onChanged?.call(i); + } + }else{ + if(data?[i -1].stepCompleted ?? false){ + if(data?[i].isCurrentStep == false){ + onChanged?.call(i); + } + } + } + } + }, + child: Column(children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 20,end: 20), + child: Text(data?[i].headerTitle ?? '', style: FontStyles.labelMedium.copyWith(fontWeight: FontWeight.w400,color: data?[i].isCurrentStep ?? false ? AppColors.primaryColor : AppColors.defaultTextColor)), + ), + const SizedBox(height: 10,), + Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + Row( + children: [ + Flexible( + child: Container( + height: 1.5, + color: (i <= 0)? Colors.transparent : (data?[i-1].stepCompleted ?? false)? AppColors.primaryColor : AppColors.stepperInactiveBorder, + ), + ), + Flexible( + child: Container( + height: 1.5, + color: (i < ((data?.length ?? 0) -1 )) ? (data?[i].stepCompleted ?? false)? AppColors.primaryColor : AppColors.stepperInactiveBorder : Colors.transparent, + ), + ) + ], + ), + Container( + decoration: BoxDecoration( + color:data?[i].isCurrentStep ?? false? AppColors.white : ( data?[i].stepCompleted ?? false)? AppColors.primaryColor : AppColors.stepperInactiveBorder, + border: Border.all(color: (data?[i].isCurrentStep ?? false)? AppColors.primaryColor : Colors.transparent), + borderRadius: BorderRadius.circular(20) + ), + constraints: const BoxConstraints( + maxWidth: 10, + maxHeight: 10 + ), + padding: const EdgeInsets.all(20), + ), + ], + ), + ],), + ) + ], + ) + ], + ), + ) + ); + } + + + return items; + } + +} diff --git a/lib/features/shared/dash_painter.dart b/lib/features/shared/dash_painter.dart new file mode 100644 index 0000000..39ce093 --- /dev/null +++ b/lib/features/shared/dash_painter.dart @@ -0,0 +1,151 @@ +part of 'dotted_border.dart'; + +typedef PathBuilder = Path Function(Size); + +class _DashPainter extends CustomPainter { + final double strokeWidth; + final List dashPattern; + final Color color; + final BorderType borderType; + final Radius radius; + final StrokeCap strokeCap; + final PathBuilder? customPath; + final EdgeInsets padding; + + _DashPainter({ + this.strokeWidth = 2, + this.dashPattern = const [3, 1], + this.color = Colors.black, + this.borderType = BorderType.Rect, + this.radius = const Radius.circular(0), + this.strokeCap = StrokeCap.butt, + this.customPath, + this.padding = EdgeInsets.zero, + }) { + assert(dashPattern.isNotEmpty, 'Dash Pattern cannot be empty'); + } + + @override + void paint(Canvas canvas, Size originalSize) { + final Size size; + if (padding == EdgeInsets.zero) { + size = originalSize; + } else { + canvas.translate(padding.left, padding.top); + size = Size( + originalSize.width - padding.horizontal, + originalSize.height - padding.vertical, + ); + } + + Paint paint = Paint() + ..strokeWidth = strokeWidth + ..color = color + ..strokeCap = strokeCap + ..style = PaintingStyle.stroke; + + Path _path; + if (customPath != null) { + _path = dashPath( + customPath!(size), + dashArray: CircularIntervalList(dashPattern), + ); + } else { + _path = _getPath(size); + } + + canvas.drawPath(_path, paint); + } + + /// Returns a [Path] based on the the [borderType] parameter + Path _getPath(Size size) { + Path path; + switch (borderType) { + case BorderType.Circle: + path = _getCirclePath(size); + break; + case BorderType.RRect: + path = _getRRectPath(size, radius); + break; + case BorderType.Rect: + path = _getRectPath(size); + break; + case BorderType.Oval: + path = _getOvalPath(size); + break; + } + + return dashPath(path, dashArray: CircularIntervalList(dashPattern)); + } + + /// Returns a circular path of [size] + Path _getCirclePath(Size size) { + double w = size.width; + double h = size.height; + double s = size.shortestSide; + + return Path() + ..addRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH( + w > s ? (w - s) / 2 : 0, + h > s ? (h - s) / 2 : 0, + s, + s, + ), + Radius.circular(s / 2), + ), + ); + } + + /// Returns a Rounded Rectangular Path with [radius] of [size] + Path _getRRectPath(Size size, Radius radius) { + return Path() + ..addRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH( + 0, + 0, + size.width, + size.height, + ), + radius, + ), + ); + } + + /// Returns a path of [size] + Path _getRectPath(Size size) { + return Path() + ..addRect( + Rect.fromLTWH( + 0, + 0, + size.width, + size.height, + ), + ); + } + + /// Return an oval path of [size] + Path _getOvalPath(Size size) { + return Path() + ..addOval( + Rect.fromLTWH( + 0, + 0, + size.width, + size.height, + ), + ); + } + + @override + bool shouldRepaint(_DashPainter oldDelegate) { + return oldDelegate.strokeWidth != this.strokeWidth || + oldDelegate.color != this.color || + oldDelegate.dashPattern != this.dashPattern || + oldDelegate.padding != this.padding || + oldDelegate.borderType != this.borderType; + } +} diff --git a/lib/features/shared/details_footer_section.dart b/lib/features/shared/details_footer_section.dart new file mode 100644 index 0000000..2bba81d --- /dev/null +++ b/lib/features/shared/details_footer_section.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:frontend_ecommerce/common/styles/font_style.dart'; + +import '../../constants/color_constants.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class DetailsFooterSection extends StatefulWidget { + const DetailsFooterSection({super.key}); + + @override + State createState() => _DetailsFooterSectionState(); +} + +class _DetailsFooterSectionState extends State { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsetsDirectional.only(start: 25,end: 25), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10,), + const Divider( + height: 1, + color: AppColors.inActiveBorder, + ), + const SizedBox(height: 10,), + Row( + children: [ + const Icon(Icons.lock), + const SizedBox(width: 5,), + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 3), + child: Text(AppLocalizations.of(context).save_information_policy_msg,style: FontStyles.labelMedium,), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(bottom: 0), + child: InkWell( + onTap: (){}, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Text(AppLocalizations.of(context).contact_seller_support, style: FontStyles.labelMedium.copyWith(color: AppColors.primaryColor),)), + ), + Column( + children: [ + Text(AppLocalizations.of(context).copy_right_text,style: FontStyles.labelSmall,), + const SizedBox(height: 5,), + InkWell( + onTap: (){}, + child: Container( + decoration: const BoxDecoration( + color: AppColors.primaryColor, + borderRadius: BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10)) + ), + padding: const EdgeInsetsDirectional.only(top: 5, bottom: 5, start: 20, end: 20), + child: Text(AppLocalizations.of(context).feedback_capital, style: FontStyles.labelSmall.copyWith(color: AppColors.white),)), + ), + ], + ) + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/shared/dotted_border.dart b/lib/features/shared/dotted_border.dart new file mode 100644 index 0000000..fdc3732 --- /dev/null +++ b/lib/features/shared/dotted_border.dart @@ -0,0 +1,80 @@ +library dotted_border; + +import 'package:flutter/material.dart'; +import 'package:path_drawing/path_drawing.dart'; + +part 'dash_painter.dart'; + +/// Add a dotted border around any [child] widget. The [strokeWidth] property +/// defines the width of the dashed border and [color] determines the stroke +/// paint color. [CircularIntervalList] is populated with the [dashPattern] to +/// render the appropriate pattern. The [radius] property is taken into account +/// only if the [borderType] is [BorderType.RRect]. A [customPath] can be passed in +/// as a parameter if you want to draw a custom shaped border. +class DottedBorder extends StatelessWidget { + final Widget child; + final EdgeInsets padding; + final EdgeInsets borderPadding; + final double strokeWidth; + final Color color; + final List dashPattern; + final BorderType borderType; + final Radius radius; + final StrokeCap strokeCap; + final PathBuilder? customPath; + + DottedBorder({ + required this.child, + this.color = Colors.black, + this.strokeWidth = 1, + this.borderType = BorderType.Rect, + this.dashPattern = const [3, 1], + this.padding = const EdgeInsets.all(2), + this.borderPadding = EdgeInsets.zero, + this.radius = const Radius.circular(0), + this.strokeCap = StrokeCap.butt, + this.customPath, + }) { + assert(_isValidDashPattern(dashPattern), 'Invalid dash pattern'); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned.fill( + child: CustomPaint( + painter: _DashPainter( + padding: borderPadding, + strokeWidth: strokeWidth, + radius: radius, + color: color, + borderType: borderType, + dashPattern: dashPattern, + customPath: customPath, + strokeCap: strokeCap, + ), + ), + ), + Padding( + padding: padding, + child: child, + ), + ], + ); + } + + /// Compute if [dashPattern] is valid. The following conditions need to be met + /// * Cannot be null or empty + /// * If [dashPattern] has only 1 element, it cannot be 0 + bool _isValidDashPattern(List? dashPattern) { + Set? _dashSet = dashPattern?.toSet(); + if (_dashSet == null) return false; + if (_dashSet.length == 1 && _dashSet.elementAt(0) == 0.0) return false; + if (_dashSet.length == 0) return false; + return true; + } +} + +/// The different supported BorderTypes +enum BorderType { Circle, RRect, Rect, Oval } diff --git a/lib/features/shared/model/stepper_data.dart b/lib/features/shared/model/stepper_data.dart new file mode 100644 index 0000000..a0840a2 --- /dev/null +++ b/lib/features/shared/model/stepper_data.dart @@ -0,0 +1,9 @@ +class StepperData{ + bool? stepCompleted; + String? headerTitle; + bool? isCurrentStep; + bool? canEdit; + + StepperData({this.stepCompleted, this.headerTitle, this.isCurrentStep, this.canEdit}); + +} \ No newline at end of file diff --git a/lib/localization/intl_de.arb b/lib/localization/intl_de.arb index 3125c35..18e7387 100644 --- a/lib/localization/intl_de.arb +++ b/lib/localization/intl_de.arb @@ -47,5 +47,37 @@ "not_receive_otp" : "OTP not received?", "send_again" : "Send again", "resend_otp_in" : "Resend OTP in", - "back_button_text": "Back" + "back_button_text": "Back", + "next_button_text" : "Next", + "add_general_information" : "Add General Information", + "fill_out_the_basic_details_for_this_contract" : "Fill out the basic details for this contract", + "fill_out_the_basic_details_for_this_contract" : "Fill out the basic details for this contract", + "optional" : "optional", +"add_gst_information" : "Add GST Information", +"gst_number_mandatory_text" : "GST Number is mandatory to sell online", +"enter_15_digit_gst_number" : "Enter 15 digit GST number", +"add_pan_card_details" : "Add PAN card details", +"pan_number_mandatory_info" : "Pan details is Mandatory to sell non-GST goods", +"enter_10_digit_pan_number" : "Enter 10 digit PAN card number", +"please_enter_a_valid_gst_number" : "Please enter valid GST number", + "please_enter_a_valid_pan_card_number" : "Please enter a valid PAN card number", + "i_have_a_gst_number" : "I have GST number", + "i_sell_only_books" : "I only sell non-GST categories, like Books etc", + "pan_name" : "PAN name", + "upload_pan_document" : "Upload PAN document", + "upload" : "Upload", + "drag_here_to_upload" : "Drag file here to upload", + "the_selected_file_is_not_allowed" : "The selected file is not allowed", + "clear_file" : "Clear file", + "allowed_extension" : "Allowed Extensions", + "please_enter_pan_name" : "Please enter PAN name", + "personal_information" : "Personal Information", + "gst_verification" : "GST Verification", + "store_details" : "Store Details", + "tax_and_account_information" : "Tax and Account Information", + "shipping_information" : "Shipping Information", + "save_information_policy_msg" : "This information will be securely saved as per ChoiceUS''s Terms of Service and Privacy Policy", + "contact_seller_support" : "Contact Seller Support", + "copy_right_text" : "Ⓒ 1999 - 2023 ChoiceUS.com , Inc. or its affiliates", + "feedback_capital" : "FEEDBACK" } \ No newline at end of file diff --git a/lib/localization/intl_en.arb b/lib/localization/intl_en.arb index 791961c..ba2ad54 100644 --- a/lib/localization/intl_en.arb +++ b/lib/localization/intl_en.arb @@ -47,5 +47,37 @@ "not_receive_otp" : "OTP not received?", "send_again" : "Send again", "resend_otp_in" : "Resend OTP in", - "back_button_text": "Back" + "back_button_text": "Back", + "next_button_text" : "Next", + "add_general_information" : "Add General Information", + "fill_out_the_basic_details_for_this_contract" : "Fill out the basic details for this contract", + "fill_out_the_basic_details_for_this_contract" : "Fill out the basic details for this contract", + "optional" : "optional", + "add_gst_information" : "Add GST Information", + "gst_number_mandatory_text" : "GST Number is mandatory to sell online", + "enter_15_digit_gst_number" : "Enter 15 digit GST number", + "add_pan_card_details" : "Add PAN card details", + "pan_number_mandatory_info" : "Pan details is Mandatory to sell non-GST goods", + "enter_10_digit_pan_number" : "Enter 10 digit PAN card number", + "please_enter_a_valid_gst_number" : "Please enter valid GST number", + "please_enter_a_valid_pan_card_number" : "Please enter a valid PAN card number", + "i_have_a_gst_number" : "I have GST number", + "i_sell_only_books" : "I only sell non-GST categories, like Books etc", + "pan_name" : "PAN name", + "upload_pan_document" : "Upload PAN document", + "upload" : "Upload", + "drag_here_to_upload" : "Drag file here to upload", + "the_selected_file_is_not_allowed" : "The selected file is not allowed", + "clear_file" : "Clear file", + "allowed_extension" : "Allowed Extensions", + "please_enter_pan_name" : "Please enter PAN name", + "personal_information" : "Personal Information", + "gst_verification" : "GST Verification", + "store_details" : "Store Details", + "tax_and_account_information" : "Tax and Account Information", + "shipping_information" : "Shipping Information", + "save_information_policy_msg" : "This information will be securely saved as per ChoiceUS''s Terms of Service and Privacy Policy", + "contact_seller_support" : "Contact Seller Support", + "copy_right_text" : "Ⓒ 1999 - 2023 ChoiceUS.com , Inc. or its affiliates", + "feedback_capital" : "FEEDBACK" } \ No newline at end of file diff --git a/lib/route/multi_provider_list.dart b/lib/route/multi_provider_list.dart index de41b52..9fa93ab 100644 --- a/lib/route/multi_provider_list.dart +++ b/lib/route/multi_provider_list.dart @@ -1,5 +1,6 @@ import 'package:frontend_ecommerce/features/buyer/authentication/view_model/login_viewmodel.dart'; import 'package:frontend_ecommerce/features/buyer/authentication/view_model/register_viewmodel.dart'; +import 'package:frontend_ecommerce/features/seller/authentication/view_model/create_contract_view_model.dart'; import 'package:frontend_ecommerce/features/seller/authentication/view_model/seller_register_view_model.dart'; import 'package:frontend_ecommerce/utils/timer_provider.dart'; import 'package:provider/provider.dart'; @@ -9,4 +10,5 @@ List providersList = [ ChangeNotifierProvider(create: (_) => LoginViewmodel()), ChangeNotifierProvider(create: (_) => SellerRegisterViewModel()), ChangeNotifierProvider(create: (_) => TimerProvider()), + ChangeNotifierProvider(create: (_) => CreateContractViewModel()), ]; \ No newline at end of file diff --git a/lib/route/router_config.dart b/lib/route/router_config.dart index c7ca905..76ea230 100644 --- a/lib/route/router_config.dart +++ b/lib/route/router_config.dart @@ -3,6 +3,7 @@ import 'package:frontend_ecommerce/features/buyer/authentication/view/buyer_logi import 'package:frontend_ecommerce/features/buyer/authentication/view/buyer_register.dart'; import 'package:frontend_ecommerce/features/buyer/dashboard/view/buyer_dashboard.dart'; import 'package:frontend_ecommerce/features/buyer/landing/view/landing_screen.dart'; +import 'package:frontend_ecommerce/features/seller/authentication/view/create_contract_screen.dart'; import 'package:frontend_ecommerce/features/seller/authentication/view/seller_register.dart'; import 'package:frontend_ecommerce/route/router_constant.dart'; import 'package:go_router/go_router.dart'; @@ -40,6 +41,13 @@ class AppRouter { return SellerRegister(); } ), + GoRoute( + path: AppPages.auth+AppPages.sellerCreateContract, + name: NamedRoute.sellerContractCreate, + builder: (BuildContext context, GoRouterState state) { + return CreateContractScreen(step: state.uri.queryParameters['step']); + } + ), ], ); } diff --git a/lib/route/router_constant.dart b/lib/route/router_constant.dart index 01dcfea..8002d8b 100644 --- a/lib/route/router_constant.dart +++ b/lib/route/router_constant.dart @@ -8,4 +8,9 @@ class AppPages{ static const String sellerDashBoard = '/seller/dashboard'; static const String buyerLogin = '/buyer/login'; static const String buyerDashboard = '/buyer/dashboard'; + static const String sellerCreateContract = '/seller/creating-contract'; +} + +class NamedRoute { + static const String sellerContractCreate = 'seller-contract-create'; } \ No newline at end of file diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 5a3b45f..4c7face 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -1,3 +1,7 @@ +import 'package:flutter/material.dart'; +import 'package:path/path.dart' as p; + +import '../common/widget/shared/custom_snackbar.dart'; class Utils{ static List getDialCodes(){ @@ -203,4 +207,17 @@ class Utils{ return dialCodes; } + + + static String getFileExtension(String path, [int level = 1]) => + p.extension(path, level); + + static showApiExceptionError(dynamic error, BuildContext context){ + CustomSnackbar( + message: error.response?.data != null ? (error.response?.data is Map && (error.response?.data as Map).containsKey('message') ? error.response?.data['message'] : 'Something went wrong') : 'Something went wrong', + context: context) + .showSnackbar(); + } + + } \ No newline at end of file diff --git a/lib/utils/validators/pattern_validator.dart b/lib/utils/validators/pattern_validator.dart index 9696653..5ca37a9 100644 --- a/lib/utils/validators/pattern_validator.dart +++ b/lib/utils/validators/pattern_validator.dart @@ -47,4 +47,14 @@ class PatternValidator { } + static bool? isValidGstNumber(context, String gstNumber){ + final regex = RegExp(r'^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[0-9]{1}[A-Z]{1}[0-9A-Z]{1}$'); + return regex.hasMatch(gstNumber); + } + + static bool? isValidPanNumber(context, String panNumber){ + final regex = RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]$'); + return regex.hasMatch(panNumber); + } + } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index cc17d24..796d27f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "5534e701a2c505fed1f0799e652dd6ae23bd4d2c4cf797220e5ced5764a7c1c2" + sha256: eae3133cbb06de9205899b822e3897fc6a8bc278ad4c944b4ce612689369694b url: "https://pub.dev" source: hosted - version: "1.3.44" + version: "1.3.47" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" cupertino_icons: dependency: "direct main" description: @@ -73,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + desktop_drop: + dependency: "direct main" + description: + name: desktop_drop + sha256: "03abf1c0443afdd1d65cf8fa589a2f01c67a11da56bbb06f6ea1de79d5628e94" + url: "https://pub.dev" + source: hosted + version: "0.5.0" dio: dependency: "direct main" description: @@ -105,54 +121,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "89500471922dd3a89ab0d6e13ab4a2268c25474bff4ca7c628f55c76e0ced1de" + url: "https://pub.dev" + source: hosted + version: "8.1.5" firebase_auth: dependency: "direct main" description: name: firebase_auth - sha256: d453acec0d958ba0e25d41a9901b32cb77d1535766903dea7a61b2788c304596 + sha256: "03483af6e67b7c4b696ca9386989a6cd5593569e1ac5af6907ea5f7fd9c16d8b" url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "5.3.4" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "78966c2ef774f5bf2a8381a307222867e9ece3509110500f7a138c115926aa65" + sha256: "3e1409f48c48930635705b1237ebbdee8c54c19106a0a4fb321dbb4b642820c4" url: "https://pub.dev" source: hosted - version: "7.4.7" + version: "7.4.10" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: "77ad3b252badedd3f08dfa21a4c7fe244be96c6da3a4067f253b13ea5d32424c" + sha256: d83fe95c44d73c9c29b006ac7df3aa5e1b8ce92b62edc44e8f86250951fe2cd0 url: "https://pub.dev" source: hosted - version: "5.13.2" + version: "5.13.5" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "51dfe2fbf3a984787a2e7b8592f2f05c986bfedd6fdacea3f9e0a7beb334de96" + sha256: fef81a53ba1ca618def1f8bef4361df07968434e62cb204c1fb90bb880a03da2 url: "https://pub.dev" source: hosted - version: "3.6.0" + version: "3.8.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 + sha256: b94b217e3ad745e784960603d33d99471621ecca151c99c670869b76e50ad2a6 url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.3.1" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 + sha256: "9e69806bb3d905aeec3c1242e0e1475de6ea6d48f456af29d598fb229a2b4e5e" url: "https://pub.dev" source: hosted - version: "2.18.1" + version: "2.18.2" flutter: dependency: "direct main" description: flutter @@ -171,6 +195,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + url: "https://pub.dev" + source: hosted + version: "2.0.23" flutter_screenutil: dependency: "direct main" description: @@ -231,10 +263,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.16" flutter_test: dependency: "direct dev" description: flutter @@ -249,42 +281,42 @@ packages: dependency: "direct main" description: name: go_router - sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459" + sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539" url: "https://pub.dev" source: hosted - version: "14.2.7" + version: "14.6.2" google_identity_services_web: dependency: transitive description: name: google_identity_services_web - sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6" + sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9" url: "https://pub.dev" source: hosted - version: "0.3.1+4" + version: "0.3.3" google_sign_in: dependency: "direct main" description: name: google_sign_in - sha256: "0b8787cb9c1a68ad398e8010e8c8766bfa33556d2ab97c439fb4137756d7308f" + sha256: fad6ddc80c427b0bba705f2116204ce1173e09cf299f85e053d57a55e5b2dd56 url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.2" google_sign_in_android: dependency: transitive description: name: google_sign_in_android - sha256: "0608de03fc541ece4f91ba3e01a68b17cce7a6cf42bd59e40bbe5c55cc3a49d8" + sha256: "3b96f9b6cf61915f73cbe1218a192623e296a9b8b31965702503649477761e36" url: "https://pub.dev" source: hosted - version: "6.1.30" + version: "6.1.34" google_sign_in_ios: dependency: transitive description: name: google_sign_in_ios - sha256: "4898410f55440049e1ba8f15411612d9f89299d89c61cd9baf7e02d56ff81ac7" + sha256: "83f015169102df1ab2905cf8abd8934e28f87db9ace7a5fa676998842fed228a" url: "https://pub.dev" source: hosted - version: "5.7.7" + version: "5.7.8" google_sign_in_platform_interface: dependency: transitive description: @@ -297,10 +329,10 @@ packages: dependency: transitive description: name: google_sign_in_web - sha256: "042805a21127a85b0dc46bba98a37926f17d2439720e8a459d27045d8ef68055" + sha256: ada595df6c30cead48e66b1f3a050edf0c5cf2ba60c185d69690e08adcc6281b url: "https://pub.dev" source: hosted - version: "0.12.4+2" + version: "0.12.4+3" http: dependency: transitive description: @@ -369,18 +401,18 @@ packages: dependency: "direct main" description: name: logger - sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.0" logging: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" matcher: dependency: transitive description: @@ -421,38 +453,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_drawing: + dependency: "direct main" + description: + name: path_drawing + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 + url: "https://pub.dev" + source: hosted + version: "1.0.1" path_parsing: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider: dependency: transitive description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -489,10 +529,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -566,34 +606,34 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.15" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.12" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.16" vector_math: dependency: transitive description: @@ -614,18 +654,18 @@ packages: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" win32: dependency: transitive description: name: win32 - sha256: "4d45dc9069dba4619dc0ebd93c7cec5e66d8482cb625a370ac806dcc8165f2ec" + sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" url: "https://pub.dev" source: hosted - version: "5.5.5" + version: "5.9.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 909ca2d..82a1c47 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,9 @@ dependencies: firebase_core: ^3.6.0 flutter_secure_storage: ^9.2.2 carousel_slider: ^5.0.0 + file_picker: ^8.1.5 + path_drawing: ^1.0.1 + desktop_drop: ^0.5.0