Compare commits

...

No commits in common. "master" and "main" have entirely different histories.
master ... main

213 changed files with 63 additions and 62529 deletions

98
.gitignore vendored Executable file → Normal file
View File

@ -1,68 +1,50 @@
# Miscellaneous
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
.env
migrate_working_dir/
# IntelliJ related
*.iml
*.lock
*.packages
*.ipr
*.iws
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
.vscode/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Unit test reports
TEST*.xml
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Generated by MacOS
.DS_Store
# Android specific files
*.android/
*.android.iml
*.apk
*.ap_
# Generated by Windows
Thumbs.db
# iOS specific files
*.ios/
*.ios.iml
*.ipa
*.xcworkspace
*.xcodeproj/
*.xcuserdatad/
# Applications
*.app
*.exe
*.war
# macOS specific files
.idea/workspace.xml
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
# Dependency lock files
.pubspec.lock
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -1,45 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "7482962148e8d758338d8a28f589f317e1e42ba4"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
- platform: android
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
- platform: ios
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
- platform: linux
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
- platform: macos
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
- platform: web
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
- platform: windows
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

33
README.md Executable file → Normal file
View File

@ -1,16 +1,29 @@
# nhancepolicy
# README #
A new Flutter project.
This README would normally document whatever steps are necessary to get your application up and running.
## Getting Started
### What is this repository for? ###
This project is a starting point for a Flutter application.
* Quick summary
* Version
* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
A few resources to get you started if this is your first Flutter project:
### How do I get set up? ###
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
* Summary of set up
* Configuration
* Dependencies
* Database configuration
* How to run tests
* Deployment instructions
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
### Contribution guidelines ###
* Writing tests
* Code review
* Other guidelines
### Who do I talk to? ###
* Repo owner or admin
* Other community or team contact

View File

@ -1,28 +0,0 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
android/.gitignore vendored
View File

@ -1,13 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -1,67 +0,0 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "com.example.nhance_app_pwa"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.nhance_app_pwa"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {}

View File

@ -1,29 +0,0 @@
{
"project_info": {
"project_number": "508866094093",
"project_id": "nhance-app-4fedb",
"storage_bucket": "nhance-app-4fedb.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:508866094093:android:9969e766daa5b770366144",
"android_client_info": {
"package_name": "com.example.nhance_app_pwa"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAZdKJco4fe0e1HP1A249BVZzA3WMYb_3g"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,49 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.nhance_app_pwa">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:label="nhancepolicy"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -1,5 +0,0 @@
package com.example.nhance_app_pwa
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,18 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true

View File

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@ -1,26 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

View File

@ -1,4 +0,0 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
- shared_preferences: true

34
ios/.gitignore vendored
View File

@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

@ -1,2 +0,0 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1,2 +0,0 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@ -1,43 +0,0 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@ -1,13 +0,0 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View File

@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Nhancepolicy</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>nhancepolicy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -1,12 +0,0 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class BranchCard extends StatelessWidget {
final String clientName;
final String branchName;
final bool isSelected;
final VoidCallback onTap;
const BranchCard({
Key? key,
required this.clientName,
required this.branchName,
required this.isSelected,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Tooltip(
message: clientName, // 🖱 hover shows full name
waitDuration: const Duration(milliseconds: 400),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color:
isSelected ? const Color(0xFF00999E) : const Color(0xFFF0F9F9),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFF00999E),
width: 1.5,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
clientName,
maxLines: 1, // SINGLE LINE
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w500,
color: isSelected ? Colors.white : const Color(0xFF00999E),
),
),
const SizedBox(height: 4),
Text(
branchName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 11,
color: isSelected ? Colors.white70 : Colors.black87,
),
),
],
),
),
),
);
}
}

View File

@ -1,220 +0,0 @@
import 'package:flutter/material.dart';
import '../customAppBar/base_layout.dart';
import '../customAppBar/toastHelper.dart';
import '../service/api_service.dart';
import '../service/secure_pop_scope.dart';
import '../service/token_storage_service.dart';
import 'branch_card_widget.dart';
import 'dart:html' as html;
class BranchSelectionPage extends StatefulWidget {
const BranchSelectionPage({Key? key}) : super(key: key);
@override
State<BranchSelectionPage> createState() => _BranchSelectionPageState();
}
class _BranchSelectionPageState extends State<BranchSelectionPage> {
List<Map<String, dynamic>> branches = [];
int? selectedIndex;
final tokenStorage = TokenStorageService();
late ApiService apiService;
@override
void initState() {
super.initState();
apiService = ApiService(context);
html.window.onPopState.listen((event) async {
final shouldLogout = await _showLogoutDialog();
if (shouldLogout) {
await apiService.logout();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
} else {
// Push state back to prevent browser navigation
html.window.history.pushState(null, '', html.window.location.href);
}
});
_loadBranches();
}
void _loadBranches() {
setState(() {
branches = tokenStorage.getCombinedBranches();
});
}
void _selectBranch(int index) {
setState(() => selectedIndex = index);
}
Future<void> _handleNext() async {
if (selectedIndex == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please select a branch')),
);
return;
}
final selectedBranch = branches[selectedIndex!];
await tokenStorage.saveSelectedBranch(selectedBranch);
final decodedToken = tokenStorage.getDecodedToken();
final token = tokenStorage.getCurrentToken();
if (decodedToken == null || token == null) {
ToastHelper.showErrorToast(context, 'Invalid token data');
return;
}
// 🔐 Save decoded values securely
await tokenStorage.saveDecodedSessionData(decodedToken, token);
// ToastHelper.showSuccessToast(context, 'Successfully Login');
if (!mounted) return;
Navigator.pushReplacementNamed(context, 'policies');
}
Future<bool> _showLogoutDialog() async {
return await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text("Confirm Logout"),
content: Text("Do you want to logout?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text("Cancel"),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("Logout"),
),
],
),
) ??
false;
}
@override
Widget build(BuildContext context) {
return BaseLayout(
child: SecurePopScope(
child: _buildContent(context),
),
);
}
Widget _buildContent(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F7F7),
body: LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
/// 🔹 RESPONSIVE BREAKPOINTS
int crossAxisCount;
if (width < 600) {
crossAxisCount = 1;
} else if (width < 900) {
crossAxisCount = 2;
} else {
crossAxisCount = 3;
}
return SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: width * 0.08,
vertical: 30,
),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Select Client',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
/// 🔹 GRID
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: branches.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisExtent: 90, // 🔥 FIXED HEIGHT
mainAxisSpacing: 15,
crossAxisSpacing: 15,
),
itemBuilder: (context, index) {
final branch = branches[index];
return BranchCard(
clientName: branch['client_name'] ??
'Unknown Client kjbgjsk jsdhbjbf sdjfjbds fdjsgjdbs gdsjbgjds',
branchName: branch['branch_name'] ?? 'Unknown Branch',
isSelected: selectedIndex == index,
onTap: () => _selectBranch(index),
);
},
),
const SizedBox(height: 30),
/// 🔹 NEXT BUTTON
Center(
child: SizedBox(
width: 140,
height: 46,
child: ElevatedButton(
onPressed: _handleNext,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF6B35),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 0,
),
child: const Text(
'Next',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
),
);
},
),
);
}
}

View File

@ -1,77 +0,0 @@
// lib/environment.dart
enum Flavor { dev, uat, prod, prod1 }
class Environment {
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1;
static String get apiUrl {
switch (flavor) {
case Flavor.dev:
return "https://venbait.in/enrollment/employeeRest/";
case Flavor.uat:
return "https://appstage.nhanceindia.in/apex/employeeRest/";
case Flavor.prod:
return "https://app.nhanceindia.in/enrolment/employeeRest/";
case Flavor.prod1:
return "https://app.nhanceindia.in/enrolment/employeeRest/";
}
}
static String get apiUrlPost {
switch (flavor) {
case Flavor.dev:
return "https://venbait.in/nhance/dev/employeeRest/";
case Flavor.uat:
return "https://appstage.nhanceindia.in/nexus/employeeRest/";
case Flavor.prod:
return "https://app.nhanceindia.in/zenith/employeeRest/";
case Flavor.prod1:
return "https://app.nhanceindia.in/zenith/employeeRest/";
}
}
static String get baseHref {
switch (flavor) {
case Flavor.dev:
return "/nhance/hr/dev/";
case Flavor.uat:
return "/";
case Flavor.prod:
return "/hr/";
case Flavor.prod1:
return "/";
}
}
static String getPageTitle(String? routeName) {
String base = "Nhance HR";
if (isProd) base = "Nhance HR"; // You can differentiate names if needed
switch (routeName) {
case 'claimsOverviewDashboard':
return "$base - Dashboard";
case 'policies':
return "$base - Policies";
case 'pendingDependentApproval':
return "$base - Approvals";
case 'CdPoliciesList':
return "$base - CD Details";
case 'ClaimsPolicies':
return "$base - Claims";
case 'hrLogin':
return "$base - Login";
case 'cdTransactionDetails':
return "$base - Transaction Details";
case 'hrPolicyDetails':
return "$base - Member Details";
case 'preFileUpload':
return "$base - Member Upload";
case 'postFileUpload':
return "$base - Member Upload";
default:
return base;
}
}
}

View File

@ -1,7 +0,0 @@
import '../main.dart';
import 'environment.dart';
Future<void> main() async {
Environment.flavor = Flavor.dev;
await startApp();
}

View File

@ -1,7 +0,0 @@
import '../main.dart';
import 'environment.dart';
Future<void> main() async {
Environment.flavor = Flavor.prod;
await startApp();
}

View File

@ -1,7 +0,0 @@
import '../main.dart';
import 'environment.dart';
Future<void> main() async {
Environment.flavor = Flavor.prod1;
await startApp();
}

View File

@ -1,7 +0,0 @@
import '../main.dart';
import 'environment.dart';
Future<void> main() async {
Environment.flavor = Flavor.uat;
await startApp();
}

View File

@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import 'top_app_bar.dart';
import 'side_bar.dart';
import '../config/environment.dart';
class BaseLayout extends StatelessWidget {
final Widget child;
const BaseLayout({super.key, required this.child});
@override
Widget build(BuildContext context) {
// 1. Capture the current route name from the navigation context
final String? routeName = ModalRoute.of(context)?.settings.name;
// 2. Use the Title widget to communicate with the browser's tab
return Title(
title: Environment.getPageTitle(routeName),
color: const Color(0xFF00999E), // Required, usually matches your theme
child: Scaffold(
appBar: const NhanceTopBar(),
body: Row(
children: [
const NhanceSideBar(),
Expanded(
child: Container(
color: const Color(0xFFF5F7F7),
padding: const EdgeInsets.all(16),
child: child,
),
),
],
),
));
}
}

View File

@ -1,204 +0,0 @@
import 'package:flutter/material.dart';
import 'package:adaptive_navbar/adaptive_navbar.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
import 'package:nhancepolicy/responsive.dart';
import 'package:nhancepolicy/responsive.dart';
import '../service/api_service.dart';
import '../service/token_storage_service.dart';
import 'package:nhancepolicy/logger.dart';
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
@override
_CustomAppBarState createState() => _CustomAppBarState();
@override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
class _CustomAppBarState extends State<CustomAppBar> {
bool showBackToHR = false;
late ApiService apiService;
final tokenService = TokenStorageService();
// 🔐 Secure storage instance
static const FlutterSecureStorage _secureStorage = FlutterSecureStorage();
// bool isLoading = true; // Add a loading state
// bool hideInactiveStatus = true;
@override
void initState() {
super.initState();
apiService = ApiService(context);
// _checkTokens();
}
Future<void> logout(BuildContext context) async {
// final prefs = await SharedPreferences.getInstance();
// final String? hrtoken = prefs.getString('_postToken');
// final String? token = prefs.getString('enrollToken');
// prefs.clear();
logDebug('LocalStorage Cleared');
apiService.logout();
// Navigator.pushNamed(context, 'hrLogin');
}
@override
Widget build(BuildContext context) {
final sw = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: Color(0xFFFFFCE5), // Set background color for AppBar
appBar: PreferredSize(
preferredSize: widget.preferredSize,
child: SafeArea(
child: Container(
// padding: Responsive.isDesktop(context)
// ? EdgeInsets.symmetric(horizontal: 46.0)
// : EdgeInsets.symmetric(horizontal: 0),
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
(Responsive.isDesktop(context) ? 0.03 : 0.02),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Logo Column
// Expanded(
// // flex: Responsive.isDesktop(context) ? 1 : 9,
// child:
Row(
mainAxisAlignment: Responsive.isDesktop(context)
? MainAxisAlignment.spaceEvenly
: MainAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
width: 150,
height: 150,
child: Image.asset(
'assets/nhance_client_logo.png',
fit: BoxFit.contain,
),
),
],
),
// ),
// AdaptiveNavBar Column
// Expanded(
// // flex: Responsive.isDesktop(context) ? 1 : 9,
// child:
Row(
mainAxisSize: MainAxisSize.min,
children: [
NavBarItem(
text: "Change Branch",
onTap: () async {
await tokenService.clearBranchSession();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'branchSelection',
(route) => false,
);
},
),
const SizedBox(width: 8),
NavBarItem(
text: "Logout",
onTap: () => logout(context),
),
],
),
// )
// Expanded(
// flex: Responsive.isDesktop(context) ? 9 : 3,
// child: AdaptiveNavBar(
// screenWidth: sw,
// backgroundColor: Color(0xFFFFFCE5),
// leading:
// Container(), // Set an empty container as we have the logo separately
// title: Text(''),
// navBarItems: [
// // if (Responsive.isDesktop(context))
// // if (hideInactiveStatus)
// // NavBarItem(
// // text: "Inactive Policy",
// // onTap: () {
// // Navigator.pushNamed(context, 'oldPolicy');
// // },
// // ),
// // if (showBackToHR)
// NavBarItem(
// text: "Change Branch",
// onTap: () async {
// final prefs = await SharedPreferences.getInstance();
// await prefs.remove('selected_branch');
// await prefs.remove('decoded_token');
// await prefs.remove('clientLogo');
// await prefs.remove('clientName');
// await prefs.remove('empAllowed_modules');
// await prefs.remove('empClientBranchId');
// await prefs.remove('empClientId');
// await prefs.remove('empEmail');
// await prefs.remove('empHrId');
// await prefs.remove('empPrimaryId');
// await prefs.remove('enrollmentAllowed_modules');
// await prefs.remove('enrollmentClient_id');
// await prefs.remove('enrollmentEmpClientBranchId');
// await prefs.remove('enrollmentEmpPrimaryId');
// await prefs.remove('enrollmentHrId');
// await prefs.remove('token');
// Navigator.pushNamed(context, 'branchSelection');
// },
// ),
// NavBarItem(
// text: "Logout",
// onTap: () async {
// logout(context);
// },
// ),
// ],
// ),
// ),
],
),
),
),
),
);
}
}
class NavBarItem extends StatelessWidget {
final String text;
final VoidCallback? onTap;
const NavBarItem({
Key? key,
required this.text,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
borderRadius: BorderRadius.circular(6),
onTap: onTap,
hoverColor: const Color(0xFFF4F3E7), // subtle hover background
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Text(
text,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
),
);
}
}

