├── .gitignore ├── LICENSE ├── README.md ├── dart_crm ├── lib │ ├── blocs │ │ ├── auth │ │ │ ├── auth_bloc.dart │ │ │ ├── settings_bloc.dart │ │ │ └── signup_bloc.dart │ │ ├── blogic │ │ │ └── addressbook_bloc.dart │ │ └── validators.dart │ ├── main.dart │ ├── models │ │ └── datamodel.dart │ ├── providers │ │ ├── auth_provider.dart │ │ └── auth_resources.dart │ ├── shared │ │ ├── custom_components.dart │ │ ├── custom_forms.dart │ │ ├── custom_style.dart │ │ └── package │ │ │ └── shared_preferences.dart │ └── views │ │ ├── addressbook │ │ ├── addressbook-add.dart │ │ ├── addressbook-edit.dart │ │ └── addressbook.dart │ │ ├── app.dart │ │ └── auth │ │ ├── login.dart │ │ ├── profile.dart │ │ ├── settings.dart │ │ └── signup.dart ├── pubspec.yaml └── web │ ├── assets │ ├── FontManifest.json │ └── fonts │ │ ├── Merriweather-Bold.ttf │ │ └── OpenSans-Regular.ttf │ ├── index.html │ └── main.dart └── nodejs_crm_server ├── .env ├── index.js ├── models ├── connector.js └── dbconnection.js ├── package.json └── validators └── validate.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Amit Shukla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```diff 2 | - If you like this project, please consider giving it a star (*) and follow me at GitHub & YouTube. 3 | ``` 4 | [](https://youtube.com/AmitShukla_AI) 5 | [](https://github.com/AmitXShukla) 6 | [](https://medium.com/@Amit_Shukla) 7 | [](https://twitter.com/ashuklax) 8 | 9 | # Flutter-MYSQL-CRM-app 10 | Flutter MYSQL CRM app - Free download with complete source code for iOS, Android, web 11 | # Do NOT Clone yet, this repository is work in progress and due 1.9 upgrade. 12 | # CRM 13 | Video Tutorials 14 | 15 | # Objective 16 | Build CRM for Small, Medium and Large Organizations
17 | Re-Write / Build new CRM GUI for existing ERP
18 | Convert Old Software to new App/UI (Desktop & Mobile) without changing database
19 | Migrate existing ERP to new platform
20 | Make an App for existing Oracle, PeopleSoft, SAP, or Siebel CRM or old custom software based ERP
21 |
22 | ** This app is NOT 100% complete yet because FLUTTER_WEB is not officially released.
23 | I am seeing performance issues with current FLUTTER_WEB version.
24 | So I'm building CRM app (complete version) in Angular, MYSQL (Option #2 mentioned below) for now.
25 | But I will update this CRM APP (this repository for Flutter/MYSQL) when FLUTTER WEB is released officially. ** 26 | 27 | ## Technologies 28 | ## Option 1: 29 | FrontEnd: Flutter/DART
30 | Backend: NodeJS/REST API or DART
31 | Mobile: Flutter (iOS, Android)
32 | Database: ANY (Firebase, MongoDB, MYSQL, MS-SQL Server, MariaDB or Oracle)
33 | 34 | ## Option 2: 35 | FrontEnd: Angular, Project Clarity
36 | Backend: NodeJS, Express or PHP or JAVA or C# (DotNet) or GraphQL
37 | Mobile: Cordova (iOS, Android)
38 | Database: ANY (Firebase, MongoDB, MYSQL, MS-SQL Server, MariaDB or Oracle)
39 | 40 | # Pro - ERP 41 | HCM
42 | CRM
43 | SCM
44 | CRM, Supply Chain,
45 | Live Inventory
46 | Book Keeping
47 | Expense Management
48 | Assets management
49 |
50 |
51 | Manage CRM 52 | 53 | Users -> Admin 54 | Employee 55 | Manager 56 | 57 | AddressBook -> 58 | Global Addressbook 59 | Local Addressbook 60 | 61 | Marketing -> 62 | Campaigns 63 | Leads 64 | Oppurtunities 65 | Expenses 66 | 67 | Call Register -> 68 | Calls 69 | eMails 70 | enquiry 71 | visits 72 | Expenses 73 | 74 | Helpdesk -> Service tickets 75 | workorders 76 | Expenses 77 | 78 | Customer -> Invoice 79 | -> Sales Register 80 | 81 | Payables -> Setup Vendor 82 | -> Voucher for Vendor 83 | -> Pay Vendor 84 | -------------------- 85 | 86 | 87 | 88 | 89 | Supply Chain & Inventory Management App (Redux or RxJs and/or React version) 90 | >> Setup
91 | Org/Company
92 | Vendor / Mfg
93 | Customer
94 |
95 | >> Register
96 | Requisition -> Generate Req and auto PO
97 | PO -> Request Inventory
98 | Receipt -> Receive Inventory
99 | Payables -> Setup Vendor
100 | -> Voucher for Vendor
101 | -> Pay Vendor
102 | Receivables-> Setup Customer
103 | -> Invoice for Customer
104 | -> Receive
105 | Sales Register
106 | # Reports 107 | Inventory Cycle Count
108 | Inventory Snapshot
109 | report - On Hand Inventory
110 | Payables -> Setup Vendor
111 | -> Voucher for Vendor
112 | -> Pay Vendor
113 | -------------------------------------------------------------------------------- /dart_crm/lib/blocs/auth/auth_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dart_crm/blocs/validators.dart'; 4 | import 'package:dart_crm/providers/auth_resources.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | class AuthBloc extends Object with Validators { 8 | final _emailController = BehaviorSubject(); 9 | final _passwordController = BehaviorSubject(); 10 | 11 | // API: Add data to stream 12 | Stream get email => _emailController.stream.transform(validateEmail); 13 | Stream get password => 14 | _passwordController.stream.transform(validatePassword); 15 | Stream get submitValid => 16 | Observable.combineLatest2(email, password, (e, p) => true); 17 | 18 | // API: change data 19 | Function(String) get changeEmail => _emailController.sink.add; 20 | Function(String) get changePassword => _passwordController.sink.add; 21 | 22 | // API: methods to perform actions 23 | submitData() { 24 | // TODO: Fix this later 25 | final validEmail = _emailController.value; 26 | final validPassword = _passwordController.value; 27 | print('Email is $validEmail, and password is $validPassword'); 28 | } 29 | 30 | validateUserAuth() async { 31 | final validEmail = _emailController.value; 32 | 33 | final validPassword = _passwordController.value; 34 | 35 | if (validEmail != "" && validPassword != "") { 36 | return AuthRepository() 37 | .validateUser({"email": validEmail, "enc_password": validPassword}); 38 | } 39 | } 40 | 41 | // API: dispose/cancel observables/subscriptions 42 | dispose() { 43 | _emailController.close(); 44 | _passwordController.close(); 45 | } 46 | } 47 | 48 | final authBloc = AuthBloc(); 49 | 50 | ///** 51 | //import 'dart:async'; 52 | // 53 | //import 'package:dart_crm/blocs/validators.dart'; 54 | //import 'package:dart_crm/models/datamodel.dart'; 55 | //import 'package:dart_crm/providers/auth_resources.dart'; 56 | //import 'package:rxdart/rxdart.dart'; 57 | // 58 | ////* Using a shortcut getter method on the class to create simpler and friendlier API for the class to provide access of a particular function on StreamController 59 | ////* Mixin can only be used on a class that extends from a base class, therefore, we are adding Bloc class that extends from the Object class 60 | ////NOTE: Or you can write "class Bloc extends Validators" since we don't really need to extend Bloc from a base class 61 | //class AuthBloc extends Object with Validators { 62 | // final _authRepository = AuthRepository(); 63 | // final _authData = PublishSubject(); 64 | // 65 | // //* "_" sets the instance variable to a private variable 66 | // //NOTE: By default, streams are created as "single-subscription stream", but in this case and in most cases, we need to create "broadcast stream" 67 | // //Note(con'd): because the email/password streams are consumed by the email/password fields as well as the combineLastest2 RxDart method 68 | // //Note:(con'd): because we need to merge these two streams as one and get the lastest streams of both that are valid to enable the button state 69 | // //Note:(con'd): Thus, below two streams are being consumed multiple times 70 | // //* original single-subscription stream 71 | // // final _emailController = StreamController(); 72 | // // final _passwordController = StreamController(); 73 | // 74 | // //* Broadcast stream 75 | // // final _emailController = StreamController.broadcast(); 76 | // // final _passwordController = StreamController.broadcast(); 77 | // 78 | // //* Replacing above Dart StreamController with RxDart BehaviourSubject (which is a broadcast stream by default) 79 | // //NOTE: We are leveraging the additional functionality from BehaviorSubject to go back in time and retrieve the lastest value of the streams for form submission 80 | // //NOTE: Dart StreamController doesn't have such functionality 81 | // final _emailController = BehaviorSubject(); 82 | // final _passwordController = BehaviorSubject(); 83 | // 84 | // // Add data to stream 85 | // Stream get email => _emailController.stream.transform(validateEmail); 86 | // Stream get password => 87 | // _passwordController.stream.transform(validatePassword); 88 | // 89 | // Stream get submitValid => 90 | // Observable.combineLatest2(email, password, (e, p) => true); 91 | // 92 | // // change data 93 | // Function(String) get changeEmail => _emailController.sink.add; 94 | // Function(String) get changePassword => _passwordController.sink.add; 95 | // 96 | // submit() { 97 | // final validEmail = _emailController.value; 98 | // final validPassword = _passwordController.value; 99 | // 100 | // print('Email is $validEmail, and password is $validPassword'); 101 | // } 102 | // 103 | // validateUserAuth() async { 104 | // final validEmail = _emailController.value; 105 | // final validPassword = _passwordController.value; 106 | // UserModel userModel = await _authRepository.validateUser(); 107 | // _authData.sink.add(userModel); 108 | // } 109 | // 110 | // dispose() { 111 | // _emailController.close(); 112 | // _passwordController.close(); 113 | // _authData.close(); 114 | // } 115 | //} 116 | // 117 | ////Note: This creates a global instance of Bloc that's automatically exported and can be accessed anywhere in the app 118 | ////final bloc = Bloc(); 119 | // 120 | ///** INCLUDE this implementation now 121 | // * import '../resources/repository.dart'; 122 | // import 'package:rxdart/rxdart.dart'; 123 | // import '../models/item_model.dart'; 124 | // 125 | // class MoviesBloc { 126 | // final _repository = Repository(); 127 | // final _moviesFetcher = PublishSubject(); 128 | // 129 | // Observable get allMovies => _moviesFetcher.stream; 130 | // 131 | // fetchAllMovies() async { 132 | // ItemModel itemModel = await _repository.fetchAllMovies(); 133 | // _moviesFetcher.sink.add(itemModel); 134 | // } 135 | // 136 | // dispose() { 137 | // _moviesFetcher.close(); 138 | // } 139 | // } 140 | // 141 | // final bloc = MoviesBloc(); 142 | // 143 | // * */ 144 | -------------------------------------------------------------------------------- /dart_crm/lib/blocs/auth/settings_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dart_crm/blocs/validators.dart'; 4 | import 'package:dart_crm/models/datamodel.dart'; 5 | import 'package:dart_crm/providers/auth_resources.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | 8 | class SettingsBloc extends Object with Validators { 9 | final _emailController = BehaviorSubject(); 10 | final _nameController = BehaviorSubject(); 11 | final _passwordController = BehaviorSubject(); 12 | 13 | // API: Add data to stream 14 | Stream get email => _emailController.stream.transform(validateEmail); 15 | Stream get name => _nameController.stream.transform(validateText); 16 | Stream get password => 17 | _passwordController.stream.transform(validatePassword); 18 | Stream get submitValid => 19 | Observable.combineLatest3(email, name, password, (e, n, p) => true); 20 | 21 | // API: change data 22 | Function(String) get changeEmail => _emailController.sink.add; 23 | Function(String) get changeName => _nameController.sink.add; 24 | Function(String) get changePassword => _passwordController.sink.add; 25 | 26 | Future getUser() async { 27 | return await SettingsRepository().getUser(); 28 | } 29 | 30 | Future setUser(formData) async { 31 | return SettingsRepository().setUser(formData); 32 | } 33 | 34 | // API: dispose/cancel observables/subscriptions 35 | dispose() { 36 | _emailController.close(); 37 | _nameController.close(); 38 | _passwordController.close(); 39 | } 40 | } 41 | 42 | final settingsBloc = SettingsBloc(); 43 | 44 | ///** 45 | //import 'dart:async'; 46 | // 47 | //import 'package:dart_crm/blocs/validators.dart'; 48 | //import 'package:dart_crm/models/datamodel.dart'; 49 | //import 'package:dart_crm/providers/auth_resources.dart'; 50 | //import 'package:rxdart/rxdart.dart'; 51 | // 52 | ////* Using a shortcut getter method on the class to create simpler and friendlier API for the class to provide access of a particular function on StreamController 53 | ////* Mixin can only be used on a class that extends from a base class, therefore, we are adding Bloc class that extends from the Object class 54 | ////NOTE: Or you can write "class Bloc extends Validators" since we don't really need to extend Bloc from a base class 55 | //class AuthBloc extends Object with Validators { 56 | // final _authRepository = AuthRepository(); 57 | // final _authData = PublishSubject(); 58 | // 59 | // //* "_" sets the instance variable to a private variable 60 | // //NOTE: By default, streams are created as "single-subscription stream", but in this case and in most cases, we need to create "broadcast stream" 61 | // //Note(con'd): because the email/password streams are consumed by the email/password fields as well as the combineLastest2 RxDart method 62 | // //Note:(con'd): because we need to merge these two streams as one and get the lastest streams of both that are valid to enable the button state 63 | // //Note:(con'd): Thus, below two streams are being consumed multiple times 64 | // //* original single-subscription stream 65 | // // final _emailController = StreamController(); 66 | // // final _passwordController = StreamController(); 67 | // 68 | // //* Broadcast stream 69 | // // final _emailController = StreamController.broadcast(); 70 | // // final _passwordController = StreamController.broadcast(); 71 | // 72 | // //* Replacing above Dart StreamController with RxDart BehaviourSubject (which is a broadcast stream by default) 73 | // //NOTE: We are leveraging the additional functionality from BehaviorSubject to go back in time and retrieve the lastest value of the streams for form submission 74 | // //NOTE: Dart StreamController doesn't have such functionality 75 | // final _emailController = BehaviorSubject(); 76 | // final _passwordController = BehaviorSubject(); 77 | // 78 | // // Add data to stream 79 | // Stream get email => _emailController.stream.transform(validateEmail); 80 | // Stream get password => 81 | // _passwordController.stream.transform(validatePassword); 82 | // 83 | // Stream get submitValid => 84 | // Observable.combineLatest2(email, password, (e, p) => true); 85 | // 86 | // // change data 87 | // Function(String) get changeEmail => _emailController.sink.add; 88 | // Function(String) get changePassword => _passwordController.sink.add; 89 | // 90 | // submit() { 91 | // final validEmail = _emailController.value; 92 | // final validPassword = _passwordController.value; 93 | // 94 | // print('Email is $validEmail, and password is $validPassword'); 95 | // } 96 | // 97 | // validateUserAuth() async { 98 | // final validEmail = _emailController.value; 99 | // final validPassword = _passwordController.value; 100 | // UserModel userModel = await _authRepository.validateUser(); 101 | // _authData.sink.add(userModel); 102 | // } 103 | // 104 | // dispose() { 105 | // _emailController.close(); 106 | // _passwordController.close(); 107 | // _authData.close(); 108 | // } 109 | //} 110 | // 111 | ////Note: This creates a global instance of Bloc that's automatically exported and can be accessed anywhere in the app 112 | ////final bloc = Bloc(); 113 | // 114 | ///** INCLUDE this implementation now 115 | // * import '../resources/repository.dart'; 116 | // import 'package:rxdart/rxdart.dart'; 117 | // import '../models/item_model.dart'; 118 | // 119 | // class MoviesBloc { 120 | // final _repository = Repository(); 121 | // final _moviesFetcher = PublishSubject(); 122 | // 123 | // Observable get allMovies => _moviesFetcher.stream; 124 | // 125 | // fetchAllMovies() async { 126 | // ItemModel itemModel = await _repository.fetchAllMovies(); 127 | // _moviesFetcher.sink.add(itemModel); 128 | // } 129 | // 130 | // dispose() { 131 | // _moviesFetcher.close(); 132 | // } 133 | // } 134 | // 135 | // final bloc = MoviesBloc(); 136 | // 137 | // * */ 138 | -------------------------------------------------------------------------------- /dart_crm/lib/blocs/auth/signup_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dart_crm/blocs/validators.dart'; 4 | import 'package:dart_crm/providers/auth_resources.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | class SignUpBloc extends Object with Validators { 8 | final _emailController = BehaviorSubject(); 9 | final _nameController = BehaviorSubject(); 10 | final _passwordController = BehaviorSubject(); 11 | 12 | // API: Add data to stream 13 | Stream get email => _emailController.stream.transform(validateEmail); 14 | Stream get name => _nameController.stream.transform(validateText); 15 | Stream get password => 16 | _passwordController.stream.transform(validatePassword); 17 | Stream get submitValid => 18 | Observable.combineLatest3(email, name, password, (e, n, p) => true); 19 | 20 | // API: change data 21 | Function(String) get changeEmail => _emailController.sink.add; 22 | Function(String) get changeName => _nameController.sink.add; 23 | Function(String) get changePassword => _passwordController.sink.add; 24 | 25 | signupUser() async { 26 | final validEmail = _emailController.value; 27 | final validName = _nameController.value; 28 | final validPassword = _passwordController.value; 29 | 30 | if (validEmail != "" && validName != "" && validPassword != "") { 31 | return SignupRepository().createUser({ 32 | "email": validEmail, 33 | "name": validName, 34 | "enc_password": validPassword 35 | }); 36 | } 37 | } 38 | 39 | // API: dispose/cancel observables/subscriptions 40 | dispose() { 41 | _emailController.close(); 42 | _nameController.close(); 43 | _passwordController.close(); 44 | } 45 | } 46 | 47 | final signupBloc = SignUpBloc(); 48 | -------------------------------------------------------------------------------- /dart_crm/lib/blocs/blogic/addressbook_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dart_crm/blocs/validators.dart'; 4 | import 'package:dart_crm/models/datamodel.dart'; 5 | import 'package:dart_crm/providers/auth_resources.dart'; 6 | 7 | class AddressBookBloc extends Object with Validators { 8 | Future getUser() async { 9 | return await SettingsRepository().getUser(); 10 | } 11 | 12 | Future getData(String _id, String srchTxt) async { 13 | return await SettingsRepository() 14 | .getData({"table_name": "addressbook", "_id": _id, "srchTxt": srchTxt}); 15 | } 16 | 17 | Future setData(formData) async { 18 | return SettingsRepository().setData(formData); 19 | } 20 | 21 | // API: dispose/cancel observables/subscriptions 22 | dispose() {} 23 | } 24 | 25 | final addressBookBloc = AddressBookBloc(); 26 | -------------------------------------------------------------------------------- /dart_crm/lib/blocs/validators.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class Validators { 4 | final validateEmail = 5 | StreamTransformer.fromHandlers(handleData: (email, sink) { 6 | Pattern pattern = 7 | r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; 8 | RegExp regex = new RegExp(pattern); 9 | if (regex.hasMatch(email)) 10 | sink.add(email); 11 | else 12 | sink.addError('Please enter a valid email'); 13 | }); 14 | 15 | final validatePassword = StreamTransformer.fromHandlers( 16 | handleData: (password, sink) { 17 | if (password.length > 4) { 18 | sink.add(password); 19 | } else { 20 | sink.addError('Invalid password, please enter more than 4 characters'); 21 | } 22 | }); 23 | 24 | final validateText = 25 | StreamTransformer.fromHandlers(handleData: (name, sink) { 26 | if (name.length > 4) { 27 | sink.add(name); 28 | } else { 29 | sink.addError('Invalid Text, please enter more than 4 characters'); 30 | } 31 | }); 32 | 33 | bool validateFormText(String txt) { 34 | //WhitelistingTextInputFormatter(new RegExp(r'^[()\d -]{1,15}$')), 35 | if (txt.isEmpty) return true; 36 | return false; 37 | } 38 | 39 | bool isValidEmail(String input) { 40 | final RegExp regex = new RegExp( 41 | r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"); 42 | return regex.hasMatch(input); 43 | } 44 | 45 | bool isValidPhoneNumber(String input) { 46 | final RegExp regex = new RegExp(r'^\(\d\d\d\)\d\d\d\-\d\d\d\d$'); 47 | return regex.hasMatch(input); 48 | } 49 | } 50 | 51 | final validatorBloc = Validators(); 52 | -------------------------------------------------------------------------------- /dart_crm/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_web/material.dart'; 2 | import './views/app.dart'; 3 | 4 | void main() { 5 | runApp(App()); 6 | } 7 | -------------------------------------------------------------------------------- /dart_crm/lib/models/datamodel.dart: -------------------------------------------------------------------------------- 1 | class DBDataModel { 2 | final int num_rows; 3 | final bool error; 4 | final String message; 5 | final List data; 6 | const DBDataModel({this.num_rows, this.error, this.message, this.data}); 7 | factory DBDataModel.fromJson(Map json) { 8 | return DBDataModel( 9 | num_rows: json['num_rows'], 10 | error: json['error'], 11 | message: json['message'], 12 | data: json['data'] 13 | .map((value) => new UserModel.fromJson(value)) 14 | .toList()); 15 | } 16 | } 17 | 18 | class AddressDataModel { 19 | final int num_rows; 20 | final bool error; 21 | final String message; 22 | final List data; 23 | const AddressDataModel({this.num_rows, this.error, this.message, this.data}); 24 | factory AddressDataModel.fromJson(Map json) { 25 | return AddressDataModel( 26 | num_rows: json['num_rows'], 27 | error: json['error'], 28 | message: json['message'], 29 | data: json['data'] 30 | .map((value) => new AddressBookModel.fromJson(value)) 31 | .toList()); 32 | } 33 | } 34 | 35 | class UserModel { 36 | final String userid; 37 | final String name; 38 | final String jwttoken; 39 | final String createdAt; 40 | final String updatedAt; 41 | final String role; 42 | 43 | const UserModel( 44 | {this.userid, 45 | this.name, 46 | this.jwttoken, 47 | this.createdAt, 48 | this.updatedAt, 49 | this.role}); 50 | 51 | factory UserModel.fromJson(Map json) { 52 | return UserModel( 53 | userid: json['userid'], 54 | name: json['name'], 55 | jwttoken: json['jwttoken'], 56 | createdAt: json['createdAt'], 57 | updatedAt: json['updatedAt'], 58 | role: json['role']); 59 | } 60 | factory UserModel.toJson(Map json) { 61 | return UserModel( 62 | userid: json['userid'], 63 | name: json['name'], 64 | jwttoken: json['jwttoken'], 65 | ); 66 | } 67 | } 68 | 69 | class AddressBookModel { 70 | final int addressid; 71 | final String first_name; 72 | final String middle_name; 73 | final String last_name; 74 | final String address; 75 | final String city; 76 | final String country; 77 | final String zip_code; 78 | final String emailid1; 79 | final String emailid2; 80 | final String phone1; 81 | final String phone2; 82 | final String createdAt; 83 | final String updatedAt; 84 | 85 | const AddressBookModel( 86 | {this.addressid, 87 | this.first_name, 88 | this.middle_name, 89 | this.last_name, 90 | this.address, 91 | this.city, 92 | this.country, 93 | this.zip_code, 94 | this.emailid1, 95 | this.emailid2, 96 | this.phone1, 97 | this.phone2, 98 | this.createdAt, 99 | this.updatedAt}); 100 | 101 | factory AddressBookModel.fromJson(Map json) { 102 | return AddressBookModel( 103 | addressid: json['addressid'], 104 | first_name: json['first_name'], 105 | middle_name: json['middle_name'], 106 | last_name: json['last_name'], 107 | address: json['address'], 108 | city: json['city'], 109 | country: json['country'], 110 | zip_code: json['zip_code'], 111 | emailid1: json['emailid1'], 112 | emailid2: json['emailid2'], 113 | phone1: json['phone1'], 114 | phone2: json['phone2'], 115 | createdAt: json['createdAt'], 116 | updatedAt: json['updatedAt']); 117 | } 118 | factory AddressBookModel.toJson(Map json) { 119 | return AddressBookModel( 120 | addressid: json['addressid'], 121 | first_name: json['first_name'], 122 | middle_name: json['middle_name'], 123 | last_name: json['last_name'], 124 | address: json['address'], 125 | city: json['city'], 126 | country: json['country'], 127 | zip_code: json['zip_code'], 128 | emailid1: json['emailid1'], 129 | emailid2: json['emailid2'], 130 | phone1: json['phone1'], 131 | phone2: json['phone2'], 132 | createdAt: json['createdAt'], 133 | updatedAt: json['updatedAt']); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /dart_crm/lib/providers/auth_provider.dart: -------------------------------------------------------------------------------- 1 | //import 'package:dart_crm/blocs/auth_bloc.dart'; 2 | //import 'package:flutter_web/material.dart'; 3 | // 4 | //class Provider extends InheritedWidget { 5 | // final bloc = Bloc(); 6 | // 7 | // Provider({Key key, Widget child}) : super(key: key, child: child); 8 | // 9 | // bool updateShouldNotify(_) => true; 10 | // 11 | // static Bloc of(BuildContext context) { 12 | // //* What it does is through the "of" function, it looks through the context of a widget from the deepest in the widget tree 13 | // //* and it keeps travelling up to each widget's parent's context until it finds a "Provider" widget 14 | // //* and performs the type conversion to Provider through "as Provider" and then access the Provider's bloc instance variable 15 | // return (context.inheritFromWidgetOfExactType(Provider) as Provider).bloc; 16 | // } 17 | //} 18 | import 'dart:async'; 19 | import 'dart:convert'; 20 | 21 | import 'package:dart_crm/models/datamodel.dart'; 22 | import 'package:http/http.dart' show Client; 23 | 24 | class UserAuthApiProvider { 25 | var fakeJsonResponse_1 = """{ 26 | "num_rows": 1, 27 | "error": false, 28 | "message": "Operation Successful.", 29 | "data": [ 30 | { 31 | "userid": "amit@elishconsulting.com", 32 | "name": "Amit Shukla", 33 | "role": "Admin", 34 | "jwttoken": "abcd", 35 | "createdAt": "abcd", 36 | "updatedAt": "abcd" 37 | } 38 | ] 39 | }"""; 40 | var serverFailed = """{ 41 | "num_rows": 0, 42 | "error": true, 43 | "message": "Server Error. Possible Broken Network. Please send an email to info@elishconsulting.com", 44 | "data": [ 45 | { 46 | "userid": "", 47 | "name": "", 48 | "role": "", 49 | "jwttoken": "", 50 | "createdAt": "", 51 | "updatedAt": "" 52 | } 53 | ] 54 | }"""; 55 | Client client = Client(); 56 | final _baseUrl = "http://localhost:3000"; 57 | 58 | // let token = localStorage.getItem('token') ? localStorage.getItem('token') : "abcd"; 59 | // let httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'token': token }) }; 60 | 61 | Future validateUserAuth(formData) async { 62 | try { 63 | final response = await client.post("$_baseUrl/login", 64 | headers: { 65 | //HttpHeaders.authorizationHeader: ["token" : "Basic your_api_token_here"] 66 | "token": "invalid_token" 67 | }, 68 | body: formData); 69 | if (response.statusCode == 200) { 70 | // If the call to the server was successful, parse the JSON 71 | print(response.body); 72 | return await DBDataModel.fromJson(json.decode(response.body)); 73 | } 74 | } catch (_) { 75 | return await DBDataModel.fromJson(json.decode(serverFailed)); 76 | } 77 | return await DBDataModel.fromJson(json.decode(serverFailed)); 78 | } 79 | 80 | Future createUser(formData) async { 81 | try { 82 | final response = await client.post("$_baseUrl/signup", 83 | headers: { 84 | //HttpHeaders.authorizationHeader: "Basic your_api_token_here" 85 | "token": "invalid_token" 86 | }, 87 | body: formData); 88 | if (response.statusCode == 200) { 89 | // If the call to the server was successful, parse the JSON 90 | return await DBDataModel.fromJson(json.decode(response.body)); 91 | } 92 | } catch (_) { 93 | return await DBDataModel.fromJson(json.decode(serverFailed)); 94 | } 95 | return await DBDataModel.fromJson(json.decode(serverFailed)); 96 | } 97 | 98 | Future getUser() async { 99 | final formData = {}; 100 | // final token = "invalid_token"; 101 | final token = 102 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFtaXRAeWFob28uY29tIiwiaWF0IjoxNTYzODUwNDIwLCJleHAiOjE1NjY0NDI0MjB9.7TAxrqaRNwIEMQH-Qv51BLk3Zy_eYe3KtGN_Id1hVA4"; 103 | // FLUTTER_WEB version is not working to store at present 104 | // TODO: enable this for android/ios version 105 | // final prefs = await SharedPreferences.getInstance(); 106 | // final token = prefs.getInt('token') ?? "invalid_token"; 107 | try { 108 | final response = await client.post("$_baseUrl/getuser", 109 | headers: {"token": token}, body: formData); 110 | if (response.statusCode == 200) { 111 | // If the call to the server was successful, parse the JSON 112 | return await DBDataModel.fromJson(json.decode(response.body)); 113 | } 114 | } catch (_) { 115 | return await DBDataModel.fromJson(json.decode(serverFailed)); 116 | } 117 | return await DBDataModel.fromJson(json.decode(serverFailed)); 118 | } 119 | 120 | Future setUser(formData) async { 121 | final formData2 = formData; 122 | //final token = "invalid_token"; 123 | final token = 124 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFtaXRAeWFob28uY29tIiwiaWF0IjoxNTYzNzcxMDEyLCJleHAiOjE1NjYzNjMwMTJ9.c39YqxKYtdqi199Ivh_-7LPzTe8BC21xWtvcqv0TTck"; 125 | // FLUTTER_WEB version is not working to store at present 126 | // TODO: enable this for android/ios version 127 | // final prefs = await SharedPreferences.getInstance(); 128 | // final token = prefs.getInt('token') ?? "invalid_token"; 129 | try { 130 | //print(formData.userid); 131 | final response = await client.post("$_baseUrl/setuser", 132 | headers: {"token": token}, body: formData2); 133 | if (response.statusCode == 200) { 134 | // If the call to the server was successful, parse the JSON 135 | return await DBDataModel.fromJson(json.decode(response.body)); 136 | } 137 | } catch (_) { 138 | return await DBDataModel.fromJson(json.decode(serverFailed)); 139 | } 140 | return await DBDataModel.fromJson(json.decode(serverFailed)); 141 | } 142 | 143 | Future setData(formData) async { 144 | final formData2 = formData; 145 | //final token = "invalid_token"; 146 | final token = 147 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFtaXRAeWFob28uY29tIiwiaWF0IjoxNTYzNzcxMDEyLCJleHAiOjE1NjYzNjMwMTJ9.c39YqxKYtdqi199Ivh_-7LPzTe8BC21xWtvcqv0TTck"; 148 | // FLUTTER_WEB version is not working to store at present 149 | // TODO: enable this for android/ios version 150 | // final prefs = await SharedPreferences.getInstance(); 151 | // final token = prefs.getInt('token') ?? "invalid_token"; 152 | try { 153 | //print(formData.userid); 154 | final response = await client.post("$_baseUrl/setdata", 155 | headers: {"token": token}, body: formData2); 156 | if (response.statusCode == 200) { 157 | // If the call to the server was successful, parse the JSON 158 | return await DBDataModel.fromJson(json.decode(response.body)); 159 | } 160 | } catch (_) { 161 | return await DBDataModel.fromJson(json.decode(serverFailed)); 162 | } 163 | return await DBDataModel.fromJson(json.decode(serverFailed)); 164 | } 165 | 166 | Future getData(formData) async { 167 | final formData2 = formData; 168 | //final token = "invalid_token"; 169 | final token = 170 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFtaXRAeWFob28uY29tIiwiaWF0IjoxNTYzNzcxMDEyLCJleHAiOjE1NjYzNjMwMTJ9.c39YqxKYtdqi199Ivh_-7LPzTe8BC21xWtvcqv0TTck"; 171 | // FLUTTER_WEB version is not working to store at present 172 | // TODO: enable this for android/ios version 173 | // final prefs = await SharedPreferences.getInstance(); 174 | // final token = prefs.getInt('token') ?? "invalid_token"; 175 | try { 176 | //print(formData.userid); 177 | final response = await client.post("$_baseUrl/getdata", 178 | headers: {"token": token}, body: formData2); 179 | if (response.statusCode == 200) { 180 | // If the call to the server was successful, parse the JSON 181 | // print(response.body); 182 | return await AddressDataModel.fromJson(json.decode(response.body)); 183 | } 184 | } catch (_) { 185 | return await AddressDataModel.fromJson(json.decode(serverFailed)); 186 | } 187 | return await AddressDataModel.fromJson(json.decode(serverFailed)); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /dart_crm/lib/providers/auth_resources.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'auth_provider.dart'; 4 | 5 | class AuthRepository { 6 | final userAuthApiProvider = UserAuthApiProvider(); 7 | Future validateUser(formData) => 8 | userAuthApiProvider.validateUserAuth(formData); 9 | } 10 | 11 | class SignupRepository { 12 | final userAuthApiProvider = UserAuthApiProvider(); 13 | Future createUser(formData) => userAuthApiProvider.createUser(formData); 14 | } 15 | 16 | class SettingsRepository { 17 | final userAuthApiProvider = UserAuthApiProvider(); 18 | //Future getUser(formData) => userAuthApiProvider.getUser(formData); 19 | Future getUser() => userAuthApiProvider.getUser(); 20 | Future setUser(formData) => userAuthApiProvider.setUser(formData); 21 | Future getData(formData) => userAuthApiProvider.getData(formData); 22 | Future setData(formData) => userAuthApiProvider.setData(formData); 23 | } 24 | -------------------------------------------------------------------------------- /dart_crm/lib/shared/custom_components.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/shared/custom_style.dart'; 2 | import 'package:flutter_web/material.dart'; 3 | 4 | class CustomSpinner extends StatelessWidget { 5 | final bool toggleSpinner; 6 | const CustomSpinner({Key key, this.toggleSpinner}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Center(child: toggleSpinner ? CircularProgressIndicator() : null); 11 | } 12 | } 13 | 14 | class CustomMessage extends StatelessWidget { 15 | final bool toggleMessage; 16 | final toggleMessageType; 17 | final String toggleMessageTxt; 18 | const CustomMessage( 19 | {Key key, 20 | this.toggleMessage, 21 | this.toggleMessageType, 22 | this.toggleMessageTxt}) 23 | : super(key: key); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Center( 28 | child: toggleMessage 29 | ? Text(toggleMessageTxt, 30 | style: toggleMessageType == cMessageType.error.toString() 31 | ? cErrorText 32 | : cSuccessText) 33 | : null); 34 | } 35 | } 36 | 37 | class CustomDrawer extends StatelessWidget { 38 | //final bool toggleSpinner; 39 | const CustomDrawer({Key key}) : super(key: key); 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Drawer( 44 | semanticLabel: cLabel, 45 | // Add a ListView to the drawer. This ensures the user can scroll 46 | // through the options in the drawer if there isn't enough vertical 47 | // space to fit everything. 48 | child: ListView( 49 | // Important: Remove any padding from the ListView. 50 | padding: EdgeInsets.all(4.0), 51 | children: [ 52 | UserAccountsDrawerHeader( 53 | accountName: Text(cAppTitle), 54 | accountEmail: Text(cEmailID), 55 | currentAccountPicture: CircleAvatar( 56 | backgroundImage: NetworkImage(cSampleImage), 57 | ), 58 | ), 59 | SizedBox(height: 10), 60 | ListTile( 61 | leading: Icon(Icons.book), 62 | title: Text( 63 | "AddressBook", 64 | style: cNavText, 65 | ), 66 | onTap: () => { 67 | Navigator.pushReplacementNamed( 68 | context, 69 | '/addressbook', 70 | ) 71 | }, 72 | ), 73 | ListTile( 74 | leading: Icon(Icons.business), 75 | title: Text( 76 | "Marketing", 77 | style: cNavText, 78 | ), 79 | onTap: null, 80 | subtitle: Text('Manage Campaigns, Leads & Opportunities.'), 81 | trailing: Icon(Icons.more_vert), 82 | isThreeLine: true, 83 | ), 84 | ListTile( 85 | leading: Icon(Icons.call), 86 | title: Text( 87 | "Call Register", 88 | style: cNavText, 89 | ), 90 | onTap: null, 91 | subtitle: Text('Manage Calls, eMails, enquiry, & visits'), 92 | trailing: Icon(Icons.more_vert), 93 | isThreeLine: true, 94 | ), 95 | ListTile( 96 | leading: Icon(Icons.dashboard), 97 | title: Text( 98 | "Customer", 99 | style: cNavText, 100 | ), 101 | onTap: null, 102 | subtitle: Text('Bills, Invoices & Sales register'), 103 | trailing: Icon(Icons.more_vert), 104 | isThreeLine: true, 105 | ), 106 | ListTile( 107 | leading: Icon(Icons.sms), 108 | title: Text( 109 | "HelpDesk", 110 | style: cNavText, 111 | ), 112 | onTap: null, 113 | subtitle: Text('Service Tickets, Workorder'), 114 | trailing: Icon(Icons.more_vert), 115 | isThreeLine: true, 116 | ), 117 | ListTile( 118 | leading: Icon(Icons.satellite), 119 | title: Text( 120 | "Vendors", 121 | style: cNavText, 122 | ), 123 | onTap: null, 124 | subtitle: Text('Vouchers, Bills, Invoices'), 125 | trailing: Icon(Icons.more_vert), 126 | isThreeLine: true, 127 | ), 128 | ListTile( 129 | leading: Icon(Icons.settings), 130 | title: Text( 131 | "Admin", 132 | style: cNavText, 133 | ), 134 | onTap: null, 135 | ), 136 | RaisedButton( 137 | child: Text('Logout'), 138 | color: Colors.blue, 139 | onPressed: () { 140 | Navigator.pushReplacementNamed( 141 | context, 142 | '/', 143 | ); 144 | }, 145 | ), 146 | ], 147 | ), 148 | ); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /dart_crm/lib/shared/custom_forms.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_web/material.dart'; 2 | 3 | class CustomFormRoundedTxt extends StatelessWidget { 4 | final Stream streamBloc; 5 | final bool obscureTxt; 6 | final onChangeTxt; 7 | final iconTxt; 8 | final hintTxt; 9 | final labelTxt; 10 | const CustomFormRoundedTxt( 11 | {Key key, 12 | this.streamBloc, 13 | this.obscureTxt, 14 | this.onChangeTxt, 15 | this.iconTxt, 16 | this.hintTxt, 17 | this.labelTxt}) 18 | : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return StreamBuilder( 23 | stream: streamBloc, 24 | builder: (context, snapshot) { 25 | return Container( 26 | width: 300.0, 27 | margin: EdgeInsets.only(top: 25.0), 28 | child: TextField( 29 | cursorColor: Colors.blueAccent, 30 | maxLength: 50, 31 | obscureText: obscureTxt, 32 | onChanged: onChangeTxt, 33 | decoration: InputDecoration( 34 | icon: iconTxt, 35 | border: OutlineInputBorder( 36 | borderRadius: BorderRadius.circular(16.0)), 37 | hintText: hintTxt, 38 | labelText: labelTxt, 39 | errorText: snapshot.error, 40 | ), 41 | )); 42 | }); 43 | } 44 | } 45 | 46 | class CustomFormTxt extends StatelessWidget { 47 | final Stream streamBloc; 48 | final int boxLength; 49 | final obscureTxt; 50 | final onChangeTxt; 51 | final iconTxt; 52 | final hintTxt; 53 | final labelTxt; 54 | const CustomFormTxt( 55 | {Key key, 56 | this.streamBloc, 57 | this.boxLength, 58 | this.obscureTxt, 59 | this.onChangeTxt, 60 | this.iconTxt, 61 | this.hintTxt, 62 | this.labelTxt}) 63 | : super(key: key); 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return StreamBuilder( 68 | stream: streamBloc, 69 | builder: (context, snapshot) { 70 | return Container( 71 | width: 300.0, 72 | margin: EdgeInsets.only(top: 5.0), 73 | child: TextField( 74 | cursorColor: Colors.blueAccent, 75 | maxLength: boxLength, 76 | obscureText: obscureTxt, 77 | onChanged: onChangeTxt, 78 | decoration: InputDecoration( 79 | icon: iconTxt, 80 | hintText: hintTxt, 81 | labelText: labelTxt, 82 | errorText: snapshot.error, 83 | ), 84 | )); 85 | }); 86 | } 87 | } 88 | 89 | class CustomFormDataTxt extends StatelessWidget { 90 | final String dbData; 91 | final bool isEnabled; 92 | final Stream streamBloc; 93 | final int boxLength; 94 | final obscureTxt; 95 | final onChangeTxt; 96 | final iconTxt; 97 | final hintTxt; 98 | final labelTxt; 99 | const CustomFormDataTxt( 100 | {Key key, 101 | this.dbData, 102 | this.isEnabled, 103 | this.streamBloc, 104 | this.boxLength, 105 | this.obscureTxt, 106 | this.onChangeTxt, 107 | this.iconTxt, 108 | this.hintTxt, 109 | this.labelTxt}) 110 | : super(key: key); 111 | 112 | @override 113 | Widget build(BuildContext context) { 114 | var txtData = TextEditingController(); 115 | txtData.text = dbData; 116 | return StreamBuilder( 117 | stream: streamBloc, 118 | builder: (context, snapshot) { 119 | return Container( 120 | width: 300.0, 121 | margin: EdgeInsets.only(top: 5.0), 122 | child: TextField( 123 | controller: txtData, 124 | cursorColor: Colors.blueAccent, 125 | enabled: isEnabled, 126 | maxLength: boxLength, 127 | obscureText: obscureTxt, 128 | onChanged: onChangeTxt, 129 | decoration: InputDecoration( 130 | icon: iconTxt, 131 | hintText: hintTxt, 132 | labelText: labelTxt, 133 | errorText: snapshot.error, 134 | ), 135 | )); 136 | }); 137 | } 138 | } 139 | 140 | class CustomFormData1Txt extends StatelessWidget { 141 | final bool isEnabled; 142 | final Stream streamBloc; 143 | final int boxLength; 144 | final obscureTxt; 145 | final onChangeTxt; 146 | final iconTxt; 147 | final hintTxt; 148 | final labelTxt; 149 | const CustomFormData1Txt( 150 | {Key key, 151 | this.isEnabled, 152 | this.streamBloc, 153 | this.boxLength, 154 | this.obscureTxt, 155 | this.onChangeTxt, 156 | this.iconTxt, 157 | this.hintTxt, 158 | this.labelTxt}) 159 | : super(key: key); 160 | 161 | @override 162 | Widget build(BuildContext context) { 163 | return StreamBuilder( 164 | stream: streamBloc, 165 | builder: (context, snapshot) { 166 | return Container( 167 | width: 300.0, 168 | margin: EdgeInsets.only(top: 5.0), 169 | child: TextField( 170 | cursorColor: Colors.blueAccent, 171 | enabled: isEnabled, 172 | maxLength: boxLength, 173 | obscureText: obscureTxt, 174 | onChanged: onChangeTxt, 175 | decoration: InputDecoration( 176 | icon: iconTxt, 177 | hintText: hintTxt, 178 | labelText: labelTxt, 179 | errorText: snapshot.error, 180 | ), 181 | )); 182 | }); 183 | } 184 | } 185 | 186 | class CustomFormTextBox extends StatelessWidget { 187 | final String dbData; 188 | final int boxLength; 189 | final bool isEnabled; 190 | final obscureTxt; 191 | final iconTxt; 192 | final hintTxt; 193 | final labelTxt; 194 | const CustomFormTextBox( 195 | {Key key, 196 | this.dbData, 197 | this.boxLength, 198 | this.isEnabled, 199 | this.obscureTxt, 200 | this.iconTxt, 201 | this.hintTxt, 202 | this.labelTxt}) 203 | : super(key: key); 204 | 205 | @override 206 | Widget build(BuildContext context) { 207 | var txt = TextEditingController(); 208 | txt.text = dbData; 209 | return Container( 210 | width: 300.0, 211 | margin: EdgeInsets.only(top: 5.0), 212 | child: TextFormField( 213 | controller: txt, 214 | enabled: isEnabled, 215 | cursorColor: Colors.blueAccent, 216 | maxLength: boxLength, 217 | obscureText: obscureTxt, 218 | decoration: InputDecoration( 219 | icon: iconTxt, 220 | hintText: hintTxt, 221 | labelText: labelTxt, 222 | ), 223 | ), 224 | ); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /dart_crm/lib/shared/custom_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_web/material.dart'; 2 | 3 | const cAppTitle = "CRM"; 4 | const cSettingsTitle = "Settings"; 5 | const cAddressBookTitle = "Address Book"; 6 | const cAddressBookAddTitle = "Add Address Book"; 7 | const cAddressBookEditTitle = "Edit Address Book"; 8 | const cSignUpTitle = "Sign up"; 9 | 10 | enum cMessageType { error, success } 11 | 12 | const cNavText = TextStyle( 13 | color: Colors.blueAccent, 14 | fontSize: 16.0, 15 | fontWeight: FontWeight.w500, 16 | fontStyle: FontStyle.normal); 17 | const cNavRightText = TextStyle( 18 | color: Colors.blueAccent, 19 | fontSize: 14.0, 20 | fontWeight: FontWeight.w500, 21 | fontStyle: FontStyle.normal); 22 | 23 | const cEmailID = "info@elishconsulting.com"; 24 | const cLabel = "Navigation Menu"; 25 | const cSampleImage = 26 | "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRJIzlrP5Fm5juFKR3saDL1rYDOV32y5IPF3UWC0CbIEhDgayJzrw"; 27 | 28 | const cBodyText = TextStyle( 29 | fontWeight: FontWeight.w400, 30 | color: Colors.blueGrey, 31 | ); 32 | const cErrorText = TextStyle( 33 | fontWeight: FontWeight.w400, 34 | color: Colors.red, 35 | ); 36 | const cWarnText = TextStyle( 37 | fontWeight: FontWeight.w400, 38 | color: Colors.yellow, 39 | ); 40 | const cSuccessText = TextStyle( 41 | fontWeight: FontWeight.w400, 42 | color: Colors.green, 43 | ); 44 | 45 | const cHeaderText = TextStyle( 46 | color: Colors.blueAccent, 47 | fontSize: 20.0, 48 | fontWeight: FontWeight.w500, 49 | fontStyle: FontStyle.normal); 50 | 51 | const cHeaderWhiteText = TextStyle( 52 | color: Colors.white, 53 | fontSize: 20.0, 54 | fontWeight: FontWeight.w500, 55 | fontStyle: FontStyle.normal); 56 | 57 | const cHeaderDarkText = TextStyle( 58 | color: Colors.blueGrey, 59 | fontSize: 20.0, 60 | fontWeight: FontWeight.w500, 61 | fontStyle: FontStyle.normal); 62 | 63 | var cThemeData = ThemeData( 64 | primaryColor: Colors.blue, 65 | //primarySwatch: Colors.white, 66 | buttonColor: Colors.blue, 67 | backgroundColor: Colors.white, 68 | buttonTheme: const ButtonThemeData(textTheme: ButtonTextTheme.primary), 69 | ); 70 | -------------------------------------------------------------------------------- /dart_crm/lib/shared/package/shared_preferences.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:flutter_web/services.dart'; 8 | import 'package:meta/meta.dart'; 9 | 10 | const MethodChannel _kChannel = 11 | MethodChannel('plugins.flutter.io/shared_preferences'); 12 | 13 | /// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing 14 | /// a persistent store for simple data. 15 | /// 16 | /// Data is persisted to disk asynchronously. 17 | class SharedPreferences { 18 | SharedPreferences._(this._preferenceCache); 19 | 20 | static const String _prefix = 'flutter.'; 21 | static SharedPreferences _instance; 22 | static Future getInstance() async { 23 | if (_instance == null) { 24 | final Map preferencesMap = 25 | await _getSharedPreferencesMap(); 26 | _instance = SharedPreferences._(preferencesMap); 27 | } 28 | return _instance; 29 | } 30 | 31 | /// The cache that holds all preferences. 32 | /// 33 | /// It is instantiated to the current state of the SharedPreferences or 34 | /// NSUserDefaults object and then kept in sync via setter methods in this 35 | /// class. 36 | /// 37 | /// It is NOT guaranteed that this cache and the device prefs will remain 38 | /// in sync since the setter method might fail for any reason. 39 | final Map _preferenceCache; 40 | 41 | /// Returns all keys in the persistent storage. 42 | Set getKeys() => Set.from(_preferenceCache.keys); 43 | 44 | /// Reads a value of any type from persistent storage. 45 | dynamic get(String key) => _preferenceCache[key]; 46 | 47 | /// Reads a value from persistent storage, throwing an exception if it's not a 48 | /// bool. 49 | bool getBool(String key) => _preferenceCache[key]; 50 | 51 | /// Reads a value from persistent storage, throwing an exception if it's not 52 | /// an int. 53 | int getInt(String key) => _preferenceCache[key]; 54 | 55 | /// Reads a value from persistent storage, throwing an exception if it's not a 56 | /// double. 57 | double getDouble(String key) => _preferenceCache[key]; 58 | 59 | /// Reads a value from persistent storage, throwing an exception if it's not a 60 | /// String. 61 | String getString(String key) => _preferenceCache[key]; 62 | 63 | /// Returns true if persistent storage the contains the given [key]. 64 | bool containsKey(String key) => _preferenceCache.containsKey(key); 65 | 66 | /// Reads a set of string values from persistent storage, throwing an 67 | /// exception if it's not a string set. 68 | List getStringList(String key) { 69 | List list = _preferenceCache[key]; 70 | if (list != null && list is! List) { 71 | list = list.cast().toList(); 72 | _preferenceCache[key] = list; 73 | } 74 | // Make a copy of the list so that later mutations won't propagate 75 | return list?.toList(); 76 | } 77 | 78 | /// Saves a boolean [value] to persistent storage in the background. 79 | /// 80 | /// If [value] is null, this is equivalent to calling [remove()] on the [key]. 81 | Future setBool(String key, bool value) => _setValue('Bool', key, value); 82 | 83 | /// Saves an integer [value] to persistent storage in the background. 84 | /// 85 | /// If [value] is null, this is equivalent to calling [remove()] on the [key]. 86 | Future setInt(String key, int value) => _setValue('Int', key, value); 87 | 88 | /// Saves a double [value] to persistent storage in the background. 89 | /// 90 | /// Android doesn't support storing doubles, so it will be stored as a float. 91 | /// 92 | /// If [value] is null, this is equivalent to calling [remove()] on the [key]. 93 | Future setDouble(String key, double value) => 94 | _setValue('Double', key, value); 95 | 96 | /// Saves a string [value] to persistent storage in the background. 97 | /// 98 | /// If [value] is null, this is equivalent to calling [remove()] on the [key]. 99 | Future setString(String key, String value) => 100 | _setValue('String', key, value); 101 | 102 | /// Saves a list of strings [value] to persistent storage in the background. 103 | /// 104 | /// If [value] is null, this is equivalent to calling [remove()] on the [key]. 105 | Future setStringList(String key, List value) => 106 | _setValue('StringList', key, value); 107 | 108 | /// Removes an entry from persistent storage. 109 | Future remove(String key) => _setValue(null, key, null); 110 | 111 | Future _setValue(String valueType, String key, Object value) { 112 | final Map params = { 113 | 'key': '$_prefix$key', 114 | }; 115 | if (value == null) { 116 | _preferenceCache.remove(key); 117 | return _kChannel 118 | .invokeMethod('remove', params) 119 | .then((dynamic result) => result); 120 | } else { 121 | if (value is List) { 122 | // Make a copy of the list so that later mutations won't propagate 123 | _preferenceCache[key] = value.toList(); 124 | } else { 125 | _preferenceCache[key] = value; 126 | } 127 | params['value'] = value; 128 | return _kChannel 129 | .invokeMethod('set$valueType', params) 130 | .then((dynamic result) => result); 131 | } 132 | } 133 | 134 | /// Always returns true. 135 | /// On iOS, synchronize is marked deprecated. On Android, we commit every set. 136 | @deprecated 137 | Future commit() async => await _kChannel.invokeMethod('commit'); 138 | 139 | /// Completes with true once the user preferences for the app has been cleared. 140 | Future clear() async { 141 | _preferenceCache.clear(); 142 | return await _kChannel.invokeMethod('clear'); 143 | } 144 | 145 | /// Fetches the latest values from the host platform. 146 | /// 147 | /// Use this method to observe modifications that were made in native code 148 | /// (without using the plugin) while the app is running. 149 | Future reload() async { 150 | final Map preferences = 151 | await SharedPreferences._getSharedPreferencesMap(); 152 | _preferenceCache.clear(); 153 | _preferenceCache.addAll(preferences); 154 | } 155 | 156 | static Future> _getSharedPreferencesMap() async { 157 | final Map fromSystem = 158 | await _kChannel.invokeMapMethod('getAll'); 159 | assert(fromSystem != null); 160 | // Strip the flutter. prefix from the returned preferences. 161 | final Map preferencesMap = {}; 162 | for (String key in fromSystem.keys) { 163 | assert(key.startsWith(_prefix)); 164 | preferencesMap[key.substring(_prefix.length)] = fromSystem[key]; 165 | } 166 | return preferencesMap; 167 | } 168 | 169 | /// Initializes the shared preferences with mock values for testing. 170 | /// 171 | /// If the singleton instance has been initialized already, it is automatically reloaded. 172 | @visibleForTesting 173 | static void setMockInitialValues(Map values) { 174 | _kChannel.setMockMethodCallHandler((MethodCall methodCall) async { 175 | if (methodCall.method == 'getAll') { 176 | return values; 177 | } 178 | return null; 179 | }); 180 | _instance?.reload(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /dart_crm/lib/views/addressbook/addressbook-add.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/blocs/blogic/addressbook_bloc.dart'; 2 | import 'package:dart_crm/blocs/validators.dart'; 3 | import 'package:dart_crm/models/datamodel.dart'; 4 | import 'package:dart_crm/shared/custom_components.dart'; 5 | import 'package:dart_crm/shared/custom_style.dart'; 6 | import 'package:flutter_web/cupertino.dart'; 7 | import 'package:flutter_web/material.dart'; 8 | 9 | class AddressBookAdd extends StatefulWidget { 10 | static const routeName = '/addressbook-add'; 11 | @override 12 | _addressBookAddState createState() => _addressBookAddState(); 13 | } 14 | 15 | class _addressBookAddState extends State { 16 | final _addressBookFormKey = GlobalKey(); 17 | // final GlobalKey _scaffoldKey = GlobalKey(); 18 | var txtFName = TextEditingController(); 19 | var txtMName = TextEditingController(); 20 | var txtLName = TextEditingController(); 21 | var txtAddress = TextEditingController(); 22 | var txtCity = TextEditingController(); 23 | var txtCountry = TextEditingController(); 24 | var txtZipCode = TextEditingController(); 25 | var txtEmailID = TextEditingController(); 26 | var txtEmailID2 = TextEditingController(); 27 | var txtPhone1 = TextEditingController(); 28 | var txtPhone2 = TextEditingController(); 29 | final focusMiddleName = FocusNode(); 30 | 31 | Future dbData; 32 | bool spinnerVisible = false; 33 | bool messageVisible = false; 34 | String messageTxt = ""; 35 | String messageType = ""; 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | addressBookBloc.dispose(); 45 | super.dispose(); 46 | } 47 | 48 | void _submitForm(AddressBookBloc bloc) async { 49 | final FormState form = _addressBookFormKey.currentState; 50 | if (!form.validate()) { 51 | // showMessage('Form is not valid! Please review and correct.'); 52 | setState(() { 53 | messageVisible = true; 54 | messageTxt = "Form is not valid! Please review and correct."; 55 | messageType = cMessageType.error.toString(); 56 | }); 57 | } else { 58 | form.save(); 59 | DBDataModel dbData = await bloc.setData({ 60 | "table_name": "addressbook", 61 | "first_name": txtFName.text, 62 | "middle_name": txtMName.text, 63 | "last_name": txtLName.text, 64 | "address": txtAddress.text, 65 | "city": txtCity.text, 66 | "country": txtCountry.text, 67 | "zip_code": txtZipCode.text, 68 | "emailid1": txtEmailID.text, 69 | "emailid2": txtEmailID2.text, 70 | "phone1": txtPhone1.text, 71 | "phone2": txtPhone2.text 72 | }); 73 | if (dbData.error) { 74 | setState(() { 75 | messageVisible = true; 76 | messageTxt = dbData.message; 77 | messageType = cMessageType.error.toString(); 78 | }); 79 | } else { 80 | messageVisible = true; 81 | messageTxt = "Record is updated. Please refresh page."; 82 | messageType = cMessageType.success.toString(); 83 | setState(() { 84 | spinnerVisible = !spinnerVisible; 85 | Future.delayed(const Duration(seconds: 1), () { 86 | spinnerVisible = !spinnerVisible; 87 | }); 88 | }); 89 | await Future.delayed(const Duration(seconds: 1), () {}); 90 | Navigator.pushReplacementNamed( 91 | context, 92 | '/addressbook', 93 | ); 94 | } 95 | } 96 | } 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | AddressBookBloc addressBookBloc = AddressBookBloc(); 101 | return Scaffold( 102 | appBar: AppBar( 103 | backgroundColor: Colors.blue, 104 | title: Text(cAddressBookTitle, style: cHeaderWhiteText), 105 | actions: [ 106 | IconButton( 107 | icon: Icon(Icons.settings, color: Colors.white, size: 30.0), 108 | tooltip: 'Setup', 109 | onPressed: () { 110 | Navigator.pushReplacementNamed( 111 | context, 112 | '/profile', 113 | ); 114 | }, 115 | ), 116 | ], 117 | ), 118 | body: Center( 119 | child: Column( 120 | children: [ 121 | SizedBox( 122 | width: 10, 123 | height: 20, 124 | ), 125 | formBuilder(context) 126 | ], 127 | ), 128 | ), 129 | drawer: CustomDrawer(), 130 | ); 131 | } 132 | 133 | Widget formBuilder(BuildContext context) { 134 | return Form( 135 | key: _addressBookFormKey, 136 | autovalidate: true, 137 | child: Container( 138 | height: 500, 139 | width: 400, 140 | child: ListView( 141 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 142 | children: [ 143 | Row(children: [ 144 | Text(cAddressBookAddTitle, style: cHeaderDarkText), 145 | SizedBox( 146 | width: 100, 147 | height: 10, 148 | ), 149 | GestureDetector( 150 | child: Icon(Icons.search, color: Colors.blue, size: 30.0), 151 | onTap: () { 152 | Navigator.pushReplacementNamed( 153 | context, 154 | '/addressbook', 155 | ); 156 | }, 157 | ), 158 | ]), 159 | Container( 160 | padding: const EdgeInsets.only(top: 20.0), 161 | child: RaisedButton( 162 | child: const Text('Save'), 163 | onPressed: () => {_submitForm(addressBookBloc)}, 164 | ), 165 | ), 166 | Container( 167 | margin: EdgeInsets.only(top: 15.0), 168 | ), 169 | CustomSpinner(toggleSpinner: spinnerVisible), 170 | CustomMessage( 171 | toggleMessage: messageVisible, 172 | toggleMessageType: messageType, 173 | toggleMessageTxt: messageTxt), 174 | TextFormField( 175 | controller: txtFName, 176 | cursorColor: Colors.blueAccent, 177 | maxLength: 50, 178 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 179 | textInputAction: TextInputAction.next, 180 | onFieldSubmitted: (v) { 181 | FocusScope.of(context).requestFocus(focusMiddleName); 182 | }, 183 | decoration: InputDecoration( 184 | icon: Icon(Icons.account_box), 185 | hintText: 'First Name', 186 | labelText: 'First Name *', 187 | fillColor: Colors.grey), 188 | validator: (val) => validatorBloc.validateFormText(val) 189 | ? 'please enter a valid text' 190 | : null, 191 | ), 192 | TextFormField( 193 | controller: txtMName, 194 | cursorColor: Colors.blueAccent, 195 | maxLength: 50, 196 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 197 | focusNode: focusMiddleName, 198 | decoration: InputDecoration( 199 | icon: Icon(Icons.account_box), 200 | hintText: 'Middle Name', 201 | labelText: 'Middle Name *', 202 | fillColor: Colors.grey), 203 | validator: (val) => validatorBloc.validateFormText(val) 204 | ? 'please enter a valid text' 205 | : null, 206 | ), 207 | TextFormField( 208 | controller: txtLName, 209 | cursorColor: Colors.blueAccent, 210 | maxLength: 50, 211 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 212 | decoration: InputDecoration( 213 | icon: Icon(Icons.account_box), 214 | hintText: 'Last Name', 215 | labelText: 'Last Name *', 216 | fillColor: Colors.grey), 217 | validator: (val) => validatorBloc.validateFormText(val) 218 | ? 'please enter a valid text' 219 | : null, 220 | ), 221 | TextFormField( 222 | controller: txtAddress, 223 | cursorColor: Colors.blueAccent, 224 | maxLength: 50, 225 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 226 | decoration: InputDecoration( 227 | icon: Icon(Icons.business), 228 | hintText: 'Address', 229 | labelText: 'Address *', 230 | fillColor: Colors.grey), 231 | validator: (val) => validatorBloc.validateFormText(val) 232 | ? 'please enter a valid address' 233 | : null, 234 | ), 235 | TextFormField( 236 | controller: txtCity, 237 | cursorColor: Colors.blueAccent, 238 | maxLength: 50, 239 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 240 | decoration: InputDecoration( 241 | icon: Icon(Icons.business), 242 | hintText: 'City', 243 | labelText: 'City *', 244 | fillColor: Colors.grey), 245 | validator: (val) => validatorBloc.validateFormText(val) 246 | ? 'please enter a valid City' 247 | : null, 248 | ), 249 | TextFormField( 250 | controller: txtCountry, 251 | cursorColor: Colors.blueAccent, 252 | maxLength: 50, 253 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 254 | decoration: InputDecoration( 255 | icon: Icon(Icons.flag), 256 | hintText: 'Country', 257 | labelText: 'Country *', 258 | fillColor: Colors.grey), 259 | validator: (val) => validatorBloc.validateFormText(val) 260 | ? 'please enter a valid Country Name' 261 | : null, 262 | ), 263 | TextFormField( 264 | controller: txtZipCode, 265 | cursorColor: Colors.blueAccent, 266 | maxLength: 10, 267 | inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], 268 | decoration: InputDecoration( 269 | icon: Icon(Icons.gps_fixed), 270 | hintText: 'Zip Code', 271 | labelText: 'Zip Code *', 272 | fillColor: Colors.grey), 273 | validator: (val) => validatorBloc.validateFormText(val) 274 | ? 'please enter a valid zipcode' 275 | : null, 276 | ), 277 | new TextFormField( 278 | controller: txtEmailID, 279 | cursorColor: Colors.blueAccent, 280 | maxLength: 50, 281 | keyboardType: TextInputType.emailAddress, 282 | decoration: InputDecoration( 283 | icon: Icon(Icons.email), 284 | hintText: 'eMail ID #1', 285 | labelText: 'emailid 1 *', 286 | fillColor: Colors.grey), 287 | validator: (val) => validatorBloc.isValidEmail(val) 288 | ? null 289 | : 'please enter a valid email ID #1', 290 | ), 291 | new TextFormField( 292 | controller: txtEmailID2, 293 | cursorColor: Colors.blueAccent, 294 | maxLength: 50, 295 | decoration: InputDecoration( 296 | icon: Icon(Icons.email), 297 | hintText: 'eMail ID #2', 298 | labelText: 'emailid 2 *', 299 | fillColor: Colors.grey), 300 | validator: (val) => validatorBloc.isValidEmail(val) 301 | ? null 302 | : 'please enter a valid email ID #2', 303 | ), 304 | TextFormField( 305 | controller: txtPhone1, 306 | cursorColor: Colors.blueAccent, 307 | maxLength: 50, 308 | inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], 309 | decoration: InputDecoration( 310 | icon: Icon(Icons.phone), 311 | hintText: 'Phone #1', 312 | labelText: 'Phone #1 *', 313 | fillColor: Colors.grey), 314 | validator: (val) => validatorBloc.isValidPhoneNumber(val) 315 | ? 'please enter a valid Phone 1 #' 316 | : null, 317 | ), 318 | TextFormField( 319 | controller: txtPhone2, 320 | cursorColor: Colors.blueAccent, 321 | maxLength: 50, 322 | inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], 323 | decoration: InputDecoration( 324 | icon: Icon(Icons.phone), 325 | hintText: 'Phone #2', 326 | labelText: 'Phone #2 *', 327 | fillColor: Colors.grey), 328 | validator: (val) => validatorBloc.isValidPhoneNumber(val) 329 | ? 'please enter a valid Phone 2 #' 330 | : null, 331 | ), 332 | ], 333 | ), 334 | )); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /dart_crm/lib/views/addressbook/addressbook-edit.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/blocs/blogic/addressbook_bloc.dart'; 2 | import 'package:dart_crm/blocs/validators.dart'; 3 | import 'package:dart_crm/models/datamodel.dart'; 4 | import 'package:dart_crm/shared/custom_components.dart'; 5 | import 'package:dart_crm/shared/custom_style.dart'; 6 | import 'package:flutter_web/cupertino.dart'; 7 | import 'package:flutter_web/material.dart'; 8 | 9 | class AddressBookEdit extends StatefulWidget { 10 | final String addressid; 11 | AddressBookEdit({Key key, @required this.addressid}) : super(key: key); 12 | static const routeName = '/addressbook-edit'; 13 | @override 14 | _addressBookEditState createState() => _addressBookEditState(); 15 | } 16 | 17 | class _addressBookEditState extends State { 18 | final _addressBookFormKey = GlobalKey(); 19 | AddressBookBloc addressBookBloc = AddressBookBloc(); 20 | // final GlobalKey _scaffoldKey = GlobalKey(); 21 | var txtAddressId = TextEditingController(); 22 | var txtFName = TextEditingController(); 23 | var txtMName = TextEditingController(); 24 | var txtLName = TextEditingController(); 25 | var txtAddress = TextEditingController(); 26 | var txtCity = TextEditingController(); 27 | var txtCountry = TextEditingController(); 28 | var txtZipCode = TextEditingController(); 29 | var txtEmailID1 = TextEditingController(); 30 | var txtEmailID2 = TextEditingController(); 31 | var txtPhone1 = TextEditingController(); 32 | var txtPhone2 = TextEditingController(); 33 | final focusMiddleName = FocusNode(); 34 | 35 | Future dbData; 36 | bool spinnerVisible = false; 37 | bool messageVisible = false; 38 | String messageTxt = ""; 39 | String messageType = ""; 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | _fetchData(widget.addressid.toString(), addressBookBloc); 45 | } 46 | 47 | Future _fetchData(String id, AddressBookBloc bloc) async { 48 | setState(() { 49 | messageVisible = false; 50 | dbData = bloc.getData(id, ""); 51 | }); 52 | var x = await bloc.getData(id, ""); 53 | return dbData; 54 | } 55 | 56 | @override 57 | void dispose() { 58 | addressBookBloc.dispose(); 59 | super.dispose(); 60 | } 61 | 62 | void _submitForm(AddressBookBloc bloc) async { 63 | final FormState form = _addressBookFormKey.currentState; 64 | if (!form.validate()) { 65 | // showMessage('Form is not valid! Please review and correct.'); 66 | setState(() { 67 | messageVisible = true; 68 | messageTxt = "Form is not valid! Please review and correct."; 69 | messageType = cMessageType.error.toString(); 70 | }); 71 | } else { 72 | form.save(); 73 | DBDataModel dbData = await bloc.setData({ 74 | "table_name": "addressbook", 75 | "addressid": txtAddressId.text, 76 | "first_name": txtFName.text, 77 | "middle_name": txtMName.text, 78 | "last_name": txtLName.text, 79 | "address": txtAddress.text, 80 | "city": txtCity.text, 81 | "country": txtCountry.text, 82 | "zip_code": txtZipCode.text, 83 | "emailid1": txtEmailID1.text, 84 | "emailid2": txtEmailID2.text, 85 | "phone1": txtPhone1.text, 86 | "phone2": txtPhone2.text 87 | }); 88 | if (dbData.error) { 89 | setState(() { 90 | messageVisible = true; 91 | messageTxt = dbData.message; 92 | messageType = cMessageType.error.toString(); 93 | }); 94 | } else { 95 | messageVisible = true; 96 | messageTxt = "Record is updated. Please refresh page."; 97 | messageType = cMessageType.success.toString(); 98 | setState(() { 99 | spinnerVisible = !spinnerVisible; 100 | Future.delayed(const Duration(seconds: 1), () { 101 | spinnerVisible = !spinnerVisible; 102 | }); 103 | }); 104 | await Future.delayed(const Duration(seconds: 1), () {}); 105 | Navigator.pushReplacementNamed( 106 | context, 107 | '/addressbook', 108 | ); 109 | } 110 | } 111 | } 112 | 113 | @override 114 | Widget build(BuildContext context) { 115 | return Scaffold( 116 | appBar: AppBar( 117 | backgroundColor: Colors.blue, 118 | title: Text(cAddressBookTitle, style: cHeaderWhiteText), 119 | actions: [ 120 | IconButton( 121 | icon: Icon(Icons.settings, color: Colors.white, size: 30.0), 122 | tooltip: 'Setup', 123 | onPressed: () { 124 | Navigator.pushReplacementNamed( 125 | context, 126 | '/profile', 127 | ); 128 | }, 129 | ), 130 | ], 131 | ), 132 | body: Center( 133 | child: Column( 134 | children: [ 135 | SizedBox( 136 | width: 10, 137 | height: 20, 138 | ), 139 | // formBuilder(context, dbData) 140 | formData(context, dbData) 141 | ], 142 | ), 143 | ), 144 | drawer: CustomDrawer(), 145 | ); 146 | } 147 | 148 | Widget formData(BuildContext context, dbData) { 149 | return FutureBuilder( 150 | //future: _fetchData(bloc), // a previously-obtained Future or null 151 | future: dbData, 152 | builder: 153 | (BuildContext context, AsyncSnapshot snapshot) { 154 | switch (snapshot.connectionState) { 155 | case ConnectionState.none: 156 | return Text('click Search Icon to show recent results.'); 157 | case ConnectionState.active: 158 | case ConnectionState.waiting: 159 | //return Text('Awaiting result...'); 160 | return CircularProgressIndicator(); 161 | case ConnectionState.done: 162 | // add jwt error component here 163 | if (snapshot.hasError) return Text('Error: ${snapshot.error}'); 164 | //return Text('Result: ${snapshot.data.message}'); 165 | if (snapshot.data.error) { 166 | return CustomMessage( 167 | toggleMessage: snapshot.data.error, 168 | toggleMessageType: cMessageType.error.toString(), 169 | toggleMessageTxt: snapshot.data.message); 170 | } else { 171 | return formBuilder(context, snapshot.data); 172 | } 173 | } 174 | return null; // unreachable 175 | }, 176 | ); 177 | } 178 | 179 | Widget formBuilder(BuildContext context, dbData) { 180 | txtAddressId.text = dbData.data[0].addressid.toString(); 181 | txtFName.text = dbData.data[0].first_name; 182 | txtMName.text = dbData.data[0].middle_name; 183 | txtLName.text = dbData.data[0].last_name; 184 | txtAddress.text = dbData.data[0].address; 185 | txtCity.text = dbData.data[0].city; 186 | txtCountry.text = dbData.data[0].country; 187 | txtZipCode.text = dbData.data[0].zip_code; 188 | txtEmailID1.text = dbData.data[0].emailid1; 189 | txtEmailID2.text = dbData.data[0].emailid2; 190 | txtPhone1.text = dbData.data[0].phone1; 191 | txtPhone2.text = dbData.data[0].phone2; 192 | return Form( 193 | key: _addressBookFormKey, 194 | autovalidate: true, 195 | child: Container( 196 | height: 500, 197 | width: 400, 198 | child: ListView( 199 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 200 | children: [ 201 | Row(children: [ 202 | Text(cAddressBookEditTitle, style: cHeaderDarkText), 203 | SizedBox( 204 | width: 100, 205 | height: 10, 206 | ), 207 | GestureDetector( 208 | child: Icon(Icons.search, color: Colors.blue, size: 30.0), 209 | onTap: () { 210 | Navigator.pushReplacementNamed( 211 | context, 212 | '/addressbook', 213 | ); 214 | }, 215 | ), 216 | SizedBox( 217 | width: 50, 218 | height: 10, 219 | ), 220 | GestureDetector( 221 | child: Icon(Icons.delete, color: Colors.blue, size: 30.0), 222 | onTap: () { 223 | // TODO: delete logic 224 | Navigator.pushReplacementNamed( 225 | context, 226 | '/addressbook', 227 | ); 228 | }, 229 | ), 230 | ]), 231 | Container( 232 | padding: const EdgeInsets.only(top: 20.0), 233 | child: RaisedButton( 234 | child: const Text('Save'), 235 | onPressed: () => {_submitForm(addressBookBloc)}, 236 | ), 237 | ), 238 | Container( 239 | margin: EdgeInsets.only(top: 15.0), 240 | ), 241 | CustomSpinner(toggleSpinner: spinnerVisible), 242 | CustomMessage( 243 | toggleMessage: messageVisible, 244 | toggleMessageType: messageType, 245 | toggleMessageTxt: messageTxt), 246 | TextFormField( 247 | controller: txtAddressId, 248 | enabled: false, 249 | decoration: InputDecoration( 250 | icon: Icon(Icons.account_circle), 251 | hintText: 'Address ID', 252 | labelText: 'Address ID *', 253 | fillColor: Colors.grey), 254 | ), 255 | TextFormField( 256 | controller: txtFName, 257 | cursorColor: Colors.blueAccent, 258 | maxLength: 50, 259 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 260 | textInputAction: TextInputAction.next, 261 | onFieldSubmitted: (v) { 262 | FocusScope.of(context).requestFocus(focusMiddleName); 263 | }, 264 | decoration: InputDecoration( 265 | icon: Icon(Icons.account_box), 266 | hintText: 'First Name', 267 | labelText: 'First Name *', 268 | fillColor: Colors.grey), 269 | validator: (val) => validatorBloc.validateFormText(val) 270 | ? 'please enter a valid text' 271 | : null, 272 | ), 273 | TextFormField( 274 | controller: txtMName, 275 | cursorColor: Colors.blueAccent, 276 | maxLength: 50, 277 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 278 | focusNode: focusMiddleName, 279 | decoration: InputDecoration( 280 | icon: Icon(Icons.account_box), 281 | hintText: 'Middle Name', 282 | labelText: 'Middle Name *', 283 | fillColor: Colors.grey), 284 | validator: (val) => validatorBloc.validateFormText(val) 285 | ? 'please enter a valid text' 286 | : null, 287 | ), 288 | TextFormField( 289 | controller: txtLName, 290 | cursorColor: Colors.blueAccent, 291 | maxLength: 50, 292 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 293 | decoration: InputDecoration( 294 | icon: Icon(Icons.account_box), 295 | hintText: 'Last Name', 296 | labelText: 'Last Name *', 297 | fillColor: Colors.grey), 298 | validator: (val) => validatorBloc.validateFormText(val) 299 | ? 'please enter a valid text' 300 | : null, 301 | ), 302 | TextFormField( 303 | controller: txtAddress, 304 | cursorColor: Colors.blueAccent, 305 | maxLength: 50, 306 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 307 | decoration: InputDecoration( 308 | icon: Icon(Icons.business), 309 | hintText: 'Address', 310 | labelText: 'Address *', 311 | fillColor: Colors.grey), 312 | validator: (val) => validatorBloc.validateFormText(val) 313 | ? 'please enter a valid address' 314 | : null, 315 | ), 316 | TextFormField( 317 | controller: txtCity, 318 | cursorColor: Colors.blueAccent, 319 | maxLength: 50, 320 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 321 | decoration: InputDecoration( 322 | icon: Icon(Icons.business), 323 | hintText: 'City', 324 | labelText: 'City *', 325 | fillColor: Colors.grey), 326 | validator: (val) => validatorBloc.validateFormText(val) 327 | ? 'please enter a valid City' 328 | : null, 329 | ), 330 | TextFormField( 331 | controller: txtCountry, 332 | cursorColor: Colors.blueAccent, 333 | maxLength: 50, 334 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 335 | decoration: InputDecoration( 336 | icon: Icon(Icons.flag), 337 | hintText: 'Country', 338 | labelText: 'Country *', 339 | fillColor: Colors.grey), 340 | validator: (val) => validatorBloc.validateFormText(val) 341 | ? 'please enter a valid Country Name' 342 | : null, 343 | ), 344 | TextFormField( 345 | controller: txtZipCode, 346 | cursorColor: Colors.blueAccent, 347 | maxLength: 10, 348 | inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], 349 | decoration: InputDecoration( 350 | icon: Icon(Icons.gps_fixed), 351 | hintText: 'Zip Code', 352 | labelText: 'Zip Code *', 353 | fillColor: Colors.grey), 354 | validator: (val) => validatorBloc.validateFormText(val) 355 | ? 'please enter a valid zipcode' 356 | : null, 357 | ), 358 | TextFormField( 359 | controller: txtEmailID1, 360 | cursorColor: Colors.blueAccent, 361 | maxLength: 50, 362 | decoration: InputDecoration( 363 | icon: Icon(Icons.email), 364 | hintText: 'eMail ID #1', 365 | labelText: 'emailid 1 *', 366 | fillColor: Colors.grey), 367 | validator: (val) => validatorBloc.isValidEmail(val) 368 | ? null 369 | : 'please enter a valid email ID #1', 370 | ), 371 | TextFormField( 372 | controller: txtEmailID2, 373 | cursorColor: Colors.blueAccent, 374 | maxLength: 50, 375 | decoration: InputDecoration( 376 | icon: Icon(Icons.email), 377 | hintText: 'eMail ID #2', 378 | labelText: 'emailid 2 *', 379 | fillColor: Colors.grey), 380 | validator: (val) => validatorBloc.isValidEmail(val) 381 | ? null 382 | : 'please enter a valid email ID #2', 383 | ), 384 | TextFormField( 385 | controller: txtPhone1, 386 | cursorColor: Colors.blueAccent, 387 | maxLength: 50, 388 | inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], 389 | decoration: InputDecoration( 390 | icon: Icon(Icons.phone), 391 | hintText: 'Phone #1', 392 | labelText: 'Phone #1 *', 393 | fillColor: Colors.grey), 394 | validator: (val) => validatorBloc.isValidPhoneNumber(val) 395 | ? 'please enter a valid Phone 1 #' 396 | : null, 397 | ), 398 | TextFormField( 399 | controller: txtPhone2, 400 | cursorColor: Colors.blueAccent, 401 | maxLength: 50, 402 | inputFormatters: [WhitelistingTextInputFormatter.digitsOnly], 403 | decoration: InputDecoration( 404 | icon: Icon(Icons.phone), 405 | hintText: 'Phone #2', 406 | labelText: 'Phone #2 *', 407 | fillColor: Colors.grey), 408 | validator: (val) => validatorBloc.isValidPhoneNumber(val) 409 | ? 'please enter a valid Phone 2 #' 410 | : null, 411 | ), 412 | ], 413 | ), 414 | )); 415 | } 416 | } 417 | 418 | /*** 419 | import 'package:dart_crm/blocs/blogic/addressbook_bloc.dart'; 420 | import 'package:dart_crm/blocs/validators.dart'; 421 | import 'package:dart_crm/models/datamodel.dart'; 422 | import 'package:dart_crm/shared/custom_components.dart'; 423 | import 'package:dart_crm/shared/custom_style.dart'; 424 | import 'package:flutter_web/cupertino.dart'; 425 | import 'package:flutter_web/material.dart'; 426 | 427 | class AddressBookEdit extends StatefulWidget { 428 | static const routeName = '/addressbook-edit'; 429 | @override 430 | _addressBookEditState createState() => _addressBookEditState(); 431 | } 432 | 433 | class _addressBookEditState extends State { 434 | final _addressBookFormKey = GlobalKey(); 435 | // final GlobalKey _scaffoldKey = GlobalKey(); 436 | var txtSearch = TextEditingController(); 437 | var txtUserID = TextEditingController(); 438 | var txtName = TextEditingController(); 439 | var txtPassword = TextEditingController(); 440 | Future dbData; 441 | bool spinnerVisible = false; 442 | bool messageVisible = false; 443 | String messageTxt = ""; 444 | String messageType = ""; 445 | 446 | @override 447 | void initState() { 448 | super.initState(); 449 | } 450 | 451 | @override 452 | void dispose() { 453 | addressBookBloc.dispose(); 454 | super.dispose(); 455 | } 456 | 457 | Future _fetchData(AddressBookBloc bloc) async { 458 | setState(() { 459 | messageVisible = false; 460 | dbData = bloc.getUser(); 461 | }); 462 | return dbData; 463 | } 464 | 465 | void _submitForm(AddressBookBloc bloc) async { 466 | final FormState form = _addressBookFormKey.currentState; 467 | if (!form.validate()) { 468 | // showMessage('Form is not valid! Please review and correct.'); 469 | setState(() { 470 | messageVisible = true; 471 | messageTxt = "Form is not valid! Please review and correct."; 472 | messageType = cMessageType.error.toString(); 473 | }); 474 | } else { 475 | form.save(); //This invokes each onSaved event 476 | DBDataModel dbData = await bloc.setData({ 477 | "userid": txtUserID.text, 478 | "name": txtName.text, 479 | "jwttoken": txtPassword.text 480 | }); 481 | if (dbData.error) { 482 | setState(() { 483 | messageVisible = true; 484 | messageTxt = dbData.message; 485 | messageType = cMessageType.error.toString(); 486 | }); 487 | } else { 488 | messageVisible = true; 489 | messageTxt = "Record is updated. Please refresh page."; 490 | messageType = cMessageType.success.toString(); 491 | setState(() { 492 | spinnerVisible = !spinnerVisible; 493 | Future.delayed(const Duration(seconds: 1), () { 494 | spinnerVisible = !spinnerVisible; 495 | }); 496 | }); 497 | await Future.delayed(const Duration(seconds: 1), () {}); 498 | Navigator.pushReplacementNamed( 499 | context, 500 | '/profile', 501 | ); 502 | } 503 | } 504 | } 505 | 506 | @override 507 | Widget build(BuildContext context) { 508 | AddressBookBloc addressBookBloc = AddressBookBloc(); 509 | return Scaffold( 510 | appBar: AppBar( 511 | backgroundColor: Colors.blue, 512 | title: Text(cAddressBookEditTitle, style: cHeaderWhiteText), 513 | actions: [ 514 | IconButton( 515 | icon: Icon(Icons.settings, color: Colors.white, size: 30.0), 516 | tooltip: 'Setup', 517 | onPressed: () { 518 | Navigator.pushReplacementNamed( 519 | context, 520 | '/profile', 521 | ); 522 | }, 523 | ), 524 | ], 525 | ), 526 | body: Center( 527 | child: Column( 528 | children: [ 529 | Row( 530 | children: [ 531 | SizedBox(width: 150, height: 50), 532 | Container( 533 | width: 200, 534 | child: TextFormField( 535 | controller: txtSearch, 536 | cursorColor: Colors.blueAccent, 537 | maxLength: 100, 538 | //inputFormatters: [new LengthLimitingTextInputFormatter(50)], 539 | //onChanged: settingsBloc.changeEmail, 540 | decoration: InputDecoration( 541 | // icon: Icon(Icons.email), 542 | hintText: 'ph# or id# or name', 543 | labelText: 'search by any text', 544 | fillColor: Colors.grey 545 | //errorText: snapshot.error, 546 | ), 547 | // validator: (val) => validatorBloc.validateFormText(val) 548 | // ? 'please enter a valid email' 549 | // : null, 550 | ), 551 | ), 552 | GestureDetector( 553 | child: Icon(Icons.search, color: Colors.blue, size: 30.0), 554 | onTap: () { 555 | Navigator.pushReplacementNamed( 556 | context, 557 | '/addressbook', 558 | ); 559 | }, 560 | ), 561 | SizedBox(width: 20, height: 50), 562 | GestureDetector( 563 | child: Icon(Icons.add, color: Colors.blue, size: 30.0), 564 | // TODO: navigate to add addressbook page 565 | onTap: () => _fetchData(addressBookBloc)), 566 | ], 567 | ), 568 | formData(context) 569 | ], 570 | ), 571 | ), 572 | drawer: CustomDrawer(), 573 | ); 574 | } 575 | 576 | Widget formData(BuildContext context) { 577 | return FutureBuilder( 578 | //future: _fetchData(bloc), // a previously-obtained Future or null 579 | future: dbData, 580 | builder: (BuildContext context, AsyncSnapshot snapshot) { 581 | switch (snapshot.connectionState) { 582 | case ConnectionState.none: 583 | return Text('click Search Icon to show recent results.'); 584 | case ConnectionState.active: 585 | case ConnectionState.waiting: 586 | //return Text('Awaiting result...'); 587 | return CircularProgressIndicator(); 588 | case ConnectionState.done: 589 | // add jwt error component here 590 | if (snapshot.hasError) return Text('Error: ${snapshot.error}'); 591 | //return Text('Result: ${snapshot.data.message}'); 592 | if (snapshot.data.error) { 593 | return CustomMessage( 594 | toggleMessage: snapshot.data.error, 595 | toggleMessageType: cMessageType.error.toString(), 596 | toggleMessageTxt: snapshot.data.message); 597 | } else { 598 | return formBuilder(context, snapshot.data); 599 | } 600 | } 601 | return null; // unreachable 602 | }, 603 | ); 604 | } 605 | 606 | Widget formBuilder(BuildContext context, DBDataModel dbData) { 607 | txtUserID.text = dbData.data[0].userid; 608 | txtName.text = dbData.data[0].name; 609 | //txtPassword.text = dbData.data[0].jwttoken; 610 | // don't fill back password, it's jwttoken coming from REST API 611 | txtPassword.text = ""; 612 | return Form( 613 | key: _addressBookFormKey, 614 | autovalidate: true, 615 | child: Container( 616 | height: 500, 617 | width: 300, 618 | child: ListView( 619 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 620 | children: [ 621 | Text("Settings", style: cHeaderDarkText), 622 | TextFormField( 623 | controller: txtUserID, 624 | cursorColor: Colors.blueAccent, 625 | enabled: false, 626 | maxLength: 50, 627 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 628 | //onChanged: settingsBloc.changeEmail, 629 | decoration: InputDecoration( 630 | icon: Icon(Icons.email), 631 | hintText: 'username@domain.com', 632 | labelText: 'EmailID *', 633 | fillColor: Colors.grey 634 | //errorText: snapshot.error, 635 | ), 636 | validator: (val) => validatorBloc.validateFormText(val) 637 | ? 'please enter a valid email' 638 | : null, 639 | ), 640 | TextFormField( 641 | controller: txtName, 642 | cursorColor: Colors.blueAccent, 643 | maxLength: 50, 644 | enabled: true, 645 | //onChanged: settingsBloc.changeEmail, 646 | decoration: InputDecoration( 647 | icon: Icon(Icons.account_box), 648 | hintText: 'Enter your name', 649 | labelText: 'Name *', 650 | //errorText: snapshot.error, 651 | ), 652 | validator: (val) => validatorBloc.validateFormText(val) 653 | ? 'please enter a valid name' 654 | : null, 655 | ), 656 | TextFormField( 657 | controller: txtPassword, 658 | cursorColor: Colors.blueAccent, 659 | enabled: true, 660 | maxLength: 300, 661 | obscureText: true, 662 | //onChanged: settingsBloc.changeEmail, 663 | decoration: InputDecoration( 664 | icon: Icon(Icons.lock_outline), 665 | hintText: 'enter password', 666 | labelText: 'Password *', 667 | //errorText: snapshot.error, 668 | ), 669 | validator: (val) => validatorBloc.validateFormText(val) 670 | ? 'please enter a valid password' 671 | : null, 672 | ), 673 | Container( 674 | margin: EdgeInsets.only(top: 25.0), 675 | ), 676 | Container( 677 | margin: EdgeInsets.only(top: 15.0), 678 | ), 679 | CustomSpinner(toggleSpinner: spinnerVisible), 680 | CustomMessage( 681 | toggleMessage: messageVisible, 682 | toggleMessageType: messageType, 683 | toggleMessageTxt: messageTxt), 684 | Container( 685 | padding: const EdgeInsets.only(top: 20.0), 686 | child: RaisedButton( 687 | child: const Text('Save'), 688 | onPressed: () => {_submitForm(addressBookBloc)}, 689 | ), 690 | ), 691 | ], 692 | ), 693 | )); 694 | } 695 | } 696 | 697 | **/ 698 | -------------------------------------------------------------------------------- /dart_crm/lib/views/addressbook/addressbook.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/blocs/blogic/addressbook_bloc.dart'; 2 | import 'package:dart_crm/models/datamodel.dart'; 3 | import 'package:dart_crm/shared/custom_components.dart'; 4 | import 'package:dart_crm/shared/custom_style.dart'; 5 | import 'package:dart_crm/views/addressbook/addressbook-edit.dart'; 6 | import 'package:flutter_web/cupertino.dart'; 7 | import 'package:flutter_web/material.dart'; 8 | 9 | class AddressBook extends StatefulWidget { 10 | static const routeName = '/addressbook'; 11 | @override 12 | _addressBookState createState() => _addressBookState(); 13 | } 14 | 15 | class _addressBookState extends State { 16 | var txtSearch = TextEditingController(); 17 | Future dbData; 18 | List addressItems = []; 19 | bool spinnerVisible = false; 20 | bool messageVisible = false; 21 | String messageTxt = ""; 22 | String messageType = ""; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | addressBookBloc.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | Future _fetchData(AddressBookBloc bloc) async { 36 | setState(() { 37 | messageVisible = false; 38 | dbData = bloc.getData("", txtSearch.text); 39 | }); 40 | return dbData; 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | AddressBookBloc addressBookBloc = AddressBookBloc(); 46 | return Scaffold( 47 | appBar: AppBar( 48 | backgroundColor: Colors.blue, 49 | title: Text(cAddressBookTitle, style: cHeaderWhiteText), 50 | actions: [ 51 | IconButton( 52 | icon: Icon(Icons.settings, color: Colors.white, size: 30.0), 53 | tooltip: 'Setup', 54 | onPressed: () { 55 | Navigator.pushReplacementNamed( 56 | context, 57 | '/profile', 58 | ); 59 | }, 60 | ), 61 | ], 62 | ), 63 | body: Center( 64 | child: Column( 65 | children: [ 66 | Row( 67 | children: [ 68 | SizedBox(width: 150, height: 50), 69 | Container( 70 | width: 200, 71 | child: TextFormField( 72 | controller: txtSearch, 73 | cursorColor: Colors.blueAccent, 74 | maxLength: 100, 75 | decoration: InputDecoration( 76 | hintText: 'ph# or id# or name', 77 | labelText: 'search by any text', 78 | fillColor: Colors.grey), 79 | ), 80 | ), 81 | GestureDetector( 82 | child: Icon(Icons.search, color: Colors.blue, size: 30.0), 83 | onTap: () => _fetchData(addressBookBloc)), 84 | SizedBox(width: 20, height: 50), 85 | GestureDetector( 86 | child: Icon(Icons.add, color: Colors.blue, size: 30.0), 87 | // TODO: navigate to add addressbook page 88 | onTap: () { 89 | Navigator.pushReplacementNamed( 90 | context, 91 | '/addressbook-add', 92 | ); 93 | }), 94 | ], 95 | ), 96 | formData(context) 97 | ], 98 | ), 99 | ), 100 | drawer: CustomDrawer(), 101 | ); 102 | } 103 | 104 | Widget formData(BuildContext context) { 105 | return FutureBuilder( 106 | //future: _fetchData(bloc), // a previously-obtained Future or null 107 | future: dbData, 108 | builder: 109 | (BuildContext context, AsyncSnapshot snapshot) { 110 | switch (snapshot.connectionState) { 111 | case ConnectionState.none: 112 | return Text('click Search Icon to show results.'); 113 | case ConnectionState.active: 114 | case ConnectionState.waiting: 115 | //return Text('Awaiting result...'); 116 | return CircularProgressIndicator(); 117 | case ConnectionState.done: 118 | if (snapshot.hasError) { 119 | return Text('Error: ${snapshot.error}'); 120 | } else { 121 | if (snapshot.data.error) { 122 | return CustomMessage( 123 | toggleMessage: snapshot.data.error, 124 | toggleMessageType: cMessageType.error.toString(), 125 | toggleMessageTxt: snapshot.data.message); 126 | } else { 127 | return _buildResultsList(context, snapshot.data); 128 | } 129 | } 130 | } 131 | return null; // unreachable 132 | }, 133 | ); 134 | } 135 | 136 | Widget _buildResultsList(context, AddressDataModel dbdata) { 137 | return Container( 138 | width: 500, 139 | height: 500, 140 | child: ListView.builder( 141 | // itemExtent: 125, 142 | itemCount: dbdata.data.length, 143 | itemBuilder: (context, index) => 144 | _buildListItem(context, dbdata.data[index])), 145 | ); 146 | } 147 | 148 | Widget _buildListItem(context, AddressBookModel data) { 149 | return ListTile( 150 | subtitle: Column( 151 | children: [ 152 | Row( 153 | children: [ 154 | GestureDetector( 155 | child: Icon(Icons.mode_edit, color: Colors.blue, size: 20.0), 156 | onTap: () { 157 | Navigator.push( 158 | context, 159 | MaterialPageRoute( 160 | builder: (context) => AddressBookEdit( 161 | addressid: data.addressid.toString()), 162 | ), 163 | ); 164 | } 165 | // { 166 | // print("form addbook resuts"); 167 | // print(data.addressid); 168 | // Navigator.pushReplacementNamed(context, '/addressbook-edit', 169 | // arguments: {"addressid": data.addressid}); 170 | // } 171 | ), 172 | SizedBox(width: 15.0), 173 | Text(data.first_name), 174 | SizedBox(width: 2.0), 175 | Text(data.middle_name), 176 | SizedBox(width: 2.0), 177 | Text(data.last_name), 178 | ], 179 | ), 180 | SizedBox(height: 2.0), 181 | Row( 182 | children: [ 183 | SizedBox(width: 25.0), 184 | Text(data.address), 185 | ], 186 | ), 187 | SizedBox(height: 2.0), 188 | Row( 189 | children: [ 190 | SizedBox(width: 25.0), 191 | Text(data.city), 192 | SizedBox(width: 2.0), 193 | Text(data.zip_code), 194 | SizedBox(width: 2.0), 195 | Text(data.country), 196 | ], 197 | ), 198 | SizedBox(height: 2.0), 199 | Row( 200 | children: [ 201 | SizedBox(width: 25.0), 202 | Text(data.emailid1), 203 | SizedBox(width: 2.0), 204 | Text(data.emailid2), 205 | ], 206 | ), 207 | SizedBox(height: 2.0), 208 | Row( 209 | children: [ 210 | SizedBox(width: 25.0), 211 | Text(data.phone1), 212 | SizedBox(width: 2.0), 213 | Text(data.phone2), 214 | ], 215 | ), 216 | ], 217 | ), 218 | ); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /dart_crm/lib/views/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/shared/custom_style.dart'; 2 | import 'package:dart_crm/views/addressbook/addressbook-add.dart'; 3 | import 'package:dart_crm/views/addressbook/addressbook-edit.dart'; 4 | import 'package:dart_crm/views/addressbook/addressbook.dart'; 5 | import 'package:dart_crm/views/auth/login.dart'; 6 | import 'package:dart_crm/views/auth/profile.dart'; 7 | import 'package:dart_crm/views/auth/settings.dart'; 8 | import 'package:dart_crm/views/auth/signup.dart'; 9 | import 'package:flutter_web/material.dart'; 10 | 11 | class App extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | routes: { 16 | // '/': (context) => ERPHomePage(), - can not set if home: ERPHomePage() is setup, only works with initiated route 17 | LoginScreen.routeName: (context) => LoginScreen(), 18 | Settings.routeName: (context) => Settings(), 19 | Profile.routeName: (context) => Profile(), 20 | SignUp.routeName: (context) => SignUp(), 21 | AddressBook.routeName: (context) => AddressBook(), 22 | AddressBookAdd.routeName: (context) => AddressBookAdd(), 23 | AddressBookEdit.routeName: (context) => AddressBookEdit(), 24 | }, 25 | debugShowCheckedModeBanner: false, 26 | theme: cThemeData, 27 | title: cAppTitle, 28 | home: ERPHomePage()); 29 | } 30 | } 31 | 32 | class ERPHomePage extends StatelessWidget { 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Row( 38 | children: [ 39 | IconButton( 40 | // TODO: Later change this navigation to about us page 41 | icon: Icon(Icons.dashboard, color: Colors.white), 42 | tooltip: 'Login', 43 | onPressed: null, 44 | ), 45 | Text( 46 | cAppTitle, 47 | style: TextStyle(wordSpacing: 2), 48 | ), 49 | ], 50 | ), 51 | ), 52 | body: Center(child: LoginScreen()), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /dart_crm/lib/views/auth/login.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dart_crm/blocs/auth/auth_bloc.dart'; 4 | import 'package:dart_crm/models/datamodel.dart'; 5 | import 'package:dart_crm/shared/custom_components.dart'; 6 | import 'package:dart_crm/shared/custom_forms.dart'; 7 | import 'package:dart_crm/shared/custom_style.dart'; 8 | import 'package:flutter_web/material.dart'; 9 | 10 | class LoginScreen extends StatefulWidget { 11 | static const routeName = '/login'; 12 | @override 13 | _loginScreenState createState() => _loginScreenState(); 14 | } 15 | 16 | class _loginScreenState extends State { 17 | bool spinnerVisible = false; 18 | bool messageVisible = false; 19 | String messageTxt = ""; 20 | String messageType = ""; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | } 26 | 27 | @override 28 | void dispose() { 29 | authBloc.dispose(); 30 | super.dispose(); 31 | } 32 | 33 | Future fetchData(AuthBloc bloc) async { 34 | setState(() => spinnerVisible = !spinnerVisible); 35 | DBDataModel dbData = await bloc.validateUserAuth(); 36 | // TODO: comment this forced delay, this is only to check spinner 37 | await new Future.delayed(const Duration(seconds: 1), () {}); 38 | if (dbData.error) { 39 | messageVisible = dbData.error; 40 | messageTxt = dbData.message; 41 | messageType = cMessageType.error.toString(); 42 | } else { 43 | messageVisible = dbData.message != "" ? true : false; 44 | messageTxt = dbData.message; 45 | messageType = cMessageType.success.toString(); 46 | // obtain shared preferences 47 | // store jwttoken 48 | // FLUTTER_WEB version is not working to store at present 49 | // TODO: enable this for android/ios version 50 | // final prefs = await SharedPreferences.getInstance(); 51 | // prefs.setInt('token', dbData.data[0].jwttoken); 52 | //await new Future.delayed(const Duration(seconds: 1), () {}); 53 | Navigator.pushReplacementNamed( 54 | context, 55 | '/profile', 56 | ); 57 | } 58 | setState(() => spinnerVisible = !spinnerVisible); 59 | return dbData; 60 | } 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | AuthBloc authBloc = AuthBloc(); 65 | return Container( 66 | margin: EdgeInsets.all(20.0), 67 | child: Center( 68 | child: Column( 69 | children: [ 70 | Container( 71 | margin: EdgeInsets.only(top: 25.0), 72 | ), 73 | Text("Login", style: cHeaderDarkText), 74 | //formEmailTxt(authBloc), 75 | CustomFormRoundedTxt( 76 | streamBloc: authBloc.email, 77 | obscureTxt: false, 78 | onChangeTxt: authBloc.changeEmail, 79 | iconTxt: Icon(Icons.email), 80 | hintTxt: 'username@domain.com', 81 | labelTxt: 'EmailID *', 82 | ), 83 | Container( 84 | margin: EdgeInsets.only(top: 5.0), 85 | ), 86 | //formPasswordTxt(authBloc), 87 | CustomFormRoundedTxt( 88 | streamBloc: authBloc.password, 89 | obscureTxt: true, 90 | onChangeTxt: authBloc.changePassword, 91 | iconTxt: Icon(Icons.lock_outline), 92 | hintTxt: 'enter password', 93 | labelTxt: 'Password *', 94 | ), 95 | Container( 96 | margin: EdgeInsets.only(top: 25.0), 97 | ), 98 | //dbFunctions(authBloc), 99 | Padding( 100 | padding: EdgeInsets.symmetric(vertical: 10.0), 101 | ), 102 | CustomSpinner(toggleSpinner: spinnerVisible), 103 | CustomMessage( 104 | toggleMessage: messageVisible, 105 | toggleMessageType: messageType, 106 | toggleMessageTxt: messageTxt), 107 | Container( 108 | margin: EdgeInsets.only(top: 15.0), 109 | ), 110 | formSubmitBtn(authBloc), 111 | Container( 112 | margin: EdgeInsets.only(top: 15.0), 113 | ), 114 | GestureDetector( 115 | onTap: () { 116 | Navigator.pushReplacementNamed( 117 | context, 118 | '/signup', 119 | ); 120 | }, 121 | child: Chip( 122 | avatar: CircleAvatar( 123 | backgroundColor: Colors.blueAccent, 124 | child: Icon(Icons.add), 125 | ), 126 | label: Text("Create new Account", style: cNavText))) 127 | ], 128 | ), 129 | ), 130 | ); 131 | } 132 | 133 | Widget formSubmitBtn(AuthBloc bloc) { 134 | // TODO: Change this to column and add row for error, or circular indicator 135 | // see below example 136 | return StreamBuilder( 137 | stream: bloc.submitValid, 138 | builder: (context, snapshot) { 139 | return RaisedButton( 140 | child: Text('Login'), 141 | color: Colors.blue, 142 | onPressed: snapshot.hasData ? () => fetchData(bloc) : null); 143 | }, 144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /dart_crm/lib/views/auth/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/blocs/auth/settings_bloc.dart'; 2 | import 'package:dart_crm/blocs/validators.dart'; 3 | import 'package:dart_crm/models/datamodel.dart'; 4 | import 'package:dart_crm/shared/custom_components.dart'; 5 | import 'package:dart_crm/shared/custom_style.dart'; 6 | import 'package:flutter_web/cupertino.dart'; 7 | import 'package:flutter_web/material.dart'; 8 | 9 | class Profile extends StatefulWidget { 10 | static const routeName = '/profile'; 11 | @override 12 | _profileState createState() => _profileState(); 13 | } 14 | 15 | class _profileState extends State { 16 | final _profileFormKey = GlobalKey(); 17 | // final GlobalKey _scaffoldKey = GlobalKey(); 18 | var txtUserID = TextEditingController(); 19 | var txtName = TextEditingController(); 20 | var txtPassword = TextEditingController(); 21 | Future dbData; 22 | bool spinnerVisible = false; 23 | bool messageVisible = false; 24 | String messageTxt = ""; 25 | String messageType = ""; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | settingsBloc.dispose(); 35 | super.dispose(); 36 | } 37 | 38 | Future _fetchData(SettingsBloc bloc) async { 39 | setState(() { 40 | messageVisible = false; 41 | dbData = bloc.getUser(); 42 | }); 43 | return dbData; 44 | } 45 | 46 | // void showMessage(String message, [MaterialColor color = Colors.red]) { 47 | // _scaffoldKey.currentState.showSnackBar( 48 | // new SnackBar(backgroundColor: color, content: new Text(message))); 49 | // } 50 | 51 | void _submitForm(SettingsBloc bloc) async { 52 | final FormState form = _profileFormKey.currentState; 53 | if (!form.validate()) { 54 | // showMessage('Form is not valid! Please review and correct.'); 55 | setState(() { 56 | messageVisible = true; 57 | messageTxt = "Form is not valid! Please review and correct."; 58 | messageType = cMessageType.error.toString(); 59 | }); 60 | } else { 61 | form.save(); //This invokes each onSaved event 62 | // UserModel formData = UserModel( 63 | // userid: txtUserID.text, 64 | // name: txtName.text, 65 | // jwttoken: txtPassword.text); 66 | DBDataModel dbData = await bloc.setUser({ 67 | "userid": txtUserID.text, 68 | "name": txtName.text, 69 | "jwttoken": txtPassword.text 70 | }); 71 | if (dbData.error) { 72 | setState(() { 73 | messageVisible = true; 74 | messageTxt = dbData.message; 75 | messageType = cMessageType.error.toString(); 76 | }); 77 | } else { 78 | messageVisible = true; 79 | messageTxt = "Record is updated. Please refresh page."; 80 | messageType = cMessageType.success.toString(); 81 | setState(() { 82 | spinnerVisible = !spinnerVisible; 83 | Future.delayed(const Duration(seconds: 1), () { 84 | spinnerVisible = !spinnerVisible; 85 | }); 86 | }); 87 | await Future.delayed(const Duration(seconds: 1), () {}); 88 | Navigator.pushReplacementNamed( 89 | context, 90 | '/profile', 91 | ); 92 | } 93 | } 94 | } 95 | 96 | @override 97 | Widget build(BuildContext context) { 98 | SettingsBloc settingsBloc = SettingsBloc(); 99 | return Scaffold( 100 | appBar: AppBar( 101 | backgroundColor: Colors.blue, 102 | title: Text(cSettingsTitle, style: cHeaderWhiteText), 103 | actions: [ 104 | IconButton( 105 | icon: Icon(Icons.settings, color: Colors.white, size: 30.0), 106 | tooltip: 'Setup', 107 | onPressed: () { 108 | Navigator.pushReplacementNamed( 109 | context, 110 | '/profile', 111 | ); 112 | }, 113 | ), 114 | ], 115 | ), 116 | body: Center( 117 | child: Column( 118 | children: [ 119 | SizedBox(width: 200, height: 50), 120 | Row( 121 | children: [ 122 | SizedBox(width: 200, height: 50), 123 | RaisedButton( 124 | child: Text('Show User Settings'), 125 | color: Colors.blue, 126 | onPressed: () => _fetchData(settingsBloc)), 127 | SizedBox(width: 10, height: 50), 128 | RaisedButton( 129 | child: Text('Logout'), 130 | color: Colors.blue, 131 | onPressed: () { 132 | Navigator.pushReplacementNamed( 133 | context, 134 | '/', 135 | ); 136 | }, 137 | ) 138 | ], 139 | ), 140 | formData(context) 141 | ], 142 | ), 143 | ), 144 | drawer: CustomDrawer(), 145 | ); 146 | } 147 | 148 | Widget formData(BuildContext context) { 149 | return FutureBuilder( 150 | //future: _fetchData(bloc), // a previously-obtained Future or null 151 | future: dbData, 152 | builder: (BuildContext context, AsyncSnapshot snapshot) { 153 | switch (snapshot.connectionState) { 154 | case ConnectionState.none: 155 | return Text('click Show User settings.'); 156 | case ConnectionState.active: 157 | case ConnectionState.waiting: 158 | //return Text('Awaiting result...'); 159 | return CircularProgressIndicator(); 160 | case ConnectionState.done: 161 | // add jwt error component here 162 | if (snapshot.hasError) return Text('Error: ${snapshot.error}'); 163 | //return Text('Result: ${snapshot.data.message}'); 164 | if (snapshot.data.error) { 165 | return CustomMessage( 166 | toggleMessage: snapshot.data.error, 167 | toggleMessageType: cMessageType.error.toString(), 168 | toggleMessageTxt: snapshot.data.message); 169 | } else { 170 | return formBuilder(context, snapshot.data); 171 | } 172 | } 173 | return null; // unreachable 174 | }, 175 | ); 176 | } 177 | 178 | Widget formBuilder(BuildContext context, DBDataModel dbData) { 179 | txtUserID.text = dbData.data[0].userid; 180 | txtName.text = dbData.data[0].name; 181 | //txtPassword.text = dbData.data[0].jwttoken; 182 | // don't fill back password, it's jwttoken coming from REST API 183 | txtPassword.text = ""; 184 | return Form( 185 | key: _profileFormKey, 186 | autovalidate: true, 187 | child: Container( 188 | height: 500, 189 | width: 300, 190 | child: ListView( 191 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 192 | children: [ 193 | Text("Settings", style: cHeaderDarkText), 194 | TextFormField( 195 | controller: txtUserID, 196 | cursorColor: Colors.blueAccent, 197 | enabled: false, 198 | maxLength: 50, 199 | inputFormatters: [new LengthLimitingTextInputFormatter(50)], 200 | //onChanged: settingsBloc.changeEmail, 201 | decoration: InputDecoration( 202 | icon: Icon(Icons.email), 203 | hintText: 'username@domain.com', 204 | labelText: 'EmailID *', 205 | fillColor: Colors.grey 206 | //errorText: snapshot.error, 207 | ), 208 | validator: (val) => validatorBloc.validateFormText(val) 209 | ? 'please enter a valid email' 210 | : null, 211 | ), 212 | TextFormField( 213 | controller: txtName, 214 | cursorColor: Colors.blueAccent, 215 | maxLength: 50, 216 | enabled: true, 217 | //onChanged: settingsBloc.changeEmail, 218 | decoration: InputDecoration( 219 | icon: Icon(Icons.account_box), 220 | hintText: 'Enter your name', 221 | labelText: 'Name *', 222 | //errorText: snapshot.error, 223 | ), 224 | validator: (val) => validatorBloc.validateFormText(val) 225 | ? 'please enter a valid name' 226 | : null, 227 | ), 228 | TextFormField( 229 | controller: txtPassword, 230 | cursorColor: Colors.blueAccent, 231 | enabled: true, 232 | maxLength: 300, 233 | obscureText: true, 234 | //onChanged: settingsBloc.changeEmail, 235 | decoration: InputDecoration( 236 | icon: Icon(Icons.lock_outline), 237 | hintText: 'enter password', 238 | labelText: 'Password *', 239 | //errorText: snapshot.error, 240 | ), 241 | validator: (val) => validatorBloc.validateFormText(val) 242 | ? 'please enter a valid password' 243 | : null, 244 | ), 245 | Container( 246 | margin: EdgeInsets.only(top: 25.0), 247 | ), 248 | Container( 249 | margin: EdgeInsets.only(top: 15.0), 250 | ), 251 | CustomSpinner(toggleSpinner: spinnerVisible), 252 | CustomMessage( 253 | toggleMessage: messageVisible, 254 | toggleMessageType: messageType, 255 | toggleMessageTxt: messageTxt), 256 | Container( 257 | padding: const EdgeInsets.only(top: 20.0), 258 | child: RaisedButton( 259 | child: const Text('Save'), 260 | onPressed: () => {_submitForm(settingsBloc)}, 261 | ), 262 | ), 263 | ], 264 | ), 265 | )); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /dart_crm/lib/views/auth/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/blocs/auth/settings_bloc.dart'; 2 | import 'package:dart_crm/models/datamodel.dart'; 3 | import 'package:dart_crm/shared/custom_components.dart'; 4 | import 'package:dart_crm/shared/custom_forms.dart'; 5 | import 'package:dart_crm/shared/custom_style.dart'; 6 | import 'package:flutter_web/cupertino.dart'; 7 | import 'package:flutter_web/material.dart'; 8 | 9 | class Settings extends StatefulWidget { 10 | static const routeName = '/settings'; 11 | @override 12 | _settingsState createState() => _settingsState(); 13 | } 14 | 15 | class _settingsState extends State { 16 | bool spinnerVisible = false; 17 | bool messageVisible = false; 18 | String messageTxt = ""; 19 | String messageType = ""; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | void dispose() { 28 | settingsBloc.dispose(); 29 | super.dispose(); 30 | } 31 | 32 | Future fetchData(SettingsBloc bloc) async { 33 | setState(() => spinnerVisible = !spinnerVisible); 34 | DBDataModel dbData = await bloc.getUser(); 35 | // TODO: comment this forced delay, this is only to check spinner 36 | await new Future.delayed(const Duration(seconds: 1), () {}); 37 | if (dbData.error) { 38 | messageVisible = dbData.error; 39 | messageTxt = dbData.message; 40 | messageType = cMessageType.error.toString(); 41 | } else { 42 | messageVisible = dbData.message != "" ? true : false; 43 | messageTxt = dbData.message; 44 | messageType = cMessageType.success.toString(); 45 | } 46 | // TODO: comment this forced delay, this is only to check spinner 47 | await new Future.delayed(const Duration(seconds: 1), () {}); 48 | setState(() { 49 | settingsBloc.changeEmail(dbData.data[0].userid); 50 | }); 51 | setState(() => spinnerVisible = !spinnerVisible); 52 | return dbData; 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | SettingsBloc settingsBloc = SettingsBloc(); 58 | return Scaffold( 59 | appBar: AppBar( 60 | backgroundColor: Colors.blue, 61 | title: Text(cSettingsTitle, style: cHeaderWhiteText), 62 | actions: [ 63 | IconButton( 64 | icon: Icon(Icons.settings, color: Colors.white, size: 30.0), 65 | tooltip: 'Setup', 66 | onPressed: () { 67 | Navigator.pushReplacementNamed( 68 | context, 69 | '/settings', 70 | ); 71 | }, 72 | ), 73 | ], 74 | ), 75 | body: Center( 76 | child: Container( 77 | margin: EdgeInsets.all(20.0), 78 | child: Center( 79 | child: Column( 80 | children: [ 81 | Row( 82 | children: [ 83 | SizedBox(width: 200, height: 50), 84 | RaisedButton( 85 | child: Text('Show User Settings'), 86 | color: Colors.blue, 87 | onPressed: () => fetchData(settingsBloc)), 88 | SizedBox(width: 10, height: 50), 89 | RaisedButton( 90 | child: Text('Logout'), 91 | color: Colors.blue, 92 | onPressed: () { 93 | Navigator.pushReplacementNamed( 94 | context, 95 | '/', 96 | ); 97 | }, 98 | ) 99 | ], 100 | ), 101 | Container( 102 | margin: EdgeInsets.only(top: 25.0), 103 | ), 104 | Text("Settings", style: cHeaderDarkText), 105 | //formEmailTxt(authBloc), 106 | CustomFormTxt( 107 | streamBloc: settingsBloc.email, 108 | boxLength: 50, 109 | obscureTxt: false, 110 | onChangeTxt: settingsBloc.changeEmail, 111 | iconTxt: Icon(Icons.email), 112 | hintTxt: 'username@domain.com', 113 | labelTxt: 'EmailID *', 114 | ), 115 | Container( 116 | margin: EdgeInsets.only(top: 5.0), 117 | ), 118 | CustomFormTxt( 119 | streamBloc: settingsBloc.name, 120 | boxLength: 50, 121 | obscureTxt: false, 122 | onChangeTxt: settingsBloc.changeName, 123 | iconTxt: Icon(Icons.account_box), 124 | hintTxt: 'enter name', 125 | labelTxt: 'Name *', 126 | ), 127 | Container( 128 | margin: EdgeInsets.only(top: 5.0), 129 | ), 130 | //formPasswordTxt(authBloc), 131 | CustomFormTxt( 132 | streamBloc: settingsBloc.password, 133 | boxLength: 20, 134 | obscureTxt: true, 135 | onChangeTxt: settingsBloc.changePassword, 136 | iconTxt: Icon(Icons.lock_outline), 137 | hintTxt: 'enter password', 138 | labelTxt: 'Password *', 139 | ), 140 | Container( 141 | margin: EdgeInsets.only(top: 25.0), 142 | ), 143 | //dbFunctions(authBloc), 144 | Padding( 145 | padding: EdgeInsets.symmetric(vertical: 10.0), 146 | ), 147 | CustomSpinner(toggleSpinner: spinnerVisible), 148 | CustomMessage( 149 | toggleMessage: messageVisible, 150 | toggleMessageType: messageType, 151 | toggleMessageTxt: messageTxt), 152 | Container( 153 | margin: EdgeInsets.only(top: 15.0), 154 | ), 155 | formSubmitBtn(settingsBloc) 156 | ], 157 | ), 158 | ), 159 | ), 160 | ), 161 | drawer: CustomDrawer(), 162 | ); 163 | } 164 | 165 | Widget formSubmitBtn(SettingsBloc bloc) { 166 | // TODO: Change this to column and add row for error, or circular indicator 167 | // see below example 168 | return StreamBuilder( 169 | stream: bloc.submitValid, 170 | builder: (context, snapshot) { 171 | return RaisedButton( 172 | child: Text('Save'), 173 | color: Colors.blue, 174 | onPressed: snapshot.hasData ? () => fetchData(bloc) : null); 175 | }, 176 | ); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /dart_crm/lib/views/auth/signup.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_crm/blocs/auth/signup_bloc.dart'; 2 | import 'package:dart_crm/models/datamodel.dart'; 3 | import 'package:dart_crm/shared/custom_components.dart'; 4 | import 'package:dart_crm/shared/custom_forms.dart'; 5 | import 'package:dart_crm/shared/custom_style.dart'; 6 | import 'package:flutter_web/material.dart'; 7 | 8 | class SignUp extends StatefulWidget { 9 | static const routeName = '/signup'; 10 | @override 11 | _signupState createState() => _signupState(); 12 | } 13 | 14 | class _signupState extends State { 15 | bool spinnerVisible = false; 16 | bool messageVisible = false; 17 | String messageTxt = ""; 18 | String messageType = ""; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | signupBloc.dispose(); 28 | super.dispose(); 29 | } 30 | 31 | Future fetchData(SignUpBloc bloc) async { 32 | setState(() => spinnerVisible = !spinnerVisible); 33 | DBDataModel dbData = await bloc.signupUser(); 34 | // TODO: comment this forced delay, this is only to check spinner 35 | await new Future.delayed(const Duration(seconds: 1), () {}); 36 | if (dbData.error) { 37 | messageVisible = dbData.error; 38 | messageTxt = dbData.message; 39 | messageType = cMessageType.error.toString(); 40 | } else { 41 | messageVisible = dbData.message != "" ? true : false; 42 | messageTxt = dbData.message; 43 | messageType = cMessageType.success.toString(); 44 | // TODO: comment this forced delay, this is only to check spinner 45 | await new Future.delayed(const Duration(seconds: 1), () {}); 46 | Navigator.pushReplacementNamed( 47 | context, 48 | '/', 49 | ); 50 | } 51 | setState(() => spinnerVisible = !spinnerVisible); 52 | return dbData; 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | SignUpBloc signUpBloc = SignUpBloc(); 58 | return Scaffold( 59 | appBar: AppBar( 60 | leading: Icon(Icons.add_box), 61 | backgroundColor: Colors.blue, 62 | title: Text(cSignUpTitle, style: cHeaderWhiteText), 63 | actions: [ 64 | IconButton( 65 | icon: Icon(Icons.close, color: Colors.white, size: 30.0), 66 | tooltip: 'Setup', 67 | onPressed: () { 68 | Navigator.pushReplacementNamed( 69 | context, 70 | '/', 71 | ); 72 | }, 73 | ), 74 | ], 75 | ), 76 | body: Container( 77 | margin: EdgeInsets.all(20.0), 78 | child: Center( 79 | child: Column( 80 | children: [ 81 | Container( 82 | margin: EdgeInsets.only(top: 25.0), 83 | ), 84 | Text("Sign up", style: cHeaderDarkText), 85 | //formEmailTxt(authBloc), 86 | CustomFormTxt( 87 | streamBloc: signUpBloc.email, 88 | boxLength: 50, 89 | obscureTxt: false, 90 | onChangeTxt: signUpBloc.changeEmail, 91 | iconTxt: Icon(Icons.email), 92 | hintTxt: 'username@domain.com', 93 | labelTxt: 'EmailID *', 94 | ), 95 | Container( 96 | margin: EdgeInsets.only(top: 5.0), 97 | ), 98 | CustomFormTxt( 99 | streamBloc: signUpBloc.name, 100 | boxLength: 50, 101 | obscureTxt: false, 102 | onChangeTxt: signUpBloc.changeName, 103 | iconTxt: Icon(Icons.account_box), 104 | hintTxt: 'enter name', 105 | labelTxt: 'Name *', 106 | ), 107 | Container( 108 | margin: EdgeInsets.only(top: 5.0), 109 | ), 110 | //formPasswordTxt(authBloc), 111 | CustomFormTxt( 112 | streamBloc: signUpBloc.password, 113 | boxLength: 20, 114 | obscureTxt: true, 115 | onChangeTxt: signUpBloc.changePassword, 116 | iconTxt: Icon(Icons.lock_outline), 117 | hintTxt: 'enter password', 118 | labelTxt: 'Password *', 119 | ), 120 | Container( 121 | margin: EdgeInsets.only(top: 25.0), 122 | ), 123 | //dbFunctions(authBloc), 124 | Padding( 125 | padding: EdgeInsets.symmetric(vertical: 10.0), 126 | ), 127 | CustomSpinner(toggleSpinner: spinnerVisible), 128 | CustomMessage( 129 | toggleMessage: messageVisible, 130 | toggleMessageType: messageType, 131 | toggleMessageTxt: messageTxt), 132 | Container( 133 | margin: EdgeInsets.only(top: 15.0), 134 | ), 135 | formSubmitBtn(signUpBloc), 136 | Container( 137 | margin: EdgeInsets.only(top: 15.0), 138 | ), 139 | GestureDetector( 140 | onTap: () { 141 | Navigator.pushReplacementNamed( 142 | context, 143 | '/', 144 | ); 145 | }, 146 | child: Chip( 147 | avatar: CircleAvatar( 148 | backgroundColor: Colors.blueAccent, 149 | child: Icon(Icons.add), 150 | ), 151 | label: Text("Already have an account", style: cNavText))) 152 | ], 153 | ), 154 | ), 155 | ), 156 | ); 157 | } 158 | 159 | Widget formSubmitBtn(SignUpBloc bloc) { 160 | // TODO: Change this to column and add row for error, or circular indicator 161 | // see below example 162 | return StreamBuilder( 163 | stream: bloc.submitValid, 164 | builder: (context, snapshot) { 165 | return RaisedButton( 166 | child: Text('Create User'), 167 | color: Colors.blue, 168 | onPressed: snapshot.hasData ? () => fetchData(bloc) : null); 169 | }, 170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /dart_crm/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_crm 2 | description: A new Flutter project. 3 | 4 | environment: 5 | sdk: '>=2.3.0-dev.0.1 <3.0.0' 6 | 7 | dependencies: 8 | flutter_web: any 9 | flutter_web_ui: any 10 | rxdart: 11 | 12 | dev_dependencies: 13 | # Enables the `pub run build_runner` command 14 | build_runner: ^1.1.2 15 | # Includes the JavaScript compilers 16 | build_web_compilers: ^1.0.0 17 | 18 | # dev_dependencies: 19 | # build_runner: any 20 | # build_web_compilers: ^2.0.0 21 | 22 | dependency_overrides: 23 | flutter_web: 24 | git: 25 | url: https://github.com/flutter/flutter_web 26 | path: packages/flutter_web 27 | flutter_web_ui: 28 | git: 29 | url: https://github.com/flutter/flutter_web 30 | path: packages/flutter_web_ui 31 | 32 | flutter: 33 | uses-material-design: true -------------------------------------------------------------------------------- /dart_crm/web/assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "family": "MaterialIcons", 4 | "fonts": [ 5 | { 6 | "asset": "https://fonts.gstatic.com/s/materialicons/v42/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" 7 | } 8 | ] 9 | } 10 | ] -------------------------------------------------------------------------------- /dart_crm/web/assets/fonts/Merriweather-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmitXShukla/Flutter-MYSQL-CRM-app/dedd1de42865d2faaac662375fcd6e6805c23b71/dart_crm/web/assets/fonts/Merriweather-Bold.ttf -------------------------------------------------------------------------------- /dart_crm/web/assets/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmitXShukla/Flutter-MYSQL-CRM-app/dedd1de42865d2faaac662375fcd6e6805c23b71/dart_crm/web/assets/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /dart_crm/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /dart_crm/web/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_web_ui/ui.dart' as ui; 2 | import 'package:dart_crm/main.dart' as app; 3 | 4 | main() async { 5 | await ui.webOnlyInitializePlatform(); 6 | app.main(); 7 | } -------------------------------------------------------------------------------- /nodejs_crm_server/.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | JWT_SECRET=MySuperSecretPassword 3 | allowedOrigins=http://localhost:8080/signup,http://localhost:3000,http://localhost:4200 4 | DB_USERNAME= 5 | DB_PASSWORD= 6 | DB_NAME= 7 | DB_HOST= -------------------------------------------------------------------------------- /nodejs_crm_server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const app = express(); 4 | // store config variables in dotenv 5 | require('dotenv').config(); 6 | const cors = require('cors'); 7 | 8 | // ****** allow cross-origin requests code START ****** // 9 | app.use(cors()); // uncomment this to enable all CORS and delete cors(corsOptions) in below code 10 | const allowedOrigins = process.env.allowedOrigins.split(','); 11 | /** 12 | app.use(cors({ 13 | origin: function (origin, callback) { 14 | // allow requests with no origin 15 | // (like mobile apps or curl requests) 16 | if (!origin) return callback(null, true); 17 | 18 | if (allowedOrigins.indexOf(origin) === -1) { 19 | var msg = 'The CORS policy for this site does not ' + 'allow access from the specified Origin.'; 20 | return callback(new Error(msg), false); 21 | } 22 | return callback(null, true); 23 | } 24 | })); 25 | */ 26 | // ****** allow cross-origin requests code END ****** // 27 | 28 | // ****** validation rules START ****** // 29 | const valFunctions = require('./validators/validate'); 30 | // ****** validation rules END ****** // 31 | 32 | // app Routes 33 | // create application/json parser 34 | const jsonParser = bodyParser.json() 35 | // create application/x-www-form-urlencoded parser 36 | const urlencodedParser = bodyParser.urlencoded({ extended: false }) 37 | 38 | // POST /login gets urlencoded bodies 39 | app.post('/signup', urlencodedParser, function (req, res) { 40 | if(valFunctions.checkInputDataNULL(req,res)) return false; 41 | if(valFunctions.checkInputDataQuality(req,res)) return false; 42 | //if(valFunctions.checkJWTToken(req,res)) return false; 43 | //if(valFunctions.checkUserAuthRole(req,res)) return false; 44 | var dbFunctions = require('./models/connector'); 45 | dbFunctions.createUser(req,res); 46 | }); 47 | app.post('/login', urlencodedParser, function (req, res) { 48 | if(valFunctions.checkInputDataNULL(req,res)) return false; 49 | if(valFunctions.checkInputDataQuality(req,res)) return false; 50 | //if(valFunctions.checkJWTToken(req,res)) return false; 51 | //if(valFunctions.checkUserAuthRole(req,res)) return false; 52 | var dbFunctions = require('./models/connector'); 53 | dbFunctions.loginUser(req,res); 54 | }); 55 | app.post('/getuser', urlencodedParser, function (req, res) { 56 | //if(valFunctions.checkInputDataNULL(req,res)) return false; 57 | //if(valFunctions.checkInputDataQuality(req,res)) return false; 58 | //if(valFunctions.checkUserAuthRole(req,res)) return false; 59 | var dbFunctions = require('./models/connector'); 60 | var jwtData = valFunctions.checkJWTToken(req,res); 61 | if(jwtData.error) { 62 | res.statusCode = 200; 63 | return res.send(jwtData); 64 | } 65 | dbFunctions.getUser(jwtData,req,res); 66 | }); 67 | app.post('/setuser', urlencodedParser, function (req, res) { 68 | var dbFunctions = require('./models/connector'); 69 | var jwtData = valFunctions.checkJWTToken(req,res); 70 | if(jwtData.error) { 71 | res.statusCode = 200; 72 | return res.send(jwtData); 73 | } 74 | dbFunctions.setUser(jwtData,req,res); 75 | }); 76 | app.post('/setdata', urlencodedParser, function (req, res) { 77 | var dbFunctions = require('./models/connector'); 78 | var jwtData = valFunctions.checkJWTToken(req,res); 79 | if(jwtData.error) { 80 | res.statusCode = 200; 81 | return res.send(jwtData); 82 | } 83 | dbFunctions.setData(req,res); 84 | }); 85 | app.post('/getdata', urlencodedParser, function (req, res) { 86 | var dbFunctions = require('./models/connector'); 87 | var jwtData = valFunctions.checkJWTToken(req,res); 88 | if(jwtData.error) { 89 | res.statusCode = 200; 90 | return res.send(jwtData); 91 | } 92 | dbFunctions.getData(req,res); 93 | }); 94 | app.post('/setlocation', urlencodedParser, function (req, res) { 95 | if(valFunctions.checkInputDataNULL(req,res)) return false; 96 | // if(valFunctions.checkInputDataQuality(req,res)) return false; 97 | //if(valFunctions.checkUserAuthRole(req,res)) return false; 98 | var dbFunctions = require('./models/connector'); 99 | var userEmail = valFunctions.checkJWTToken(req,res); 100 | if(!userEmail) return false; 101 | dbFunctions.setLocation(userEmail,req,res); 102 | }); 103 | //app.get('/getlocation', urlencodedParser, function (req, res) { 104 | // //if(valFunctions.checkInputDataNULL(req,res)) return false; 105 | // //if(valFunctions.checkInputDataQuality(req,res)) return false; 106 | // //if(valFunctions.checkUserAuthRole(req,res)) return false; 107 | // var dbFunctions = require('./models/connector'); 108 | // var userEmail = valFunctions.checkJWTToken(req,res); 109 | // if(!userEmail) return false; 110 | // dbFunctions.getLocation(userEmail,res); 111 | //}); 112 | app.use('/', (req, res) => res.send("Welcome GPS Mobile Tracker App User !")); 113 | app.listen(process.env.PORT, () => console.log('Elish Enterprise Server is ready on localhost:' + process.env.PORT)); -------------------------------------------------------------------------------- /nodejs_crm_server/models/connector.js: -------------------------------------------------------------------------------- 1 | const pool = require('./dbconnection'); 2 | var password = require('password-hash-and-salt'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | var enc_pswd = []; 6 | var jwttoken = []; 7 | var resultsNotFound = { 8 | "num_rows": 0, 9 | "error": true, 10 | "message": "Operation Failed.", 11 | "data": [] 12 | }; 13 | var resultsFound = { 14 | "num_rows": 1, 15 | "error": false, 16 | "message": "Operation Successful.", 17 | "data": [] 18 | }; 19 | 20 | module.exports = { 21 | getUser: function (userEmail, req, res) { 22 | pool.getConnection(function (err, connection) { 23 | if (err) throw err; // not connected! 24 | var sql = 'SELECT * FROM `tmp_user` WHERE `userid` = ?'; 25 | // Use the connection 26 | connection.query(sql, userEmail, function (error, results, fields) { 27 | if (error) { 28 | resultsNotFound["message"] = "Something went wrong with Server."; 29 | resultsNotFound["data"] = []; 30 | return res.send(resultsNotFound); 31 | } 32 | if (results == "") { 33 | resultsNotFound["message"] = "User Id and Password didn't match."; 34 | resultsNotFound["data"] = []; 35 | return res.send(resultsNotFound); 36 | } 37 | resultsFound["data"] = results; 38 | res.send(resultsFound); 39 | // When done with the connection, release it. 40 | connection.release(); // Handle error after the release. 41 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 42 | }); 43 | }); 44 | }, 45 | 46 | createUser: function (req, res) { 47 | pool.getConnection(function (err, connection) { 48 | if (err) throw err; // not connected! 49 | password(req.body.enc_password).hash(function (error, hash) { 50 | if (error) 51 | throw new Error('Something went wrong!'); 52 | enc_pswd.hash = hash; 53 | }); 54 | var sql = 'INSERT INTO tmp_user SET ?'; 55 | // jwttoken is fake fieldname for password, which will store encrypted password 56 | var values = { 57 | 'userid': req.body.email, 58 | 'name': req.body.name, 59 | 'role': 'Guest', 60 | 'jwttoken': enc_pswd.hash, 61 | 'createdAt': new Date(), 62 | 'updatedAt': new Date() 63 | } 64 | // Use the connection 65 | connection.query(sql, values, function (error, results, fields) { 66 | if (error) { 67 | resultsNotFound["message"] = "emailID already exists."; 68 | return res.send(resultsNotFound); 69 | } else return res.send(resultsFound); 70 | // When done with the connection, release it. 71 | connection.release(); // Handle error after the release. 72 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 73 | }); 74 | }); 75 | }, 76 | loginUser: function (req, res) { 77 | pool.getConnection(function (err, connection) { 78 | if (err) throw err; // not connected! 79 | var sql = 'SELECT * FROM `tmp_user` WHERE `userid` = ?'; 80 | // Use the connection 81 | connection.query(sql, req.body.email, function (error, results, fields) { 82 | if (error) { 83 | resultsNotFound["message"] = "Something went wrong with Server."; 84 | return res.send(resultsNotFound); 85 | } 86 | if (results == "") { 87 | resultsNotFound["message"] = "User Id not found."; 88 | return res.send(resultsNotFound); 89 | } 90 | // Verifying a hash 91 | password(req.body.enc_password).verifyAgainst(results[0].jwttoken, function (error, verified) { 92 | if (error) { 93 | throw new Error('Something went wrong!'); 94 | resultsNotFound["message"] = "Something went wrong!"; 95 | return res.send(resultsNotFound); 96 | } 97 | if (!verified) { 98 | resultsNotFound["message"] = "Incorrect Password."; 99 | return res.send(resultsNotFound); 100 | } else { 101 | resultsFound["data"] = results; 102 | resultsFound["data"][0]["jwttoken"] = jwt.sign({ 103 | email: req.body.email 104 | }, 105 | process.env.JWT_SECRET, { 106 | expiresIn: '30d' 107 | } 108 | ); 109 | res.send(resultsFound); 110 | } 111 | }); 112 | // When done with the connection, release it. 113 | connection.release(); // Handle error after the release. 114 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 115 | }); 116 | }); 117 | }, 118 | setUser: function (userEmail, req, res) { 119 | pool.getConnection(function (err, connection) { 120 | if (err) throw err; // not connected! 121 | password(req.body.jwttoken).hash(function (error, hash) { 122 | if (error) 123 | throw new Error('Something went wrong!'); 124 | jwttoken.hash = hash; 125 | }); 126 | var sql = 'UPDATE tmp_user SET ? WHERE `userid` = ?'; 127 | var values = { 128 | 'userid': req.body.userid, 129 | 'name': req.body.name, 130 | 'role': 'Guest', 131 | 'jwttoken': jwttoken.hash, 132 | 'updatedAt': new Date() 133 | } 134 | // Use the connection 135 | connection.query(sql, [values, userEmail], function (error, results, fields) { 136 | if (error) { 137 | return res.send(resultsNotFound); 138 | } else return res.send(resultsFound); 139 | // When done with the connection, release it. 140 | connection.release(); // Handle error after the release. 141 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 142 | }); 143 | }); 144 | }, 145 | setData: function (req, res) { 146 | pool.getConnection(function (err, connection) { 147 | var sql = ""; 148 | var values = {}; 149 | if (err) throw err; // not connected! 150 | // check if req data is for which table ie table_name 151 | if (req.body.table_name == "addressbook") { 152 | if (req.body.addressid !== undefined) { 153 | sql = 'UPDATE tmp_addressbook SET '; 154 | sql = sql + "first_name = '" + req.body.first_name +"'"; 155 | sql = sql + ", middle_name = '" + req.body.middle_name +"'"; 156 | sql = sql + ", last_name = '" + req.body.last_name +"'"; 157 | sql = sql + ", address = '" + req.body.address +"'"; 158 | sql = sql + ", city = '" + req.body.city +"'"; 159 | sql = sql + ", country = '" + req.body.country +"'"; 160 | sql = sql + ", zip_code = '" + req.body.zip_code +"'"; 161 | sql = sql + ", emailid1 = '" + req.body.emailid1 +"'"; 162 | sql = sql + ", emailid2 = '" + req.body.emailid2 +"'"; 163 | sql = sql + ", phone1 = '" + req.body.phone1 +"'"; 164 | sql = sql + ", phone2 = '" + req.body.phone2 +"'"; 165 | sql = sql + ", updatedAt = '" + new Date() +"'"; 166 | sql = sql + ", deleted = 'N'"; 167 | sql = sql + " WHERE addressid = " + req.body.addressid; 168 | value = { 169 | "first_name": req.body.first_name, 170 | "middle_name": req.body.middle_name, 171 | "last_name": req.body.last_name, 172 | "address": req.body.address, 173 | "city": req.body.city, 174 | "country": req.body.country, 175 | "zip_code": req.body.zip_code, 176 | "emailid1": req.body.emailid1, 177 | "emailid2": req.body.emailid2, 178 | "phone1": req.body.phone1, 179 | "phone2": req.body.phone2, 180 | 'updatedAt': new Date(), 181 | 'deleted': "N" 182 | }; 183 | } else { 184 | sql = 'INSERT INTO tmp_addressbook SET ?'; 185 | values = { 186 | "first_name": req.body.first_name, 187 | "middle_name": req.body.middle_name, 188 | "last_name": req.body.last_name, 189 | "address": req.body.address, 190 | "city": req.body.city, 191 | "country": req.body.country, 192 | "zip_code": req.body.zip_code, 193 | "emailid1": req.body.emailid1, 194 | "emailid2": req.body.emailid2, 195 | "phone1": req.body.phone1, 196 | "phone2": req.body.phone2, 197 | 'createdAt': new Date(), 198 | 'updatedAt': new Date(), 199 | 'deleted': 'N' 200 | } 201 | } 202 | } 203 | // Use the connection 204 | connection.query(sql, values, function (error, results, fields) { 205 | if (error) { 206 | resultsNotFound["message"] = "Data is NOT updated."; 207 | return res.send(resultsNotFound); 208 | } else return res.send(resultsFound); 209 | 210 | // When done with the connection, release it. 211 | connection.release(); // Handle error after the release. 212 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 213 | }); 214 | }); 215 | }, 216 | getData: function (req, res) { 217 | var sql = ""; 218 | var values; 219 | pool.getConnection(function (err, connection) { 220 | if (err) throw err; // not connected! 221 | if (req.body._id !== "") { 222 | sql = 'SELECT * FROM `tmp_addressbook` WHERE deleted = \'N\' AND `addressid` LIKE ?'; 223 | values = req.body._id; 224 | } else { 225 | sql = 'SELECT * FROM `tmp_addressbook` WHERE deleted = \'N\' AND `addressid` LIKE ?'; 226 | values = "%"; 227 | } 228 | if (req.body.srchTxt !== "") { 229 | sql = 'SELECT * FROM `tmp_addressbook` WHERE deleted = \'N\' AND `first_name` LIKE \''+req.body.srchTxt +'\' OR `last_name` LIKE \''+req.body.srchTxt +'\' OR `phone1` LIKE \''+req.body.srchTxt +'\''; 230 | values = req.body.srchTxt; 231 | } 232 | console.log(sql) 233 | // Use the connection 234 | connection.query(sql, values, function (error, results, fields) { 235 | if (error) { 236 | resultsNotFound["message"] = "Something went wrong with Server."; 237 | resultsNotFound["data"] = []; 238 | return res.send(resultsNotFound); 239 | } 240 | if (results == "") { 241 | resultsNotFound["message"] = "No matching data is found."; 242 | resultsNotFound["data"] = []; 243 | return res.send(resultsNotFound); 244 | } 245 | resultsFound["data"] = results; 246 | res.send(resultsFound); 247 | // When done with the connection, release it. 248 | connection.release(); // Handle error after the release. 249 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 250 | }); 251 | }); 252 | }, 253 | setLocation: function (userEmail, req, res) { 254 | pool.getConnection(function (err, connection) { 255 | if (err) throw err; // not connected! 256 | 257 | var sql = 'INSERT INTO usergps SET ?'; 258 | var values = { 259 | 'lat': req.body.lat, 260 | 'long': req.body.long, 261 | 'email': userEmail, 262 | 'createdAt': new Date() 263 | } 264 | // Use the connection 265 | connection.query(sql, values, function (error, results, fields) { 266 | if (error) { 267 | resultsNotFound["message"] = "Data is NOT updated."; 268 | return res.send(resultsNotFound); 269 | } else return res.send(resultsFound); 270 | 271 | // When done with the connection, release it. 272 | connection.release(); // Handle error after the release. 273 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 274 | }); 275 | }); 276 | }, 277 | getLocation: function (input, res) { 278 | pool.getConnection(function (err, connection) { 279 | if (err) throw err; // not connected! 280 | 281 | var sql = 'SELECT * FROM `usergps` WHERE `email` = ?'; 282 | var values = [input] 283 | // Use the connection 284 | connection.query(sql, values, function (error, results, fields) { 285 | if (error) { 286 | resultsNotFound["message"] = "Something went wrong with Server."; 287 | return res.send(resultsNotFound); 288 | } 289 | if (results == "") { 290 | resultsNotFound["message"] = "User Id not found."; 291 | return res.send(resultsNotFound); 292 | } 293 | resultsFound["data"] = results; 294 | res.send(resultsFound); 295 | // When done with the connection, release it. 296 | connection.release(); // Handle error after the release. 297 | if (error) throw error; // Don't use the connection here, it has been returned to the pool. 298 | }); 299 | }); 300 | }, 301 | }; -------------------------------------------------------------------------------- /nodejs_crm_server/models/dbconnection.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | var pool = mysql.createPool({ 3 | connectionLimit : 10, 4 | host : process.env.DB_HOST, 5 | user : process.env.DB_USERNAME, 6 | password : process.env.DB_PASSWORD, 7 | database : process.env.DB_NAME, 8 | }); 9 | pool.getConnection(function(err, connection) { 10 | if (err) throw err; // not connected! 11 | }); 12 | 13 | module.exports = pool; -------------------------------------------------------------------------------- /nodejs_crm_server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "FLUTTER_CRM_APP", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index", 8 | "test": "test" 9 | }, 10 | "author": "Amit Shukla", 11 | "license": "MIT", 12 | "dependencies": { 13 | "body-parser": "^1.18.3", 14 | "cors": "^2.8.4", 15 | "dotenv": "^6.1.0", 16 | "express": "^4.16.4", 17 | "jsonwebtoken": "^8.3.0", 18 | "mysql": "^2.16.0", 19 | "nodemon": "^1.18.4", 20 | "password-hash-and-salt": "^0.1.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nodejs_crm_server/validators/validate.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | var resultsNotFound = { 3 | "num_rows": 0, 4 | "error": true, 5 | "message": "Operation Failed.", 6 | "data": "" 7 | }; 8 | 9 | module.exports = { 10 | checkInputDataNULL: function(req, res) { 11 | // VALIDATE sql INJECTIONS HERE 12 | resultsNotFound["message"] = "There is no data submitted from Client."; 13 | if (!req.body) return res.send(resultsNotFound); 14 | }, 15 | checkInputDataQuality: function(req, res) { 16 | // VALIDATE sql INJECTIONS HERE 17 | resultsNotFound["message"] = "There is no data submitted from Client."; 18 | if (req.body.email == "") return res.send(resultsNotFound); 19 | }, 20 | checkJWTToken: function(req, res) { 21 | resultsNotFound["message"] = "Your token in not valid, please logoff and login again."; 22 | resultsNotFound["data"] = []; 23 | const token = req.headers.token; 24 | if (!token) return res.send(resultsNotFound); 25 | var decoded = jwt.verify(token, process.env.JWT_SECRET, function(err,result){ 26 | if(err) return resultsNotFound; 27 | return result.email; 28 | }); 29 | return decoded; 30 | } 31 | }; --------------------------------------------------------------------------------