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`
diff --git a/.changeset/public-donkeys-tickle.md b/.changeset/public-donkeys-tickle.md
new file mode 100644
index 00000000..35000081
--- /dev/null
+++ b/.changeset/public-donkeys-tickle.md
@@ -0,0 +1,8 @@
+---
+'@asgardeo/javascript': patch
+'@asgardeo/nextjs': patch
+'@asgardeo/node': patch
+'@asgardeo/react': patch
+---
+
+Fix `getAccessToken` imperative usage
diff --git a/packages/javascript/src/api/updateMeProfile.ts b/packages/javascript/src/api/updateMeProfile.ts
index 90c376f6..ff99cd84 100644
--- a/packages/javascript/src/api/updateMeProfile.ts
+++ b/packages/javascript/src/api/updateMeProfile.ts
@@ -147,10 +147,11 @@ const updateMeProfile = async ({
}
throw new AsgardeoAPIError(
- `Network or parsing error: ${error instanceof Error ? error.message : 'Unknown error'}`,
+ error?.response?.data?.detail ||
+ 'An error occurred while updating the user profile. Please try again.',
'updateMeProfile-NetworkError-001',
'javascript',
- 0,
+ error?.data?.status,
'Network Error',
);
}
diff --git a/packages/javascript/src/errors/AsgardeoError.ts b/packages/javascript/src/errors/AsgardeoError.ts
index 5d34f6cb..2ae81905 100644
--- a/packages/javascript/src/errors/AsgardeoError.ts
+++ b/packages/javascript/src/errors/AsgardeoError.ts
@@ -55,12 +55,7 @@ export default class AsgardeoError extends Error {
constructor(message: string, code: string, origin: string) {
const _origin: string = AsgardeoError.resolveOrigin(origin);
- const prefix: string = `🛡️ Asgardeo - ${_origin}:`;
- const regex: RegExp = new RegExp(`🛡️\\s*Asgardeo\\s*-\\s*${_origin}:`, 'i');
- const sanitized: string = message.replace(regex, '');
- const _message: string = `${prefix} ${sanitized.trim()}\n\n(code="${code}")\n`;
-
- super(_message);
+ super(message);
this.name = new.target.name;
this.code = code;
@@ -72,6 +67,7 @@ export default class AsgardeoError extends Error {
}
public override toString(): string {
- return `[${this.name}]\nMessage: ${this.message}`;
+ const prefix: string = `🛡️ Asgardeo - ${this.origin}:`;
+ return `[${this.name}]\n${prefix} ${this.message}\n(code=\"${this.code}\")`;
}
}
diff --git a/packages/javascript/src/i18n/en-US.ts b/packages/javascript/src/i18n/en-US.ts
index 31242dda..789135a9 100644
--- a/packages/javascript/src/i18n/en-US.ts
+++ b/packages/javascript/src/i18n/en-US.ts
@@ -78,6 +78,13 @@ const translations: I18nTranslations = {
'username.password.title': 'Sign In',
'username.password.subtitle': 'Enter your username and password to continue.',
+ /* |---------------------------------------------------------------| */
+ /* | User Profile | */
+ /* |---------------------------------------------------------------| */
+
+ 'user.profile.title': 'Profile',
+ 'user.profile.update.generic.error': 'An error occurred while updating your profile. Please try again.',
+
/* |---------------------------------------------------------------| */
/* | Organization Switcher | */
/* |---------------------------------------------------------------| */
diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts
index c01d490f..813f10e2 100644
--- a/packages/javascript/src/index.ts
+++ b/packages/javascript/src/index.ts
@@ -112,6 +112,8 @@ 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 formatDate} from './utils/formatDate';
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/models/i18n.ts b/packages/javascript/src/models/i18n.ts
index dbdf9d27..39afb9c9 100644
--- a/packages/javascript/src/models/i18n.ts
+++ b/packages/javascript/src/models/i18n.ts
@@ -76,6 +76,13 @@ export interface I18nTranslations {
'username.password.title': string;
'username.password.subtitle': string;
+ /* |---------------------------------------------------------------| */
+ /* | User Profile | */
+ /* |---------------------------------------------------------------| */
+
+ 'user.profile.title': string;
+ 'user.profile.update.generic.error': string;
+
/* |---------------------------------------------------------------| */
/* | Organization Switcher | */
/* |---------------------------------------------------------------| */
diff --git a/packages/javascript/src/theme/createTheme.ts b/packages/javascript/src/theme/createTheme.ts
index 59bc9393..323a3554 100644
--- a/packages/javascript/src/theme/createTheme.ts
+++ b/packages/javascript/src/theme/createTheme.ts
@@ -54,33 +54,46 @@ const lightTheme: ThemeConfig = {
primary: {
main: '#1a73e8',
contrastText: '#ffffff',
+ dark: '#174ea6',
},
secondary: {
main: '#424242',
contrastText: '#ffffff',
+ dark: '#212121',
},
background: {
surface: '#ffffff',
disabled: '#f0f0f0',
+ dark: '#212121',
body: {
main: '#1a1a1a',
+ dark: '#212121',
},
},
error: {
main: '#d32f2f',
- contrastText: '#ffffff',
+ contrastText: '#d52828',
+ dark: '#b71c1c',
+ },
+ info: {
+ main: '#bbebff',
+ contrastText: '#43aeda',
+ dark: '#01579b',
},
success: {
main: '#4caf50',
- contrastText: '#ffffff',
+ contrastText: '#00a807',
+ dark: '#388e3c',
},
warning: {
main: '#ff9800',
- contrastText: '#ffffff',
+ contrastText: '#be7100',
+ dark: '#f57c00',
},
text: {
primary: '#1a1a1a',
secondary: '#666666',
+ dark: '#212121',
},
border: '#e0e0e0',
},
@@ -144,33 +157,46 @@ const darkTheme: ThemeConfig = {
primary: {
main: '#1a73e8',
contrastText: '#ffffff',
+ dark: '#174ea6',
},
secondary: {
main: '#424242',
contrastText: '#ffffff',
+ dark: '#212121',
},
background: {
surface: '#121212',
disabled: '#1f1f1f',
+ dark: '#212121',
body: {
main: '#ffffff',
+ dark: '#212121',
},
},
error: {
main: '#d32f2f',
- contrastText: '#ffffff',
+ contrastText: '#d52828',
+ dark: '#b71c1c',
+ },
+ info: {
+ main: '#bbebff',
+ contrastText: '#43aeda',
+ dark: '#01579b',
},
success: {
main: '#4caf50',
- contrastText: '#ffffff',
+ contrastText: '#00a807',
+ dark: '#388e3c',
},
warning: {
main: '#ff9800',
- contrastText: '#ffffff',
+ contrastText: '#be7100',
+ dark: '#f57c00',
},
text: {
primary: '#ffffff',
secondary: '#b3b3b3',
+ dark: '#212121',
},
border: '#404040',
},
@@ -306,6 +332,14 @@ const toCssVariables = (theme: ThemeConfig): Record => {
cssVars[`--${prefix}-color-warning-contrastText`] = theme.colors.warning.contrastText;
}
+ // Colors - Info
+ if (theme.colors?.info?.main) {
+ cssVars[`--${prefix}-color-info-main`] = theme.colors.info.main;
+ }
+ if (theme.colors?.info?.contrastText) {
+ cssVars[`--${prefix}-color-info-contrastText`] = theme.colors.info.contrastText;
+ }
+
// Colors - Text
if (theme.colors?.text?.primary) {
cssVars[`--${prefix}-color-text-primary`] = theme.colors.text.primary;
@@ -455,6 +489,10 @@ const toThemeVars = (theme: ThemeConfig): ThemeVars => {
main: `var(--${prefix}-color-error-main)`,
contrastText: `var(--${prefix}-color-error-contrastText)`,
},
+ info: {
+ contrastText: `var(--${prefix}-color-info-contrastText)`,
+ main: `var(--${prefix}-color-info-main)`,
+ },
success: {
main: `var(--${prefix}-color-success-main)`,
contrastText: `var(--${prefix}-color-success-contrastText)`,
diff --git a/packages/javascript/src/theme/types.ts b/packages/javascript/src/theme/types.ts
index 9fee3ad6..19aea3a2 100644
--- a/packages/javascript/src/theme/types.ts
+++ b/packages/javascript/src/theme/types.ts
@@ -57,34 +57,47 @@ export interface ThemeColors {
background: {
body: {
main: string;
+ dark?: string;
};
disabled: string;
surface: string;
+ dark?: string;
};
border: string;
error: {
contrastText: string;
main: string;
+ dark?: string;
+ };
+ info: {
+ contrastText: string;
+ main: string;
+ dark?: string;
};
primary: {
contrastText: string;
main: string;
+ dark?: string;
};
secondary: {
contrastText: string;
main: string;
+ dark?: string;
};
success: {
contrastText: string;
main: string;
+ dark?: string;
};
text: {
primary: string;
secondary: string;
+ dark?: string;
};
warning: {
contrastText: string;
main: string;
+ dark?: string;
};
}
@@ -155,33 +168,46 @@ export interface ThemeVars {
primary: {
main: string;
contrastText: string;
+ dark?: string;
};
secondary: {
main: string;
contrastText: string;
+ dark?: string;
};
background: {
surface: string;
disabled: string;
+ dark?: string;
body: {
main: string;
+ dark?: string;
};
};
error: {
main: string;
contrastText: string;
+ dark?: string;
+ };
+ info: {
+ contrastText: string;
+ main: string;
+ dark?: string;
};
success: {
main: string;
contrastText: string;
+ dark?: string;
};
warning: {
main: string;
contrastText: string;
+ dark?: string;
};
text: {
primary: string;
secondary: string;
+ dark?: string;
};
border: string;
};
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;
diff --git a/packages/javascript/src/utils/formatDate.ts b/packages/javascript/src/utils/formatDate.ts
new file mode 100644
index 00000000..0c34fa35
--- /dev/null
+++ b/packages/javascript/src/utils/formatDate.ts
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+/**
+ * Formats a date string to a human-readable format.
+ *
+ * @param dateString - The date string to format (optional)
+ * @returns A formatted date string in 'Month Day, Year' format, or '-' if no date is provided, or the original string if parsing fails
+ *
+ * @example
+ * ```typescript
+ * formatDate('2025-07-09T10:30:00Z'); // Returns "July 9, 2025"
+ * formatDate(''); // Returns "-"
+ * formatDate(undefined); // Returns "-"
+ * formatDate('invalid-date'); // Returns "invalid-date"
+ * ```
+ */
+const formatDate = (dateString?: string): string => {
+ if (!dateString) return '-';
+
+ try {
+ return new Date(dateString).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+ } catch {
+ return dateString;
+ }
+};
+
+export default formatDate;
diff --git a/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts b/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts
index 4f04984f..494c1a23 100644
--- a/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts
+++ b/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts
@@ -23,7 +23,13 @@ import createTheme from '../theme/createTheme';
/**
* Safely extracts a color value from the branding preference structure
*/
-const extractColorValue = (colorVariant?: {main?: string; contrastText?: string}) => {
+type ColorVariant = {main?: string; dark?: string; contrastText?: string};
+type TextColors = {primary?: string; secondary?: string; dark?: string};
+
+const extractColorValue = (colorVariant?: ColorVariant, preferDark = false): string | undefined => {
+ if (preferDark && colorVariant?.dark && colorVariant.dark.trim()) {
+ return colorVariant.dark;
+ }
return colorVariant?.main;
};
@@ -59,36 +65,50 @@ const transformThemeVariant = (themeVariant: ThemeVariant, isDark = false): Part
activatedOpacity: 0.12,
},
primary: {
- main: extractColorValue(colors?.primary),
+ main: extractColorValue(colors?.primary as ColorVariant, isDark),
contrastText: extractContrastText(colors?.primary),
+ dark: colors?.primary?.dark || (colors?.primary as ColorVariant)?.main,
},
secondary: {
- main: extractColorValue(colors?.secondary),
+ main: extractColorValue(colors?.secondary as ColorVariant, isDark),
contrastText: extractContrastText(colors?.secondary),
+ dark: colors?.secondary?.dark || (colors?.secondary as ColorVariant)?.main,
},
background: {
- surface: extractColorValue(colors?.background?.surface),
- disabled: extractColorValue(colors?.background?.surface),
+ surface: extractColorValue(colors?.background?.surface as ColorVariant, isDark),
+ disabled: extractColorValue(colors?.background?.surface as ColorVariant, isDark),
+ dark:
+ (colors?.background?.surface as ColorVariant)?.dark || (colors?.background?.surface as ColorVariant)?.main,
body: {
- main: extractColorValue(colors?.background?.body),
+ main: extractColorValue(colors?.background?.body as ColorVariant, isDark),
+ dark: (colors?.background?.body as ColorVariant)?.dark || (colors?.background?.body as ColorVariant)?.main,
},
},
text: {
- primary: colors?.text?.primary,
- secondary: colors?.text?.secondary,
+ primary: (colors?.text as TextColors)?.primary,
+ secondary: (colors?.text as TextColors)?.secondary,
+ dark: (colors?.text as TextColors)?.dark || (colors?.text as TextColors)?.primary,
},
border: colors?.outlined?.default,
error: {
- main: extractColorValue(colors?.alerts?.error),
+ main: extractColorValue(colors?.alerts?.error as ColorVariant, isDark),
contrastText: extractContrastText(colors?.alerts?.error),
+ dark: (colors?.alerts?.error as ColorVariant)?.dark || (colors?.alerts?.error as ColorVariant)?.main,
},
- success: {
- main: extractColorValue(colors?.alerts?.info),
+ info: {
+ main: extractColorValue(colors?.alerts?.info as ColorVariant, isDark),
contrastText: extractContrastText(colors?.alerts?.info),
+ dark: (colors?.alerts?.info as ColorVariant)?.dark || (colors?.alerts?.info as ColorVariant)?.main,
+ },
+ success: {
+ main: extractColorValue(colors?.alerts?.neutral as ColorVariant, isDark),
+ contrastText: extractContrastText(colors?.alerts?.neutral),
+ dark: (colors?.alerts?.neutral as ColorVariant)?.dark || (colors?.alerts?.neutral as ColorVariant)?.main,
},
warning: {
- main: extractColorValue(colors?.alerts?.warning),
+ main: extractColorValue(colors?.alerts?.warning as ColorVariant, isDark),
contrastText: extractContrastText(colors?.alerts?.warning),
+ dark: (colors?.alerts?.warning as ColorVariant)?.dark || (colors?.alerts?.warning as ColorVariant)?.main,
},
},
// Extract border radius from buttons or inputs
diff --git a/packages/react/src/components/control/AsgardeoLoading.tsx b/packages/nextjs/src/client/components/control/Loading/Loading.tsx
similarity index 72%
rename from packages/react/src/components/control/AsgardeoLoading.tsx
rename to packages/nextjs/src/client/components/control/Loading/Loading.tsx
index a42227f9..6a69d840 100644
--- a/packages/react/src/components/control/AsgardeoLoading.tsx
+++ b/packages/nextjs/src/client/components/control/Loading/Loading.tsx
@@ -16,13 +16,15 @@
* under the License.
*/
+'use client';
+
import {FC, PropsWithChildren, ReactNode} from 'react';
-import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo';
+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 +38,18 @@ 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> = ({
- children,
- fallback = null,
-}: PropsWithChildren) => {
+const Loading: FC> = ({children, fallback = null}: PropsWithChildren) => {
const {isLoading} = useAsgardeo();
if (!isLoading) {
@@ -60,6 +59,6 @@ const AsgardeoLoading: FC> = ({
return <>{children}>;
};
-AsgardeoLoading.displayName = 'AsgardeoLoading';
+Loading.displayName = 'Loading';
-export default AsgardeoLoading;
+export default Loading;
diff --git a/packages/react/package.json b/packages/react/package.json
index 389e27ed..e129c4e8 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -43,7 +43,7 @@
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@types/node": "^22.15.3",
- "@types/react": "^19.1.4",
+ "@types/react": "^19.1.5",
"@vitest/browser": "^3.1.3",
"@wso2/eslint-plugin": "catalog:",
"@wso2/prettier-config": "catalog:",
@@ -63,9 +63,9 @@
},
"dependencies": {
"@asgardeo/browser": "workspace:^",
+ "@emotion/css": "^11.13.5",
"@floating-ui/react": "^0.27.12",
"@types/react-dom": "^19.1.5",
- "clsx": "^2.1.1",
"esbuild": "^0.25.4",
"react-dom": "^19.1.0",
"tslib": "^2.8.1"
diff --git a/packages/react/src/components/actions/SignInButton/BaseSignInButton.tsx b/packages/react/src/components/actions/SignInButton/BaseSignInButton.tsx
index 0a09b290..8baf41ac 100644
--- a/packages/react/src/components/actions/SignInButton/BaseSignInButton.tsx
+++ b/packages/react/src/components/actions/SignInButton/BaseSignInButton.tsx
@@ -17,7 +17,7 @@
*/
import {WithPreferences, withVendorCSSClassPrefix} from '@asgardeo/browser';
-import clsx from 'clsx';
+import {cx} from '@emotion/css';
import {
ButtonHTMLAttributes,
forwardRef,
@@ -93,7 +93,7 @@ const BaseSignInButton: ForwardRefExoticComponent {
+ * 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;
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 96%
rename from packages/react/src/components/control/SignedOut.tsx
rename to packages/react/src/components/control/SignedOut/SignedOut.tsx
index af89c52b..cfad0b76 100644
--- a/packages/react/src/components/control/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.
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..4928372a
--- /dev/null
+++ b/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.styles.ts
@@ -0,0 +1,179 @@
+/**
+ * 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 {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
+ */
+const useStyles = (theme: Theme, colorScheme: string) => {
+ return useMemo(() => {
+ const root = css`
+ padding: calc(${theme.vars.spacing.unit} * 4);
+ min-width: 600px;
+ margin: 0 auto;
+ `;
+
+ const card = css`
+ background: ${theme.vars.colors.background.surface};
+ border-radius: ${theme.vars.borderRadius.large};
+ padding: calc(${theme.vars.spacing.unit} * 4);
+ `;
+
+ const content = css`
+ display: flex;
+ flex-direction: column;
+ gap: calc(${theme.vars.spacing.unit} * 2);
+ `;
+
+ const form = css`
+ display: flex;
+ flex-direction: column;
+ gap: calc(${theme.vars.spacing.unit} * 2);
+ width: 100%;
+ `;
+
+ const header = css`
+ display: flex;
+ align-items: center;
+ gap: calc(${theme.vars.spacing.unit} * 1.5);
+ margin-bottom: calc(${theme.vars.spacing.unit} * 1.5);
+ `;
+
+ const field = css`
+ display: flex;
+ align-items: center;
+ padding: ${theme.vars.spacing.unit} 0;
+ border-bottom: 1px solid ${theme.vars.colors.border};
+ min-height: 32px;
+ `;
+
+ const fieldGroup = css`
+ display: flex;
+ flex-direction: column;
+ gap: calc(${theme.vars.spacing.unit} * 0.5);
+ `;
+
+ const textarea = css`
+ 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;
+ }
+ `;
+
+ const textareaError = css`
+ border-color: ${theme.vars.colors.error.main};
+ `;
+
+ const input = css``;
+
+ const avatarContainer = css`
+ align-items: flex-start;
+ display: flex;
+ gap: calc(${theme.vars.spacing.unit} * 2);
+ margin-bottom: ${theme.vars.spacing.unit};
+ `;
+
+ const actions = css`
+ display: flex;
+ gap: ${theme.vars.spacing.unit};
+ justify-content: flex-end;
+ padding-top: calc(${theme.vars.spacing.unit} * 2);
+ `;
+
+ const infoContainer = css`
+ display: flex;
+ flex-direction: column;
+ gap: ${theme.vars.spacing.unit};
+ `;
+
+ const value = css`
+ 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;
+ `;
+
+ const popup = css`
+ padding: calc(${theme.vars.spacing.unit} * 2);
+ `;
+
+ const errorAlert = css`
+ margin-bottom: calc(${theme.vars.spacing.unit} * 2);
+ `;
+
+ return {
+ root,
+ card,
+ content,
+ form,
+ header,
+ field,
+ fieldGroup,
+ textarea,
+ textareaError,
+ input,
+ avatarContainer,
+ actions,
+ infoContainer,
+ value,
+ popup,
+ errorAlert,
+ };
+ }, [
+ theme.vars.spacing.unit,
+ theme.vars.colors.background.surface,
+ theme.vars.colors.border,
+ theme.vars.borderRadius.large,
+ theme.vars.borderRadius.medium,
+ theme.vars.typography.fontSizes.md,
+ theme.vars.colors.text.primary,
+ theme.vars.colors.primary.main,
+ theme.vars.colors.background.disabled,
+ theme.vars.colors.text.secondary,
+ theme.vars.colors.error.main,
+ colorScheme,
+ ]);
+};
+
+export default useStyles;
diff --git a/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx b/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx
index 30a6b194..fd5ed234 100644
--- a/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx
+++ b/packages/react/src/components/presentation/CreateOrganization/BaseCreateOrganization.tsx
@@ -16,114 +16,18 @@
* under the License.
*/
-import {withVendorCSSClassPrefix, CreateOrganizationPayload} from '@asgardeo/browser';
-import clsx from 'clsx';
-import {ChangeEvent, CSSProperties, FC, ReactElement, ReactNode, useMemo, useState} from 'react';
+import {CreateOrganizationPayload} 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';
import Button from '../../primitives/Button/Button';
-import {Dialog, DialogContent, DialogHeading} from '../../primitives/Popover/Popover';
+import Dialog from '../../primitives/Dialog/Dialog';
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 +80,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: '',
@@ -214,7 +118,6 @@ export const BaseCreateOrganization: FC = ({
[field]: value,
}));
- // Clear error when user starts typing
if (formErrors[field]) {
setFormErrors(prev => ({
...prev,
@@ -223,23 +126,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 +182,17 @@ export const BaseCreateOrganization: FC = ({
}
};
- const containerStyle = {
- ...styles.root,
- ...(cardLayout ? styles.card : {}),
- };
-
const createOrganizationContent = (
-