View File

@ -1,88 +0,0 @@
import 'package:flutter/material.dart';
import 'package:adaptive_navbar/adaptive_navbar.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../responsive.dart';
class CustomAppBarChatbot extends StatelessWidget
implements PreferredSizeWidget {
@override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
Future<void> logout(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
if (token != null && token.isNotEmpty) {
await prefs.clear();
Navigator.pushNamed(context, 'phone');
}
}
@override
Widget build(BuildContext context) {
final sw = MediaQuery.of(context).size.width;
final isPoliciesPage = Uri.base.fragment == 'policies';
return ClipRRect(
borderRadius: Responsive.isDesktop(context) || isPoliciesPage
? BorderRadius.zero
: BorderRadius.only(
bottomLeft: Radius.circular(32.0),
bottomRight: Radius.circular(32.0),
),
child: AppBar(
backgroundColor: const Color(0xFFFFFBDE),
elevation: 0, // Set background color for AppBar
toolbarHeight: kToolbarHeight,
titleSpacing: 0.0,
automaticallyImplyLeading: false,
title: Padding(
padding: Responsive.isDesktop(context)
? EdgeInsets.symmetric(horizontal: 16.0)
: EdgeInsets.symmetric(horizontal: 10),
child: Row(
children: [
// Logo Column
Expanded(
flex: Responsive.isDesktop(context) ? 3 : 9,
child: Row(
mainAxisAlignment: Responsive.isDesktop(context)
? MainAxisAlignment.spaceEvenly
: MainAxisAlignment
.start, // Adjust the alignment as needed
children: [
Container(
margin: EdgeInsets.only(
top: 10, bottom: 10, left: 10, right: 10),
width: Responsive.isDesktop(context) ? 230 : 170,
height: Responsive.isDesktop(context) ? 230 : 170,
child: Image.asset(
'assets/nhance_client_logo.png',
fit: BoxFit.contain, // Adjust the fit as needed
),
),
],
),
),
// AdaptiveNavBar Column
Expanded(
flex: Responsive.isDesktop(context) ? 9 : 3,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.close_outlined, // Replace with your desired icon
size: 24.0, // Adjust the icon size
color: Colors.black, // Adjust the icon color
),
),
),
],
),
),
),
);
}
}

View File

@ -1,23 +0,0 @@
import 'package:flutter/material.dart';
class CustomFooter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.0),
color: Color(0xFFFFFBDE), // Background color of the footer
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Copyright @ ${DateTime.now().year} Nhance',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600], // Text color
),
),
],
),
);
}
}

View File

@ -1,162 +0,0 @@
import 'package:flutter/material.dart';
import 'package:adaptive_navbar/adaptive_navbar.dart';
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
import 'package:nhancepolicy/responsive.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:nhancepolicy/responsive.dart';
import 'package:nhancepolicy/logger.dart';
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
@override
_CustomAppBarState createState() => _CustomAppBarState();
@override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
class _CustomAppBarState extends State<CustomAppBar> {
bool showBackToHR = false;
// bool isLoading = true; // Add a loading state
// bool hideInactiveStatus = true;
@override
void initState() {
super.initState();
_checkTokens();
}
Future<void> _checkTokens() async {
final prefs = await SharedPreferences.getInstance();
// final String? hrtoken = prefs.getString('hrtoken');
final String? hrtoken = prefs.getString('enrollToken');
final String? token = prefs.getString('token');
// showBackToHR = prefs.getBool('showBackToHR')!;
setState(() {
showBackToHR = prefs.getBool('showBackToHR')!;
// showBackToHR = (hrtoken != null && hrtoken.isNotEmpty) &&
// (token != null && token.isNotEmpty);
// logDebug((hrtoken != null && hrtoken.isNotEmpty));
// logDebug((token != null && token.isNotEmpty));
// logDebug('showBackToHR $showBackToHR');
// hideInactiveStatus = token != null && token.isNotEmpty;
});
}
Future<void> hrLogout(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
final String? hrtoken = prefs.getString('hrtoken');
if (hrtoken != null && hrtoken.isNotEmpty) {
prefs.setBool('showBackToHR', false);
await prefs.remove('hrSelectArgumentsData');
prefs.remove('token');
Navigator.pushNamed(context, 'policies');
} else {
await prefs.clear();
Navigator.pushNamed(context, 'phone');
}
}
Future<void> logout(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
final String? hrtoken = prefs.getString('hrtoken');
final String? token = prefs.getString('token');
if (token != null && token.isNotEmpty) {
if (hrtoken != null && hrtoken.isNotEmpty) {
await prefs.clear();
Navigator.pushNamed(context, 'hrLogin');
} else {
await prefs.clear();
Navigator.pushNamed(context, 'phone');
}
} else if (hrtoken != null && hrtoken.isNotEmpty) {
await prefs.clear();
Navigator.pushNamed(context, 'hrLogin');
}
}
@override
Widget build(BuildContext context) {
final sw = MediaQuery.of(context).size.width;
// if (isLoading) {
// // Show a loading indicator or a placeholder AppBar while waiting
// return AppBar(
// title: Text('Loading...'),
// );
// }
return Scaffold(
backgroundColor: Color(0xFFFFFCE5), // Set background color for AppBar
appBar: PreferredSize(
preferredSize: widget.preferredSize,
child: SafeArea(
child: Container(
padding: Responsive.isDesktop(context)
? EdgeInsets.symmetric(horizontal: 16.0)
: EdgeInsets.symmetric(horizontal: 0),
child: Row(
children: [
// Logo Column
Expanded(
flex: Responsive.isDesktop(context) ? 3 : 9,
child: Row(
mainAxisAlignment: Responsive.isDesktop(context)
? MainAxisAlignment.spaceEvenly
: MainAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
width: 150,
height: 150,
child: Image.asset(
'assets/nhance_client_logo.png',
fit: BoxFit.contain,
),
),
],
),
),
// AdaptiveNavBar Column
Expanded(
flex: Responsive.isDesktop(context) ? 9 : 3,
child: AdaptiveNavBar(
screenWidth: sw,
backgroundColor: Color(0xFFFFFCE5),
leading:
Container(), // Set an empty container as we have the logo separately
title: Text(''),
navBarItems: [
// if (Responsive.isDesktop(context))
// if (hideInactiveStatus)
// NavBarItem(
// text: "Inactive Policy",
// onTap: () {
// Navigator.pushNamed(context, 'oldPolicy');
// },
// ),
if (showBackToHR)
NavBarItem(
text: "Back To HR",
onTap: () {
hrLogout(context);
},
),
NavBarItem(
text: "Logout",
onTap: () async {
logout(context);
},
),
],
),
),
],
),
),
),
),
);
}
}

View File

