From bc005ef1a21bea7894e3852dcfa892d8b63e0e98 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Wed, 26 Jun 2024 16:51:11 +0530 Subject: [PATCH 1/4] feat(react): Improvements to Identifier first authenticator in the React SDK --- .../core/src/i18n/screens/common/en-US.ts | 4 +-- .../src/i18n/screens/identifierFirst/en-US.ts | 29 ++++++++++++++++ .../src/i18n/screens/identifierFirst/fr-FR.ts | 29 ++++++++++++++++ .../src/i18n/screens/identifierFirst/model.ts | 30 ++++++++++++++++ packages/core/src/i18n/screens/keys.ts | 34 +++++++++++++++++++ packages/core/src/i18n/screens/model.ts | 4 ++- packages/react/src/assets/building-icon.svg | 25 ++++++++++++++ .../components/SignIn/fragments/BasicAuth.tsx | 5 +-- .../SignIn/fragments/IdentifierFirst.tsx | 5 +-- .../SignIn/fragments/LoginOptionsBox.tsx | 3 +- .../react/src/hooks/use-authentication.ts | 4 ++- packages/react/src/models/auth-context.ts | 2 ++ .../react/src/models/use-authentication.ts | 2 ++ .../react/src/providers/AsgardeoProvider.tsx | 5 +++ 14 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 packages/core/src/i18n/screens/identifierFirst/en-US.ts create mode 100644 packages/core/src/i18n/screens/identifierFirst/fr-FR.ts create mode 100644 packages/core/src/i18n/screens/identifierFirst/model.ts create mode 100644 packages/react/src/assets/building-icon.svg diff --git a/packages/core/src/i18n/screens/common/en-US.ts b/packages/core/src/i18n/screens/common/en-US.ts index 2e90d059..180d6546 100644 --- a/packages/core/src/i18n/screens/common/en-US.ts +++ b/packages/core/src/i18n/screens/common/en-US.ts @@ -24,9 +24,9 @@ export const common: Common = { error: 'Something went wrong. Please try again.', 'multiple.options.prefix': 'Sign in with', or: 'OR', - 'prefix.register': "Don't have an account?", + 'prefix.register': "Don't have an account? ", '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/fragments/BasicAuth.tsx b/packages/react/src/components/SignIn/fragments/BasicAuth.tsx index 722eba08..6a942597 100644 --- a/packages/react/src/components/SignIn/fragments/BasicAuth.tsx +++ b/packages/react/src/components/SignIn/fragments/BasicAuth.tsx @@ -46,10 +46,9 @@ const BasicAuth = ({ showSelfSignUp, renderLoginOptions, }: BasicAuthProps): ReactElement => { - const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); - const {isAuthLoading} = useContext(AsgardeoContext); + const {isAuthLoading, username, setUsername} = useContext(AsgardeoContext); const {t, isLoading} = useTranslations({ componentLocaleOverride: brandingProps?.locale, @@ -114,8 +113,6 @@ const BasicAuth = ({ password, username, }); - setUsername(''); - setPassword(''); }} > {t(keys.login.button)} diff --git a/packages/react/src/components/SignIn/fragments/IdentifierFirst.tsx b/packages/react/src/components/SignIn/fragments/IdentifierFirst.tsx index 3bf7be50..f3359f5c 100644 --- a/packages/react/src/components/SignIn/fragments/IdentifierFirst.tsx +++ b/packages/react/src/components/SignIn/fragments/IdentifierFirst.tsx @@ -46,9 +46,7 @@ const IdentifierFirst = ({ showSelfSignUp, renderLoginOptions, }: BasicAuthProps): ReactElement => { - const [username, setUsername] = useState(''); - - const {isAuthLoading} = useContext(AsgardeoContext); + const {isAuthLoading, username, setUsername} = useContext(AsgardeoContext); const {t, isLoading} = useTranslations({ componentLocaleOverride: brandingProps?.locale, @@ -101,7 +99,6 @@ const IdentifierFirst = ({ handleAuthenticate(authenticator.authenticatorId, { username, }); - setUsername(''); }} > {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={{socialName}} onClick={handleOnClick} disabled={isAuthLoading} > diff --git a/packages/react/src/hooks/use-authentication.ts b/packages/react/src/hooks/use-authentication.ts index 2864cd30..e0beac02 100644 --- a/packages/react/src/hooks/use-authentication.ts +++ b/packages/react/src/hooks/use-authentication.ts @@ -32,7 +32,7 @@ import UseAuthentication from '../models/use-authentication'; const useAuthentication = (): UseAuthentication => { 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/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, ], ); From 43444087466db1c12fdb97e283192d5e2ccc00f1 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Wed, 26 Jun 2024 16:57:28 +0530 Subject: [PATCH 2/4] =?UTF-8?q?chore(workspace):=20=F0=9F=94=96=20Add=20ch?= =?UTF-8?q?angeset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/calm-deers-hug.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/calm-deers-hug.md 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 From 5462aab1ce426a666519167afacf14ab94678aaf Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Wed, 26 Jun 2024 17:20:58 +0530 Subject: [PATCH 3/4] chore(react): Improve self-sign up link UI --- packages/core/src/i18n/screens/common/en-US.ts | 2 +- .../react/src/components/SignIn/fragments/basic-auth.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/i18n/screens/common/en-US.ts b/packages/core/src/i18n/screens/common/en-US.ts index 180d6546..5c66e5c7 100644 --- a/packages/core/src/i18n/screens/common/en-US.ts +++ b/packages/core/src/i18n/screens/common/en-US.ts @@ -24,7 +24,7 @@ export const common: Common = { error: 'Something went wrong. Please try again.', 'multiple.options.prefix': 'Sign in with', or: 'OR', - 'prefix.register': "Don't have an account? ", + 'prefix.register': "Don't have an account?", 'privacy.policy': 'Privacy Policy', register: 'Register', 'site.title': 'WSO2 Identity Server', diff --git a/packages/react/src/components/SignIn/fragments/basic-auth.scss b/packages/react/src/components/SignIn/fragments/basic-auth.scss index b4274107..50a8fac3 100644 --- a/packages/react/src/components/SignIn/fragments/basic-auth.scss +++ b/packages/react/src/components/SignIn/fragments/basic-auth.scss @@ -45,6 +45,10 @@ } } +.asgardeo-register-link { + margin-left: 3px; +} + @keyframes fade-in { to { opacity: 1; From 23ad290b4988a4eea09b9871b36e8538832b1f03 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Wed, 26 Jun 2024 18:49:09 +0530 Subject: [PATCH 4/4] feat(react): Add support to passing react elements as child props to components --- .../react/src/components/SignIn/SignIn.tsx | 32 +++++++++++++++---- .../components/SignIn/fragments/BasicAuth.tsx | 7 ++-- .../components/SignIn/fragments/EmailOtp.tsx | 6 ++-- .../SignIn/fragments/IdentifierFirst.tsx | 7 ++-- .../components/SignIn/fragments/SmsOtp.tsx | 12 +++++-- .../src/components/SignIn/fragments/Totp.tsx | 12 +++++-- packages/react/src/models/sign-in.ts | 5 +++ 7 files changed, 65 insertions(+), 16 deletions(-) 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 6a942597..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,11 +41,12 @@ import './basic-auth.scss'; const BasicAuth = ({ handleAuthenticate, authenticator, + children, alert, brandingProps, showSelfSignUp, renderLoginOptions, -}: BasicAuthProps): ReactElement => { +}: PropsWithChildren): ReactElement => { const [password, setPassword] = useState(''); const {isAuthLoading, username, setUsername} = useContext(AsgardeoContext); @@ -102,6 +103,8 @@ const BasicAuth = ({ onChange={(e: React.ChangeEvent): void => setPassword(e.target.value)} /> + {children} + { +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 } + { +}: PropsWithChildren): ReactElement => { const {isAuthLoading, username, setUsername} = useContext(AsgardeoContext); const {t, isLoading} = useTranslations({ @@ -89,6 +90,8 @@ const IdentifierFirst = ({ onChange={(e: React.ChangeEvent): void => setUsername(e.target.value)} /> + {children} + { +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} +