diff --git a/App.tsx b/App.tsx
index ae836f9e6..89bf65594 100644
--- a/App.tsx
+++ b/App.tsx
@@ -8,7 +8,6 @@ import ThemeManager from './src/custom-theme/ThemeManager';
import BaseProps from './src/types/BaseProps';
import { ThemeType } from './src/types/Theme';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
-import RNBootSplash from 'react-native-bootsplash';
export interface Props extends BaseProps {}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 344a64abb..af289b527 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,72 +1,100 @@
+ package="com.emobility">
-
-
-
+
+
+
+ android:name=".MainApplication"
+ android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:allowBackup="false"
+ android:theme="@style/BootTheme">
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/eMobility.xcodeproj/project.pbxproj b/ios/eMobility.xcodeproj/project.pbxproj
index 7157fecda..c260e7e97 100644
--- a/ios/eMobility.xcodeproj/project.pbxproj
+++ b/ios/eMobility.xcodeproj/project.pbxproj
@@ -998,10 +998,12 @@
CODE_SIGN_ENTITLEMENTS = eMobility/eMobility.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 8;
+ CURRENT_PROJECT_VERSION = 3;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = SG7N28T3A2;
ENABLE_BITCODE = NO;
+ EXCLUDED_ARCHS = "";
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = eMobility.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/Frameworks @executable_path/Frameworks";
@@ -1045,7 +1047,7 @@
"\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-splash-screen\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/yoga\"",
);
- MARKETING_VERSION = 2.7.1;
+ MARKETING_VERSION = 2.7.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1073,9 +1075,11 @@
CODE_SIGN_ENTITLEMENTS = eMobility/eMobility.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 8;
+ CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = SG7N28T3A2;
ENABLE_BITCODE = NO;
+ EXCLUDED_ARCHS = "";
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
INFOPLIST_FILE = eMobility.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/Frameworks @executable_path/Frameworks";
@@ -1119,7 +1123,7 @@
"\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-splash-screen\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/yoga\"",
);
- MARKETING_VERSION = 2.7.1;
+ MARKETING_VERSION = 2.7.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
diff --git a/ios/eMobility/AppDelegate.m b/ios/eMobility/AppDelegate.m
index ede818541..f5254e048 100644
--- a/ios/eMobility/AppDelegate.m
+++ b/ios/eMobility/AppDelegate.m
@@ -143,13 +143,19 @@ - (UIInterfaceOrientationMask)application:(UIApplication *)application supported
return [Orientation getOrientation];
}
-- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options
+- (BOOL)application:(UIApplication *)application
+ openURL:(NSURL *)url
+ options:(NSDictionary *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
-- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
+
+- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
+ restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler
{
- return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
+ return [RCTLinkingManager application:application
+ continueUserActivity:userActivity
+ restorationHandler:restorationHandler];
}
@end
diff --git a/ios/eMobility/Info.plist b/ios/eMobility/Info.plist
index 4a8e76201..2abd0d13e 100644
--- a/ios/eMobility/Info.plist
+++ b/ios/eMobility/Info.plist
@@ -1,114 +1,126 @@
-
- LSApplicationQueriesSchemes
-
- comgooglemaps
- citymapper
- uber
- lyft
- transit
- truckmap
- waze
- yandexnavi
- moovit
- yandextaxi
- yandexmaps
- kakaomap
- szn-mapy
- mapsme
-
- CFBundleDevelopmentRegion
- en
- CFBundleDisplayName
- $(BUNDLE_NAME)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(MARKETING_VERSION)
- CFBundleSignature
- ????
- CFBundleURLTypes
-
-
- CFBundleTypeRole
- Editor
- CFBundleURLName
- eMobility
- CFBundleURLSchemes
-
- eMobility
-
-
-
- CFBundleVersion
- $(CURRENT_PROJECT_VERSION)
- LSRequiresIPhoneOS
-
- NSAppTransportSecurity
- NSExceptionDomains
+ LSApplicationQueriesSchemes
+
+ comgooglemaps
+ citymapper
+ uber
+ lyft
+ transit
+ truckmap
+ waze
+ yandexnavi
+ moovit
+ yandextaxi
+ yandexmaps
+ kakaomap
+ szn-mapy
+ mapsme
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ $(BUNDLE_NAME)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleSignature
+ ????
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ eMobility
+ CFBundleURLSchemes
+
+ eMobility
+
+
+
+ CFBundleURLSchemes
+
+ https
+
+
+
+ CFBundleURLSchemes
+
+ http
+
+
+
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ LSRequiresIPhoneOS
+
+ NSAppTransportSecurity
- localhost
+ NSExceptionDomains
- NSExceptionAllowsInsecureHTTPLoads
-
+ localhost
+
+ NSExceptionAllowsInsecureHTTPLoads
+
+
+ NSLocationWhenInUseUsageDescription
+ Discover Charging Stations
+ UIAppFonts
+
+ AntDesign.ttf
+ Entypo.ttf
+ EvilIcons.ttf
+ Feather.ttf
+ FontAwesome.ttf
+ FontAwesome5_Brands.ttf
+ FontAwesome5_Regular.ttf
+ FontAwesome5_Solid.ttf
+ Foundation.ttf
+ Ionicons.ttf
+ MaterialCommunityIcons.ttf
+ MaterialIcons.ttf
+ Octicons.ttf
+ Roboto_medium.ttf
+ Roboto.ttf
+ rubicon-icon-font.ttf
+ SimpleLineIcons.ttf
+ Zocial.ttf
+ Fontisto.ttf
+
+ UIBackgroundModes
+
+ remote-notification
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortraitUpsideDown
+
+ NSCameraUsageDescription
+ ${PRODUCT_NAME} needs access to the camera to scan the charging station's QR Code to register the organization or start a transaction
+ UIViewControllerBasedStatusBarAppearance
+
- NSLocationWhenInUseUsageDescription
- Discover Charging Stations
- UIAppFonts
-
- AntDesign.ttf
- Entypo.ttf
- EvilIcons.ttf
- Feather.ttf
- FontAwesome.ttf
- FontAwesome5_Brands.ttf
- FontAwesome5_Regular.ttf
- FontAwesome5_Solid.ttf
- Foundation.ttf
- Ionicons.ttf
- MaterialCommunityIcons.ttf
- MaterialIcons.ttf
- Octicons.ttf
- Roboto_medium.ttf
- Roboto.ttf
- rubicon-icon-font.ttf
- SimpleLineIcons.ttf
- Zocial.ttf
- Fontisto.ttf
-
- UIBackgroundModes
-
- remote-notification
-
- UILaunchStoryboardName
- LaunchScreen
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
- UIInterfaceOrientationPortraitUpsideDown
-
- NSCameraUsageDescription
- ${PRODUCT_NAME} needs access to the camera to scan the charging station's QR Code to register the organization or start a transaction
- UIViewControllerBasedStatusBarAppearance
-
-
-
+
\ No newline at end of file
diff --git a/ios/eMobility/eMobility.entitlements b/ios/eMobility/eMobility.entitlements
index 903def2af..50ab07a9c 100644
--- a/ios/eMobility/eMobility.entitlements
+++ b/ios/eMobility/eMobility.entitlements
@@ -4,5 +4,20 @@
aps-environment
development
+ com.apple.developer.associated-domains
+
+ applinks:*.e-mobility-group.com
+ applinks:*.e-mobility-group.org
+ applinks:*.e-mobility-group.eu
+ applinks:*.e-mobility-group.fr
+ applinks:*.qa-e-mobility-group.com
+ applinks:*.qa-e-mobility-group.org
+ applinks:*.qa-e-mobility-group.eu
+ applinks:*.qa-e-mobility-group.fr
+ applinks:*.e-mobility-labs.com
+ applinks:*.e-mobility-labs.org
+ applinks:*.e-mobility-labs.eu
+ applinks:*.e-mobility-labs.fr
+
diff --git a/ios/eMobility/eMobilityRelease.entitlements b/ios/eMobility/eMobilityRelease.entitlements
new file mode 100644
index 000000000..50ab07a9c
--- /dev/null
+++ b/ios/eMobility/eMobilityRelease.entitlements
@@ -0,0 +1,23 @@
+
+
+
+
+ aps-environment
+ development
+ com.apple.developer.associated-domains
+
+ applinks:*.e-mobility-group.com
+ applinks:*.e-mobility-group.org
+ applinks:*.e-mobility-group.eu
+ applinks:*.e-mobility-group.fr
+ applinks:*.qa-e-mobility-group.com
+ applinks:*.qa-e-mobility-group.org
+ applinks:*.qa-e-mobility-group.eu
+ applinks:*.qa-e-mobility-group.fr
+ applinks:*.e-mobility-labs.com
+ applinks:*.e-mobility-labs.org
+ applinks:*.e-mobility-labs.eu
+ applinks:*.e-mobility-labs.fr
+
+
+
diff --git a/package.json b/package.json
index 9a6ace1de..f3b9e7f14 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,12 @@
"build:prepare": "if [ ! -f android/app/google-services.json ]; then cp assets/google-services-template.json android/app/google-services.json; fi && if [ ! -f ios/GoogleService-Info.plist ]; then cp assets/GoogleService-Info-template.plist ios/GoogleService-Info.plist; fi",
"import-sort": "npx import-sort-cli --write '{src,__tests__,types}/**/*.ts{,x}' *.ts{,x}",
"check:i18n": "cross-env TS_NODE_FILES=true ts-node-dev --project tsconfig-i18n.json --files test/I18nChecker.ts",
- "react-native:install": "npm install --force && npm clean-install && cd ios && pod install"
+ "react-native:install": "npm install --force && npm clean-install && cd ios && pod install",
+ "deep-link:android:verify-email": "npx uri-scheme open \"https://slf.e-mobility-group.com/verify-email?Email=alixhumbert@gmail.com&VerificationToken=3q45j34trqfweofn2eijtne234fwegf\" --android",
+ "deep-link:android:reset-password": "npx uri-scheme open \"https://slfcah.e-mobility-group.com/define-password?hash=3q45j34trqfweofn2eijtne234fwegf\" --android",
+ "deep-link:ios:verify-email": "npx uri-scheme open \"https://slf.e-mobility-group.com/verify-email?Email=alixhumbert@gmail.com&VerificationToken=3q45j34trqfweofn2eijtne234fwegf\" --ios",
+ "deep-link:ios:reset-password": "npx uri-scheme open \"https://slfcah.e-mobility-group.com/define-password?hash=3q45j34trqfweofn2eijtne234fwegf\" --ios"
+
},
"importSort": {
".js, .jsx, .es6, .es, .mjs": {
diff --git a/src/App.tsx b/src/App.tsx
index bae346e83..d0d6fe200 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -19,7 +19,7 @@ import CentralServerProvider from './provider/CentralServerProvider';
import ProviderFactory from './provider/ProviderFactory';
import Eula from './screens/auth/eula/Eula';
import Login from './screens/auth/login/Login';
-import ResetPassword from './screens/auth/reset-password/ResetPassword';
+import CreatePassword from './screens/auth/create-password/CreatePassword';
import RetrievePassword from './screens/auth/retrieve-password/RetrievePassword';
import SignUp from './screens/auth/sign-up/SignUp';
import Cars from './screens/cars/Cars';
@@ -58,6 +58,7 @@ import FontAwesome from 'react-native-vector-icons/FontAwesome';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Settings from './screens/settings/Settings';
import RNBootSplash from 'react-native-bootsplash';
+import Message from './utils/Message';
// Init i18n
I18nManager.initialize();
@@ -135,7 +136,7 @@ function createAuthNavigator(props: BaseProps) {
-
+
);
@@ -520,6 +521,8 @@ export default class App extends React.Component {
public centralServerProvider: CentralServerProvider;
private location: LocationManager;
private appVersion: CheckVersionResponse;
+ private deepLinkingUnsubscribe: () => void;
+ private navigationRef: any;
public constructor(props: Props) {
super(props);
@@ -540,6 +543,7 @@ export default class App extends React.Component {
public async componentDidMount() {
// Load Navigation State
+ Message.showError('mounted')
const navigationState = await SecuredStorage.getNavigationState();
// Get the central server
this.centralServerProvider = await ProviderFactory.getProvider();
@@ -549,10 +553,6 @@ export default class App extends React.Component {
await this.notificationManager.start();
// Assign
this.centralServerProvider.setNotificationManager(this.notificationManager);
- // Init Deep Linking ---------------------------------------
- this.deepLinkingManager = DeepLinkingManager.getInstance();
- // Activate Deep links
- this.deepLinkingManager.startListening();
// Location ------------------------------------------------
this.location = await LocationManager.getInstance();
this.location.startListening();
@@ -572,7 +572,7 @@ export default class App extends React.Component {
public componentWillUnmount() {
// Deactivate Deep links
- this.deepLinkingManager?.stopListening();
+ this.deepLinkingUnsubscribe?.();
// Stop Notifications
this.notificationManager?.stop();
// Stop Location
@@ -598,14 +598,8 @@ export default class App extends React.Component {
return (
void RNBootSplash.hide({ fade: true })}
- ref={(navigatorRef) => {
- if (navigatorRef) {
- this.notificationManager?.initialize(navigatorRef);
- this.notificationManager.checkOnHoldNotification();
- this.deepLinkingManager?.initialize(navigatorRef, this.centralServerProvider);
- }
- }}
+ onReady={() => void this.onNavigationReady()}
+ ref={(ref) => this.navigationRef = ref}
onStateChange={persistNavigationState}
initialState={this.state.navigationState}>
@@ -616,4 +610,17 @@ export default class App extends React.Component {
);
}
+
+ private async onNavigationReady(): Promise {
+ await RNBootSplash.hide({ fade: true });
+ this.centralServerProvider = await ProviderFactory.getProvider();
+ Message.showWarning('onnavigationready');
+ this.notificationManager?.initialize(this.navigationRef);
+ void this.notificationManager.checkOnHoldNotification();
+ // Init Deep Linking ---------------------------------------
+ this.deepLinkingManager = DeepLinkingManager.getInstance();
+ this.deepLinkingManager?.initialize(this.navigationRef, this.centralServerProvider);
+ // Activate deep linking
+ this.deepLinkingUnsubscribe = this.deepLinkingManager.startListening();
+ }
}
diff --git a/src/I18n/languages/cs.json b/src/I18n/languages/cs.json
index eac6ddccc..cf1f32d08 100644
--- a/src/I18n/languages/cs.json
+++ b/src/I18n/languages/cs.json
@@ -113,7 +113,7 @@
"mandatoryTenant": "Organizace je povinná",
"mandatoryEndpointName": "Endpoint Name is mandatory",
"mandatoryEndpointURL": "Endpoint URL is mandatory",
- "unknownTenant": "Neznámá organizace!",
+ "unknownTenant": "Organization with subdomain '{{tenantSubdomain}}' not found!",
"mandatoryName": "Název je povinný",
"mandatoryFirstName": "První jméno je povinné",
"mandatoryEmail": "E-mail je povinný",
@@ -134,8 +134,7 @@
"accountAlreadyActive": "Váš účet je již aktivní",
"accountVerifiedSuccess": "Váš účet byl úspěšně aktivován!",
"accountPending": "Váš účet čeká na vyřízení! Zkontrolujte svůj e-mail",
- "activationTokenNotValid": "Aktivační odkaz vašeho účtu již není platný",
- "activationEmailNotValid": "Tento e-mail neexistuje",
+ "activationEmailNotValid": "This Email does not exist in organization {{tenantName}}",
"activationUnexpectedError": "Nelze aktivovat účet",
"loginUnexpectedError": "Nelze se přihlásit",
"resetSuccess": "Požadavek přijat! Zkontrolujte svůj e-mail",
@@ -144,6 +143,7 @@
"forgotYourPassword": "Zapomněli jste své heslo?",
"retrievePassword": "Načíst heslo",
"resetPassword": "Resetovat heslo",
+ "createPassword": "Create Password",
"backLogin": "Zpět na přehlášení",
"required": "Požadováno",
"acceptEula": "Přijímám",
@@ -154,6 +154,10 @@
"resetPasswordUnexpectedError": "Heslo nelze resetovat",
"resetPasswordHashNotValid": "Požadavek již není platný, požádejte znovu o nové heslo",
"verifyAccountTokenNotValid": "Požadavek již není platný, požádejte o validaci nového účtu",
+ "invalidLinkNoToken": "Invalid link (missing token)",
+ "invalidLinkNoSubdomain": "Invalid link (missing subdomain)",
+ "invalidLinkNoEmail": "Invalid link (missing email)",
+ "invalidLinkNoHash": "Invalid link (missing hash)",
"haveAlreadyAccount": "Máte již účet?",
"name": "Příjmení",
"firstName": "Křestní jméno",
diff --git a/src/I18n/languages/de.json b/src/I18n/languages/de.json
index e11168b63..ed541f938 100644
--- a/src/I18n/languages/de.json
+++ b/src/I18n/languages/de.json
@@ -113,7 +113,7 @@
"mandatoryTenant": "Die Organisation ist notwendig",
"mandatoryEndpointName": "Der Endpunkt-Name ist notwendig",
"mandatoryEndpointURL": "Die Endpunkt-URL ist notwendig",
- "unknownTenant": "Unbekannte Organisation!",
+ "unknownTenant": "Organization with subdomain '{{tenantSubdomain}}' not found!",
"mandatoryName": "Der Name ist notwendig",
"mandatoryFirstName": "Der Vorname ist notwendig",
"mandatoryEmail": "Die E-Mail ist notwendig",
@@ -134,8 +134,7 @@
"accountAlreadyActive": "Ihr Account ist bereits aktiviert",
"accountVerifiedSuccess": "Ihr Account wurde erfolgreich aktiviert!",
"accountPending": "Ihr Konto wurde noch nicht aktiviert, überprüfen Sie Ihre E-Mails!",
- "activationTokenNotValid": "Der Aktivierungslink Ihres Account ist nicht mehr aktiv",
- "activationEmailNotValid": "Diese Email existiert nicht mehr",
+ "activationEmailNotValid": "This Email does not exist in organization {{tenantName}}",
"activationUnexpectedError": "Account kann nicht aktiviert werden",
"loginUnexpectedError": "Login fehlgeschlagen",
"resetSuccess": "Anfrage angenommen, überprüfen Sie Ihre E-Mails!",
@@ -144,6 +143,7 @@
"forgotYourPassword": "Passwort vergessen?",
"retrievePassword": "Passwort wieder auffinden",
"resetPassword": "Passwort zurücksetzen",
+ "createPassword": "Create Password",
"backLogin": "Zurück zur Anmeldung",
"required": "Benötigt",
"acceptEula": "Ich bestätige den ",
@@ -154,6 +154,10 @@
"resetPasswordHashNotValid": "Anfrage nicht mehr gültig, fordern Sie erneut ein neues Passwort an",
"resetPasswordUnexpectedError": "Passwort kann nicht zurückgesetzt werden",
"verifyAccountTokenNotValid": "Anfrage ist nicht länger gültig, fordern sie eine neue Accountverifizierung an",
+ "invalidLinkNoToken": "Invalid link (missing token)",
+ "invalidLinkNoSubdomain": "Invalid link (missing subdomain)",
+ "invalidLinkNoEmail": "Invalid link (missing email)",
+ "invalidLinkNoHash": "Invalid link (missing hash)",
"haveAlreadyAccount": "Haben Sie bereits ein Konto?",
"name": "Name",
"firstName": "Vorname",
diff --git a/src/I18n/languages/en.json b/src/I18n/languages/en.json
index 9bfbb48c1..4fe1f8bd5 100644
--- a/src/I18n/languages/en.json
+++ b/src/I18n/languages/en.json
@@ -113,7 +113,7 @@
"mandatoryTenant": "The Organization is mandatory",
"mandatoryEndpointName": "Endpoint Name is mandatory",
"mandatoryEndpointURL": "Endpoint URL is mandatory",
- "unknownTenant": "Unknown Organization!",
+ "unknownTenant": "Organization with subdomain '{{tenantSubdomain}}' not found!",
"mandatoryName": "The name is mandatory",
"mandatoryFirstName": "The First Name is mandatory",
"mandatoryEmail": "The email is mandatory",
@@ -134,8 +134,7 @@
"accountAlreadyActive": "Your account is already active",
"accountVerifiedSuccess": "Your account has been activated with success!",
"accountPending": "Your account is pending! Check your e-mail",
- "activationTokenNotValid": "Your account's activation link is no longer valid",
- "activationEmailNotValid": "This Email does not exist",
+ "activationEmailNotValid": "This Email does not exist in organization {{tenantName}}",
"activationUnexpectedError": "Cannot activate account",
"loginUnexpectedError": "Cannot login",
"resetSuccess": "Request accepted! Check your e-mail",
@@ -144,6 +143,7 @@
"forgotYourPassword": "Forgot Password?",
"retrievePassword": "Retrieve Password",
"resetPassword": "Reset Password",
+ "createPassword": "Create Password",
"backLogin": "Back to Login",
"required": "Required",
"acceptEula": "I accept the ",
@@ -154,6 +154,10 @@
"resetPasswordUnexpectedError": "Cannot reset password",
"resetPasswordHashNotValid": "Request no longer valid, request a new password again",
"verifyAccountTokenNotValid": "Request no longer valid, request a new account validation",
+ "invalidLinkNoToken": "Invalid link (missing token)",
+ "invalidLinkNoSubdomain": "Invalid link (missing subdomain)",
+ "invalidLinkNoEmail": "Invalid link (missing email)",
+ "invalidLinkNoHash": "Invalid link (missing hash)",
"haveAlreadyAccount": "Already have an account?",
"name": "Name",
"firstName": "First Name",
diff --git a/src/I18n/languages/es.json b/src/I18n/languages/es.json
index d7db4ae79..616b8d239 100644
--- a/src/I18n/languages/es.json
+++ b/src/I18n/languages/es.json
@@ -113,7 +113,7 @@
"mandatoryTenant": "La organización es obligatoria",
"mandatoryEndpointName": "Endpoint Name is mandatory",
"mandatoryEndpointURL": "Endpoint URL is mandatory",
- "unknownTenant": "¡Organización desconocida!",
+ "unknownTenant": "Organization with subdomain '{{tenantSubdomain}}' not found!",
"mandatoryName": "El apellido es obligatorio",
"mandatoryFirstName": "El nombre es obligatorio",
"mandatoryEmail": "El correo electrónico es obligatorio",
@@ -134,8 +134,7 @@
"accountAlreadyActive": "Su cuenta ya ha sido activada",
"accountVerifiedSuccess": "¡Su cuenta ha sido activada exitosamente!",
"accountPending": "¡Su cuenta está pendiente! Revise su correo electrónico",
- "activationTokenNotValid": "El código de activación de su cuenta ya no es valido",
- "activationEmailNotValid": "El correo electónico ya no existe",
+ "activationEmailNotValid": "This Email does not exist in organization {{tenantName}}",
"activationUnexpectedError": "No se ha podido activar la cuenta",
"loginUnexpectedError": "No se puede iniciar sesión",
"resetSuccess": "¡Solicitud aceptada! Revise su correo electrónico",
@@ -144,6 +143,7 @@
"forgotYourPassword": "¿Olvidó su contraseña?",
"retrievePassword": "Recuperar contraseña",
"resetPassword": "Restablecer contraseña",
+ "createPassword": "Create Password",
"backLogin": "Iniciar sesión",
"required": "Obligatorio",
"acceptEula": "Acepto el ",
@@ -154,6 +154,10 @@
"resetPasswordUnexpectedError": "No se ha podido reestablecer su contraseña",
"resetPasswordHashNotValid": "La solicitud ya no es válida, solicite nuevamente una contraseña",
"verifyAccountTokenNotValid": "La solicitud ya no es válida, solicite nuevamente una validación de cuenta",
+ "invalidLinkNoToken": "Invalid link (missing token)",
+ "invalidLinkNoSubdomain": "Invalid link (missing subdomain)",
+ "invalidLinkNoEmail": "Invalid link (missing email)",
+ "invalidLinkNoHash": "Invalid link (missing hash)",
"haveAlreadyAccount": "¿Ya tiene una cuenta?",
"name": "Apellido(s)",
"firstName": "Nombre(s)",
diff --git a/src/I18n/languages/fr.json b/src/I18n/languages/fr.json
index 972cd50b2..9fd601b20 100644
--- a/src/I18n/languages/fr.json
+++ b/src/I18n/languages/fr.json
@@ -113,7 +113,7 @@
"mandatoryTenant": "L'organisation est obligatoire",
"mandatoryEndpointName": "Le nom est obligatoire",
"mandatoryEndpointURL": "L'URL est obligatoire",
- "unknownTenant": "Organisation inconnue!",
+ "unknownTenant": "Aucune organisation trouvée avec le sous-domain '{{tenantSubdomain}}'!",
"mandatoryName": "Le Nom est obligatoire",
"mandatoryFirstName": "Le Prénom est obligatoire",
"mandatoryEmail": "L'Email est obligatoire",
@@ -134,8 +134,7 @@
"accountAlreadyActive": "Votre compte a déjà été activé",
"accountVerifiedSuccess": "Votre compte a été activé avec succès!",
"accountPending": "Votre compte n'est pas activé! Vérifiez vos e-mails",
- "activationTokenNotValid": "Le lien d'activation de votre compte n'est plus valide",
- "activationEmailNotValid": "Cet Email n'existe plus",
+ "activationEmailNotValid": "Cet email n'existe pas dans l'organisation {{tenantName}}",
"activationUnexpectedError": "Impossible d'activer le compte",
"loginUnexpectedError": "Connexion impossible",
"resetSuccess": "Requête acceptée ! Vérifiez vos e-mails",
@@ -144,6 +143,7 @@
"forgotYourPassword": "Mot de passe oublié?",
"retrievePassword": "Récupérez Mot de Passe",
"resetPassword": "Réinitialiser Mot de Passe",
+ "createPassword": "Créer Mot de Passe",
"backLogin": "Se Connecter",
"required": "Obligatoire",
"acceptEula": "J'accepte les ",
@@ -154,6 +154,10 @@
"resetPasswordHashNotValid": "La requete n'est plus valide, refaites une demande de mot de passe",
"resetPasswordUnexpectedError": "Impossible de réinitialiser le mot de passe",
"verifyAccountTokenNotValid": "La requete n'est plus valide, refaites une demande d'activation de compte",
+ "invalidLinkNoToken": "Lien non valide (token manquant)",
+ "invalidLinkNoSubdomain": "Invalid link (sous-domaine manquant)",
+ "invalidLinkNoEmail": "Lien non valide (email manquant)",
+ "invalidLinkNoHash": "Lien non valide (hash) manquant)",
"haveAlreadyAccount": "Avez-vous déjà un compte?",
"name": "Nom",
"firstName": "Prénom",
diff --git a/src/I18n/languages/it.json b/src/I18n/languages/it.json
index 49776960a..50c1c92aa 100644
--- a/src/I18n/languages/it.json
+++ b/src/I18n/languages/it.json
@@ -113,7 +113,7 @@
"mandatoryTenant": "L'organizzazione è obbligatoria",
"mandatoryEndpointName": "Endpoint Name is mandatory",
"mandatoryEndpointURL": "Endpoint URL is mandatory",
- "unknownTenant": "Organizzazione Sconosciuta!",
+ "unknownTenant": "Organization with subdomain '{{tenantSubdomain}}' not found!",
"mandatoryName": "Il Cognome è obbligatorio",
"mandatoryFirstName": "Il Nome è obbligatorio",
"mandatoryEmail": "L'email è obbligatoria",
@@ -134,8 +134,7 @@
"accountAlreadyActive": "Il tuo account è già attivo",
"accountVerifiedSuccess": "Il tuo account è stato attivato con successo!",
"accountPending": "Il tuo account è in sospeso! Controlla la tua email",
- "activationTokenNotValid": "Il link di attivazione del tuo account non è più valido",
- "activationEmailNotValid": "Questa email non esiste",
+ "activationEmailNotValid": "This Email does not exist in organization {{tenantName}}",
"activationUnexpectedError": "Non posso attivare l'account",
"loginUnexpectedError": "Non riesco ad eseguire il login",
"resetSuccess": "Richiesta accettata! Controlla la tua email",
@@ -144,6 +143,7 @@
"forgotYourPassword": "Passowrd Dimenticata?",
"retrievePassword": "Recupera Password",
"resetPassword": "Resetta la Password",
+ "createPassword": "Create Password",
"backLogin": "Indietro al login",
"required": "Richiesto",
"acceptEula": "Accetto l'",
@@ -154,6 +154,10 @@
"resetPasswordUnexpectedError": "Non posso resettare la password",
"resetPasswordHashNotValid": "Richiesta non più valida, richiedi nuovamente una password",
"verifyAccountTokenNotValid": "Richiesta non più valida, richiedi una nuova validazione dell'account",
+ "invalidLinkNoToken": "Invalid link (missing token)",
+ "invalidLinkNoSubdomain": "Invalid link (missing subdomain)",
+ "invalidLinkNoEmail": "Invalid link (missing email)",
+ "invalidLinkNoHash": "Invalid link (missing hash)",
"haveAlreadyAccount": "Hai già un'account?",
"name": "Cognome",
"firstName": "Nome",
diff --git a/src/I18n/languages/pt.json b/src/I18n/languages/pt.json
index 18b545586..912775cab 100644
--- a/src/I18n/languages/pt.json
+++ b/src/I18n/languages/pt.json
@@ -113,7 +113,7 @@
"mandatoryTenant": "A organização é obrigatório",
"mandatoryEndpointName": "Endpoint Name is mandatory",
"mandatoryEndpointURL": "Endpoint URL is mandatory",
- "unknownTenant": "Organização Desconhecida!",
+ "unknownTenant": "Organization with subdomain '{{tenantSubdomain}}' not found!",
"mandatoryName": "O nome é obrigatório",
"mandatoryFirstName": "O primeiro nome é obrigatório",
"mandatoryEmail": "O email é obrigatório",
@@ -134,8 +134,7 @@
"accountAlreadyActive": "A sua conta já está ativa",
"accountVerifiedSuccess": "A sua conta foi ativada com sucesso!",
"accountPending": "A sua conta está pendendte! Por favor Verifique o seu email",
- "activationTokenNotValid": "Oseu link de ativação de conta já não está válido",
- "activationEmailNotValid": "Este emial não existe",
+ "activationEmailNotValid": "This Email does not exist in organization {{tenantName}}",
"activationUnexpectedError": "A conta não pode ser ativada",
"loginUnexpectedError": "Não consigo aceder",
"resetSuccess": "Pedido aceite! Verifique o seu e-mail",
@@ -144,6 +143,7 @@
"forgotYourPassword": "Esqueceu-se da password?",
"retrievePassword": "Reativar Password",
"resetPassword": "Redefinir Password",
+ "createPassword": "Create Password",
"backLogin": "Voltar ao Login",
"required": "Obrigatório",
"acceptEula": "Eu aceito ",
@@ -154,6 +154,10 @@
"resetPasswordUnexpectedError": "Não posso redefinir a password",
"resetPasswordHashNotValid": "O pedido excedeu a validade, peça uma nova password",
"verifyAccountTokenNotValid": "O pedido excedeu a validade, peça uma nova validação de conta",
+ "invalidLinkNoToken": "Invalid link (missing token)",
+ "invalidLinkNoSubdomain": "Invalid link (missing subdomain)",
+ "invalidLinkNoEmail": "Invalid link (missing email)",
+ "invalidLinkNoHash": "Invalid link (missing hash)",
"haveAlreadyAccount": "Já tem uma conta?",
"name": "Nome",
"firstName": "Primeiro Nome",
diff --git a/src/deeplinking/DeepLinkingManager.tsx b/src/deeplinking/DeepLinkingManager.tsx
index d81822f8c..1687731fb 100644
--- a/src/deeplinking/DeepLinkingManager.tsx
+++ b/src/deeplinking/DeepLinkingManager.tsx
@@ -1,7 +1,7 @@
import { CommonActions, NavigationContainerRef } from '@react-navigation/native';
import { StatusCodes } from 'http-status-codes';
import I18n from 'i18n-js';
-import { EmitterSubscription, Linking } from 'react-native';
+import { Linking } from 'react-native';
import DeepLinking from 'react-native-deep-linking';
import CentralServerProvider from '../provider/CentralServerProvider';
@@ -10,11 +10,14 @@ import Constants from '../utils/Constants';
import Message from '../utils/Message';
import Utils from '../utils/Utils';
+let isInitialURLRead = false;
+
export default class DeepLinkingManager {
private static instance: DeepLinkingManager;
- private navigator: NavigationContainerRef;
+ private navigator: NavigationContainerRef;
private centralServerProvider: CentralServerProvider;
- private linkingSubscription: EmitterSubscription;
+ private url: string;
+ private readonly scheme: string = 'https://';
// eslint-disable-next-line no-useless-constructor
private constructor() {}
@@ -26,149 +29,188 @@ export default class DeepLinkingManager {
return DeepLinkingManager.instance;
}
- public initialize(navigator: NavigationContainerRef, centralServerProvider: CentralServerProvider) {
+ public async initialize(navigator: NavigationContainerRef, centralServerProvider: CentralServerProvider): Promise {
// Keep
this.navigator = navigator;
this.centralServerProvider = centralServerProvider;
// Activate Deep Linking
+ DeepLinking.addScheme(this.scheme);
+ // Allow opening the app when link has been opened by the dashboard
DeepLinking.addScheme('eMobility://');
- DeepLinking.addScheme('emobility://');
// Init Routes
this.addResetPasswordRoute();
this.addVerifyAccountRoute();
// Init URL
- Linking.getInitialURL()
- .then((url) => {
- if (url) {
- Linking.openURL(url);
+ if (!isInitialURLRead) {
+ try {
+ const initialURL = await Linking.getInitialURL();
+ isInitialURLRead = true;
+ Message.showError(initialURL);
+ console.log('initialURL ' + initialURL);
+ if ( initialURL ) {
+ await this.handleUrl({ url: initialURL });
}
- })
- .catch((err) => {
+ } catch ( err ) {
console.error('An error occurred', err);
- });
+ }
+ }
}
public startListening() {
- this.linkingSubscription = Linking.addEventListener('url', this.handleUrl);
+ const linkingSubscription = Linking.addEventListener('url', this.handleUrl.bind(this));
+ return () => {
+ linkingSubscription?.remove();
+ };
}
- public stopListening() {
- this.linkingSubscription?.remove();
+ public async handleUrl({ url }: { url: string }): Promise {
+ // const canOpenURl = await Linking.canOpenURL(url);
+ // if (canOpenURl) {
+ if (url) {
+ this.centralServerProvider.setAutoLoginDisabled(true);
+ this.url = url;
+ console.log('evaluating url');
+ Message.showSuccess('Evaluating URL');
+ DeepLinking.evaluateUrl(url);
+ }
+ //}
}
- public handleUrl = ({ url }: { url: string }) => {
- Linking.canOpenURL(url).then((supported) => {
- if (supported) {
- DeepLinking.evaluateUrl(url);
- }
- });
- };
+ private getTenantSubdomainFromURL(): string {
+ const regex = new RegExp('\\/\\/(.*?)\\.');
+ const res = this.url?.match(regex);
+ return res?.[1];
+ }
- private addResetPasswordRoute = () => {
- // Add Route
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
- DeepLinking.addRoute('/resetPassword/:tenant/:hash', async (response: { tenant: string; hash: string }) => {
- // Check params
- if (!response.tenant) {
- Message.showError(I18n.t('authentication.mandatoryTenant'));
+ private addResetPasswordRoute(): void {
+ // Handle reset password request
+ const definePasswordCallback = async (subdomain: string, params: Record) => {
+ if (!subdomain) {
+ Message.showError(I18n.t('authentication.invalidLinkNoSubdomain'));
+ return;
}
- // Get the Tenant
- const tenant = await this.centralServerProvider.getTenant(response.tenant);
+ const tenant = await this.centralServerProvider.getTenant(subdomain);
if (!tenant) {
- Message.showError(I18n.t('authentication.unknownTenant'));
+ Message.showError(I18n.t('authentication.unknownTenant', {tenantSubdomain: subdomain}));
+ return;
}
- if (!response.hash) {
- Message.showError(I18n.t('authentication.resetPasswordHashNotValid'));
+ const hash = params?.hash;
+ if (!hash) {
+ Message.showError(I18n.t('authentication.invalidLinkNoHash'));
+ return;
}
// Disable
this.centralServerProvider.setAutoLoginDisabled(true);
// Navigate
- this.navigator.dispatch(
- CommonActions.navigate({
- name: 'ResetPassword',
- key: `${Utils.randomNumber()}`,
- params: { tenantSubDomain: response.tenant, hash: response.hash }
- })
- );
+ // Message.showInfo('Navigating to reset password');
+ this.navigator.navigate('ResetPassword', {
+ key: `${Utils.randomNumber()}`,
+ tenantSubDomain: subdomain,
+ hash,
+ load: true
+ });
+ };
+ // Add route to handle links from the dashboard
+ DeepLinking.addRoute('/resetPassword/:tenant/:hash', ({tenant, ...params}: {tenant: string; hash: string}) => {
+ void definePasswordCallback(tenant, params as Record);
});
- };
+ // Add route to handle links directly from the mails
+ DeepLinking.addRoute('/define-password:hash', () => {
+ const subdomain = this.getTenantSubdomainFromURL();
+ const params = Utils.getURLParameters(this.url) as {hash: string};
+ void definePasswordCallback(subdomain, params);
+ });
+ }
- private addVerifyAccountRoute = () => {
- // Add Route
- DeepLinking.addRoute(
- '/verifyAccount/:tenant/:email/:token/:resetToken',
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
- async (response: { tenant: string; email: string; token: string; resetToken: string }) => {
- // Check params
- if (!response.tenant) {
- Message.showError(I18n.t('authentication.mandatoryTenant'));
- }
- if (!response.email) {
- Message.showError(I18n.t('authentication.mandatoryEmail'));
- }
- // Get the Tenant
- const tenant = await this.centralServerProvider.getTenant(response.tenant);
- if (!tenant) {
- Message.showError(I18n.t('authentication.unknownTenant'));
- }
- if (!response.token) {
- Message.showError(I18n.t('authentication.verifyAccountTokenNotValid'));
- }
- // Disable
- this.centralServerProvider.setAutoLoginDisabled(true);
- await this.centralServerProvider.logoff();
- // Navigate to login page
- this.navigator.dispatch(
- CommonActions.navigate({
- name: 'Login',
- key: `${Utils.randomNumber()}`,
- params: { tenantSubDomain: response.tenant, email: response.email }
- })
- );
- // Call the backend
- try {
- // Validate Account
- const result = await this.centralServerProvider.verifyEmail(response.tenant, response.email, response.token);
- if (result.status === Constants.REST_RESPONSE_SUCCESS) {
- Message.showSuccess(I18n.t('authentication.accountVerifiedSuccess'));
- // Check if user has to change his password
- if (response.resetToken && response.resetToken !== 'null') {
- // Change password
- this.navigator.dispatch(
- CommonActions.navigate({
- name: 'ResetPassword',
- key: `${Utils.randomNumber()}`,
- params: { tenantSubDomain: response.tenant, hash: response.resetToken }
- })
- );
- }
+ private addVerifyAccountRoute(): void {
+ // Handle verify account request
+ const verifyAccountCallback = async (subdomain: string, params: Record) => {
+ const tenant = await this.centralServerProvider.getTenant(subdomain);
+ const email = params?.Email;
+ const verificationToken = params?.VerificationToken;
+ const resetToken = params?.ResetToken;
+ // Check params
+ if (!email) {
+ Message.showError(I18n.t('authentication.invalidLinkNoEmail'));
+ return;
+ }
+ if (!subdomain) {
+ Message.showError(I18n.t('authentication.invalidLinkNoSubdomain'));
+ return;
+ }
+ if (!tenant) {
+ Message.showError(I18n.t('authentication.unknownTenant', {tenantSubdomain: subdomain}));
+ return;
+ }
+ if (!verificationToken) {
+ Message.showError(I18n.t('authentication.invalidLinkNoToken'));
+ return;
+ }
+ // Disable
+ this.centralServerProvider.setAutoLoginDisabled(true);
+ await this.centralServerProvider.logoff();
+ // Navigate to login page
+ this.navigator.navigate('Login', {
+ key: `${Utils.randomNumber()}`,
+ tenantSubDomain: subdomain,
+ email
+ });
+ // Call the backend
+ try {
+ // Validate Account
+ const result = await this.centralServerProvider.verifyEmail(subdomain, email, verificationToken);
+ if (result.status === Constants.REST_RESPONSE_SUCCESS) {
+ Message.showSuccess(I18n.t('authentication.accountVerifiedSuccess'));
+ // Check if user has to change his password
+ if (resetToken && resetToken !== 'null') {
+ // Change password
+ this.navigator.dispatch(
+ CommonActions.navigate({
+ name: 'ResetPassword',
+ key: `${Utils.randomNumber()}`,
+ params: { tenantSubDomain: subdomain, hash: resetToken, email }
+ })
+ );
}
- } catch (error) {
- // Check request?
- if (error.request) {
- // Show error
- switch (error.request.status) {
- // Account already active
- case HTTPError.USER_ACCOUNT_ALREADY_ACTIVE_ERROR:
- Message.showError(I18n.t('authentication.accountAlreadyActive'));
- break;
+ }
+ } catch (error) {
+ // Check request?
+ if (error.request) {
+ // Show error
+ switch (error.request.status) {
+ // Account already active
+ case HTTPError.USER_ACCOUNT_ALREADY_ACTIVE_ERROR:
+ Message.showError(I18n.t('authentication.accountAlreadyActive'));
+ break;
// VerificationToken no longer valid
- case HTTPError.INVALID_TOKEN_ERROR:
- Message.showError(I18n.t('authentication.activationTokenNotValid'));
- break;
+ case HTTPError.INVALID_TOKEN_ERROR:
+ Message.showError(I18n.t('authentication.verifyAccountTokenNotValid'));
+ break;
// Email does not exist
- case StatusCodes.NOT_FOUND:
- Message.showError(I18n.t('authentication.activationEmailNotValid'));
- break;
+ case StatusCodes.NOT_FOUND:
+ Message.showError(I18n.t('authentication.activationEmailNotValid', {tenantName: tenant?.name}));
+ break;
// Other common Error
- default:
- await Utils.handleHttpUnexpectedError(this.centralServerProvider, error, 'authentication.activationUnexpectedError');
- }
- } else {
- Message.showError(I18n.t('authentication.activationUnexpectedError'));
+ default:
+ await Utils.handleHttpUnexpectedError(this.centralServerProvider, error, 'authentication.activationUnexpectedError');
}
+ } else {
+ Message.showError(I18n.t('authentication.activationUnexpectedError'));
}
}
+ };
+ // Add route to handle links from the dashboard
+ DeepLinking.addRoute(
+ '/verifyAccount/:tenant/:email/:token/:resetToken',
+ ({ tenant, ...params}: {tenant: string; email: string; token: string; resetToken: string}) => {
+ void verifyAccountCallback(tenant, params as Record);
+ });
+ // Add route to handle links directly from the mails
+ DeepLinking.addRoute('/verify-email:params', () => {
+ const params = Utils.getURLParameters(this.url) as {Email: string; VerificationToken: string; ResetToken: string};
+ const subdomain = this.getTenantSubdomainFromURL();
+ void verifyAccountCallback(subdomain, params);
+ }
);
- };
+ }
}
diff --git a/src/screens/auth/create-password/CreatePassword.tsx b/src/screens/auth/create-password/CreatePassword.tsx
new file mode 100644
index 000000000..109c387c6
--- /dev/null
+++ b/src/screens/auth/create-password/CreatePassword.tsx
@@ -0,0 +1,314 @@
+import { CommonActions } from '@react-navigation/native';
+import { StatusCodes } from 'http-status-codes';
+import I18n from 'i18n-js';
+import { Button, FormControl, Icon, Stack, Spinner } from 'native-base';
+import React from 'react';
+import {
+ Keyboard,
+ KeyboardAvoidingView,
+ ScrollView,
+ TextInput,
+ Text,
+ View,
+} from 'react-native';
+
+import computeFormStyleSheet from '../../../FormStyles';
+import BaseProps from '../../../types/BaseProps';
+import Message from '../../../utils/Message';
+import Utils from '../../../utils/Utils';
+import BaseScreen from '../../base-screen/BaseScreen';
+import AuthHeader from '../AuthHeader';
+import computeStyleSheet from '../AuthStyles';
+import { TenantConnection } from '../../../types/Tenant';
+import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
+import { scale } from 'react-native-size-matters';
+
+export interface Props extends BaseProps {}
+
+interface State {
+ tenantSubDomain?: string;
+ tenantName?: string;
+ tenantLogo?: string;
+ hash?: string;
+ password?: string;
+ repeatPassword?: string;
+ errorPassword?: Record[];
+ errorRepeatPassword?: Record[];
+ createPasswordLoading?: boolean;
+ contentLoading?: boolean;
+ hideRepeatPassword?: boolean;
+ hidePassword?: boolean;
+ email?: string;
+}
+
+export default class CreatePassword extends BaseScreen {
+ public state: State;
+ public props: Props;
+ private repeatPasswordInput: TextInput;
+ private formValidationDef = {
+ password: {
+ presence: {
+ allowEmpty: false,
+ message: '^' + I18n.t('authentication.mandatoryPassword')
+ },
+ equality: {
+ attribute: 'ghost',
+ message: '^' + I18n.t('authentication.passwordRule'),
+ comparator(password: string, ghost: string) {
+ // True if EULA is checked
+ return /(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!#@:;,<>\/''\$%\^&\*\.\?\-_\+\=\(\)])(?=.{8,})/.test(password);
+ }
+ }
+ },
+ repeatPassword: {
+ presence: {
+ allowEmpty: false,
+ message: '^' + I18n.t('authentication.mandatoryPassword')
+ },
+ equality: {
+ attribute: 'password',
+ message: '^' + I18n.t('authentication.passwordNotMatch')
+ }
+ }
+ };
+
+ public constructor(props: Props) {
+ super(props);
+ this.state = {
+ tenantSubDomain: Utils.getParamFromNavigation(this.props.route, 'tenantSubDomain', '') as string,
+ hash: Utils.getParamFromNavigation(this.props.route, 'hash', null) as string,
+ tenantName: '',
+ tenantLogo: null,
+ password: '',
+ repeatPassword: '',
+ createPasswordLoading: false,
+ hidePassword: true,
+ hideRepeatPassword: true,
+ contentLoading: true
+ };
+ }
+
+ // Enforce goBack to Login page as deeplinking is broken with react-navigation
+ public onBack(): boolean {
+ this.props.navigation.navigate('Login');
+ return true;
+ }
+
+ public setState = (
+ state: State | ((prevState: Readonly, props: Readonly) => State | Pick) | Pick,
+ callback?: () => void
+ ) => {
+ super.setState(state, callback);
+ };
+
+ public async getTenantLogo(tenant: TenantConnection): Promise {
+ try {
+ if (tenant) {
+ const tenantLogo = await this.centralServerProvider.getTenantLogoBySubdomain(tenant);
+ return tenantLogo;
+ }
+ } catch (error) {
+ switch ( error?.request?.status ) {
+ case StatusCodes.NOT_FOUND:
+ return null;
+ default:
+ await Utils.handleHttpUnexpectedError(
+ this.centralServerProvider,
+ error,
+ null,
+ null,
+ null,
+ async (redirectedTenant: TenantConnection) => this.getTenantLogo(redirectedTenant)
+ );
+ break;
+ }
+ }
+ return null;
+ }
+
+ private async load(): Promise {
+ this.setState({contentLoading: true}, async () => {
+ const tenantSubDomain = Utils.getParamFromNavigation(this.props.route, 'tenantSubDomain', this.state.tenantSubDomain) as string;
+ const hash = Utils.getParamFromNavigation(this.props.route, 'hash', null) as string;
+ const tenant = await this.centralServerProvider.getTenant(tenantSubDomain);
+ const tenantLogo = await this.getTenantLogo(tenant);
+ this.setState({ tenantLogo, tenantSubDomain, tenantName: tenant?.name ?? '', hash, contentLoading: false});
+ });
+ }
+
+ public async componentDidMount(): Promise {
+ // Call parent
+ await super.componentDidMount();
+ // Init
+ await this.load();
+ // Disable Auto Login
+ this.centralServerProvider.setAutoLoginDisabled(true);
+ }
+
+ public async componentDidFocus(): Promise {
+ super.componentDidFocus();
+ await this.load();
+ }
+
+ public async componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any) {
+ const load = Utils.getParamFromNavigation(this.props.route, 'load', false, true) as string;
+ if (load) {
+ this.load();
+ }
+ }
+
+ public resetPassword = async () => {
+ // Check field
+ const formIsValid = Utils.validateInput(this, this.formValidationDef);
+ if (formIsValid) {
+ const { tenantSubDomain, password, hash } = this.state;
+ try {
+ // Loading
+ this.setState({ createPasswordLoading: true });
+ // Register
+ await this.centralServerProvider.resetPassword(tenantSubDomain, hash, password);
+ // Clear user's credentials
+ await this.centralServerProvider.clearUserPassword(tenantSubDomain);
+ // Reset
+ this.setState({ createPasswordLoading: false });
+ // Show
+ Message.showSuccess(I18n.t('authentication.resetPasswordSuccess'));
+ // Navigate
+ this.props.navigation.dispatch(
+ CommonActions.reset({
+ index: 0,
+ routes: [
+ {
+ name: 'Login',
+ params: {
+ tenantSubDomain: this.state.tenantSubDomain
+ }
+ }
+ ]
+ })
+ );
+ } catch (error) {
+ // Reset
+ this.setState({ createPasswordLoading: false });
+ // Check request?
+ if (error.request) {
+ // Show error
+ switch (error.request.status) {
+ // Invalid Hash
+ case StatusCodes.NOT_FOUND:
+ Message.showError(I18n.t('authentication.resetPasswordHashNotValid'));
+ break;
+ default:
+ // Other common Error
+ await Utils.handleHttpUnexpectedError(this.centralServerProvider, error, 'authentication.resetPasswordUnexpectedError', null, null, async () => this.resetPassword());
+ }
+ } else {
+ Message.showError(I18n.t('authentication.resetPasswordUnexpectedError'));
+ }
+ }
+ }
+ };
+
+ public render() {
+ const style = computeStyleSheet();
+ const formStyle = computeFormStyleSheet();
+ const commonColor = Utils.getCurrentCommonColor();
+ const { tenantName, createPasswordLoading, hidePassword, hideRepeatPassword, tenantLogo, contentLoading } = this.state;
+ // Get logo
+ return (
+
+ {contentLoading ? (
+
+ ) : (
+ <>
+
+
+
+
+
+
+ this.repeatPasswordInput.focus()}
+ returnKeyType={'next'}
+ placeholder={I18n.t('authentication.password')}
+ placeholderTextColor={commonColor.placeholderTextColor}
+ style={formStyle.inputField}
+ autoCapitalize="none"
+ blurOnSubmit={false}
+ autoCorrect={false}
+ keyboardType={'default'}
+ onChangeText={(text) => this.setState({ password: text })}
+ secureTextEntry={hidePassword}
+ />
+ this.setState({ hidePassword: !hidePassword })}
+ style={formStyle.inputIcon}
+ />
+
+ {this.state.errorPassword &&
+ this.state.errorPassword.map((errorMessage, index) => (
+
+ {errorMessage}
+
+ ))}
+
+
+ (this.repeatPasswordInput = ref)}
+ selectionColor={commonColor.textColor}
+ onSubmitEditing={() => Keyboard.dismiss()}
+ returnKeyType={'next'}
+ placeholder={I18n.t('authentication.repeatPassword')}
+ placeholderTextColor={commonColor.placeholderTextColor}
+ style={formStyle.inputField}
+ autoCapitalize="none"
+ blurOnSubmit={false}
+ autoCorrect={false}
+ keyboardType={'default'}
+ onChangeText={(text) => this.setState({ repeatPassword: text })}
+ secureTextEntry={hideRepeatPassword}
+ />
+ this.setState({ hideRepeatPassword: !hideRepeatPassword })}
+ style={formStyle.inputIcon}
+ />
+
+ {this.state.errorRepeatPassword &&
+ this.state.errorRepeatPassword.map((errorMessage, index) => (
+
+ {errorMessage}
+
+ ))}
+ {createPasswordLoading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ >
+ )}
+
+ );
+ }
+}
diff --git a/src/screens/auth/reset-password/ResetPassword.tsx b/src/screens/auth/reset-password/ResetPassword.tsx
deleted file mode 100644
index b2401315f..000000000
--- a/src/screens/auth/reset-password/ResetPassword.tsx
+++ /dev/null
@@ -1,283 +0,0 @@
-import { CommonActions } from '@react-navigation/native';
-import { StatusCodes } from 'http-status-codes';
-import I18n from 'i18n-js';
-import { Button, FormControl, Icon, Stack, Spinner } from 'native-base';
-import React from 'react';
-import { Keyboard, KeyboardAvoidingView, ScrollView, TextInput, Text, View } from 'react-native';
-
-import computeFormStyleSheet from '../../../FormStyles';
-import BaseProps from '../../../types/BaseProps';
-import Message from '../../../utils/Message';
-import Utils from '../../../utils/Utils';
-import BaseScreen from '../../base-screen/BaseScreen';
-import AuthHeader from '../AuthHeader';
-import computeStyleSheet from '../AuthStyles';
-import { TenantConnection } from '../../../types/Tenant';
-import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
-import { scale } from 'react-native-size-matters';
-
-export interface Props extends BaseProps {}
-
-interface State {
- tenantSubDomain?: string;
- tenantName?: string;
- tenantLogo?: string;
- hash?: string;
- password?: string;
- repeatPassword?: string;
- errorPassword?: Record[];
- errorRepeatPassword?: Record[];
- loading?: boolean;
- hideRepeatPassword?: boolean;
- hidePassword?: boolean;
-}
-
-export default class ResetPassword extends BaseScreen {
- public state: State;
- public props: Props;
- private repeatPasswordInput: TextInput;
- private formValidationDef = {
- password: {
- presence: {
- allowEmpty: false,
- message: '^' + I18n.t('authentication.mandatoryPassword')
- },
- equality: {
- attribute: 'ghost',
- message: '^' + I18n.t('authentication.passwordRule'),
- comparator(password: string, ghost: string) {
- // True if EULA is checked
- return /(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!#@:;,<>\/''\$%\^&\*\.\?\-_\+\=\(\)])(?=.{8,})/.test(password);
- }
- }
- },
- repeatPassword: {
- presence: {
- allowEmpty: false,
- message: '^' + I18n.t('authentication.mandatoryPassword')
- },
- equality: {
- attribute: 'password',
- message: '^' + I18n.t('authentication.passwordNotMatch')
- }
- }
- };
-
- public constructor(props: Props) {
- super(props);
- this.state = {
- tenantSubDomain: Utils.getParamFromNavigation(this.props.route, 'tenantSubDomain', '') as string,
- hash: Utils.getParamFromNavigation(this.props.route, 'hash', null) as string,
- tenantName: '',
- tenantLogo: null,
- password: '',
- repeatPassword: '',
- loading: false,
- hidePassword: true,
- hideRepeatPassword: true
- };
- }
-
- public setState = (
- state: State | ((prevState: Readonly, props: Readonly) => State | Pick) | Pick,
- callback?: () => void
- ) => {
- super.setState(state, callback);
- };
-
- public async setTenantLogo(tenant: TenantConnection): Promise {
- try {
- if (tenant) {
- const tenantLogo = await this.centralServerProvider.getTenantLogoBySubdomain(tenant);
- this.setState({tenantLogo});
- }
- } catch (error) {
- switch ( error?.request?.status ) {
- case StatusCodes.NOT_FOUND:
- return null;
- default:
- await Utils.handleHttpUnexpectedError(
- this.centralServerProvider,
- error,
- null,
- null,
- null,
- async (redirectedTenant: TenantConnection) => this.setTenantLogo(redirectedTenant)
- );
- break;
- }
- }
- return null;
- }
-
- public async componentDidMount(): Promise {
- // Call parent
- await super.componentDidMount();
- // Init
- const tenant = await this.centralServerProvider.getTenant(this.state.tenantSubDomain);
- await this.setTenantLogo(tenant);
- this.setState({
- tenantName: tenant ? tenant.name : ''
- });
- // Disable Auto Login
- this.centralServerProvider.setAutoLoginDisabled(true);
- }
-
- public async componentDidFocus(): Promise {
- super.componentDidFocus();
- const tenantSubdomain = Utils.getParamFromNavigation(this.props.route, 'tenantSubDomain', this.state.tenantSubDomain) as string;
- const tenant = await this.centralServerProvider.getTenant(tenantSubdomain);
- await this.setTenantLogo(tenant);
- }
-
- public resetPassword = async () => {
- // Check field
- const formIsValid = Utils.validateInput(this, this.formValidationDef);
- if (formIsValid) {
- const { tenantSubDomain, password, hash } = this.state;
- try {
- // Loading
- this.setState({ loading: true });
- // Register
- await this.centralServerProvider.resetPassword(tenantSubDomain, hash, password);
- // Clear user's credentials
- await this.centralServerProvider.clearUserPassword(tenantSubDomain);
- // Reset
- this.setState({ loading: false });
- // Show
- Message.showSuccess(I18n.t('authentication.resetPasswordSuccess'));
- // Navigate
- this.props.navigation.dispatch(
- CommonActions.reset({
- index: 0,
- routes: [
- {
- name: 'Login',
- params: {
- tenantSubDomain: this.state.tenantSubDomain
- }
- }
- ]
- })
- );
- } catch (error) {
- // Reset
- this.setState({ loading: false });
- // Check request?
- if (error.request) {
- // Show error
- switch (error.request.status) {
- // Invalid Hash
- case StatusCodes.NOT_FOUND:
- Message.showError(I18n.t('authentication.resetPasswordHashNotValid'));
- break;
- default:
- // Other common Error
- await Utils.handleHttpUnexpectedError(this.centralServerProvider, error, 'authentication.resetPasswordUnexpectedError', null, null, async () => this.resetPassword());
- }
- } else {
- Message.showError(I18n.t('authentication.resetPasswordUnexpectedError'));
- }
- }
- }
- };
-
- public render() {
- const style = computeStyleSheet();
- const formStyle = computeFormStyleSheet();
- const commonColor = Utils.getCurrentCommonColor();
- const { tenantName, loading, hidePassword, hideRepeatPassword, tenantLogo } = this.state;
- // Get logo
- return (
-
-
-
-
-
-
-
- this.repeatPasswordInput.focus()}
- returnKeyType={'next'}
- placeholder={I18n.t('authentication.password')}
- placeholderTextColor={commonColor.placeholderTextColor}
- style={formStyle.inputField}
- autoCapitalize="none"
- blurOnSubmit={false}
- autoCorrect={false}
- keyboardType={'default'}
- onChangeText={(text) => this.setState({ password: text })}
- secureTextEntry={hidePassword}
- />
- this.setState({ hidePassword: !hidePassword })}
- style={formStyle.inputIcon}
- />
-
- {this.state.errorPassword &&
- this.state.errorPassword.map((errorMessage, index) => (
-
- {errorMessage}
-
- ))}
-
-
- (this.repeatPasswordInput = ref)}
- selectionColor={commonColor.textColor}
- onSubmitEditing={() => Keyboard.dismiss()}
- returnKeyType={'next'}
- placeholder={I18n.t('authentication.repeatPassword')}
- placeholderTextColor={commonColor.placeholderTextColor}
- style={formStyle.inputField}
- autoCapitalize="none"
- blurOnSubmit={false}
- autoCorrect={false}
- keyboardType={'default'}
- onChangeText={(text) => this.setState({ repeatPassword: text })}
- secureTextEntry={hideRepeatPassword}
- />
- this.setState({ hideRepeatPassword: !hideRepeatPassword })}
- style={formStyle.inputIcon}
- />
-
- {this.state.errorRepeatPassword &&
- this.state.errorRepeatPassword.map((errorMessage, index) => (
-
- {errorMessage}
-
- ))}
- {loading ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
- );
- }
-}
diff --git a/src/screens/auth/retrieve-password/RetrievePassword.tsx b/src/screens/auth/retrieve-password/RetrievePassword.tsx
index 36f99fcdd..188c3a349 100644
--- a/src/screens/auth/retrieve-password/RetrievePassword.tsx
+++ b/src/screens/auth/retrieve-password/RetrievePassword.tsx
@@ -228,7 +228,7 @@ export default class RetrievePassword extends BaseScreen {
{captchaSiteKey && captchaBaseUrl && !captcha && (
;
};
+ public static getURLParameters(url: string): Record {
+ const res = {} as Record;
+ if ( url ) {
+ const params = url.split('?')?.[1]?.split('&');
+ params?.forEach(param => {
+ const paramParts = param.split('=');
+ if ( paramParts.length === 2 ) {
+ res[paramParts[0]] = paramParts[1];
+ }
+ });
+ }
+ return res;
+ }
+
public static async checkForUpdate(): Promise {
try {
return await checkVersion();