@ -1,807 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:nhancepolicy/service/api_service.dart';
import 'package:nhancepolicy/service/svg_service.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:nhancepolicy/logger.dart';
class NhanceSideBar extends StatefulWidget {
const NhanceSideBar({super.key});
@override
State<NhanceSideBar> createState() => _NhanceSideBarState();
}
class _NhanceSideBarState extends State<NhanceSideBar> {
String? activeRoute;
late ApiService apiService;
OverlayEntry? _claimsOverlayEntry;
bool _isHoveringClaimsItem = false;
bool _isHoveringClaimsMenu = false;
DateTime? _claimsMenuSuppressUntil;
// bool isLoading = true; // Add a loading state
// bool hideInactiveStatus = true;
final tokenService = TokenStorageService();
// dynamic enrollmentModules = [];
// dynamic postModules = [];
List<Map<String, dynamic>> sideMenuItems = [];
List<int> postModules = [];
List<int> enrollmentModules = [];
List<String> claimsSubMenu = [];
@override
void initState() {
super.initState();
apiService = ApiService(context);
_buildSideMenu();
// _checkTokens();
}
Future<void> _buildSideMenu() async {
final enrollmentRaw =
await tokenService.readValue('enrollmentAllowed_modules'); // "[1]"
final postRaw =
await tokenService.readValue('empAllowed_modules'); // "[2,3,4]"
logDebug('enrollmentRaw $enrollmentRaw');
logDebug('postRaw $postRaw');
// Decode safely
enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty
? List<int>.from(jsonDecode(enrollmentRaw))
: [];
postModules = postRaw != null && postRaw.isNotEmpty
? List<int>.from(jsonDecode(postRaw))
: [];
final claimsSubMenuRaw = await tokenService.readValue('claims_sub_menu');
if (claimsSubMenuRaw != null && claimsSubMenuRaw.isNotEmpty) {
claimsSubMenu = List<dynamic>.from(jsonDecode(claimsSubMenuRaw))
.map((e) => e.toString())
.toList();
} else {
// Fallback: decoded JWT / selected branch may still have the list.
final decoded = tokenService.getDecodedToken();
final branch = tokenService.getSelectedBranch();
final fromDecoded = decoded?['claims_sub_menu'];
final fromBranch = branch?['claims_sub_menu'];
final source = fromBranch is List
? fromBranch
: (fromDecoded is List ? fromDecoded : const []);
claimsSubMenu = source.map((e) => e.toString()).toList();
}
logDebug('enrollmentModules $enrollmentModules');
logDebug('postModules $postModules');
logDebug('claimsSubMenu $claimsSubMenu');
final List<Map<String, dynamic>> items = [];
// POLICIES (1 OR 2)
if (enrollmentModules.contains(1) || postModules.contains(2)) {
items.add({
'route': 'policies',
'label': 'Policies',
'icon': 'policies',
});
items.add({
'route': 'pendingDependentApproval',
'label': 'Approvals',
'icon': 'pending_approve',
'useMaterialIcon': true,
});
}
// CD
if (postModules.contains(3)) {
items.add({
'route': 'CdPoliciesList',
'label': 'CD',
'icon': 'cd',
});
}
// CLAIMS only when module 4 and at least one sub-menu is allowed
if (postModules.contains(4) && _claimsSubMenuRoutes.isNotEmpty) {
items.add({
'route': 'ClaimsPolicies',
'label': 'Claims',
'icon': 'claims',
});
}
setState(() {
sideMenuItems = items;
});
}
/// Allowed Claims destinations based on claims_sub_menu.
List<({String label, String route})> get _claimsSubMenuRoutes {
final items = <({String label, String route})>[];
if (claimsSubMenu.contains('EB')) {
items.add((label: 'Employee Benefits', route: 'ClaimsPolicies'));
}
if (claimsSubMenu.contains('Non-EB')) {
items.add((label: 'Non-Employee Benefits', route: 'nonEBClaimsList'));
}
return items;
}
bool get _hasMultipleClaimsSubMenus => _claimsSubMenuRoutes.length > 1;
String? get _singleClaimsRoute {
final routes = _claimsSubMenuRoutes;
if (routes.length == 1) return routes.first.route;
return null;
}
Future<void> logout(BuildContext context) async {
// final prefs = await SharedPreferences.getInstance();
// final String? hrtoken = prefs.getString('_postToken');
// final String? token = prefs.getString('enrollToken');
// prefs.clear();
logDebug('LocalStorage Cleared');
apiService.logout();
// Navigator.pushNamed(context, 'hrLogin');
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Safely capture the current route name
final newRoute = ModalRoute.of(context)?.settings.name;
if (activeRoute != newRoute) {
setState(() {
activeRoute = newRoute;
});
_closeClaimsMenu();
// Re-verify the menu items if the route changes
_buildSideMenu();
}
}
void _navigate(String routeName) {
_closeClaimsMenu();
if (activeRoute == routeName) return;
// Navigation should happen first, didChangeDependencies will handle the state
Navigator.pushReplacementNamed(context, routeName);
}
void _showClaimsSubMenu(GlobalKey key) {
if (!_hasMultipleClaimsSubMenus) return;
if (_claimsMenuSuppressUntil != null &&
DateTime.now().isBefore(_claimsMenuSuppressUntil!)) {
return;
}
final targetContext = key.currentContext;
if (targetContext == null) return;
final box = targetContext.findRenderObject() as RenderBox?;
if (box == null) return;
final offset = box.localToGlobal(Offset.zero);
final subMenus = _claimsSubMenuRoutes;
_claimsOverlayEntry?.remove();
_claimsOverlayEntry = OverlayEntry(
builder: (context) => Positioned(
left: offset.dx + box.size.width + 6,
top: offset.dy,
child: MouseRegion(
onEnter: (_) {
_isHoveringClaimsMenu = true;
},
onExit: (_) {
_isHoveringClaimsMenu = false;
_scheduleCloseClaimsMenu();
},
child: Material(
color: Colors.transparent,
child: Container(
width: 200,
padding: const EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final item in subMenus)
_buildClaimsMenuItem(item.label, item.route),
],
),
),
),
),
),
);
Overlay.of(context).insert(_claimsOverlayEntry!);
}
Widget _buildClaimsMenuItem(String label, String route) {
return _ClaimsSubMenuItem(
label: label,
onTap: () {
_claimsMenuSuppressUntil =
DateTime.now().add(const Duration(milliseconds: 700));
_isHoveringClaimsItem = false;
_isHoveringClaimsMenu = false;
_closeClaimsMenu();
_navigate(route);
},
);
}
void _scheduleCloseClaimsMenu() {
Future.delayed(const Duration(milliseconds: 120), () {
if (!_isHoveringClaimsItem && !_isHoveringClaimsMenu) {
_closeClaimsMenu();
}
});
}
void _closeClaimsMenu() {
_claimsOverlayEntry?.remove();
_claimsOverlayEntry = null;
_isHoveringClaimsMenu = false;
}
Future<void> _openHelpDialog() async {
final isPostUser = postModules.isNotEmpty;
await showDialog<void>(
context: context,
builder: (dialogContext) => _HelpContactDialog(
isPostUser: isPostUser,
apiService: apiService,
tokenService: tokenService,
),
);
}
@override
Widget build(BuildContext context) {
return Container(
width: 70,
color: const Color(0xFF009E9E),
child: Column(
children: [
// const SizedBox(height: 12),
//
// _SideItem(
// // icon: Icons.policy,
// icon: SvgPicture.string(
// SvgService.getSvg('policies'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "Policies",
// isActive: activeRoute == 'oldPolicy', // or correct policy route
// onTap: () => _navigate('oldPolicy'),
// ),
//
// _SideItem(
// // icon: Icons.credit_card,
// icon: SvgPicture.string(
// SvgService.getSvg('cd'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "CD",
// isActive: activeRoute == 'cdTransactionDetails',
// onTap: () => _navigate('cdTransactionDetails'),
// ),
//
// _SideItem(
// // icon: Icons.assignment,
// icon: SvgPicture.string(
// SvgService.getSvg('claims'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "Claims",
// isActive: activeRoute == 'claims', // claims tab inside dashboard
// onTap: () => _navigate('claims'),
// ),
//
// const Spacer(),
//
// _SideItem(
// // icon: Icons.logout,
// icon: SvgPicture.string(
// SvgService.getSvg('logout'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "Logout",
// onTap: () {
// logout(context);
// },
// ),
if (postModules.isNotEmpty && postModules.contains(5))
_SideItem(
icon: const Icon(
Icons.space_dashboard_outlined,
color: Colors.white,
size: 30,
),
label: "Dashboard",
isActive: activeRoute == 'claimsOverviewDashboard',
onTap: () => _navigate('claimsOverviewDashboard'),
),
...sideMenuItems.map((item) {
final isClaims = item['route'] == 'ClaimsPolicies';
final isClaimsActive =
activeRoute == 'ClaimsPolicies' || activeRoute == 'nonEBClaimsList';
final itemKey = GlobalKey();
final showClaimsSubMenu = isClaims && _hasMultipleClaimsSubMenus;
final claimsDirectRoute =
isClaims ? (_singleClaimsRoute ?? 'ClaimsPolicies') : null;
final useMaterialIcon = item['useMaterialIcon'] == true;
return _SideItem(
key: itemKey,
icon: useMaterialIcon
? const Icon(
Icons.how_to_reg_outlined,
color: Colors.white,
size: 30,
)
: SvgPicture.string(
SvgService.getSvg(item['icon']),
width: 35,
height: 35,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
label: item['label'],
isActive: isClaims ? isClaimsActive : activeRoute == item['route'],
onTap: showClaimsSubMenu
? null
: () => _navigate(
claimsDirectRoute ?? item['route'] as String,
),
onHoverEnter: showClaimsSubMenu
? () {
_isHoveringClaimsItem = true;
_showClaimsSubMenu(itemKey);
}
: null,
onHoverExit: showClaimsSubMenu
? () {
_isHoveringClaimsItem = false;
_scheduleCloseClaimsMenu();
}
: null,
);
}),
// if (postModules.isNotEmpty && postModules.contains(5))
// _SideItem(
// // icon: Icons.dashboard,
// icon: SvgPicture.string(
// SvgService.getSvg('dashboard'),
// width: 35,
// height: 35,
// colorFilter: const ColorFilter.mode(
// Colors.white,
// BlendMode.srcIn,
// ),
// ),
// label: "Insights",
// isActive: activeRoute == 'hrDashboard',
// onTap: () => _navigate('hrDashboard'),
// ),
const Spacer(),
if (enrollmentModules.isNotEmpty || postModules.isNotEmpty)
_SideItem(
icon: const Icon(
Icons.help_outline,
color: Colors.white,
size: 30,
),
label: 'Help',
onTap: _openHelpDialog,
),
_SideItem(
icon: SvgPicture.string(
SvgService.getSvg('logout'),
width: 35,
height: 35,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
label: "Logout",
onTap: () => logout(context),
),
// const SizedBox(height: 10),
],
),
);
}
}
class _HelpContactDialog extends StatefulWidget {
final bool isPostUser;
final ApiService apiService;
final TokenStorageService tokenService;
const _HelpContactDialog({
required this.isPostUser,
required this.apiService,
required this.tokenService,
});
@override
State<_HelpContactDialog> createState() => _HelpContactDialogState();
}
class _HelpContactDialogState extends State<_HelpContactDialog> {
bool _isLoading = false;
List<Map<String, dynamic>> _level1Contacts = [];
List<Map<String, dynamic>> _level2Contacts = [];
String? _errorMessage;
@override
void initState() {
super.initState();
if (widget.isPostUser) {
_loadClientRM();
}
}
Future<void> _loadClientRM() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final clientId = await widget.tokenService.readValue('empClientId');
final token = widget.tokenService.getCurrentToken();
if (clientId == null ||
clientId.toString().isEmpty ||
token == null ||
token.isEmpty) {
setState(() {
_errorMessage = 'Client details not found';
_isLoading = false;
});
return;
}
final response = await widget.apiService.getClientRMApi(
clientId.toString(),
token,
);
if (!mounted) return;
if (response['status'] == 'success' && response['data'] is Map) {
final data = Map<String, dynamic>.from(response['data'] as Map);
setState(() {
_level1Contacts = _parseContacts(data['level_1']);
_level2Contacts = _parseContacts(data['level_2']);
_isLoading = false;
});
} else {
setState(() {
_errorMessage =
response['message']?.toString() ?? 'Failed to load contacts';
_isLoading = false;
});
}
} catch (e) {
logDebug('getClientRM failed: $e');
if (mounted) {
setState(() {
_errorMessage = 'Failed to load contacts';
_isLoading = false;
});
}
}
}
List<Map<String, dynamic>> _parseContacts(dynamic raw) {
if (raw is! List) return [];
return raw
.whereType<Map>()
.map((item) => Map<String, dynamic>.from(item))
.toList();
}
Widget _buildContactCard(Map<String, dynamic> contact) {
final name = contact['first_name']?.toString() ?? '-';
final email = contact['email']?.toString() ?? '-';
final mobile = contact['mobile']?.toString() ?? '-';
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFE8E8E8)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: const Color(0xFF009195),
),
),
const SizedBox(height: 6),
Text(
email,
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black87),
),
const SizedBox(height: 2),
Text(
mobile,
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black87),
),
],
),
);
}
Widget _buildLevelSection({
required String title,
required List<Map<String, dynamic>> contacts,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 8),
if (contacts.isEmpty)
Text(
'No contact assigned',
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black54),
)
else
...contacts.map(_buildContactCard),
const SizedBox(height: 12),
],
);
}
Widget _buildContent() {
if (!widget.isPostUser) {
return Center(
child: Text(
'No Level Contact',
style: GoogleFonts.poppins(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.black54,
),
),
);
}
if (_isLoading) {
return const SizedBox(
height: 180,
child: Center(child: CircularProgressIndicator()),
);
}
if (_errorMessage != null) {
return Center(
child: Text(
_errorMessage!,
textAlign: TextAlign.center,
style: GoogleFonts.poppins(fontSize: 14, color: Colors.red),
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLevelSection(
title: 'Level 1 - Account Manager',
contacts: _level1Contacts,
),
_buildLevelSection(
title: 'Level 2 - Head',
contacts: _level2Contacts,
),
],
);
}
@override
Widget build(BuildContext context) {
final maxHeight = MediaQuery.of(context).size.height * 0.65;
final cappedMaxHeight = maxHeight.clamp(280.0, 520.0);
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: Text(
'Help',
style: GoogleFonts.poppins(fontWeight: FontWeight.w600, fontSize: 18),
),
content: SizedBox(
width: 560,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: cappedMaxHeight),
child: Scrollbar(
child: SingleChildScrollView(child: _buildContent()),
),
),
),
actions: [
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF009195),
),
child: Text('Close', style: GoogleFonts.poppins(color: Colors.white)),
),
],
);
}
}
class _SideItem extends StatelessWidget {
// final IconData icon;
final Widget icon; // 👈 changed
final String label;
final bool isActive;
final VoidCallback? onTap;
final VoidCallback? onHoverEnter;
final VoidCallback? onHoverExit;
const _SideItem({
super.key,
required this.icon,
required this.label,
this.isActive = false,
this.onTap,
this.onHoverEnter,
this.onHoverExit,
});
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => onHoverEnter?.call(),
onExit: (_) => onHoverExit?.call(),
child: InkWell(
onTap: onTap,
child: Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isActive
? const Color(0xFF065D61) // ACTIVE like your screenshot
: Colors.transparent,
),
child: Column(
children: [
// 👇 SVG or Icon widget
icon,
// Icon(icon, color: Colors.white, size: 22),
const SizedBox(height: 6),
Text(
label,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w500,
height: 1.15,
),
),
],
),
),
),
);
}
}
class _ClaimsSubMenuItem extends StatefulWidget {
final String label;
final VoidCallback onTap;
const _ClaimsSubMenuItem({
required this.label,
required this.onTap,
});
@override
State<_ClaimsSubMenuItem> createState() => _ClaimsSubMenuItemState();
}
class _ClaimsSubMenuItemState extends State<_ClaimsSubMenuItem> {
bool isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => isHovered = true),
onExit: (_) => setState(() => isHovered = false),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.onTap,
child: Container(
width: double.infinity,
color: isHovered ? const Color(0xFFE5F6F6) : Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
widget.label,
style: TextStyle(
fontSize: 12,
color: isHovered ? const Color(0xFF006F73) : Colors.black87,
fontWeight: isHovered ? FontWeight.w600 : FontWeight.w400,
),
),
),
),
),
);
}
}

View File

@ -1,286 +0,0 @@
import 'package:flutter/material.dart';
// import 'package:fluttertoast/fluttertoast.dart';
import 'package:toastification/toastification.dart';
import 'package:nhancepolicy/logger.dart';
class ToastHelper {
static void showSuccessToast(BuildContext context, String message) {
toastification.show(
context: context,
type: ToastificationType.success,
style: ToastificationStyle.flatColored,
autoCloseDuration: const Duration(seconds: 2),
title: Text(
message,
maxLines: 3, // allow wrapping
overflow: TextOverflow.visible,
softWrap: true,
),
// you can also use RichText widget for title and description parameters
// description: RichText(
// text: const TextSpan(text: 'This is a sample toast message. ')),
alignment: Alignment.topRight,
direction: TextDirection.ltr,
animationDuration: const Duration(milliseconds: 100),
animationBuilder: (context, animation, alignment, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
icon: const Icon(Icons.check),
primaryColor: Colors.green,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x07000000),
blurRadius: 16,
offset: Offset(0, 16),
spreadRadius: 0,
)
],
showProgressBar: true,
closeButtonShowType: CloseButtonShowType.onHover,
closeOnClick: false,
pauseOnHover: true,
dragToClose: true,
applyBlurEffect: true,
callbacks: ToastificationCallbacks(
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
onCloseButtonTap: (toastItem) =>
logDebug('Toast ${toastItem.id} close button tapped'),
onAutoCompleteCompleted: (toastItem) =>
logDebug('Toast ${toastItem.id} auto complete completed'),
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
),
);
}
static void showWarningToast(BuildContext context, String message) {
toastification.show(
context: context,
type: ToastificationType.warning,
style: ToastificationStyle.flatColored,
autoCloseDuration: const Duration(seconds: 2),
title: Text(message),
// you can also use RichText widget for title and description parameters
// description: RichText(
// text: const TextSpan(text: 'This is a sample toast message. ')),
alignment: Alignment.topRight,
direction: TextDirection.ltr,
animationDuration: const Duration(milliseconds: 100),
animationBuilder: (context, animation, alignment, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
icon: const Icon(Icons.warning),
primaryColor: Colors.amberAccent,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x07000000),
blurRadius: 16,
offset: Offset(0, 16),
spreadRadius: 0,
)
],
showProgressBar: true,
closeButtonShowType: CloseButtonShowType.onHover,
closeOnClick: false,
pauseOnHover: true,
dragToClose: true,
applyBlurEffect: true,
callbacks: ToastificationCallbacks(
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
onCloseButtonTap: (toastItem) =>
logDebug('Toast ${toastItem.id} close button tapped'),
onAutoCompleteCompleted: (toastItem) =>
logDebug('Toast ${toastItem.id} auto complete completed'),
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
),
);
}
static void showErrorToast(BuildContext context, String message) {
toastification.show(
context: context,
type: ToastificationType.error,
style: ToastificationStyle.flatColored,
autoCloseDuration: const Duration(seconds: 2),
title: Text(
message,
maxLines: 3, // allow wrapping
overflow: TextOverflow.visible,
softWrap: true,
),
// you can also use RichText widget for title and description parameters
// description: RichText(
// text: const TextSpan(text: 'This is a sample toast message. ')),
alignment: Alignment.topRight,
direction: TextDirection.ltr,
animationDuration: const Duration(milliseconds: 100),
animationBuilder: (context, animation, alignment, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
icon: const Icon(Icons.error),
primaryColor: Colors.redAccent,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x07000000),
blurRadius: 16,
offset: Offset(0, 16),
spreadRadius: 0,
)
],
showProgressBar: true,
closeButtonShowType: CloseButtonShowType.onHover,
closeOnClick: false,
pauseOnHover: true,
dragToClose: true,
applyBlurEffect: true,
callbacks: ToastificationCallbacks(
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
onCloseButtonTap: (toastItem) =>
logDebug('Toast ${toastItem.id} close button tapped'),
onAutoCompleteCompleted: (toastItem) =>
logDebug('Toast ${toastItem.id} auto complete completed'),
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
),
);
// _showToast(context, message, Colors.red);
}
static void showErrorToast2(
BuildContext context, String message, String decmessage) {
toastification.show(
context: context,
type: ToastificationType.error,
style: ToastificationStyle.flatColored,
autoCloseDuration: const Duration(seconds: 2),
title: Text(message),
// you can also use RichText widget for title and description parameters
description: RichText(text: TextSpan(text: decmessage)),
alignment: Alignment.topRight,
direction: TextDirection.ltr,
animationDuration: const Duration(milliseconds: 100),
animationBuilder: (context, animation, alignment, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
icon: const Icon(Icons.error),
primaryColor: Colors.redAccent,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x07000000),
blurRadius: 16,
offset: Offset(0, 16),
spreadRadius: 0,
)
],
showProgressBar: true,
closeButtonShowType: CloseButtonShowType.onHover,
closeOnClick: false,
pauseOnHover: true,
dragToClose: true,
applyBlurEffect: true,
callbacks: ToastificationCallbacks(
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
onCloseButtonTap: (toastItem) =>
logDebug('Toast ${toastItem.id} close button tapped'),
onAutoCompleteCompleted: (toastItem) =>
logDebug('Toast ${toastItem.id} auto complete completed'),
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
),
);
// _showToast(context, message, Colors.red);
}
static void showInfoToast(BuildContext context, String message) {
toastification.show(
context: context,
type: ToastificationType.info,
style: ToastificationStyle.flatColored,
autoCloseDuration: const Duration(seconds: 2),
title: Text(message),
// you can also use RichText widget for title and description parameters
// description: RichText(
// text: const TextSpan(text: 'This is a sample toast message. ')),
alignment: Alignment.topRight,
direction: TextDirection.ltr,
animationDuration: const Duration(milliseconds: 100),
animationBuilder: (context, animation, alignment, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
icon: const Icon(Icons.info),
primaryColor: Colors.lightBlue,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x07000000),
blurRadius: 16,
offset: Offset(0, 16),
spreadRadius: 0,
)
],
showProgressBar: true,
closeButtonShowType: CloseButtonShowType.onHover,
closeOnClick: false,
pauseOnHover: true,
dragToClose: true,
applyBlurEffect: true,
callbacks: ToastificationCallbacks(
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
onCloseButtonTap: (toastItem) =>
logDebug('Toast ${toastItem.id} close button tapped'),
onAutoCompleteCompleted: (toastItem) =>
logDebug('Toast ${toastItem.id} auto complete completed'),
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
),
);
// _showToast(context, message, Colors.red);
}
static void _showToast(BuildContext context, String message, Color color) {
// Fluttertoast.showToast(
// msg: message,
// toastLength: Toast.LENGTH_SHORT,
// gravity: ToastGravity.TOP,
// timeInSecForIosWeb: 5,
// backgroundColor: color,
// textColor: Colors.white,
// fontSize: 16.0,
// );
}
}

