From dcde365fbb41d0304234e4d6eb7b7b512ece09fe Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 15:22:21 +0530 Subject: [PATCH 01/50] feat(react): add Loading component and update exports in index file --- .../control/{AsgardeoLoading.tsx => Loading.tsx} | 16 ++++++++-------- packages/react/src/index.ts | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) rename packages/react/src/components/control/{AsgardeoLoading.tsx => Loading.tsx} (77%) diff --git a/packages/react/src/components/control/AsgardeoLoading.tsx b/packages/react/src/components/control/Loading.tsx similarity index 77% rename from packages/react/src/components/control/AsgardeoLoading.tsx rename to packages/react/src/components/control/Loading.tsx index a42227f9..0ff9e5d2 100644 --- a/packages/react/src/components/control/AsgardeoLoading.tsx +++ b/packages/react/src/components/control/Loading.tsx @@ -22,7 +22,7 @@ import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; /** * Props for the Loading component. */ -export interface AsgardeoLoadingProps { +export interface LoadingProps { /** * Content to show when the user is not signed in. */ @@ -36,21 +36,21 @@ export interface AsgardeoLoadingProps { * * @example * ```tsx - * import { AsgardeoLoading } from '@asgardeo/auth-react'; + * import { Loading } from '@asgardeo/auth-react'; * * const App = () => { * return ( - * Finished Loading...

}> + * Finished Loading...

}> *

Loading...

- *
+ * * ); * } * ``` */ -const AsgardeoLoading: FC> = ({ +const Loading: FC> = ({ children, fallback = null, -}: PropsWithChildren) => { +}: PropsWithChildren) => { const {isLoading} = useAsgardeo(); if (!isLoading) { @@ -60,6 +60,6 @@ const AsgardeoLoading: FC> = ({ return <>{children}; }; -AsgardeoLoading.displayName = 'AsgardeoLoading'; +Loading.displayName = 'Loading'; -export default AsgardeoLoading; +export default Loading; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index fafa46a8..d3060e4d 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -115,8 +115,8 @@ export * from './components/control/SignedIn'; export {default as SignedOut} from './components/control/SignedOut'; export * from './components/control/SignedOut'; -export {default as AsgardeoLoading} from './components/control/AsgardeoLoading'; -export * from './components/control/AsgardeoLoading'; +export {default as Loading} from './components/control/Loading'; +export * from './components/control/Loading'; export {default as BaseSignIn} from './components/presentation/SignIn/BaseSignIn'; export * from './components/presentation/SignIn/BaseSignIn'; From 98cd771c1c5e5063cbaf3b53df27dc4639773839 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 15:23:22 +0530 Subject: [PATCH 02/50] chore(react): add Loading, SignedIn, and SignedOut components with updated exports --- .../src/components/control/{ => Loading}/Loading.tsx | 7 ++----- .../components/control/{ => SignedIn}/SignedIn.tsx | 2 +- .../components/control/{ => SignedOut}/SignedOut.tsx | 0 packages/react/src/index.ts | 12 ++++++------ 4 files changed, 9 insertions(+), 12 deletions(-) rename packages/react/src/components/control/{ => Loading}/Loading.tsx (88%) rename packages/react/src/components/control/{ => SignedIn}/SignedIn.tsx (96%) rename packages/react/src/components/control/{ => SignedOut}/SignedOut.tsx (100%) diff --git a/packages/react/src/components/control/Loading.tsx b/packages/react/src/components/control/Loading/Loading.tsx similarity index 88% rename from packages/react/src/components/control/Loading.tsx rename to packages/react/src/components/control/Loading/Loading.tsx index 0ff9e5d2..1aed3c1e 100644 --- a/packages/react/src/components/control/Loading.tsx +++ b/packages/react/src/components/control/Loading/Loading.tsx @@ -17,7 +17,7 @@ */ import {FC, PropsWithChildren, ReactNode} from 'react'; -import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; +import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; /** * Props for the Loading component. @@ -47,10 +47,7 @@ export interface LoadingProps { * } * ``` */ -const Loading: FC> = ({ - children, - fallback = null, -}: PropsWithChildren) => { +const Loading: FC> = ({children, fallback = null}: PropsWithChildren) => { const {isLoading} = useAsgardeo(); if (!isLoading) { diff --git a/packages/react/src/components/control/SignedIn.tsx b/packages/react/src/components/control/SignedIn/SignedIn.tsx similarity index 96% rename from packages/react/src/components/control/SignedIn.tsx rename to packages/react/src/components/control/SignedIn/SignedIn.tsx index 76c9502c..2a656712 100644 --- a/packages/react/src/components/control/SignedIn.tsx +++ b/packages/react/src/components/control/SignedIn/SignedIn.tsx @@ -17,7 +17,7 @@ */ import {FC, PropsWithChildren, ReactNode} from 'react'; -import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; +import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; /** * Props for the SignedIn component. diff --git a/packages/react/src/components/control/SignedOut.tsx b/packages/react/src/components/control/SignedOut/SignedOut.tsx similarity index 100% rename from packages/react/src/components/control/SignedOut.tsx rename to packages/react/src/components/control/SignedOut/SignedOut.tsx diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index d3060e4d..831fee2a 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -109,14 +109,14 @@ export * from './components/actions/SignUpButton/BaseSignUpButton'; export {default as SignUpButton} from './components/actions/SignUpButton/SignUpButton'; export * from './components/actions/SignUpButton/SignUpButton'; -export {default as SignedIn} from './components/control/SignedIn'; -export * from './components/control/SignedIn'; +export {default as SignedIn} from './components/control/SignedIn/SignedIn'; +export * from './components/control/SignedIn/SignedIn'; -export {default as SignedOut} from './components/control/SignedOut'; -export * from './components/control/SignedOut'; +export {default as SignedOut} from './components/control/SignedOut/SignedOut'; +export * from './components/control/SignedOut/SignedOut'; -export {default as Loading} from './components/control/Loading'; -export * from './components/control/Loading'; +export {default as Loading} from './components/control/Loading/Loading'; +export * from './components/control/Loading/Loading'; export {default as BaseSignIn} from './components/presentation/SignIn/BaseSignIn'; export * from './components/presentation/SignIn/BaseSignIn'; From 9dc1a5c090d97e8479b3b9e9abc6f14a82e71904 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 15:24:55 +0530 Subject: [PATCH 03/50] feat(react): add Loading component and update exports in index file --- .../components/control/Loading/Loading.tsx | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 packages/nextjs/src/client/components/control/Loading/Loading.tsx diff --git a/packages/nextjs/src/client/components/control/Loading/Loading.tsx b/packages/nextjs/src/client/components/control/Loading/Loading.tsx new file mode 100644 index 00000000..6a69d840 --- /dev/null +++ b/packages/nextjs/src/client/components/control/Loading/Loading.tsx @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2025, 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. + */ + +'use client'; + +import {FC, PropsWithChildren, ReactNode} from 'react'; +import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; + +/** + * Props for the Loading component. + */ +export interface LoadingProps { + /** + * Content to show when the user is not signed in. + */ + fallback?: ReactNode; +} + +/** + * A component that only renders its children when the Asgardeo is loading. + * + * @remarks This component is only supported in browser based React applications (CSR). + * + * @example + * ```tsx + * import { Loading } from '@asgardeo/auth-react'; + * + * const App = () => { + * return ( + * Finished Loading...

}> + *

Loading...

+ *
+ * ); + * } + * ``` + */ +const Loading: FC> = ({children, fallback = null}: PropsWithChildren) => { + const {isLoading} = useAsgardeo(); + + if (!isLoading) { + return <>{fallback}; + } + + return <>{children}; +}; + +Loading.displayName = 'Loading'; + +export default Loading; From 5a3523f10c775c5486dbab47ed5d5e01b892b0fc Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 17:28:12 +0530 Subject: [PATCH 04/50] fix(react): correct import path for useAsgardeo in SignedOut component --- packages/react/src/components/control/SignedOut/SignedOut.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/control/SignedOut/SignedOut.tsx b/packages/react/src/components/control/SignedOut/SignedOut.tsx index af89c52b..cfad0b76 100644 --- a/packages/react/src/components/control/SignedOut/SignedOut.tsx +++ b/packages/react/src/components/control/SignedOut/SignedOut.tsx @@ -17,7 +17,7 @@ */ import {FC, PropsWithChildren, ReactNode} from 'react'; -import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; +import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; /** * Props for the SignedOut component. From a1a6afe776a527d7485920f3ee448131196300aa Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 17:29:23 +0530 Subject: [PATCH 05/50] chore(react): add changeset for import path fix and component rename --- .changeset/five-geese-retire.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/five-geese-retire.md diff --git a/.changeset/five-geese-retire.md b/.changeset/five-geese-retire.md new file mode 100644 index 00000000..1c9e6fe9 --- /dev/null +++ b/.changeset/five-geese-retire.md @@ -0,0 +1,6 @@ +--- +'@asgardeo/react': patch +--- + +- Fix import path for useAsgardeo in SignedOut component +- Rename `AsgardeoLoading` -> `Loading` From 3607845acb66a04d489f530c91c65ae5b54459d0 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 8 Jul 2025 08:35:00 +0530 Subject: [PATCH 06/50] feat(javascript): add bem utility function for BEM-style class name generation --- packages/javascript/src/index.ts | 1 + packages/javascript/src/utils/bem.ts | 60 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 packages/javascript/src/utils/bem.ts diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts index c01d490f..ca7d9c7d 100644 --- a/packages/javascript/src/index.ts +++ b/packages/javascript/src/index.ts @@ -112,6 +112,7 @@ export {default as AsgardeoJavaScriptClient} from './AsgardeoJavaScriptClient'; export {default as createTheme} from './theme/createTheme'; export {ThemeColors, ThemeConfig, Theme, ThemeMode, ThemeDetection} from './theme/types'; +export {default as bem} from './utils/bem'; export {default as processUsername} from './utils/processUsername'; export {default as deepMerge} from './utils/deepMerge'; export {default as deriveOrganizationHandleFromBaseUrl} from './utils/deriveOrganizationHandleFromBaseUrl'; diff --git a/packages/javascript/src/utils/bem.ts b/packages/javascript/src/utils/bem.ts new file mode 100644 index 00000000..71f90128 --- /dev/null +++ b/packages/javascript/src/utils/bem.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2025, 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. + */ + +/** + * Creates a BEM-style class name by combining a base class with element and/or modifier + * + * @param baseClass - The base CSS class string (usually from emotion's css function) + * @param element - The BEM element name (optional) + * @param modifier - The BEM modifier name (optional) + * @returns The combined class name string + * + * @example + * ```tsx + * const baseClass = css` + * display: flex; + * &__element { + * color: red; + * } + * &--modifier { + * background: blue; + * } + * `; + * + * import bem from './utils/bem'; + * + * const elementClass = bem(baseClass, 'element'); + * const modifierClass = bem(baseClass, null, 'modifier'); + * const elementWithModifierClass = bem(baseClass, 'element', 'modifier'); + * ``` + */ +const bem = (baseClass: string, element?: string | null, modifier?: string | null): string => { + let className = baseClass; + + if (element) { + className += `__${element}`; + } + + if (modifier) { + className += `--${modifier}`; + } + + return className; +}; + +export default bem; From 91e17cfb84e63e85133c5eb201d0bd00833ff049 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 8 Jul 2025 08:37:12 +0530 Subject: [PATCH 07/50] chore(react): implement BEM-style styling for BaseCreateOrganization component --- .../BaseCreateOrganization.styles.ts | 168 ++++++++++++++ .../BaseCreateOrganization.tsx | 217 +++++++----------- .../CreateOrganization/CreateOrganization.tsx | 1 - 3 files changed, 245 insertions(+), 141 deletions(-) create mode 100644 packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.styles.ts diff --git a/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.styles.ts b/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.styles.ts new file mode 100644 index 00000000..30dc7b99 --- /dev/null +++ b/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.styles.ts @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2025, 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 {css} from '@emotion/css'; +import {useMemo} from 'react'; +import {bem, Theme} from '@asgardeo/browser'; + +/** + * Creates styles for the BaseCreateOrganization component using BEM methodology + * @param theme - The theme object containing design tokens + * @param colorScheme - The current color scheme (used for memoization) + * @returns Object containing CSS class names for component styling + */ +export const useStyles = (theme: Theme, colorScheme: string) => { + return useMemo(() => { + const cssCreateOrganization = css` + padding: calc(${theme.vars.spacing.unit} * 4); + min-width: 600px; + margin: 0 auto; + + &--card { + background: ${theme.vars.colors.background.surface}; + border-radius: ${theme.vars.borderRadius.large}; + padding: calc(${theme.vars.spacing.unit} * 4); + } + + &__content { + display: flex; + flex-direction: column; + gap: calc(${theme.vars.spacing.unit} * 2); + } + + &__form { + display: flex; + flex-direction: column; + gap: calc(${theme.vars.spacing.unit} * 2); + width: 100%; + } + + &__header { + display: flex; + align-items: center; + gap: calc(${theme.vars.spacing.unit} * 1.5); + margin-bottom: calc(${theme.vars.spacing.unit} * 1.5); + } + + &__field { + display: flex; + align-items: center; + padding: ${theme.vars.spacing.unit} 0; + border-bottom: 1px solid ${theme.vars.colors.border}; + min-height: 32px; + } + + &__field-group { + display: flex; + flex-direction: column; + gap: calc(${theme.vars.spacing.unit} * 0.5); + } + + &__textarea { + width: 100%; + padding: ${theme.vars.spacing.unit} calc(${theme.vars.spacing.unit} * 1.5); + border: 1px solid ${theme.vars.colors.border}; + border-radius: ${theme.vars.borderRadius.medium}; + font-size: ${theme.vars.typography.fontSizes.md}; + color: ${theme.vars.colors.text.primary}; + background-color: ${theme.vars.colors.background.surface}; + font-family: inherit; + min-height: 80px; + resize: vertical; + outline: none; + + &:focus { + border-color: ${theme.vars.colors.primary.main}; + box-shadow: 0 0 0 2px ${theme.vars.colors.primary.main}20; + } + + &:disabled { + background-color: ${theme.vars.colors.background.disabled}; + color: ${theme.vars.colors.text.secondary}; + cursor: not-allowed; + } + + &--error { + border-color: ${theme.vars.colors.error.main}; + } + } + + &__input { + /* Base input styles will be handled by TextField component */ + } + + &__avatar-container { + align-items: flex-start; + display: flex; + gap: calc(${theme.vars.spacing.unit} * 2); + margin-bottom: ${theme.vars.spacing.unit}; + } + + &__actions { + display: flex; + gap: ${theme.vars.spacing.unit}; + justify-content: flex-end; + padding-top: calc(${theme.vars.spacing.unit} * 2); + } + + &__info-container { + display: flex; + flex-direction: column; + gap: ${theme.vars.spacing.unit}; + } + + &__value { + color: ${theme.vars.colors.text.primary}; + flex: 1; + display: flex; + align-items: center; + gap: ${theme.vars.spacing.unit}; + overflow: hidden; + min-height: 32px; + line-height: 32px; + } + + &__popup { + padding: calc(${theme.vars.spacing.unit} * 2); + } + + &__error-alert { + margin-bottom: calc(${theme.vars.spacing.unit} * 2); + } + `; + + return { + createOrganization: cssCreateOrganization, + 'createOrganization--card': bem(cssCreateOrganization, null, 'card'), + createOrganization__content: bem(cssCreateOrganization, 'content'), + createOrganization__form: bem(cssCreateOrganization, 'form'), + createOrganization__header: bem(cssCreateOrganization, 'header'), + createOrganization__field: bem(cssCreateOrganization, 'field'), + createOrganization__fieldGroup: bem(cssCreateOrganization, 'field-group'), + createOrganization__textarea: bem(cssCreateOrganization, 'textarea'), + 'createOrganization__textarea--error': bem(cssCreateOrganization, 'textarea', 'error'), + createOrganization__input: bem(cssCreateOrganization, 'input'), + createOrganization__avatarContainer: bem(cssCreateOrganization, 'avatar-container'), + createOrganization__actions: bem(cssCreateOrganization, 'actions'), + createOrganization__infoContainer: bem(cssCreateOrganization, 'info-container'), + createOrganization__value: bem(cssCreateOrganization, 'value'), + createOrganization__popup: bem(cssCreateOrganization, 'popup'), + createOrganization__errorAlert: bem(cssCreateOrganization, 'error-alert'), + }; + }, [theme, colorScheme]); +}; diff --git a/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx b/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx index 30a6b194..0af4e482 100644 --- a/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx +++ b/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx @@ -16,9 +16,9 @@ * under the License. */ -import {withVendorCSSClassPrefix, CreateOrganizationPayload} from '@asgardeo/browser'; -import clsx from 'clsx'; -import {ChangeEvent, CSSProperties, FC, ReactElement, ReactNode, useMemo, useState} from 'react'; +import {withVendorCSSClassPrefix, CreateOrganizationPayload, bem} from '@asgardeo/browser'; +import {cx} from '@emotion/css'; +import {ChangeEvent, CSSProperties, FC, ReactElement, ReactNode, useState} from 'react'; import useTheme from '../../../contexts/Theme/useTheme'; import useTranslation from '../../../hooks/useTranslation'; import Alert from '../../primitives/Alert/Alert'; @@ -28,102 +28,7 @@ import FormControl from '../../primitives/FormControl/FormControl'; import InputLabel from '../../primitives/InputLabel/InputLabel'; import TextField from '../../primitives/TextField/TextField'; import Typography from '../../primitives/Typography/Typography'; - -const useStyles = () => { - const {theme, colorScheme} = useTheme(); - - return useMemo( - () => ({ - root: { - padding: `calc(${theme.vars.spacing.unit} * 4)`, - minWidth: '600px', - margin: '0 auto', - } as CSSProperties, - card: { - background: theme.vars.colors.background.surface, - borderRadius: theme.vars.borderRadius.large, - padding: `calc(${theme.vars.spacing.unit} * 4)`, - } as CSSProperties, - content: { - display: 'flex', - flexDirection: 'column', - gap: `calc(${theme.vars.spacing.unit} * 2)`, - } as CSSProperties, - form: { - display: 'flex', - flexDirection: 'column', - gap: `calc(${theme.vars.spacing.unit} * 2)`, - width: '100%', - } as CSSProperties, - header: { - display: 'flex', - alignItems: 'center', - gap: `calc(${theme.vars.spacing.unit} * 1.5)`, - marginBottom: `calc(${theme.vars.spacing.unit} * 1.5)`, - } as CSSProperties, - field: { - display: 'flex', - alignItems: 'center', - padding: `${theme.vars.spacing.unit} 0`, - borderBottom: `1px solid ${theme.vars.colors.border}`, - minHeight: '32px', - } as CSSProperties, - textarea: { - width: '100%', - padding: `${theme.vars.spacing.unit} calc(${theme.vars.spacing.unit} * 1.5)`, - border: `1px solid ${theme.vars.colors.border}`, - borderRadius: theme.vars.borderRadius.medium, - fontSize: theme.vars.typography.fontSizes.md, - color: theme.vars.colors.text.primary, - backgroundColor: theme.vars.colors.background.surface, - fontFamily: 'inherit', - minHeight: '80px', - resize: 'vertical', - outline: 'none', - '&:focus': { - borderColor: theme.vars.colors.primary.main, - boxShadow: `0 0 0 2px ${theme.vars.colors.primary.main}20`, - }, - '&:disabled': { - backgroundColor: theme.vars.colors.background.disabled, - color: theme.vars.colors.text.secondary, - cursor: 'not-allowed', - }, - } as CSSProperties, - avatarContainer: { - alignItems: 'flex-start', - display: 'flex', - gap: `calc(${theme.vars.spacing.unit} * 2)`, - marginBottom: theme.vars.spacing.unit, - } as CSSProperties, - actions: { - display: 'flex', - gap: theme.vars.spacing.unit, - justifyContent: 'flex-end', - paddingTop: `calc(${theme.vars.spacing.unit} * 2)`, - } as CSSProperties, - infoContainer: { - display: 'flex', - flexDirection: 'column' as const, - gap: theme.vars.spacing.unit, - } as CSSProperties, - value: { - color: theme.vars.colors.text.primary, - flex: 1, - display: 'flex', - alignItems: 'center', - gap: theme.vars.spacing.unit, - overflow: 'hidden', - minHeight: '32px', - lineHeight: '32px', - } as CSSProperties, - popup: { - padding: `calc(${theme.vars.spacing.unit} * 2)`, - } as CSSProperties, - }), - [theme, colorScheme], - ); -}; +import {useStyles} from './BaseCreateOrganization.styles'; /** * Interface for organization form data. @@ -176,8 +81,8 @@ export const BaseCreateOrganization: FC = ({ style, title = 'Create Organization', }): ReactElement => { - const styles = useStyles(); - const {theme} = useTheme(); + const {theme, colorScheme} = useTheme(); + const styles = useStyles(theme, colorScheme); const {t} = useTranslation(); const [formData, setFormData] = useState({ description: '', @@ -223,23 +128,34 @@ export const BaseCreateOrganization: FC = ({ } }; + /** + * Handles changes to the organization name input. + * Automatically generates the organization handle based on the name if the handle is not set or matches + * + * @param value - The new value for the organization name. + */ const handleNameChange = (value: string): void => { handleInputChange('name', value); - // Auto-generate handle from name if handle is empty or matches previous auto-generated value if (!formData.handle || formData.handle === generateHandleFromName(formData.name)) { const newHandle = generateHandleFromName(value); handleInputChange('handle', newHandle); } }; + /** + * Removes special characters except space and hyphen from the organization name + * and generates a valid handle. + * @param name + * @returns + */ const generateHandleFromName = (name: string): string => { return name .toLowerCase() - .replace(/[^a-z0-9\s-]/g, '') // Remove special characters except spaces and hyphens - .replace(/\s+/g, '-') // Replace spaces with hyphens - .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen - .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens + .replace(/[^a-z0-9\s-]/g, '') + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); }; const handleSubmit = async (e: React.FormEvent): Promise => { @@ -268,33 +184,40 @@ export const BaseCreateOrganization: FC = ({ } }; - const containerStyle = { - ...styles.root, - ...(cardLayout ? styles.card : {}), - }; - const createOrganizationContent = (
-
+
- {/* Error Alert */} {error && ( - + Error {error} )} - - {/* Organization Name */} -
+
= ({ disabled={loading} required error={formErrors.name} - className={withVendorCSSClassPrefix('create-organization__input')} + className={cx( + withVendorCSSClassPrefix(bem('create-organization', 'input')), + styles.createOrganization__input, + )} />
- - {/* Organization Handle */} -
+
= ({ required error={formErrors.handle} helperText="This will be your organization's unique identifier. Only lowercase letters, numbers, and hyphens are allowed." - className={withVendorCSSClassPrefix('create-organization__input')} + className={cx( + withVendorCSSClassPrefix(bem('create-organization', 'input')), + styles.createOrganization__input, + )} />
- - {/* Organization Description */} -
+
{t('organization.create.description.label')}