diff --git a/.vscode/settings.json b/.vscode/settings.json index 522634ab..2e34a441 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,8 @@ "workspace", "core", "react", - "auth-components" + "auth-components", + "sample-app" ], "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" diff --git a/package.json b/package.json index 220637cb..10cd326a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "nx": "18.2.4" }, "workspaces": [ - "packages/*" + "packages/*", + "recipes/*" ] } diff --git a/packages/core/src/api/sign-out.ts b/packages/core/src/api/sign-out.ts index c5f4b771..54235386 100644 --- a/packages/core/src/api/sign-out.ts +++ b/packages/core/src/api/sign-out.ts @@ -18,6 +18,7 @@ import {AuthClient, ResponseMode} from '../auth-client'; import AsgardeoUIException from '../exception'; +import {UIAuthClient} from '../models/auth-config'; /** * Sign out the user. @@ -45,9 +46,11 @@ const signOut = async (): Promise => { const formBody: URLSearchParams = new URLSearchParams(); + const authClient: UIAuthClient = AuthClient.getInstance(); + try { - formBody.append('id_token_hint', await AuthClient.getInstance().getIDToken()); - formBody.append('client_id', (await AuthClient.getInstance().getDataLayer().getConfigData()).clientID); + formBody.append('id_token_hint', await authClient.getIDToken()); + formBody.append('client_id', (await authClient.getDataLayer().getConfigData()).clientID); formBody.append('response_mode', ResponseMode.direct); } catch (error) { throw new AsgardeoUIException('JS_UI_CORE-SIGNOUT-SO-IV', 'Failed to build the body of the signout request.'); @@ -60,7 +63,8 @@ const signOut = async (): Promise => { }; try { - signOutUrl = (await AuthClient.getInstance().getOIDCServiceEndpoints()).endSessionEndpoint; + const {endSessionEndpoint} = await authClient.getOIDCServiceEndpoints(); + signOutUrl = endSessionEndpoint; } catch (error) { throw new AsgardeoUIException('JS_UI_CORE-SIGNOUT-SO-NF', 'Failed to retrieve the sign out endpoint.'); } diff --git a/packages/core/src/branding/get-branding.ts b/packages/core/src/branding/get-branding.ts index 0f63c92c..81c051ec 100644 --- a/packages/core/src/branding/get-branding.ts +++ b/packages/core/src/branding/get-branding.ts @@ -41,8 +41,14 @@ const getBranding = async (props: GetBrandingProps): Promise => { if (!merged) { let brandingFromConsole: BrandingPreferenceAPIResponse; - if ((await AuthClient.getInstance().getDataLayer().getConfigData()).enableConsoleBranding ?? true) { - brandingFromConsole = await getBrandingPreference(); + try { + if ((await AuthClient.getInstance().getDataLayer().getConfigData()).enableConsoleBranding ?? true) { + brandingFromConsole = await getBrandingPreference(); + } + } catch { + /** + * If the branding from the console cannot be fetched, proceed with the default branding. + */ } if (brandingFromConsole?.preference?.configs?.isBrandingEnabled) { diff --git a/packages/core/src/i18n/get-localization.ts b/packages/core/src/i18n/get-localization.ts index 84844730..fc708692 100644 --- a/packages/core/src/i18n/get-localization.ts +++ b/packages/core/src/i18n/get-localization.ts @@ -21,7 +21,6 @@ import merge from 'lodash.merge'; import {TextObject} from './screens/model'; import getBrandingPreferenceText from '../api/get-branding-preference-text'; import {AuthClient} from '../auth-client'; -import AsgardeoUIException from '../exception'; import {UIAuthConfig} from '../models/auth-config'; import {BrandingPreferenceTypes} from '../models/branding-api-response'; import {BrandingPreferenceTextAPIResponse} from '../models/branding-text-api-response'; @@ -42,21 +41,21 @@ const getLocalization = async (props: GetLocalizationProps): Promise const configData: AuthClientConfig = await AuthClient.getInstance().getDataLayer().getConfigData(); + const DEFAULT_NAME: string = 'carbon.super'; + try { if (configData.enableConsoleTextBranding ?? true) { textFromConsoleBranding = await getBrandingPreferenceText({ locale, - name: configData.name ?? 'carbon.super', + name: configData.name ?? DEFAULT_NAME, screen, type: configData.type ?? BrandingPreferenceTypes.Org, }); } } catch (error) { - throw new AsgardeoUIException( - 'JS_UI_CORE-LOCALIZATION-IV', - 'Error occurred while fetching text from console branding.', - error.stack, - ); + /** + * If the branding from the console cannot be fetched, proceed with the default branding. + */ } /** diff --git a/packages/core/src/i18n/screens/common/en-US.ts b/packages/core/src/i18n/screens/common/en-US.ts index 9a57d9c6..0d732c12 100644 --- a/packages/core/src/i18n/screens/common/en-US.ts +++ b/packages/core/src/i18n/screens/common/en-US.ts @@ -20,7 +20,11 @@ import {Common} from './model'; export const common: Common = { copyright: '© {{currentYear}} WSO2 LLC.', + error: 'Something went wrong. Please try again.', + or: 'OR', + 'prefix.register': "Don't have an account?", 'privacy.policy': 'Privacy Policy', + register: 'Register', 'site.title': 'WSO2 Identity Server', 'terms.of.service': 'Terms of Servicet', }; diff --git a/packages/core/src/i18n/screens/common/fr-FR.ts b/packages/core/src/i18n/screens/common/fr-FR.ts index 6cac3ee7..fe34a940 100644 --- a/packages/core/src/i18n/screens/common/fr-FR.ts +++ b/packages/core/src/i18n/screens/common/fr-FR.ts @@ -20,7 +20,11 @@ import {Common} from './model'; export const common: Common = { copyright: '© {{currentYear}} WSO2 LLC.', + error: "Quelque chose s'est mal passé. Veuillez réessayer.", + or: 'OU', + 'prefix.register': "Vous n'avez pas de compte?", 'privacy.policy': 'Politique de confidentialité', + register: "S'inscrire", 'site.title': 'Identity Server WSO2', 'terms.of.service': 'Conditions de service', }; diff --git a/packages/core/src/i18n/screens/common/model.ts b/packages/core/src/i18n/screens/common/model.ts index d63ac8dd..d2aaadc0 100644 --- a/packages/core/src/i18n/screens/common/model.ts +++ b/packages/core/src/i18n/screens/common/model.ts @@ -21,7 +21,11 @@ */ export interface Common { copyright: string; + error: string; + or: string; + 'prefix.register': string; 'privacy.policy': string; + register: string; 'site.title': string; 'terms.of.service': string; } diff --git a/packages/core/src/i18n/screens/emailOtp/en-US.ts b/packages/core/src/i18n/screens/emailOtp/en-US.ts new file mode 100644 index 00000000..cc0c43fe --- /dev/null +++ b/packages/core/src/i18n/screens/emailOtp/en-US.ts @@ -0,0 +1,26 @@ +/** + * 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 {EmailOTP} from './model'; + +export const emailOtp: EmailOTP = { + continue: 'Continue', + 'email.otp.heading': 'OTP Verification', + 'enter.verification.code.got.by.device': 'Enter the code sent to your email ID.', + 'resend.code': 'Resend code', +}; diff --git a/packages/core/src/i18n/screens/emailOtp/model.ts b/packages/core/src/i18n/screens/emailOtp/model.ts new file mode 100644 index 00000000..8b8b9b41 --- /dev/null +++ b/packages/core/src/i18n/screens/emailOtp/model.ts @@ -0,0 +1,27 @@ +/** + * 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 TOTP text. + */ +export interface EmailOTP { + continue: string; + 'email.otp.heading': string; + 'enter.verification.code.got.by.device': string; + 'resend.code': string; +} diff --git a/packages/core/src/i18n/screens/keys.ts b/packages/core/src/i18n/screens/keys.ts index 52a63054..52f34777 100644 --- a/packages/core/src/i18n/screens/keys.ts +++ b/packages/core/src/i18n/screens/keys.ts @@ -22,9 +22,15 @@ interface Keys { common: { copyright: string; + error: string; + or: string; + prefix: { + register: string; + }; privacy: { policy: string; }; + register: string; site: { title: string; }; @@ -34,6 +40,28 @@ interface Keys { }; }; }; + emailOtp: { + continue: string; + email: { + otp: { + heading: string; + }; + }; + enter: { + verification: { + code: { + got: { + by: { + device: string; + }; + }; + }; + }; + }; + resend: { + code: string; + }; + }; login: { button: string; enter: { @@ -49,8 +77,22 @@ interface Keys { remember: { me: string; }; + retry: string; username: string; }; + smsOtp: { + continue: string; + resend: { + code: string; + }; + + sms: { + otp: { + heading: string; + subheading: string; + }; + }; + }; totp: { continue: string; enroll: { @@ -75,9 +117,15 @@ interface Keys { export const keys: Keys = { common: { copyright: 'common.copyright', + error: 'common.error', + or: 'common.or', + prefix: { + register: 'common.prefix.register', + }, privacy: { policy: 'common.privacy.policy', }, + register: 'common.register', site: { title: 'common.site.title', }, @@ -87,6 +135,28 @@ export const keys: Keys = { }, }, }, + emailOtp: { + continue: 'emailOtp.continue', + email: { + otp: { + heading: 'emailOtp.email.otp.heading', + }, + }, + enter: { + verification: { + code: { + got: { + by: { + device: 'emailOtp.enter.verification.code.got.by.device', + }, + }, + }, + }, + }, + resend: { + code: 'emailOtp.resend.code', + }, + }, login: { button: 'login.login.button', enter: { @@ -102,8 +172,22 @@ export const keys: Keys = { remember: { me: 'login.remember.me', }, + retry: 'login.retry', username: 'login.username', }, + smsOtp: { + continue: 'smsOtp.continue', + resend: { + code: 'smsOtp.resend.code', + }, + + sms: { + otp: { + heading: 'smsOtp.sms.otp.heading', + subheading: 'smsOtp.sms.otp.subheading', + }, + }, + }, totp: { continue: 'totp.continue', enroll: { diff --git a/packages/core/src/i18n/screens/login/en-US.ts b/packages/core/src/i18n/screens/login/en-US.ts index 173742de..dff3ce0d 100644 --- a/packages/core/src/i18n/screens/login/en-US.ts +++ b/packages/core/src/i18n/screens/login/en-US.ts @@ -25,5 +25,6 @@ export const login: Login = { 'login.heading': 'Sign In', password: 'Password', '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/login/fr-FR.ts b/packages/core/src/i18n/screens/login/fr-FR.ts index f5277a65..571b8c15 100644 --- a/packages/core/src/i18n/screens/login/fr-FR.ts +++ b/packages/core/src/i18n/screens/login/fr-FR.ts @@ -25,5 +25,6 @@ export const login: Login = { 'login.heading': 'Se connecter', password: 'Mot de passe', '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/login/model.ts b/packages/core/src/i18n/screens/login/model.ts index b550eb90..e9e51dc7 100644 --- a/packages/core/src/i18n/screens/login/model.ts +++ b/packages/core/src/i18n/screens/login/model.ts @@ -26,5 +26,6 @@ export interface Login { 'login.heading': string; password: string; 'remember.me': string; + retry: string; username: string; } diff --git a/packages/core/src/i18n/screens/smsOtp/en-US.ts b/packages/core/src/i18n/screens/smsOtp/en-US.ts new file mode 100644 index 00000000..39078995 --- /dev/null +++ b/packages/core/src/i18n/screens/smsOtp/en-US.ts @@ -0,0 +1,26 @@ +/** + * 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 {SmsOTP} from './model'; + +export const smsOtp: SmsOTP = { + continue: 'Continue', + 'resend.code': 'Resend code', + 'sms.otp.heading': 'OTP Verification', + 'sms.otp.subheading': 'Enter the code sent to your mobile device.', +}; diff --git a/packages/core/src/i18n/screens/smsOtp/model.ts b/packages/core/src/i18n/screens/smsOtp/model.ts new file mode 100644 index 00000000..b44b4646 --- /dev/null +++ b/packages/core/src/i18n/screens/smsOtp/model.ts @@ -0,0 +1,27 @@ +/** + * 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 TOTP text. + */ +export interface SmsOTP { + continue: string; + 'resend.code': string; + 'sms.otp.heading': string; + 'sms.otp.subheading': string; +} diff --git a/packages/core/src/models/screen-type.ts b/packages/core/src/models/screen-type.ts index 686add22..078ee062 100644 --- a/packages/core/src/models/screen-type.ts +++ b/packages/core/src/models/screen-type.ts @@ -21,6 +21,8 @@ */ export enum ScreenType { Common = 'common', + EmailOTP = 'emailOtp', Login = 'login', + SMSOTP = 'smsOtp', TOTP = 'totp', } diff --git a/packages/react/declarations.d.ts b/packages/react/declarations.d.ts new file mode 100644 index 00000000..875a50f8 --- /dev/null +++ b/packages/react/declarations.d.ts @@ -0,0 +1,22 @@ +/** + * 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. + */ + +declare module '*.svg' { + const content: string; + export default content; +} diff --git a/packages/react/src/assets/email-solid.svg b/packages/react/src/assets/email-solid.svg new file mode 100644 index 00000000..a7a67eb4 --- /dev/null +++ b/packages/react/src/assets/email-solid.svg @@ -0,0 +1,21 @@ + + + + + diff --git a/packages/react/src/assets/social-logins/facebook.svg b/packages/react/src/assets/social-logins/facebook.svg new file mode 100644 index 00000000..b217c013 --- /dev/null +++ b/packages/react/src/assets/social-logins/facebook.svg @@ -0,0 +1,24 @@ + + + diff --git a/packages/react/src/assets/social-logins/github.svg b/packages/react/src/assets/social-logins/github.svg new file mode 100644 index 00000000..e1b19871 --- /dev/null +++ b/packages/react/src/assets/social-logins/github.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/packages/react/src/assets/social-logins/google.svg b/packages/react/src/assets/social-logins/google.svg new file mode 100644 index 00000000..6375b41b --- /dev/null +++ b/packages/react/src/assets/social-logins/google.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/packages/react/src/assets/social-logins/microsoft.svg b/packages/react/src/assets/social-logins/microsoft.svg new file mode 100644 index 00000000..431b21e9 --- /dev/null +++ b/packages/react/src/assets/social-logins/microsoft.svg @@ -0,0 +1,27 @@ + + + + + diff --git a/packages/react/src/components/SignIn/SignIn.tsx b/packages/react/src/components/SignIn/SignIn.tsx new file mode 100644 index 00000000..215699ca --- /dev/null +++ b/packages/react/src/components/SignIn/SignIn.tsx @@ -0,0 +1,347 @@ +/** + * 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 { + AsgardeoUIException, + AuthApiResponse, + AuthClient, + Authenticator, + Branding, + FlowStatus, + Metadata, + PromptType, + ScreenType, + UIAuthClient, + authenticate, + authorize, + getBranding, + keys, +} from '@asgardeo/js-ui-core'; +import {CircularProgress, ThemeProvider} from '@oxygen-ui/react'; +import {FC, ReactElement, useContext, useEffect, useState} from 'react'; +import BasicAuth from './fragments/BasicAuth'; +import EmailOtp from './fragments/EmailOtp'; +import LoginOptionsBox from './fragments/LoginOptionsBox'; +import SmsOtp from './fragments/SmsOtp'; +import Totp from './fragments/Totp'; +import AsgardeoContext from '../../contexts/asgardeo-context'; +import BrandingPreferenceContext from '../../contexts/branding-preference-context'; +import useAuthentication from '../../hooks/use-authentication'; +import {useConfig} from '../../hooks/use-config'; +import useTranslations from '../../hooks/use-translations'; +import AuthContext from '../../models/auth-context'; +import {AlertType, SignInProps} from '../../models/sign-in'; +import {SignIn as UISignIn} from '../../oxygen-ui-react-auth-components'; +import generateThemeSignIn from '../../theme/generate-theme-sign-in'; +import SPACryptoUtils from '../../utils/crypto-utils'; +import './sign-in.scss'; + +/** + * This component provides the sign-in functionality. + * + * @param {SignInProps} props - Props injected to the component. + * @param {BrandingProps} props.brandingProps - Branding related props. + * + * @returns {ReactElement} - React element. + */ +const SignIn: FC = (props: SignInProps) => { + const {brandingProps} = props; + const [authResponse, setAuthResponse] = useState(); + const [isComponentLoading, setIsComponentLoading] = useState(true); + const [alert, setAlert] = useState(); + const [showSelfSignUp, setShowSelfSignUp] = useState(true); + const [componentBranding, setComponentBranding] = useState(); + + const {isAuthenticated} = useAuthentication(); + + const authContext: AuthContext | undefined = useContext(AsgardeoContext); + + const {config} = useConfig(); + + const brandingPreference: Branding = useContext(BrandingPreferenceContext); + + const {isLoading, t} = useTranslations({ + componentLocaleOverride: brandingProps?.locale, + componentTextOverrides: brandingProps?.preference?.text, + screen: ScreenType.Common, + }); + + useEffect(() => { + getBranding({branding: brandingProps, merged: brandingPreference}).then((response: Branding) => { + setComponentBranding(response); + }); + + /** + * Calling authorize function and initiating the flow + */ + authorize() + .then((response: AuthApiResponse) => { + setAuthResponse(response); + setIsComponentLoading(false); + }) + .catch((error: Error) => { + setAlert({alertType: {error: true}, key: keys.common.error}); + setIsComponentLoading(false); + throw new AsgardeoUIException('REACT_UI-SIGN_IN-SI-SE01', 'Authorization failed', error.stack); + }); + }, [brandingPreference, brandingProps]); + + /** + * Handles the generalized authentication process. + * @param {string} authenticatorId - Authenticator ID. + * @param {object} [authParams] - Authentication parameters. + */ + const handleAuthenticate = async (authenticatorId: string, authParams?: {[key: string]: string}): Promise => { + setAlert(undefined); + + if (authResponse === undefined) { + throw new AsgardeoUIException('REACT_UI-SIGN_IN-HA-IV02', 'Auth response is undefined.'); + } + + setIsComponentLoading(true); + + const resp: AuthApiResponse = await authenticate({ + flowId: authResponse.flowId, + selectedAuthenticator: { + authenticatorId, + params: authParams, + }, + }); + + if (!authParams) { + const metaData: Metadata = resp.nextStep.authenticators[0].metadata; + if (metaData.promptType === PromptType.RedirectionPromt) { + /** + * Open a popup window to handle redirection prompts + */ + window.open( + metaData.additionalData?.redirectUrl, + resp.nextStep.authenticators[0].authenticator, + 'width=500,height=600', + ); + + /** + * Add an event listener to the window to capture the message from the popup + */ + window.addEventListener('message', function messageEventHandler(event: MessageEvent) { + /** + * Check the origin of the message to ensure it's from the popup window + */ + if (event.origin !== config.signInRedirectURL) return; + + const {code, state} = event.data; + + if (code && state) { + handleAuthenticate(resp.nextStep.authenticators[0].authenticatorId, {code, state}); + } + + /** + * Remove the event listener + */ + window.removeEventListener('message', messageEventHandler); + }); + } else if (metaData.promptType === PromptType.UserPrompt) { + setAuthResponse(resp); + } + } else if (resp.flowStatus === FlowStatus.SuccessCompleted && resp.authData) { + /** + * when the authentication is successful, generate the token + */ + setAuthResponse(resp); + + const authInstance: UIAuthClient = AuthClient.getInstance(); + const state: string = (await authInstance.getDataLayer().getTemporaryDataParameter('state')).toString(); + + await authInstance.requestAccessToken(resp.authData.code, resp.authData.session_state, state); + + authContext.setAuthentication(); + } else if (resp.flowStatus === FlowStatus.FailIncomplete) { + setAuthResponse({ + ...resp, + nextStep: authResponse.nextStep, + }); + + setAlert({alertType: {error: true}, key: keys.login.retry}); + } else { + setAuthResponse(resp); + setShowSelfSignUp(false); + } + + setIsComponentLoading(false); + }; + + const renderLoginOptions = (authenticators: Authenticator[]): ReactElement[] => { + const LoginOptions: ReactElement[] = []; + + authenticators.forEach((authenticator: Authenticator) => { + LoginOptions.push( + => handleAuthenticate(authenticator.authenticatorId)} + key={authenticator.authenticatorId} + />, + ); + }); + + return LoginOptions; + }; + + const renderSignIn = (): ReactElement => { + const {authenticators} = authResponse.nextStep; + + if (authenticators) { + const usernamePasswordAuthenticator: Authenticator = authenticators.find( + (authenticator: Authenticator) => authenticator.authenticator === 'Username & Password', + ); + + if (usernamePasswordAuthenticator) { + return ( + auth.authenticatorId !== usernamePasswordAuthenticator.authenticatorId, + ), + )} + /> + ); + } + + if (authenticators.length === 1) { + if (authenticators[0].authenticator === 'TOTP') { + return ( + + ); + } + if ( + // TODO: change after api based auth gets fixed + new SPACryptoUtils() + .base64URLDecode(authResponse.nextStep.authenticators[0].authenticatorId) + .split(':')[0] === 'email-otp-authenticator' + ) { + return ( + + ); + } + + if ( + // TODO: change after api based auth gets fixed + new SPACryptoUtils() + .base64URLDecode(authResponse.nextStep.authenticators[0].authenticatorId) + .split(':')[0] === 'sms-otp-authenticator' + ) { + return ( + + ); + } + } + + /** + * If there are multiple authenticators without Username and password, render the multiple options screen + */ + if (authenticators.length > 1) { + return ( + + + Sign In + + {renderLoginOptions(authenticators)} + + ); + } + } + return null; + }; + + /** + * Renders the circular progress component while the component or text is loading. + */ + if (isComponentLoading || isLoading) { + return ( +
+ +
+ ); + } + + const imgUrl: string = brandingPreference?.preference?.theme?.LIGHT?.images?.logo?.imgURL; + let copyrightText: string = t(keys.common.copyright); + + if (copyrightText.includes('{{currentYear}}')) { + copyrightText = copyrightText.replace('{{currentYear}}', new Date().getFullYear().toString()); + } + + return ( + + + + {authResponse?.flowStatus !== FlowStatus.SuccessCompleted && !isAuthenticated && ( + <> + {renderSignIn()} + + + {t(keys.common.terms.of.service)} + + ), + }, + { + children: ( + + {t(keys.common.privacy.policy)} + + ), + }, + {children: {componentBranding?.locale ?? 'en-US'}}, + ]} + /> + + )} + {(authResponse?.flowStatus === FlowStatus.SuccessCompleted || isAuthenticated) && ( +
Successfully Authenticated
+ )} +
+
+ ); +}; + +export default SignIn; diff --git a/packages/react/src/components/SignIn/fragments/BasicAuth.tsx b/packages/react/src/components/SignIn/fragments/BasicAuth.tsx new file mode 100644 index 00000000..a70ea66a --- /dev/null +++ b/packages/react/src/components/SignIn/fragments/BasicAuth.tsx @@ -0,0 +1,121 @@ +/** + * 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 {ScreenType, keys} from '@asgardeo/js-ui-core'; +import {CircularProgress, Grid} from '@oxygen-ui/react'; +import {useState} from 'react'; +import useTranslations from '../../../hooks/use-translations'; +import BasicAuthProps from '../../../models/basic-auth-props'; +import {SignIn as UISignIn} from '../../../oxygen-ui-react-auth-components'; + +/** + * This component renders the basic authentication form. + * + * @param {BasicAuthProps} props - Props injected to the basic authentication component. + * @param {BrandingProps} props.brandingProps - Branding props. + * @param {Function} props.handleAuthenticate - Callback to handle authentication. + * @param {Authenticator} props.authenticator - Authenticator. + * @param {AlertType} props.alert - Alert type. + * @param {ReactElement[]} props.renderLoginOptions - Login options. + * @param {boolean} props.showSelfSignUp - Show self sign up. + * + * @return {JSX.Element} + */ +const BasicAuth = ({ + handleAuthenticate, + authenticator, + alert, + brandingProps, + showSelfSignUp, + renderLoginOptions, +}: BasicAuthProps): JSX.Element => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + + const {t, isLoading} = useTranslations({ + componentLocaleOverride: brandingProps?.locale, + componentTextOverrides: brandingProps?.preference?.text, + screen: ScreenType.Login, + }); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + + {t(keys.login.login.heading)} + + {alert && {t(alert.key)}} + + ): void => setUsername(e.target.value)} + /> + + ): void => setPassword(e.target.value)} + /> + + { + handleAuthenticate(authenticator.authenticatorId, {password, username}); + setUsername(''); + setPassword(''); + }} + > + {t(keys.login.button)} + + + {showSelfSignUp && ( + + {t(keys.common.prefix.register)} + + {t(keys.common.register)} + + + )} + + {renderLoginOptions.length !== 0 && {t(keys.common.or)} } + + {renderLoginOptions} + + ); +}; + +export default BasicAuth; diff --git a/packages/react/src/components/SignIn/fragments/EmailOtp.tsx b/packages/react/src/components/SignIn/fragments/EmailOtp.tsx new file mode 100644 index 00000000..ab6718cb --- /dev/null +++ b/packages/react/src/components/SignIn/fragments/EmailOtp.tsx @@ -0,0 +1,93 @@ +/** + * 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 {ScreenType, keys} from '@asgardeo/js-ui-core'; +import {CircularProgress} from '@oxygen-ui/react'; +import {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'; + +/** + * Email OTP component. + * + * @param {EmailOtpProps} props - Props injected to the component. + * @param {BrandingProps} props.brandingProps - Branding props. + * @param {Authenticator} props.authenticator - Authenticator. + * @param {Function} props.handleAuthenticate - Callback to handle authentication. + * @param {AlertType} props.alert - Alert type. + * @return {ReactElement} + */ +const EmailOtp = ({alert, brandingProps, authenticator, handleAuthenticate}: EmailOtpProps): ReactElement => { + const [otp, setOtp] = useState(); + + const {isLoading, t} = useTranslations({ + componentLocaleOverride: brandingProps?.locale, + componentTextOverrides: brandingProps?.preference?.text, + screen: ScreenType.EmailOTP, + }); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + + {t(keys.emailOtp.email.otp.heading)} + + {alert && {alert.key}} + + ): void => setOtp(e.target.value)} + /> + + { + handleAuthenticate(); + setOtp(''); + }} + > + {t(keys.emailOtp.continue)} + + + handleAuthenticate(authenticator.authenticatorId)} + color="secondary" + variant="contained" + > + {t(keys.emailOtp.resend.code)} + + + ); +}; + +export default EmailOtp; diff --git a/packages/react/src/components/SignIn/fragments/LoginOptionsBox.tsx b/packages/react/src/components/SignIn/fragments/LoginOptionsBox.tsx new file mode 100644 index 00000000..dec0358c --- /dev/null +++ b/packages/react/src/components/SignIn/fragments/LoginOptionsBox.tsx @@ -0,0 +1,54 @@ +/** + * 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 emailSolid from '../../../assets/email-solid.svg'; +import facebook from '../../../assets/social-logins/facebook.svg'; +import github from '../../../assets/social-logins/github.svg'; +import google from '../../../assets/social-logins/google.svg'; +import microsoft from '../../../assets/social-logins/microsoft.svg'; +import LoginOptionsBoxProps from '../../../models/login-options-box-props'; +import {SignIn as UISignIn} from '../../../oxygen-ui-react-auth-components'; + +const images: {[key: string]: string} = { + 'Email OTP': emailSolid, + Facebook: facebook, + Github: github, + Google: google, + Microsoft: microsoft, +}; + +/** + * This component renders the login options box. + * + * @param {LoginOptionsBoxProps} props - Props injected to the component. + * @param {string} props.socialName - Name of the social login. + * @param {string} props.displayName - Display name of the social login. + * @param {Function} props.handleOnClick - On click handler. + * @return {JSX.Element} + */ +const LoginOptionsBox = ({socialName, displayName, handleOnClick}: LoginOptionsBoxProps): JSX.Element => ( + } + onClick={handleOnClick} + > + Sign In with {displayName} + +); + +export default LoginOptionsBox; diff --git a/packages/react/src/components/SignIn/fragments/SmsOtp.tsx b/packages/react/src/components/SignIn/fragments/SmsOtp.tsx new file mode 100644 index 00000000..518d8a8f --- /dev/null +++ b/packages/react/src/components/SignIn/fragments/SmsOtp.tsx @@ -0,0 +1,77 @@ +/** + * 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 {ScreenType, keys} from '@asgardeo/js-ui-core'; +import {CircularProgress} from '@oxygen-ui/react'; +import {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 [otp, setOtp] = useState(); + + const {isLoading, t} = useTranslations({ + componentLocaleOverride: brandingProps?.locale, + componentTextOverrides: brandingProps?.preference?.text, + screen: ScreenType.SMSOTP, + }); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + + {t(keys.smsOtp.sms.otp.heading)} + + {t(keys.smsOtp.sms.otp.subheading)} + + {alert && {alert.key}} + + + + { + handleAuthenticate(authenticator.authenticatorId, {OTPCode: otp}); + setOtp(''); + }} + > + {t(keys.smsOtp.continue)} + + + handleAuthenticate(authenticator.authenticatorId)} + color="secondary" + variant="contained" + > + {t(keys.smsOtp.resend.code)} + + + ); +}; + +export default SmsOtp; diff --git a/packages/react/src/components/SignIn/fragments/Totp.tsx b/packages/react/src/components/SignIn/fragments/Totp.tsx new file mode 100644 index 00000000..5183e37c --- /dev/null +++ b/packages/react/src/components/SignIn/fragments/Totp.tsx @@ -0,0 +1,86 @@ +/** + * 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 {ScreenType, keys} from '@asgardeo/js-ui-core'; +import {CircularProgress} from '@oxygen-ui/react'; +import {useState, ReactElement} from 'react'; +import useTranslations from '../../../hooks/use-translations'; +import TotpProps from '../../../models/totp-props'; +import {SignIn as UISignIn} from '../../../oxygen-ui-react-auth-components'; + +/** + * This component renders the TOTP authentication screen. + * + * @param {TotpProps} props - Props injected to the component. + * @param {AlertType} props.alert - Alert type. + * @param {string} props.authenticator - Authenticator. + * @param {BrandingProps} props.brandingProps - Branding props. + * @param {Function} props.handleAuthenticate - Callback to handle authentication. + * + * @return {ReactElement} + */ +const Totp = ({brandingProps, authenticator, handleAuthenticate, alert}: TotpProps): ReactElement => { + const [totp, setTotp] = useState(); + + const {isLoading, t} = useTranslations({ + componentLocaleOverride: brandingProps?.locale, + componentTextOverrides: brandingProps?.preference?.text, + screen: ScreenType.TOTP, + }); + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( + + {t(keys.totp.heading)} + + {t(keys.totp.enter.verification.code.got.by.device)} + + {alert && {alert.key}} + + + + handleAuthenticate(authenticator.authenticatorId, {token: totp})} + > + totp.continue + + + + {t(keys.totp.enroll.message1)} +
+ {t(keys.totp.enroll.message2)} +
+ + {t(keys.totp.enroll.message2)} +
+ ); +}; + +export default Totp; diff --git a/packages/react/src/components/SignIn/sign-in.scss b/packages/react/src/components/SignIn/sign-in.scss new file mode 100644 index 00000000..c24aa658 --- /dev/null +++ b/packages/react/src/components/SignIn/sign-in.scss @@ -0,0 +1,45 @@ +/** + * 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. + */ + +.asgardeo-sign-in { + min-width: 350px; + min-height: 200px; + + .email-otp-resend-button { + margin-top: 0; + } + + .multiple-otions-title { + margin: 16px 0 34px; + } + + .multiple-options-paper { + padding-bottom: 64px; + } +} + +.circular-progress-holder { + min-height: 0.7*100vh; + display: flex; + justify-content: center; + align-items: center; + + .circular-progress { + color: rgb(214 211 211); + } +} diff --git a/packages/react/src/components/SignInButton/SignInButton.tsx b/packages/react/src/components/SignInButton/SignInButton.tsx new file mode 100644 index 00000000..9bb78943 --- /dev/null +++ b/packages/react/src/components/SignInButton/SignInButton.tsx @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * 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 {Box, Button} from '@oxygen-ui/react'; +import React, {ReactElement, useState} from 'react'; +import './sign-in-button.scss'; +import SignIn from '../SignIn/SignIn'; + +/** + * SignInButton component. This button will render a modal with the SignIn component when clicked. + * + * @param {Object} props - Component props. + * @param {ReactElement} props.customComponent - Optional custom component to be rendered. + * @returns {ReactElement} Rendered SignInButton component. + */ +const SignInButton = ({customComponent}: {customComponent?: ReactElement}): ReactElement => { + const [modalVisible, setModalVisible] = useState(false); + + const openModal = (): void => { + setModalVisible(true); + }; + + const closeModal = (): void => { + setModalVisible(false); + }; + + return ( +
+ {customComponent ? ( + React.cloneElement(customComponent, { + onClick: openModal, + }) + ) : ( + + )} + + {modalVisible && ( + + + + )} + + {modalVisible && } +
+ ); +}; + +export default SignInButton; diff --git a/packages/react/src/components/SignInButton/sign-in-button.scss b/packages/react/src/components/SignInButton/sign-in-button.scss new file mode 100644 index 00000000..7a59663c --- /dev/null +++ b/packages/react/src/components/SignInButton/sign-in-button.scss @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * 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. + */ + +.asgardeo { + .asgardeo-sign-in-button { + color: var(--oxygen-palette-text-primary); + } + + .OxygenSignInImage { + height: 45px; + } + + .popup-box { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1000; + min-height: 65%; + min-width: 35%; + max-height: 100%; + display: flex; + justify-content: center; + align-items: center; + overflow: auto; + padding-top: 40px; + + /* Hide scrollbar for Chrome, Safari and Opera */ + ::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + + .popup-box-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 76.8%); + z-index: 100; + } +} diff --git a/packages/react/src/components/SignOutButton/SignOutButton.tsx b/packages/react/src/components/SignOutButton/SignOutButton.tsx new file mode 100644 index 00000000..bd529209 --- /dev/null +++ b/packages/react/src/components/SignOutButton/SignOutButton.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * 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 {Button} from '@oxygen-ui/react'; +import {ReactElement} from 'react'; +import useAuthentication from '../../hooks/use-authentication'; + +/** + * SignOutButton component. + * + * This component renders a sign out button. When clicked, it triggers the sign out process. + * + * @returns {ReactElement} Rendered SignOutButton component. + */ +const SignOutButton = (): ReactElement => { + const {signOut} = useAuthentication(); + + return ( +
+ +
+ ); +}; + +export default SignOutButton; diff --git a/packages/react/src/components/SignedIn/SignedIn.tsx b/packages/react/src/components/SignedIn/SignedIn.tsx new file mode 100644 index 00000000..44ec80d5 --- /dev/null +++ b/packages/react/src/components/SignedIn/SignedIn.tsx @@ -0,0 +1,38 @@ +/** + * 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 {FC, PropsWithChildren} from 'react'; +import useAuthentication from '../../hooks/use-authentication'; +import SignedProps from '../../models/signed-props'; + +/** + * This component renders its children if the user is signed out. + * + * @param {PropsWithChildren} props - Props injected to the component. + * @param {ReactElement} props.fallback - Fallback element to render. + * + * @return {JSX.Element} + */ +const SignedIn: FC> = (props: PropsWithChildren) => { + const {fallback, children} = props; + const {isAuthenticated} = useAuthentication(); + + return isAuthenticated ? children : fallback ?? null; +}; + +export default SignedIn; diff --git a/packages/react/src/components/SignedOut/SignedOut.tsx b/packages/react/src/components/SignedOut/SignedOut.tsx new file mode 100644 index 00000000..77664846 --- /dev/null +++ b/packages/react/src/components/SignedOut/SignedOut.tsx @@ -0,0 +1,38 @@ +/** + * 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 {FC, PropsWithChildren} from 'react'; +import useAuthentication from '../../hooks/use-authentication'; +import SignedProps from '../../models/signed-props'; + +/** + * This component renders its children if the user is signed out. + * + * @param {PropsWithChildren} props - Props injected to the component. + * @param {ReactElement} props.fallback - Fallback element to render. + * + * @return {JSX.Element} + */ +const SignedOut: FC> = (props: PropsWithChildren) => { + const {fallback, children} = props; + const {isAuthenticated} = useAuthentication(); + + return !isAuthenticated ? children : fallback ?? null; +}; + +export default SignedOut; diff --git a/packages/react/src/components/public-components.ts b/packages/react/src/components/public-components.ts new file mode 100644 index 00000000..5e518d19 --- /dev/null +++ b/packages/react/src/components/public-components.ts @@ -0,0 +1,23 @@ +/** + * 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. + */ + +export {default as SignIn} from './SignIn/SignIn'; +export {default as SignedIn} from './SignedIn/SignedIn'; +export {default as SignedOut} from './SignedOut/SignedOut'; +export {default as SignInButton} from './SignInButton/SignInButton'; +export {default as SignOutButton} from './SignOutButton/SignOutButton'; diff --git a/packages/react/src/contexts/branding-preference-context.ts b/packages/react/src/contexts/branding-preference-context.ts index 602111e5..1477b451 100644 --- a/packages/react/src/contexts/branding-preference-context.ts +++ b/packages/react/src/contexts/branding-preference-context.ts @@ -16,9 +16,9 @@ * under the License. */ -import {BrandingProps} from '@asgardeo/js-ui-core'; +import {Branding} from '@asgardeo/js-ui-core'; import {Context, createContext} from 'react'; -const BrandingPreferenceContext: Context = createContext(undefined); +const BrandingPreferenceContext: Context = createContext(undefined); export default BrandingPreferenceContext; diff --git a/packages/react/src/hooks/use-authentication.ts b/packages/react/src/hooks/use-authentication.ts index 701093bc..f31d9352 100644 --- a/packages/react/src/hooks/use-authentication.ts +++ b/packages/react/src/hooks/use-authentication.ts @@ -35,9 +35,10 @@ const useAuthentication = (): UseAuthentication => { const {user, isAuthenticated, accessToken} = contextValue; const signOut: () => void = () => { - signOutApiCall(); - sessionStorage.clear(); - window.location.reload(); + signOutApiCall().then(() => { + sessionStorage.clear(); + window.location.reload(); + }); }; return {accessToken, isAuthenticated, signOut, user}; diff --git a/packages/react/src/hooks/use-translations.ts b/packages/react/src/hooks/use-translations.ts index f8750ca2..65e962a2 100644 --- a/packages/react/src/hooks/use-translations.ts +++ b/packages/react/src/hooks/use-translations.ts @@ -57,10 +57,10 @@ const useTranslations = (props: SetTranslationsProps): UseTranslations => { * @returns {string} The requested translation. */ const t = (key: string): string => { - const parts: string[] = key.split('.'); + const keySegments: string[] = key.split('.'); - const screenKey: string = parts[0]; - const rightPart: string = parts.slice(1).join('.'); + const screenKey: string = keySegments[0]; + const rightPart: string = keySegments.slice(1).join('.'); return text[screenKey][rightPart]; }; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts new file mode 100644 index 00000000..ee3020e7 --- /dev/null +++ b/packages/react/src/index.ts @@ -0,0 +1,22 @@ +/** + * 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. + */ + +export * from './components/public-components'; +export * from './models/public-models'; +export {default as AsgardeoProvider} from './providers/AsgardeoProvider'; +export {default as useAuthentication} from './hooks/use-authentication'; diff --git a/packages/react/src/models/asgardeo-provider-props.ts b/packages/react/src/models/asgardeo-provider-props.ts index b080687b..8deb0965 100644 --- a/packages/react/src/models/asgardeo-provider-props.ts +++ b/packages/react/src/models/asgardeo-provider-props.ts @@ -18,10 +18,10 @@ import {BrandingProps, Store, UIAuthConfig} from '@asgardeo/js-ui-core'; -interface AsgardeProviderProps { +interface AsgardeoProviderProps { branding?: BrandingProps; config: UIAuthConfig; store?: Store; } -export default AsgardeProviderProps; +export default AsgardeoProviderProps; diff --git a/packages/react/src/models/auth-context.ts b/packages/react/src/models/auth-context.ts index 1960de6d..5a07ebeb 100644 --- a/packages/react/src/models/auth-context.ts +++ b/packages/react/src/models/auth-context.ts @@ -21,7 +21,6 @@ import {UIAuthConfig, MeAPIResponse} from '@asgardeo/js-ui-core'; interface AuthContext { accessToken: string; config: UIAuthConfig; - customizationOptions?: any; isAuthenticated: boolean | undefined; setAuthentication: () => void; user: MeAPIResponse; diff --git a/packages/react/src/models/basic-auth-props.ts b/packages/react/src/models/basic-auth-props.ts new file mode 100644 index 00000000..e7f4feeb --- /dev/null +++ b/packages/react/src/models/basic-auth-props.ts @@ -0,0 +1,32 @@ +/** + * 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 {Authenticator, BrandingProps} from '@asgardeo/js-ui-core'; +import {ReactElement} from 'react'; +import {AlertType} from './sign-in'; + +interface BasicAuthProps { + alert?: AlertType; + authenticator: Authenticator; + brandingProps?: BrandingProps; + handleAuthenticate: Function; + renderLoginOptions?: ReactElement[]; + showSelfSignUp: boolean; +} + +export default BasicAuthProps; diff --git a/packages/react/src/models/email-otp-props.ts b/packages/react/src/models/email-otp-props.ts new file mode 100644 index 00000000..5936197b --- /dev/null +++ b/packages/react/src/models/email-otp-props.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 {Authenticator, BrandingProps} from '@asgardeo/js-ui-core'; +import {AlertType} from './sign-in'; + +interface EmailOtpProps { + alert?: AlertType; + authenticator?: Authenticator; + brandingProps?: BrandingProps; + handleAuthenticate: Function; +} + +export default EmailOtpProps; diff --git a/packages/react/src/models/i18n.ts b/packages/react/src/models/i18n.ts index 343a78ff..36513c3d 100644 --- a/packages/react/src/models/i18n.ts +++ b/packages/react/src/models/i18n.ts @@ -27,7 +27,7 @@ export type I18nLocalization = export interface SetTranslationsProps { componentLocaleOverride?: string; componentTextOverrides?: BrandingPreferenceTextProps; - screen: ScreenType; + screen?: ScreenType; } export interface I18n { setTranslations: (props: SetTranslationsProps) => Promise; diff --git a/packages/react/src/models/login-options-box-props.ts b/packages/react/src/models/login-options-box-props.ts new file mode 100644 index 00000000..62778bd2 --- /dev/null +++ b/packages/react/src/models/login-options-box-props.ts @@ -0,0 +1,25 @@ +/** + * 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 LoginOptionsBoxProps { + displayName: string; + handleOnClick: () => void; + socialName: string; +} + +export default LoginOptionsBoxProps; diff --git a/packages/react/src/models/public-models.ts b/packages/react/src/models/public-models.ts new file mode 100644 index 00000000..f51eee77 --- /dev/null +++ b/packages/react/src/models/public-models.ts @@ -0,0 +1,19 @@ +/** + * 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. + */ + +export type {UIAuthConfig} from '@asgardeo/js-ui-core'; diff --git a/packages/react/src/models/sign-in.ts b/packages/react/src/models/sign-in.ts new file mode 100644 index 00000000..99c61414 --- /dev/null +++ b/packages/react/src/models/sign-in.ts @@ -0,0 +1,31 @@ +/** + * 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 {BrandingProps} from '@asgardeo/js-ui-core'; + +export interface SignInProps { + brandingProps?: BrandingProps; +} + +export type AlertType = { + alertType: + | {error?: boolean; info?: never; warning?: never} + | {error?: never; info?: boolean; warning?: never} + | {error?: never; infor?: never; warning?: boolean}; + key: string; +}; diff --git a/packages/react/src/models/signed-props.ts b/packages/react/src/models/signed-props.ts new file mode 100644 index 00000000..acc02059 --- /dev/null +++ b/packages/react/src/models/signed-props.ts @@ -0,0 +1,25 @@ +/** + * 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 {ReactElement} from 'react'; + +interface SignedProps { + fallback?: ReactElement; +} + +export default SignedProps; diff --git a/packages/react/src/models/totp-props.ts b/packages/react/src/models/totp-props.ts new file mode 100644 index 00000000..d5da0c42 --- /dev/null +++ b/packages/react/src/models/totp-props.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 {Authenticator, BrandingProps} from '@asgardeo/js-ui-core'; +import {AlertType} from './sign-in'; + +interface TotpProps { + alert?: AlertType; + authenticator: Authenticator; + brandingProps?: BrandingProps; + handleAuthenticate: Function; +} + +export default TotpProps; diff --git a/packages/react/src/oxygen-ui-react-auth-components/SignIn/sign-in.scss b/packages/react/src/oxygen-ui-react-auth-components/SignIn/sign-in.scss index 0db727a4..609fac01 100644 --- a/packages/react/src/oxygen-ui-react-auth-components/SignIn/sign-in.scss +++ b/packages/react/src/oxygen-ui-react-auth-components/SignIn/sign-in.scss @@ -24,4 +24,5 @@ align-items: center; text-align: left; padding: 20px; + max-width: fit-content; } diff --git a/packages/react/src/oxygen-ui-react-auth-components/SignInButton/sign-in-button.scss b/packages/react/src/oxygen-ui-react-auth-components/SignInButton/sign-in-button.scss index 6498e7ab..315220b9 100644 --- a/packages/react/src/oxygen-ui-react-auth-components/SignInButton/sign-in-button.scss +++ b/packages/react/src/oxygen-ui-react-auth-components/SignInButton/sign-in-button.scss @@ -27,6 +27,10 @@ padding: 8px 0; box-shadow: 0 1px 1px 2px rgb(0 0 0 / 12%), 0 1px 1px 0 rgb(0 0 0 / 24%); + img { + max-height: 22px; + } + &:hover { background: rgb(213 212 212); box-shadow: 0 1px 1px 2px rgb(0 0 0 / 12%), 0 1px 1px 0 rgb(0 0 0 / 24%); diff --git a/packages/react/src/providers/AsgardeoProvider.tsx b/packages/react/src/providers/AsgardeoProvider.tsx index bab24026..887d463c 100644 --- a/packages/react/src/providers/AsgardeoProvider.tsx +++ b/packages/react/src/providers/AsgardeoProvider.tsx @@ -21,7 +21,7 @@ import {FC, PropsWithChildren, useCallback, useEffect, useMemo, useState} from ' import BrandingPreferenceProvider from './BrandingPreferenceProvider'; import I18nProvider from './I18nProvider'; import AsgardeoContext from '../contexts/asgardeo-context'; -import AsgardeProviderProps from '../models/asgardeo-provider-props'; +import AsgardeoProviderProps from '../models/asgardeo-provider-props'; import AuthContext from '../models/auth-context'; import SPACryptoUtils from '../utils/crypto-utils'; import SessionStore from '../utils/session-store'; @@ -31,7 +31,7 @@ import SessionStore from '../utils/session-store'; * It takes an object of type `AsgardeProviderProps` as props, which includes the children to render, * a configuration object, a store instance, and a branding object. * - * @param {PropsWithChildren} props - The properties passed to the component. + * @param {PropsWithChildren} props - The properties passed to the component. * @param {ReactNode} props.children - The children to render inside the provider. * @param {Config} props.config - The configuration object for the Asgardeo context. * @param {Store} [props.store] - An optional store instance. If not provided, a new SessionStore will be created. @@ -39,8 +39,8 @@ import SessionStore from '../utils/session-store'; * * @returns {ReactElement} A React element that provides the Asgardeo context to all its children. */ -const AsgardeoProvider: FC> = ( - props: PropsWithChildren, +const AsgardeoProvider: FC> = ( + props: PropsWithChildren, ) => { const {children, config, store, branding} = props; diff --git a/packages/react/src/providers/BrandingPreferenceProvider.tsx b/packages/react/src/providers/BrandingPreferenceProvider.tsx index 845928e1..cb46a81b 100644 --- a/packages/react/src/providers/BrandingPreferenceProvider.tsx +++ b/packages/react/src/providers/BrandingPreferenceProvider.tsx @@ -17,7 +17,7 @@ */ import {Branding, getBranding} from '@asgardeo/js-ui-core'; -import {ThemeProvider} from '@oxygen-ui/react'; +import {CircularProgress, ThemeProvider} from '@oxygen-ui/react'; import {FC, PropsWithChildren, useEffect, useState} from 'react'; import BrandingPreferenceContext from '../contexts/branding-preference-context'; import BrandingPreferenceProviderProps from '../models/branding-preference-provider-props'; @@ -47,6 +47,14 @@ const BrandingPreferenceProvider: FC + + + ); + } + return ( {children} diff --git a/packages/react/src/theme/generate-theme-sign-in.ts b/packages/react/src/theme/generate-theme-sign-in.ts new file mode 100644 index 00000000..05c457e3 --- /dev/null +++ b/packages/react/src/theme/generate-theme-sign-in.ts @@ -0,0 +1,154 @@ +/** + * 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 {BrandingPreferenceTheme, ThemeConfig} from '@asgardeo/js-ui-core'; +import {extendTheme, Theme} from '@oxygen-ui/react'; + +/** + * This function generates the theme for the sign-in component. + * + * @param {BrandingPreferenceTheme} brandingPreferenceTheme - The branding preference theme. + * + * @return {Theme} The generated theme. + */ +const generateThemeSignIn: (brandingPreferenceTheme: BrandingPreferenceTheme) => Theme = ( + brandingPreferenceTheme: BrandingPreferenceTheme, +) => { + const mode: string = brandingPreferenceTheme?.activeTheme.toLowerCase() ?? 'light'; + const brandingTheme: ThemeConfig = brandingPreferenceTheme[mode.toUpperCase()]; + + return extendTheme({ + colorSchemes: { + dark: { + brand: { + logo: { + main: brandingTheme?.images?.myAccountLogo?.imgURL ?? `../assets/asgardeo-logo.svg`, + }, + }, + palette: { + primary: { + main: brandingTheme?.colors?.primary?.main ?? 'var(--oxygen-palette-primary-main)', + }, + }, + }, + light: { + brand: { + logo: { + main: brandingTheme?.images?.myAccountLogo?.imgURL ?? `../assets/asgardeo-logo.svg`, + }, + }, + palette: { + primary: { + main: brandingTheme?.colors?.primary?.main ?? 'var(--oxygen-palette-primary-main)', + }, + }, + }, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + borderRadius: brandingTheme?.buttons?.primary?.base?.border?.borderRadius, + color: brandingTheme?.buttons?.primary?.base?.font?.color, + }, + }, + }, + MuiCircularProgress: { + styleOverrides: { + root: { + color: `${brandingTheme?.colors?.primary?.main} !important`, + }, + }, + }, + MuiFormControl: { + styleOverrides: { + root: { + background: brandingTheme?.inputs?.base?.background?.backgroundColor, + borderColor: brandingTheme?.inputs?.base?.border?.borderColor, + borderRadius: brandingTheme?.inputs?.base?.border?.borderRadius, + color: brandingTheme?.inputs?.base?.font?.color, + }, + }, + }, + MuiInputLabel: { + styleOverrides: { + root: { + color: `${ + brandingTheme?.inputs?.base?.labels?.font?.color !== '' + ? brandingTheme?.inputs?.base?.labels?.font?.color + : brandingTheme?.colors?.text?.primary + } !important`, + }, + }, + }, + MuiLink: { + styleOverrides: { + root: { + color: `${brandingTheme?.colors?.text?.primary} !important`, + }, + }, + }, + MuiOutlinedInput: { + styleOverrides: { + input: { + padding: '0.67857143em 1em', + }, + }, + }, + MuiPaper: { + styleOverrides: { + root: { + '.OxygenSignInButton-social': { + backgroundColor: brandingTheme?.buttons.externalConnection.base.background.backgroundColor, + borderRadius: brandingTheme?.buttons.externalConnection.base.border.borderRadius, + color: brandingTheme?.buttons.externalConnection.base.font.color, + }, + background: brandingTheme?.colors?.background?.surface?.main, + borderColor: brandingTheme?.colors?.outlined?.default, + borderRadius: brandingTheme?.loginBox?.border?.borderRadius, + }, + }, + }, + MuiTextField: { + styleOverrides: { + root: { + color: 'purple', + }, + }, + }, + MuiTypography: { + styleOverrides: { + root: { + color: brandingTheme?.colors?.text?.primary, + }, + }, + }, + }, + shape: { + borderRadius: 4, + }, + typography: { + fontFamily: brandingTheme?.typography.font.fontFamily ?? 'Gilmer, sans-serif', + h1: { + fontWeight: 700, + }, + }, + }); +}; + +export default generateThemeSignIn; diff --git a/packages/react/src/theme/generate-theme.ts b/packages/react/src/theme/generate-theme.ts index 5f2cee55..f25efcad 100644 --- a/packages/react/src/theme/generate-theme.ts +++ b/packages/react/src/theme/generate-theme.ts @@ -43,6 +43,12 @@ const generateTheme: (brandingPreferenceTheme: BrandingPreferenceTheme) => Theme }, }, palette: { + gradients: { + primary: { + stop1: brandingTheme.colors.primary.main, + stop2: brandingTheme.colors.primary.main, + }, + }, primary: { main: brandingTheme?.colors?.primary?.main ?? 'var(--oxygen-palette-primary-main)', }, @@ -55,6 +61,12 @@ const generateTheme: (brandingPreferenceTheme: BrandingPreferenceTheme) => Theme }, }, palette: { + gradients: { + primary: { + stop1: brandingTheme.colors.primary.main, + stop2: brandingTheme.colors.primary.main, + }, + }, primary: { main: brandingTheme?.colors?.primary?.main ?? 'var(--oxygen-palette-primary-main)', }, @@ -70,6 +82,13 @@ const generateTheme: (brandingPreferenceTheme: BrandingPreferenceTheme) => Theme }, }, }, + MuiCircularProgress: { + styleOverrides: { + root: { + color: `${brandingTheme?.colors?.primary?.main} !important`, + }, + }, + }, MuiFormControl: { styleOverrides: { root: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87817104..fb8a4b15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2655,8 +2655,8 @@ packages: eslint: ^7.32.0 || ^8.2.0 eslint-plugin-import: ^2.25.3 dependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@8.57.0) @@ -2722,7 +2722,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5) debug: 3.2.7 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -2770,7 +2770,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 diff --git a/recipes/react-vite/.eslintrc.cjs b/recipes/react-vite/.eslintrc.cjs new file mode 100644 index 00000000..d6c95379 --- /dev/null +++ b/recipes/react-vite/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/recipes/react-vite/.gitignore b/recipes/react-vite/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/recipes/react-vite/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/recipes/react-vite/README.md b/recipes/react-vite/README.md new file mode 100644 index 00000000..408858c9 --- /dev/null +++ b/recipes/react-vite/README.md @@ -0,0 +1 @@ +# React + TypeScript + Vite Sample Application diff --git a/recipes/react-vite/index.html b/recipes/react-vite/index.html new file mode 100644 index 00000000..e4b78eae --- /dev/null +++ b/recipes/react-vite/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/recipes/react-vite/package.json b/recipes/react-vite/package.json new file mode 100644 index 00000000..bbb9aa35 --- /dev/null +++ b/recipes/react-vite/package.json @@ -0,0 +1,31 @@ +{ + "name": "react-vite", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@vitejs/plugin-basic-ssl": "^1.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-vite": "link:", + "vite-plugin-node-polyfills": "^0.21.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/recipes/react-vite/public/vite.svg b/recipes/react-vite/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/recipes/react-vite/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/recipes/react-vite/src/App.css b/recipes/react-vite/src/App.css new file mode 100644 index 00000000..2eb1147c --- /dev/null +++ b/recipes/react-vite/src/App.css @@ -0,0 +1,7 @@ +#root { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; +} diff --git a/recipes/react-vite/src/App.tsx b/recipes/react-vite/src/App.tsx new file mode 100644 index 00000000..79b2b171 --- /dev/null +++ b/recipes/react-vite/src/App.tsx @@ -0,0 +1,47 @@ +import "./App.css"; +import { + SignIn, + SignedIn, + SignedOut, + SignInButton, + SignOutButton, + useAuthentication, +} from "../../../packages/react/src"; // ToDO: temporary + +function App() { + const {user, accessToken} = useAuthentication(); + console.log(user); + return ( + <> + + + Fallback content}> +
Protected content
+ +
+ {accessToken} + {user && ( + <> + { +
" + ), + }} + /> + } + + )}
+ + + signedout fallback
}> +
Public content
+ + + + ); +} + +export default App; diff --git a/recipes/react-vite/src/main.tsx b/recipes/react-vite/src/main.tsx new file mode 100644 index 00000000..b4319810 --- /dev/null +++ b/recipes/react-vite/src/main.tsx @@ -0,0 +1,24 @@ +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import { AsgardeoProvider, UIAuthConfig } from "../../../packages/react/src"; //TODO: temporary + +const config: UIAuthConfig = { + baseUrl: "https://localhost:9443", + clientID: "b1uRjwpqydvxjGR42Y6BnIdQMRMa", + scope: ["openid", "internal_login", "profile"], + signInRedirectURL: "https://localhost:5173", + enableConsoleTextBranding: true, +}; + +const devConfig: UIAuthConfig = { + baseUrl: "https://dev.api.asgardeo.io/t/movinorg", + clientID: "kH5OfXOvpGLOvp1iAw4zQmNvv4oa", + scope: ["openid", "internal_login", "profile"], + signInRedirectURL: "https://localhost:5173", +}; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); diff --git a/recipes/react-vite/src/vite-env.d.ts b/recipes/react-vite/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/recipes/react-vite/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/recipes/react-vite/tsconfig.json b/recipes/react-vite/tsconfig.json new file mode 100644 index 00000000..a7fc6fbf --- /dev/null +++ b/recipes/react-vite/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/recipes/react-vite/tsconfig.node.json b/recipes/react-vite/tsconfig.node.json new file mode 100644 index 00000000..97ede7ee --- /dev/null +++ b/recipes/react-vite/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/recipes/react-vite/vite.config.ts b/recipes/react-vite/vite.config.ts new file mode 100644 index 00000000..1c59cea3 --- /dev/null +++ b/recipes/react-vite/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import basicSsl from "@vitejs/plugin-basic-ssl"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), basicSsl(), nodePolyfills()], +})