View File

@ -1,400 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../config/environment.dart';
import '../service/token_storage_service.dart';
class NhanceTopBar extends StatefulWidget implements PreferredSizeWidget {
const NhanceTopBar({super.key});
@override
Size get preferredSize => const Size.fromHeight(64);
@override
State<NhanceTopBar> createState() => _NhanceTopBarState();
}
class _NhanceTopBarState extends State<NhanceTopBar> {
final tokenStorage = TokenStorageService();
List<Map<String, dynamic>> branches = [];
Map<String, dynamic>? selectedBranch;
/// client_id logo URL
final Map<String, String> _logoByClientId = {};
@override
void initState() {
super.initState();
_loadBranches();
}
void _loadBranches() {
branches = tokenStorage.getCombinedBranches();
selectedBranch = tokenStorage.getSelectedBranch();
setState(() {});
_loadBranchLogos();
}
String _clientKey(Map<String, dynamic> branch) {
return (branch['client_id'] ?? branch['id'] ?? '').toString();
}
String? _logoFromBranch(Map<String, dynamic> branch) {
final candidates = [
branch['client_logo'],
branch['logo'],
branch['clientLogo'],
];
for (final value in candidates) {
final url = value?.toString().trim() ?? '';
if (url.isNotEmpty) return url;
}
return null;
}
Map<String, dynamic>? _decodeBranchToken(String token) {
try {
final parts = token.split('.');
if (parts.length != 3) return null;
final payload = base64Url.normalize(parts[1]);
final decoded = utf8.decode(base64Url.decode(payload));
final parsed = json.decode(decoded);
if (parsed is Map<String, dynamic>) return parsed;
if (parsed is Map) return Map<String, dynamic>.from(parsed);
return null;
} catch (_) {
return null;
}
}
/// Read post/pre client + branch ids from selected branch JWT payload.
Map<String, String>? _clientDetailsParamsFromBranch(
Map<String, dynamic> branch,
) {
final token = branch['token']?.toString() ??
tokenStorage.getCurrentToken() ??
'';
if (token.isEmpty) return null;
final decoded = _decodeBranchToken(token);
if (decoded == null) return null;
final postClientId = decoded['post_client_id']?.toString().trim() ?? '';
final postBranchId = decoded['post_branch_id']?.toString().trim() ?? '';
final preClientId = decoded['pre_client_id']?.toString().trim() ?? '';
final preBranchId = decoded['pre_branch_id']?.toString().trim() ?? '';
if (postClientId.isEmpty &&
postBranchId.isEmpty &&
preClientId.isEmpty &&
preBranchId.isEmpty) {
return null;
}
return {
'post_client_id': postClientId,
'post_branch_id': postBranchId,
'pre_client_id': preClientId,
'pre_branch_id': preBranchId,
};
}
Future<void> _loadBranchLogos() async {
final uniqueBranches = <String, Map<String, dynamic>>{};
for (final branch in branches) {
final key = _clientKey(branch);
if (key.isEmpty) continue;
uniqueBranches.putIfAbsent(key, () => branch);
}
for (final entry in uniqueBranches.entries) {
final key = entry.key;
if (_logoByClientId.containsKey(key)) continue;
final fromBranch = _logoFromBranch(entry.value);
if (fromBranch != null) {
if (!mounted) return;
setState(() => _logoByClientId[key] = fromBranch);
continue;
}
final logoUrl = await _fetchClientLogoUrl(entry.value);
if (!mounted) return;
if (logoUrl != null && logoUrl.isNotEmpty) {
setState(() => _logoByClientId[key] = logoUrl);
}
}
}
Future<String?> _fetchClientLogoUrl(Map<String, dynamic> branch) async {
try {
final token = branch['token']?.toString() ??
tokenStorage.getCurrentToken() ??
'';
if (token.isEmpty) return null;
final params = _clientDetailsParamsFromBranch(branch);
if (params == null) return null;
final url = Uri.parse('${Environment.apiUrl}getClientDetails').replace(
queryParameters: params,
);
final response = await http.get(
url,
headers: {
'Authorization': 'Bearer $token',
'APP-SIGNATURE':
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
},
);
if (response.statusCode != 200) return null;
final data = jsonDecode(response.body);
if (data is! Map || data['status'] != 'success') return null;
final logo = data['data']?['client']?['client_logo']?.toString().trim();
if (logo == null || logo.isEmpty) return null;
return logo;
} catch (_) {
return null;
}
}
Future<String> _getBranchSwitchRoute() async {
final postRaw = await tokenStorage.readValue('empAllowed_modules');
final postModules = postRaw != null && postRaw.isNotEmpty
? List<int>.from(jsonDecode(postRaw))
: <int>[];
if (postModules.isNotEmpty && postModules.contains(5)) {
return 'claimsOverviewDashboard';
}
return 'policies';
}
Future<void> _onBranchSelected(Map<String, dynamic> branch) async {
final tokenStorage = TokenStorageService();
await tokenStorage.resetSessionAndSwitchBranch(branch);
if (!mounted) return;
_loadBranches();
final route = await _getBranchSwitchRoute();
if (!mounted) return;
Navigator.pushReplacementNamed(context, route);
}
@override
Widget build(BuildContext context) {
final selectedLogo = selectedBranch == null
? null
: _logoByClientId[_clientKey(selectedBranch!)];
return AppBar(
automaticallyImplyLeading: false,
backgroundColor: const Color(0xFFBFEFEF),
elevation: 0,
title: Row(
children: [
Image.asset('assets/nhance_client_logo.png', height: 32),
const Spacer(),
if (selectedBranch != null)
_BranchPopup(
clientName: selectedBranch!['client_name']?.toString() ?? '',
branchName: selectedBranch!['branch_name']?.toString() ?? '',
selectedLogoUrl: selectedLogo,
branches: branches,
logoByClientId: _logoByClientId,
clientKeyBuilder: _clientKey,
onSelected: _onBranchSelected,
),
],
),
);
}
}
class _BranchPopup extends StatelessWidget {
final String clientName;
final String branchName;
final String? selectedLogoUrl;
final List<Map<String, dynamic>> branches;
final Map<String, String> logoByClientId;
final String Function(Map<String, dynamic>) clientKeyBuilder;
final Function(Map<String, dynamic>) onSelected;
const _BranchPopup({
required this.clientName,
required this.branchName,
required this.selectedLogoUrl,
required this.branches,
required this.logoByClientId,
required this.clientKeyBuilder,
required this.onSelected,
});
@override
Widget build(BuildContext context) {
return PopupMenuButton<Map<String, dynamic>>(
tooltip: '',
offset: const Offset(0, 48),
onSelected: onSelected,
itemBuilder: (context) {
return branches.map((branch) {
final logoUrl = logoByClientId[clientKeyBuilder(branch)];
return PopupMenuItem<Map<String, dynamic>>(
value: branch,
child: Row(
children: [
_BranchLogo(logoUrl: logoUrl, size: 24),
const SizedBox(width: 8),
Expanded(
child: Text(
branch['client_name']?.toString() ?? '',
style: const TextStyle(fontSize: 13),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 10),
const Icon(Icons.location_on, size: 14, color: Colors.grey),
const SizedBox(width: 4),
Flexible(
child: Text(
branch['branch_name']?.toString() ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}).toList();
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_BranchLogo(logoUrl: selectedLogoUrl, size: 32),
const SizedBox(width: 8),
Container(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Text(
clientName,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 8),
Row(
children: [
const Icon(
Icons.location_on,
size: 14,
color: Colors.grey,
),
const SizedBox(width: 4),
Text(
branchName,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
const SizedBox(width: 6),
const Icon(
Icons.keyboard_arrow_down,
size: 20,
color: Colors.orange,
),
],
),
),
],
),
);
}
}
class _BranchLogo extends StatelessWidget {
final String? logoUrl;
final double size;
const _BranchLogo({
required this.logoUrl,
required this.size,
});
@override
Widget build(BuildContext context) {
final url = logoUrl?.trim() ?? '';
if (url.isEmpty) {
return _placeholder();
}
return ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image.network(
url,
width: size,
height: size,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => _placeholder(),
loadingBuilder: (context, child, progress) {
if (progress == null) return child;
return SizedBox(
width: size,
height: size,
child: const Padding(
padding: EdgeInsets.all(4),
child: CircularProgressIndicator(strokeWidth: 1.5),
),
);
},
),
);
}
Widget _placeholder() {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: const Color(0xFFE8F5F5),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: const Color(0xFFBFEFEF)),
),
child: Icon(
Icons.business,
size: size * 0.6,
color: const Color(0xFF00999E),
),
);
}
}

View File

@ -1,797 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:nhancepolicy/responsive.dart';
import 'package:nhancepolicy/service/api_service.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:pinput/pinput.dart';
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:jwt_decode/jwt_decode.dart';
import '../customAppBar/toastHelper.dart';
import '../models/platform_helper_mobile.dart'
if (dart.library.html) '../models/platform_helper_other.dart';
import 'branch/branch_selection_page.dart';
import 'config/environment.dart';
import 'package:nhancepolicy/logger.dart';
// import 'dart:html' as html;
class MyEmailVerify extends StatefulWidget {
final String type; // 'email' or 'mobile'
final String value;
const MyEmailVerify({super.key, required this.type, required this.value});
@override
State<MyEmailVerify> createState() => _MyEmailVerifyState();
}
class _MyEmailVerifyState extends State<MyEmailVerify> {
late ApiService apiService;
TextEditingController _otpController = TextEditingController();
final _formKey = GlobalKey<FormState>();
late Timer _timer;
int _secondsRemaining = 60;
bool _isTimerRunning = false;
dynamic enrollmentEmpClientBranchId;
dynamic enrollmentEmpCodeString;
dynamic enrollmentEmpPrimaryId;
dynamic enrollmentGpaEmpName;
dynamic enrollmentClient_id;
dynamic enrollmentHrId;
List<dynamic> enrollmentAllowed_modules = [];
dynamic enrollmentEmp_status;
dynamic empCodeString;
dynamic empClientBranchId;
dynamic empPrimaryId;
dynamic gpaEmpName;
dynamic empClientId;
dynamic empHrId;
List<dynamic> empAllowed_modules = [];
dynamic _token;
dynamic _postToken;
dynamic clientName;
dynamic clientLogo;
dynamic emp_status;
dynamic fCMToken;
late String verificationId;
bool _isLoading = false;
late String _verificationId;
dynamic empMobileNo;
dynamic empEmailid;
final tokenService = TokenStorageService();
@override
void initState() {
super.initState();
apiService = ApiService(context);
startTimer();
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
void startTimer() {
_isTimerRunning = true;
_timer = Timer.periodic(Duration(seconds: 1), (Timer timer) {
setState(() {
if (_secondsRemaining > 0) {
_secondsRemaining--;
} else {
_isTimerRunning = false;
_timer.cancel();
}
});
});
}
void verifyOTP(String otp) async {
try {
if(otp.isEmpty){
ToastHelper.showErrorToast(context, 'Please enter OTP');
return;
}
final Map<String, dynamic> payload = (widget.type == 'mobile')
? {'otp': _otpController.text, 'mobile_no': widget.value}
: {'otp': _otpController.text, 'email': widget.value};
final response = await http.post(
Uri.parse(Environment.apiUrl + 'getVerifiedHrData'),
body: json.encode(payload),
headers: {
'APP-SIGNATURE':
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
HttpHeaders.contentTypeHeader: 'application/json',
},
);
if (response.statusCode == 200) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
logDebug('Response data: $data');
final String mainStatus = data['status'] ?? 'failed';
final Map<String, dynamic> postEnrollment1 =
data['post_enrollment'] ?? {};
final String postStatus = postEnrollment1['status'] ?? 'failed';
/// BLOCK if BOTH failed
if (mainStatus != 'success' && postStatus != 'success') {
ToastHelper.showErrorToast(
context,
postEnrollment1['message'],
);
return;
}
// Safely extract preEnrollmentData
List<dynamic> preEnrollmentData = [];
if (data['data'] is List) {
preEnrollmentData = data['data'];
}
// Safely extract postEnrollmentData
final postEnrollment = data['post_enrollment'] ?? {};
List<dynamic> postEnrollmentData = [];
if (postEnrollment['data'] is List) {
postEnrollmentData = postEnrollment['data'];
}
// Extract enrollment data
// List<dynamic> preEnrollmentData = data['data'] ?? [];
// Map<String, dynamic> postEnrollment = data['post_enrollment'] ?? {};
// List<dynamic> postEnrollmentData = postEnrollment['data'] ?? [];
// Save to global storage
final tokenStorage = TokenStorageService();
await tokenStorage.saveEnrollmentData(
preEnrollmentData,
postEnrollmentData,
);
// Navigate to branch selection
// 🔥 Get all valid branches
final branches = tokenStorage.getCombinedBranches();
if (branches.length == 1) {
// ONLY ONE BRANCH AUTO SELECT
final singleBranch = branches.first;
// Save selected branch
await tokenStorage.saveSelectedBranch(singleBranch);
final decodedToken = tokenStorage.getDecodedToken();
final token = tokenStorage.getCurrentToken();
if (decodedToken == null || token == null) {
ToastHelper.showErrorToast(context, 'Invalid token data');
return;
}
// Save decoded session values
await tokenStorage.saveDecodedSessionData(decodedToken, token);
if (!mounted) return;
// 🚀 DIRECTLY GO TO POLICIES
Navigator.pushReplacementNamed(context, 'policies');
} else {
// 🧭 MULTIPLE BRANCHES SHOW SELECTION PAGE
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const BranchSelectionPage(),
),
);
}
// logDebug('data: $data');
// _token = data['data'];
// String status = data['status'];
//
// // Directly access the post_enrollment data
// Map<String, dynamic> post = data['post_enrollment'];
// logDebug('post: $post');
//
// _postToken = post['data'];
// String postStatus = post['status'];
//
// if (postStatus == 'success') {
// setState(() {
// _isLoading = false;
// });
// postSuccessData(post, data);
// } else if (status == 'success') {
// setState(() {
// _isLoading = false;
// });
// enrollmentSuccessData(data);
// } else {
// setState(() {
// _isLoading = false;
// });
// ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again');
// // Show a Snackbar if the OTP is invalid
// logDebug('Invalid OTP. Please try again');
// }
} else if (response.statusCode == 401) {
setState(() {
_isLoading = false;
});
await tokenService.clearAll(); // 🔐 clears flutter_secure_storage
ToastHelper.showErrorToast(context, 'Session Out');
if (!context.mounted) return;
Navigator.pushNamedAndRemoveUntil(
context,
'hrLogin',
(route) => false,
);
} else if (response.statusCode == 429) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
if (data.containsKey('error')) {
final message = data['error']['message'];
ToastHelper.showErrorToast(context, message);
} else {
final message = data['message'];
ToastHelper.showErrorToast(context, message);
}
} else if (response.statusCode == 403) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
if (data.containsKey('error')) {
final message = data['error']['message'];
ToastHelper.showErrorToast(context, message);
} else {
final message = data['message'];
ToastHelper.showErrorToast(context, message);
}
} else if (response.statusCode == 451) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
if (data.containsKey('error')) {
final message = data['error']['message'];
ToastHelper.showErrorToast(context, message);
} else {
final message = data['message'];
ToastHelper.showErrorToast(context, message);
}
} else {
setState(() {
_isLoading = false;
});
throw Exception('Failed to load data');
}
} catch (e) {
setState(() {
_isLoading = false;
});
logDebug('Error: $e');
ToastHelper.showWarningToast(context, 'Something went wrong');
// Show a Snackbar if there's an error while verifying OTP
logDebug('Failed to verify OTP. Please try again.');
}
}
void postSuccessData(post, data) async {
String status = data['status'];
String postStatus = post['status'];
if (postStatus == 'success') {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('_postToken', post['data']);
// Decode the JWT token received from the API response
Map<String, dynamic>? decodedToken = Jwt.parseJwt(post['data']);
logDebug('postdecodedToken : $decodedToken');
empClientBranchId = decodedToken['ref_id'];
prefs.setString('empClientBranchId', empClientBranchId);
empCodeString = decodedToken['emp_code'].toString();
prefs.setString('empCode', empCodeString);
empPrimaryId = decodedToken['id'];
prefs.setString('empPrimaryId', empPrimaryId);
gpaEmpName = decodedToken['name'].toString();
prefs.setString('gpaEmpName', gpaEmpName);
empClientId = decodedToken['client_id'];
prefs.setString('empClientId', empClientId);
empHrId = decodedToken['id'];
prefs.setString('empHrId', empHrId);
empAllowed_modules = decodedToken['allowed_modules'];
prefs.setString('empAllowed_modules', jsonEncode(empAllowed_modules));
// emp_status = decodedToken['emp_status'];
// prefs.setString('emp_status', emp_status);
// getClientLogoAndDetails();
logDebug('Successfully Login');
// Redirect to another page
}
if (status == 'success') {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('enrollToken', data['data']);
// Decode the JWT token received from the API response
Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
logDebug('postenrolldecodedToken : $decodedToken');
enrollmentEmpClientBranchId = decodedToken['ref_id'];
prefs.setString(
'enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
enrollmentEmpCodeString = decodedToken['emp_code'].toString();
prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
enrollmentEmpPrimaryId = decodedToken['id'];
prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
enrollmentGpaEmpName = decodedToken['name'].toString();
prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
enrollmentClient_id = decodedToken['client_id'];
prefs.setString('enrollmentClient_id', enrollmentClient_id);
enrollmentHrId = decodedToken['id'];
prefs.setString('enrollmentHrId', enrollmentHrId);
enrollmentAllowed_modules = decodedToken['allowed_modules'];
prefs.setString(
'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
// enrollmentEmp_status = decodedToken['emp_status'];
// prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
}
getClientLogoAndDetails(empClientId, empClientBranchId, enrollmentClient_id,
enrollmentEmpClientBranchId, post['data']);
final SharedPreferences prefs = await SharedPreferences.getInstance();
final _postToken = prefs.getString('_postToken');
if (_postToken != null && _postToken.isNotEmpty) {
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
// if (emp_status == 'enrolled' || emp_status == 'active') {
Navigator.pushReplacementNamed(context, 'policies');
// } else {
// Navigator.pushReplacementNamed(context, 'empDetails');
// }
}
}
void enrollmentSuccessData(data) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('enrollToken', data['data']);
// Decode the JWT token received from the API response
Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
logDebug('enrolldecodedToken : $decodedToken');
enrollmentEmpClientBranchId = decodedToken['ref_id'];
prefs.setString('enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
enrollmentEmpCodeString = decodedToken['emp_code'].toString();
prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
enrollmentEmpPrimaryId = decodedToken['id'];
prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
enrollmentGpaEmpName = decodedToken['name'].toString();
prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
enrollmentClient_id = decodedToken['client_id'];
prefs.setString('enrollmentClient_id', enrollmentClient_id);
enrollmentHrId = decodedToken['id'];
logDebug("enrollmentHrId- $enrollmentHrId");
prefs.setString('enrollmentHrId', enrollmentHrId);
enrollmentAllowed_modules = decodedToken['allowed_modules'];
prefs.setString(
'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
// enrollmentEmp_status = decodedToken['emp_status'];
// prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
getClientLogoAndDetails(empClientId, empClientBranchId, enrollmentClient_id,
enrollmentEmpClientBranchId, data['data']);
logDebug('Successfully Login');
// Redirect to another page
final enrollToken = prefs.getString('enrollToken');
if (enrollToken != null && enrollToken.isNotEmpty) {
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
// if (emp_status == 'enrolled' || emp_status == 'active') {
// Navigator.pushReplacementNamed(context, 'home');
// } else {
Navigator.pushReplacementNamed(context, 'policies');
// }
}
}
void _resendOTP() {
setState(() {
_secondsRemaining = 60;
_isTimerRunning = true;
});
startTimer();
verifyMobileAndEmailNumber();
}
Future<void> verifyMobileAndEmailNumber() async {
try {
// var enteredMobileNumber = mobileController.text;
final Map<String, dynamic> payload = (widget.type == 'mobile')
? {'mobile_number': widget.value}
: {'email': widget.value};
String apiEndpoint = (widget.type == 'mobile')
? Environment.apiUrl + 'verifyHrWithMobileNumber'
: Environment.apiUrl + 'verifyHrWithEmail';
final response = await http.post(
Uri.parse(apiEndpoint),
body: json.encode(payload),
headers: {
'APP-SIGNATURE':
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
HttpHeaders.contentTypeHeader: 'application/json',
},
);
if (response.statusCode == 200) {
Map<String, dynamic> data = json.decode(response.body);
bool userVerification = data['data']['user_verification'];
String message = data['data']['message'];
if (userVerification) {
ToastHelper.showSuccessToast(
context, 'Verification code sent to ${widget.value}');
} else {
ToastHelper.showErrorToast(context, message);
logDebug('Invalid mobile number');
}
} else if (response.statusCode == 403) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
final message = data['message'];
ToastHelper.showErrorToast(context, message);
} else if (response.statusCode == 451) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
final message = data['error']['message'];
ToastHelper.showErrorToast(context, message);
} else {
ToastHelper.showErrorToast(context, 'Something went wrong');
throw Exception('Failed to verify mobile number');
}
} catch (e) {
ToastHelper.showErrorToast(context, 'Something went wrong');
logDebug('Error: $e');
}
}
Future<void> getClientLogoAndDetails(empClientId, empClientBranchId,
enrollmentClientId, enrollmentClientBranchId, token) async {
var url = Uri.parse(Environment.apiUrl +
'getClientDetails?post_client_id=$empClientId&post_branch_id=$empClientBranchId&pre_client_id=$enrollmentClientId&pre_branch_id=$enrollmentClientBranchId');
try {
var response = await http.get(
url,
headers: {
'APP-SIGNATURE':
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
'Authorization':
'Bearer $token', // Add token to the Authorization header
},
);
if (response.statusCode == 200) {
// logDebug('response.statusCode == 200');
Map<String, dynamic> data = json.decode(response.body);
// logDebug(data);
if (data.containsKey('data')) {
dynamic clientDetails = data['data'];
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('clientLogo', clientDetails['client']['client_logo']);
prefs.setString('clientName', clientDetails['client']['client_name']);
setState(() {
// dynamic clientDetails = data['data'];
// logDebug(clientDetails);
clientName = clientDetails['client']['client_name'];
logDebug(clientName);
clientLogo = clientDetails['client']['client_logo'];
logDebug(clientLogo);
});
} else {
// Handle other status messages if needed
// ToastHelper.showErrorToast(
// context, 'API request failed with status: ${data['status']}');
logDebug('API request failed with status: ${data['status']}');
}
} else {
// Handle other status codes
// ToastHelper.showErrorToast(
// context, 'Request failed with status: ${response.statusCode}');
logDebug('Request failed with status: ${response.statusCode}');
}
} catch (e) {
// Handle exceptions
logDebug('Exception occurred: $e');
}
}
@override
Widget build(BuildContext context) {
final Size _size = MediaQuery.of(context).size;
final defaultPinTheme = PinTheme(
width: 56,
height: 56,
textStyle: TextStyle(
fontSize: 20,
color: Color.fromRGBO(30, 60, 87, 1),
fontWeight: FontWeight.w600,
),
decoration: BoxDecoration(
border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)),
borderRadius: BorderRadius.circular(20),
),
);
final focusedPinTheme = defaultPinTheme.copyDecorationWith(
border: Border.all(color: Color.fromRGBO(114, 178, 238, 1)),
borderRadius: BorderRadius.circular(8),
);
final submittedPinTheme = defaultPinTheme.copyWith(
decoration: defaultPinTheme.decoration?.copyWith(
color: Color.fromRGBO(234, 239, 243, 1),
),
);
return Scaffold(
body: Container(
width: double.infinity,
height: _size.height,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Color(0xFF00B6AC),
Color(0xFF83E0DE),
Color(0xFF01B4A8),
],
),
),
child: Center(
child: Container(
width: double.infinity, // fixed web width
height: _size.height, // fixed web height (IMPORTANT)
margin: const EdgeInsets.all(60),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(40)),
),
child: Row(
children: [
// ================= LEFT IMAGE =================
Expanded(
flex: 5,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(40),
bottomLeft: Radius.circular(40),
),
child: Image.asset(
'assets/hrLogin.png',
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
),
),
),
// ================= RIGHT FORM =================
Expanded(
flex: 7,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 300,
height: 100,
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(horizontal: 150)
: EdgeInsets.symmetric(horizontal: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Welcome to Nhance",
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(horizontal: 150)
: EdgeInsets.symmetric(horizontal: 0),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text:
"Please enter the one-time usage code sent to your ",
style: TextStyle(
fontSize: 12,
height: 1.5,
color: Color(0xFF000000)),
children: [
TextSpan(
text: widget.type == 'mobile'
? 'mobile number ${widget.value}'
: 'Email Id ${widget.value}',
),
TextSpan(
text: widget.type == 'mobile'
? ' (Change Mobile)'
: ' (Change Email)',
style: TextStyle(
fontSize: 12,
color: Color(
0xFFE26728)), // Change color as desired
recognizer: TapGestureRecognizer()
..onTap = () {
// Navigate to the page where the user can change the phone number
Navigator.pushNamed(
context, 'hrLogin');
},
),
],
),
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(horizontal: 150)
: EdgeInsets.symmetric(horizontal: 0),
child: Pinput(
length: 6,
// defaultPinTheme: defaultPinTheme,
// focusedPinTheme: focusedPinTheme,
// submittedPinTheme: submittedPinTheme,
showCursor: true,
controller: _otpController,
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(horizontal: 150)
: EdgeInsets.symmetric(horizontal: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment
.end, // Align text to the right
children: [
_isTimerRunning
? Text(
"Resend OTP in $_secondsRemaining seconds",
style: GoogleFonts.poppins(
color: Colors.black),
)
: InkWell(
onTap: () {
_resendOTP();
},
child: Text(
"Resend OTP",
style: GoogleFonts.poppins(
color: Colors.blue),
),
),
],
),
),
SizedBox(height: 15),
Container(
margin: Responsive.isDesktop(context)
? EdgeInsets.symmetric(horizontal: 150)
: EdgeInsets.symmetric(horizontal: 0),
child: SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF00989E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!
.save(); // Save form fields before calling verifyOTP
verifyOTP(_otpController.text);
}
},
child: Text(
"Submit",
style: GoogleFonts.poppins(
color: Color(0xFFFFFFFF)),
),
),
),
),
SizedBox(height: 15),
Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 8),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: 'By continuing, you agree with our ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
children: <TextSpan>[
TextSpan(
text: 'privacy policy ',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
TextSpan(
text: 'and ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
),
TextSpan(
text: 'terms of use',
style: GoogleFonts.poppins(
color: Color(0xFF00989E),
fontSize: 9,
),
),
],
),
),
),
],
),
),
),
),
],
),
),
)));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,663 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
import 'package:nhancepolicy/hrVerify.dart';
import 'dart:convert';
import 'dart:io';
import 'package:nhancepolicy/responsive.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_animated_button/flutter_animated_button.dart';
import 'package:nhancepolicy/service/token_storage_service.dart';
import 'package:url_launcher/url_launcher.dart';
import 'config/environment.dart';
import 'email_verify.dart';
import 'package:nhancepolicy/logger.dart';
class MyHrLogin extends StatefulWidget {
const MyHrLogin({Key? key});
@override
State<MyHrLogin> createState() => _MyPhoneState();
}
class _MyPhoneState extends State<MyHrLogin> {
final TextEditingController emailMobileController = TextEditingController();
// final TextEditingController emailController = TextEditingController();
// TextEditingController countryController = TextEditingController();
// TextEditingController mobileController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isCheckingToken = false;
dynamic empMobileNo;
dynamic empEmailid;
late String _verificationId;
int? _resendToken;
bool _isLoading = false;
bool isEmailFieldVisible = false;
final tokenService = TokenStorageService();
@override
void initState() {
logDebug('vndbbcbdskbvkjs1');
// countryController.text = "+91";
super.initState();
clearLocalStorageWhenStarts('initState');
}
clearLocalStorageWhenStarts(fromData) async {
logDebug(fromData);
logDebug('Clearing secure storage on app start');
await tokenService.clearAll();
logDebug('Secure Storage Cleared');
}
void toggleField() {
setState(() {
isEmailFieldVisible = !isEmailFieldVisible;
});
}
Future<void> verifyMobileAndEmailNumber() async {
await tokenService.clearAll();
logDebug('verifyMobileAndEmailNumber :- Local Storage Clear');
try {
if (_formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});
String input = emailMobileController.text.trim();
bool isEmail = RegExp(r'^[^@]+@[^@]+\.[^@]+$').hasMatch(input);
bool isMobile = RegExp(r'^[0-9]{10}$').hasMatch(input);
if (isEmail) {
setState(() {
isEmailFieldVisible = true;
});
logDebug("User entered Email: $input");
} else if (isMobile) {
isEmailFieldVisible = false;
logDebug("User entered Mobile: $input");
}
// Determine the API and the payload based on the visible field
String apiEndpoint = isEmailFieldVisible
? Environment.apiUrl + 'verifyHrWithEmail'
: Environment.apiUrl + 'verifyHrWithMobileNumber';
Map<String, dynamic> payload = isEmailFieldVisible
? {'email': emailMobileController.text}
: {'mobile_number': emailMobileController.text};
// var enteredMobileNumber = mobileController.text;
final response = await http.post(
Uri.parse(apiEndpoint),
body: json.encode(payload),
headers: {
'APP-SIGNATURE':
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
HttpHeaders.contentTypeHeader: 'application/json',
},
);
if (response.statusCode == 200) {
Map<String, dynamic> data = json.decode(response.body);
bool userVerification = data['data']['user_verification'];
String message = data['data']['message'];
if (userVerification) {
if (isEmailFieldVisible) {
logDebug('isEmailFieldVisible $isEmailFieldVisible');
await tokenService.writeValue(
'empEmail',
emailMobileController.text,
);
// logDebug('${emailMobileController.text}');
// final message = 'Verification code sent to ${emailMobileController.text}';
ToastHelper.showSuccessToast(context,
'Verification code sent to ${emailMobileController.text}');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyEmailVerify(
type: 'email',
value: emailMobileController.text
.trim(), // Pass the phone number
),
),
);
} else {
// final message = 'Verification code sent to ${emailMobileController.text}';
ToastHelper.showSuccessToast(context,
'Verification code sent to ${emailMobileController.text}');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyEmailVerify(
type: 'mobile',
value: emailMobileController.text
.trim(), // Pass the phone number
),
),
);
}
// ToastHelper.showSuccessToast(context, message);
} else {
setState(() {
_isLoading = false;
});
ToastHelper.showErrorToast(context, message);
logDebug('Invalid mobile number');
}
} else if (response.statusCode == 429) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
if (data.containsKey('error')) {
final message = data['error']['message'];
ToastHelper.showErrorToast(context, message);
} else {
final message = data['message'];
ToastHelper.showErrorToast(context, message);
}
} else if (response.statusCode == 403) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
if (data.containsKey('error')) {
final message = data['error']['message'];
ToastHelper.showErrorToast(context, message);
} else {
final message = data['message'];
ToastHelper.showErrorToast(context, message);
}
} else if (response.statusCode == 451) {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
if (data.containsKey('error')) {
final message = data['error']['message'];
ToastHelper.showErrorToast(context, message);
} else {
final message = data['message'];
ToastHelper.showErrorToast(context, message);
}
} else {
setState(() {
_isLoading = false;
});
Map<String, dynamic> data = json.decode(response.body);
final message = data['message'];
ToastHelper.showErrorToast(context, message);
throw Exception('Failed to verify mobile number');
}
}
} catch (e) {
setState(() {
_isLoading = false;
});
ToastHelper.showErrorToast(context, 'Something went wrong');
logDebug('Error: $e');
}
}
// Future<void> _verifyPhoneNumber() async {
// var enteredMobileNumber = mobileController.text;
// var countryCode = countryController.text;
// logDebug('${countryCode + enteredMobileNumber}');
// await _auth.verifyPhoneNumber(
// phoneNumber: '${countryCode + enteredMobileNumber}',
// timeout: const Duration(seconds: 60),
// verificationCompleted: (PhoneAuthCredential credential) async {
// await _auth.signInWithCredential(credential);
// // ToastHelper.showSuccessToast(context, 'Verified Successfully!');
// // setState(() {
// // _isLoading = false;
// // });
// },
// verificationFailed: (FirebaseAuthException e) {
// logDebug('Verification Failed: ${e.code} - ${e.message}');
// String errorMessage;
// if (e.code == 'invalid-app-credential') {
// errorMessage = 'Invalid Credential. Please try again.';
// } else if (e.code == 'invalid-phone-number') {
// errorMessage = 'The provided phone number is not valid.';
// } else if (e.code == 'too-many-requests') {
// errorMessage = 'Too many requests. Try again later.';
// } else {
// errorMessage = 'Verification Failed: ${e.message}';
// }
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text(errorMessage),
// duration: Duration(seconds: 2),
// ),
// );
//
// ToastHelper.showSuccessToast(context, errorMessage);
// setState(() {
// _isLoading = false;
// });
// },
// codeSent: (String verificationId, int? resendToken) async {
// setState(() {
// _verificationId = verificationId;
// _resendToken = resendToken;
// });
//
// // When getting the verificationId
// SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs.setString('verificationId', _verificationId);
//
// ToastHelper.showSuccessToast(
// context, 'Verification code sent to ${enteredMobileNumber}');
//
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => MyHrVerify(
// verificationId: _verificationId,
// mobileNumber: enteredMobileNumber,
// resendToken: _resendToken,
// onResendCode: _resendCode, // Pass the phone number
// ),
// ),
// );
// setState(() {
// _isLoading = false;
// });
// },
// codeAutoRetrievalTimeout: (String verificationId) async {
// setState(() {
// _verificationId = verificationId;
// });
// final prefs = await SharedPreferences.getInstance();
// await prefs.setString('verificationId', verificationId);
// ToastHelper.showSuccessToast(context, 'Code auto-retrieval timed out.');
// setState(() {
// _isLoading = false;
// });
// },
// );
// }
//
// void _resendCode(String mobileNumber, int? resendToken) async {
// await _auth.verifyPhoneNumber(
// phoneNumber: '${countryController.text + mobileNumber}',
// timeout: const Duration(seconds: 60),
// forceResendingToken: resendToken,
// verificationCompleted: (PhoneAuthCredential credential) async {
// await _auth.signInWithCredential(credential);
// },
// verificationFailed: (FirebaseAuthException e) {
// if (e.code == 'invalid-phone-number') {
// logDebug('The provided phone number is not valid.');
// }
// },
// codeSent: (String verificationId, int? resendToken) async {
// final prefs = await SharedPreferences.getInstance();
// await prefs.setString('verificationId', verificationId);
// setState(() {
// _verificationId = verificationId;
// _resendToken = resendToken;
// });
// ToastHelper.showSuccessToast(
// context, 'Verification code resent to ${mobileNumber}');
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => MyHrVerify(
// verificationId: _verificationId,
// mobileNumber: mobileNumber,
// resendToken: _resendToken,
// onResendCode: _resendCode,
// ),
// ),
// );
// },
// codeAutoRetrievalTimeout: (String verificationId) async {
// final prefs = await SharedPreferences.getInstance();
// await prefs.setString('verificationId', verificationId);
// setState(() {
// _verificationId = verificationId;
// });
// },
// );
// }
@override
Widget build(BuildContext context) {
final Size _size = MediaQuery.of(context).size;
return Scaffold(
body: Container(
width: double.infinity,
height: _size.height,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Color(0xFF00B6AC),
Color(0xFF83E0DE),
Color(0xFF01B4A8),
],
),
),
child: Center(
child: Container(
width: double.infinity, // fixed web width
height: _size.height, // fixed web height (IMPORTANT)
margin: const EdgeInsets.all(60),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(40)),
),
child: Row(
children: [
// ================= LEFT IMAGE =================
Expanded(
flex: 5,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(40),
bottomLeft: Radius.circular(40),
),
child: Image.asset(
'assets/hrLogin.png',
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
),
),
),
// ================= RIGHT FORM =================
Expanded(
flex: 7,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/Nhance-Logo-Final 1.png',
width: 300,
height: 100,
),
SizedBox(height: 25),
Container(
margin: EdgeInsets.symmetric(horizontal: 150),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Text(
"Login with your ${isEmailFieldVisible ? 'email' : 'mobile number'} and OTP to review and enroll for exciting health benefits for you and your family",
style: GoogleFonts.poppins(
fontSize: 12,
color: Color(0xFF000000)),
textAlign: TextAlign.center,
),
)
],
),
),
SizedBox(height: 20),
Container(
height: 55,
margin:
const EdgeInsets.symmetric(horizontal: 150),
decoration: BoxDecoration(
border:
Border.all(width: 1, color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
child: TextFormField(
controller: emailMobileController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Email / Mobile Number ",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
),
validator: (value) {
if (value == null ||
value.trim().isEmpty) {
return "Please enter email or mobile number";
}
String input = value.trim();
// Reject all spaces
if (input.contains(' ')) {
return "No spaces allowed";
}
final emailRegex =
RegExp(r'^[^@]+@[^@]+\.[^@]+$');
final mobileRegex =
RegExp(r'^[0-9]{10}$');
bool isEmailFormat =
emailRegex.hasMatch(input);
bool isMobileFormat =
mobileRegex.hasMatch(input);
// ---------------------------
// 🛑 MOBILE VALIDATION
// ---------------------------
if (RegExp(r'^[0-9]+$').hasMatch(input)) {
if (input.length != 10) {
return "Mobile number must be exactly 10 digits";
}
}
// ---------------------------
// 🛑 EMAIL VALIDATION
// ---------------------------
// Reject anything that has '@' but is NOT a valid email format
if (input.contains('@') &&
!isEmailFormat) {
return "Enter a valid email address";
}
// Reject email with extra digits at the end
if (input.contains('@') &&
RegExp(r'\d+$').hasMatch(input)) {
return "Email cannot contain extra numbers";
}
// Reject email+mobile combination
if (input.contains('@') &&
RegExp(r'\d{10}$').hasMatch(input)) {
return "Enter only email OR mobile number";
}
// ---------------------------
// 🛑 MIXED CONTENT (letters + digits but NOT email)
// ---------------------------
bool hasLetters =
RegExp(r'[A-Za-z]').hasMatch(input);
bool hasDigits =
RegExp(r'[0-9]').hasMatch(input);
if ((hasLetters && hasDigits) &&
!input.contains('@')) {
return "Enter only email OR 10-digit mobile number";
}
// ---------------------------
// 🟢 FINAL CHECK
// ---------------------------
if (!isEmailFormat && !isMobileFormat) {
return "Enter a valid email or 10-digit mobile number";
}
return null;
}),
),
SizedBox(height: 20),
Container(
margin: EdgeInsets.symmetric(horizontal: 150),
child: SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF00989E),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: _isLoading
? null
: verifyMobileAndEmailNumber,
child: _isLoading
? const CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(
Color(0xFF00989E),
),
)
: Text(
"Login with Email / Mobile OTP",
style: GoogleFonts.poppins(
color: const Color(0xFFFFFFFF),
),
),
),
),
),
SizedBox(height: 20),
Container(
width: double.infinity,
alignment: Alignment.bottomCenter,
padding:
const EdgeInsets.symmetric(vertical: 8),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: 'By continuing, you agree with our ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
children: [
TextSpan(
text: 'privacy policy ',
style: GoogleFonts.poppins(
color: const Color(0xFFE26828),
fontSize: 9,
// decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
final url = Uri.parse(
'https://nhanceindia.in/privacy-policy/');
if (await canLaunchUrl(url)) {
await launchUrl(url,
mode: LaunchMode
.externalApplication);
}
},
),
TextSpan(
text: 'and ',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 9,
),
),
TextSpan(
text: 'terms of use',
style: GoogleFonts.poppins(
color: const Color(0xFFE26828),
fontSize: 9,
// decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
final url = Uri.parse(
'https://nhanceindia.in/privacy-policy/');
if (await canLaunchUrl(url)) {
await launchUrl(url,
mode: LaunchMode
.externalApplication);
}
},
),
],
),
),
),
// Container(
// width: double
// .infinity, // Make the footer full width
// child: Container(
// alignment: Alignment.bottomCenter,
// padding:
// EdgeInsets.symmetric(vertical: 8),
// child: RichText(
// textAlign: TextAlign.center,
// text: TextSpan(
// text:
// 'By continuing, you agree with our ',
// style: GoogleFonts.poppins(
// color: Colors.black,
// fontSize: 9,
// ),
// children: <TextSpan>[
// TextSpan(
// text: 'privacy policy ',
// style: GoogleFonts.poppins(
// color: Color(0xFFE26828),
// fontSize: 9,
// ),
// ),
// TextSpan(
// text: 'and ',
// style: GoogleFonts.poppins(
// color: Colors.black,
// fontSize: 9,
// ),
// ),
// TextSpan(
// text: 'terms of use',
// style: GoogleFonts.poppins(
// color: Color(0xFFE26828),
// fontSize: 9,
// ),
// ),
// ],
// ),
// ),
// ),
// ),
],
),
),
),
),
],
),
),
)));
}
}

