diff --git a/.changeset/calm-deers-hug.md b/.changeset/calm-deers-hug.md
new file mode 100644
index 00000000..2a7aeca3
--- /dev/null
+++ b/.changeset/calm-deers-hug.md
@@ -0,0 +1,6 @@
+---
+"@asgardeo/react": patch
+"@asgardeo/js": patch
+---
+
+Improvements to Identifier first authenticator in the React SDK
diff --git a/packages/core/src/i18n/screens/common/en-US.ts b/packages/core/src/i18n/screens/common/en-US.ts
index 2e90d059..5c66e5c7 100644
--- a/packages/core/src/i18n/screens/common/en-US.ts
+++ b/packages/core/src/i18n/screens/common/en-US.ts
@@ -28,5 +28,5 @@ export const common: Common = {
'privacy.policy': 'Privacy Policy',
register: 'Register',
'site.title': 'WSO2 Identity Server',
- 'terms.of.service': 'Terms of Servicet',
+ 'terms.of.service': 'Terms of Service',
};
diff --git a/packages/core/src/i18n/screens/identifierFirst/en-US.ts b/packages/core/src/i18n/screens/identifierFirst/en-US.ts
new file mode 100644
index 00000000..f783f78f
--- /dev/null
+++ b/packages/core/src/i18n/screens/identifierFirst/en-US.ts
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {IdentifierFirst} from './model';
+
+export const identifierFirst: IdentifierFirst = {
+ continue: 'Continue',
+ 'enter.your.username': 'Enter your username',
+ 'login.button': 'Sign In',
+ 'login.heading': 'Sign In',
+ 'remember.me': 'Remember me on this computer',
+ retry: 'Login failed! Please check your username and password and try again.',
+ username: 'Username',
+};
diff --git a/packages/core/src/i18n/screens/identifierFirst/fr-FR.ts b/packages/core/src/i18n/screens/identifierFirst/fr-FR.ts
new file mode 100644
index 00000000..05a834d9
--- /dev/null
+++ b/packages/core/src/i18n/screens/identifierFirst/fr-FR.ts
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {IdentifierFirst} from './model';
+
+export const identifierFirst: IdentifierFirst = {
+ continue: 'Continuer',
+ 'enter.your.username': "Entrez votre nom d'utilisateur",
+ 'login.button': 'Se connecter',
+ 'login.heading': 'Se connecter',
+ 'remember.me': 'Se souvenir de moi sur cet ordinateur',
+ retry: "Échec de la connexion! Veuillez vérifier votre nom d'utilisateur et votre mot de passe et réessayer.",
+ username: "Nom d'utilisateur",
+};
diff --git a/packages/core/src/i18n/screens/identifierFirst/model.ts b/packages/core/src/i18n/screens/identifierFirst/model.ts
new file mode 100644
index 00000000..739eb60c
--- /dev/null
+++ b/packages/core/src/i18n/screens/identifierFirst/model.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Interface for the IdentifierFirst text.
+ */
+export interface IdentifierFirst {
+ continue: string;
+ 'enter.your.username': string;
+ 'login.button': string;
+ 'login.heading': string;
+ 'remember.me': string;
+ retry: string;
+ username: string;
+}
diff --git a/packages/core/src/i18n/screens/keys.ts b/packages/core/src/i18n/screens/keys.ts
index 7f777d6b..2d074abd 100644
--- a/packages/core/src/i18n/screens/keys.ts
+++ b/packages/core/src/i18n/screens/keys.ts
@@ -75,6 +75,23 @@ interface Keys {
placeholder: string;
};
};
+ identifierFirst: {
+ button: string;
+ continue: string;
+ enter: {
+ your: {
+ username: string;
+ };
+ };
+ login: {
+ heading: string;
+ };
+ remember: {
+ me: string;
+ };
+ retry: string;
+ username: string;
+ };
login: {
button: string;
enter: {
@@ -183,6 +200,23 @@ export const keys: Keys = {
placeholder: 'emailOtp.username.placeholder',
},
},
+ identifierFirst: {
+ button: 'identifierFirst.button',
+ continue: 'identifierFirst.continue',
+ enter: {
+ your: {
+ username: 'identifierFirst.enter.your.username',
+ },
+ },
+ login: {
+ heading: 'identifierFirst.login.heading',
+ },
+ remember: {
+ me: 'identifierFirst.remember.me',
+ },
+ retry: 'identifierFirst.retry',
+ username: 'identifierFirst.username',
+ },
login: {
button: 'login.login.button',
enter: {
diff --git a/packages/core/src/i18n/screens/model.ts b/packages/core/src/i18n/screens/model.ts
index 3c15943d..f9c28a87 100644
--- a/packages/core/src/i18n/screens/model.ts
+++ b/packages/core/src/i18n/screens/model.ts
@@ -17,6 +17,7 @@
*/
import {Common} from './common/model';
+import {IdentifierFirst} from './identifierFirst/model';
import {Login} from './login/model';
import {TOTP} from './totp/model';
@@ -25,6 +26,7 @@ import {TOTP} from './totp/model';
*/
export interface TextPreference {
common: Common;
+ identifierFirst: IdentifierFirst;
login: Login;
totp: TOTP;
}
@@ -32,4 +34,4 @@ export interface TextPreference {
/**
* Interface for the return type of the getLocalization function.
*/
-export type TextObject = Login | TOTP | Common;
+export type TextObject = Login | TOTP | Common | IdentifierFirst;
diff --git a/packages/react/src/assets/building-icon.svg b/packages/react/src/assets/building-icon.svg
new file mode 100644
index 00000000..fc8ff94a
--- /dev/null
+++ b/packages/react/src/assets/building-icon.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/packages/react/src/components/SignIn/SignIn.tsx b/packages/react/src/components/SignIn/SignIn.tsx
index 73844755..682a37d7 100644
--- a/packages/react/src/components/SignIn/SignIn.tsx
+++ b/packages/react/src/components/SignIn/SignIn.tsx
@@ -62,7 +62,17 @@ import IdentifierFirst from './fragments/IdentifierFirst';
* @returns {ReactElement} - React element.
*/
const SignIn: FC = (props: SignInProps): ReactElement => {
- const {brandingProps, showFooter = true, showLogo = true, showSignUp} = props;
+ const {
+ basicAuthChildren,
+ brandingProps,
+ emailOtpChildren,
+ identifierFirstChildren,
+ showFooter = true,
+ showLogo = true,
+ showSignUp,
+ smsOtpChildren,
+ totpChildren,
+ } = props;
const [isComponentLoading, setIsComponentLoading] = useState(true);
const [alert, setAlert] = useState();
@@ -231,7 +241,9 @@ const SignIn: FC = (props: SignInProps): ReactElement => {
(auth: Authenticator) => auth.authenticatorId !== usernamePasswordAuthenticator.authenticatorId,
),
)}
- />
+ >
+ {basicAuthChildren}
+
);
}
@@ -252,7 +264,9 @@ const SignIn: FC = (props: SignInProps): ReactElement => {
(auth: Authenticator) => auth.authenticatorId !== identifierFirstAuthenticator.authenticatorId,
),
)}
- />
+ >
+ {identifierFirstChildren}
+
);
}
@@ -264,7 +278,9 @@ const SignIn: FC = (props: SignInProps): ReactElement => {
authenticator={authenticators[0]}
alert={alert}
handleAuthenticate={handleAuthenticate}
- />
+ >
+ {totpChildren}
+
);
}
if (
@@ -279,7 +295,9 @@ const SignIn: FC = (props: SignInProps): ReactElement => {
brandingProps={brandingProps}
authenticator={authenticators[0]}
handleAuthenticate={handleAuthenticate}
- />
+ >
+ {emailOtpChildren}
+
);
}
@@ -295,7 +313,9 @@ const SignIn: FC = (props: SignInProps): ReactElement => {
brandingProps={brandingProps}
authenticator={authenticators[0]}
handleAuthenticate={handleAuthenticate}
- />
+ >
+ {smsOtpChildren}
+
);
}
}
diff --git a/packages/react/src/components/SignIn/fragments/BasicAuth.tsx b/packages/react/src/components/SignIn/fragments/BasicAuth.tsx
index 722eba08..bc1baadf 100644
--- a/packages/react/src/components/SignIn/fragments/BasicAuth.tsx
+++ b/packages/react/src/components/SignIn/fragments/BasicAuth.tsx
@@ -18,7 +18,7 @@
import {ScreenType, keys} from '@asgardeo/js';
import {CircularProgress, Grid, Skeleton} from '@oxygen-ui/react';
-import {ReactElement, useContext, useState} from 'react';
+import {PropsWithChildren, ReactElement, useContext, useState} from 'react';
import AsgardeoContext from '../../../contexts/asgardeo-context';
import useTranslations from '../../../hooks/use-translations';
import BasicAuthProps from '../../../models/basic-auth-props';
@@ -41,15 +41,15 @@ import './basic-auth.scss';
const BasicAuth = ({
handleAuthenticate,
authenticator,
+ children,
alert,
brandingProps,
showSelfSignUp,
renderLoginOptions,
-}: BasicAuthProps): ReactElement => {
- const [username, setUsername] = useState('');
+}: PropsWithChildren): ReactElement => {
const [password, setPassword] = useState('');
- const {isAuthLoading} = useContext(AsgardeoContext);
+ const {isAuthLoading, username, setUsername} = useContext(AsgardeoContext);
const {t, isLoading} = useTranslations({
componentLocaleOverride: brandingProps?.locale,
@@ -103,6 +103,8 @@ const BasicAuth = ({
onChange={(e: React.ChangeEvent): void => setPassword(e.target.value)}
/>
+ {children}
+
{t(keys.login.button)}
diff --git a/packages/react/src/components/SignIn/fragments/EmailOtp.tsx b/packages/react/src/components/SignIn/fragments/EmailOtp.tsx
index eb94da42..1fb8e7a0 100644
--- a/packages/react/src/components/SignIn/fragments/EmailOtp.tsx
+++ b/packages/react/src/components/SignIn/fragments/EmailOtp.tsx
@@ -18,7 +18,7 @@
import {ScreenType, keys} from '@asgardeo/js';
import {CircularProgress, Skeleton} from '@oxygen-ui/react';
-import {ReactElement, useContext, useState} from 'react';
+import {PropsWithChildren, ReactElement, useContext, useState} from 'react';
import AsgardeoContext from '../../../contexts/asgardeo-context';
import useTranslations from '../../../hooks/use-translations';
import EmailOtpProps from '../../../models/email-otp-props';
@@ -35,7 +35,7 @@ import './email-otp.scss';
* @param {AlertType} props.alert - Alert type.
* @return {ReactElement}
*/
-const EmailOtp = ({alert, brandingProps, authenticator, handleAuthenticate}: EmailOtpProps): ReactElement => {
+const EmailOtp = ({alert, brandingProps, authenticator, children, handleAuthenticate}: PropsWithChildren): ReactElement => {
const [inputValue, setInputValue] = useState();
const [isContinueLoading, setIsContinueLoading] = useState(false);
const [isResendLoading, setIsResendLoading] = useState(false);
@@ -90,6 +90,8 @@ const EmailOtp = ({alert, brandingProps, authenticator, handleAuthenticate}: Ema
onChange={(e: React.ChangeEvent): void => setInputValue(e.target.value)}
/>
+ { children }
+
{
- const [username, setUsername] = useState('');
-
- const {isAuthLoading} = useContext(AsgardeoContext);
+}: PropsWithChildren): ReactElement => {
+ const {isAuthLoading, username, setUsername} = useContext(AsgardeoContext);
const {t, isLoading} = useTranslations({
componentLocaleOverride: brandingProps?.locale,
@@ -91,6 +90,8 @@ const IdentifierFirst = ({
onChange={(e: React.ChangeEvent): void => setUsername(e.target.value)}
/>
+ {children}
+
{t(keys.login.button)}
diff --git a/packages/react/src/components/SignIn/fragments/LoginOptionsBox.tsx b/packages/react/src/components/SignIn/fragments/LoginOptionsBox.tsx
index 537b4246..3faeedab 100644
--- a/packages/react/src/components/SignIn/fragments/LoginOptionsBox.tsx
+++ b/packages/react/src/components/SignIn/fragments/LoginOptionsBox.tsx
@@ -17,6 +17,7 @@
*/
import {ReactElement} from 'react';
+import buildingIcon from '../../../assets/building-icon.svg';
import emailSolid from '../../../assets/email-solid.svg';
import smsIcon from '../../../assets/sms-icon.svg';
import facebook from '../../../assets/social-logins/facebook.svg';
@@ -55,7 +56,7 @@ const LoginOptionsBox = ({
}
+ startIcon={
}
onClick={handleOnClick}
disabled={isAuthLoading}
>
diff --git a/packages/react/src/components/SignIn/fragments/SmsOtp.tsx b/packages/react/src/components/SignIn/fragments/SmsOtp.tsx
index e97f60a8..6f3c5fe8 100644
--- a/packages/react/src/components/SignIn/fragments/SmsOtp.tsx
+++ b/packages/react/src/components/SignIn/fragments/SmsOtp.tsx
@@ -18,12 +18,18 @@
import {ScreenType, keys} from '@asgardeo/js';
import {CircularProgress} from '@oxygen-ui/react';
-import {ReactElement, useState} from 'react';
+import {PropsWithChildren, ReactElement, useState} from 'react';
import useTranslations from '../../../hooks/use-translations';
import EmailOtpProps from '../../../models/email-otp-props';
import {SignIn as UISignIn} from '../../../oxygen-ui-react-auth-components';
-const SmsOtp = ({alert, brandingProps, authenticator, handleAuthenticate}: EmailOtpProps): ReactElement => {
+const SmsOtp = ({
+ alert,
+ brandingProps,
+ authenticator,
+ children,
+ handleAuthenticate,
+}: PropsWithChildren): ReactElement => {
const [otp, setOtp] = useState();
const {isLoading, t} = useTranslations({
@@ -50,6 +56,8 @@ const SmsOtp = ({alert, brandingProps, authenticator, handleAuthenticate}: Email
+ {children}
+
{
+const Totp = ({
+ brandingProps,
+ authenticator,
+ children,
+ handleAuthenticate,
+ alert,
+}: PropsWithChildren): ReactElement => {
const [totp, setTotp] = useState();
const {isAuthLoading} = useContext(AsgardeoContext);
@@ -79,6 +85,8 @@ const Totp = ({brandingProps, authenticator, handleAuthenticate, alert}: TotpPro
+ {children}
+
{
const contextValue: AuthContext = useContext(AsgardeoContext);
- const {user, isAuthenticated, accessToken, authResponse} = contextValue;
+ const {accessToken, authResponse, isAuthenticated, setUsername, user, username} = contextValue;
const signOut: () => void = () => {
signOutApiCall().then(() => {
@@ -47,8 +47,10 @@ const useAuthentication = (): UseAuthentication => {
accessToken,
authResponse,
isAuthenticated,
+ setUsername,
signOut,
user,
+ username,
};
};
diff --git a/packages/react/src/models/auth-context.ts b/packages/react/src/models/auth-context.ts
index cd618a32..ae6794de 100644
--- a/packages/react/src/models/auth-context.ts
+++ b/packages/react/src/models/auth-context.ts
@@ -35,7 +35,9 @@ interface AuthContext {
setIsTextLoading: (value: boolean) => void;
setOnSignIn: (response?: any) => void | Promise;
setOnSignOut: (response?: any) => void | Promise;
+ setUsername: (username: string) => void;
user: MeAPIResponse;
+ username: string;
}
export default AuthContext;
diff --git a/packages/react/src/models/sign-in.ts b/packages/react/src/models/sign-in.ts
index 4d4b2c8b..fecde462 100644
--- a/packages/react/src/models/sign-in.ts
+++ b/packages/react/src/models/sign-in.ts
@@ -20,10 +20,15 @@ import {BrandingProps} from '@asgardeo/js';
import {ReactElement} from 'react';
export interface SignInProps {
+ basicAuthChildren?: ReactElement;
brandingProps?: BrandingProps;
+ emailOtpChildren?: ReactElement;
+ identifierFirstChildren?: ReactElement;
showFooter?: boolean;
showLogo?: boolean;
showSignUp?: boolean;
+ smsOtpChildren?: ReactElement;
+ totpChildren?: ReactElement;
}
export type AlertType = {
diff --git a/packages/react/src/models/use-authentication.ts b/packages/react/src/models/use-authentication.ts
index 695df5f7..9d6dc59d 100644
--- a/packages/react/src/models/use-authentication.ts
+++ b/packages/react/src/models/use-authentication.ts
@@ -22,8 +22,10 @@ interface UseAuthentication {
accessToken: string;
authResponse: AuthApiResponse;
isAuthenticated: Promise | boolean;
+ setUsername: (username: string) => void;
signOut: () => void;
user: MeAPIResponse;
+ username: string;
}
export default UseAuthentication;
diff --git a/packages/react/src/providers/AsgardeoProvider.tsx b/packages/react/src/providers/AsgardeoProvider.tsx
index 6513ed47..77a47fa4 100644
--- a/packages/react/src/providers/AsgardeoProvider.tsx
+++ b/packages/react/src/providers/AsgardeoProvider.tsx
@@ -60,6 +60,7 @@ const AsgardeoProvider: FC> = (
const [isTextLoading, setIsTextLoading] = useState(true);
const [isAuthLoading, setIsAuthLoading] = useState(false);
const [authResponse, setAuthResponse] = useState();
+ const [username, setUsername] = useState('');
const onSignInRef: React.MutableRefObject = useRef();
const onSignOutRef: React.MutableRefObject = useRef();
@@ -146,7 +147,9 @@ const AsgardeoProvider: FC> = (
setIsTextLoading,
setOnSignIn,
setOnSignOut,
+ setUsername,
user,
+ username,
}),
[
accessToken,
@@ -160,7 +163,9 @@ const AsgardeoProvider: FC> = (
setAuthentication,
setOnSignIn,
setOnSignOut,
+ setUsername,
user,
+ username,
],
);