View File

@ -1,930 +0,0 @@
// import 'package:firebase_auth/firebase_auth.dart';
// import 'package:flutter/material.dart';
// import 'package:google_fonts/google_fonts.dart';
// import 'package:nhancepolicy/customAppBar/toastHelper.dart';
// import 'package:nhancepolicy/models/environment.dart';
// import 'package:nhancepolicy/service/api_service.dart';
// import 'package:nhancepolicy/service/token_storage_service.dart';
// import 'package:pinput/pinput.dart';
// import 'dart:async';
// import 'package:flutter/gestures.dart';
// import 'package:http/http.dart' as http;
// import 'dart:convert';
// import 'dart:io';
// import 'package:shared_preferences/shared_preferences.dart';
// import 'package:nhancepolicy/responsive.dart';
// import 'package:jwt_decode/jwt_decode.dart';
//
// import 'branch/branch_selection_page.dart';
//
// class MyHrVerify extends StatefulWidget {
// final String verificationId;
// final String mobileNumber;
// final int? resendToken;
// final Function(String, int?) onResendCode;
//
// const MyHrVerify({
// Key? key,
// required this.verificationId,
// required this.mobileNumber,
// required this.resendToken,
// required this.onResendCode,
// }) : super(key: key);
//
// @override
// State<MyHrVerify> createState() => _MyVerifyState();
// }
//
// class _MyVerifyState extends State<MyHrVerify> {
// TextEditingController countryController = TextEditingController();
// late ApiService apiService;
// TextEditingController _otpController = TextEditingController();
// final _formKey = GlobalKey<FormState>();
// late Timer _timer;
// int _secondsRemaining = 60;
// bool _isTimerRunning = false;
//
// dynamic enrollmentEmpClientBranchId;
// dynamic enrollmentEmpCodeString;
// dynamic enrollmentEmpPrimaryId;
// dynamic enrollmentGpaEmpName;
// dynamic enrollmentClient_id;
// dynamic enrollmentHrId;
// List<dynamic> enrollmentAllowed_modules = [];
// dynamic enrollmentEmp_status;
//
// dynamic empClientBranchId;
// dynamic empCodeString;
// dynamic empPrimaryId;
// dynamic gpaEmpName;
// dynamic client_id;
// dynamic empHrId;
// List<dynamic> empAllowed_modules = [];
// dynamic emp_status;
//
// dynamic _token;
// dynamic _postToken;
// dynamic clientName;
// dynamic clientLogo;
//
// dynamic fCMToken;
// late String verificationId;
// late String mobileNumber;
// final FirebaseAuth _auth = FirebaseAuth.instance;
// bool _isLoading = false;
// dynamic empMobileNo;
// dynamic empEmailid;
//
// late String _verificationId;
//
// @override
// void initState() {
// super.initState();
// countryController.text = "+91";
// apiService = ApiService(context);
// // checkTokenAvailability();
// verificationId = widget.verificationId;
// mobileNumber = widget.mobileNumber;
// logDebug('Received verificationId: $verificationId');
// logDebug('Received mobileNumber: $mobileNumber');
// if (verificationId.isEmpty) {
// // Handle the case where verificationId is not provided
// Navigator.pop(context);
// }
// startTimer();
// // Equivalent to componentDidMount
// _auth.authStateChanges().listen((User? user) {
// if (user != null) {
// // If user is already signed in, log them out
// logOutFirebase();
// }
// });
// }
//
// void logOutFirebase() async {
// await _auth.signOut();
// logDebug('User signed out');
// }
//
// @override
// void dispose() {
// _timer.cancel();
// super.dispose();
// }
//
// void startTimer() {
// _isTimerRunning = true;
// _timer = Timer.periodic(Duration(seconds: 1), (Timer timer) {
// setState(() {
// if (_secondsRemaining > 0) {
// _secondsRemaining--;
// } else {
// _isTimerRunning = false;
// _timer.cancel();
// }
// });
// });
// }
//
// void generateToken(bool otpVerifyStatus) async {
// try {
// final response = await http.post(
// Uri.parse(Environment.apiUrl + 'getVerifiedHrData'),
// body: json.encode(
// {'mobile_no': mobileNumber, 'otp_verification': otpVerifyStatus}),
// headers: {
// HttpHeaders.contentTypeHeader: 'application/json',
// },
// );
//
// if (response.statusCode == 200) {
// setState(() {
// _isLoading = false;
// });
// Map<String, dynamic> data = json.decode(response.body);
//
// logDebug('Response data: $data');
//
// // Extract enrollment data
// List<dynamic> preEnrollmentData = data['data'] ?? [];
// Map<String, dynamic> postEnrollment = data['post_enrollment'] ?? {};
// List<dynamic> postEnrollmentData = postEnrollment['data'] ?? [];
//
// // Save to global storage
// final tokenStorage = TokenStorageService();
// await tokenStorage.saveEnrollmentData(
// preEnrollmentData,
// postEnrollmentData,
// );
//
// // Navigate to branch selection
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (context) => BranchSelectionPage(),
// ),
// );
//
// // logDebug('data: $data');
// // _token = data['data'];
// // String status = data['status'];
// //
// // // Directly access the post_enrollment data
// // Map<String, dynamic> post = data['post_enrollment'];
// // logDebug('post: $post');
// //
// // _postToken = post['data'];
// // String postStatus = post['status'];
// //
// // if (postStatus == 'success') {
// // setState(() {
// // _isLoading = false;
// // });
// // postSuccessData(post, data);
// // } else if (status == 'success') {
// // setState(() {
// // _isLoading = false;
// // });
// // enrollmentSuccessData(data);
// // } else {
// // setState(() {
// // _isLoading = false;
// // });
// // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again');
// // // Show a Snackbar if the OTP is invalid
// // logDebug('Invalid OTP. Please try again');
// // }
// } else {
// setState(() {
// _isLoading = false;
// });
// ToastHelper.showWarningToast(context, 'Something went wrong');
// throw Exception('Failed to verify OTP');
// }
// } catch (e) {
// setState(() {
// _isLoading = false;
// });
// logDebug('Error: $e');
// ToastHelper.showWarningToast(context, 'Something went wrong');
// // Show a Snackbar if there's an error while verifying OTP
// logDebug('Failed to verify OTP. Please try again.');
// }
// }
//
// void postSuccessData(post, data) async {
// String status = data['status'];
// String postStatus = post['status'];
//
// if (postStatus == 'success') {
// final SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs.setString('_postToken', post['data']);
//
// // Decode the JWT token received from the API response
// Map<String, dynamic>? decodedToken = Jwt.parseJwt(post['data']);
// logDebug('postdecodedToken : $decodedToken');
// empClientBranchId = decodedToken['ref_id'];
// prefs.setString('empClientBranchId', empClientBranchId);
// empCodeString = decodedToken['emp_code'].toString();
// prefs.setString('empCode', empCodeString);
// empPrimaryId = decodedToken['id'];
// prefs.setString('empPrimaryId', empPrimaryId);
// gpaEmpName = decodedToken['name'].toString();
// prefs.setString('gpaEmpName', gpaEmpName);
// client_id = decodedToken['client_id'];
// prefs.setString('client_id', client_id);
// empHrId = decodedToken['id'];
// prefs.setString('empHrId', empHrId);
// empAllowed_modules = decodedToken['allowed_modules'];
// prefs.setString('empAllowed_modules', jsonEncode(empAllowed_modules));
// emp_status = decodedToken['emp_status'];
// prefs.setString('emp_status', emp_status);
// }
//
// if (status == 'success') {
// final SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs.setString('enrollToken', data['data']);
//
// // Decode the JWT token received from the API response
// Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
// logDebug('decodedToken : $decodedToken');
// enrollmentEmpClientBranchId = decodedToken['ref_id'];
// prefs.setString(
// 'enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
// enrollmentEmpCodeString = decodedToken['emp_code'].toString();
// prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
// enrollmentEmpPrimaryId = decodedToken['id'];
// prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
// enrollmentGpaEmpName = decodedToken['name'].toString();
// prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
// enrollmentClient_id = decodedToken['client_id'];
// prefs.setString('enrollmentClient_id', enrollmentClient_id);
//
// enrollmentHrId = decodedToken['id'];
// prefs.setString('enrollmentHrId', enrollmentHrId);
// enrollmentAllowed_modules = decodedToken['allowed_modules'];
// prefs.setString(
// 'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
// // enrollmentEmp_status = decodedToken['emp_status'];
// // prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
// }
//
// final SharedPreferences prefs = await SharedPreferences.getInstance();
// final _postToken = prefs.getString('_postToken');
//
// if (_postToken != null && _postToken.isNotEmpty) {
// ToastHelper.showSuccessToast(context, 'Successfully Login');
// // if (emp_status == 'enrolled' || emp_status == 'active') {
// Navigator.pushReplacementNamed(context, 'hrDashboard');
// // } else {
// // Navigator.pushReplacementNamed(context, 'empDetails');
// // }
// }
// }
//
// void enrollmentSuccessData(data) async {
// final SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs.setString('enrollToken', data['data']);
//
// // Decode the JWT token received from the API response
// Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
// logDebug('enrolldecodedToken : $decodedToken');
// enrollmentEmpClientBranchId = decodedToken['ref_id'];
// prefs.setString('enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
// enrollmentEmpCodeString = decodedToken['emp_code'].toString();
// prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
// enrollmentEmpPrimaryId = decodedToken['id'];
// prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
// enrollmentGpaEmpName = decodedToken['name'].toString();
// prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
// enrollmentClient_id = decodedToken['client_id'];
// prefs.setString('enrollmentClient_id', enrollmentClient_id);
//
// enrollmentHrId = decodedToken['id'];
// prefs.setString('enrollmentHrId', enrollmentHrId);
// enrollmentAllowed_modules = decodedToken['allowed_modules'];
// prefs.setString(
// 'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
// // enrollmentEmp_status = decodedToken['emp_status'];
// // prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
//
// logDebug('Successfully Login');
//
// // Redirect to another page
// final enrollToken = prefs.getString('enrollToken');
//
// if (enrollToken != null && enrollToken.isNotEmpty) {
// ToastHelper.showSuccessToast(context, 'Successfully Login');
// // if (emp_status == 'enrolled' || emp_status == 'active') {
// // Navigator.pushReplacementNamed(context, 'home');
// // } else {
// Navigator.pushReplacementNamed(context, 'hrDashboard');
// // }
// }
// }
//
// void verifyOTP(String otp) async {
// try {
// // setState(() {
// // _isLoading = true;
// // });
// // Later, in verify screen:
// SharedPreferences prefs = await SharedPreferences.getInstance();
// dynamic verificationId = prefs.getString('verificationId');
//
// final credential = PhoneAuthProvider.credential(
// verificationId: verificationId, // Make sure this is up-to-date
// smsCode: otp,
// );
//
// // final userCredential = await FirebaseAuth.instance.signInWithCredential(credential);
// // await FirebaseAuth.instance.signInWithCredential(credential);
//
// // if (userCredential.user != null) {
// bool otpVerifyStatus = true;
// generateToken(otpVerifyStatus);
// // } else {
// // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again.');
// // }
//
// // await FirebaseAuth.instance.signInWithCredential(credential).then((userCredential) async {
// // if (userCredential.user != null) {
// // bool otpVerifyStatus = true;
// // generateToken(otpVerifyStatus);
// // } else {
// // setState(() {
// // _isLoading = false;
// // });
// // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again');
// // }
// // });
// } on FirebaseAuthException catch (e) {
// if (e.code == 'code-expired') {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text('OTP expired. Please request a new one.'),
// duration: Duration(seconds: 2),
// ),
// );
// ToastHelper.showErrorToast(
// context, 'OTP expired. Please request a new one.');
// } else if (e.code == 'invalid-verification-code') {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text('Invalid OTP entered. Please try again.'),
// duration: Duration(seconds: 2),
// ),
// );
// ToastHelper.showErrorToast(
// context, 'Invalid OTP entered. Please try again.');
// } else if (e.code == 'session-expired') {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text('Session expired. Try restarting the verification.'),
// duration: Duration(seconds: 2),
// ),
// );
// ToastHelper.showErrorToast(
// context, 'Session expired. Try restarting the verification.');
// } else {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text('Error: ${e.message}'),
// duration: Duration(seconds: 2),
// ),
// );
// ToastHelper.showErrorToast(context, 'Error: ${e.message}');
// }
// } catch (e) {
// // setState(() {
// // _isLoading = false;
// // });
// logDebug('Error: $e');
// ToastHelper.showErrorToast(
// context, 'Failed to verify OTP. Please try again.');
// }
// }
//
// void _resendOTP() {
// setState(() {
// _secondsRemaining = 60;
// _isTimerRunning = true;
// });
// startTimer();
// widget.onResendCode(widget.mobileNumber, widget.resendToken);
// }
//
// @override
// Widget build(BuildContext context) {
// // Retrieve the passed mobile number value
// // final String mobileNumber =
// // ModalRoute.of(context)!.settings.arguments as String;
// Size _size = MediaQuery.of(context).size;
// EdgeInsets marginInsets = EdgeInsets.zero;
// if (Responsive.isDesktop(context)) {
// marginInsets = const EdgeInsets.only(
// left: 0,
// right: 0,
// bottom: 0,
// top: 0,
// );
// } else if (Responsive.isMobile(context)) {
// marginInsets = const EdgeInsets.only(
// left: 25, // Example value for mobile
// right: 25, // Example value for mobile
// bottom: 0, // Example value for mobile
// top: 0, // Example value for mobile
// );
// } else if (Responsive.isTablet(context)) {
// marginInsets = const EdgeInsets.only(
// left: 25, // Example value for mobile
// right: 25, // Example value for mobile
// bottom: 0, // Example value for mobile
// top: 0, // Example value for mobile
// );
// }
// final defaultPinTheme = PinTheme(
// width: 56,
// height: 56,
// textStyle: TextStyle(
// fontSize: 20,
// color: Color.fromRGBO(30, 60, 87, 1),
// fontWeight: FontWeight.w600,
// ),
// decoration: BoxDecoration(
// border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)),
// borderRadius: BorderRadius.circular(20),
// ),
// );
//
// final focusedPinTheme = defaultPinTheme.copyDecorationWith(
// border: Border.all(color: Color.fromRGBO(114, 178, 238, 1)),
// borderRadius: BorderRadius.circular(8),
// );
//
// final submittedPinTheme = defaultPinTheme.copyWith(
// decoration: defaultPinTheme.decoration?.copyWith(
// color: Color.fromRGBO(234, 239, 243, 1),
// ),
// );
//
// return Scaffold(
// body: SingleChildScrollView(
// keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
// child: Container(
// height: _size.height,
// color: Colors.white,
// child: Stack(
// children: [
// Visibility(
// visible: _size.width <= 1100,
// child: ClipRRect(
// borderRadius: BorderRadius.only(
// bottomLeft: Radius.circular(30),
// bottomRight: Radius.circular(30),
// ),
// child: Container(
// height: _size.height / 3,
// width: double.infinity,
// color: Color(0xFF00989E),
// child: Stack(
// children: [
// Column(
// children: [
// SizedBox(
// height: _size.height /
// 6.4), // Adjust the spacing between the rows
// Row(
// mainAxisAlignment: MainAxisAlignment
// .center, // Align to the center
// children: [
// Expanded(
// flex: Responsive.isDesktop(context) ? 10 : 12,
// child: Align(
// alignment: Responsive.isDesktop(context)
// ? Alignment.centerLeft
// : Alignment.bottomCenter,
// child: Image.asset(
// _size.width <= 1100
// ? 'assets/mobileViewLogo.png'
// : 'assets/Nhance-Logo-Final 1.png',
// width: 150,
// height: 150,
// ),
// ),
// ),
// if (!Responsive.isMobile(context) &&
// !Responsive.isTablet(context))
// Expanded(
// flex: 2,
// child: MouseRegion(
// cursor: SystemMouseCursors.click,
// child: GestureDetector(
// onTap: () {
// // Add your navigation logic here
// // For example, you can use Navigator.push to navigate to another page
// Navigator.pushNamed(
// context, 'hrLogin');
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment
// .end, // Align to the end (right)
// children: [
// Text(
// 'HR Login',
// style: TextStyle(
// color: Color(
// 0xFF000000), // Text color
// // Add other text styles as needed
// ),
// ),
// SizedBox(width: 5),
// Icon(
// Icons
// .east, // Icon for customer login
// color: Colors
// .black, // Adjust color as needed
// ),
// ],
// ),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ],
// ),
// ),
// ),
// ),
// Container(
// margin: marginInsets,
// alignment: Alignment.bottomCenter,
// child: SingleChildScrollView(
// child: Form(
// key: _formKey,
// child: Column(
// children: [
// Row(
// children: [
// if (_size.width > 1100)
// Expanded(
// flex: _size.width < 1100 ? 6 : 12,
// child: LayoutBuilder(
// builder: (BuildContext context,
// BoxConstraints constraints) {
// if (constraints.maxWidth > 600) {
// return Image.asset(
// 'assets/hrLogin.jpg',
// height: _size.height,
// fit: BoxFit.cover,
// );
// } else {
// return SizedBox();
// }
// },
// ),
// ),
// Expanded(
// flex: _size.width < 1100 ? 6 : 12,
// child: Container(
// margin: _size.width > 1100
// ? EdgeInsets.only(left: 20, right: 20)
// : EdgeInsets.only(left: 0, right: 0),
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// if (!Responsive.isMobile(context) &&
// !Responsive.isTablet(context))
// Row(
// children: [
// Expanded(
// flex: 8,
// child: Align(
// alignment: Alignment.center, // Align to the start
// child: _size.width <= 1100
// ? Image.asset(
// 'assets/Nhance-Logo-Final-mobile.png',
// width: 150,
// height: 70,
// )
// : _size.width > 1100
// ? Image.asset(
// 'assets/Nhance-Logo-Final 1.png',
// width: 150,
// height: 70,
// )
// : Image.asset(
// 'assets/Nhance-Logo-Final 1.png',
// width: 150,
// height: 70,
// ),
// )),
// ],
// ),
// SizedBox(height: 80),
// Container(
// margin: Responsive.isDesktop(context)
// ? EdgeInsets.symmetric(
// horizontal: 150)
// : EdgeInsets.symmetric(horizontal: 0),
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// Text(
// "Welcome to Nhance",
// style: GoogleFonts.poppins(
// fontSize: 16,
// fontWeight: FontWeight.bold,
// ),
// ),
// ],
// ),
// ),
// SizedBox(height: 10),
// Container(
// margin: Responsive.isDesktop(context)
// ? EdgeInsets.symmetric(
// horizontal: 150)
// : EdgeInsets.symmetric(horizontal: 0),
// child: RichText(
// textAlign: TextAlign.center,
// text: TextSpan(
// text:
// "Please enter the one-time usage code sent to your mobile number $mobileNumber",
// style: GoogleFonts.poppins(
// fontSize: 12,
// height: 1.5,
// color: Color(0xFF000000)),
// children: [
// TextSpan(
// text: " (Change Number)",
// style: GoogleFonts.poppins(
// fontSize: 12,
// color: Color(
// 0xFFE26728)), // Change color as desired
// recognizer: TapGestureRecognizer()
// ..onTap = () {
// // Navigate to the page where the user can change the phone number
// Navigator.pushNamed(
// context, 'hrLogin');
// },
// ),
// ],
// ),
// ),
// ),
// SizedBox(height: 15),
// Container(
// margin: Responsive.isDesktop(context)
// ? EdgeInsets.symmetric(
// horizontal: 150)
// : EdgeInsets.symmetric(horizontal: 0),
// child: Pinput(
// length: 6,
// // defaultPinTheme: defaultPinTheme,
// // focusedPinTheme: focusedPinTheme,
// // submittedPinTheme: submittedPinTheme,
// showCursor: true,
// controller: _otpController,
// ),
// ),
// SizedBox(height: 10),
// Container(
// margin: Responsive.isDesktop(context)
// ? EdgeInsets.symmetric(
// horizontal: 150)
// : EdgeInsets.symmetric(horizontal: 0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment
// .end, // Align text to the right
// children: [
// _isTimerRunning
// ? Text(
// "Resend OTP in $_secondsRemaining seconds",
// style: GoogleFonts.poppins(
// color: Colors.black),
// )
// : InkWell(
// onTap: () {
// _resendOTP();
// },
// child: Text(
// "Resend OTP",
// style: GoogleFonts.poppins(
// color: Colors.blue),
// ),
// ),
// ],
// ),
// ),
// SizedBox(height: 10),
// Container(
// margin: Responsive.isDesktop(context)
// ? EdgeInsets.symmetric(
// horizontal: 150)
// : EdgeInsets.symmetric(horizontal: 0),
// child: SizedBox(
// width: double.infinity,
// height: 45,
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF00989E),
// shape: RoundedRectangleBorder(
// borderRadius:
// BorderRadius.circular(10),
// ),
// ),
// onPressed: () {
// if (_formKey.currentState!
// .validate()) {
// _formKey.currentState!
// .save(); // Save form fields before calling verifyOTP
// verifyOTP(_otpController.text);
// }
// },
// child: Text(
// "Submit",
// style: GoogleFonts.poppins(
// color: Color(0xFFFFFFFF)),
// ),
// ),
// ),
// ),
// SizedBox(
// height: _size.width <= 1100 ? 0 : 0,
// ),
// // _size.width > 1100
// // ? Container(
// // margin: EdgeInsets.symmetric(
// // horizontal: 150),
// // child: Column(
// // children: [
// // SizedBox(height: 30),
// // Text(
// // "Benefits of Login",
// // style: GoogleFonts.poppins(
// // fontSize: 20,
// // fontWeight: FontWeight.bold,
// // ),
// // ),
// // SizedBox(height: 15),
// // ],
// // ))
// // : SizedBox(),
// // _size.width > 1100
// // ? Container(
// // margin: EdgeInsets.symmetric(
// // horizontal: 150),
// // child: Row(
// // mainAxisAlignment:
// // MainAxisAlignment.center,
// // children: [
// // Expanded(
// // flex: 6,
// // child: Container(
// // padding:
// // EdgeInsets.symmetric(
// // vertical: 8),
// // child: Row(
// // mainAxisAlignment:
// // MainAxisAlignment
// // .center,
// // children: [
// // Expanded(
// // child: Container(
// // padding: EdgeInsets
// // .symmetric(
// // vertical:
// // 12),
// // decoration:
// // BoxDecoration(
// // border: Border(
// // right:
// // BorderSide(
// // width: 1,
// // color: Colors
// // .black,
// // ),
// // ),
// // ),
// // child: Column(
// // children: [
// // Icon(
// // Icons
// // .policy,
// // color: Color(
// // 0xFFE26728)),
// // SizedBox(
// // height: 10),
// // Text(
// // "View Policy"),
// // ],
// // ),
// // ),
// // ),
// // Expanded(
// // child: Container(
// // padding: EdgeInsets
// // .symmetric(
// // vertical:
// // 12),
// // child: Column(
// // children: [
// // Icon(Icons.edit,
// // color: Color(
// // 0xFFE26728)),
// // SizedBox(
// // height: 10),
// // Text(
// // "Manage Claims"),
// // ],
// // ),
// // ),
// // ),
// // ],
// // ),
// // ),
// // ),
// // ],
// // ),
// // )
// // : SizedBox(
// // height:
// // Responsive.isDesktop(context)
// // ? _size.height * 0.1
// // : _size.height * 0.2,
// // ),
// SizedBox(
// height: Responsive.isDesktop(context)
// ? _size.height * 0.3
// : _size.height * 0.2,
// ),
// // SizedBox(
// // height: _size.height * 0.1,
// // ),
// Align(
// alignment: Alignment.bottomCenter,
// child: Container(
// width: double
// .infinity, // Make the footer full width
// child: Container(
// alignment: Alignment.bottomCenter,
// padding:
// EdgeInsets.symmetric(vertical: 8),
// child: RichText(
// textAlign: TextAlign.center,
// text: TextSpan(
// text:
// 'By continuing, you agree with our ',
// style: GoogleFonts.poppins(
// color: Colors.black,
// fontSize: 9,
// ),
// children: <TextSpan>[
// TextSpan(
// text: 'privacy policy ',
// style: GoogleFonts.poppins(
// color: Color(0xFF00989E),
// fontSize: 9,
// ),
// ),
// TextSpan(
// text: 'and ',
// style: GoogleFonts.poppins(
// color: Colors.black,
// fontSize: 9,
// ),
// ),
// TextSpan(
// text: 'terms of use',
// style: GoogleFonts.poppins(
// color: Color(0xFF00989E),
// fontSize: 9,
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ),
// ),
// ),
// ],
// )),
// ));
// }
// }

View File

@ -1,111 +0,0 @@
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
import 'package:nhancepolicy/config/environment.dart';
String _formatMessage(
String message, {
String? className,
String? methodName,
}) {
final timestamp = DateTime.now().toIso8601String();
final contextParts = <String>[];
if (className != null && className.isNotEmpty) {
contextParts.add(className);
}
if (methodName != null && methodName.isNotEmpty) {
contextParts.add(methodName);
}
final context = contextParts.isEmpty ? '' : '[${contextParts.join('.')}] ';
return '[$timestamp] $context$message';
}
void _log(
String level,
String message, {
String tag = 'APP',
Object? error,
StackTrace? stackTrace,
String? className,
String? methodName,
}) {
final shouldLog = kDebugMode || Environment.flavor == Flavor.dev;
if (!shouldLog) {
return;
}
final formatted =
_formatMessage(message, className: className, methodName: methodName);
final channel = '$tag:$level';
developer.log(
formatted,
name: channel,
error: error,
stackTrace: stackTrace,
);
debugPrint('[$channel] $formatted');
}
void logDebug(
Object? message, {
String tag = 'APP',
String? className,
String? methodName,
}) {
_log(
'DEBUG',
'${message ?? 'null'}',
tag: tag,
className: className,
methodName: methodName,
);
}
void logInfo(
Object? message, {
String tag = 'APP',
String? className,
String? methodName,
}) {
_log(
'INFO',
'${message ?? 'null'}',
tag: tag,
className: className,
methodName: methodName,
);
}
void logWarning(
Object? message, {
String tag = 'APP',
String? className,
String? methodName,
}) {
_log(
'WARN',
'${message ?? 'null'}',
tag: tag,
className: className,
methodName: methodName,
);
}
void logError(
Object? message, {
String tag = 'APP',
Object? error,
StackTrace? stackTrace,
String? className,
String? methodName,
}) {
_log(
'ERROR',
'${message ?? 'null'}',
tag: tag,
error: error,
stackTrace: stackTrace,
className: className,
methodName: methodName,
);
}

Some files were not shown because too many files have changed in this diff Show More