From 35423862a26d4c16f2f81491b3e5baa31c14d22e Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Thu, 12 Mar 2026 12:03:51 +0530 Subject: [PATCH 01/22] (Phase 1) Vuejs sdk redesigned for vuejs3 --- packages/vue/esbuild.config.mjs | 45 + packages/vue/package.json | 169 +- packages/vue/src/AsgardeoVueClient.ts | 514 +++ packages/vue/src/{ => __legacy__}/auth-api.ts | 870 ++--- .../src/__legacy__/composables/useAsgardeo.ts | 30 + .../composables/useAsgardeoContext.ts | 74 +- packages/vue/src/__legacy__/index.ts | 22 + .../src/__legacy__/plugins/AsgardeoPlugin.ts | 231 ++ .../vue/src/{ => __legacy__}/public-api.ts | 40 +- .../tests/AsgardeoPlugin.test.ts | 568 +-- .../{ => __legacy__}/tests/auth-api.test.ts | 1010 +++--- .../src/{ => __legacy__}/tests/mocks/mocks.ts | 412 +-- .../tests/useAsgardeo.test.ts | 174 +- .../tests/useAsgardeoContext.test.ts | 100 +- packages/vue/src/{ => __legacy__}/types.ts | 274 +- .../vue/src/{ => __legacy__}/vitest.config.ts | 76 +- packages/vue/src/__temp__/api.ts | 273 ++ packages/vue/src/__temp__/models.ts | 44 + packages/vue/src/api/getAllOrganizations.ts | 63 + packages/vue/src/api/getMeOrganizations.ts | 63 + packages/vue/src/api/getSchemas.ts | 59 + packages/vue/src/api/getScim2Me.ts | 59 + .../vue/src/components/AsgardeoProvider.ts | 433 +++ packages/vue/src/composables/useAsgardeo.ts | 94 +- packages/vue/src/index.ts | 79 +- packages/vue/src/keys.ts | 25 + packages/vue/src/models/config.ts | 21 + packages/vue/src/models/contexts.ts | 101 + packages/vue/src/plugins/AsgardeoPlugin.ts | 286 +- packages/vue/tsconfig.json | 75 +- packages/vue/tsconfig.lib.json | 44 +- packages/vue/tsconfig.spec.json | 15 + packages/vue/vitest.config.ts | 26 + pnpm-lock.yaml | 3074 +---------------- 34 files changed, 4178 insertions(+), 5265 deletions(-) create mode 100644 packages/vue/esbuild.config.mjs create mode 100644 packages/vue/src/AsgardeoVueClient.ts rename packages/vue/src/{ => __legacy__}/auth-api.ts (97%) create mode 100644 packages/vue/src/__legacy__/composables/useAsgardeo.ts rename packages/vue/src/{ => __legacy__}/composables/useAsgardeoContext.ts (97%) create mode 100644 packages/vue/src/__legacy__/index.ts create mode 100644 packages/vue/src/__legacy__/plugins/AsgardeoPlugin.ts rename packages/vue/src/{ => __legacy__}/public-api.ts (97%) rename packages/vue/src/{ => __legacy__}/tests/AsgardeoPlugin.test.ts (97%) rename packages/vue/src/{ => __legacy__}/tests/auth-api.test.ts (97%) rename packages/vue/src/{ => __legacy__}/tests/mocks/mocks.ts (96%) rename packages/vue/src/{ => __legacy__}/tests/useAsgardeo.test.ts (97%) rename packages/vue/src/{ => __legacy__}/tests/useAsgardeoContext.test.ts (97%) rename packages/vue/src/{ => __legacy__}/types.ts (96%) rename packages/vue/src/{ => __legacy__}/vitest.config.ts (96%) create mode 100644 packages/vue/src/__temp__/api.ts create mode 100644 packages/vue/src/__temp__/models.ts create mode 100644 packages/vue/src/api/getAllOrganizations.ts create mode 100644 packages/vue/src/api/getMeOrganizations.ts create mode 100644 packages/vue/src/api/getSchemas.ts create mode 100644 packages/vue/src/api/getScim2Me.ts create mode 100644 packages/vue/src/components/AsgardeoProvider.ts create mode 100644 packages/vue/src/keys.ts create mode 100644 packages/vue/src/models/config.ts create mode 100644 packages/vue/src/models/contexts.ts create mode 100644 packages/vue/tsconfig.spec.json create mode 100644 packages/vue/vitest.config.ts diff --git a/packages/vue/esbuild.config.mjs b/packages/vue/esbuild.config.mjs new file mode 100644 index 000000000..acb159724 --- /dev/null +++ b/packages/vue/esbuild.config.mjs @@ -0,0 +1,45 @@ +/** + * 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 {readFileSync} from 'fs'; +import {build} from 'esbuild'; + +const pkg = JSON.parse(readFileSync('./package.json', 'utf8')); + +const commonOptions = { + bundle: true, + entryPoints: ['src/index.ts'], + external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], + metafile: true, + platform: 'browser', + target: ['es2020'], +}; + +await build({ + ...commonOptions, + format: 'esm', + outfile: 'dist/index.js', + sourcemap: true, +}); + +await build({ + ...commonOptions, + format: 'cjs', + outfile: 'dist/cjs/index.js', + sourcemap: true, +}); diff --git a/packages/vue/package.json b/packages/vue/package.json index 2acbf734a..72f6bc69e 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,95 +1,74 @@ -{ - "name": "@asgardeo/vue", - "version": "0.0.10", - "description": "Vue SDK for Asgardeo - Authentication and Identity Management", - "main": "dist/cjs/index.js", - "module": "dist/esm/index.js", - "types": "dist/index.d.ts", - "type": "module", - "author": "WSO2", - "license": "Apache-2.0", - "files": [ - "dist", - "LICENSE", - "README.md" - ], - "homepage": "https://github.com/asgardeo/javascript/tree/main/packages/vue#readme", - "bugs": { - "url": "https://github.com/asgardeo/javascript/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/asgardeo/javascript", - "directory": "packages/vue" - }, - "keywords": [ - "asgardeo", - "authentication", - "identity", - "oauth", - "oidc", - "vue", - "vue3", - "login", - "sso", - "identity-management" - ], - "scripts": { - "build": "rollup -c", - "dev": "rollup -c -w", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts", - "lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", - "typecheck": "vue-tsc --noEmit", - "test": "vitest --config src/vitest.config.ts --environment=jsdom --passWithNoTests" - }, - "devDependencies": { - "vite": "7.1.12", - "@rollup/plugin-commonjs": "25.0.7", - "@rollup/plugin-image": "3.0.3", - "@rollup/plugin-node-resolve": "15.2.3", - "@rollup/plugin-typescript": "11.1.6", - "@types/node": "20.12.7", - "@vitest/coverage-v8": "3.0.8", - "@vitest/web-worker": "3.0.8", - "@vue/eslint-config-prettier": "8.0.0", - "@vue/eslint-config-typescript": "12.0.0", - "@vue/test-utils": "2.4.6", - "@wso2/eslint-plugin": "catalog:", - "@wso2/prettier-config": "catalog:", - "@wso2/stylelint-config": "catalog:", - "eslint": "8.57.0", - "prettier": "3.2.5", - "rollup": "4.32.0", - "rollup-plugin-dts": "6.1.0", - "rollup-plugin-polyfill-node": "0.13.0", - "rollup-plugin-styles": "4.0.0", - "sass": "1.75.0", - "stylelint": "15.1.0", - "tslib": "2.6.2", - "typescript": "5.1.6", - "vitest": "3.0.8", - "vue-tsc": "2.2.2" - }, - "dependencies": { - "@asgardeo/auth-spa": "3.3.2", - "@asgardeo/js": "0.1.3", - "@vitejs/plugin-vue": "5.2.4", - "base64url": "3.0.1", - "buffer": "6.0.3", - "clsx": "2.1.1", - "fast-sha256": "1.3.0", - "jose": "5.3.0", - "randombytes": "2.1.0" - }, - "peerDependencies": { - "vue": ">=3.5.13" - }, - "exports": { - "types": "./dist/index.d.ts", - "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js" - }, - "publishConfig": { - "access": "public" - } -} +{ + "name": "@asgardeo/vue", + "version": "1.0.0", + "description": "Vue 3 SDK for Asgardeo - Authentication and Identity Management", + "keywords": [ + "asgardeo", + "authentication", + "identity", + "oauth", + "oidc", + "vue", + "vue3", + "login", + "sso", + "identity-management" + ], + "homepage": "https://github.com/asgardeo/javascript/tree/main/packages/vue#readme", + "bugs": { + "url": "https://github.com/asgardeo/javascript/issues" + }, + "author": "WSO2", + "license": "Apache-2.0", + "type": "module", + "main": "dist/cjs/index.js", + "module": "dist/index.js", + "exports": { + "import": "./dist/index.js", + "require": "./dist/cjs/index.js" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "types": "dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/asgardeo/javascript", + "directory": "packages/vue" + }, + "scripts": { + "build": "pnpm clean && node esbuild.config.mjs && tsc -p tsconfig.lib.json --emitDeclarationOnly --outDir dist", + "clean": "rimraf dist", + "lint": "eslint . --ext .js,.ts,.vue,.cjs,.mjs", + "lint:fix": "eslint . --ext .js,.ts,.vue,.cjs,.mjs --fix", + "test": "vitest --passWithNoTests", + "typecheck": "tsc -p tsconfig.lib.json" + }, + "devDependencies": { + "@types/node": "22.15.3", + "@vue/test-utils": "2.4.6", + "@wso2/eslint-plugin": "catalog:", + "@wso2/prettier-config": "catalog:", + "esbuild": "0.25.9", + "eslint": "8.57.0", + "jsdom": "26.1.0", + "prettier": "2.6.2", + "rimraf": "6.1.0", + "typescript": "5.7.2", + "vitest": "3.1.3", + "vue": "3.5.13" + }, + "peerDependencies": { + "vue": ">=3.5.0" + }, + "dependencies": { + "@asgardeo/browser": "workspace:*", + "@asgardeo/i18n": "workspace:*", + "tslib": "2.8.1" + }, + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/packages/vue/src/AsgardeoVueClient.ts b/packages/vue/src/AsgardeoVueClient.ts new file mode 100644 index 000000000..04fbc1bd8 --- /dev/null +++ b/packages/vue/src/AsgardeoVueClient.ts @@ -0,0 +1,514 @@ +/** + * 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 { + AsgardeoBrowserClient, + flattenUserSchema, + generateFlattenedUserProfile, + UserProfile, + SignInOptions, + SignOutOptions, + User, + generateUserProfile, + EmbeddedFlowExecuteResponse, + SignUpOptions, + EmbeddedFlowExecuteRequestPayload, + AsgardeoRuntimeError, + executeEmbeddedSignUpFlow, + EmbeddedSignInFlowHandleRequestPayload, + executeEmbeddedSignInFlow, + executeEmbeddedSignInFlowV2, + Organization, + IdToken, + EmbeddedFlowExecuteRequestConfig, + deriveOrganizationHandleFromBaseUrl, + AllOrganizationsApiResponse, + extractUserClaimsFromIdToken, + TokenResponse, + HttpRequestConfig, + HttpResponse, + navigate, + getRedirectBasedSignUpUrl, + Config, + TokenExchangeRequestConfig, + Platform, + isEmpty, + EmbeddedSignInFlowResponseV2, + executeEmbeddedSignUpFlowV2, + EmbeddedSignInFlowStatusV2, +} from '@asgardeo/browser'; +import AuthAPI from './__temp__/api'; +import getAllOrganizations from './api/getAllOrganizations'; +import getMeOrganizations from './api/getMeOrganizations'; +import getSchemas from './api/getSchemas'; +import getScim2Me from './api/getScim2Me'; +import {AsgardeoVueConfig} from './models/config'; + +/** + * Client for implementing Asgardeo in Vue applications. + * This class provides the core functionality for managing user authentication and sessions. + * + * @typeParam T - Configuration type that extends AsgardeoVueConfig. + */ +class AsgardeoVueClient extends AsgardeoBrowserClient { + private asgardeo: AuthAPI; + + private loadingState: boolean = false; + + private clientInstanceId: number; + + constructor(instanceId: number = 0) { + super(); + this.clientInstanceId = instanceId; + + // FIXME: This has to be the browser client from `@asgardeo/browser` package. + this.asgardeo = new AuthAPI(undefined, instanceId); + } + + public getInstanceId(): number { + return this.clientInstanceId; + } + + private setLoading(loading: boolean): void { + this.loadingState = loading; + } + + private async withLoading(operation: () => Promise): Promise { + this.setLoading(true); + try { + const result: TResult = await operation(); + return result; + } finally { + this.setLoading(false); + } + } + + override initialize(config: AsgardeoVueConfig): Promise { + let resolvedOrganizationHandle: string | undefined = config?.organizationHandle; + + if (!resolvedOrganizationHandle) { + resolvedOrganizationHandle = deriveOrganizationHandleFromBaseUrl(config?.baseUrl); + } + + return this.withLoading(async () => + this.asgardeo.init({...config, organizationHandle: resolvedOrganizationHandle} as any), + ); + } + + override reInitialize(config: Partial): Promise { + return this.withLoading(async () => { + let isInitialized: boolean; + + try { + await this.asgardeo.reInitialize(config); + isInitialized = true; + } catch (error) { + throw new AsgardeoRuntimeError( + `Failed to check if the client is initialized: ${error instanceof Error ? error.message : String(error)}`, + 'AsgardeoVueClient-reInitialize-RuntimeError-001', + 'vue', + 'An error occurred while checking the initialization status of the client.', + ); + } + + return isInitialized; + }); + } + + // eslint-disable-next-line class-methods-use-this + override async updateUserProfile(): Promise { + throw new Error('Not implemented'); + } + + override async getUser(options?: any): Promise { + try { + let baseUrl: string = options?.baseUrl; + + if (!baseUrl) { + const configData: any = await this.asgardeo.getConfigData(); + baseUrl = configData?.baseUrl; + } + + const profile: User = await getScim2Me({baseUrl}); + const schemas: any = await getSchemas({baseUrl}); + + return generateUserProfile(profile, flattenUserSchema(schemas)); + } catch (error) { + return extractUserClaimsFromIdToken(await this.getDecodedIdToken()); + } + } + + async getDecodedIdToken(sessionId?: string): Promise { + return this.asgardeo.getDecodedIdToken(sessionId); + } + + async getIdToken(): Promise { + return this.withLoading(async () => this.asgardeo.getIdToken()); + } + + override async getUserProfile(options?: any): Promise { + return this.withLoading(async () => { + try { + let baseUrl: string = options?.baseUrl; + + if (!baseUrl) { + const configData: any = await this.asgardeo.getConfigData(); + baseUrl = configData?.baseUrl; + } + + const profile: User = await getScim2Me({baseUrl, instanceId: this.getInstanceId()}); + const schemas: any = await getSchemas({baseUrl, instanceId: this.getInstanceId()}); + + const processedSchemas: any = flattenUserSchema(schemas); + + const output: UserProfile = { + flattenedProfile: generateFlattenedUserProfile(profile, processedSchemas), + profile, + schemas: processedSchemas, + }; + + return output; + } catch (error) { + return { + flattenedProfile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()), + profile: extractUserClaimsFromIdToken(await this.getDecodedIdToken()), + schemas: [], + }; + } + }); + } + + override async getMyOrganizations(options?: any): Promise { + try { + let baseUrl: string = options?.baseUrl; + + if (!baseUrl) { + const configData: any = await this.asgardeo.getConfigData(); + baseUrl = configData?.baseUrl; + } + + return await getMeOrganizations({baseUrl, instanceId: this.getInstanceId()}); + } catch (error) { + throw new AsgardeoRuntimeError( + `Failed to fetch the user's associated organizations: ${ + error instanceof Error ? error.message : String(error) + }`, + 'AsgardeoVueClient-getMyOrganizations-RuntimeError-001', + 'vue', + 'An error occurred while fetching associated organizations of the signed-in user.', + ); + } + } + + override async getAllOrganizations(options?: any): Promise { + try { + let baseUrl: string = options?.baseUrl; + + if (!baseUrl) { + const configData: any = await this.asgardeo.getConfigData(); + baseUrl = configData?.baseUrl; + } + + return await getAllOrganizations({baseUrl, instanceId: this.getInstanceId()}); + } catch (error) { + throw new AsgardeoRuntimeError( + `Failed to fetch all organizations: ${error instanceof Error ? error.message : String(error)}`, + 'AsgardeoVueClient-getAllOrganizations-RuntimeError-001', + 'vue', + 'An error occurred while fetching all the organizations associated with the user.', + ); + } + } + + override async getCurrentOrganization(): Promise { + try { + return await this.withLoading(async () => { + const idToken: IdToken = await this.getDecodedIdToken(); + return { + id: idToken?.org_id, + name: idToken?.org_name, + orgHandle: idToken?.org_handle, + }; + }); + } catch (error) { + throw new AsgardeoRuntimeError( + `Failed to fetch the current organization: ${error instanceof Error ? error.message : String(error)}`, + 'AsgardeoVueClient-getCurrentOrganization-RuntimeError-001', + 'vue', + 'An error occurred while fetching the current organization of the signed-in user.', + ); + } + } + + override async switchOrganization(organization: Organization): Promise { + return this.withLoading(async () => { + try { + const configData: any = await this.asgardeo.getConfigData(); + const sourceInstanceId: number | undefined = configData?.organizationChain?.sourceInstanceId; + + if (!organization.id) { + throw new AsgardeoRuntimeError( + 'Organization ID is required for switching organizations', + 'vue-AsgardeoVueClient-SwitchOrganizationError-001', + 'vue', + 'The organization object must contain a valid ID to perform the organization switch.', + ); + } + + const exchangeConfig: TokenExchangeRequestConfig = { + attachToken: false, + data: { + client_id: '{{clientId}}', + grant_type: 'organization_switch', + scope: '{{scopes}}', + switching_organization: organization.id, + token: '{{accessToken}}', + }, + id: 'organization-switch', + returnsSession: true, + signInRequired: sourceInstanceId === undefined, + }; + + return (await this.asgardeo.exchangeToken(exchangeConfig, () => {})) as TokenResponse | Response; + } catch (error) { + throw new AsgardeoRuntimeError( + `Failed to switch organization: ${error.message || error}`, + 'vue-AsgardeoVueClient-SwitchOrganizationError-003', + 'vue', + 'An error occurred while switching to the specified organization. Please try again.', + ); + } + }); + } + + override isLoading(): boolean { + return this.loadingState || this.asgardeo.isLoading(); + } + + async isInitialized(): Promise { + return this.asgardeo.isInitialized(); + } + + override async isSignedIn(): Promise { + return this.asgardeo.isSignedIn(); + } + + override getConfiguration(): T { + return this.asgardeo.getConfigData() as unknown as T; + } + + override async exchangeToken(config: TokenExchangeRequestConfig): Promise { + return this.withLoading( + async () => this.asgardeo.exchangeToken(config, () => {}) as unknown as TokenResponse | Response, + ); + } + + override signIn( + options?: SignInOptions, + sessionId?: string, + onSignInSuccess?: (afterSignInUrl: string) => void, + ): Promise; + override signIn( + payload: EmbeddedSignInFlowHandleRequestPayload, + request: EmbeddedFlowExecuteRequestConfig, + sessionId?: string, + onSignInSuccess?: (afterSignInUrl: string) => void, + ): Promise; + override async signIn(...args: any[]): Promise { + return this.withLoading(async () => { + const arg1: any = args[0]; + const arg2: any = args[1]; + + const config: AsgardeoVueConfig | undefined = (await this.asgardeo.getConfigData()) as + | AsgardeoVueConfig + | undefined; + + const platformFromStorage: string | null = sessionStorage.getItem('asgardeo_platform'); + const isV2Platform: boolean = + (config && config.platform === Platform.AsgardeoV2) || platformFromStorage === 'AsgardeoV2'; + + if (isV2Platform && typeof arg1 === 'object' && arg1 !== null && (arg1 as any).callOnlyOnRedirect === true) { + return undefined as any; + } + + if ( + isV2Platform && + typeof arg1 === 'object' && + arg1 !== null && + !isEmpty(arg1) && + ('flowId' in arg1 || 'applicationId' in arg1) + ) { + const authIdFromUrl: string = new URL(window.location.href).searchParams.get('authId'); + const authIdFromStorage: string = sessionStorage.getItem('asgardeo_auth_id'); + const authId: string = authIdFromUrl || authIdFromStorage; + const baseUrlFromStorage: string = sessionStorage.getItem('asgardeo_base_url'); + const baseUrl: string = config?.baseUrl || baseUrlFromStorage; + + const response: EmbeddedSignInFlowResponseV2 = await executeEmbeddedSignInFlowV2({ + authId, + baseUrl, + payload: arg1 as EmbeddedSignInFlowHandleRequestPayload, + url: arg2?.url, + }); + + if ( + isV2Platform && + response && + typeof response === 'object' && + response['flowStatus'] === EmbeddedSignInFlowStatusV2.Complete && + response['assertion'] + ) { + const decodedAssertion: { + [key: string]: unknown; + exp?: number; + iat?: number; + scope?: string; + } = await this.decodeJwtToken<{ + [key: string]: unknown; + exp?: number; + iat?: number; + scope?: string; + }>(response['assertion']); + + const createdAt: number = decodedAssertion.iat ? decodedAssertion.iat * 1000 : Date.now(); + const expiresIn: number = + decodedAssertion.exp && decodedAssertion.iat ? decodedAssertion.exp - decodedAssertion.iat : 3600; + + await this.setSession({ + access_token: response['assertion'], + created_at: createdAt, + expires_in: expiresIn, + id_token: response['assertion'], + scope: decodedAssertion.scope, + token_type: 'Bearer', + }); + } + + return response; + } + + if (typeof arg1 === 'object' && 'flowId' in arg1 && typeof arg2 === 'object' && 'url' in arg2) { + return executeEmbeddedSignInFlow({ + payload: arg1, + url: arg2.url, + }); + } + + return (await this.asgardeo.signIn(arg1 as any)) as unknown as Promise; + }); + } + + override async signInSilently(options?: SignInOptions): Promise { + return this.asgardeo.signInSilently(options as Record); + } + + override signOut(options?: SignOutOptions, afterSignOut?: (afterSignOutUrl: string) => void): Promise; + override signOut( + options?: SignOutOptions, + sessionId?: string, + afterSignOut?: (afterSignOutUrl: string) => void, + ): Promise; + override async signOut(...args: any[]): Promise { + if (args[1] && typeof args[1] !== 'function') { + throw new Error('The second argument must be a function.'); + } + + const config: AsgardeoVueConfig = (await this.asgardeo.getConfigData()) as AsgardeoVueConfig; + + if (config.platform === Platform.AsgardeoV2) { + this.asgardeo.clearSession(); + + if (config.signInUrl) { + navigate(config.signInUrl); + } else { + this.signIn(config.signInOptions); + } + + args[1]?.(config.afterSignOutUrl || ''); + + return Promise.resolve(config.afterSignOutUrl || ''); + } + + const response: boolean = await this.asgardeo.signOut(args[1]); + + return Promise.resolve(String(response)); + } + + override async signUp(options?: SignUpOptions): Promise; + override async signUp(payload: EmbeddedFlowExecuteRequestPayload): Promise; + override async signUp(...args: any[]): Promise { + const config: AsgardeoVueConfig = (await this.asgardeo.getConfigData()) as AsgardeoVueConfig; + const firstArg: any = args[0]; + const baseUrl: string = config?.baseUrl; + + if (config.platform === Platform.AsgardeoV2) { + const authIdFromUrl: string = new URL(window.location.href).searchParams.get('authId'); + const authIdFromStorage: string = sessionStorage.getItem('asgardeo_auth_id'); + const authId: string = authIdFromUrl || authIdFromStorage; + + if (authIdFromUrl && !authIdFromStorage) { + sessionStorage.setItem('asgardeo_auth_id', authIdFromUrl); + } + + return executeEmbeddedSignUpFlowV2({ + authId, + baseUrl, + payload: + typeof firstArg === 'object' && 'flowType' in firstArg + ? {...(firstArg as EmbeddedFlowExecuteRequestPayload), verbose: true} + : (firstArg as EmbeddedFlowExecuteRequestPayload), + }) as any; + } + + if (typeof firstArg === 'object' && 'flowType' in firstArg) { + return executeEmbeddedSignUpFlow({ + baseUrl, + payload: firstArg as EmbeddedFlowExecuteRequestPayload, + }); + } + + navigate(getRedirectBasedSignUpUrl(config as Config)); + return undefined; + } + + async request(requestConfig?: HttpRequestConfig): Promise> { + return this.asgardeo.httpRequest(requestConfig); + } + + async requestAll(requestConfigs?: HttpRequestConfig[]): Promise[]> { + return this.asgardeo.httpRequestAll(requestConfigs); + } + + override async getAccessToken(sessionId?: string): Promise { + return this.asgardeo.getAccessToken(sessionId); + } + + override clearSession(sessionId?: string): void { + this.asgardeo.clearSession(sessionId); + } + + override async setSession(sessionData: Record, sessionId?: string): Promise { + return (await this.asgardeo.getStorageManager()).setSessionData(sessionData, sessionId); + } + + override decodeJwtToken>(token: string): Promise { + return this.asgardeo.decodeJwtToken(token); + } +} + +export default AsgardeoVueClient; diff --git a/packages/vue/src/auth-api.ts b/packages/vue/src/__legacy__/auth-api.ts similarity index 97% rename from packages/vue/src/auth-api.ts rename to packages/vue/src/__legacy__/auth-api.ts index cd57c4fcb..f522dda19 100644 --- a/packages/vue/src/auth-api.ts +++ b/packages/vue/src/__legacy__/auth-api.ts @@ -1,435 +1,435 @@ -/** - * 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 { - AsgardeoAuthException, - AsgardeoSPAClient, - AuthClientConfig, - BasicUserInfo, - Config, - IdToken, - Hooks, - HttpClientInstance, - HttpRequestConfig, - HttpResponse, - OIDCEndpoints, - SignInConfig, - SPACustomGrantConfig, -} from '@asgardeo/auth-spa'; -import {reactive} from 'vue'; -import {AuthStateInterface, AuthVueConfig} from './types'; - -class AuthAPI { - static DEFAULT_STATE: AuthStateInterface; - - private _authState: AuthStateInterface = reactive({...AuthAPI.DEFAULT_STATE}); - - private _client: AsgardeoSPAClient; - - constructor(spaClient?: AsgardeoSPAClient) { - this._client = spaClient ?? AsgardeoSPAClient.getInstance(); - } - - /** - * Method to return Auth Client instance authentication state. - * - * @return {AuthStateInterface} Authentication State. - */ - public getState = (): AuthStateInterface => this._authState; - - /** - * Initializes the AuthClient instance with the given authentication configuration. - * - * @param {AuthClientConfig} config - The authentication configuration object - * containing details such as client ID, redirect URLs, and base URL. - * @returns {Promise} A promise that resolves to `true` if initialization is successful. - */ - public init = (config: AuthVueConfig): Promise => this._client.initialize(config); - - /** - * Handles user sign-in by exchanging the authorization code for tokens - * and updating the authentication state if the user is authenticated. - * - * @param {SignInConfig} config - The sign-in configuration containing client-specific settings. - * @param {string} authorizationCode - The authorization code received from the authentication provider. - * @param {string} sessionState - The session state value to track the authentication session. - * @param {string} [authState] - An optional authentication state parameter for additional tracking. - * @param {{ params: Record }} [tokenRequestConfig] - Optional token request parameters. - * @returns {Promise} A promise resolving to the authenticated user's basic information. - */ - public signIn = async ( - config?: SignInConfig, - authorizationCode?: string, - sessionState?: string, - authState?: string, - callback?: (response: BasicUserInfo) => void, - tokenRequestConfig?: {params: Record}, - ): Promise => - this._client - .signIn(config, authorizationCode, sessionState, authState, tokenRequestConfig) - .then(async (response: BasicUserInfo) => { - if (!response) { - return response; - } - if (await this._client.isSignedIn()) { - Object.assign(this._authState, { - allowedScopes: response.allowedScopes, - displayName: response.displayName, - email: response.email, - isLoading: false, - isSignedIn: true, - isSigningOut: false, - sub: response.sub, - username: response.username, - }); - - if (callback) { - callback(response); - } - } - - return response; - }) - .catch((error: Error) => Promise.reject(error)); - - /** - * Signs the user out and resets the authentication state. - * - * @param {(response?: boolean) => void} callback - An optional callback function to execute after sign-out. - * @returns {Promise} A promise resolving to `true` if sign-out is successful. - * - */ - public signOut = async (callback?: (response?: boolean) => void): Promise => - this._client - .signOut() - .then((response: boolean) => { - if (callback) { - callback(response); - } - return response; - }) - .catch((error: AsgardeoAuthException) => Promise.reject(error)); - - /** - * Method to update Auth Client instance authentication state. - * - * @param {AuthStateInterface} state - State values to update in authentication state. - */ - public updateState(state: AuthStateInterface): void { - this._authState = {...this._authState, ...state}; - } - - /** - * This method returns a Promise that resolves with the basic user information obtained from the ID token. - * - * @return {Promise} a promise that resolves with the user information. - */ - public async getBasicUserInfo(): Promise { - return this._client.getBasicUserInfo(); - } - - /** - * This method returns a Promise that resolves with the basic user information obtained from the ID token. - * - * @return {Promise} a promise that resolves with the user information. - */ - public async getUser(): Promise { - return this._client.getBasicUserInfo(); - } - - /** - * This method sends an API request to a protected endpoint. - * The access token is automatically attached to the header of the request. - * This is the only way by which protected endpoints can be accessed - * when the web worker is used to store session information. - * - * @param {HttpRequestConfig} config - The config object containing attributes necessary to send a request. - * - * @return {Promise} - Returns a Promise that resolves with the response to the request. - */ - public async httpRequest(config: HttpRequestConfig): Promise> { - return this._client.httpRequest(config); - } - - /** - * This method sends multiple API requests to a protected endpoint. - * The access token is automatically attached to the header of the request. - * This is the only way by which multiple requests can be sent to protected endpoints - * when the web worker is used to store session information. - * - * @param {HttpRequestConfig[]} configs - The config object containing attributes necessary to send a request. - * - * @return {Promise} a Promise that resolves with the responses to the requests. - */ - public async httpRequestAll(configs: HttpRequestConfig[]): Promise[]> { - return this._client.httpRequestAll(configs); - } - - /** - * This method allows you to send a request with a custom grant. - * - * @param {CustomGrantRequestParams} config - The request parameters. - * @param {(response: BasicUserInfo | Response) => void} [callback] - An optional callback function. - * - * @return {Promise} a promise that resolves with - * the value returned by the custom grant request. - */ - public exchangeToken( - config: SPACustomGrantConfig, - callback?: (response: BasicUserInfo | Response) => void, - ): Promise { - return this._client - .exchangeToken(config) - .then((response: BasicUserInfo | Response) => { - if (!response) { - return response; - } - - if (config.returnsSession) { - Object.assign(this._authState, { - ...this._authState, - ...(response as BasicUserInfo), - isLoading: false, - isSignedIn: true, - }); - } - if (callback) { - callback(response); - } - return response; - }) - .catch((error: AsgardeoAuthException) => Promise.reject(error)); - } - - /** - * This method ends a user session. The access token is revoked and the session information is destroyed. - * - * @return {Promise} - A promise that resolves with `true` if the process is successful. - */ - public async revokeAccessToken(): Promise { - return this._client - .revokeAccessToken() - .then(() => { - this._authState = {...AuthAPI.DEFAULT_STATE, isLoading: false}; - return true; - }) - .catch((error: AsgardeoAuthException) => Promise.reject(error)); - } - - /** - * This method returns a Promise that resolves with an object containing the service endpoints. - * - * @return {Promise} - A Promise that resolves with an object containing the service endpoints. - */ - public async getOpenIDProviderEndpoints(): Promise { - return this._client.getOpenIDProviderEndpoints(); - } - - /** - * This methods returns the Axios http client. - * - * @return {HttpClientInstance} - The Axios HTTP client. - */ - public async getHttpClient(): Promise { - return this._client.getHttpClient(); - } - - /** - * This method decodes the payload of the id token and returns it. - * - * @return {Promise} - A Promise that resolves with - * the decoded payload of the id token. - */ - public async getDecodedIdToken(): Promise { - return this._client.getDecodedIdToken(); - } - - /** - * This method decodes the payload of the idp id token and returns it. - * @remarks - * This method is intended for retrieving the IdP ID token when extending a plugin. - * - * @return {Promise} - A Promise that resolves with - * the decoded payload of the idp id token. - */ - public async getDecodedIDPIDToken(): Promise { - return this._client.getDecodedIdToken(); - } - - /** - * This method returns the ID token. - * - * @return {Promise} - A Promise that resolves with the id token. - */ - public async getIdToken(): Promise { - return this._client.getIdToken(); - } - - /** - * This method return a Promise that resolves with the access token. - * - * @remarks - * This method will not return the access token if the storage type is set to `webWorker`. - * - * @return {Promise} - A Promise that resolves with the access token. - */ - public getAccessToken = async (): Promise => this._client.getAccessToken(); - - /** - * This method returns a Promise that resolves with the IDP access token. - * - * @remarks - * This method will not return the IDP access token if the storage type is set to `webWorker`. - * It can be used to access the IDP access token when custom authentication grant functionalities are used. - * - * @return {Promise} A Promise that resolves with the IDP access token. - */ - public async getIDPAccessToken(): Promise { - return this._client.getIDPAccessToken(); - } - - /** - * This method refreshes the access token. - * - * @return {BasicUserInfo} - A Promise that resolves with an object containing - * information about the refreshed access token. - */ - public async refreshAccessToken(): Promise { - return this._client.refreshAccessToken(); - } - - /** - * This method specifies if the user is authenticated or not. - * - * @return {Promise} - A Promise that resolves with `true` if the user is authenticated. - */ - public async isSignedIn(): Promise { - return this._client.isSignedIn(); - } - - /** - * This method specifies if the session is active or not. - * - * @return {Promise} - A Promise that resolves with `true` if there is an active session. - */ - public async isSessionActive(): Promise { - return this._client.isSessionActive(); - } - - /** - * This method enables callback functions attached to the http client. - * - * @return {Promise} - A promise that resolves with `true`. - */ - public async enableHttpHandler(): Promise { - return this._client.enableHttpHandler(); - } - - /** - * This method disables callback functions attached to the http client. - * - * @return {Promise} - A promise that resolves with `true`. - */ - public async disableHttpHandler(): Promise { - return this._client.disableHttpHandler(); - } - - /** - * This method updates the configuration that was passed into the constructor when instantiating this class. - * - * @param {Partial>} config - A config object to update the SDK configurations with. - */ - public async reInitialize(config: Partial>): Promise { - return this._client.reInitialize(config); - } - - /** - * This method attaches a callback function to an event hook that fires the callback when the event happens. - * - * @param {Hooks.CustomGrant} hook - The name of the hook. - * @param {(response?: any) => void} callback - The callback function. - * @param {string} id- Optional id for the hook. This is used when multiple custom grants are used. - * - */ - public on(hook: Hooks.CustomGrant, callback: (response?: any) => void, id: string): Promise; - public on(hook: Exclude, callback: (response?: any) => void): Promise; - public on(hook: Hooks, callback: (response?: any) => void, id?: string): Promise { - if (hook === Hooks.CustomGrant) { - return this._client.on(hook, callback, id); - } - - return this._client.on(hook, callback); - } - - /** - * This method allows you to sign in silently. - * First, this method sends a prompt-none request to check for an active user session in the identity provider. - * If a session exists, it retrieves the access token and stores it. Otherwise, it returns `false`. - * - * @param {Record} [additionalParams] - Optional additional parameters to be sent with the request. - * @param {{ params: Record }} [tokenRequestConfig] - Optional configuration for the token request. - * - * @returns {Promise} A Promise that resolves with the user information after signing in, - * or `false` if the user is not signed in. - * - * @example - * ``` - * client.signInSilently(); - * ``` - */ - public async signInSilently( - additionalParams?: Record, - tokenRequestConfig?: {params: Record}, - ): Promise { - return this._client - .signInSilently(additionalParams, tokenRequestConfig) - .then(async (response: BasicUserInfo | boolean) => { - if (!response) { - Object.assign(this._authState, {isLoading: false}); - return false; - } - - if (await this._client.isSignedIn()) { - const basicUserInfo: BasicUserInfo = response as BasicUserInfo; - Object.assign(this._authState, { - allowedScopes: basicUserInfo.allowedScopes, - displayName: basicUserInfo.displayName, - email: basicUserInfo.email, - isLoading: false, - isSignedIn: true, - sub: basicUserInfo.sub, - username: basicUserInfo.username, - }); - } - return response; - }) - .catch((error: AsgardeoAuthException) => Promise.reject(error)); - } -} - -AuthAPI.DEFAULT_STATE = { - allowedScopes: '', - displayName: '', - email: '', - isLoading: true, - isSignedIn: false, - sub: '', - username: '', -}; - -export default AuthAPI; +/** + * 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 { + AsgardeoAuthException, + AsgardeoSPAClient, + AuthClientConfig, + BasicUserInfo, + Config, + IdToken, + Hooks, + HttpClientInstance, + HttpRequestConfig, + HttpResponse, + OIDCEndpoints, + SignInConfig, + SPACustomGrantConfig, +} from '@asgardeo/auth-spa'; +import {reactive} from 'vue'; +import {AuthStateInterface, AuthVueConfig} from './types'; + +class AuthAPI { + static DEFAULT_STATE: AuthStateInterface; + + private _authState: AuthStateInterface = reactive({...AuthAPI.DEFAULT_STATE}); + + private _client: AsgardeoSPAClient; + + constructor(spaClient?: AsgardeoSPAClient) { + this._client = spaClient ?? AsgardeoSPAClient.getInstance(); + } + + /** + * Method to return Auth Client instance authentication state. + * + * @return {AuthStateInterface} Authentication State. + */ + public getState = (): AuthStateInterface => this._authState; + + /** + * Initializes the AuthClient instance with the given authentication configuration. + * + * @param {AuthClientConfig} config - The authentication configuration object + * containing details such as client ID, redirect URLs, and base URL. + * @returns {Promise} A promise that resolves to `true` if initialization is successful. + */ + public init = (config: AuthVueConfig): Promise => this._client.initialize(config); + + /** + * Handles user sign-in by exchanging the authorization code for tokens + * and updating the authentication state if the user is authenticated. + * + * @param {SignInConfig} config - The sign-in configuration containing client-specific settings. + * @param {string} authorizationCode - The authorization code received from the authentication provider. + * @param {string} sessionState - The session state value to track the authentication session. + * @param {string} [authState] - An optional authentication state parameter for additional tracking. + * @param {{ params: Record }} [tokenRequestConfig] - Optional token request parameters. + * @returns {Promise} A promise resolving to the authenticated user's basic information. + */ + public signIn = async ( + config?: SignInConfig, + authorizationCode?: string, + sessionState?: string, + authState?: string, + callback?: (response: BasicUserInfo) => void, + tokenRequestConfig?: {params: Record}, + ): Promise => + this._client + .signIn(config, authorizationCode, sessionState, authState, tokenRequestConfig) + .then(async (response: BasicUserInfo) => { + if (!response) { + return response; + } + if (await this._client.isSignedIn()) { + Object.assign(this._authState, { + allowedScopes: response.allowedScopes, + displayName: response.displayName, + email: response.email, + isLoading: false, + isSignedIn: true, + isSigningOut: false, + sub: response.sub, + username: response.username, + }); + + if (callback) { + callback(response); + } + } + + return response; + }) + .catch((error: Error) => Promise.reject(error)); + + /** + * Signs the user out and resets the authentication state. + * + * @param {(response?: boolean) => void} callback - An optional callback function to execute after sign-out. + * @returns {Promise} A promise resolving to `true` if sign-out is successful. + * + */ + public signOut = async (callback?: (response?: boolean) => void): Promise => + this._client + .signOut() + .then((response: boolean) => { + if (callback) { + callback(response); + } + return response; + }) + .catch((error: AsgardeoAuthException) => Promise.reject(error)); + + /** + * Method to update Auth Client instance authentication state. + * + * @param {AuthStateInterface} state - State values to update in authentication state. + */ + public updateState(state: AuthStateInterface): void { + this._authState = {...this._authState, ...state}; + } + + /** + * This method returns a Promise that resolves with the basic user information obtained from the ID token. + * + * @return {Promise} a promise that resolves with the user information. + */ + public async getBasicUserInfo(): Promise { + return this._client.getBasicUserInfo(); + } + + /** + * This method returns a Promise that resolves with the basic user information obtained from the ID token. + * + * @return {Promise} a promise that resolves with the user information. + */ + public async getUser(): Promise { + return this._client.getBasicUserInfo(); + } + + /** + * This method sends an API request to a protected endpoint. + * The access token is automatically attached to the header of the request. + * This is the only way by which protected endpoints can be accessed + * when the web worker is used to store session information. + * + * @param {HttpRequestConfig} config - The config object containing attributes necessary to send a request. + * + * @return {Promise} - Returns a Promise that resolves with the response to the request. + */ + public async httpRequest(config: HttpRequestConfig): Promise> { + return this._client.httpRequest(config); + } + + /** + * This method sends multiple API requests to a protected endpoint. + * The access token is automatically attached to the header of the request. + * This is the only way by which multiple requests can be sent to protected endpoints + * when the web worker is used to store session information. + * + * @param {HttpRequestConfig[]} configs - The config object containing attributes necessary to send a request. + * + * @return {Promise} a Promise that resolves with the responses to the requests. + */ + public async httpRequestAll(configs: HttpRequestConfig[]): Promise[]> { + return this._client.httpRequestAll(configs); + } + + /** + * This method allows you to send a request with a custom grant. + * + * @param {CustomGrantRequestParams} config - The request parameters. + * @param {(response: BasicUserInfo | Response) => void} [callback] - An optional callback function. + * + * @return {Promise} a promise that resolves with + * the value returned by the custom grant request. + */ + public exchangeToken( + config: SPACustomGrantConfig, + callback?: (response: BasicUserInfo | Response) => void, + ): Promise { + return this._client + .exchangeToken(config) + .then((response: BasicUserInfo | Response) => { + if (!response) { + return response; + } + + if (config.returnsSession) { + Object.assign(this._authState, { + ...this._authState, + ...(response as BasicUserInfo), + isLoading: false, + isSignedIn: true, + }); + } + if (callback) { + callback(response); + } + return response; + }) + .catch((error: AsgardeoAuthException) => Promise.reject(error)); + } + + /** + * This method ends a user session. The access token is revoked and the session information is destroyed. + * + * @return {Promise} - A promise that resolves with `true` if the process is successful. + */ + public async revokeAccessToken(): Promise { + return this._client + .revokeAccessToken() + .then(() => { + this._authState = {...AuthAPI.DEFAULT_STATE, isLoading: false}; + return true; + }) + .catch((error: AsgardeoAuthException) => Promise.reject(error)); + } + + /** + * This method returns a Promise that resolves with an object containing the service endpoints. + * + * @return {Promise} - A Promise that resolves with an object containing the service endpoints. + */ + public async getOpenIDProviderEndpoints(): Promise { + return this._client.getOpenIDProviderEndpoints(); + } + + /** + * This methods returns the Axios http client. + * + * @return {HttpClientInstance} - The Axios HTTP client. + */ + public async getHttpClient(): Promise { + return this._client.getHttpClient(); + } + + /** + * This method decodes the payload of the id token and returns it. + * + * @return {Promise} - A Promise that resolves with + * the decoded payload of the id token. + */ + public async getDecodedIdToken(): Promise { + return this._client.getDecodedIdToken(); + } + + /** + * This method decodes the payload of the idp id token and returns it. + * @remarks + * This method is intended for retrieving the IdP ID token when extending a plugin. + * + * @return {Promise} - A Promise that resolves with + * the decoded payload of the idp id token. + */ + public async getDecodedIDPIDToken(): Promise { + return this._client.getDecodedIdToken(); + } + + /** + * This method returns the ID token. + * + * @return {Promise} - A Promise that resolves with the id token. + */ + public async getIdToken(): Promise { + return this._client.getIdToken(); + } + + /** + * This method return a Promise that resolves with the access token. + * + * @remarks + * This method will not return the access token if the storage type is set to `webWorker`. + * + * @return {Promise} - A Promise that resolves with the access token. + */ + public getAccessToken = async (): Promise => this._client.getAccessToken(); + + /** + * This method returns a Promise that resolves with the IDP access token. + * + * @remarks + * This method will not return the IDP access token if the storage type is set to `webWorker`. + * It can be used to access the IDP access token when custom authentication grant functionalities are used. + * + * @return {Promise} A Promise that resolves with the IDP access token. + */ + public async getIDPAccessToken(): Promise { + return this._client.getIDPAccessToken(); + } + + /** + * This method refreshes the access token. + * + * @return {BasicUserInfo} - A Promise that resolves with an object containing + * information about the refreshed access token. + */ + public async refreshAccessToken(): Promise { + return this._client.refreshAccessToken(); + } + + /** + * This method specifies if the user is authenticated or not. + * + * @return {Promise} - A Promise that resolves with `true` if the user is authenticated. + */ + public async isSignedIn(): Promise { + return this._client.isSignedIn(); + } + + /** + * This method specifies if the session is active or not. + * + * @return {Promise} - A Promise that resolves with `true` if there is an active session. + */ + public async isSessionActive(): Promise { + return this._client.isSessionActive(); + } + + /** + * This method enables callback functions attached to the http client. + * + * @return {Promise} - A promise that resolves with `true`. + */ + public async enableHttpHandler(): Promise { + return this._client.enableHttpHandler(); + } + + /** + * This method disables callback functions attached to the http client. + * + * @return {Promise} - A promise that resolves with `true`. + */ + public async disableHttpHandler(): Promise { + return this._client.disableHttpHandler(); + } + + /** + * This method updates the configuration that was passed into the constructor when instantiating this class. + * + * @param {Partial>} config - A config object to update the SDK configurations with. + */ + public async reInitialize(config: Partial>): Promise { + return this._client.reInitialize(config); + } + + /** + * This method attaches a callback function to an event hook that fires the callback when the event happens. + * + * @param {Hooks.CustomGrant} hook - The name of the hook. + * @param {(response?: any) => void} callback - The callback function. + * @param {string} id- Optional id for the hook. This is used when multiple custom grants are used. + * + */ + public on(hook: Hooks.CustomGrant, callback: (response?: any) => void, id: string): Promise; + public on(hook: Exclude, callback: (response?: any) => void): Promise; + public on(hook: Hooks, callback: (response?: any) => void, id?: string): Promise { + if (hook === Hooks.CustomGrant) { + return this._client.on(hook, callback, id); + } + + return this._client.on(hook, callback); + } + + /** + * This method allows you to sign in silently. + * First, this method sends a prompt-none request to check for an active user session in the identity provider. + * If a session exists, it retrieves the access token and stores it. Otherwise, it returns `false`. + * + * @param {Record} [additionalParams] - Optional additional parameters to be sent with the request. + * @param {{ params: Record }} [tokenRequestConfig] - Optional configuration for the token request. + * + * @returns {Promise} A Promise that resolves with the user information after signing in, + * or `false` if the user is not signed in. + * + * @example + * ``` + * client.signInSilently(); + * ``` + */ + public async signInSilently( + additionalParams?: Record, + tokenRequestConfig?: {params: Record}, + ): Promise { + return this._client + .signInSilently(additionalParams, tokenRequestConfig) + .then(async (response: BasicUserInfo | boolean) => { + if (!response) { + Object.assign(this._authState, {isLoading: false}); + return false; + } + + if (await this._client.isSignedIn()) { + const basicUserInfo: BasicUserInfo = response as BasicUserInfo; + Object.assign(this._authState, { + allowedScopes: basicUserInfo.allowedScopes, + displayName: basicUserInfo.displayName, + email: basicUserInfo.email, + isLoading: false, + isSignedIn: true, + sub: basicUserInfo.sub, + username: basicUserInfo.username, + }); + } + return response; + }) + .catch((error: AsgardeoAuthException) => Promise.reject(error)); + } +} + +AuthAPI.DEFAULT_STATE = { + allowedScopes: '', + displayName: '', + email: '', + isLoading: true, + isSignedIn: false, + sub: '', + username: '', +}; + +export default AuthAPI; diff --git a/packages/vue/src/__legacy__/composables/useAsgardeo.ts b/packages/vue/src/__legacy__/composables/useAsgardeo.ts new file mode 100644 index 000000000..c2d3057a7 --- /dev/null +++ b/packages/vue/src/__legacy__/composables/useAsgardeo.ts @@ -0,0 +1,30 @@ +/** + * 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 {useAsgardeoContext} from './useAsgardeoContext'; +import {AuthContextInterface} from '../types'; + +/** + * Hook to access the Asgardeo authentication context. + * + * @returns {AuthContextInterface} The authentication context containing authentication methods and state. + */ +export function useAsgardeo(): AuthContextInterface { + const asgardeo: AuthContextInterface = useAsgardeoContext(); + return asgardeo; +} diff --git a/packages/vue/src/composables/useAsgardeoContext.ts b/packages/vue/src/__legacy__/composables/useAsgardeoContext.ts similarity index 97% rename from packages/vue/src/composables/useAsgardeoContext.ts rename to packages/vue/src/__legacy__/composables/useAsgardeoContext.ts index 45d18075c..ce1d9f1a9 100644 --- a/packages/vue/src/composables/useAsgardeoContext.ts +++ b/packages/vue/src/__legacy__/composables/useAsgardeoContext.ts @@ -1,37 +1,37 @@ -/** - * 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 {inject} from 'vue'; -import {ASGARDEO_INJECTION_KEY} from '../plugins/AsgardeoPlugin'; -import {AuthContextInterface} from '../types'; - -/** - * Retrieves the Asgardeo authentication context from Vue's dependency injection system. - * - * @throws {Error} Throws an error if the Vue plugin is not installed. - * @returns {AuthContextInterface} The authentication context containing authentication methods and state. - */ -export function useAsgardeoContext(): AuthContextInterface { - const ctx: AuthContextInterface = inject(ASGARDEO_INJECTION_KEY); - - if (!ctx) { - throw new Error('This can be only used when vue plugin is installed'); - } - - return ctx; -} +/** + * 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 {inject} from 'vue'; +import {ASGARDEO_INJECTION_KEY} from '../plugins/AsgardeoPlugin'; +import {AuthContextInterface} from '../types'; + +/** + * Retrieves the Asgardeo authentication context from Vue's dependency injection system. + * + * @throws {Error} Throws an error if the Vue plugin is not installed. + * @returns {AuthContextInterface} The authentication context containing authentication methods and state. + */ +export function useAsgardeoContext(): AuthContextInterface { + const ctx: AuthContextInterface = inject(ASGARDEO_INJECTION_KEY); + + if (!ctx) { + throw new Error('This can be only used when vue plugin is installed'); + } + + return ctx; +} diff --git a/packages/vue/src/__legacy__/index.ts b/packages/vue/src/__legacy__/index.ts new file mode 100644 index 000000000..777bae24d --- /dev/null +++ b/packages/vue/src/__legacy__/index.ts @@ -0,0 +1,22 @@ +/** + * 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. + */ + +export * from './public-api'; +export * from './types'; + +export * from '@asgardeo/auth-spa'; diff --git a/packages/vue/src/__legacy__/plugins/AsgardeoPlugin.ts b/packages/vue/src/__legacy__/plugins/AsgardeoPlugin.ts new file mode 100644 index 000000000..a73dba14a --- /dev/null +++ b/packages/vue/src/__legacy__/plugins/AsgardeoPlugin.ts @@ -0,0 +1,231 @@ +/** + * 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 { + AsgardeoAuthException, + AuthClientConfig, + Config, + TokenExchangeRequestConfig, + IdToken, + Hooks, + HttpClientInstance, + HttpRequestConfig, + HttpResponse, + OIDCEndpoints, + SignInConfig, + SPAUtils, + type BasicUserInfo, +} from '@asgardeo/auth-spa'; +import type {Plugin, Ref, App, Reactive} from 'vue'; +import {reactive, ref} from 'vue'; +import AuthAPI from '../auth-api'; +import type {AuthContextInterface, AuthParams, AuthStateInterface, AuthVueConfig} from '../types'; + +export type AsgardeoPluginOptions = AuthVueConfig; + +/** + * Default `AuthVueConfig` config. + */ +const defaultConfig: Partial = { + disableAutoSignIn: true, + disableTrySignInSilently: true, +}; + +export const ASGARDEO_INJECTION_KEY: symbol = Symbol('asgardeo'); + +export const asgardeoPlugin: Plugin = { + install(app: App, options: AsgardeoPluginOptions): void { + const AuthClient: AuthAPI = new AuthAPI(); + const isInitialized: Ref = ref(false); + const error: Ref = ref(null); + + const state: Reactive = reactive({...AuthClient.getState()}); + + /* eslint-disable no-useless-catch */ + const withStateSync = async (cb: () => T | Promise, refreshState: boolean = true): Promise => { + let result: T; + try { + result = await cb(); + return result; + } catch (err) { + throw err; + } finally { + if (refreshState) { + const currentState: AuthStateInterface = AuthClient.getState(); + Object.assign(state, currentState); + } + } + }; + + const signInSilently = async ( + additionalParams?: Record, + tokenRequestConfig?: {params: Record}, + ): Promise => + withStateSync(async () => AuthClient.signInSilently(additionalParams, tokenRequestConfig)); + + const checkIsAuthenticated = async (): Promise => + withStateSync(async () => { + const isAuthenticatedState: boolean = await AuthClient.isSignedIn(); + if (!isAuthenticatedState) { + AuthClient.updateState({...state, isLoading: false, isSignedIn: false}); + return; + } + const response: BasicUserInfo = await AuthClient.getUser(); + const stateToUpdate: AuthStateInterface = response + ? { + allowedScopes: response.allowedScopes, + displayName: response.displayName, + email: response.email, + isLoading: false, + isSignedIn: true, + sub: response.sub, + username: response.username, + } + : {...state, isLoading: false, isSignedIn: isAuthenticatedState}; + AuthClient.updateState(stateToUpdate); + }); + + const initialize = async (): Promise => { + await withStateSync(async () => { + if (isInitialized.value) return; + + try { + const config: AuthVueConfig = {...defaultConfig, ...options} as AuthVueConfig; + await AuthClient.init(config); + isInitialized.value = true; + + if (!config.skipRedirectCallback) { + const url: URL = new URL(window.location.href); + const authParams: AuthParams = null; + + if ( + (SPAUtils.hasAuthSearchParamsInURL() && + new URL(url.origin + url.pathname).toString() === new URL(config?.afterSignInUrl).toString()) || + authParams?.authorizationCode || + url.searchParams.get('error') + ) { + await AuthClient.signIn( + {callOnlyOnRedirect: true}, + authParams?.authorizationCode, + authParams?.sessionState, + authParams?.state, + ); + SPAUtils.removeAuthorizationCode(); + return; + } + } + + if (!config.disableAutoSignIn && (await AuthClient.isSessionActive())) { + await AuthClient.signIn(); + } + + await checkIsAuthenticated(); + + if (state.isSignedIn) { + return; + } + + if (!config.disableTrySignInSilently) { + await signInSilently(); + } + } catch (err) { + error.value = err; + throw err; + } + }); + }; + + initialize(); + + const authContext: AuthContextInterface = { + disableHttpHandler: (): Promise => AuthClient.disableHttpHandler(), + enableHttpHandler: (): Promise => AuthClient.enableHttpHandler(), + error: error.value, + exchangeToken: async ( + config: TokenExchangeRequestConfig, + callback?: (response: BasicUserInfo | Response) => void, + ): Promise => { + try { + const response: BasicUserInfo | Response = await AuthClient.exchangeToken(config); + callback?.(response); + return response; + } catch (err) { + error.value = err; + throw err; + } + }, + getAccessToken: (): Promise => AuthClient.getAccessToken(), + getDecodedIdToken: (): Promise => AuthClient.getDecodedIdToken(), + getHttpClient: (): Promise => AuthClient.getHttpClient(), + getIdToken: (): Promise => AuthClient.getIdToken(), + getOpenIDProviderEndpoints: (): Promise => AuthClient.getOpenIDProviderEndpoints(), + getUser: (): Promise => AuthClient.getUser(), + httpRequest: (config: HttpRequestConfig): Promise> => AuthClient.httpRequest(config), + httpRequestAll: (configs: HttpRequestConfig[]): Promise[]> => + AuthClient.httpRequestAll(configs), + isSignedIn: (): Promise => AuthClient.isSignedIn(), + on: (hook: Hooks, callback: (response?: any) => void, id?: string): void => { + if (hook === Hooks.CustomGrant && id) { + AuthClient.on(hook, callback, id); + } else { + AuthClient.on(hook as Exclude, callback); + } + }, + reInitialize: async (config: Partial>): Promise => + withStateSync(async () => { + await AuthClient.reInitialize(config); + }), + refreshAccessToken: (): Promise => AuthClient.refreshAccessToken(), + revokeAccessToken: (): Promise => AuthClient.revokeAccessToken(), + signIn: async ( + config?: SignInConfig, + authorizationCode?: string, + sessionState?: string, + authState?: string, + callback?: (response: BasicUserInfo) => void, + tokenRequestConfig?: {params: Record}, + ): Promise => + withStateSync(async () => { + const result: BasicUserInfo = await AuthClient.signIn( + config, + authorizationCode, + sessionState, + authState, + callback, + tokenRequestConfig, + ); + + if (result) { + error.value = null; + callback?.(result); + } + return result; + }), + signInSilently, + signOut: async (callback?: (response: boolean) => void): Promise => + withStateSync(async () => { + const result: boolean = await AuthClient.signOut(); + callback?.(result); + return result; + }), + state, + }; + + app.provide(ASGARDEO_INJECTION_KEY, authContext); + }, +}; diff --git a/packages/vue/src/public-api.ts b/packages/vue/src/__legacy__/public-api.ts similarity index 97% rename from packages/vue/src/public-api.ts rename to packages/vue/src/__legacy__/public-api.ts index c22503085..d7c5d5000 100644 --- a/packages/vue/src/public-api.ts +++ b/packages/vue/src/__legacy__/public-api.ts @@ -1,20 +1,20 @@ -/** - * 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. - */ - -export {asgardeoPlugin} from './plugins/AsgardeoPlugin'; -export {useAsgardeo} from './composables/useAsgardeo'; +/** + * 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. + */ + +export {asgardeoPlugin} from './plugins/AsgardeoPlugin'; +export {useAsgardeo} from './composables/useAsgardeo'; diff --git a/packages/vue/src/tests/AsgardeoPlugin.test.ts b/packages/vue/src/__legacy__/tests/AsgardeoPlugin.test.ts similarity index 97% rename from packages/vue/src/tests/AsgardeoPlugin.test.ts rename to packages/vue/src/__legacy__/tests/AsgardeoPlugin.test.ts index 6850b4e9f..2b7ed05eb 100644 --- a/packages/vue/src/tests/AsgardeoPlugin.test.ts +++ b/packages/vue/src/__legacy__/tests/AsgardeoPlugin.test.ts @@ -1,284 +1,284 @@ -/** - * 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 { - AuthClientConfig, - BasicUserInfo, - Config, - IdToken, - Hooks, - HttpClientInstance, - SPACustomGrantConfig, -} from '@asgardeo/auth-spa'; -import {describe, it, expect, beforeEach, vi, Mock} from 'vitest'; -import {createApp} from 'vue'; -import {mockAuthAPI, mockState, mockConfig} from './mocks/mocks'; -import AuthAPI from '../auth-api'; -import {asgardeoPlugin, ASGARDEO_INJECTION_KEY} from '../plugins/AsgardeoPlugin'; -import {AuthContextInterface, AuthStateInterface} from '../types'; - -vi.mock('../auth-api'); -vi.mock('@asgardeo/auth-spa'); - -vi.mocked(AuthAPI).mockImplementation(() => mockAuthAPI as unknown as AuthAPI); - -describe('asgardeoPlugin', () => { - let app: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - - // Reset the state - Object.assign(mockState, { - allowedScopes: '', - displayName: '', - email: '', - isLoading: true, - isSignedIn: false, - sub: '', - username: '', - }); - - app = createApp({}); - - app.use(asgardeoPlugin, mockConfig); - }); - - it('should provide the authentication context', () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - expect(authContext).toBeDefined(); - expect(authContext.state.isSignedIn).toBe(false); - }); - - it('should call AuthAPI init on install', async () => { - expect(mockAuthAPI.init).toHaveBeenCalledWith( - expect.objectContaining({ - afterSignInUrl: 'http://localhost:5173/', - afterSignOutUrl: 'http://localhost:5173/', - baseUrl: 'https://api.asgardeo.io/t/mock-tenant', - clientId: 'mock-client-id', - disableAutoSignIn: true, - disableTrySignInSilently: true, - }), - ); - }); - - it('should sign in a user and sync state', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - const authenticatedState: AuthStateInterface = { - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - isLoading: false, - isSignedIn: true, - sub: 'user-id-123', - username: 'testUser', - }; - - mockAuthAPI.getState.mockReturnValue(authenticatedState); - - await authContext.signIn(); - - expect(mockAuthAPI.signIn).toHaveBeenCalled(); - - expect(authContext.state).toMatchObject(authenticatedState); - }); - - it('should sign out a user and sync state', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const unauthenticatedState: AuthStateInterface = { - allowedScopes: '', - displayName: '', - email: '', - isLoading: false, - isSignedIn: false, - sub: '', - username: '', - }; - - mockAuthAPI.getState.mockReturnValue(unauthenticatedState); - - await authContext.signOut(); - - expect(mockAuthAPI.signOut).toHaveBeenCalled(); - - expect(authContext.state).toMatchObject(unauthenticatedState); - }); - - it('should handle errors in withStateSync', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const testError: Error = new Error('Test error'); - mockAuthAPI.getAccessToken.mockRejectedValueOnce(testError); - - mockAuthAPI.getState.mockReturnValue(mockState); - - await expect(authContext.getAccessToken()).rejects.toThrow('Test error'); - - expect(mockAuthAPI.getState).toHaveBeenCalled(); - }); - - it('should retrieve basic user info and sync state', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const userInfoState: AuthStateInterface = { - ...mockState, - displayName: 'Test User', - email: 'test@example.com', - username: 'testUser', - }; - - mockAuthAPI.getState.mockReturnValueOnce(userInfoState); - - await authContext.getUser(); - - expect(mockAuthAPI.getUser).toHaveBeenCalled(); - expect(authContext.state).toMatchObject(userInfoState); - }); - - it('should try sign in silently and sync state', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const additionalParams: Record = {prompt: 'none'}; - const tokenRequestConfig: {params: Record} = {params: {scope: 'openid profile'}}; - - const silentSignInState: AuthStateInterface = { - ...mockState, - isSignedIn: true, - username: 'testUser', - }; - - mockAuthAPI.getState.mockReturnValueOnce(silentSignInState); - - await authContext.signInSilently(additionalParams, tokenRequestConfig); - - expect(mockAuthAPI.signInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); - expect(authContext.state).toMatchObject(silentSignInState); - }); - - it('should update config and sync state', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const newConfig: Partial> = {clientId: 'new-client-id'}; - - const updatedState: AuthStateInterface = { - ...mockState, - isLoading: false, - }; - - mockAuthAPI.getState.mockReturnValueOnce(updatedState); - - await authContext.reInitialize(newConfig); - - expect(mockAuthAPI.reInitialize).toHaveBeenCalledWith(newConfig); - - expect(mockAuthAPI.getState).toHaveBeenCalled(); - - expect(authContext.state).toMatchObject(updatedState); - }); - - it('should handle token operations correctly', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const refreshedState: AuthStateInterface = { - ...mockState, - isSignedIn: true, - username: 'testUser', - }; - mockAuthAPI.getState.mockReturnValueOnce(refreshedState); - await authContext.refreshAccessToken(); - expect(mockAuthAPI.refreshAccessToken).toHaveBeenCalled(); - expect(authContext.state).toMatchObject(refreshedState); - - const accessToken: string = 'mock-access-token'; - mockAuthAPI.getAccessToken.mockResolvedValueOnce(accessToken); - const accessTokenValue: string = await authContext.getAccessToken(); - expect(mockAuthAPI.getAccessToken).toHaveBeenCalled(); - expect(accessTokenValue).toBe(accessToken); - - const decodedIDToken: IdToken = {aud: 'client-id', iss: 'https://test.com', sub: 'user-id-123'}; - mockAuthAPI.getDecodedIdToken.mockResolvedValueOnce(decodedIDToken); - const idToken: IdToken = await authContext.getDecodedIdToken(); - expect(mockAuthAPI.getDecodedIdToken).toHaveBeenCalled(); - expect(idToken).toMatchObject(decodedIDToken); - }); - - it('should handle HTTP operations correctly', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - await authContext.enableHttpHandler(); - expect(mockAuthAPI.enableHttpHandler).toHaveBeenCalled(); - - await authContext.disableHttpHandler(); - expect(mockAuthAPI.disableHttpHandler).toHaveBeenCalled(); - - const mockHttpClient: Partial = {}; - mockAuthAPI.getHttpClient.mockResolvedValueOnce(mockHttpClient); - const httpClient: HttpClientInstance = await authContext.getHttpClient(); - expect(mockAuthAPI.getHttpClient).toHaveBeenCalled(); - expect(httpClient).toBe(mockHttpClient); - expect(httpClient).toBe(mockHttpClient); - }); - - it('should handle custom grant request', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const customGrantConfig: SPACustomGrantConfig = { - attachToken: true, - data: {}, - id: '1', - returnsSession: true, - signInRequired: true, - }; - - const customGrantResponse: BasicUserInfo | Response = { - allowedScopes: 'openid', - sessionState: 'test', - }; - - mockAuthAPI.exchangeToken.mockResolvedValueOnce(customGrantResponse); - - authContext.exchangeToken(customGrantConfig); - - expect(mockAuthAPI.exchangeToken).toHaveBeenCalledWith(customGrantConfig); - }); - - it('should properly register event handlers with the on method', async () => { - const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; - - const regularHook: Hooks = Hooks.SignIn; - const regularCallback: Mock = vi.fn(); - - const customGrantHook: Hooks = Hooks.CustomGrant; - const customGrantCallback: Mock = vi.fn(); - const customGrantId: string = 'custom-grant-id'; - - authContext.on(regularHook, regularCallback); - authContext.on(customGrantHook, customGrantCallback, customGrantId); - - expect(mockAuthAPI.on).toHaveBeenCalledTimes(2); - - expect(mockAuthAPI.on.mock.calls[0][0]).toBe(regularHook); - expect(mockAuthAPI.on.mock.calls[0][1]).toBe(regularCallback); - - expect(mockAuthAPI.on.mock.calls[1][0]).toBe(customGrantHook); - expect(mockAuthAPI.on.mock.calls[1][1]).toBe(customGrantCallback); - expect(mockAuthAPI.on.mock.calls[1][2]).toBe(customGrantId); - }); -}); +/** + * 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 { + AuthClientConfig, + BasicUserInfo, + Config, + IdToken, + Hooks, + HttpClientInstance, + SPACustomGrantConfig, +} from '@asgardeo/auth-spa'; +import {describe, it, expect, beforeEach, vi, Mock} from 'vitest'; +import {createApp} from 'vue'; +import {mockAuthAPI, mockState, mockConfig} from './mocks/mocks'; +import AuthAPI from '../auth-api'; +import {asgardeoPlugin, ASGARDEO_INJECTION_KEY} from '../plugins/AsgardeoPlugin'; +import {AuthContextInterface, AuthStateInterface} from '../types'; + +vi.mock('../auth-api'); +vi.mock('@asgardeo/auth-spa'); + +vi.mocked(AuthAPI).mockImplementation(() => mockAuthAPI as unknown as AuthAPI); + +describe('asgardeoPlugin', () => { + let app: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + + // Reset the state + Object.assign(mockState, { + allowedScopes: '', + displayName: '', + email: '', + isLoading: true, + isSignedIn: false, + sub: '', + username: '', + }); + + app = createApp({}); + + app.use(asgardeoPlugin, mockConfig); + }); + + it('should provide the authentication context', () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + expect(authContext).toBeDefined(); + expect(authContext.state.isSignedIn).toBe(false); + }); + + it('should call AuthAPI init on install', async () => { + expect(mockAuthAPI.init).toHaveBeenCalledWith( + expect.objectContaining({ + afterSignInUrl: 'http://localhost:5173/', + afterSignOutUrl: 'http://localhost:5173/', + baseUrl: 'https://api.asgardeo.io/t/mock-tenant', + clientId: 'mock-client-id', + disableAutoSignIn: true, + disableTrySignInSilently: true, + }), + ); + }); + + it('should sign in a user and sync state', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + const authenticatedState: AuthStateInterface = { + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + isLoading: false, + isSignedIn: true, + sub: 'user-id-123', + username: 'testUser', + }; + + mockAuthAPI.getState.mockReturnValue(authenticatedState); + + await authContext.signIn(); + + expect(mockAuthAPI.signIn).toHaveBeenCalled(); + + expect(authContext.state).toMatchObject(authenticatedState); + }); + + it('should sign out a user and sync state', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const unauthenticatedState: AuthStateInterface = { + allowedScopes: '', + displayName: '', + email: '', + isLoading: false, + isSignedIn: false, + sub: '', + username: '', + }; + + mockAuthAPI.getState.mockReturnValue(unauthenticatedState); + + await authContext.signOut(); + + expect(mockAuthAPI.signOut).toHaveBeenCalled(); + + expect(authContext.state).toMatchObject(unauthenticatedState); + }); + + it('should handle errors in withStateSync', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const testError: Error = new Error('Test error'); + mockAuthAPI.getAccessToken.mockRejectedValueOnce(testError); + + mockAuthAPI.getState.mockReturnValue(mockState); + + await expect(authContext.getAccessToken()).rejects.toThrow('Test error'); + + expect(mockAuthAPI.getState).toHaveBeenCalled(); + }); + + it('should retrieve basic user info and sync state', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const userInfoState: AuthStateInterface = { + ...mockState, + displayName: 'Test User', + email: 'test@example.com', + username: 'testUser', + }; + + mockAuthAPI.getState.mockReturnValueOnce(userInfoState); + + await authContext.getUser(); + + expect(mockAuthAPI.getUser).toHaveBeenCalled(); + expect(authContext.state).toMatchObject(userInfoState); + }); + + it('should try sign in silently and sync state', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const additionalParams: Record = {prompt: 'none'}; + const tokenRequestConfig: {params: Record} = {params: {scope: 'openid profile'}}; + + const silentSignInState: AuthStateInterface = { + ...mockState, + isSignedIn: true, + username: 'testUser', + }; + + mockAuthAPI.getState.mockReturnValueOnce(silentSignInState); + + await authContext.signInSilently(additionalParams, tokenRequestConfig); + + expect(mockAuthAPI.signInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); + expect(authContext.state).toMatchObject(silentSignInState); + }); + + it('should update config and sync state', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const newConfig: Partial> = {clientId: 'new-client-id'}; + + const updatedState: AuthStateInterface = { + ...mockState, + isLoading: false, + }; + + mockAuthAPI.getState.mockReturnValueOnce(updatedState); + + await authContext.reInitialize(newConfig); + + expect(mockAuthAPI.reInitialize).toHaveBeenCalledWith(newConfig); + + expect(mockAuthAPI.getState).toHaveBeenCalled(); + + expect(authContext.state).toMatchObject(updatedState); + }); + + it('should handle token operations correctly', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const refreshedState: AuthStateInterface = { + ...mockState, + isSignedIn: true, + username: 'testUser', + }; + mockAuthAPI.getState.mockReturnValueOnce(refreshedState); + await authContext.refreshAccessToken(); + expect(mockAuthAPI.refreshAccessToken).toHaveBeenCalled(); + expect(authContext.state).toMatchObject(refreshedState); + + const accessToken: string = 'mock-access-token'; + mockAuthAPI.getAccessToken.mockResolvedValueOnce(accessToken); + const accessTokenValue: string = await authContext.getAccessToken(); + expect(mockAuthAPI.getAccessToken).toHaveBeenCalled(); + expect(accessTokenValue).toBe(accessToken); + + const decodedIDToken: IdToken = {aud: 'client-id', iss: 'https://test.com', sub: 'user-id-123'}; + mockAuthAPI.getDecodedIdToken.mockResolvedValueOnce(decodedIDToken); + const idToken: IdToken = await authContext.getDecodedIdToken(); + expect(mockAuthAPI.getDecodedIdToken).toHaveBeenCalled(); + expect(idToken).toMatchObject(decodedIDToken); + }); + + it('should handle HTTP operations correctly', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + await authContext.enableHttpHandler(); + expect(mockAuthAPI.enableHttpHandler).toHaveBeenCalled(); + + await authContext.disableHttpHandler(); + expect(mockAuthAPI.disableHttpHandler).toHaveBeenCalled(); + + const mockHttpClient: Partial = {}; + mockAuthAPI.getHttpClient.mockResolvedValueOnce(mockHttpClient); + const httpClient: HttpClientInstance = await authContext.getHttpClient(); + expect(mockAuthAPI.getHttpClient).toHaveBeenCalled(); + expect(httpClient).toBe(mockHttpClient); + expect(httpClient).toBe(mockHttpClient); + }); + + it('should handle custom grant request', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const customGrantConfig: SPACustomGrantConfig = { + attachToken: true, + data: {}, + id: '1', + returnsSession: true, + signInRequired: true, + }; + + const customGrantResponse: BasicUserInfo | Response = { + allowedScopes: 'openid', + sessionState: 'test', + }; + + mockAuthAPI.exchangeToken.mockResolvedValueOnce(customGrantResponse); + + authContext.exchangeToken(customGrantConfig); + + expect(mockAuthAPI.exchangeToken).toHaveBeenCalledWith(customGrantConfig); + }); + + it('should properly register event handlers with the on method', async () => { + const authContext: AuthContextInterface = app._context.provides[ASGARDEO_INJECTION_KEY]; + + const regularHook: Hooks = Hooks.SignIn; + const regularCallback: Mock = vi.fn(); + + const customGrantHook: Hooks = Hooks.CustomGrant; + const customGrantCallback: Mock = vi.fn(); + const customGrantId: string = 'custom-grant-id'; + + authContext.on(regularHook, regularCallback); + authContext.on(customGrantHook, customGrantCallback, customGrantId); + + expect(mockAuthAPI.on).toHaveBeenCalledTimes(2); + + expect(mockAuthAPI.on.mock.calls[0][0]).toBe(regularHook); + expect(mockAuthAPI.on.mock.calls[0][1]).toBe(regularCallback); + + expect(mockAuthAPI.on.mock.calls[1][0]).toBe(customGrantHook); + expect(mockAuthAPI.on.mock.calls[1][1]).toBe(customGrantCallback); + expect(mockAuthAPI.on.mock.calls[1][2]).toBe(customGrantId); + }); +}); diff --git a/packages/vue/src/tests/auth-api.test.ts b/packages/vue/src/__legacy__/tests/auth-api.test.ts similarity index 97% rename from packages/vue/src/tests/auth-api.test.ts rename to packages/vue/src/__legacy__/tests/auth-api.test.ts index 943eae15a..1601330c3 100644 --- a/packages/vue/src/tests/auth-api.test.ts +++ b/packages/vue/src/__legacy__/tests/auth-api.test.ts @@ -1,505 +1,505 @@ -/** - * 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 { - type AsgardeoSPAClient, - type AuthClientConfig, - type BasicUserInfo, - type Config, - type IdToken, - Hooks, - type HttpRequestConfig, - type HttpResponse, - type SPACustomGrantConfig, - type SignInConfig, -} from '@asgardeo/auth-spa'; -import {describe, it, expect, beforeEach, vi, Mock} from 'vitest'; -import {MockAsgardeoAuthException, asgardeoAuthSPAMock} from './mocks/mocks'; -import AuthAPI from '../auth-api'; -import {type AuthStateInterface, type AuthVueConfig} from '../types'; - -vi.doMock('@asgardeo/auth-spa', () => asgardeoAuthSPAMock); - -describe('AuthAPI', () => { - let authApi: AuthAPI; - let mockClient: any; - - beforeEach(() => { - vi.clearAllMocks(); - mockClient = asgardeoAuthSPAMock.AsgardeoSPAClient.getInstance(); - authApi = new AuthAPI(mockClient); - }); - - describe('constructor', () => { - it('should initialize with the default state', () => { - expect(authApi.getState()).toEqual({ - allowedScopes: '', - displayName: '', - email: '', - isLoading: true, - isSignedIn: false, - sub: '', - username: '', - }); - }); - - it('should use the provided SPA client if passed', () => { - const customClient: AsgardeoSPAClient = {} as AsgardeoSPAClient; - const customAuthApi: AuthAPI = new AuthAPI(customClient); - expect(customAuthApi).toBeDefined(); - }); - }); - - describe('init', () => { - it('should call initialize on the client with the provided config', async () => { - const config: AuthVueConfig = { - afterSignInUrl: 'http://localhost:5173/', - afterSignOutUrl: 'http://localhost:5173/', - baseUrl: 'https://api.asgardeo.io/t/mock-tenant', - clientId: 'mock-client-id', - }; - - await authApi.init(config); - - expect(mockClient.initialize).toHaveBeenCalledWith(config); - }); - }); - - describe('signIn', () => { - it('should call signIn on the client and update state on success', async () => { - const config: Partial = {some: 'config'}; - const authCode: string = 'auth-code'; - const sessionState: string = 'session-state'; - const authState: string = 'auth-state'; - const callback: Mock = vi.fn(); - const tokenConfig: {params: Record} = {params: {scope: 'openid'}}; - - const response: boolean | BasicUserInfo = await authApi.signIn( - config as any, - authCode, - sessionState, - authState, - callback, - tokenConfig, - ); - - expect(mockClient.signIn).toHaveBeenCalledWith(config, authCode, sessionState, authState, tokenConfig); - - expect(mockClient.isSignedIn).toHaveBeenCalled(); - expect(callback).toHaveBeenCalledWith(response); - - expect(authApi.getState()).toMatchObject({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - isLoading: false, - isSignedIn: true, - isSigningOut: false, - sub: 'user-id-123', - username: 'testUser', - }); - }); - - it('should handle errors during sign in', async () => { - const error: Error = new Error('Sign in failed'); - mockClient.signIn.mockRejectedValueOnce(error); - - await expect(authApi.signIn()).rejects.toThrow('Sign in failed'); - }); - - it('should handle null response', async () => { - mockClient.signIn.mockResolvedValueOnce(null); - const result: BasicUserInfo = await authApi.signIn(); - expect(result).toBeNull(); - - expect(authApi.getState().isSignedIn).toBe(false); - }); - - it('should handle unauthenticated scenarios', async () => { - mockClient.isSignedIn.mockResolvedValueOnce(false); - const response: boolean | BasicUserInfo = await authApi.signIn(); - - // Response should be returned, but state shouldn't be updated - expect(response).toBeDefined(); - expect(authApi.getState().isSignedIn).toBe(false); - }); - }); - - describe('signOut', () => { - it('should call signOut on the client', async () => { - const callback: Mock = vi.fn(); - - await authApi.signOut(callback); - - expect(mockClient.signOut).toHaveBeenCalled(); - expect(callback).toHaveBeenCalledWith(true); - }); - - it('should handle errors during sign out', async () => { - const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( - 'AUTH001', - 'AsgardeoAuthException', - 'Sign out failed', - ); - - mockClient.signOut.mockRejectedValueOnce(error); - - await expect(authApi.signOut()).rejects.toThrow('Sign out failed'); - }); - }); - - describe('updateState', () => { - it('should update the auth state', () => { - const newState: AuthStateInterface = { - allowedScopes: 'read write', - isLoading: false, - isSignedIn: true, - username: 'newUser', - }; - - authApi.updateState(newState); - - expect(authApi.getState()).toMatchObject({ - ...AuthAPI.DEFAULT_STATE, - ...newState, - }); - }); - }); - - describe('getUser', () => { - it('should call getUser on the client', async () => { - const result: BasicUserInfo = await authApi.getUser(); - - expect(mockClient.getBasicUserInfo).toHaveBeenCalled(); - expect(result).toEqual({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - sub: 'user-id-123', - username: 'testUser', - }); - }); - }); - - describe('httpRequest', () => { - it('should call httpRequest on the client', async () => { - const config: HttpRequestConfig = {url: 'https://api.example.com/data'}; - const result: HttpResponse = await authApi.httpRequest(config); - - expect(mockClient.httpRequest).toHaveBeenCalledWith(config); - expect(result).toEqual({data: {}, status: 200}); - }); - }); - - describe('httpRequestAll', () => { - it('should call httpRequestAll on the client', async () => { - const configs: HttpRequestConfig[] = [ - {url: 'https://api.example.com/data1'}, - {url: 'https://api.example.com/data2'}, - ]; - - const result: HttpResponse[] = await authApi.httpRequestAll(configs); - - expect(mockClient.httpRequestAll).toHaveBeenCalledWith(configs); - expect(result).toEqual([{data: {}, status: 200}]); - }); - }); - - describe('exchangeToken', () => { - it('should call exchangeToken on the client', async () => { - const config: SPACustomGrantConfig = { - attachToken: false, - data: {key: 'value'}, - id: 'custom-grant-id', - returnsSession: true, - signInRequired: true, - }; - const callback: Mock = vi.fn(); - - const result: BasicUserInfo | Response = await authApi.exchangeToken(config, callback); - - expect(mockClient.exchangeToken).toHaveBeenCalledWith(config); - expect(callback).toHaveBeenCalledWith(result); - - // Check state updates for returnsSession = true - expect(authApi.getState()).toMatchObject({ - displayName: 'Test User', - email: 'test@example.com', - isLoading: false, - isSignedIn: true, - username: 'testUser', - }); - }); - - it('should not update state when returnsSession is false', async () => { - const config: SPACustomGrantConfig = { - attachToken: false, - data: {key: 'value'}, - id: 'custom-grant-id', - returnsSession: false, - signInRequired: true, - }; - - await authApi.exchangeToken(config); - - // State should not be updated for session properties - expect(authApi.getState().username).toBe(''); - expect(authApi.getState().isSignedIn).toBe(false); - }); - - it('should handle null response', async () => { - mockClient.exchangeToken.mockResolvedValueOnce(null); - const config: SPACustomGrantConfig = { - attachToken: false, - data: {key: 'value'}, - id: 'custom-grant-id', - returnsSession: true, - signInRequired: true, - }; - - const result: BasicUserInfo | Response = await authApi.exchangeToken(config); - expect(result).toBeNull(); - }); - - it('should handle errors', async () => { - const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( - 'AUTH002', - 'AsgardeoAuthException', - 'Custom grant failed', - ); - - mockClient.exchangeToken.mockRejectedValueOnce(error); - - const config: SPACustomGrantConfig = { - attachToken: false, - data: {key: 'value'}, - id: 'custom-grant-id', - returnsSession: true, - signInRequired: true, - }; - - await expect(authApi.exchangeToken(config)).rejects.toThrow('Custom grant failed'); - }); - }); - - describe('revokeAccessToken', () => { - it('should call revokeAccessToken on the client and reset state', async () => { - authApi.updateState({ - allowedScopes: 'read write', - email: 'test@example.com', - isLoading: false, - isSignedIn: true, - username: 'testUser', - }); - - const result: boolean = await authApi.revokeAccessToken(); - - expect(mockClient.revokeAccessToken).toHaveBeenCalled(); - expect(result).toBe(true); - - // State should be reset to default with isLoading false - expect(authApi.getState()).toEqual({ - ...AuthAPI.DEFAULT_STATE, - isLoading: false, - }); - }); - - it('should handle errors', async () => { - const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( - 'AUTH003', - 'AsgardeoAuthException', - 'Token revocation failed', - ); - - mockClient.revokeAccessToken.mockRejectedValueOnce(error); - - await expect(authApi.revokeAccessToken()).rejects.toThrow('Token revocation failed'); - }); - }); - - describe('getOpenIDProviderEndpoints', () => { - it('should call getOpenIDProviderEndpoints on the client', async () => { - await authApi.getOpenIDProviderEndpoints(); - expect(mockClient.getOpenIDProviderEndpoints).toHaveBeenCalled(); - }); - }); - - describe('getHttpClient', () => { - it('should call getHttpClient on the client', async () => { - await authApi.getHttpClient(); - expect(mockClient.getHttpClient).toHaveBeenCalled(); - }); - }); - - describe('token related methods', () => { - it('should call getDecodedIdToken on the client', async () => { - const result: IdToken = await authApi.getDecodedIdToken(); - expect(mockClient.getDecodedIdToken).toHaveBeenCalled(); - expect(result).toEqual({sub: 'user-id-123'}); - }); - - it('should call getDecodedIDPIDToken on the client', async () => { - const result: IdToken = await authApi.getDecodedIDPIDToken(); - expect(mockClient.getDecodedIdToken).toHaveBeenCalled(); - expect(result).toEqual({sub: 'user-id-123'}); - }); - - it('should call getIdToken on the client', async () => { - const result: string = await authApi.getIdToken(); - expect(mockClient.getIdToken).toHaveBeenCalled(); - expect(result).toBe('mock-id-token'); - }); - - it('should call getAccessToken on the client', async () => { - const result: string = await authApi.getAccessToken(); - expect(mockClient.getAccessToken).toHaveBeenCalled(); - expect(result).toBe('mock-access-token'); - }); - - it('should call getIDPAccessToken on the client', async () => { - const result: string = await authApi.getIDPAccessToken(); - expect(mockClient.getIDPAccessToken).toHaveBeenCalled(); - expect(result).toBe('mock-idp-access-token'); - }); - - it('should call refreshAccessToken on the client', async () => { - const result: BasicUserInfo = await authApi.refreshAccessToken(); - expect(mockClient.refreshAccessToken).toHaveBeenCalled(); - expect(result).toEqual({ - displayName: 'Test User', - email: 'test@example.com', - username: 'testUser', - }); - }); - }); - - describe('authentication status methods', () => { - it('should call isSignedIn on the client', async () => { - const result: boolean = await authApi.isSignedIn(); - expect(mockClient.isSignedIn).toHaveBeenCalled(); - expect(result).toBe(true); - }); - - it('should call isSessionActive on the client', async () => { - const result: boolean = await authApi.isSessionActive(); - expect(mockClient.isSessionActive).toHaveBeenCalled(); - expect(result).toBe(true); - }); - }); - - describe('HTTP handler methods', () => { - it('should call enableHttpHandler on the client', async () => { - const result: boolean = await authApi.enableHttpHandler(); - expect(mockClient.enableHttpHandler).toHaveBeenCalled(); - expect(result).toBe(true); - }); - - it('should call disableHttpHandler on the client', async () => { - const result: boolean = await authApi.disableHttpHandler(); - expect(mockClient.disableHttpHandler).toHaveBeenCalled(); - expect(result).toBe(true); - }); - }); - - describe('reInitialize', () => { - it('should call reInitialize on the client', async () => { - const config: Partial> = {clientId: 'new-client-id'}; - await authApi.reInitialize(config); - expect(mockClient.reInitialize).toHaveBeenCalledWith(config); - }); - }); - - describe('on', () => { - it('should register regular event hooks', async () => { - const hook: Hooks = Hooks.SignIn; - const callback: Mock = vi.fn(); - - await authApi.on(hook, callback); - - expect(mockClient.on).toHaveBeenCalledWith(hook, callback); - }); - - it('should register custom grant hooks with id', async () => { - const hook: Hooks = Hooks.CustomGrant; - const callback: Mock = vi.fn(); - const id: string = 'custom-grant-id'; - - await authApi.on(hook, callback, id); - - expect(mockClient.on).toHaveBeenCalledWith(hook, callback, id); - }); - }); - - describe('signInSilently', () => { - it('should call signInSilently on the client and update state on success', async () => { - const additionalParams: Record = {prompt: 'none'}; - const tokenRequestConfig: {params: Record} = {params: {scope: 'openid profile'}}; - - const result: boolean | BasicUserInfo = await authApi.signInSilently(additionalParams, tokenRequestConfig); - - expect(mockClient.signInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); - expect(authApi.getState()).toMatchObject({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - isLoading: false, - isSignedIn: true, - sub: 'user-id-123', - username: 'testUser', - }); - expect(result).toEqual({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - sub: 'user-id-123', - username: 'testUser', - }); - }); - - it('should handle false response from signInSilently', async () => { - mockClient.signInSilently.mockResolvedValueOnce(false); - mockClient.isSignedIn.mockResolvedValueOnce(false); - - const result: boolean | BasicUserInfo = await authApi.signInSilently(); - - expect(result).toBe(false); - expect(authApi.getState().isLoading).toBe(false); - expect(authApi.getState().isSignedIn).toBe(false); - }); - - it('should handle errors', async () => { - const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( - 'AUTH004', - 'AsgardeoAuthException', - 'Custom grant failed', - ); - - mockClient.exchangeToken.mockRejectedValueOnce(error); - - const config: SPACustomGrantConfig = { - attachToken: false, - data: {key: 'value'}, - id: 'custom-grant-id', - returnsSession: true, - signInRequired: true, - }; - - await expect(authApi.exchangeToken(config)).rejects.toThrow('Custom grant failed'); - }); - }); -}); +/** + * 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 { + type AsgardeoSPAClient, + type AuthClientConfig, + type BasicUserInfo, + type Config, + type IdToken, + Hooks, + type HttpRequestConfig, + type HttpResponse, + type SPACustomGrantConfig, + type SignInConfig, +} from '@asgardeo/auth-spa'; +import {describe, it, expect, beforeEach, vi, Mock} from 'vitest'; +import {MockAsgardeoAuthException, asgardeoAuthSPAMock} from './mocks/mocks'; +import AuthAPI from '../auth-api'; +import {type AuthStateInterface, type AuthVueConfig} from '../types'; + +vi.doMock('@asgardeo/auth-spa', () => asgardeoAuthSPAMock); + +describe('AuthAPI', () => { + let authApi: AuthAPI; + let mockClient: any; + + beforeEach(() => { + vi.clearAllMocks(); + mockClient = asgardeoAuthSPAMock.AsgardeoSPAClient.getInstance(); + authApi = new AuthAPI(mockClient); + }); + + describe('constructor', () => { + it('should initialize with the default state', () => { + expect(authApi.getState()).toEqual({ + allowedScopes: '', + displayName: '', + email: '', + isLoading: true, + isSignedIn: false, + sub: '', + username: '', + }); + }); + + it('should use the provided SPA client if passed', () => { + const customClient: AsgardeoSPAClient = {} as AsgardeoSPAClient; + const customAuthApi: AuthAPI = new AuthAPI(customClient); + expect(customAuthApi).toBeDefined(); + }); + }); + + describe('init', () => { + it('should call initialize on the client with the provided config', async () => { + const config: AuthVueConfig = { + afterSignInUrl: 'http://localhost:5173/', + afterSignOutUrl: 'http://localhost:5173/', + baseUrl: 'https://api.asgardeo.io/t/mock-tenant', + clientId: 'mock-client-id', + }; + + await authApi.init(config); + + expect(mockClient.initialize).toHaveBeenCalledWith(config); + }); + }); + + describe('signIn', () => { + it('should call signIn on the client and update state on success', async () => { + const config: Partial = {some: 'config'}; + const authCode: string = 'auth-code'; + const sessionState: string = 'session-state'; + const authState: string = 'auth-state'; + const callback: Mock = vi.fn(); + const tokenConfig: {params: Record} = {params: {scope: 'openid'}}; + + const response: boolean | BasicUserInfo = await authApi.signIn( + config as any, + authCode, + sessionState, + authState, + callback, + tokenConfig, + ); + + expect(mockClient.signIn).toHaveBeenCalledWith(config, authCode, sessionState, authState, tokenConfig); + + expect(mockClient.isSignedIn).toHaveBeenCalled(); + expect(callback).toHaveBeenCalledWith(response); + + expect(authApi.getState()).toMatchObject({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + isLoading: false, + isSignedIn: true, + isSigningOut: false, + sub: 'user-id-123', + username: 'testUser', + }); + }); + + it('should handle errors during sign in', async () => { + const error: Error = new Error('Sign in failed'); + mockClient.signIn.mockRejectedValueOnce(error); + + await expect(authApi.signIn()).rejects.toThrow('Sign in failed'); + }); + + it('should handle null response', async () => { + mockClient.signIn.mockResolvedValueOnce(null); + const result: BasicUserInfo = await authApi.signIn(); + expect(result).toBeNull(); + + expect(authApi.getState().isSignedIn).toBe(false); + }); + + it('should handle unauthenticated scenarios', async () => { + mockClient.isSignedIn.mockResolvedValueOnce(false); + const response: boolean | BasicUserInfo = await authApi.signIn(); + + // Response should be returned, but state shouldn't be updated + expect(response).toBeDefined(); + expect(authApi.getState().isSignedIn).toBe(false); + }); + }); + + describe('signOut', () => { + it('should call signOut on the client', async () => { + const callback: Mock = vi.fn(); + + await authApi.signOut(callback); + + expect(mockClient.signOut).toHaveBeenCalled(); + expect(callback).toHaveBeenCalledWith(true); + }); + + it('should handle errors during sign out', async () => { + const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( + 'AUTH001', + 'AsgardeoAuthException', + 'Sign out failed', + ); + + mockClient.signOut.mockRejectedValueOnce(error); + + await expect(authApi.signOut()).rejects.toThrow('Sign out failed'); + }); + }); + + describe('updateState', () => { + it('should update the auth state', () => { + const newState: AuthStateInterface = { + allowedScopes: 'read write', + isLoading: false, + isSignedIn: true, + username: 'newUser', + }; + + authApi.updateState(newState); + + expect(authApi.getState()).toMatchObject({ + ...AuthAPI.DEFAULT_STATE, + ...newState, + }); + }); + }); + + describe('getUser', () => { + it('should call getUser on the client', async () => { + const result: BasicUserInfo = await authApi.getUser(); + + expect(mockClient.getBasicUserInfo).toHaveBeenCalled(); + expect(result).toEqual({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + sub: 'user-id-123', + username: 'testUser', + }); + }); + }); + + describe('httpRequest', () => { + it('should call httpRequest on the client', async () => { + const config: HttpRequestConfig = {url: 'https://api.example.com/data'}; + const result: HttpResponse = await authApi.httpRequest(config); + + expect(mockClient.httpRequest).toHaveBeenCalledWith(config); + expect(result).toEqual({data: {}, status: 200}); + }); + }); + + describe('httpRequestAll', () => { + it('should call httpRequestAll on the client', async () => { + const configs: HttpRequestConfig[] = [ + {url: 'https://api.example.com/data1'}, + {url: 'https://api.example.com/data2'}, + ]; + + const result: HttpResponse[] = await authApi.httpRequestAll(configs); + + expect(mockClient.httpRequestAll).toHaveBeenCalledWith(configs); + expect(result).toEqual([{data: {}, status: 200}]); + }); + }); + + describe('exchangeToken', () => { + it('should call exchangeToken on the client', async () => { + const config: SPACustomGrantConfig = { + attachToken: false, + data: {key: 'value'}, + id: 'custom-grant-id', + returnsSession: true, + signInRequired: true, + }; + const callback: Mock = vi.fn(); + + const result: BasicUserInfo | Response = await authApi.exchangeToken(config, callback); + + expect(mockClient.exchangeToken).toHaveBeenCalledWith(config); + expect(callback).toHaveBeenCalledWith(result); + + // Check state updates for returnsSession = true + expect(authApi.getState()).toMatchObject({ + displayName: 'Test User', + email: 'test@example.com', + isLoading: false, + isSignedIn: true, + username: 'testUser', + }); + }); + + it('should not update state when returnsSession is false', async () => { + const config: SPACustomGrantConfig = { + attachToken: false, + data: {key: 'value'}, + id: 'custom-grant-id', + returnsSession: false, + signInRequired: true, + }; + + await authApi.exchangeToken(config); + + // State should not be updated for session properties + expect(authApi.getState().username).toBe(''); + expect(authApi.getState().isSignedIn).toBe(false); + }); + + it('should handle null response', async () => { + mockClient.exchangeToken.mockResolvedValueOnce(null); + const config: SPACustomGrantConfig = { + attachToken: false, + data: {key: 'value'}, + id: 'custom-grant-id', + returnsSession: true, + signInRequired: true, + }; + + const result: BasicUserInfo | Response = await authApi.exchangeToken(config); + expect(result).toBeNull(); + }); + + it('should handle errors', async () => { + const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( + 'AUTH002', + 'AsgardeoAuthException', + 'Custom grant failed', + ); + + mockClient.exchangeToken.mockRejectedValueOnce(error); + + const config: SPACustomGrantConfig = { + attachToken: false, + data: {key: 'value'}, + id: 'custom-grant-id', + returnsSession: true, + signInRequired: true, + }; + + await expect(authApi.exchangeToken(config)).rejects.toThrow('Custom grant failed'); + }); + }); + + describe('revokeAccessToken', () => { + it('should call revokeAccessToken on the client and reset state', async () => { + authApi.updateState({ + allowedScopes: 'read write', + email: 'test@example.com', + isLoading: false, + isSignedIn: true, + username: 'testUser', + }); + + const result: boolean = await authApi.revokeAccessToken(); + + expect(mockClient.revokeAccessToken).toHaveBeenCalled(); + expect(result).toBe(true); + + // State should be reset to default with isLoading false + expect(authApi.getState()).toEqual({ + ...AuthAPI.DEFAULT_STATE, + isLoading: false, + }); + }); + + it('should handle errors', async () => { + const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( + 'AUTH003', + 'AsgardeoAuthException', + 'Token revocation failed', + ); + + mockClient.revokeAccessToken.mockRejectedValueOnce(error); + + await expect(authApi.revokeAccessToken()).rejects.toThrow('Token revocation failed'); + }); + }); + + describe('getOpenIDProviderEndpoints', () => { + it('should call getOpenIDProviderEndpoints on the client', async () => { + await authApi.getOpenIDProviderEndpoints(); + expect(mockClient.getOpenIDProviderEndpoints).toHaveBeenCalled(); + }); + }); + + describe('getHttpClient', () => { + it('should call getHttpClient on the client', async () => { + await authApi.getHttpClient(); + expect(mockClient.getHttpClient).toHaveBeenCalled(); + }); + }); + + describe('token related methods', () => { + it('should call getDecodedIdToken on the client', async () => { + const result: IdToken = await authApi.getDecodedIdToken(); + expect(mockClient.getDecodedIdToken).toHaveBeenCalled(); + expect(result).toEqual({sub: 'user-id-123'}); + }); + + it('should call getDecodedIDPIDToken on the client', async () => { + const result: IdToken = await authApi.getDecodedIDPIDToken(); + expect(mockClient.getDecodedIdToken).toHaveBeenCalled(); + expect(result).toEqual({sub: 'user-id-123'}); + }); + + it('should call getIdToken on the client', async () => { + const result: string = await authApi.getIdToken(); + expect(mockClient.getIdToken).toHaveBeenCalled(); + expect(result).toBe('mock-id-token'); + }); + + it('should call getAccessToken on the client', async () => { + const result: string = await authApi.getAccessToken(); + expect(mockClient.getAccessToken).toHaveBeenCalled(); + expect(result).toBe('mock-access-token'); + }); + + it('should call getIDPAccessToken on the client', async () => { + const result: string = await authApi.getIDPAccessToken(); + expect(mockClient.getIDPAccessToken).toHaveBeenCalled(); + expect(result).toBe('mock-idp-access-token'); + }); + + it('should call refreshAccessToken on the client', async () => { + const result: BasicUserInfo = await authApi.refreshAccessToken(); + expect(mockClient.refreshAccessToken).toHaveBeenCalled(); + expect(result).toEqual({ + displayName: 'Test User', + email: 'test@example.com', + username: 'testUser', + }); + }); + }); + + describe('authentication status methods', () => { + it('should call isSignedIn on the client', async () => { + const result: boolean = await authApi.isSignedIn(); + expect(mockClient.isSignedIn).toHaveBeenCalled(); + expect(result).toBe(true); + }); + + it('should call isSessionActive on the client', async () => { + const result: boolean = await authApi.isSessionActive(); + expect(mockClient.isSessionActive).toHaveBeenCalled(); + expect(result).toBe(true); + }); + }); + + describe('HTTP handler methods', () => { + it('should call enableHttpHandler on the client', async () => { + const result: boolean = await authApi.enableHttpHandler(); + expect(mockClient.enableHttpHandler).toHaveBeenCalled(); + expect(result).toBe(true); + }); + + it('should call disableHttpHandler on the client', async () => { + const result: boolean = await authApi.disableHttpHandler(); + expect(mockClient.disableHttpHandler).toHaveBeenCalled(); + expect(result).toBe(true); + }); + }); + + describe('reInitialize', () => { + it('should call reInitialize on the client', async () => { + const config: Partial> = {clientId: 'new-client-id'}; + await authApi.reInitialize(config); + expect(mockClient.reInitialize).toHaveBeenCalledWith(config); + }); + }); + + describe('on', () => { + it('should register regular event hooks', async () => { + const hook: Hooks = Hooks.SignIn; + const callback: Mock = vi.fn(); + + await authApi.on(hook, callback); + + expect(mockClient.on).toHaveBeenCalledWith(hook, callback); + }); + + it('should register custom grant hooks with id', async () => { + const hook: Hooks = Hooks.CustomGrant; + const callback: Mock = vi.fn(); + const id: string = 'custom-grant-id'; + + await authApi.on(hook, callback, id); + + expect(mockClient.on).toHaveBeenCalledWith(hook, callback, id); + }); + }); + + describe('signInSilently', () => { + it('should call signInSilently on the client and update state on success', async () => { + const additionalParams: Record = {prompt: 'none'}; + const tokenRequestConfig: {params: Record} = {params: {scope: 'openid profile'}}; + + const result: boolean | BasicUserInfo = await authApi.signInSilently(additionalParams, tokenRequestConfig); + + expect(mockClient.signInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); + expect(authApi.getState()).toMatchObject({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + isLoading: false, + isSignedIn: true, + sub: 'user-id-123', + username: 'testUser', + }); + expect(result).toEqual({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + sub: 'user-id-123', + username: 'testUser', + }); + }); + + it('should handle false response from signInSilently', async () => { + mockClient.signInSilently.mockResolvedValueOnce(false); + mockClient.isSignedIn.mockResolvedValueOnce(false); + + const result: boolean | BasicUserInfo = await authApi.signInSilently(); + + expect(result).toBe(false); + expect(authApi.getState().isLoading).toBe(false); + expect(authApi.getState().isSignedIn).toBe(false); + }); + + it('should handle errors', async () => { + const error: MockAsgardeoAuthException = new asgardeoAuthSPAMock.AsgardeoAuthException( + 'AUTH004', + 'AsgardeoAuthException', + 'Custom grant failed', + ); + + mockClient.exchangeToken.mockRejectedValueOnce(error); + + const config: SPACustomGrantConfig = { + attachToken: false, + data: {key: 'value'}, + id: 'custom-grant-id', + returnsSession: true, + signInRequired: true, + }; + + await expect(authApi.exchangeToken(config)).rejects.toThrow('Custom grant failed'); + }); + }); +}); diff --git a/packages/vue/src/tests/mocks/mocks.ts b/packages/vue/src/__legacy__/tests/mocks/mocks.ts similarity index 96% rename from packages/vue/src/tests/mocks/mocks.ts rename to packages/vue/src/__legacy__/tests/mocks/mocks.ts index 84edd6291..c76e368c7 100644 --- a/packages/vue/src/tests/mocks/mocks.ts +++ b/packages/vue/src/__legacy__/tests/mocks/mocks.ts @@ -1,206 +1,206 @@ -/** - * 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 {AsgardeoSPAClient, BasicUserInfo} from '@asgardeo/auth-spa'; -import {Mock, vi} from 'vitest'; -import {AuthContextInterface, AuthStateInterface, AuthVueConfig} from '../../types'; - -export const mockAuthContext: Partial = { - signIn: vi.fn(), - signOut: vi.fn(), - state: { - allowedScopes: 'openid profile email', - displayName: 'John Doe', - isLoading: false, - isSignedIn: true, - }, -}; - -export const mockState: AuthStateInterface = { - allowedScopes: '', - displayName: '', - email: '', - isLoading: true, - isSignedIn: false, - sub: '', - username: '', -}; - -export type MockAuthAPI = { - disableHttpHandler: Mock; - enableHttpHandler: Mock; - exchangeToken: Mock; - getAccessToken: Mock; - getDecodedIdToken: Mock; - getHttpClient: Mock; - getIdToken: Mock; - getOpenIDProviderEndpoints: Mock; - getState: Mock; - getUser: Mock; - httpRequest: Mock; - httpRequestAll: Mock; - init: Mock; - isSessionActive: Mock; - isSignedIn: Mock; - on: Mock; - reInitialize: Mock; - refreshAccessToken: Mock; - revokeAccessToken: Mock; - signIn: Mock; - signInSilently: Mock; - signOut: Mock; - updateState: Mock; -}; - -export const mockAuthAPI: MockAuthAPI = { - disableHttpHandler: vi.fn().mockResolvedValue(true), - enableHttpHandler: vi.fn().mockResolvedValue(true), - exchangeToken: vi.fn().mockResolvedValue({ - displayName: 'Test User', - email: 'test@example.com', - username: 'testUser', - } as BasicUserInfo), - getAccessToken: vi.fn().mockResolvedValue('mock-access-token'), - getDecodedIdToken: vi.fn().mockResolvedValue({aud: 'client-id', iss: 'https://test.com', sub: 'user-id-123'}), - getHttpClient: vi.fn().mockResolvedValue({}), - getIdToken: vi.fn().mockResolvedValue('mock-id-token'), - getOpenIDProviderEndpoints: vi.fn().mockResolvedValue({}), - getState: vi.fn().mockReturnValue(mockState), - getUser: vi.fn().mockResolvedValue({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - sub: 'user-id-123', - username: 'testUser', - }), - httpRequest: vi.fn().mockResolvedValue({data: {}, status: 200}), - httpRequestAll: vi.fn().mockResolvedValue([{data: {}, status: 200}]), - init: vi.fn().mockResolvedValue(true), - isSessionActive: vi.fn().mockResolvedValue(true), - isSignedIn: vi.fn().mockResolvedValue(true), - on: vi.fn(), - reInitialize: vi.fn().mockResolvedValue(undefined), - refreshAccessToken: vi.fn().mockResolvedValue({ - displayName: 'Test User', - email: 'test@example.com', - username: 'testUser', - }), - revokeAccessToken: vi.fn().mockResolvedValue(true), - signIn: vi.fn().mockResolvedValue({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - sub: 'user-id-123', - username: 'testUser', - }), - signInSilently: vi.fn().mockResolvedValue(false), - signOut: vi.fn().mockResolvedValue(true), - updateState: vi.fn().mockImplementation((newState: AuthStateInterface) => { - Object.assign(mockState, newState); - }), -}; - -export const mockAsgardeoSPAClient: Partial = { - disableHttpHandler: vi.fn().mockResolvedValue(true), - enableHttpHandler: vi.fn().mockResolvedValue(true), - exchangeToken: vi.fn().mockResolvedValue({ - displayName: 'Test User', - email: 'test@example.com', - username: 'testUser', - }), - getAccessToken: vi.fn().mockResolvedValue('mock-access-token'), - getBasicUserInfo: vi.fn().mockResolvedValue({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - sub: 'user-id-123', - username: 'testUser', - }), - getDecodedIdToken: vi.fn().mockResolvedValue({sub: 'user-id-123'}), - getHttpClient: vi.fn().mockResolvedValue({}), - getIDPAccessToken: vi.fn().mockResolvedValue('mock-idp-access-token'), - getIdToken: vi.fn().mockResolvedValue('mock-id-token'), - getOpenIDProviderEndpoints: vi.fn().mockResolvedValue({}), - httpRequest: vi.fn().mockResolvedValue({data: {}, status: 200}), - httpRequestAll: vi.fn().mockResolvedValue([{data: {}, status: 200}]), - initialize: vi.fn().mockResolvedValue(true), - isSessionActive: vi.fn().mockResolvedValue(true), - isSignedIn: vi.fn().mockResolvedValue(true), - on: vi.fn().mockResolvedValue(undefined), - reInitialize: vi.fn().mockResolvedValue(undefined), - refreshAccessToken: vi.fn().mockResolvedValue({ - displayName: 'Test User', - email: 'test@example.com', - username: 'testUser', - }), - revokeAccessToken: vi.fn().mockResolvedValue(true), - signIn: vi.fn().mockResolvedValue({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - sub: 'user-id-123', - username: 'testUser', - }), - signInSilently: vi.fn().mockResolvedValue({ - allowedScopes: 'openid profile', - displayName: 'Test User', - email: 'test@example.com', - sub: 'user-id-123', - username: 'testUser', - }), - signOut: vi.fn().mockResolvedValue(true), -}; - -export class MockAsgardeoAuthException extends Error { - public code: string | undefined; - - public override name: string; - - public override message: string; - - constructor(code: string, name: string, message: string) { - super(message); - this.code = code; - this.name = name; - this.message = message; - Object.setPrototypeOf(this, new.target.prototype); - } -} - -export const asgardeoAuthSPAMock: any = { - AsgardeoAuthException: MockAsgardeoAuthException, - AsgardeoSPAClient: { - getInstance: vi.fn().mockReturnValue(mockAsgardeoSPAClient), - }, - Hooks: { - CustomGrant: 'custom-grant', - HttpRequest: 'http-request', - HttpRequestError: 'http-request-error', - HttpRequestFinish: 'http-request-finish', - Initialize: 'initialize', - SignIn: 'sign-in', - SignOut: 'sign-out', - }, -}; - -export const mockConfig: AuthVueConfig = { - afterSignInUrl: 'http://localhost:5173/', - afterSignOutUrl: 'http://localhost:5173/', - baseUrl: 'https://api.asgardeo.io/t/mock-tenant', - clientId: 'mock-client-id', -}; +/** + * 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 {AsgardeoSPAClient, BasicUserInfo} from '@asgardeo/auth-spa'; +import {Mock, vi} from 'vitest'; +import {AuthContextInterface, AuthStateInterface, AuthVueConfig} from '../../types'; + +export const mockAuthContext: Partial = { + signIn: vi.fn(), + signOut: vi.fn(), + state: { + allowedScopes: 'openid profile email', + displayName: 'John Doe', + isLoading: false, + isSignedIn: true, + }, +}; + +export const mockState: AuthStateInterface = { + allowedScopes: '', + displayName: '', + email: '', + isLoading: true, + isSignedIn: false, + sub: '', + username: '', +}; + +export type MockAuthAPI = { + disableHttpHandler: Mock; + enableHttpHandler: Mock; + exchangeToken: Mock; + getAccessToken: Mock; + getDecodedIdToken: Mock; + getHttpClient: Mock; + getIdToken: Mock; + getOpenIDProviderEndpoints: Mock; + getState: Mock; + getUser: Mock; + httpRequest: Mock; + httpRequestAll: Mock; + init: Mock; + isSessionActive: Mock; + isSignedIn: Mock; + on: Mock; + reInitialize: Mock; + refreshAccessToken: Mock; + revokeAccessToken: Mock; + signIn: Mock; + signInSilently: Mock; + signOut: Mock; + updateState: Mock; +}; + +export const mockAuthAPI: MockAuthAPI = { + disableHttpHandler: vi.fn().mockResolvedValue(true), + enableHttpHandler: vi.fn().mockResolvedValue(true), + exchangeToken: vi.fn().mockResolvedValue({ + displayName: 'Test User', + email: 'test@example.com', + username: 'testUser', + } as BasicUserInfo), + getAccessToken: vi.fn().mockResolvedValue('mock-access-token'), + getDecodedIdToken: vi.fn().mockResolvedValue({aud: 'client-id', iss: 'https://test.com', sub: 'user-id-123'}), + getHttpClient: vi.fn().mockResolvedValue({}), + getIdToken: vi.fn().mockResolvedValue('mock-id-token'), + getOpenIDProviderEndpoints: vi.fn().mockResolvedValue({}), + getState: vi.fn().mockReturnValue(mockState), + getUser: vi.fn().mockResolvedValue({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + sub: 'user-id-123', + username: 'testUser', + }), + httpRequest: vi.fn().mockResolvedValue({data: {}, status: 200}), + httpRequestAll: vi.fn().mockResolvedValue([{data: {}, status: 200}]), + init: vi.fn().mockResolvedValue(true), + isSessionActive: vi.fn().mockResolvedValue(true), + isSignedIn: vi.fn().mockResolvedValue(true), + on: vi.fn(), + reInitialize: vi.fn().mockResolvedValue(undefined), + refreshAccessToken: vi.fn().mockResolvedValue({ + displayName: 'Test User', + email: 'test@example.com', + username: 'testUser', + }), + revokeAccessToken: vi.fn().mockResolvedValue(true), + signIn: vi.fn().mockResolvedValue({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + sub: 'user-id-123', + username: 'testUser', + }), + signInSilently: vi.fn().mockResolvedValue(false), + signOut: vi.fn().mockResolvedValue(true), + updateState: vi.fn().mockImplementation((newState: AuthStateInterface) => { + Object.assign(mockState, newState); + }), +}; + +export const mockAsgardeoSPAClient: Partial = { + disableHttpHandler: vi.fn().mockResolvedValue(true), + enableHttpHandler: vi.fn().mockResolvedValue(true), + exchangeToken: vi.fn().mockResolvedValue({ + displayName: 'Test User', + email: 'test@example.com', + username: 'testUser', + }), + getAccessToken: vi.fn().mockResolvedValue('mock-access-token'), + getBasicUserInfo: vi.fn().mockResolvedValue({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + sub: 'user-id-123', + username: 'testUser', + }), + getDecodedIdToken: vi.fn().mockResolvedValue({sub: 'user-id-123'}), + getHttpClient: vi.fn().mockResolvedValue({}), + getIDPAccessToken: vi.fn().mockResolvedValue('mock-idp-access-token'), + getIdToken: vi.fn().mockResolvedValue('mock-id-token'), + getOpenIDProviderEndpoints: vi.fn().mockResolvedValue({}), + httpRequest: vi.fn().mockResolvedValue({data: {}, status: 200}), + httpRequestAll: vi.fn().mockResolvedValue([{data: {}, status: 200}]), + initialize: vi.fn().mockResolvedValue(true), + isSessionActive: vi.fn().mockResolvedValue(true), + isSignedIn: vi.fn().mockResolvedValue(true), + on: vi.fn().mockResolvedValue(undefined), + reInitialize: vi.fn().mockResolvedValue(undefined), + refreshAccessToken: vi.fn().mockResolvedValue({ + displayName: 'Test User', + email: 'test@example.com', + username: 'testUser', + }), + revokeAccessToken: vi.fn().mockResolvedValue(true), + signIn: vi.fn().mockResolvedValue({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + sub: 'user-id-123', + username: 'testUser', + }), + signInSilently: vi.fn().mockResolvedValue({ + allowedScopes: 'openid profile', + displayName: 'Test User', + email: 'test@example.com', + sub: 'user-id-123', + username: 'testUser', + }), + signOut: vi.fn().mockResolvedValue(true), +}; + +export class MockAsgardeoAuthException extends Error { + public code: string | undefined; + + public override name: string; + + public override message: string; + + constructor(code: string, name: string, message: string) { + super(message); + this.code = code; + this.name = name; + this.message = message; + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export const asgardeoAuthSPAMock: any = { + AsgardeoAuthException: MockAsgardeoAuthException, + AsgardeoSPAClient: { + getInstance: vi.fn().mockReturnValue(mockAsgardeoSPAClient), + }, + Hooks: { + CustomGrant: 'custom-grant', + HttpRequest: 'http-request', + HttpRequestError: 'http-request-error', + HttpRequestFinish: 'http-request-finish', + Initialize: 'initialize', + SignIn: 'sign-in', + SignOut: 'sign-out', + }, +}; + +export const mockConfig: AuthVueConfig = { + afterSignInUrl: 'http://localhost:5173/', + afterSignOutUrl: 'http://localhost:5173/', + baseUrl: 'https://api.asgardeo.io/t/mock-tenant', + clientId: 'mock-client-id', +}; diff --git a/packages/vue/src/tests/useAsgardeo.test.ts b/packages/vue/src/__legacy__/tests/useAsgardeo.test.ts similarity index 97% rename from packages/vue/src/tests/useAsgardeo.test.ts rename to packages/vue/src/__legacy__/tests/useAsgardeo.test.ts index cdddc3b0e..c96972112 100644 --- a/packages/vue/src/tests/useAsgardeo.test.ts +++ b/packages/vue/src/__legacy__/tests/useAsgardeo.test.ts @@ -1,87 +1,87 @@ -/** - * 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 { - AsgardeoAuthException, - BasicUserInfo, - IdToken, - HttpClientInstance, - HttpResponse, - OIDCEndpoints, -} from '@asgardeo/auth-spa'; -import {describe, it, expect, beforeEach, Mock, vi} from 'vitest'; -import {useAsgardeo} from '../composables/useAsgardeo'; -import {useAsgardeoContext} from '../composables/useAsgardeoContext'; -import {AuthContextInterface} from '../types'; - -vi.mock('../composables/useAsgardeoContext', () => ({ - useAsgardeoContext: vi.fn(), -})); - -describe('useAsgardeo', () => { - const mockAuthContext: AuthContextInterface = { - disableHttpHandler: vi.fn().mockResolvedValue(true), - enableHttpHandler: vi.fn().mockResolvedValue(true), - error: new AsgardeoAuthException('Some error', 'Error message', 'error'), - exchangeToken: vi.fn(), - getAccessToken: vi.fn().mockResolvedValue('token'), - getDecodedIdToken: vi.fn().mockResolvedValue({} as IdToken), - getHttpClient: vi.fn().mockResolvedValue({} as HttpClientInstance), - getIdToken: vi.fn().mockResolvedValue('id_token'), - getOpenIDProviderEndpoints: vi.fn().mockResolvedValue({} as OIDCEndpoints), - getUser: vi.fn().mockResolvedValue({} as BasicUserInfo), - httpRequest: vi.fn().mockResolvedValue({} as HttpResponse), - httpRequestAll: vi.fn().mockResolvedValue([{} as HttpResponse]), - isSignedIn: vi.fn().mockResolvedValue(true), - on: vi.fn(), - reInitialize: vi.fn().mockResolvedValue(undefined), - refreshAccessToken: vi.fn().mockResolvedValue({} as BasicUserInfo), - revokeAccessToken: vi.fn().mockResolvedValue(true), - signIn: vi.fn().mockResolvedValue({} as BasicUserInfo), - signInSilently: vi.fn().mockResolvedValue(true), - signOut: vi.fn().mockResolvedValue(true), - state: { - allowedScopes: '', - isLoading: false, - isSignedIn: true, - }, - }; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should return the auth context from useAsgardeoContext', () => { - (useAsgardeoContext as Mock).mockReturnValue(mockAuthContext); - - const result: AuthContextInterface = useAsgardeo(); - - expect(useAsgardeoContext).toHaveBeenCalled(); - expect(result).toBe(mockAuthContext); - }); - - it('should propagate errors from useAsgardeoContext', () => { - const errorMessage: string = 'This can be only used when vue plugin is installed'; - (useAsgardeoContext as Mock).mockImplementation(() => { - throw new Error(errorMessage); - }); - - expect(() => useAsgardeo()).toThrow(errorMessage); - expect(useAsgardeoContext).toHaveBeenCalled(); - }); -}); +/** + * 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 { + AsgardeoAuthException, + BasicUserInfo, + IdToken, + HttpClientInstance, + HttpResponse, + OIDCEndpoints, +} from '@asgardeo/auth-spa'; +import {describe, it, expect, beforeEach, Mock, vi} from 'vitest'; +import {useAsgardeo} from '../composables/useAsgardeo'; +import {useAsgardeoContext} from '../composables/useAsgardeoContext'; +import {AuthContextInterface} from '../types'; + +vi.mock('../composables/useAsgardeoContext', () => ({ + useAsgardeoContext: vi.fn(), +})); + +describe('useAsgardeo', () => { + const mockAuthContext: AuthContextInterface = { + disableHttpHandler: vi.fn().mockResolvedValue(true), + enableHttpHandler: vi.fn().mockResolvedValue(true), + error: new AsgardeoAuthException('Some error', 'Error message', 'error'), + exchangeToken: vi.fn(), + getAccessToken: vi.fn().mockResolvedValue('token'), + getDecodedIdToken: vi.fn().mockResolvedValue({} as IdToken), + getHttpClient: vi.fn().mockResolvedValue({} as HttpClientInstance), + getIdToken: vi.fn().mockResolvedValue('id_token'), + getOpenIDProviderEndpoints: vi.fn().mockResolvedValue({} as OIDCEndpoints), + getUser: vi.fn().mockResolvedValue({} as BasicUserInfo), + httpRequest: vi.fn().mockResolvedValue({} as HttpResponse), + httpRequestAll: vi.fn().mockResolvedValue([{} as HttpResponse]), + isSignedIn: vi.fn().mockResolvedValue(true), + on: vi.fn(), + reInitialize: vi.fn().mockResolvedValue(undefined), + refreshAccessToken: vi.fn().mockResolvedValue({} as BasicUserInfo), + revokeAccessToken: vi.fn().mockResolvedValue(true), + signIn: vi.fn().mockResolvedValue({} as BasicUserInfo), + signInSilently: vi.fn().mockResolvedValue(true), + signOut: vi.fn().mockResolvedValue(true), + state: { + allowedScopes: '', + isLoading: false, + isSignedIn: true, + }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return the auth context from useAsgardeoContext', () => { + (useAsgardeoContext as Mock).mockReturnValue(mockAuthContext); + + const result: AuthContextInterface = useAsgardeo(); + + expect(useAsgardeoContext).toHaveBeenCalled(); + expect(result).toBe(mockAuthContext); + }); + + it('should propagate errors from useAsgardeoContext', () => { + const errorMessage: string = 'This can be only used when vue plugin is installed'; + (useAsgardeoContext as Mock).mockImplementation(() => { + throw new Error(errorMessage); + }); + + expect(() => useAsgardeo()).toThrow(errorMessage); + expect(useAsgardeoContext).toHaveBeenCalled(); + }); +}); diff --git a/packages/vue/src/tests/useAsgardeoContext.test.ts b/packages/vue/src/__legacy__/tests/useAsgardeoContext.test.ts similarity index 97% rename from packages/vue/src/tests/useAsgardeoContext.test.ts rename to packages/vue/src/__legacy__/tests/useAsgardeoContext.test.ts index 6ca8e2564..adad5c744 100644 --- a/packages/vue/src/tests/useAsgardeoContext.test.ts +++ b/packages/vue/src/__legacy__/tests/useAsgardeoContext.test.ts @@ -1,50 +1,50 @@ -/** - * 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 {describe, it, expect, vi, beforeEach, Mock} from 'vitest'; -import {inject} from 'vue'; -import {mockAuthContext} from './mocks/mocks'; -import {useAsgardeoContext} from '../composables/useAsgardeoContext'; -import {ASGARDEO_INJECTION_KEY} from '../plugins/AsgardeoPlugin'; -import {AuthContextInterface} from '../types'; - -vi.mock('vue', () => ({ - inject: vi.fn(), -})); - -describe('useAsgardeoContext', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should return the injected auth context when it exists', () => { - (inject as Mock).mockReturnValue(mockAuthContext); - - const result: AuthContextInterface = useAsgardeoContext(); - - expect(inject).toHaveBeenCalledWith(ASGARDEO_INJECTION_KEY); - expect(result).toBe(mockAuthContext); - }); - - it('should throw an error when the auth context is not injected', () => { - (inject as Mock).mockReturnValue(null); - - expect(() => useAsgardeoContext()).toThrow('This can be only used when vue plugin is installed'); - expect(inject).toHaveBeenCalledWith(ASGARDEO_INJECTION_KEY); - }); -}); +/** + * 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 {describe, it, expect, vi, beforeEach, Mock} from 'vitest'; +import {inject} from 'vue'; +import {mockAuthContext} from './mocks/mocks'; +import {useAsgardeoContext} from '../composables/useAsgardeoContext'; +import {ASGARDEO_INJECTION_KEY} from '../plugins/AsgardeoPlugin'; +import {AuthContextInterface} from '../types'; + +vi.mock('vue', () => ({ + inject: vi.fn(), +})); + +describe('useAsgardeoContext', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return the injected auth context when it exists', () => { + (inject as Mock).mockReturnValue(mockAuthContext); + + const result: AuthContextInterface = useAsgardeoContext(); + + expect(inject).toHaveBeenCalledWith(ASGARDEO_INJECTION_KEY); + expect(result).toBe(mockAuthContext); + }); + + it('should throw an error when the auth context is not injected', () => { + (inject as Mock).mockReturnValue(null); + + expect(() => useAsgardeoContext()).toThrow('This can be only used when vue plugin is installed'); + expect(inject).toHaveBeenCalledWith(ASGARDEO_INJECTION_KEY); + }); +}); diff --git a/packages/vue/src/types.ts b/packages/vue/src/__legacy__/types.ts similarity index 96% rename from packages/vue/src/types.ts rename to packages/vue/src/__legacy__/types.ts index eb7b00073..ac19402bf 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/__legacy__/types.ts @@ -1,137 +1,137 @@ -/** - * 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 { - AsgardeoAuthException, - AuthClientConfig, - AuthSPAClientConfig, - BasicUserInfo, - Config, - TokenExchangeRequestConfig, - IdToken, - Hooks, - HttpClientInstance, - HttpRequestConfig, - HttpResponse, - OIDCEndpoints, - SignInConfig, -} from '@asgardeo/auth-spa'; - -export interface VueConfig { - /** - * Prevents the SDK from automatically signing in the user if an active session exists. - */ - disableAutoSignIn?: boolean; - - /** - * The SDK attempts to check for an active session on the server and update - * session state automatically. This option allows disabling that behavior. - */ - disableTrySignInSilently?: boolean; - - /** - * The SDK's `AuthProvider` by default listens to route changes - * to detect `code` & `session_state` parameters and perform a token exchange. - * This option allows disabling that behavior. - */ - skipRedirectCallback?: boolean; -} - -export type AuthVueConfig = AuthSPAClientConfig & VueConfig; - -/** - * Interface for the Authenticated state of the user which is exposed - * via `state` object from `useAuthContext` hook. - */ -export interface AuthStateInterface { - /** - * The scopes that are allowed for the user. - */ - allowedScopes: string; - /** - * The display name of the user. - */ - displayName?: string; - /** - * The email address of the user. - */ - email?: string; - /** - * Are the Auth requests loading. - */ - isLoading: boolean; - /** - * Specifies if the user is authenticated or not. - */ - isSignedIn: boolean; - /** - * The uid corresponding to the user who the ID token belonged to. - */ - sub?: string; - /** - * The username of the user. - */ - username?: string; -} - -export interface AuthContextInterface { - disableHttpHandler(): Promise; - enableHttpHandler(): Promise; - error: AsgardeoAuthException; - exchangeToken(config: TokenExchangeRequestConfig, callback?: (response: BasicUserInfo | Response) => void): void; - getAccessToken(): Promise; - getDecodedIdToken(): Promise; - getHttpClient(): Promise; - getIdToken(): Promise; - getOpenIDProviderEndpoints(): Promise; - getUser(): Promise; - httpRequest(config: HttpRequestConfig): Promise>; - httpRequestAll(configs: HttpRequestConfig[]): Promise[]>; - isSignedIn(): Promise; - on(hook: Hooks.CustomGrant, callback: (response?: any) => void, id: string): void; - on(hook: Exclude, callback: (response?: any) => void): void; - on(hook: Hooks, callback: (response?: any) => void, id?: string): void; - reInitialize(config: Partial>): Promise; - refreshAccessToken(): Promise; - revokeAccessToken(): Promise; - signIn: ( - config?: SignInConfig, - authorizationCode?: string, - sessionState?: string, - state?: string, - callback?: (response: BasicUserInfo) => void, - tokenRequestConfig?: { - params: Record; - }, - ) => Promise; - signInSilently: ( - additionalParams?: Record, - tokenRequestConfig?: {params: Record}, - ) => Promise; - signOut: (callback?: (response: boolean) => void) => Promise; - state: AuthStateInterface; -} - -/** - * The model of the object returned by the `getAuthParams` prop method of the `AuthProvider`. - */ -export interface AuthParams { - authorizationCode?: string; - sessionState?: string; - state?: string; -} +/** + * 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 { + AsgardeoAuthException, + AuthClientConfig, + AuthSPAClientConfig, + BasicUserInfo, + Config, + TokenExchangeRequestConfig, + IdToken, + Hooks, + HttpClientInstance, + HttpRequestConfig, + HttpResponse, + OIDCEndpoints, + SignInConfig, +} from '@asgardeo/auth-spa'; + +export interface VueConfig { + /** + * Prevents the SDK from automatically signing in the user if an active session exists. + */ + disableAutoSignIn?: boolean; + + /** + * The SDK attempts to check for an active session on the server and update + * session state automatically. This option allows disabling that behavior. + */ + disableTrySignInSilently?: boolean; + + /** + * The SDK's `AuthProvider` by default listens to route changes + * to detect `code` & `session_state` parameters and perform a token exchange. + * This option allows disabling that behavior. + */ + skipRedirectCallback?: boolean; +} + +export type AuthVueConfig = AuthSPAClientConfig & VueConfig; + +/** + * Interface for the Authenticated state of the user which is exposed + * via `state` object from `useAuthContext` hook. + */ +export interface AuthStateInterface { + /** + * The scopes that are allowed for the user. + */ + allowedScopes: string; + /** + * The display name of the user. + */ + displayName?: string; + /** + * The email address of the user. + */ + email?: string; + /** + * Are the Auth requests loading. + */ + isLoading: boolean; + /** + * Specifies if the user is authenticated or not. + */ + isSignedIn: boolean; + /** + * The uid corresponding to the user who the ID token belonged to. + */ + sub?: string; + /** + * The username of the user. + */ + username?: string; +} + +export interface AuthContextInterface { + disableHttpHandler(): Promise; + enableHttpHandler(): Promise; + error: AsgardeoAuthException; + exchangeToken(config: TokenExchangeRequestConfig, callback?: (response: BasicUserInfo | Response) => void): void; + getAccessToken(): Promise; + getDecodedIdToken(): Promise; + getHttpClient(): Promise; + getIdToken(): Promise; + getOpenIDProviderEndpoints(): Promise; + getUser(): Promise; + httpRequest(config: HttpRequestConfig): Promise>; + httpRequestAll(configs: HttpRequestConfig[]): Promise[]>; + isSignedIn(): Promise; + on(hook: Hooks.CustomGrant, callback: (response?: any) => void, id: string): void; + on(hook: Exclude, callback: (response?: any) => void): void; + on(hook: Hooks, callback: (response?: any) => void, id?: string): void; + reInitialize(config: Partial>): Promise; + refreshAccessToken(): Promise; + revokeAccessToken(): Promise; + signIn: ( + config?: SignInConfig, + authorizationCode?: string, + sessionState?: string, + state?: string, + callback?: (response: BasicUserInfo) => void, + tokenRequestConfig?: { + params: Record; + }, + ) => Promise; + signInSilently: ( + additionalParams?: Record, + tokenRequestConfig?: {params: Record}, + ) => Promise; + signOut: (callback?: (response: boolean) => void) => Promise; + state: AuthStateInterface; +} + +/** + * The model of the object returned by the `getAuthParams` prop method of the `AuthProvider`. + */ +export interface AuthParams { + authorizationCode?: string; + sessionState?: string; + state?: string; +} diff --git a/packages/vue/src/vitest.config.ts b/packages/vue/src/__legacy__/vitest.config.ts similarity index 96% rename from packages/vue/src/vitest.config.ts rename to packages/vue/src/__legacy__/vitest.config.ts index 4a6b9c9b4..0567e3dff 100644 --- a/packages/vue/src/vitest.config.ts +++ b/packages/vue/src/__legacy__/vitest.config.ts @@ -1,38 +1,38 @@ -/** - * 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 path from 'path'; -import vue from '@vitejs/plugin-vue'; -import {defineConfig} from 'vitest/config'; - -export default defineConfig({ - plugins: [vue()], - resolve: { - alias: { - '@': path.resolve(__dirname), - }, - }, - test: { - deps: { - inline: ['@asgardeo/auth-spa'], - }, - environment: 'jsdom', - globals: true, - setupFiles: ['@vitest/web-worker'], - }, -}); +/** + * 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 path from 'path'; +import vue from '@vitejs/plugin-vue'; +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': path.resolve(__dirname), + }, + }, + test: { + deps: { + inline: ['@asgardeo/auth-spa'], + }, + environment: 'jsdom', + globals: true, + setupFiles: ['@vitest/web-worker'], + }, +}); diff --git a/packages/vue/src/__temp__/api.ts b/packages/vue/src/__temp__/api.ts new file mode 100644 index 000000000..8c76e228d --- /dev/null +++ b/packages/vue/src/__temp__/api.ts @@ -0,0 +1,273 @@ +/** + * 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 { + AsgardeoSPAClient, + AuthClientConfig, + User, + LegacyConfig as Config, + IdToken, + Hooks, + HttpClientInstance, + HttpRequestConfig, + HttpResponse, + OIDCEndpoints, + SignInConfig, + SPACustomGrantConfig, +} from '@asgardeo/browser'; +import {AuthStateInterface} from './models'; + +class AuthAPI { + static DEFAULT_STATE: AuthStateInterface; + + private authState: AuthStateInterface = AuthAPI.DEFAULT_STATE; + + private client: AsgardeoSPAClient; + + private apiInstanceId: number; + + private loadingState: boolean; + + constructor(spaClient?: AsgardeoSPAClient, instanceId: number = 0) { + this.apiInstanceId = instanceId; + this.client = spaClient ?? AsgardeoSPAClient.getInstance(instanceId); + + this.getState = this.getState.bind(this); + this.init = this.init.bind(this); + this.signIn = this.signIn.bind(this); + this.signOut = this.signOut.bind(this); + this.updateState = this.updateState.bind(this); + } + + public getInstanceId(): number { + return this.apiInstanceId; + } + + public setLoadingState(isLoading: boolean): void { + this.loadingState = isLoading; + } + + public getLoadingState(): boolean { + return this.loadingState; + } + + public isLoading(): boolean { + return this.getLoadingState(); + } + + public getState(): AuthStateInterface { + return this.authState; + } + + public async init(config: AuthClientConfig): Promise { + return this.client.initialize(config); + } + + public async getConfigData(): Promise> { + return this.client.getConfigData(); + } + + public async getStorageManager(): Promise { + return this.client.getStorageManager(); + } + + public async isInitialized(): Promise { + return this.client.isInitialized(); + } + + public async signIn( + config: SignInConfig, + authorizationCode?: string, + sessionState?: string, + authState?: string, + callback?: (response: User) => void, + tokenRequestConfig?: { + params: Record; + }, + ): Promise { + return this.client + .signIn(config, authorizationCode, sessionState, authState, tokenRequestConfig) + .then(async (response: User) => { + if (!response) { + return null; + } + + if (await this.client.isSignedIn()) { + const stateToUpdate: AuthStateInterface = { + displayName: response.displayName, + email: response.email, + isLoading: false, + isSignedIn: true, + username: response.username, + }; + + this.updateState(stateToUpdate); + this.setLoadingState(false); + + if (callback) { + callback(response); + } + } + + return response; + }) + .catch((error: Error) => Promise.reject(error)); + } + + public signOut(callback?: (response?: boolean) => void): Promise { + return this.client + .signOut() + .then((response: boolean) => { + if (callback) { + callback(response); + } + + return response; + }) + .catch((error: Error) => Promise.reject(error)); + } + + public updateState(state: AuthStateInterface): void { + this.authState = {...this.authState, ...state}; + } + + public async getUser(): Promise { + return this.client.getUser(); + } + + public async httpRequest(config: HttpRequestConfig): Promise> { + return this.client.httpRequest(config); + } + + public async httpRequestAll(configs: HttpRequestConfig[]): Promise[]> { + return this.client.httpRequestAll(configs); + } + + public exchangeToken( + config: SPACustomGrantConfig, + callback: (response: User | Response) => void, + ): Promise { + return this.client + .exchangeToken(config) + .then((response: User | Response) => { + if (!response) { + return null; + } + + if (config.returnsSession) { + this.updateState({ + ...this.getState(), + ...(response as User), + isLoading: false, + isSignedIn: true, + }); + } + + if (callback) { + callback(response); + } + + return response; + }) + .catch((error: Error) => Promise.reject(error)); + } + + public async getOpenIDProviderEndpoints(): Promise { + return this.client.getOpenIDProviderEndpoints(); + } + + public async getHttpClient(): Promise { + return this.client.getHttpClient(); + } + + public async decodeJwtToken>(token: string): Promise { + return this.client.decodeJwtToken(token); + } + + public async getDecodedIdToken(sessionId?: string): Promise { + return this.client.getDecodedIdToken(sessionId); + } + + public async getIdToken(): Promise { + return this.client.getIdToken(); + } + + public async getAccessToken(sessionId?: string): Promise { + return this.client.getAccessToken(sessionId); + } + + public async refreshAccessToken(): Promise { + return this.client.refreshAccessToken(); + } + + public async isSignedIn(): Promise { + return this.client.isSignedIn(); + } + + public async enableHttpHandler(): Promise { + return this.client.enableHttpHandler(); + } + + public async disableHttpHandler(): Promise { + return this.client.disableHttpHandler(); + } + + public async reInitialize(config: Partial>): Promise { + return this.client.reInitialize(config); + } + + public on(hook: Hooks.CustomGrant, callback: (response?: any) => void, id: string): Promise; + public on(hook: Exclude, callback: (response?: any) => void): Promise; + public on(hook: Hooks, callback: (response?: any) => void, id?: string): Promise { + if (hook === Hooks.CustomGrant) { + return this.client.on(hook, callback, id); + } + + return this.client.on(hook, callback); + } + + public async signInSilently( + additionalParams?: Record, + tokenRequestConfig?: {params: Record}, + ): Promise { + return this.client + .signInSilently(additionalParams, tokenRequestConfig) + .then(async (response: User | boolean) => { + if (!response) { + return false; + } + + return response; + }) + .catch((error: Error) => Promise.reject(error)); + } + + public clearSession(sessionId?: string): void { + this.client.clearSession(sessionId); + } +} + +AuthAPI.DEFAULT_STATE = { + displayName: '', + email: '', + isLoading: true, + isSignedIn: false, + username: '', +}; + +export default AuthAPI; diff --git a/packages/vue/src/__temp__/models.ts b/packages/vue/src/__temp__/models.ts new file mode 100644 index 000000000..4668d1e3a --- /dev/null +++ b/packages/vue/src/__temp__/models.ts @@ -0,0 +1,44 @@ +/** + * 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. + */ + +/** + * Interface for the Authenticated state of the user which is exposed + * via `state` object from the auth context. + */ +export interface AuthStateInterface { + /** + * The display name of the user. + */ + displayName?: string; + /** + * The email address of the user. + */ + email?: string; + /** + * Are the Auth requests loading. + */ + isLoading: boolean; + /** + * Specifies if the user is authenticated or not. + */ + isSignedIn: boolean; + /** + * The username of the user. + */ + username?: string; +} diff --git a/packages/vue/src/api/getAllOrganizations.ts b/packages/vue/src/api/getAllOrganizations.ts new file mode 100644 index 000000000..890a7238a --- /dev/null +++ b/packages/vue/src/api/getAllOrganizations.ts @@ -0,0 +1,63 @@ +/** + * 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 { + HttpInstance, + HttpResponse, + AsgardeoSPAClient, + HttpRequestConfig, + getAllOrganizations as baseGetAllOrganizations, + GetAllOrganizationsConfig as BaseGetAllOrganizationsConfig, + AllOrganizationsApiResponse, +} from '@asgardeo/browser'; + +export interface GetAllOrganizationsConfig extends Omit { + fetcher?: (url: string, config: RequestInit) => Promise; + instanceId?: number; +} + +const getAllOrganizations = async ({ + fetcher, + instanceId = 0, + ...requestConfig +}: GetAllOrganizationsConfig): Promise => { + const defaultFetcher = async (url: string, config: RequestInit): Promise => { + const client: AsgardeoSPAClient = AsgardeoSPAClient.getInstance(instanceId); + const httpClient: HttpInstance = client.httpRequest.bind(client); + const response: HttpResponse = await httpClient({ + headers: config.headers as Record, + method: config.method || 'GET', + url, + } as HttpRequestConfig); + + return { + json: () => Promise.resolve(response.data), + ok: response.status >= 200 && response.status < 300, + status: response.status, + statusText: response.statusText || '', + text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data)), + } as Response; + }; + + return baseGetAllOrganizations({ + ...requestConfig, + fetcher: fetcher || defaultFetcher, + }); +}; + +export default getAllOrganizations; diff --git a/packages/vue/src/api/getMeOrganizations.ts b/packages/vue/src/api/getMeOrganizations.ts new file mode 100644 index 000000000..d5a763988 --- /dev/null +++ b/packages/vue/src/api/getMeOrganizations.ts @@ -0,0 +1,63 @@ +/** + * 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 { + Organization, + HttpInstance, + HttpResponse, + AsgardeoSPAClient, + HttpRequestConfig, + getMeOrganizations as baseGetMeOrganizations, + GetMeOrganizationsConfig as BaseGetMeOrganizationsConfig, +} from '@asgardeo/browser'; + +export interface GetMeOrganizationsConfig extends Omit { + fetcher?: (url: string, config: RequestInit) => Promise; + instanceId?: number; +} + +const getMeOrganizations = async ({ + fetcher, + instanceId = 0, + ...requestConfig +}: GetMeOrganizationsConfig): Promise => { + const defaultFetcher = async (url: string, config: RequestInit): Promise => { + const client: AsgardeoSPAClient = AsgardeoSPAClient.getInstance(instanceId); + const httpClient: HttpInstance = client.httpRequest.bind(client); + const response: HttpResponse = await httpClient({ + headers: config.headers as Record, + method: config.method || 'GET', + url, + } as HttpRequestConfig); + + return { + json: () => Promise.resolve(response.data), + ok: response.status >= 200 && response.status < 300, + status: response.status, + statusText: response.statusText || '', + text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data)), + } as Response; + }; + + return baseGetMeOrganizations({ + ...requestConfig, + fetcher: fetcher || defaultFetcher, + }); +}; + +export default getMeOrganizations; diff --git a/packages/vue/src/api/getSchemas.ts b/packages/vue/src/api/getSchemas.ts new file mode 100644 index 000000000..4c5ebb092 --- /dev/null +++ b/packages/vue/src/api/getSchemas.ts @@ -0,0 +1,59 @@ +/** + * 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 { + Schema, + HttpInstance, + HttpResponse, + AsgardeoSPAClient, + HttpRequestConfig, + getSchemas as baseGetSchemas, + GetSchemasConfig as BaseGetSchemasConfig, +} from '@asgardeo/browser'; + +export interface GetSchemasConfig extends Omit { + fetcher?: (url: string, config: RequestInit) => Promise; + instanceId?: number; +} + +const getSchemas = async ({fetcher, instanceId = 0, ...requestConfig}: GetSchemasConfig): Promise => { + const defaultFetcher = async (url: string, config: RequestInit): Promise => { + const client: AsgardeoSPAClient = AsgardeoSPAClient.getInstance(instanceId); + const httpClient: HttpInstance = client.httpRequest.bind(client); + const response: HttpResponse = await httpClient({ + headers: config.headers as Record, + method: config.method || 'GET', + url, + } as HttpRequestConfig); + + return { + json: () => Promise.resolve(response.data), + ok: response.status >= 200 && response.status < 300, + status: response.status, + statusText: response.statusText || '', + text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data)), + } as Response; + }; + + return baseGetSchemas({ + ...requestConfig, + fetcher: fetcher || defaultFetcher, + }); +}; + +export default getSchemas; diff --git a/packages/vue/src/api/getScim2Me.ts b/packages/vue/src/api/getScim2Me.ts new file mode 100644 index 000000000..c429bd195 --- /dev/null +++ b/packages/vue/src/api/getScim2Me.ts @@ -0,0 +1,59 @@ +/** + * 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 { + User, + HttpInstance, + HttpResponse, + AsgardeoSPAClient, + HttpRequestConfig, + getScim2Me as baseGetScim2Me, + GetScim2MeConfig as BaseGetScim2MeConfig, +} from '@asgardeo/browser'; + +export interface GetScim2MeConfig extends Omit { + fetcher?: (url: string, config: RequestInit) => Promise; + instanceId?: number; +} + +const getScim2Me = async ({fetcher, instanceId = 0, ...requestConfig}: GetScim2MeConfig): Promise => { + const defaultFetcher = async (url: string, config: RequestInit): Promise => { + const client: AsgardeoSPAClient = AsgardeoSPAClient.getInstance(instanceId); + const httpClient: HttpInstance = client.httpRequest.bind(client); + const response: HttpResponse = await httpClient({ + headers: config.headers as Record, + method: config.method || 'GET', + url, + } as HttpRequestConfig); + + return { + json: () => Promise.resolve(response.data), + ok: response.status >= 200 && response.status < 300, + status: response.status, + statusText: response.statusText || '', + text: () => Promise.resolve(typeof response.data === 'string' ? response.data : JSON.stringify(response.data)), + } as Response; + }; + + return baseGetScim2Me({ + ...requestConfig, + fetcher: fetcher || defaultFetcher, + }); +}; + +export default getScim2Me; diff --git a/packages/vue/src/components/AsgardeoProvider.ts b/packages/vue/src/components/AsgardeoProvider.ts new file mode 100644 index 000000000..b092fb7ea --- /dev/null +++ b/packages/vue/src/components/AsgardeoProvider.ts @@ -0,0 +1,433 @@ +/** + * 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 { + AsgardeoRuntimeError, + extractUserClaimsFromIdToken, + hasAuthParamsInUrl, + hasCalledForThisInstanceInUrl, + IdToken, + Organization, + Platform, + User, + SignInOptions, + TokenResponse, + EmbeddedSignInFlowResponseV2, +} from '@asgardeo/browser'; +import { + defineComponent, + h, + onMounted, + onUnmounted, + provide, + ref, + shallowRef, + type PropType, +} from 'vue'; +import AsgardeoVueClient from '../AsgardeoVueClient'; +import {ASGARDEO_KEY} from '../keys'; +import type {AsgardeoContext} from '../models/contexts'; +import type {AsgardeoVueConfig} from '../models/config'; + +/** + * Checks if the current URL contains authentication parameters. + */ +function hasAuthParams(url: URL, afterSignInUrl: string): boolean { + return ( + (hasAuthParamsInUrl() && new URL(url.origin + url.pathname).toString() === new URL(afterSignInUrl).toString()) || + url.searchParams.get('error') !== null + ); +} + +/** + * Root provider component for the Asgardeo Vue SDK. + * + * This component initializes the client, manages authentication state, + * and provides the Asgardeo context to child components via Vue's provide/inject. + * + * @example + * ```vue + * + * ``` + */ +const AsgardeoProvider = defineComponent({ + name: 'AsgardeoProvider', + props: { + /** The base URL of the Asgardeo tenant. */ + baseUrl: {type: String, required: true}, + /** The OAuth2 client ID. */ + clientId: {type: String, required: true}, + /** The URL to redirect to after sign in. Defaults to `window.location.origin`. */ + afterSignInUrl: {type: String, default: () => window.location.origin}, + /** The URL to redirect to after sign out. Defaults to `window.location.origin`. */ + afterSignOutUrl: {type: String, default: () => window.location.origin}, + /** The scopes to request. */ + scopes: {type: Array as PropType, default: undefined}, + /** The sign-in URL. */ + signInUrl: {type: String, default: undefined}, + /** The sign-up URL. */ + signUpUrl: {type: String, default: undefined}, + /** The organization handle. */ + organizationHandle: {type: String, default: undefined}, + /** The Asgardeo application ID. */ + applicationId: {type: String, default: undefined}, + /** Additional sign-in options. */ + signInOptions: {type: Object as PropType, default: undefined}, + /** Whether to sync sessions across tabs. */ + syncSession: {type: Boolean, default: undefined}, + /** Instance ID for multi-instance support. */ + instanceId: {type: Number, default: 0}, + /** Organization chain config. */ + organizationChain: {type: Object, default: undefined}, + /** Storage type. */ + storage: {type: String, default: undefined}, + /** Platform type. */ + platform: {type: String, default: undefined}, + }, + setup(props, {slots}) { + // ── Client ── + const asgardeo = new AsgardeoVueClient(props.instanceId); + + // ── Reactive State ── + const isSignedIn = ref(false); + const isInitialized = ref(false); + const isLoading = ref(true); + const user = shallowRef(null); + const currentOrganization = shallowRef(null); + const resolvedBaseUrl = ref(props.baseUrl); + + let isUpdatingSession = false; + let signInCheckInterval: ReturnType | undefined; + let loadingCheckInterval: ReturnType | undefined; + + // ── Build config from props ── + function buildConfig(): AsgardeoVueConfig { + return { + afterSignInUrl: props.afterSignInUrl, + afterSignOutUrl: props.afterSignOutUrl, + applicationId: props.applicationId, + baseUrl: props.baseUrl, + clientId: props.clientId, + organizationChain: props.organizationChain, + organizationHandle: props.organizationHandle, + scopes: props.scopes, + signInOptions: props.signInOptions, + signInUrl: props.signInUrl, + signUpUrl: props.signUpUrl, + storage: props.storage, + syncSession: props.syncSession, + platform: props.platform, + } as AsgardeoVueConfig; + } + + // ── Session Update ── + async function updateSession(): Promise { + try { + isUpdatingSession = true; + isLoading.value = true; + let baseUrl = resolvedBaseUrl.value; + + const decodedToken: IdToken = await asgardeo.getDecodedIdToken(); + + if (decodedToken?.['user_org']) { + baseUrl = `${(await asgardeo.getConfiguration()).baseUrl}/o`; + resolvedBaseUrl.value = baseUrl; + } + + const config = buildConfig(); + + if (config.platform === Platform.AsgardeoV2) { + const claims = extractUserClaimsFromIdToken(decodedToken); + user.value = claims; + } else { + try { + const fetchedUser: User = await asgardeo.getUser({baseUrl}); + user.value = fetchedUser; + } catch { + // silent + } + + try { + const fetchedOrg: Organization = await asgardeo.getCurrentOrganization(); + currentOrganization.value = fetchedOrg; + } catch { + // silent + } + } + + const currentSignInStatus = await asgardeo.isSignedIn(); + isSignedIn.value = currentSignInStatus; + } catch { + // silent + } finally { + isUpdatingSession = false; + isLoading.value = asgardeo.isLoading(); + } + } + + // ── Sign In (wrapper) ── + async function signIn(...args: any[]): Promise { + const arg1 = args[0]; + const config = buildConfig(); + const isV2FlowRequest = + config.platform === Platform.AsgardeoV2 && + typeof arg1 === 'object' && + arg1 !== null && + ('flowId' in arg1 || 'applicationId' in arg1); + + try { + if (!isV2FlowRequest) { + isUpdatingSession = true; + isLoading.value = true; + } + + const response = await asgardeo.signIn(...args); + + if (isV2FlowRequest || (response && typeof response === 'object' && 'flowStatus' in response)) { + return response; + } + + if (await asgardeo.isSignedIn()) { + await updateSession(); + } + + return response as User; + } catch (error) { + throw new AsgardeoRuntimeError( + `Sign in failed: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, + 'asgardeo-signIn-Error', + 'vue', + 'An error occurred while trying to sign in.', + ); + } finally { + if (!isV2FlowRequest) { + isUpdatingSession = false; + isLoading.value = asgardeo.isLoading(); + } + } + } + + // ── Sign Out ── + async function signOut(...args: any[]): Promise { + return asgardeo.signOut(...args); + } + + // ── Sign Up ── + async function signUp(...args: any[]): Promise { + return asgardeo.signUp(...args); + } + + // ── Sign In Silently ── + async function signInSilently(options?: SignInOptions): Promise { + try { + isUpdatingSession = true; + isLoading.value = true; + const response = await asgardeo.signInSilently(options); + + if (await asgardeo.isSignedIn()) { + await updateSession(); + } + + return response; + } catch (error) { + throw new AsgardeoRuntimeError( + `Error while signing in silently: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, + 'asgardeo-signInSilently-Error', + 'vue', + 'An error occurred while trying to sign in silently.', + ); + } finally { + isUpdatingSession = false; + isLoading.value = asgardeo.isLoading(); + } + } + + // ── Switch Organization ── + async function switchOrganization(organization: Organization): Promise { + try { + isUpdatingSession = true; + isLoading.value = true; + const response = await asgardeo.switchOrganization(organization); + + if (await asgardeo.isSignedIn()) { + await updateSession(); + } + + return response; + } catch (error) { + throw new AsgardeoRuntimeError( + `Failed to switch organization: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, + 'asgardeo-switchOrganization-Error', + 'vue', + 'An error occurred while switching to the specified organization.', + ); + } finally { + isUpdatingSession = false; + isLoading.value = asgardeo.isLoading(); + } + } + + // ── Provide Context ── + const context: AsgardeoContext = { + afterSignInUrl: props.afterSignInUrl, + applicationId: props.applicationId, + baseUrl: props.baseUrl, + clientId: props.clientId, + clearSession: (...args: any[]) => asgardeo.clearSession(...args), + exchangeToken: (config) => asgardeo.exchangeToken(config), + getAccessToken: () => asgardeo.getAccessToken(), + getDecodedIdToken: () => asgardeo.getDecodedIdToken(), + getIdToken: () => asgardeo.getIdToken(), + http: { + request: (requestConfig) => asgardeo.request(requestConfig), + requestAll: (requestConfigs) => asgardeo.requestAll(requestConfigs), + }, + instanceId: props.instanceId, + isInitialized, + isLoading, + isSignedIn, + organization: currentOrganization, + organizationHandle: props.organizationHandle, + platform: props.platform as AsgardeoVueConfig['platform'], + reInitialize: (config) => asgardeo.reInitialize(config), + signIn, + signInOptions: props.signInOptions, + signInSilently, + signInUrl: props.signInUrl, + signOut, + signUp, + signUpUrl: props.signUpUrl, + storage: props.storage as AsgardeoVueConfig['storage'], + switchOrganization, + user, + }; + + provide(ASGARDEO_KEY, context); + + // ── Lifecycle ── + onMounted(async () => { + // 1. Initialize the client + const config = buildConfig(); + await asgardeo.initialize(config); + + const initializedConfig = asgardeo.getConfiguration(); + + if (initializedConfig?.platform) { + sessionStorage.setItem('asgardeo_platform', initializedConfig.platform); + } + if (initializedConfig?.baseUrl) { + sessionStorage.setItem('asgardeo_base_url', initializedConfig.baseUrl); + } + + // 2. Check initialization status + try { + const status = await asgardeo.isInitialized(); + isInitialized.value = status; + } catch { + isInitialized.value = false; + } + + // 3. Try to sign in if already authenticated or if URL has auth params + const alreadySignedIn = await asgardeo.isSignedIn(); + + if (alreadySignedIn) { + await updateSession(); + } else { + const currentUrl = new URL(window.location.href); + const hasParams = + hasAuthParams(currentUrl, props.afterSignInUrl) && + hasCalledForThisInstanceInUrl(props.instanceId ?? 0, currentUrl.search); + + if (hasParams) { + try { + const isV2Platform = config.platform === Platform.AsgardeoV2; + + if (isV2Platform) { + const urlParams = currentUrl.searchParams; + const code = urlParams.get('code'); + const flowIdFromUrl = urlParams.get('flowId'); + const storedFlowId = sessionStorage.getItem('asgardeo_flow_id'); + + if (code && !flowIdFromUrl && !storedFlowId) { + await signIn(); + } + } else { + await signIn({callOnlyOnRedirect: true}); + } + } catch (error) { + throw new AsgardeoRuntimeError( + `Sign in failed: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`, + 'asgardeo-signIn-Error', + 'vue', + 'An error occurred while trying to sign in.', + ); + } + } + } + + // 4. Set up polling for sign-in status + try { + const status = await asgardeo.isSignedIn(); + isSignedIn.value = status; + + if (!status) { + signInCheckInterval = setInterval(async () => { + const newStatus = await asgardeo.isSignedIn(); + if (newStatus) { + isSignedIn.value = true; + if (signInCheckInterval) { + clearInterval(signInCheckInterval); + signInCheckInterval = undefined; + } + } + }, 1000); + } + } catch { + isSignedIn.value = false; + } + + // 5. Set up polling for loading state + loadingCheckInterval = setInterval(() => { + if (isUpdatingSession) return; + + const currentUrl = new URL(window.location.href); + if (!isSignedIn.value && hasAuthParams(currentUrl, props.afterSignInUrl)) return; + + isLoading.value = asgardeo.isLoading(); + }, 100); + }); + + onUnmounted(() => { + if (signInCheckInterval) { + clearInterval(signInCheckInterval); + } + if (loadingCheckInterval) { + clearInterval(loadingCheckInterval); + } + }); + + // ── Render ── + return () => (slots['default'] ? slots['default']() : undefined); + }, +}); + +export default AsgardeoProvider; diff --git a/packages/vue/src/composables/useAsgardeo.ts b/packages/vue/src/composables/useAsgardeo.ts index 0f29f297d..ad7ee80bd 100644 --- a/packages/vue/src/composables/useAsgardeo.ts +++ b/packages/vue/src/composables/useAsgardeo.ts @@ -1,30 +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. - */ - -import {useAsgardeoContext} from './useAsgardeoContext'; -import {AuthContextInterface} from '../types'; - -/** - * Hook to access the Asgardeo authentication context. - * - * @returns {AuthContextInterface} The authentication context containing authentication methods and state. - */ -export function useAsgardeo(): AuthContextInterface { - const asgardeo: AuthContextInterface = useAsgardeoContext(); - return asgardeo; -} +/** + * 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 {inject} from 'vue'; +import {ASGARDEO_KEY} from '../keys'; +import type {AsgardeoContext} from '../models/contexts'; + +/** + * Primary composable for Asgardeo authentication. + * + * Must be called inside a component that is a descendant of ``. + * Returns all auth-related reactive state and action methods. + * + * @throws Error if called outside of ``. + * + * @example + * ```vue + * + * + * + * ``` + */ +const useAsgardeo = (): AsgardeoContext => { + const context = inject(ASGARDEO_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useAsgardeo() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useAsgardeo; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 6afdfd2f1..6f1ef2301 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,22 +1,57 @@ -/** - * 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. - */ - -export * from './public-api'; -export * from './types'; - -export * from '@asgardeo/auth-spa'; +/** + * 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. + */ + +// ── Plugin ── +export {default as AsgardeoPlugin} from './plugins/AsgardeoPlugin'; + +// ── Components ── +export {default as AsgardeoProvider} from './components/AsgardeoProvider'; + +// ── Composables ── +export {default as useAsgardeo} from './composables/useAsgardeo'; + +// ── Client ── +export {default as AsgardeoVueClient} from './AsgardeoVueClient'; + +// ── Keys ── +export {ASGARDEO_KEY} from './keys'; + +// ── Models / Types ── +export type {AsgardeoVueConfig} from './models/config'; +export type {AsgardeoContext} from './models/contexts'; + +// ── Re-exports from @asgardeo/browser ── +export { + type AllOrganizationsApiResponse, + type Config, + type EmbeddedFlowExecuteRequestConfig, + type EmbeddedFlowExecuteRequestPayload, + type EmbeddedFlowExecuteResponse, + type EmbeddedSignInFlowHandleRequestPayload, + type HttpRequestConfig, + type HttpResponse, + type IdToken, + type Organization, + type SignInOptions, + type SignOutOptions, + type SignUpOptions, + type TokenExchangeRequestConfig, + type TokenResponse, + type User, + type UserProfile, +} from '@asgardeo/browser'; diff --git a/packages/vue/src/keys.ts b/packages/vue/src/keys.ts new file mode 100644 index 000000000..a94e7b470 --- /dev/null +++ b/packages/vue/src/keys.ts @@ -0,0 +1,25 @@ +/** + * 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 type {InjectionKey} from 'vue'; +import type {AsgardeoContext} from './models/contexts'; + +/** + * Injection key for the core Asgardeo authentication context. + */ +export const ASGARDEO_KEY: InjectionKey = Symbol('asgardeo'); diff --git a/packages/vue/src/models/config.ts b/packages/vue/src/models/config.ts new file mode 100644 index 000000000..3d428b1e7 --- /dev/null +++ b/packages/vue/src/models/config.ts @@ -0,0 +1,21 @@ +/** + * 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 {AsgardeoBrowserConfig} from '@asgardeo/browser'; + +export type AsgardeoVueConfig = AsgardeoBrowserConfig; diff --git a/packages/vue/src/models/contexts.ts b/packages/vue/src/models/contexts.ts new file mode 100644 index 000000000..109c68f23 --- /dev/null +++ b/packages/vue/src/models/contexts.ts @@ -0,0 +1,101 @@ +/** + * 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 type {Ref} from 'vue'; +import type { + HttpRequestConfig, + HttpResponse, + IdToken, + Organization, + SignInOptions, + TokenExchangeRequestConfig, + TokenResponse, +} from '@asgardeo/browser'; +import type {AsgardeoVueConfig} from './config'; +import type AsgardeoVueClient from '../AsgardeoVueClient'; + +/** + * Shape of the core Asgardeo context provided via `provide`/`inject`. + * + * Reactive refs are exposed as `Readonly>` so consumers can read + * them in templates and `watch()` calls but cannot mutate them directly. + */ +export interface AsgardeoContext { + /** The `afterSignInUrl` from the config. */ + afterSignInUrl: string | undefined; + /** The Asgardeo application ID from the config. */ + applicationId: string | undefined; + /** The base URL of the Asgardeo tenant. */ + baseUrl: string | undefined; + /** The OAuth2 client ID. */ + clientId: string | undefined; + /** The instance ID for multi-instance support. */ + instanceId: number; + + // ── Reactive Auth State ── + + /** Whether the SDK has finished initializing. */ + isInitialized: Readonly>; + /** Whether the SDK is performing a background operation. */ + isLoading: Readonly>; + /** Whether the user is currently signed in. */ + isSignedIn: Readonly>; + /** The current user object, or `null` if not signed in. */ + user: Readonly>; + /** The current organization, or `null`. */ + organization: Readonly>; + + // ── Auth Actions ── + + signIn: (...args: any[]) => Promise; + signOut: (...args: any[]) => Promise; + signUp: (...args: any[]) => Promise; + signInSilently: (options?: SignInOptions) => Promise; + + // ── Token ── + + getAccessToken: () => Promise; + getDecodedIdToken: () => Promise; + getIdToken: () => Promise; + exchangeToken: (config: TokenExchangeRequestConfig) => Promise; + + // ── HTTP ── + + http: { + request: (requestConfig?: HttpRequestConfig) => Promise>; + requestAll: (requestConfigs?: HttpRequestConfig[]) => Promise[]>; + }; + + // ── Organization ── + + switchOrganization: AsgardeoVueClient['switchOrganization']; + + // ── Lifecycle ── + + reInitialize: (config: Partial) => Promise; + clearSession: (...args: any[]) => void; + + // ── Config ── + + signInOptions: SignInOptions | undefined; + signInUrl: string | undefined; + signUpUrl: string | undefined; + organizationHandle: string | undefined; + storage: AsgardeoVueConfig['storage'] | undefined; + platform: AsgardeoVueConfig['platform'] | undefined; +} diff --git a/packages/vue/src/plugins/AsgardeoPlugin.ts b/packages/vue/src/plugins/AsgardeoPlugin.ts index 93b86cd37..42c51ff68 100644 --- a/packages/vue/src/plugins/AsgardeoPlugin.ts +++ b/packages/vue/src/plugins/AsgardeoPlugin.ts @@ -1,231 +1,55 @@ -/** - * 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 { - AsgardeoAuthException, - AuthClientConfig, - Config, - TokenExchangeRequestConfig, - IdToken, - Hooks, - HttpClientInstance, - HttpRequestConfig, - HttpResponse, - OIDCEndpoints, - SignInConfig, - SPAUtils, - type BasicUserInfo, -} from '@asgardeo/auth-spa'; -import type {Plugin, Ref, App, Reactive} from 'vue'; -import {reactive, ref} from 'vue'; -import AuthAPI from '../auth-api'; -import type {AuthContextInterface, AuthParams, AuthStateInterface, AuthVueConfig} from '../types'; - -export type AsgardeoPluginOptions = AuthVueConfig; - -/** - * Default `AuthVueConfig` config. - */ -const defaultConfig: Partial = { - disableAutoSignIn: true, - disableTrySignInSilently: true, -}; - -export const ASGARDEO_INJECTION_KEY: symbol = Symbol('asgardeo'); - -export const asgardeoPlugin: Plugin = { - install(app: App, options: AsgardeoPluginOptions): void { - const AuthClient: AuthAPI = new AuthAPI(); - const isInitialized: Ref = ref(false); - const error: Ref = ref(null); - - const state: Reactive = reactive({...AuthClient.getState()}); - - /* eslint-disable no-useless-catch */ - const withStateSync = async (cb: () => T | Promise, refreshState: boolean = true): Promise => { - let result: T; - try { - result = await cb(); - return result; - } catch (err) { - throw err; - } finally { - if (refreshState) { - const currentState: AuthStateInterface = AuthClient.getState(); - Object.assign(state, currentState); - } - } - }; - - const signInSilently = async ( - additionalParams?: Record, - tokenRequestConfig?: {params: Record}, - ): Promise => - withStateSync(async () => AuthClient.signInSilently(additionalParams, tokenRequestConfig)); - - const checkIsAuthenticated = async (): Promise => - withStateSync(async () => { - const isAuthenticatedState: boolean = await AuthClient.isSignedIn(); - if (!isAuthenticatedState) { - AuthClient.updateState({...state, isLoading: false, isSignedIn: false}); - return; - } - const response: BasicUserInfo = await AuthClient.getUser(); - const stateToUpdate: AuthStateInterface = response - ? { - allowedScopes: response.allowedScopes, - displayName: response.displayName, - email: response.email, - isLoading: false, - isSignedIn: true, - sub: response.sub, - username: response.username, - } - : {...state, isLoading: false, isSignedIn: isAuthenticatedState}; - AuthClient.updateState(stateToUpdate); - }); - - const initialize = async (): Promise => { - await withStateSync(async () => { - if (isInitialized.value) return; - - try { - const config: AuthVueConfig = {...defaultConfig, ...options} as AuthVueConfig; - await AuthClient.init(config); - isInitialized.value = true; - - if (!config.skipRedirectCallback) { - const url: URL = new URL(window.location.href); - const authParams: AuthParams = null; - - if ( - (SPAUtils.hasAuthSearchParamsInURL() && - new URL(url.origin + url.pathname).toString() === new URL(config?.afterSignInUrl).toString()) || - authParams?.authorizationCode || - url.searchParams.get('error') - ) { - await AuthClient.signIn( - {callOnlyOnRedirect: true}, - authParams?.authorizationCode, - authParams?.sessionState, - authParams?.state, - ); - SPAUtils.removeAuthorizationCode(); - return; - } - } - - if (!config.disableAutoSignIn && (await AuthClient.isSessionActive())) { - await AuthClient.signIn(); - } - - await checkIsAuthenticated(); - - if (state.isSignedIn) { - return; - } - - if (!config.disableTrySignInSilently) { - await signInSilently(); - } - } catch (err) { - error.value = err; - throw err; - } - }); - }; - - initialize(); - - const authContext: AuthContextInterface = { - disableHttpHandler: (): Promise => AuthClient.disableHttpHandler(), - enableHttpHandler: (): Promise => AuthClient.enableHttpHandler(), - error: error.value, - exchangeToken: async ( - config: TokenExchangeRequestConfig, - callback?: (response: BasicUserInfo | Response) => void, - ): Promise => { - try { - const response: BasicUserInfo | Response = await AuthClient.exchangeToken(config); - callback?.(response); - return response; - } catch (err) { - error.value = err; - throw err; - } - }, - getAccessToken: (): Promise => AuthClient.getAccessToken(), - getDecodedIdToken: (): Promise => AuthClient.getDecodedIdToken(), - getHttpClient: (): Promise => AuthClient.getHttpClient(), - getIdToken: (): Promise => AuthClient.getIdToken(), - getOpenIDProviderEndpoints: (): Promise => AuthClient.getOpenIDProviderEndpoints(), - getUser: (): Promise => AuthClient.getUser(), - httpRequest: (config: HttpRequestConfig): Promise> => AuthClient.httpRequest(config), - httpRequestAll: (configs: HttpRequestConfig[]): Promise[]> => - AuthClient.httpRequestAll(configs), - isSignedIn: (): Promise => AuthClient.isSignedIn(), - on: (hook: Hooks, callback: (response?: any) => void, id?: string): void => { - if (hook === Hooks.CustomGrant && id) { - AuthClient.on(hook, callback, id); - } else { - AuthClient.on(hook as Exclude, callback); - } - }, - reInitialize: async (config: Partial>): Promise => - withStateSync(async () => { - await AuthClient.reInitialize(config); - }), - refreshAccessToken: (): Promise => AuthClient.refreshAccessToken(), - revokeAccessToken: (): Promise => AuthClient.revokeAccessToken(), - signIn: async ( - config?: SignInConfig, - authorizationCode?: string, - sessionState?: string, - authState?: string, - callback?: (response: BasicUserInfo) => void, - tokenRequestConfig?: {params: Record}, - ): Promise => - withStateSync(async () => { - const result: BasicUserInfo = await AuthClient.signIn( - config, - authorizationCode, - sessionState, - authState, - callback, - tokenRequestConfig, - ); - - if (result) { - error.value = null; - callback?.(result); - } - return result; - }), - signInSilently, - signOut: async (callback?: (response: boolean) => void): Promise => - withStateSync(async () => { - const result: boolean = await AuthClient.signOut(); - callback?.(result); - return result; - }), - state, - }; - - app.provide(ASGARDEO_INJECTION_KEY, authContext); - }, -}; +/** + * 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 type {Plugin} from 'vue'; +import AsgardeoProvider from '../components/AsgardeoProvider'; +import type {AsgardeoVueConfig} from '../models/config'; + +/** + * Vue plugin for Asgardeo authentication. + * + * Registers the `` component globally so it can be used + * anywhere in the application without explicit imports. + * + * @example + * ```ts + * import { createApp } from 'vue'; + * import { AsgardeoPlugin } from '@asgardeo/vue'; + * import App from './App.vue'; + * + * const app = createApp(App); + * app.use(AsgardeoPlugin); + * app.mount('#app'); + * ``` + * + * Then in your root component: + * ```vue + * + * ``` + */ +const AsgardeoPlugin: Plugin = { + install(app) { + app.component('AsgardeoProvider', AsgardeoProvider); + }, +}; + +export default AsgardeoPlugin; diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json index 022b24a83..75da83552 100644 --- a/packages/vue/tsconfig.json +++ b/packages/vue/tsconfig.json @@ -1,39 +1,36 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "jsx": "preserve", - "target": "es2016", - "module": "ESNext", - "emitDecoratorMetadata": true, - "esModuleInterop": true, - "experimentalDecorators": true, - "forceConsistentCasingInFileNames": true, - "strict": false, - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "allowJs": true, - "allowSyntheticDefaultImports": true, - "declaration": true, - "declarationDir": "dist/types", - "outDir": "dist", - "moduleResolution": "node", - "importHelpers": true, - "resolveJsonModule": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "types": ["vite/client", "vitest"], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": [], - "files": [], - "exclude": ["node_modules", "tmp"], - "references": [ - { - "path": "./tsconfig.lib.json" - } - ] -} +{ + "extends": "../../tsconfig.json", + "compileOnSave": false, + "compilerOptions": { + "declaration": false, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "importHelpers": true, + "jsx": "preserve", + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "moduleResolution": "node", + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "sourceMap": true, + "target": "ESNext", + "forceConsistentCasingInFileNames": true, + "strict": false, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "exclude": ["node_modules", "tmp", "dist"], + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/vue/tsconfig.lib.json b/packages/vue/tsconfig.lib.json index b344ad806..2a1118c07 100644 --- a/packages/vue/tsconfig.lib.json +++ b/packages/vue/tsconfig.lib.json @@ -1,23 +1,21 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "dist", - "declarationDir": "types", - "types": ["node"], - "emitDeclarationOnly": true - }, - "exclude": [ - "test-configs", - "vitest.config.ts", - "**/*.spec.ts", - "**/*.test.ts", - "**/*.spec.js", - "**/*.test.js", - "**/*.spec.vue", - "**/*.test.vue", - "scripts", - "dist" - ], - "include": ["**/*.js", "**/*.ts", "**/*.vue", "rollup.config.cjs", "declarations.d.ts"] -} +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "dist", + "declarationDir": "dist", + "types": ["node"] + }, + "exclude": [ + "src/__legacy__/**", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.ts", "src/**/*.jsx", "src/**/*.tsx"] +} diff --git a/packages/vue/tsconfig.spec.json b/packages/vue/tsconfig.spec.json new file mode 100644 index 000000000..507626122 --- /dev/null +++ b/packages/vue/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "module": "ESNext", + "types": ["vitest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.js", + "**/*.spec.js", + "**/*.d.ts" + ] +} diff --git a/packages/vue/vitest.config.ts b/packages/vue/vitest.config.ts new file mode 100644 index 000000000..d631be14e --- /dev/null +++ b/packages/vue/vitest.config.ts @@ -0,0 +1,26 @@ +/** + * 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e437af1a2..5dec47ad8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,9 +15,6 @@ catalogs: '@wso2/prettier-config': specifier: git+https://github.com/brionmario/wso2-ui-configs.git#d3041825a4f8f235c8f9fa36b55cf29d54e791c8&path:packages/prettier-config version: 0.1.0 - '@wso2/stylelint-config': - specifier: git+https://github.com/brionmario/wso2-ui-configs.git#d3041825a4f8f235c8f9fa36b55cf29d54e791c8&path:packages/stylelint-config - version: 0.1.0 dompurify: specifier: 3.3.1 version: 3.3.1 @@ -584,115 +581,52 @@ importers: packages/vue: dependencies: - '@asgardeo/auth-spa': - specifier: 3.3.2 - version: 3.3.2 - '@asgardeo/js': - specifier: 0.1.3 - version: 0.1.3 - '@vitejs/plugin-vue': - specifier: 5.2.4 - version: 5.2.4(vite@7.1.12(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0))(vue@3.5.13(typescript@5.1.6)) - base64url: - specifier: 3.0.1 - version: 3.0.1 - buffer: - specifier: 6.0.3 - version: 6.0.3 - clsx: - specifier: 2.1.1 - version: 2.1.1 - fast-sha256: - specifier: 1.3.0 - version: 1.3.0 - jose: - specifier: 5.3.0 - version: 5.3.0 - randombytes: - specifier: 2.1.0 - version: 2.1.0 - vue: - specifier: '>=3.5.13' - version: 3.5.13(typescript@5.1.6) + '@asgardeo/browser': + specifier: workspace:* + version: link:../browser + '@asgardeo/i18n': + specifier: workspace:* + version: link:../i18n + tslib: + specifier: 2.8.1 + version: 2.8.1 devDependencies: - '@rollup/plugin-commonjs': - specifier: 25.0.7 - version: 25.0.7(rollup@4.32.0) - '@rollup/plugin-image': - specifier: 3.0.3 - version: 3.0.3(rollup@4.32.0) - '@rollup/plugin-node-resolve': - specifier: 15.2.3 - version: 15.2.3(rollup@4.32.0) - '@rollup/plugin-typescript': - specifier: 11.1.6 - version: 11.1.6(rollup@4.32.0)(tslib@2.6.2)(typescript@5.1.6) '@types/node': - specifier: 20.12.7 - version: 20.12.7 - '@vitest/coverage-v8': - specifier: 3.0.8 - version: 3.0.8(vitest@3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0)) - '@vitest/web-worker': - specifier: 3.0.8 - version: 3.0.8(vitest@3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0)) - '@vue/eslint-config-prettier': - specifier: 8.0.0 - version: 8.0.0(eslint@8.57.0)(prettier@2.6.2) - '@vue/eslint-config-typescript': - specifier: 12.0.0 - version: 12.0.0(eslint-plugin-vue@10.1.0(eslint@8.57.0)(vue-eslint-parser@10.1.3(eslint@8.57.0)))(eslint@8.57.0)(typescript@5.1.6) + specifier: 22.15.3 + version: 22.15.3 '@vue/test-utils': specifier: 2.4.6 version: 2.4.6 '@wso2/eslint-plugin': specifier: 'catalog:' - version: https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/eslint-plugin(eslint@8.57.0)(typescript@5.1.6) + version: https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/eslint-plugin(eslint@8.57.0)(typescript@5.7.2) '@wso2/prettier-config': specifier: 'catalog:' - version: https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/prettier-config(prettier@2.6.2)(typescript@5.1.6) - '@wso2/stylelint-config': - specifier: 'catalog:' - version: https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/stylelint-config(postcss@8.5.6)(stylelint@15.1.0(typescript@5.1.6))(typescript@5.1.6) + version: https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/prettier-config(prettier@2.6.2)(typescript@5.7.2) + esbuild: + specifier: 0.25.9 + version: 0.25.9 eslint: specifier: 8.57.0 version: 8.57.0 + jsdom: + specifier: 26.1.0 + version: 26.1.0 prettier: specifier: 2.6.2 version: 2.6.2 - rollup: - specifier: 4.32.0 - version: 4.32.0 - rollup-plugin-dts: + rimraf: specifier: 6.1.0 - version: 6.1.0(rollup@4.32.0)(typescript@5.1.6) - rollup-plugin-polyfill-node: - specifier: 0.13.0 - version: 0.13.0(rollup@4.32.0) - rollup-plugin-styles: - specifier: 4.0.0 - version: 4.0.0(rollup@4.32.0) - sass: - specifier: 1.75.0 - version: 1.75.0 - stylelint: - specifier: 15.1.0 - version: 15.1.0(typescript@5.1.6) - tslib: - specifier: 2.6.2 - version: 2.6.2 + version: 6.1.0 typescript: - specifier: 5.1.6 - version: 5.1.6 - vite: - specifier: 7.1.12 - version: 7.1.12(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) + specifier: 5.7.2 + version: 5.7.2 vitest: - specifier: 3.0.8 - version: 3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - vue-tsc: - specifier: 2.2.2 - version: 2.2.2(typescript@5.1.6) + specifier: 3.1.3 + version: 3.1.3(@types/node@22.15.3)(@vitest/browser@3.1.3)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) + vue: + specifier: 3.5.13 + version: 3.5.13(typescript@5.7.2) samples/react-tanstack-router: dependencies: @@ -864,15 +798,6 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} - '@asgardeo/auth-js@5.1.2': - resolution: {integrity: sha512-fGfUorhUwUSXC/sETTlRbXJ0kMwcLKvVXCieePnxg2AVeiy1lFdRqZt1zsnr4/rd0jd/woYBE96rOAS5fVGFPw==} - - '@asgardeo/auth-spa@3.3.2': - resolution: {integrity: sha512-wFIEhz1K5igyS8EwSRu+RBPD7IiOPlmjHy2vT8WZj/JhYF34cCJveoJyVbELYzLWDAVySJtnuP9AccxcAYxpsg==} - - '@asgardeo/js@0.1.3': - resolution: {integrity: sha512-k7dtodvqqIXOcBUxqTPwm7ChdX5bWl2K70eFQDmnha4bNVcNrsNOzPt4rbIAntOjfUwJpg05YO/ZIV4lZwfB8Q==} - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -963,10 +888,6 @@ packages: resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@1.0.2': - resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} - engines: {node: '>=18'} - '@bufbuild/protobuf@2.7.0': resolution: {integrity: sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA==} @@ -1049,12 +970,6 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-parser-algorithms@2.7.1': - resolution: {integrity: sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-tokenizer': ^2.4.1 - '@csstools/css-parser-algorithms@3.0.5': resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} @@ -1065,27 +980,10 @@ packages: resolution: {integrity: sha512-g0Kw9W3vjx5BEBAF8c5Fm2NcB/Fs8jJXh85aXqwEXiL+tqtOut07TWgyaGzAAfTM+gKckrrncyeGEZPcaRgm2Q==} engines: {node: '>=18'} - '@csstools/css-tokenizer@2.4.1': - resolution: {integrity: sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==} - engines: {node: ^14 || ^16 || >=18} - '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@csstools/media-query-list-parser@2.1.13': - resolution: {integrity: sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-parser-algorithms': ^2.7.1 - '@csstools/css-tokenizer': ^2.4.1 - - '@csstools/selector-specificity@2.2.0': - resolution: {integrity: sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - postcss-selector-parser: ^6.0.10 - '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} @@ -1602,12 +1500,6 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -1889,10 +1781,6 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2177,10 +2065,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@pkgr/core@0.2.4': - resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.58.2': resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} @@ -2233,206 +2117,72 @@ packages: '@types/react': optional: true - '@rollup/plugin-commonjs@25.0.7': - resolution: {integrity: sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-image@3.0.3': - resolution: {integrity: sha512-qXWQwsXpvD4trSb8PeFPFajp8JLpRtqqOeNYRUKnEQNHm7e5UP7fuSRcbjQAJ7wDZBbnJvSdY5ujNBQd9B1iFg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-inject@5.0.5': - resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-node-resolve@15.2.3': - resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-typescript@11.1.6': - resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.14.0||^3.0.0||^4.0.0 - tslib: '*' - typescript: '>=3.7.0' - peerDependenciesMeta: - rollup: - optional: true - tslib: - optional: true - - '@rollup/pluginutils@4.2.1': - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - - '@rollup/pluginutils@5.1.4': - resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/rollup-android-arm-eabi@4.32.0': - resolution: {integrity: sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.50.0': resolution: {integrity: sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.32.0': - resolution: {integrity: sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.50.0': resolution: {integrity: sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.32.0': - resolution: {integrity: sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.50.0': resolution: {integrity: sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.32.0': - resolution: {integrity: sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.50.0': resolution: {integrity: sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.32.0': - resolution: {integrity: sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.50.0': resolution: {integrity: sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.32.0': - resolution: {integrity: sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.50.0': resolution: {integrity: sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.32.0': - resolution: {integrity: sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==} - cpu: [arm] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-gnueabihf@4.50.0': resolution: {integrity: sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.32.0': - resolution: {integrity: sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==} - cpu: [arm] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm-musleabihf@4.50.0': resolution: {integrity: sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.32.0': - resolution: {integrity: sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm64-gnu@4.50.0': resolution: {integrity: sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.32.0': - resolution: {integrity: sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==} - cpu: [arm64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-musl@4.50.0': resolution: {integrity: sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loongarch64-gnu@4.32.0': - resolution: {integrity: sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==} - cpu: [loong64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-loongarch64-gnu@4.50.0': resolution: {integrity: sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-powerpc64le-gnu@4.32.0': - resolution: {integrity: sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.50.0': resolution: {integrity: sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.32.0': - resolution: {integrity: sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.50.0': resolution: {integrity: sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==} cpu: [riscv64] @@ -2445,36 +2195,18 @@ packages: os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.32.0': - resolution: {integrity: sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-s390x-gnu@4.50.0': resolution: {integrity: sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.32.0': - resolution: {integrity: sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==} - cpu: [x64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.50.0': resolution: {integrity: sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.32.0': - resolution: {integrity: sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==} - cpu: [x64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-x64-musl@4.50.0': resolution: {integrity: sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==} cpu: [x64] @@ -2486,31 +2218,16 @@ packages: cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.32.0': - resolution: {integrity: sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.50.0': resolution: {integrity: sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.32.0': - resolution: {integrity: sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.50.0': resolution: {integrity: sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.32.0': - resolution: {integrity: sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.50.0': resolution: {integrity: sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==} cpu: [x64] @@ -2683,10 +2400,6 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -2705,13 +2418,6 @@ packages: '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} - '@types/cssnano@5.1.3': - resolution: {integrity: sha512-BahAZSSvuFXyhgJiwQgsfsNlStE9K/ULGL+YEzK4mmL2Vf02Pjl2yZs+KmbkAg3MxkC9WwMuFwuwnwvrg7CqvQ==} - deprecated: This is a stub types definition. cssnano provides its own type definitions, so you do not need this installed. - - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -2721,15 +2427,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@20.12.7': - resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} - '@types/node@22.15.3': resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==} @@ -2739,9 +2439,6 @@ packages: '@types/node@24.12.0': resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -2753,9 +2450,6 @@ packages: '@types/react@19.1.5': resolution: {integrity: sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==} - '@types/resolve@1.20.2': - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - '@types/semver@7.7.0': resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} @@ -2915,13 +2609,6 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 - '@vitejs/plugin-vue@5.2.4': - resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - vite: 7.1.12 - vue: ^3.2.25 - '@vitest/browser@3.1.3': resolution: {integrity: sha512-Dgyez9LbHJHl9ObZPo5mu4zohWLo7SMv8zRWclMF+dxhQjmOtEP0raEX13ac5ygcvihNoQPBZXdya5LMSbcCDQ==} peerDependencies: @@ -2937,32 +2624,9 @@ packages: webdriverio: optional: true - '@vitest/coverage-v8@3.0.8': - resolution: {integrity: sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==} - peerDependencies: - '@vitest/browser': 3.0.8 - vitest: 3.0.8 - peerDependenciesMeta: - '@vitest/browser': - optional: true - - '@vitest/expect@3.0.8': - resolution: {integrity: sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==} - '@vitest/expect@3.1.3': resolution: {integrity: sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==} - '@vitest/mocker@3.0.8': - resolution: {integrity: sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - '@vitest/mocker@3.1.3': resolution: {integrity: sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==} peerDependencies: @@ -2974,96 +2638,33 @@ packages: vite: optional: true - '@vitest/pretty-format@3.0.8': - resolution: {integrity: sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==} - '@vitest/pretty-format@3.1.3': resolution: {integrity: sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==} - '@vitest/runner@3.0.8': - resolution: {integrity: sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==} - '@vitest/runner@3.1.3': resolution: {integrity: sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==} - '@vitest/snapshot@3.0.8': - resolution: {integrity: sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==} - '@vitest/snapshot@3.1.3': resolution: {integrity: sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==} - '@vitest/spy@3.0.8': - resolution: {integrity: sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==} - '@vitest/spy@3.1.3': resolution: {integrity: sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==} - '@vitest/utils@3.0.8': - resolution: {integrity: sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==} - '@vitest/utils@3.1.3': resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==} - '@vitest/web-worker@3.0.8': - resolution: {integrity: sha512-7XO1wID/LB2srFtD3tleBNl0dfNsJRqzX83nA7XxP/bmQ49woE2tcvprAv7JWPRJbBvgTFw9Q3+YvkHuv9bTmg==} - peerDependencies: - vitest: 3.0.8 - - '@volar/language-core@2.4.14': - resolution: {integrity: sha512-X6beusV0DvuVseaOEy7GoagS4rYHgDHnTrdOj5jeUb49fW5ceQyP9Ej5rBhqgz2wJggl+2fDbbojq1XKaxDi6w==} - - '@volar/source-map@2.4.14': - resolution: {integrity: sha512-5TeKKMh7Sfxo8021cJfmBzcjfY1SsXsPMMjMvjY7ivesdnybqqS+GxGAoXHAOUawQTwtdUxgP65Im+dEmvWtYQ==} - - '@volar/typescript@2.4.14': - resolution: {integrity: sha512-p8Z6f/bZM3/HyCdRNFZOEEzts51uV8WHeN8Tnfnm2EBv6FDB2TQLzfVx7aJvnl8ofKAOnS64B2O8bImBFaauRw==} - '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} - '@vue/compiler-core@3.5.14': - resolution: {integrity: sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==} - '@vue/compiler-dom@3.5.13': resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} - '@vue/compiler-dom@3.5.14': - resolution: {integrity: sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==} - '@vue/compiler-sfc@3.5.13': resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} '@vue/compiler-ssr@3.5.13': resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} - '@vue/compiler-vue2@2.7.16': - resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - - '@vue/eslint-config-prettier@8.0.0': - resolution: {integrity: sha512-55dPqtC4PM/yBjhAr+yEw6+7KzzdkBuLmnhBrDfp4I48+wy+Giqqj9yUr5T2uD/BkBROjjmqnLZmXRdOx/VtQg==} - peerDependencies: - eslint: '>= 8.0.0' - prettier: 2.6.2 - - '@vue/eslint-config-typescript@12.0.0': - resolution: {integrity: sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 - eslint-plugin-vue: ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@vue/language-core@2.2.2': - resolution: {integrity: sha512-QotO41kurE5PLf3vrNgGTk3QswO2PdUFjBwNiOi7zMmGhwb25PSTh9hD1MCgKC06AVv+8sZQvlL3Do4TTVHSiQ==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@vue/reactivity@3.5.13': resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} @@ -3081,9 +2682,6 @@ packages: '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - '@vue/shared@3.5.14': - resolution: {integrity: sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==} - '@vue/test-utils@2.4.6': resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} @@ -3109,17 +2707,6 @@ packages: typescript: optional: true - '@wso2/stylelint-config@https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/stylelint-config': - resolution: {path: packages/stylelint-config, tarball: https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8} - version: 0.1.0 - engines: {node: '>=14.0.0'} - peerDependencies: - stylelint: '>=14.0.0' - typescript: '>=4.0.0' - peerDependenciesMeta: - typescript: - optional: true - '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} @@ -3156,12 +2743,6 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - - alien-signals@1.0.13: - resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} - ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -3186,10 +2767,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -3236,10 +2813,6 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - asn1.js@4.10.1: resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} @@ -3250,10 +2823,6 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -3272,9 +2841,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - await-semaphore@0.1.3: - resolution: {integrity: sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q==} - axe-core@4.11.0: resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} engines: {node: '>=4'} @@ -3293,9 +2859,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@2.0.0: - resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -3310,10 +2873,6 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -3327,9 +2886,6 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -3377,10 +2933,6 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -3405,17 +2957,6 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} @@ -3434,10 +2975,6 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -3487,9 +3024,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - colorjs.io@0.5.2: resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} @@ -3504,10 +3038,6 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -3562,15 +3092,6 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - create-ecdh@4.0.4: resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} @@ -3594,62 +3115,10 @@ packages: resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} engines: {node: '>= 0.10'} - css-declaration-sorter@6.4.1: - resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: ^8.0.9 - - css-functions-list@3.2.3: - resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} - engines: {node: '>=12 || >=16'} - - css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - - css-tree@1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} - - css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.1.0: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssnano-preset-default@5.2.14: - resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano-utils@3.1.0: - resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano@5.1.15: - resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - csso@4.2.0: - resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} - engines: {node: '>=8.0.0'} - cssstyle@4.6.0: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} @@ -3687,9 +3156,6 @@ packages: dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} - de-indent@1.0.2: - resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} - debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -3716,21 +3182,9 @@ packages: supports-color: optional: true - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -3738,10 +3192,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -3807,25 +3257,12 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - dom-walk@0.1.2: resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - dompurify@3.3.1: resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} - domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} @@ -3884,9 +3321,6 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -4101,20 +3535,6 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-prettier@5.4.0: - resolution: {integrity: sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: 2.6.2 - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - eslint-plugin-react-hooks@4.6.2: resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} @@ -4155,13 +3575,6 @@ packages: eslint: ^5 || ^6 || ^7 || ^8 typescript: ^3 || ^4 || ^5 - eslint-plugin-vue@10.1.0: - resolution: {integrity: sha512-/VTiJ1eSfNLw6lvG9ENySbGmcVvz6wZ9nA7ZqXlLBY2RkaF15iViYKxglWiIch12KiLAj0j1iXPYU6W4wTROFA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - vue-eslint-parser: ^10.0.0 - eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -4254,9 +3667,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - evp_bytestokey@1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} @@ -4293,13 +3703,6 @@ packages: fast-sha256@1.3.0: resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} - - fastest-levenshtein@1.0.16: - resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} - engines: {node: '>= 4.9.1'} - fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -4328,10 +3731,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - filter-obj@1.1.0: - resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} - engines: {node: '>=0.10.0'} - finalhandler@2.1.0: resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} engines: {node: '>= 0.8'} @@ -4404,10 +3803,6 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -4497,19 +3892,6 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - global-modules@2.0.0: - resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} - engines: {node: '>=6'} - - global-prefix@3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} - global@4.4.0: resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} @@ -4537,9 +3919,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - globjoin@0.1.4: - resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -4550,10 +3929,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -4595,20 +3970,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -4617,13 +3981,6 @@ packages: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} - http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -4652,12 +4009,6 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} - icss-utils@5.1.0: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -4665,9 +4016,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} - immutable@5.1.2: resolution: {integrity: sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==} @@ -4675,10 +4023,6 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-lazy@4.0.0: - resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} - engines: {node: '>=8'} - import-meta-resolve@3.1.1: resolution: {integrity: sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==} @@ -4686,10 +4030,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -4723,18 +4063,10 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-boolean-object@1.2.2: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - is-builtin-module@3.2.1: - resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} - engines: {node: '>=6'} - is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -4784,9 +4116,6 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -4803,23 +4132,12 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -4885,22 +4203,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -4931,15 +4233,9 @@ packages: jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - jose@4.15.9: - resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} - jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} - jose@5.3.0: - resolution: {integrity: sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==} - jose@6.0.11: resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==} @@ -4991,9 +4287,6 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -5015,9 +4308,6 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -5025,13 +4315,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - known-css-properties@0.26.0: - resolution: {integrity: sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==} - language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -5185,10 +4468,6 @@ packages: resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -5208,27 +4487,12 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.isempty@4.4.0: - resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -5243,10 +4507,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.1.0: - resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} - engines: {node: 20 || >=22} - lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} @@ -5254,10 +4514,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - lucide-react@0.294.0: resolution: {integrity: sha512-V7o0/VECSGbLHn3/1O67FUgBwWB+hmzshrgDVRJQhMh8uj5D3HBuIvhuAmQTtlupILSplwIZg5FTc4tTKMA2SA==} peerDependencies: @@ -5270,41 +4526,17 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mathml-tag-names@2.1.3: - resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} - md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - - mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} @@ -5315,10 +4547,6 @@ packages: memory-cache@0.2.0: resolution: {integrity: sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==} - meow@9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} - merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -5358,14 +4586,6 @@ packages: min-document@2.19.1: resolution: {integrity: sha512-8lqe85PkqQJzIcs2iD7xW/WSxcncC3/DPVbTOafKNJDIMXwGfwXS350mH4SJslomntN2iYtFBuC0yNO3CEap6g==} - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - mini-svg-data-uri@1.4.4: - resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} - hasBin: true - minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -5379,10 +4599,6 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - minimatch@9.0.1: resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} engines: {node: '>=16 || 14 >=14.17'} @@ -5395,10 +4611,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -5424,9 +4636,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -5486,32 +4695,14 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} @@ -5593,10 +4784,6 @@ packages: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -5617,14 +4804,6 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -5657,9 +4836,6 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -5747,217 +4923,6 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-calc@8.2.4: - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: ^8.2.2 - - postcss-colormin@5.3.1: - resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-convert-values@5.1.3: - resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-comments@5.1.2: - resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-duplicates@5.1.0: - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-empty@5.1.1: - resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-overridden@5.1.0: - resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-media-query-parser@0.2.3: - resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - - postcss-merge-longhand@5.1.7: - resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-merge-rules@5.1.4: - resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-font-values@5.1.0: - resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-gradients@5.1.1: - resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-params@5.1.4: - resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-selectors@5.2.1: - resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-modules-extract-imports@3.1.0: - resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-local-by-default@4.2.0: - resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-scope@3.2.1: - resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-values@4.0.0: - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-normalize-charset@5.1.0: - resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-display-values@5.1.0: - resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-positions@5.1.1: - resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-repeat-style@5.1.1: - resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-string@5.1.0: - resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-timing-functions@5.1.0: - resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-unicode@5.1.1: - resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-url@5.1.0: - resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-whitespace@5.1.1: - resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-ordered-values@5.1.3: - resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-initial@5.1.2: - resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-transforms@5.1.0: - resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-resolve-nested-selector@0.1.6: - resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} - - postcss-safe-parser@6.0.0: - resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.3.3 - - postcss-scss@4.0.9: - resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.4.29 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} - engines: {node: '>=4'} - - postcss-svgo@5.1.0: - resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-unique-selectors@5.1.1: - resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -5965,14 +4930,6 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.1: - resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -6032,17 +4989,9 @@ packages: quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} - query-string@7.1.3: - resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} - engines: {node: '>=6'} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -6089,14 +5038,6 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - read-yaml-file@2.1.0: resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==} engines: {node: '>=10.13'} @@ -6108,18 +5049,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -6196,29 +5129,6 @@ packages: resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} engines: {node: '>= 0.8'} - rollup-plugin-dts@6.1.0: - resolution: {integrity: sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw==} - engines: {node: '>=16'} - peerDependencies: - rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 - - rollup-plugin-polyfill-node@0.13.0: - resolution: {integrity: sha512-FYEvpCaD5jGtyBuBFcQImEGmTxDTPbiHjJdrYIp+mFIwgXiXabxvKUK7ZT9P31ozu2Tqm9llYQMRWsfvTMTAOw==} - peerDependencies: - rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - - rollup-plugin-styles@4.0.0: - resolution: {integrity: sha512-A2K2sao84OsTmDxXG83JTCdXWrmgvQkkI38XDat46rdtpGMRm9tSYqeCdlwwGDJF4kKIafhV1mUidqu8MxUGig==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - rollup: ^2.63.0 - - rollup@4.32.0: - resolution: {integrity: sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rollup@4.50.0: resolution: {integrity: sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -6375,11 +5285,6 @@ packages: engines: {node: '>=16.0.0'} hasBin: true - sass@1.75.0: - resolution: {integrity: sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==} - engines: {node: '>=14.0.0'} - hasBin: true - sass@1.92.1: resolution: {integrity: sha512-ffmsdbwqb3XeyR8jJR6KelIXARM9bFQe8A6Q3W4Klmwy5Ckd5gz7jgUNHo4UOqutU5Sk1DtKLbpDP0nLCg1xqQ==} engines: {node: '>=14.0.0'} @@ -6400,10 +5305,6 @@ packages: resolution: {integrity: sha512-KEeg9wGi/96Q8Edp97gt5Z5dYCcvG7vPKdKFq6rPLCHtaaHwZKbhbC+gY74O7OjX8Fnels49wqc8IgkNhBJZfw==} engines: {node: 18 || >=20} - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -6505,10 +5406,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -6527,26 +5424,6 @@ packages: spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.21: - resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} - - split-on-first@1.1.0: - resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} - engines: {node: '>=6'} - - stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -6568,10 +5445,6 @@ packages: stream-browserify@3.0.0: resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} - strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} - engines: {node: '>=4'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -6625,17 +5498,10 @@ packages: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - style-search@0.1.0: - resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} - styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -6649,50 +5515,6 @@ packages: babel-plugin-macros: optional: true - stylehacks@5.1.1: - resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - stylelint-config-recommended-scss@8.0.0: - resolution: {integrity: sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==} - peerDependencies: - postcss: ^8.3.3 - stylelint: ^14.10.0 - peerDependenciesMeta: - postcss: - optional: true - - stylelint-config-recommended@9.0.0: - resolution: {integrity: sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==} - peerDependencies: - stylelint: ^14.10.0 - - stylelint-config-standard-scss@6.1.0: - resolution: {integrity: sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q==} - peerDependencies: - postcss: ^8.3.3 - stylelint: ^14.14.0 - peerDependenciesMeta: - postcss: - optional: true - - stylelint-config-standard@29.0.0: - resolution: {integrity: sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==} - peerDependencies: - stylelint: ^14.14.0 - - stylelint-scss@4.7.0: - resolution: {integrity: sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==} - peerDependencies: - stylelint: ^14.5.1 || ^15.0.0 - - stylelint@15.1.0: - resolution: {integrity: sha512-Tw8OyIiYhxnIHUzgoLlCyWgCUKsPYiP3TDgs7M1VbayS+q5qZly2yxABg+YPe/hFRWiu0cOtptCtpyrn1CrnYw==} - engines: {node: ^14.13.1 || >=16.0.0} - hasBin: true - stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} @@ -6704,22 +5526,10 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - supports-hyperlinks@2.3.0: - resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} - engines: {node: '>=8'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svg-tags@1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - - svgo@2.8.0: - resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} - engines: {node: '>=10.13.0'} - hasBin: true - symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -6731,17 +5541,9 @@ packages: resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==} engines: {node: '>=16.0.0'} - synckit@0.11.6: - resolution: {integrity: sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw==} - engines: {node: ^14.18.0 || >=16.0.0} - tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - table@6.9.0: - resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} - engines: {node: '>=10.0.0'} - tailwind-merge@3.3.0: resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==} @@ -6769,10 +5571,6 @@ packages: engines: {node: '>=10'} hasBin: true - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -6861,10 +5659,6 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} @@ -6887,9 +5681,6 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -6911,22 +5702,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -6954,11 +5733,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - typescript@5.1.6: - resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.7.2: resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} @@ -6976,9 +5750,6 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -6993,10 +5764,6 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -7022,12 +5789,6 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - v8-compile-cache@2.4.0: - resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - varint@6.0.0: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} @@ -7035,11 +5796,6 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite-node@3.0.8: - resolution: {integrity: sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite-node@3.1.3: resolution: {integrity: sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -7085,74 +5841,6 @@ packages: yaml: optional: true - vite@7.1.12: - resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.0.8: - resolution: {integrity: sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.0.8 - '@vitest/ui': 3.0.8 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - vitest@3.1.3: resolution: {integrity: sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -7181,30 +5869,9 @@ packages: jsdom: optional: true - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - vue-component-type-helpers@2.2.10: resolution: {integrity: sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==} - vue-eslint-parser@10.1.3: - resolution: {integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - - vue-eslint-parser@9.4.3: - resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - - vue-tsc@2.2.2: - resolution: {integrity: sha512-1icPKkxAA5KTAaSwg0wVWdE48EdsH8fgvcbAiqojP4jXKl6LEM3soiW1aG/zrWrFt8Mw1ncG2vG1PvpZpVfehA==} - hasBin: true - peerDependencies: - typescript: '>=5.0.0' - vue@3.5.13: resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} peerDependencies: @@ -7271,10 +5938,6 @@ packages: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -7300,10 +5963,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.18.2: resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} @@ -7328,10 +5987,6 @@ packages: utf-8-validate: optional: true - xml-name-validator@4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -7346,9 +6001,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -7362,10 +6014,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -7413,28 +6061,6 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} - '@asgardeo/auth-js@5.1.2': {} - - '@asgardeo/auth-spa@3.3.2': - dependencies: - '@asgardeo/auth-js': 5.1.2 - await-semaphore: 0.1.3 - axios: 1.13.5 - base64url: 3.0.1 - buffer: 6.0.3 - fast-sha256: 1.3.0 - jose: 4.15.9 - randombytes: 2.1.0 - transitivePeerDependencies: - - debug - - '@asgardeo/js@0.1.3': - dependencies: - '@asgardeo/auth-js': 5.1.2 - csstype: 3.1.3 - lodash.isempty: 4.4.0 - lodash.merge: 4.6.2 - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -7555,8 +6181,6 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@bcoe/v8-coverage@1.0.2': {} - '@bufbuild/protobuf@2.7.0': optional: true @@ -7733,29 +6357,14 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1)': - dependencies: - '@csstools/css-tokenizer': 2.4.1 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-tokenizer': 3.0.4 '@csstools/css-syntax-patches-for-csstree@1.0.25': {} - '@csstools/css-tokenizer@2.4.1': {} - '@csstools/css-tokenizer@3.0.4': {} - '@csstools/media-query-list-parser@2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)': - dependencies: - '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) - '@csstools/css-tokenizer': 2.4.1 - - '@csstools/selector-specificity@2.2.0(postcss-selector-parser@6.1.2)': - dependencies: - postcss-selector-parser: 6.1.2 - '@emnapi/core@1.4.3': dependencies: '@emnapi/wasi-threads': 1.0.2 @@ -8072,11 +6681,6 @@ snapshots: eslint: 9.25.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.0)': - dependencies: - eslint: 8.57.0 - eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} '@eslint/config-array@0.20.0': @@ -8309,8 +6913,6 @@ snapshots: dependencies: minipass: 7.1.2 - '@istanbuljs/schema@0.1.3': {} - '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -8528,8 +7130,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.2.4': {} - '@playwright/test@1.58.2': dependencies: playwright: 1.58.2 @@ -8567,182 +7167,66 @@ snapshots: optionalDependencies: '@types/react': 19.1.5 - '@rollup/plugin-commonjs@25.0.7(rollup@4.32.0)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.32.0) - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 8.1.0 - is-reference: 1.2.1 - magic-string: 0.30.17 - optionalDependencies: - rollup: 4.32.0 - - '@rollup/plugin-image@3.0.3(rollup@4.32.0)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.32.0) - mini-svg-data-uri: 1.4.4 - optionalDependencies: - rollup: 4.32.0 - - '@rollup/plugin-inject@5.0.5(rollup@4.32.0)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.32.0) - estree-walker: 2.0.2 - magic-string: 0.30.17 - optionalDependencies: - rollup: 4.32.0 - - '@rollup/plugin-node-resolve@15.2.3(rollup@4.32.0)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.32.0) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-builtin-module: 3.2.1 - is-module: 1.0.0 - resolve: 1.22.10 - optionalDependencies: - rollup: 4.32.0 - - '@rollup/plugin-typescript@11.1.6(rollup@4.32.0)(tslib@2.6.2)(typescript@5.1.6)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.32.0) - resolve: 1.22.10 - typescript: 5.1.6 - optionalDependencies: - rollup: 4.32.0 - tslib: 2.6.2 - - '@rollup/pluginutils@4.2.1': - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - - '@rollup/pluginutils@5.1.4(rollup@4.32.0)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.32.0 - - '@rollup/rollup-android-arm-eabi@4.32.0': - optional: true - '@rollup/rollup-android-arm-eabi@4.50.0': optional: true - '@rollup/rollup-android-arm64@4.32.0': - optional: true - '@rollup/rollup-android-arm64@4.50.0': optional: true - '@rollup/rollup-darwin-arm64@4.32.0': - optional: true - '@rollup/rollup-darwin-arm64@4.50.0': optional: true - '@rollup/rollup-darwin-x64@4.32.0': - optional: true - '@rollup/rollup-darwin-x64@4.50.0': optional: true - '@rollup/rollup-freebsd-arm64@4.32.0': - optional: true - '@rollup/rollup-freebsd-arm64@4.50.0': optional: true - '@rollup/rollup-freebsd-x64@4.32.0': - optional: true - '@rollup/rollup-freebsd-x64@4.50.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.32.0': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.50.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.32.0': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.50.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.32.0': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.32.0': - optional: true - '@rollup/rollup-linux-arm64-musl@4.50.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.32.0': - optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.32.0': - optional: true - '@rollup/rollup-linux-ppc64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.32.0': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.50.0': optional: true '@rollup/rollup-linux-riscv64-musl@4.50.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.32.0': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.50.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.32.0': - optional: true - '@rollup/rollup-linux-x64-gnu@4.50.0': optional: true - '@rollup/rollup-linux-x64-musl@4.32.0': - optional: true - '@rollup/rollup-linux-x64-musl@4.50.0': optional: true '@rollup/rollup-openharmony-arm64@4.50.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.32.0': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.50.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.32.0': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.50.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.32.0': - optional: true - '@rollup/rollup-win32-x64-msvc@4.50.0': optional: true @@ -8892,8 +7376,6 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 - '@trysound/sax@0.2.0': {} - '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 @@ -8921,28 +7403,14 @@ snapshots: dependencies: '@babel/types': 7.27.1 - '@types/cssnano@5.1.3(postcss@8.4.31)': - dependencies: - cssnano: 5.1.15(postcss@8.4.31) - transitivePeerDependencies: - - postcss - - '@types/estree@1.0.6': {} - '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} - '@types/minimist@1.2.5': {} - '@types/node@12.20.55': {} - '@types/node@20.12.7': - dependencies: - undici-types: 5.26.5 - '@types/node@22.15.3': dependencies: undici-types: 6.21.0 @@ -8956,8 +7424,6 @@ snapshots: undici-types: 7.16.0 optional: true - '@types/normalize-package-data@2.4.4': {} - '@types/parse-json@4.0.2': {} '@types/react-dom@19.1.5(@types/react@19.1.5)': @@ -8968,33 +7434,11 @@ snapshots: dependencies: csstype: 3.1.3 - '@types/resolve@1.20.2': {} - '@types/semver@7.7.0': {} '@types/trusted-types@2.0.7': optional: true - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - eslint: 8.57.0 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - semver: 7.7.3 - ts-api-utils: 1.4.3(typescript@5.1.6) - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -9032,14 +7476,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - transitivePeerDependencies: - - supports-color - - typescript - '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.7.2) @@ -9048,19 +7484,6 @@ snapshots: - supports-color - typescript - '@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - eslint: 8.57.0 - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 @@ -9101,18 +7524,6 @@ snapshots: '@typescript-eslint/types': 8.30.1 '@typescript-eslint/visitor-keys': 8.30.1 - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - debug: 4.4.3 - eslint: 8.57.0 - ts-api-utils: 1.4.3(typescript@5.1.6) - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.2) @@ -9142,20 +7553,6 @@ snapshots: '@typescript-eslint/types@8.30.1': {} - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.1.6)': - dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.7.3 - tsutils: 3.21.0(typescript@5.1.6) - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.7.2)': dependencies: '@typescript-eslint/types': 5.62.0 @@ -9170,21 +7567,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.1.6)': - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.7.3 - ts-api-utils: 1.4.3(typescript@5.1.6) - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.7.2)': dependencies: '@typescript-eslint/types': 6.21.0 @@ -9214,21 +7596,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.7.0 - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) - eslint: 8.57.0 - eslint-scope: 5.1.1 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - - typescript - '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.0) @@ -9244,20 +7611,6 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.7.0 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) - eslint: 8.57.0 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - - typescript - '@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.0) @@ -9315,11 +7668,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.2.4(vite@7.1.12(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0))(vue@3.5.13(typescript@5.1.6))': - dependencies: - vite: 7.1.12(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - vue: 3.5.13(typescript@5.1.6) - '@vitest/browser@3.1.3(playwright@1.55.1)(vite@6.4.1(@types/node@22.15.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.1.3)': dependencies: '@testing-library/dom': 10.4.0 @@ -9379,31 +7727,6 @@ snapshots: - vite optional: true - '@vitest/coverage-v8@3.0.8(vitest@3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 1.0.2 - debug: 4.4.1 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.17 - magicast: 0.3.5 - std-env: 3.9.0 - test-exclude: 7.0.1 - tinyrainbow: 2.0.0 - vitest: 3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@3.0.8': - dependencies: - '@vitest/spy': 3.0.8 - '@vitest/utils': 3.0.8 - chai: 5.2.0 - tinyrainbow: 2.0.0 - '@vitest/expect@3.1.3': dependencies: '@vitest/spy': 3.1.3 @@ -9411,14 +7734,6 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.8(vite@6.4.1(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.0.8 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.4.1(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - '@vitest/mocker@3.1.3(vite@6.4.1(@types/node@22.15.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.1.3 @@ -9435,75 +7750,31 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@22.15.30)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - '@vitest/pretty-format@3.0.8': - dependencies: - tinyrainbow: 2.0.0 - '@vitest/pretty-format@3.1.3': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.0.8': - dependencies: - '@vitest/utils': 3.0.8 - pathe: 2.0.3 - '@vitest/runner@3.1.3': dependencies: '@vitest/utils': 3.1.3 pathe: 2.0.3 - '@vitest/snapshot@3.0.8': - dependencies: - '@vitest/pretty-format': 3.0.8 - magic-string: 0.30.17 - pathe: 2.0.3 - '@vitest/snapshot@3.1.3': dependencies: '@vitest/pretty-format': 3.1.3 magic-string: 0.30.17 pathe: 2.0.3 - '@vitest/spy@3.0.8': - dependencies: - tinyspy: 3.0.2 - '@vitest/spy@3.1.3': dependencies: tinyspy: 3.0.2 - '@vitest/utils@3.0.8': - dependencies: - '@vitest/pretty-format': 3.0.8 - loupe: 3.1.3 - tinyrainbow: 2.0.0 - '@vitest/utils@3.1.3': dependencies: '@vitest/pretty-format': 3.1.3 loupe: 3.1.3 tinyrainbow: 2.0.0 - '@vitest/web-worker@3.0.8(vitest@3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0))': - dependencies: - debug: 4.4.1 - vitest: 3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - transitivePeerDependencies: - - supports-color - - '@volar/language-core@2.4.14': - dependencies: - '@volar/source-map': 2.4.14 - - '@volar/source-map@2.4.14': {} - - '@volar/typescript@2.4.14': - dependencies: - '@volar/language-core': 2.4.14 - path-browserify: 1.0.1 - vscode-uri: 3.1.0 - '@vue/compiler-core@3.5.13': dependencies: '@babel/parser': 7.27.2 @@ -9512,24 +7783,11 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-core@3.5.14': - dependencies: - '@babel/parser': 7.27.2 - '@vue/shared': 3.5.14 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.13': dependencies: '@vue/compiler-core': 3.5.13 '@vue/shared': 3.5.13 - '@vue/compiler-dom@3.5.14': - dependencies: - '@vue/compiler-core': 3.5.14 - '@vue/shared': 3.5.14 - '@vue/compiler-sfc@3.5.13': dependencies: '@babel/parser': 7.27.2 @@ -9539,7 +7797,7 @@ snapshots: '@vue/shared': 3.5.13 estree-walker: 2.0.2 magic-string: 0.30.17 - postcss: 8.5.1 + postcss: 8.5.6 source-map-js: 1.2.1 '@vue/compiler-ssr@3.5.13': @@ -9547,45 +7805,6 @@ snapshots: '@vue/compiler-dom': 3.5.13 '@vue/shared': 3.5.13 - '@vue/compiler-vue2@2.7.16': - dependencies: - de-indent: 1.0.2 - he: 1.2.0 - - '@vue/eslint-config-prettier@8.0.0(eslint@8.57.0)(prettier@2.6.2)': - dependencies: - eslint: 8.57.0 - eslint-config-prettier: 8.10.0(eslint@8.57.0) - eslint-plugin-prettier: 5.4.0(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.6.2) - prettier: 2.6.2 - transitivePeerDependencies: - - '@types/eslint' - - '@vue/eslint-config-typescript@12.0.0(eslint-plugin-vue@10.1.0(eslint@8.57.0)(vue-eslint-parser@10.1.3(eslint@8.57.0)))(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - eslint-plugin-vue: 10.1.0(eslint@8.57.0)(vue-eslint-parser@10.1.3(eslint@8.57.0)) - vue-eslint-parser: 9.4.3(eslint@8.57.0) - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - - '@vue/language-core@2.2.2(typescript@5.1.6)': - dependencies: - '@volar/language-core': 2.4.14 - '@vue/compiler-dom': 3.5.14 - '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.14 - alien-signals: 1.0.13 - minimatch: 9.0.5 - muggle-string: 0.4.1 - path-browserify: 1.0.1 - optionalDependencies: - typescript: 5.1.6 - '@vue/reactivity@3.5.13': dependencies: '@vue/shared': 3.5.13 @@ -9602,55 +7821,19 @@ snapshots: '@vue/shared': 3.5.13 csstype: 3.1.3 - '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.1.6))': + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.2))': dependencies: '@vue/compiler-ssr': 3.5.13 '@vue/shared': 3.5.13 - vue: 3.5.13(typescript@5.1.6) + vue: 3.5.13(typescript@5.7.2) '@vue/shared@3.5.13': {} - '@vue/shared@3.5.14': {} - '@vue/test-utils@2.4.6': dependencies: js-beautify: 1.15.4 vue-component-type-helpers: 2.2.10 - '@wso2/eslint-plugin@https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/eslint-plugin(eslint@8.57.0)(typescript@5.1.6)': - dependencies: - '@babel/core': 7.27.1 - '@babel/eslint-parser': 7.28.5(@babel/core@7.27.1)(eslint@8.57.0) - '@next/eslint-plugin-next': 13.5.11 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - eslint-config-airbnb: 19.0.4(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.37.5(eslint@8.57.0))(eslint@8.57.0) - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint@8.57.0) - eslint-config-airbnb-typescript: 17.1.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6))(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint@8.57.0) - eslint-config-prettier: 8.10.0(eslint@8.57.0) - eslint-plugin-eslint-plugin: 5.5.1(eslint@8.57.0) - eslint-plugin-header: 3.1.1(eslint@8.57.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) - eslint-plugin-node: 11.1.0(eslint@8.57.0) - eslint-plugin-prettier: 4.2.5(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.6.2) - eslint-plugin-react: 7.37.5(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) - eslint-plugin-testing-library: 5.11.1(eslint@8.57.0)(typescript@5.1.6) - eslint-plugin-tsdoc: 0.2.17 - eslint-plugin-typescript-sort-keys: 2.3.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6) - prettier: 2.6.2 - requireindex: 1.2.0 - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - jest - - supports-color - '@wso2/eslint-plugin@https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/eslint-plugin(eslint@8.57.0)(typescript@5.7.2)': dependencies: '@babel/core': 7.27.1 @@ -9685,28 +7868,12 @@ snapshots: - jest - supports-color - '@wso2/prettier-config@https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/prettier-config(prettier@2.6.2)(typescript@5.1.6)': - dependencies: - prettier: 2.6.2 - optionalDependencies: - typescript: 5.1.6 - '@wso2/prettier-config@https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/prettier-config(prettier@2.6.2)(typescript@5.7.2)': dependencies: prettier: 2.6.2 optionalDependencies: typescript: 5.7.2 - '@wso2/stylelint-config@https://codeload.github.com/brionmario/wso2-ui-configs/tar.gz/d3041825a4f8f235c8f9fa36b55cf29d54e791c8#path:packages/stylelint-config(postcss@8.5.6)(stylelint@15.1.0(typescript@5.1.6))(typescript@5.1.6)': - dependencies: - postcss-scss: 4.0.9(postcss@8.5.6) - stylelint: 15.1.0(typescript@5.1.6) - stylelint-config-standard-scss: 6.1.0(postcss@8.5.6)(stylelint@15.1.0(typescript@5.1.6)) - optionalDependencies: - typescript: 5.1.6 - transitivePeerDependencies: - - postcss - '@yarnpkg/lockfile@1.1.0': {} '@yarnpkg/parsers@3.0.2': @@ -9740,15 +7907,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - alien-signals@1.0.13: {} - ansi-colors@4.1.3: {} ansi-regex@5.0.1: {} @@ -9763,11 +7921,6 @@ snapshots: ansi-styles@6.2.3: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - argparse@2.0.1: {} aria-query@5.3.0: @@ -9845,8 +7998,6 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - arrify@1.0.1: {} - asn1.js@4.10.1: dependencies: bn.js: 4.12.2 @@ -9857,8 +8008,6 @@ snapshots: ast-types-flow@0.0.8: {} - astral-regex@2.0.0: {} - async-function@1.0.0: {} asynckit@0.4.0: {} @@ -9877,8 +8026,6 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - await-semaphore@0.1.3: {} - axe-core@4.11.0: {} axios@1.13.5: @@ -9899,8 +8046,6 @@ snapshots: balanced-match@1.0.2: {} - balanced-match@2.0.0: {} - base64-js@1.5.1: {} base64url@3.0.1: {} @@ -9913,8 +8058,6 @@ snapshots: dependencies: require-from-string: 2.0.2 - binary-extensions@2.3.0: {} - bl@4.1.0: dependencies: buffer: 5.7.1 @@ -9939,8 +8082,6 @@ snapshots: transitivePeerDependencies: - supports-color - boolbase@1.0.0: {} - brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -10017,8 +8158,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - builtin-modules@3.3.0: {} - bytes@3.1.2: {} cac@6.7.14: {} @@ -10042,21 +8181,6 @@ snapshots: callsites@3.1.0: {} - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - - camelcase@5.3.1: {} - - caniuse-api@3.0.0: - dependencies: - browserslist: 4.24.5 - caniuse-lite: 1.0.30001718 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001718: {} chai@5.2.0: @@ -10076,18 +8200,6 @@ snapshots: check-error@2.1.1: {} - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -10130,8 +8242,6 @@ snapshots: color-name@1.1.4: {} - colord@2.9.3: {} - colorjs.io@0.5.2: optional: true @@ -10144,8 +8254,6 @@ snapshots: commander@2.20.3: optional: true - commander@7.2.0: {} - commondir@1.0.1: {} confbox@0.1.8: {} @@ -10189,15 +8297,6 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.1.6): - dependencies: - import-fresh: 3.3.1 - js-yaml: 4.1.1 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.1.6 - create-ecdh@4.0.4: dependencies: bn.js: 4.12.2 @@ -10254,87 +8353,11 @@ snapshots: randombytes: 2.1.0 randomfill: 1.0.4 - css-declaration-sorter@6.4.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - css-functions-list@3.2.3: {} - - css-select@4.3.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - - css-tree@1.1.3: - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - - css-tree@2.3.1: - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - css-tree@3.1.0: dependencies: mdn-data: 2.12.2 source-map-js: 1.2.1 - css-what@6.1.0: {} - - cssesc@3.0.0: {} - - cssnano-preset-default@5.2.14(postcss@8.4.31): - dependencies: - css-declaration-sorter: 6.4.1(postcss@8.4.31) - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-calc: 8.2.4(postcss@8.4.31) - postcss-colormin: 5.3.1(postcss@8.4.31) - postcss-convert-values: 5.1.3(postcss@8.4.31) - postcss-discard-comments: 5.1.2(postcss@8.4.31) - postcss-discard-duplicates: 5.1.0(postcss@8.4.31) - postcss-discard-empty: 5.1.1(postcss@8.4.31) - postcss-discard-overridden: 5.1.0(postcss@8.4.31) - postcss-merge-longhand: 5.1.7(postcss@8.4.31) - postcss-merge-rules: 5.1.4(postcss@8.4.31) - postcss-minify-font-values: 5.1.0(postcss@8.4.31) - postcss-minify-gradients: 5.1.1(postcss@8.4.31) - postcss-minify-params: 5.1.4(postcss@8.4.31) - postcss-minify-selectors: 5.2.1(postcss@8.4.31) - postcss-normalize-charset: 5.1.0(postcss@8.4.31) - postcss-normalize-display-values: 5.1.0(postcss@8.4.31) - postcss-normalize-positions: 5.1.1(postcss@8.4.31) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.31) - postcss-normalize-string: 5.1.0(postcss@8.4.31) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.31) - postcss-normalize-unicode: 5.1.1(postcss@8.4.31) - postcss-normalize-url: 5.1.0(postcss@8.4.31) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.31) - postcss-ordered-values: 5.1.3(postcss@8.4.31) - postcss-reduce-initial: 5.1.2(postcss@8.4.31) - postcss-reduce-transforms: 5.1.0(postcss@8.4.31) - postcss-svgo: 5.1.0(postcss@8.4.31) - postcss-unique-selectors: 5.1.1(postcss@8.4.31) - - cssnano-utils@3.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - cssnano@5.1.15(postcss@8.4.31): - dependencies: - cssnano-preset-default: 5.2.14(postcss@8.4.31) - lilconfig: 2.1.0 - postcss: 8.4.31 - yaml: 1.10.2 - - csso@4.2.0: - dependencies: - css-tree: 1.1.3 - cssstyle@4.6.0: dependencies: '@asamuzakjp/css-color': 3.2.0 @@ -10381,8 +8404,6 @@ snapshots: dataloader@1.4.0: {} - de-indent@1.0.2: {} - debug@3.2.7: dependencies: ms: 2.1.3 @@ -10395,23 +8416,12 @@ snapshots: dependencies: ms: 2.1.3 - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - decimal.js@10.6.0: {} - decode-uri-component@0.2.2: {} - deep-eql@5.0.2: {} deep-is@0.1.4: {} - deepmerge@4.3.1: {} - defaults@1.0.4: dependencies: clone: 1.0.4 @@ -10470,30 +8480,12 @@ snapshots: dom-accessibility-api@0.5.16: {} - dom-serializer@1.4.1: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - dom-walk@0.1.2: {} - domelementtype@2.3.0: {} - - domhandler@4.3.1: - dependencies: - domelementtype: 2.3.0 - dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 - domutils@2.8.0: - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - dotenv-expand@11.0.7: dependencies: dotenv: 16.4.7 @@ -10555,8 +8547,6 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 - entities@2.2.0: {} - entities@4.5.0: {} entities@6.0.0: {} @@ -10785,15 +8775,6 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint@8.57.0): - dependencies: - confusing-browser-globals: 1.0.11 - eslint: 8.57.0 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0) - object.assign: 4.1.7 - object.entries: 1.1.9 - semver: 6.3.1 - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0): dependencies: confusing-browser-globals: 1.0.11 @@ -10803,14 +8784,6 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 - eslint-config-airbnb-typescript@17.1.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6))(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint@8.57.0): - dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0) - eslint-config-airbnb-typescript@17.1.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2) @@ -10819,17 +8792,6 @@ snapshots: eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0) - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.37.5(eslint@8.57.0))(eslint@8.57.0): - dependencies: - eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) - eslint-plugin-react: 7.37.5(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) - object.assign: 4.1.7 - object.entries: 1.1.9 - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.37.5(eslint@8.57.0))(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -10853,16 +8815,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -10889,35 +8841,6 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 @@ -10947,16 +8870,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6): - dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - optionalDependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6) - transitivePeerDependencies: - - supports-color - - typescript - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.7.2) @@ -11004,15 +8917,6 @@ snapshots: optionalDependencies: eslint-config-prettier: 8.10.0(eslint@8.57.0) - eslint-plugin-prettier@5.4.0(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.6.2): - dependencies: - eslint: 8.57.0 - prettier: 2.6.2 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.6 - optionalDependencies: - eslint-config-prettier: 8.10.0(eslint@8.57.0) - eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -11047,14 +8951,6 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-testing-library@5.11.1(eslint@8.57.0)(typescript@5.1.6): - dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - transitivePeerDependencies: - - supports-color - - typescript - eslint-plugin-testing-library@5.11.1(eslint@8.57.0)(typescript@5.7.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.7.2) @@ -11068,17 +8964,6 @@ snapshots: '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - eslint-plugin-typescript-sort-keys@2.3.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6): - dependencies: - '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - eslint: 8.57.0 - json-schema: 0.4.0 - natural-compare-lite: 1.4.0 - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - eslint-plugin-typescript-sort-keys@2.3.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2): dependencies: '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.0)(typescript@5.7.2) @@ -11086,20 +8971,9 @@ snapshots: eslint: 8.57.0 json-schema: 0.4.0 natural-compare-lite: 1.4.0 - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - eslint-plugin-vue@10.1.0(eslint@8.57.0)(vue-eslint-parser@10.1.3(eslint@8.57.0)): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.0) - eslint: 8.57.0 - natural-compare: 1.4.0 - nth-check: 2.1.1 - postcss-selector-parser: 6.1.2 - semver: 7.7.3 - vue-eslint-parser: 10.1.3(eslint@8.57.0) - xml-name-validator: 4.0.0 + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color eslint-scope@5.1.1: dependencies: @@ -11252,8 +9126,6 @@ snapshots: etag@1.8.1: {} - eventemitter3@4.0.7: {} - evp_bytestokey@1.0.3: dependencies: md5.js: 1.3.5 @@ -11316,10 +9188,6 @@ snapshots: fast-sha256@1.3.0: {} - fast-uri@3.0.6: {} - - fastest-levenshtein@1.0.16: {} - fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -11344,8 +9212,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - filter-obj@1.1.0: {} - finalhandler@2.1.0: dependencies: debug: 4.4.3 @@ -11421,12 +9287,6 @@ snapshots: fs-constants@1.0.0: {} - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -11542,24 +9402,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@8.1.0: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - - global-modules@2.0.0: - dependencies: - global-prefix: 3.0.0 - - global-prefix@3.0.0: - dependencies: - ini: 1.3.8 - kind-of: 6.0.3 - which: 1.3.1 - global@4.4.0: dependencies: min-document: 2.19.1 @@ -11589,16 +9431,12 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globjoin@0.1.4: {} - gopd@1.2.0: {} graceful-fs@4.2.11: {} graphemer@1.4.0: {} - hard-rejection@2.1.0: {} - has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -11642,20 +9480,12 @@ snapshots: dependencies: function-bind: 1.1.2 - he@1.2.0: {} - hmac-drbg@1.0.1: dependencies: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hosted-git-info@2.8.9: {} - - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 @@ -11666,10 +9496,6 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - html-escaper@2.0.2: {} - - html-tags@3.3.1: {} - http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -11710,16 +9536,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - ieee754@1.2.1: {} ignore@5.3.2: {} - immutable@4.3.7: {} - immutable@5.1.2: optional: true @@ -11728,14 +9548,10 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-lazy@4.0.0: {} - import-meta-resolve@3.1.1: {} imurmurhash@0.1.4: {} - indent-string@4.0.0: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -11773,19 +9589,11 @@ snapshots: dependencies: has-bigints: 1.1.0 - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-boolean-object@1.2.2: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-builtin-module@3.2.1: - dependencies: - builtin-modules: 3.3.0 - is-callable@1.2.7: {} is-core-module@2.16.1: @@ -11829,8 +9637,6 @@ snapshots: is-map@2.0.3: {} - is-module@1.0.0: {} - is-negative-zero@2.0.3: {} is-number-object@1.1.1: @@ -11842,18 +9648,10 @@ snapshots: is-path-inside@3.0.3: {} - is-plain-obj@1.1.0: {} - - is-plain-object@5.0.0: {} - is-potential-custom-element-name@1.0.1: {} is-promise@4.0.0: {} - is-reference@1.2.1: - dependencies: - '@types/estree': 1.0.8 - is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -11913,27 +9711,6 @@ snapshots: isexe@2.0.0: {} - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@5.0.6: - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.1 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.1.7: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -11969,12 +9746,8 @@ snapshots: jju@1.4.0: {} - jose@4.15.9: {} - jose@5.10.0: {} - jose@5.3.0: {} - jose@6.0.11: {} js-beautify@1.15.4: @@ -12056,8 +9829,6 @@ snapshots: json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: {} - json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -12074,12 +9845,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -12091,10 +9856,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kind-of@6.0.3: {} - - known-css-properties@0.26.0: {} - language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -12201,8 +9962,6 @@ snapshots: lightningcss-win32-x64-msvc: 1.31.1 optional: true - lilconfig@2.1.0: {} - lines-and-columns@1.2.4: {} lines-and-columns@2.0.3: {} @@ -12221,20 +9980,10 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.isempty@4.4.0: {} - - lodash.memoize@4.1.2: {} - lodash.merge@4.6.2: {} lodash.startcase@4.4.0: {} - lodash.truncate@4.4.2: {} - - lodash.uniq@4.5.0: {} - - lodash@4.17.23: {} - log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -12248,18 +9997,12 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.1.0: {} - lru-cache@11.2.4: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - lucide-react@0.294.0(react@19.2.4): dependencies: react: 19.2.4 @@ -12270,59 +10013,24 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - magicast@0.3.5: - dependencies: - '@babel/parser': 7.27.2 - '@babel/types': 7.27.1 - source-map-js: 1.2.1 - make-dir@3.1.0: dependencies: semver: 6.3.1 - make-dir@4.0.0: - dependencies: - semver: 7.7.2 - - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - math-intrinsics@1.1.0: {} - mathml-tag-names@2.1.3: {} - md5.js@1.3.5: dependencies: hash-base: 3.1.2 inherits: 2.0.4 safe-buffer: 5.2.1 - mdn-data@2.0.14: {} - - mdn-data@2.0.30: {} - mdn-data@2.12.2: {} media-typer@1.1.0: {} memory-cache@0.2.0: {} - meow@9.0.0: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - merge-descriptors@2.0.0: {} merge2@1.4.1: {} @@ -12355,10 +10063,6 @@ snapshots: dependencies: dom-walk: 0.1.2 - min-indent@1.0.1: {} - - mini-svg-data-uri@1.4.4: {} - minimalistic-assert@1.0.1: {} minimalistic-crypto-utils@1.0.1: {} @@ -12371,10 +10075,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.2 - minimatch@9.0.1: dependencies: brace-expansion: 2.0.2 @@ -12387,12 +10087,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - minimist@1.2.8: {} minipass@7.1.2: {} @@ -12414,8 +10108,6 @@ snapshots: ms@2.1.3: {} - muggle-string@0.4.1: {} - nanoid@3.3.11: {} natural-compare-lite@1.4.0: {} @@ -12464,34 +10156,12 @@ snapshots: dependencies: abbrev: 2.0.0 - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.10 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.16.1 - semver: 7.7.3 - validate-npm-package-license: 3.0.4 - - normalize-path@3.0.0: {} - normalize-range@0.1.2: {} - normalize-url@6.1.0: {} - npm-run-path@4.0.1: dependencies: path-key: 3.1.1 - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - nwsapi@2.2.23: {} nx@20.8.1: @@ -12636,8 +10306,6 @@ snapshots: dependencies: p-map: 2.1.0 - p-finally@1.0.0: {} - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -12656,15 +10324,6 @@ snapshots: p-map@2.1.0: {} - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -12703,8 +10362,6 @@ snapshots: parseurl@1.3.3: {} - path-browserify@1.0.1: {} - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -12720,7 +10377,7 @@ snapshots: path-scurry@2.0.0: dependencies: - lru-cache: 11.1.0 + lru-cache: 11.2.4 minipass: 7.1.2 path-to-regexp@8.2.0: {} @@ -12780,197 +10437,6 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-calc@8.2.4(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.1.2 - postcss-value-parser: 4.2.0 - - postcss-colormin@5.3.1(postcss@8.4.31): - dependencies: - browserslist: 4.24.5 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-convert-values@5.1.3(postcss@8.4.31): - dependencies: - browserslist: 4.24.5 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-discard-comments@5.1.2(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - postcss-discard-duplicates@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - postcss-discard-empty@5.1.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - postcss-discard-overridden@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - postcss-media-query-parser@0.2.3: {} - - postcss-merge-longhand@5.1.7(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.31) - - postcss-merge-rules@5.1.4(postcss@8.4.31): - dependencies: - browserslist: 4.24.5 - caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-selector-parser: 6.1.2 - - postcss-minify-font-values@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-minify-gradients@5.1.1(postcss@8.4.31): - dependencies: - colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-minify-params@5.1.4(postcss@8.4.31): - dependencies: - browserslist: 4.24.5 - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-minify-selectors@5.2.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.1.2 - - postcss-modules-extract-imports@3.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - postcss-modules-local-by-default@4.2.0(postcss@8.4.31): - dependencies: - icss-utils: 5.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-selector-parser: 7.1.0 - postcss-value-parser: 4.2.0 - - postcss-modules-scope@3.2.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 7.1.0 - - postcss-modules-values@4.0.0(postcss@8.4.31): - dependencies: - icss-utils: 5.1.0(postcss@8.4.31) - postcss: 8.4.31 - - postcss-normalize-charset@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - - postcss-normalize-display-values@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-normalize-positions@5.1.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-normalize-repeat-style@5.1.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-normalize-string@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-normalize-timing-functions@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-normalize-unicode@5.1.1(postcss@8.4.31): - dependencies: - browserslist: 4.24.5 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-normalize-url@5.1.0(postcss@8.4.31): - dependencies: - normalize-url: 6.1.0 - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-normalize-whitespace@5.1.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-ordered-values@5.1.3(postcss@8.4.31): - dependencies: - cssnano-utils: 3.1.0(postcss@8.4.31) - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-reduce-initial@5.1.2(postcss@8.4.31): - dependencies: - browserslist: 4.24.5 - caniuse-api: 3.0.0 - postcss: 8.4.31 - - postcss-reduce-transforms@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - - postcss-resolve-nested-selector@0.1.6: {} - - postcss-safe-parser@6.0.0(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - - postcss-scss@4.0.9(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-selector-parser@7.1.0: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-svgo@5.1.0(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-value-parser: 4.2.0 - svgo: 2.8.0 - - postcss-unique-selectors@5.1.1(postcss@8.4.31): - dependencies: - postcss: 8.4.31 - postcss-selector-parser: 6.1.2 - postcss-value-parser@4.2.0: {} postcss@8.4.31: @@ -12979,18 +10445,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.1: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.3: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -13053,17 +10507,8 @@ snapshots: quansync@0.2.10: {} - query-string@7.1.3: - dependencies: - decode-uri-component: 0.2.2 - filter-obj: 1.1.0 - split-on-first: 1.1.0 - strict-uri-encode: 2.0.0 - queue-microtask@1.2.3: {} - quick-lru@4.0.1: {} - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -13105,19 +10550,6 @@ snapshots: react@19.2.4: {} - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - read-yaml-file@2.1.0: dependencies: js-yaml: 4.1.1 @@ -13139,18 +10571,9 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - readdirp@4.1.2: optional: true - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -13230,66 +10653,6 @@ snapshots: hash-base: 3.1.2 inherits: 2.0.4 - rollup-plugin-dts@6.1.0(rollup@4.32.0)(typescript@5.1.6): - dependencies: - magic-string: 0.30.17 - rollup: 4.32.0 - typescript: 5.1.6 - optionalDependencies: - '@babel/code-frame': 7.27.1 - - rollup-plugin-polyfill-node@0.13.0(rollup@4.32.0): - dependencies: - '@rollup/plugin-inject': 5.0.5(rollup@4.32.0) - rollup: 4.32.0 - - rollup-plugin-styles@4.0.0(rollup@4.32.0): - dependencies: - '@rollup/pluginutils': 4.2.1 - '@types/cssnano': 5.1.3(postcss@8.4.31) - cosmiconfig: 7.1.0 - cssnano: 5.1.15(postcss@8.4.31) - fs-extra: 10.1.0 - icss-utils: 5.1.0(postcss@8.4.31) - mime-types: 2.1.35 - p-queue: 6.6.2 - postcss: 8.4.31 - postcss-modules-extract-imports: 3.1.0(postcss@8.4.31) - postcss-modules-local-by-default: 4.2.0(postcss@8.4.31) - postcss-modules-scope: 3.2.1(postcss@8.4.31) - postcss-modules-values: 4.0.0(postcss@8.4.31) - postcss-value-parser: 4.2.0 - query-string: 7.1.3 - resolve: 1.22.10 - rollup: 4.32.0 - source-map-js: 1.2.1 - tslib: 2.8.1 - - rollup@4.32.0: - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.32.0 - '@rollup/rollup-android-arm64': 4.32.0 - '@rollup/rollup-darwin-arm64': 4.32.0 - '@rollup/rollup-darwin-x64': 4.32.0 - '@rollup/rollup-freebsd-arm64': 4.32.0 - '@rollup/rollup-freebsd-x64': 4.32.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.32.0 - '@rollup/rollup-linux-arm-musleabihf': 4.32.0 - '@rollup/rollup-linux-arm64-gnu': 4.32.0 - '@rollup/rollup-linux-arm64-musl': 4.32.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.32.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.32.0 - '@rollup/rollup-linux-riscv64-gnu': 4.32.0 - '@rollup/rollup-linux-s390x-gnu': 4.32.0 - '@rollup/rollup-linux-x64-gnu': 4.32.0 - '@rollup/rollup-linux-x64-musl': 4.32.0 - '@rollup/rollup-win32-arm64-msvc': 4.32.0 - '@rollup/rollup-win32-ia32-msvc': 4.32.0 - '@rollup/rollup-win32-x64-msvc': 4.32.0 - fsevents: 2.3.3 - rollup@4.50.0: dependencies: '@types/estree': 1.0.8 @@ -13452,12 +10815,6 @@ snapshots: sass-embedded-win32-x64: 1.92.1 optional: true - sass@1.75.0: - dependencies: - chokidar: 3.6.0 - immutable: 4.3.7 - source-map-js: 1.2.1 - sass@1.92.1: dependencies: chokidar: 4.0.3 @@ -13481,8 +10838,6 @@ snapshots: dependencies: get-random-values: 3.0.0 - semver@5.7.2: {} - semver@6.3.1: {} semver@7.7.2: {} @@ -13632,12 +10987,6 @@ snapshots: slash@3.0.0: {} - slice-ansi@4.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -13648,31 +10997,14 @@ snapshots: source-map@0.5.7: {} - source-map@0.6.1: {} + source-map@0.6.1: + optional: true spawndamnit@3.0.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.21 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 - - spdx-license-ids@3.0.21: {} - - split-on-first@1.1.0: {} - - stable@0.1.8: {} - stackback@0.0.2: {} statuses@2.0.1: {} @@ -13691,8 +11023,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - strict-uri-encode@2.0.0: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -13775,107 +11105,13 @@ snapshots: strip-bom@4.0.0: {} - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - strip-json-comments@3.1.1: {} - style-search@0.1.0: {} - styled-jsx@5.1.6(react@19.2.4): dependencies: client-only: 0.0.1 react: 19.2.4 - stylehacks@5.1.1(postcss@8.4.31): - dependencies: - browserslist: 4.24.5 - postcss: 8.4.31 - postcss-selector-parser: 6.1.2 - - stylelint-config-recommended-scss@8.0.0(postcss@8.5.6)(stylelint@15.1.0(typescript@5.1.6)): - dependencies: - postcss-scss: 4.0.9(postcss@8.5.6) - stylelint: 15.1.0(typescript@5.1.6) - stylelint-config-recommended: 9.0.0(stylelint@15.1.0(typescript@5.1.6)) - stylelint-scss: 4.7.0(stylelint@15.1.0(typescript@5.1.6)) - optionalDependencies: - postcss: 8.5.6 - - stylelint-config-recommended@9.0.0(stylelint@15.1.0(typescript@5.1.6)): - dependencies: - stylelint: 15.1.0(typescript@5.1.6) - - stylelint-config-standard-scss@6.1.0(postcss@8.5.6)(stylelint@15.1.0(typescript@5.1.6)): - dependencies: - stylelint: 15.1.0(typescript@5.1.6) - stylelint-config-recommended-scss: 8.0.0(postcss@8.5.6)(stylelint@15.1.0(typescript@5.1.6)) - stylelint-config-standard: 29.0.0(stylelint@15.1.0(typescript@5.1.6)) - optionalDependencies: - postcss: 8.5.6 - - stylelint-config-standard@29.0.0(stylelint@15.1.0(typescript@5.1.6)): - dependencies: - stylelint: 15.1.0(typescript@5.1.6) - stylelint-config-recommended: 9.0.0(stylelint@15.1.0(typescript@5.1.6)) - - stylelint-scss@4.7.0(stylelint@15.1.0(typescript@5.1.6)): - dependencies: - postcss-media-query-parser: 0.2.3 - postcss-resolve-nested-selector: 0.1.6 - postcss-selector-parser: 6.1.2 - postcss-value-parser: 4.2.0 - stylelint: 15.1.0(typescript@5.1.6) - - stylelint@15.1.0(typescript@5.1.6): - dependencies: - '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) - '@csstools/css-tokenizer': 2.4.1 - '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - '@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.1.2) - balanced-match: 2.0.0 - colord: 2.9.3 - cosmiconfig: 8.3.6(typescript@5.1.6) - css-functions-list: 3.2.3 - css-tree: 2.3.1 - debug: 4.4.1 - fast-glob: 3.3.3 - fastest-levenshtein: 1.0.16 - file-entry-cache: 6.0.1 - global-modules: 2.0.0 - globby: 11.1.0 - globjoin: 0.1.4 - html-tags: 3.3.1 - ignore: 5.3.2 - import-lazy: 4.0.0 - imurmurhash: 0.1.4 - is-plain-object: 5.0.0 - known-css-properties: 0.26.0 - mathml-tag-names: 2.1.3 - meow: 9.0.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - picocolors: 1.1.1 - postcss: 8.5.3 - postcss-media-query-parser: 0.2.3 - postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 6.0.0(postcss@8.5.3) - postcss-selector-parser: 6.1.2 - postcss-value-parser: 4.2.0 - resolve-from: 5.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - style-search: 0.1.0 - supports-hyperlinks: 2.3.0 - svg-tags: 1.0.0 - table: 6.9.0 - v8-compile-cache: 2.4.0 - write-file-atomic: 5.0.1 - transitivePeerDependencies: - - supports-color - - typescript - stylis@4.2.0: {} supports-color@7.2.0: @@ -13887,25 +11123,8 @@ snapshots: has-flag: 4.0.0 optional: true - supports-hyperlinks@2.3.0: - dependencies: - has-flag: 4.0.0 - supports-color: 7.2.0 - supports-preserve-symlinks-flag@1.0.0: {} - svg-tags@1.0.0: {} - - svgo@2.8.0: - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 4.3.0 - css-tree: 1.1.3 - csso: 4.2.0 - picocolors: 1.1.1 - stable: 0.1.8 - symbol-tree@3.2.4: {} sync-child-process@1.0.2: @@ -13916,20 +11135,8 @@ snapshots: sync-message-port@1.1.3: optional: true - synckit@0.11.6: - dependencies: - '@pkgr/core': 0.2.4 - tabbable@6.2.0: {} - table@6.9.0: - dependencies: - ajv: 8.17.1 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - tailwind-merge@3.3.0: {} tailwindcss@4.1.8: {} @@ -13962,12 +11169,6 @@ snapshots: source-map-support: 0.5.21 optional: true - test-exclude@7.0.1: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.5.0 - minimatch: 9.0.5 - text-table@0.2.0: {} tiny-invariant@1.3.3: {} @@ -14040,12 +11241,6 @@ snapshots: dependencies: punycode: 2.3.1 - trim-newlines@3.0.1: {} - - ts-api-utils@1.4.3(typescript@5.1.6): - dependencies: - typescript: 5.1.6 - ts-api-utils@1.4.3(typescript@5.7.2): dependencies: typescript: 5.7.2 @@ -14069,15 +11264,8 @@ snapshots: tslib@1.14.1: {} - tslib@2.6.2: {} - tslib@2.8.1: {} - tsutils@3.21.0(typescript@5.1.6): - dependencies: - tslib: 1.14.1 - typescript: 5.1.6 - tsutils@3.21.0(typescript@5.7.2): dependencies: tslib: 1.14.1 @@ -14096,14 +11284,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.18.1: {} - type-fest@0.20.2: {} - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -14153,8 +11335,6 @@ snapshots: transitivePeerDependencies: - supports-color - typescript@5.1.6: {} - typescript@5.7.2: {} typescript@5.8.3: {} @@ -14168,8 +11348,6 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - undici-types@5.26.5: {} - undici-types@6.21.0: {} undici-types@7.16.0: @@ -14179,8 +11357,6 @@ snapshots: universalify@0.1.2: {} - universalify@2.0.1: {} - unpipe@1.0.0: {} update-browserslist-db@1.1.3(browserslist@4.24.5): @@ -14201,43 +11377,15 @@ snapshots: uuid@11.1.0: {} - v8-compile-cache@2.4.0: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - varint@6.0.0: optional: true vary@1.1.2: {} - vite-node@3.0.8(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.4.1(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@3.1.3(@types/node@22.15.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.4.1(@types/node@22.15.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) @@ -14258,7 +11406,7 @@ snapshots: vite-node@3.1.3(@types/node@22.15.30)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.4.1(@types/node@22.15.30)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) @@ -14276,25 +11424,6 @@ snapshots: - tsx - yaml - vite@6.4.1(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): - dependencies: - esbuild: 0.25.9 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.50.0 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 20.12.7 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.31.1 - sass: 1.75.0 - sass-embedded: 1.92.1 - terser: 5.39.2 - tsx: 4.21.0 - yaml: 2.8.0 - vite@6.4.1(@types/node@22.15.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): dependencies: esbuild: 0.25.9 @@ -14352,64 +11481,6 @@ snapshots: tsx: 4.21.0 yaml: 2.8.0 - vite@7.1.12(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): - dependencies: - esbuild: 0.25.9 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.50.0 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 20.12.7 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.31.1 - sass: 1.75.0 - sass-embedded: 1.92.1 - terser: 5.39.2 - tsx: 4.21.0 - yaml: 2.8.0 - - vitest@3.0.8(@types/node@20.12.7)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): - dependencies: - '@vitest/expect': 3.0.8 - '@vitest/mocker': 3.0.8(vite@6.4.1(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0)) - '@vitest/pretty-format': 3.1.3 - '@vitest/runner': 3.0.8 - '@vitest/snapshot': 3.0.8 - '@vitest/spy': 3.0.8 - '@vitest/utils': 3.0.8 - chai: 5.2.0 - debug: 4.4.1 - expect-type: 1.2.1 - magic-string: 0.30.17 - pathe: 2.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.0.2 - tinyrainbow: 2.0.0 - vite: 6.4.1(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - vite-node: 3.0.8(@types/node@20.12.7)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.75.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 20.12.7 - jsdom: 27.4.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@3.1.3(@types/node@22.15.3)(@vitest/browser@3.1.3)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0): dependencies: '@vitest/expect': 3.1.3 @@ -14420,14 +11491,14 @@ snapshots: '@vitest/spy': 3.1.3 '@vitest/utils': 3.1.3 chai: 5.2.0 - debug: 4.4.1 + debug: 4.4.3 expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.0.2 tinyrainbow: 2.0.0 vite: 6.4.1(@types/node@22.15.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) @@ -14461,14 +11532,14 @@ snapshots: '@vitest/spy': 3.1.3 '@vitest/utils': 3.1.3 chai: 5.2.0 - debug: 4.4.1 + debug: 4.4.3 expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.0.2 tinyrainbow: 2.0.0 vite: 6.4.1(@types/node@22.15.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) @@ -14502,14 +11573,14 @@ snapshots: '@vitest/spy': 3.1.3 '@vitest/utils': 3.1.3 chai: 5.2.0 - debug: 4.4.1 + debug: 4.4.3 expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.0.2 tinyrainbow: 2.0.0 vite: 6.4.1(@types/node@22.15.30)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.92.1)(sass@1.92.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.0) @@ -14533,51 +11604,17 @@ snapshots: - tsx - yaml - vscode-uri@3.1.0: {} - vue-component-type-helpers@2.2.10: {} - vue-eslint-parser@10.1.3(eslint@8.57.0): - dependencies: - debug: 4.4.3 - eslint: 8.57.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - lodash: 4.17.23 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - - vue-eslint-parser@9.4.3(eslint@8.57.0): - dependencies: - debug: 4.4.3 - eslint: 8.57.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - lodash: 4.17.23 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - - vue-tsc@2.2.2(typescript@5.1.6): - dependencies: - '@volar/typescript': 2.4.14 - '@vue/language-core': 2.2.2(typescript@5.1.6) - typescript: 5.1.6 - - vue@3.5.13(typescript@5.1.6): + vue@3.5.13(typescript@5.7.2): dependencies: '@vue/compiler-dom': 3.5.13 '@vue/compiler-sfc': 3.5.13 '@vue/runtime-dom': 3.5.13 - '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.1.6)) + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.2)) '@vue/shared': 3.5.13 optionalDependencies: - typescript: 5.1.6 + typescript: 5.7.2 w3c-xmlserializer@5.0.0: dependencies: @@ -14657,10 +11694,6 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -14686,17 +11719,10 @@ snapshots: wrappy@1.0.2: {} - write-file-atomic@5.0.1: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 4.1.0 - ws@8.18.2: {} ws@8.19.0: {} - xml-name-validator@4.0.0: {} - xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} @@ -14705,16 +11731,12 @@ snapshots: yallist@3.1.1: {} - yallist@4.0.0: {} - yallist@5.0.0: {} yaml@1.10.2: {} yaml@2.8.0: {} - yargs-parser@20.2.9: {} - yargs-parser@21.1.1: {} yargs@17.7.2: From 70661ea8a9d7a1c25607fb7cf6a5d7d53237a345 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Thu, 12 Mar 2026 15:22:02 +0530 Subject: [PATCH 02/22] (Phase 2) feat: add multiple providers for enhanced functionality - Introduced BrandingProvider to manage branding preferences and themes. - Added FlowMetaProvider for fetching and managing flow metadata. - Implemented FlowProvider to handle authentication flow UI state. - Created I18nProvider for internationalization support and translation management. - Developed OrganizationProvider to manage organization state and switching. - Added ThemeProvider for theme management and detection. - Implemented UserProvider to manage user profile state and updates. --- packages/vue/src/composables/useBranding.ts | 59 ++++ packages/vue/src/composables/useFlow.ts | 61 ++++ packages/vue/src/composables/useFlowMeta.ts | 57 ++++ packages/vue/src/composables/useI18n.ts | 61 ++++ .../vue/src/composables/useOrganization.ts | 57 ++++ packages/vue/src/composables/useTheme.ts | 59 ++++ packages/vue/src/composables/useUser.ts | 60 ++++ packages/vue/src/index.ts | 40 ++- packages/vue/src/keys.ts | 46 ++- packages/vue/src/models/contexts.ts | 212 ++++++++++++ .../vue/src/providers/BrandingProvider.ts | 99 ++++++ .../vue/src/providers/FlowMetaProvider.ts | 173 ++++++++++ packages/vue/src/providers/FlowProvider.ts | 162 ++++++++++ packages/vue/src/providers/I18nProvider.ts | 305 ++++++++++++++++++ .../vue/src/providers/OrganizationProvider.ts | 121 +++++++ packages/vue/src/providers/ThemeProvider.ts | 251 ++++++++++++++ packages/vue/src/providers/UserProvider.ts | 78 +++++ 17 files changed, 1898 insertions(+), 3 deletions(-) create mode 100644 packages/vue/src/composables/useBranding.ts create mode 100644 packages/vue/src/composables/useFlow.ts create mode 100644 packages/vue/src/composables/useFlowMeta.ts create mode 100644 packages/vue/src/composables/useI18n.ts create mode 100644 packages/vue/src/composables/useOrganization.ts create mode 100644 packages/vue/src/composables/useTheme.ts create mode 100644 packages/vue/src/composables/useUser.ts create mode 100644 packages/vue/src/providers/BrandingProvider.ts create mode 100644 packages/vue/src/providers/FlowMetaProvider.ts create mode 100644 packages/vue/src/providers/FlowProvider.ts create mode 100644 packages/vue/src/providers/I18nProvider.ts create mode 100644 packages/vue/src/providers/OrganizationProvider.ts create mode 100644 packages/vue/src/providers/ThemeProvider.ts create mode 100644 packages/vue/src/providers/UserProvider.ts diff --git a/packages/vue/src/composables/useBranding.ts b/packages/vue/src/composables/useBranding.ts new file mode 100644 index 000000000..1a58fb113 --- /dev/null +++ b/packages/vue/src/composables/useBranding.ts @@ -0,0 +1,59 @@ +/** + * 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 {inject} from 'vue'; +import {BRANDING_KEY} from '../keys'; +import type {BrandingContextValue} from '../models/contexts'; + +/** + * Composable for accessing branding preference data. + * + * Must be called inside a component that is a descendant of ``. + * + * @returns {BrandingContextValue} The branding context with preferences, theme, and fetch operations. + * @throws {Error} If called outside of ``. + * + * @example + * ```vue + * + * + * + * ``` + */ +const useBranding = (): BrandingContextValue => { + const context = inject(BRANDING_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useBranding() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useBranding; diff --git a/packages/vue/src/composables/useFlow.ts b/packages/vue/src/composables/useFlow.ts new file mode 100644 index 000000000..ba7a35387 --- /dev/null +++ b/packages/vue/src/composables/useFlow.ts @@ -0,0 +1,61 @@ +/** + * 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 {inject} from 'vue'; +import {FLOW_KEY} from '../keys'; +import type {FlowContextValue} from '../models/contexts'; + +/** + * Composable for managing authentication flow UI state. + * + * Must be called inside a component that is a descendant of ``. + * + * @returns {FlowContextValue} The flow context with step navigation, messages, and loading state. + * @throws {Error} If called outside of ``. + * + * @example + * ```vue + * + * + * + * ``` + */ +const useFlow = (): FlowContextValue => { + const context = inject(FLOW_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useFlow() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useFlow; diff --git a/packages/vue/src/composables/useFlowMeta.ts b/packages/vue/src/composables/useFlowMeta.ts new file mode 100644 index 000000000..0ce6e841c --- /dev/null +++ b/packages/vue/src/composables/useFlowMeta.ts @@ -0,0 +1,57 @@ +/** + * 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 {inject} from 'vue'; +import {FLOW_META_KEY} from '../keys'; +import type {FlowMetaContextValue} from '../models/contexts'; + +/** + * Composable for accessing flow metadata. + * + * Must be called inside a component that is a descendant of ``. + * + * @returns {FlowMetaContextValue} The flow meta context with metadata, loading state, and language switching. + * @throws {Error} If called outside of ``. + * + * @example + * ```vue + * + * ``` + */ +const useFlowMeta = (): FlowMetaContextValue => { + const context = inject(FLOW_META_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useFlowMeta() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useFlowMeta; diff --git a/packages/vue/src/composables/useI18n.ts b/packages/vue/src/composables/useI18n.ts new file mode 100644 index 000000000..42ce81114 --- /dev/null +++ b/packages/vue/src/composables/useI18n.ts @@ -0,0 +1,61 @@ +/** + * 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 {inject} from 'vue'; +import {I18N_KEY} from '../keys'; +import type {I18nContextValue} from '../models/contexts'; + +/** + * Composable for accessing internationalization utilities. + * + * Must be called inside a component that is a descendant of ``. + * + * @returns {I18nContextValue} The i18n context with translation function, language management, and bundle injection. + * @throws {Error} If called outside of ``. + * + * @example + * ```vue + * + * + * + * ``` + */ +const useI18n = (): I18nContextValue => { + const context = inject(I18N_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useI18n() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useI18n; diff --git a/packages/vue/src/composables/useOrganization.ts b/packages/vue/src/composables/useOrganization.ts new file mode 100644 index 000000000..14a0bc103 --- /dev/null +++ b/packages/vue/src/composables/useOrganization.ts @@ -0,0 +1,57 @@ +/** + * 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 {inject} from 'vue'; +import {ORGANIZATION_KEY} from '../keys'; +import type {OrganizationContextValue} from '../models/contexts'; + +/** + * Composable for accessing organization data and operations. + * + * Must be called inside a component that is a descendant of ``. + * + * @returns {OrganizationContextValue} The organization context. + * @throws {Error} If called outside of ``. + * + * @example + * ```vue + * + * ``` + */ +const useOrganization = (): OrganizationContextValue => { + const context = inject(ORGANIZATION_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useOrganization() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useOrganization; diff --git a/packages/vue/src/composables/useTheme.ts b/packages/vue/src/composables/useTheme.ts new file mode 100644 index 000000000..ef682a2ff --- /dev/null +++ b/packages/vue/src/composables/useTheme.ts @@ -0,0 +1,59 @@ +/** + * 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 {inject} from 'vue'; +import {THEME_KEY} from '../keys'; +import type {ThemeContextValue} from '../models/contexts'; + +/** + * Composable for accessing and controlling the active theme. + * + * Must be called inside a component that is a descendant of ``. + * + * @returns {ThemeContextValue} The theme context with the active theme, color scheme, and toggle function. + * @throws {Error} If called outside of ``. + * + * @example + * ```vue + * + * + * + * ``` + */ +const useTheme = (): ThemeContextValue => { + const context = inject(THEME_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useTheme() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useTheme; diff --git a/packages/vue/src/composables/useUser.ts b/packages/vue/src/composables/useUser.ts new file mode 100644 index 000000000..1f648838e --- /dev/null +++ b/packages/vue/src/composables/useUser.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. + */ + +import {inject} from 'vue'; +import {USER_KEY} from '../keys'; +import type {UserContextValue} from '../models/contexts'; + +/** + * Composable for accessing user profile data. + * + * Must be called inside a component that is a descendant of ``. + * + * @returns {UserContextValue} The user context containing profile, schemas, and update operations. + * @throws {Error} If called outside of ``. + * + * @example + * ```vue + * + * + * + * ``` + */ +const useUser = (): UserContextValue => { + const context = inject(USER_KEY); + + if (!context) { + throw new Error( + '[Asgardeo] useUser() was called outside of . ' + + 'Make sure to install the AsgardeoPlugin or wrap your app with .', + ); + } + + return context; +}; + +export default useUser; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 6f1ef2301..fcaaa104e 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -22,18 +22,54 @@ export {default as AsgardeoPlugin} from './plugins/AsgardeoPlugin'; // ── Components ── export {default as AsgardeoProvider} from './components/AsgardeoProvider'; +// ── Providers ── +export {default as BrandingProvider} from './providers/BrandingProvider'; +export {default as FlowMetaProvider} from './providers/FlowMetaProvider'; +export {default as FlowProvider} from './providers/FlowProvider'; +export {default as I18nProvider} from './providers/I18nProvider'; +export {default as OrganizationProvider} from './providers/OrganizationProvider'; +export {default as ThemeProvider} from './providers/ThemeProvider'; +export {default as UserProvider} from './providers/UserProvider'; + // ── Composables ── export {default as useAsgardeo} from './composables/useAsgardeo'; +export {default as useBranding} from './composables/useBranding'; +export {default as useFlow} from './composables/useFlow'; +export {default as useFlowMeta} from './composables/useFlowMeta'; +export {default as useI18n} from './composables/useI18n'; +export {default as useOrganization} from './composables/useOrganization'; +export {default as useTheme} from './composables/useTheme'; +export {default as useUser} from './composables/useUser'; // ── Client ── export {default as AsgardeoVueClient} from './AsgardeoVueClient'; // ── Keys ── -export {ASGARDEO_KEY} from './keys'; +export { + ASGARDEO_KEY, + BRANDING_KEY, + FLOW_KEY, + FLOW_META_KEY, + I18N_KEY, + ORGANIZATION_KEY, + THEME_KEY, + USER_KEY, +} from './keys'; // ── Models / Types ── export type {AsgardeoVueConfig} from './models/config'; -export type {AsgardeoContext} from './models/contexts'; +export type { + AsgardeoContext, + BrandingContextValue, + FlowContextValue, + FlowMessage, + FlowMetaContextValue, + FlowStep, + I18nContextValue, + OrganizationContextValue, + ThemeContextValue, + UserContextValue, +} from './models/contexts'; // ── Re-exports from @asgardeo/browser ── export { diff --git a/packages/vue/src/keys.ts b/packages/vue/src/keys.ts index a94e7b470..f339a02dd 100644 --- a/packages/vue/src/keys.ts +++ b/packages/vue/src/keys.ts @@ -17,9 +17,53 @@ */ import type {InjectionKey} from 'vue'; -import type {AsgardeoContext} from './models/contexts'; +import type { + AsgardeoContext, + BrandingContextValue, + FlowContextValue, + FlowMetaContextValue, + I18nContextValue, + OrganizationContextValue, + ThemeContextValue, + UserContextValue, +} from './models/contexts'; /** * Injection key for the core Asgardeo authentication context. */ export const ASGARDEO_KEY: InjectionKey = Symbol('asgardeo'); + +/** + * Injection key for the User context (profile, schemas, update operations). + */ +export const USER_KEY: InjectionKey = Symbol('asgardeo-user'); + +/** + * Injection key for the Organization context (list, current org, switching). + */ +export const ORGANIZATION_KEY: InjectionKey = Symbol('asgardeo-organization'); + +/** + * Injection key for the Flow context (embedded flow UI state). + */ +export const FLOW_KEY: InjectionKey = Symbol('asgardeo-flow'); + +/** + * Injection key for the FlowMeta context (server-driven flow metadata). + */ +export const FLOW_META_KEY: InjectionKey = Symbol('asgardeo-flow-meta'); + +/** + * Injection key for the Theme context (color scheme, CSS variables, toggle). + */ +export const THEME_KEY: InjectionKey = Symbol('asgardeo-theme'); + +/** + * Injection key for the Branding context (branding preferences from server). + */ +export const BRANDING_KEY: InjectionKey = Symbol('asgardeo-branding'); + +/** + * Injection key for the I18n context (translation function, language switching). + */ +export const I18N_KEY: InjectionKey = Symbol('asgardeo-i18n'); diff --git a/packages/vue/src/models/contexts.ts b/packages/vue/src/models/contexts.ts index 109c68f23..74a413216 100644 --- a/packages/vue/src/models/contexts.ts +++ b/packages/vue/src/models/contexts.ts @@ -18,14 +18,24 @@ import type {Ref} from 'vue'; import type { + AllOrganizationsApiResponse, + BrandingPreference, + CreateOrganizationPayload, + FlowMetadataResponse, HttpRequestConfig, HttpResponse, IdToken, Organization, + Schema, SignInOptions, + Theme, TokenExchangeRequestConfig, TokenResponse, + UpdateMeProfileConfig, + User, + UserProfile, } from '@asgardeo/browser'; +import type {I18nBundle} from '@asgardeo/i18n'; import type {AsgardeoVueConfig} from './config'; import type AsgardeoVueClient from '../AsgardeoVueClient'; @@ -99,3 +109,205 @@ export interface AsgardeoContext { storage: AsgardeoVueConfig['storage'] | undefined; platform: AsgardeoVueConfig['platform'] | undefined; } + +// ───────────────────────────────────────────────────────────────────────────── +// User Context +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Shape of the User context exposed by `useUser()`. + */ +export interface UserContextValue { + /** The flattened user profile (top-level attribute map). */ + flattenedProfile: Readonly>; + /** The raw nested user profile from the SCIM2/ME endpoint. */ + profile: Readonly>; + /** Refetch the user profile from the server. */ + revalidateProfile: () => Promise; + /** The SCIM2 schemas describing the user profile attributes. */ + schemas: Readonly>; + /** + * Update the user profile. Accepts the standard SCIM2 patch request config. + */ + updateProfile: ( + requestConfig: UpdateMeProfileConfig, + sessionId?: string, + ) => Promise<{data: {user: User}; error: string; success: boolean}>; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Organization Context +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Shape of the Organization context exposed by `useOrganization()`. + */ +export interface OrganizationContextValue { + /** Optional function to create a new sub-organization. */ + createOrganization?: (payload: CreateOrganizationPayload, sessionId: string) => Promise; + /** The organization the user is currently operating in. */ + currentOrganization: Readonly>; + /** Last error message from an organization operation, if any. */ + error: Readonly>; + /** Fetch all organizations (paginated). */ + getAllOrganizations: () => Promise; + /** Whether an organization operation is in-flight. */ + isLoading: Readonly>; + /** The list of organizations the signed-in user is a member of. */ + myOrganizations: Readonly>; + /** Re-fetch the user's organization list from the server. */ + revalidateMyOrganizations: () => Promise; + /** Switch to the given organization (performs token exchange). */ + switchOrganization: (organization: Organization) => Promise; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Flow Context +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Types of authentication flow steps that can be displayed. + */ +export type FlowStep = { + canGoBack?: boolean; + id: string; + metadata?: Record; + subtitle?: string; + title: string; + type: 'signin' | 'signup' | 'organization-signin' | 'forgot-password' | 'reset-password' | 'verify-email' | 'mfa'; +} | null; + +/** + * A message that can be displayed inside an authentication flow UI. + */ +export interface FlowMessage { + dismissible?: boolean; + id?: string; + message: string; + type: 'success' | 'error' | 'warning' | 'info'; +} + +/** + * Shape of the Flow context exposed by `useFlow()`. + */ +export interface FlowContextValue { + addMessage: (message: FlowMessage) => void; + clearMessages: () => void; + currentStep: Readonly>; + error: Readonly>; + isLoading: Readonly>; + messages: Readonly>; + navigateToFlow: ( + flowType: NonNullable['type'], + options?: {metadata?: Record; subtitle?: string; title?: string}, + ) => void; + onGoBack: Readonly void) | undefined>>; + removeMessage: (messageId: string) => void; + reset: () => void; + setCurrentStep: (step: FlowStep) => void; + setError: (error: string | null) => void; + setIsLoading: (loading: boolean) => void; + setOnGoBack: (callback?: () => void) => void; + setShowBackButton: (show: boolean) => void; + setSubtitle: (subtitle?: string) => void; + setTitle: (title: string) => void; + showBackButton: Readonly>; + subtitle: Readonly>; + title: Readonly>; +} + +// ───────────────────────────────────────────────────────────────────────────── +// FlowMeta Context +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Shape of the FlowMeta context exposed by `useFlowMeta()`. + */ +export interface FlowMetaContextValue { + /** Error from the flow metadata fetch, if any. */ + error: Readonly>; + /** Manually re-fetch flow metadata from the server. */ + fetchFlowMeta: () => Promise; + /** Whether the flow metadata is currently being fetched. */ + isLoading: Readonly>; + /** The fetched `FlowMetadataResponse`, or `null` while loading or on error. */ + meta: Readonly>; + /** + * Fetch flow metadata for the given language and activate it in the i18n system. + * Use this to switch the UI language at runtime. + */ + switchLanguage: (language: string) => Promise; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Theme Context +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Shape of the Theme context exposed by `useTheme()`. + */ +export interface ThemeContextValue { + /** Error from the branding theme fetch, if any. */ + brandingError: Readonly>; + /** The current color scheme ('light' | 'dark'). */ + colorScheme: Readonly>; + /** The text direction for the UI. */ + direction: Readonly>; + /** Whether the theme inherits from Asgardeo branding preferences. */ + inheritFromBranding: boolean; + /** Whether the branding theme is currently loading. */ + isBrandingLoading: Readonly>; + /** The resolved Theme object used by all styled components. */ + theme: Readonly>; + /** Toggle between light and dark mode. */ + toggleTheme: () => void; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Branding Context +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Shape of the Branding context exposed by `useBranding()`. + */ +export interface BrandingContextValue { + /** The active theme from the branding preference ('light' | 'dark'), or null. */ + activeTheme: Readonly>; + /** The raw branding preference data from the server. */ + brandingPreference: Readonly>; + /** Error from the branding fetch, if any. */ + error: Readonly>; + /** Trigger a branding preference fetch (deduplicated). */ + fetchBranding: () => Promise; + /** Whether the branding preference is currently loading. */ + isLoading: Readonly>; + /** Force a fresh branding preference fetch (bypasses dedup). */ + refetch: () => Promise; + /** The transformed `Theme` object derived from the branding preference. */ + theme: Readonly>; +} + +// ───────────────────────────────────────────────────────────────────────────── +// I18n Context +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Shape of the I18n context exposed by `useI18n()`. + */ +export interface I18nContextValue { + /** All available i18n bundles (default + injected + user-provided). */ + bundles: Readonly>>; + /** The current language code (e.g., 'en-US'). */ + currentLanguage: Readonly>; + /** The fallback language code. */ + fallbackLanguage: string; + /** + * Inject additional bundles into the i18n system (e.g., from flow metadata). + * Injected bundles take precedence over defaults but are overridden by prop-provided bundles. + */ + injectBundles: (bundles: Record) => void; + /** Change the current language. */ + setLanguage: (language: string) => void; + /** Translate a key with optional named parameters. */ + t: (key: string, params?: Record) => string; +} diff --git a/packages/vue/src/providers/BrandingProvider.ts b/packages/vue/src/providers/BrandingProvider.ts new file mode 100644 index 000000000..66224ffb2 --- /dev/null +++ b/packages/vue/src/providers/BrandingProvider.ts @@ -0,0 +1,99 @@ +/** + * 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 {BrandingPreference, Theme, transformBrandingPreferenceToTheme} from '@asgardeo/browser'; +import {defineComponent, h, provide, readonly, shallowReadonly, ref, watch, type PropType, type Ref} from 'vue'; +import {BRANDING_KEY} from '../keys'; +import type {BrandingContextValue} from '../models/contexts'; + +/** + * BrandingProvider manages branding preference state and makes branding data + * available to child components via `useBranding()`. + * + * It receives branding preferences from a parent component (typically + * ``) and transforms them into `Theme` objects. + * + * @internal — This provider is mounted automatically by ``. + */ +const BrandingProvider = defineComponent({ + name: 'BrandingProvider', + props: { + /** Whether branding is enabled. When false the context provides null. */ + enabled: {type: Boolean, default: true}, + /** Force a specific theme mode, overriding the one declared in branding. */ + forceTheme: {type: String as PropType<'light' | 'dark'>, default: undefined}, + /** Raw branding preference received from the parent. */ + brandingPreference: {type: Object as PropType, default: null}, + /** Loading state from the parent. */ + isLoading: {type: Boolean, default: false}, + /** Error state from the parent. */ + error: {type: Object as PropType, default: null}, + /** Re-fetch callback from the parent (bypasses dedup). */ + refetch: {type: Function as PropType<() => Promise>, default: undefined}, + }, + setup(props, {slots}) { + const theme: Ref = ref(null); + const activeTheme: Ref<'light' | 'dark' | null> = ref(null); + + // Process branding preference whenever it changes + const processBranding = () => { + if (!props.enabled || !props.brandingPreference) { + theme.value = null; + activeTheme.value = null; + return; + } + + const activeThemeFromBranding = (props.brandingPreference as any)?.preference?.theme?.activeTheme as + | string + | undefined; + if (activeThemeFromBranding) { + const mode = activeThemeFromBranding.toLowerCase(); + activeTheme.value = mode === 'light' || mode === 'dark' ? mode : null; + } else { + activeTheme.value = null; + } + + const transformedTheme = transformBrandingPreferenceToTheme(props.brandingPreference, props.forceTheme); + theme.value = transformedTheme; + }; + + watch(() => [props.brandingPreference, props.forceTheme, props.enabled], processBranding, {immediate: true}); + + const fetchBranding = async (): Promise => { + if (props.refetch) { + await props.refetch(); + } + }; + + const context: BrandingContextValue = { + activeTheme: readonly(activeTheme), + brandingPreference: readonly(ref(props.brandingPreference)) as Readonly>, + error: readonly(ref(props.error)) as Readonly>, + fetchBranding, + isLoading: readonly(ref(props.isLoading)) as Readonly>, + refetch: props.refetch ?? fetchBranding, + theme: shallowReadonly(theme), + }; + + provide(BRANDING_KEY, context); + + return () => h('div', {style: 'display:contents'}, slots['default']?.()); + }, +}); + +export default BrandingProvider; diff --git a/packages/vue/src/providers/FlowMetaProvider.ts b/packages/vue/src/providers/FlowMetaProvider.ts new file mode 100644 index 000000000..76519383c --- /dev/null +++ b/packages/vue/src/providers/FlowMetaProvider.ts @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2026, 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 {FlowMetadataResponse, FlowMetaType, getFlowMetaV2} from '@asgardeo/browser'; +import {I18nBundle, TranslationBundleConstants} from '@asgardeo/i18n'; +import {defineComponent, h, inject, onMounted, provide, readonly, shallowReadonly, ref, watch, type Ref} from 'vue'; +import {ASGARDEO_KEY, FLOW_META_KEY, I18N_KEY} from '../keys'; +import type {FlowMetaContextValue} from '../models/contexts'; + +/** + * FlowMetaProvider fetches flow metadata from the `GET /flow/meta` endpoint + * (v2 API) and makes it available via `useFlowMeta()`. + * + * It also integrates with `I18nProvider` so that server-side translations + * from the metadata are automatically injected into the i18n system. + * + * @internal — This provider is mounted automatically by ``. + */ +const FlowMetaProvider = defineComponent({ + name: 'FlowMetaProvider', + props: { + /** + * When false the provider skips fetching and provides null meta. + * @default true + */ + enabled: {type: Boolean, default: true}, + }, + setup(props, {slots}) { + const asgardeoContext = inject(ASGARDEO_KEY); + const i18nContext = inject(I18N_KEY, null); + + const meta: Ref = ref(null); + const isLoading: Ref = ref(false); + const error: Ref = ref(null); + const pendingLanguage: Ref = ref(null); + + const baseUrl = asgardeoContext?.baseUrl; + const applicationId = asgardeoContext?.applicationId; + + const fetchFlowMeta = async (): Promise => { + if (!props.enabled) { + meta.value = null; + return; + } + + isLoading.value = true; + error.value = null; + + try { + const result: FlowMetadataResponse = await getFlowMetaV2({ + baseUrl, + id: applicationId, + type: FlowMetaType.App, + }); + meta.value = result; + } catch (err: unknown) { + error.value = err instanceof Error ? err : new Error(String(err)); + } finally { + isLoading.value = false; + } + }; + + const switchLanguage = async (language: string): Promise => { + if (!props.enabled) return; + + isLoading.value = true; + error.value = null; + + try { + const result: FlowMetadataResponse = await getFlowMetaV2({ + baseUrl, + id: applicationId, + language, + type: FlowMetaType.App, + }); + + // Inject translations before switching language so the i18n state is updated + if (result.i18n?.translations && i18nContext?.injectBundles) { + const flatTranslations: Record = {}; + Object.entries(result.i18n.translations).forEach(([namespace, keys]) => { + Object.entries(keys as Record).forEach(([key, value]) => { + flatTranslations[`${namespace}.${key}`] = value; + }); + }); + const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle; + i18nContext.injectBundles({[language]: bundle}); + } + + // Defer setLanguage so that injectBundles' state is committed first + pendingLanguage.value = language; + meta.value = result; + } catch (err: unknown) { + error.value = err instanceof Error ? err : new Error(String(err)); + } finally { + isLoading.value = false; + } + }; + + // After injectBundles + pendingLanguage are committed, call setLanguage + watch(pendingLanguage, (lang) => { + if (lang && i18nContext?.setLanguage) { + i18nContext.setLanguage(lang); + pendingLanguage.value = null; + } + }); + + // When meta loads with i18n translations, inject them into the i18n system + watch( + () => meta.value?.i18n?.translations, + (translations) => { + if (!translations || !i18nContext?.injectBundles) return; + + const metaLanguage: string = + (meta.value?.i18n as any)?.language || TranslationBundleConstants.FALLBACK_LOCALE; + + const flatTranslations: Record = {}; + Object.entries(translations).forEach(([namespace, keys]) => { + Object.entries(keys as Record).forEach(([key, value]) => { + flatTranslations[`${namespace}.${key}`] = value; + }); + }); + + const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle; + const bundlesToInject: Record = {[metaLanguage]: bundle}; + + const currentLang = i18nContext.currentLanguage.value; + const fallbackLang = i18nContext.fallbackLanguage; + + if (currentLang && currentLang !== metaLanguage) { + bundlesToInject[currentLang] = bundle; + } + if (fallbackLang && fallbackLang !== metaLanguage) { + bundlesToInject[fallbackLang] = bundle; + } + + i18nContext.injectBundles(bundlesToInject); + }, + ); + + onMounted(() => { + fetchFlowMeta(); + }); + + const context: FlowMetaContextValue = { + error: readonly(error), + fetchFlowMeta, + isLoading: readonly(isLoading), + meta: shallowReadonly(meta), + switchLanguage, + }; + + provide(FLOW_META_KEY, context); + + return () => h('div', {style: 'display:contents'}, slots['default']?.()); + }, +}); + +export default FlowMetaProvider; diff --git a/packages/vue/src/providers/FlowProvider.ts b/packages/vue/src/providers/FlowProvider.ts new file mode 100644 index 000000000..f476260e2 --- /dev/null +++ b/packages/vue/src/providers/FlowProvider.ts @@ -0,0 +1,162 @@ +/** + * 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 {defineComponent, h, provide, readonly, shallowReadonly, ref, type PropType, type Ref} from 'vue'; +import {FLOW_KEY} from '../keys'; +import type {FlowContextValue, FlowMessage, FlowStep} from '../models/contexts'; + +/** + * FlowProvider manages authentication flow UI state and makes it available + * via `useFlow()`. + * + * It tracks the current step, title, subtitle, messages, loading state, + * and back-navigation callback for embedded authentication flows. + * + * @internal — This provider is mounted automatically by ``. + */ +const FlowProvider = defineComponent({ + name: 'FlowProvider', + props: { + /** Initial step to start with. */ + initialStep: {type: Object as PropType, default: null}, + /** Initial title. */ + initialTitle: {type: String, default: ''}, + /** Initial subtitle. */ + initialSubtitle: {type: String, default: undefined}, + /** Callback when the flow step changes. */ + onFlowChange: {type: Function as PropType<(step: FlowStep) => void>, default: undefined}, + }, + setup(props, {slots}) { + const currentStep: Ref = ref(props.initialStep ?? null); + const title: Ref = ref(props.initialTitle ?? ''); + const subtitle: Ref = ref(props.initialSubtitle); + const messages: Ref = ref([]); + const error: Ref = ref(null); + const isLoading: Ref = ref(false); + const showBackButton: Ref = ref(false); + const onGoBack: Ref<(() => void) | undefined> = ref(undefined); + + const setCurrentStep = (step: FlowStep): void => { + currentStep.value = step; + if (step) { + title.value = step.title; + subtitle.value = step.subtitle; + showBackButton.value = step.canGoBack ?? false; + } + props.onFlowChange?.(step); + }; + + const setTitle = (newTitle: string): void => { + title.value = newTitle; + }; + + const setSubtitle = (newSubtitle?: string): void => { + subtitle.value = newSubtitle; + }; + + const setError = (newError: string | null): void => { + error.value = newError; + }; + + const setIsLoading = (loading: boolean): void => { + isLoading.value = loading; + }; + + const setShowBackButton = (show: boolean): void => { + showBackButton.value = show; + }; + + const setOnGoBack = (callback?: () => void): void => { + onGoBack.value = callback; + }; + + const addMessage = (message: FlowMessage): void => { + const messageWithId: FlowMessage = { + ...message, + id: message.id ?? `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + }; + messages.value = [...messages.value, messageWithId]; + }; + + const removeMessage = (messageId: string): void => { + messages.value = messages.value.filter((msg) => msg.id !== messageId); + }; + + const clearMessages = (): void => { + messages.value = []; + }; + + const reset = (): void => { + currentStep.value = props.initialStep ?? null; + title.value = props.initialTitle ?? ''; + subtitle.value = props.initialSubtitle; + messages.value = []; + error.value = null; + isLoading.value = false; + showBackButton.value = false; + onGoBack.value = undefined; + }; + + const navigateToFlow = ( + flowType: NonNullable['type'], + options?: {metadata?: Record; subtitle?: string; title?: string}, + ): void => { + const stepId = `${flowType}-${Date.now()}`; + const step: NonNullable = { + canGoBack: flowType !== 'signin', + id: stepId, + metadata: options?.metadata, + subtitle: options?.subtitle, + title: options?.title ?? '', + type: flowType, + }; + setCurrentStep(step); + clearMessages(); + error.value = null; + }; + + const context: FlowContextValue = { + addMessage, + clearMessages, + currentStep: readonly(currentStep), + error: readonly(error), + isLoading: readonly(isLoading), + messages: shallowReadonly(messages), + navigateToFlow, + onGoBack: readonly(onGoBack), + removeMessage, + reset, + setCurrentStep, + setError, + setIsLoading, + setOnGoBack, + setShowBackButton, + setSubtitle, + setTitle, + showBackButton: readonly(showBackButton), + subtitle: readonly(subtitle), + title: readonly(title), + }; + + provide(FLOW_KEY, context); + + return () => h('div', {style: 'display:contents'}, slots['default']?.()); + }, +}); + +export default FlowProvider; diff --git a/packages/vue/src/providers/I18nProvider.ts b/packages/vue/src/providers/I18nProvider.ts new file mode 100644 index 000000000..705b39658 --- /dev/null +++ b/packages/vue/src/providers/I18nProvider.ts @@ -0,0 +1,305 @@ +/** + * 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 {deepMerge, I18nPreferences, I18nStorageStrategy, createPackageComponentLogger} from '@asgardeo/browser'; +import { + I18nBundle, + I18nTranslations, + TranslationBundleConstants, + getDefaultI18nBundles, + normalizeTranslations, +} from '@asgardeo/i18n'; +import {computed, defineComponent, h, provide, readonly, ref, watch, type PropType, type Ref} from 'vue'; +import {I18N_KEY} from '../keys'; +import type {I18nContextValue} from '../models/contexts'; + +const logger = createPackageComponentLogger('@asgardeo/vue', 'I18nProvider'); + +const DEFAULT_STORAGE_KEY = 'asgardeo-i18n-language'; +const DEFAULT_URL_PARAM = 'lang'; + +// ── Storage helpers ────────────────────────────────────────────────────────── + +const deriveRootDomain = (hostname: string): string => { + const parts = hostname.split('.'); + return parts.length > 1 ? parts.slice(-2).join('.') : hostname; +}; + +const getCookie = (name: string): string | null => { + if (typeof document === 'undefined') return null; + const match = document.cookie.match( + new RegExp(`(?:^|; )${name.replace(/([.*+?^${}()|[\]\\])/g, '\\$1')}=([^;]*)`), + ); + return match ? decodeURIComponent(match[1]) : null; +}; + +const setCookie = (name: string, value: string, domain: string): void => { + if (typeof document === 'undefined') return; + const maxAge = 365 * 24 * 60 * 60; + const secure = typeof window !== 'undefined' && window.location.protocol === 'https:' ? '; Secure' : ''; + document.cookie = + `${encodeURIComponent(name)}=${encodeURIComponent(value)}` + + `; Max-Age=${maxAge}` + + `; Path=/` + + `; Domain=${domain}` + + `; SameSite=Lax${secure}`; +}; + +interface StorageAdapter { + read: () => string | null; + write: (language: string) => void; +} + +const createStorageAdapter = (strategy: I18nStorageStrategy, key: string, cookieDomain?: string): StorageAdapter => { + switch (strategy) { + case 'cookie': + return { + read: () => getCookie(key), + write: (language: string) => { + const domain = + cookieDomain ?? (typeof window !== 'undefined' ? deriveRootDomain(window.location.hostname) : ''); + if (domain) setCookie(key, language, domain); + }, + }; + case 'localStorage': + return { + read: () => { + if (typeof window === 'undefined' || !window.localStorage) return null; + try { + return window.localStorage.getItem(key); + } catch { + return null; + } + }, + write: (language: string) => { + if (typeof window === 'undefined' || !window.localStorage) return; + try { + window.localStorage.setItem(key, language); + } catch { + logger.warn('Failed to persist language preference to localStorage.'); + } + }, + }; + case 'none': + default: + return {read: () => null, write: () => {}}; + } +}; + +const detectUrlParamLanguage = (paramName: string): string | null => { + if (typeof window === 'undefined') return null; + try { + return new URLSearchParams(window.location.search).get(paramName); + } catch { + return null; + } +}; + +const detectBrowserLanguage = (): string => { + if (typeof window !== 'undefined' && window.navigator) { + return window.navigator.language || TranslationBundleConstants.FALLBACK_LOCALE; + } + return TranslationBundleConstants.FALLBACK_LOCALE; +}; + +// ── Component ──────────────────────────────────────────────────────────────── + +/** + * I18nProvider manages internationalization state and provides translation + * functions to child components via `useI18n()`. + * + * Language resolution order: + * URL param → stored preference → browser language → fallback locale + * + * @internal — This provider is mounted automatically by ``. + */ +const I18nProvider = defineComponent({ + name: 'I18nProvider', + props: { + /** i18n preferences passed down from the AsgardeoProvider config. */ + preferences: {type: Object as PropType, default: undefined}, + }, + setup(props, {slots}) { + const defaultBundles: Record = getDefaultI18nBundles(); + + const storageStrategy: I18nStorageStrategy = props.preferences?.storageStrategy ?? 'cookie'; + const storageKey: string = props.preferences?.storageKey ?? DEFAULT_STORAGE_KEY; + const urlParamConfig: string | false = + props.preferences?.urlParam === undefined ? DEFAULT_URL_PARAM : props.preferences.urlParam; + + const resolvedCookieDomain: string | undefined = + storageStrategy === 'cookie' + ? props.preferences?.cookieDomain ?? + (typeof window !== 'undefined' ? deriveRootDomain(window.location.hostname) : undefined) + : undefined; + + const storage = createStorageAdapter(storageStrategy, storageKey, resolvedCookieDomain); + + // Determine initial language + const determineInitialLanguage = (): string => { + if (props.preferences?.language) return props.preferences.language; + if (urlParamConfig !== false) { + const urlLanguage = detectUrlParamLanguage(urlParamConfig); + if (urlLanguage) { + storage.write(urlLanguage); + return urlLanguage; + } + } + const storedLanguage = storage.read(); + if (storedLanguage) return storedLanguage; + const browserLanguage = detectBrowserLanguage(); + if (browserLanguage) return browserLanguage; + return props.preferences?.fallbackLanguage || TranslationBundleConstants.FALLBACK_LOCALE; + }; + + const currentLanguage: Ref = ref(determineInitialLanguage()); + + // Bundles injected at runtime (e.g., from flow metadata translations). + const injectedBundles: Ref> = ref({}); + + const injectBundles = (newBundles: Record): void => { + const merged: Record = {...injectedBundles.value}; + Object.entries(newBundles).forEach(([key, bundle]) => { + const normalizedTranslations: I18nTranslations = normalizeTranslations( + bundle.translations as unknown as Record>, + ); + if (merged[key]) { + merged[key] = { + ...merged[key], + translations: deepMerge(merged[key].translations, normalizedTranslations), + }; + } else { + merged[key] = {...bundle, translations: normalizedTranslations}; + } + }); + injectedBundles.value = merged; + }; + + /** + * Merge bundles: defaults → injected (meta) → prop-provided (highest priority) + */ + const mergedBundles = computed>(() => { + const merged: Record = {}; + + // 1. Default bundles + Object.entries(defaultBundles).forEach(([key, bundle]) => { + const languageKey = key.replace('_', '-'); + merged[languageKey] = bundle; + }); + + // 2. Injected bundles (from flow metadata) + Object.entries(injectedBundles.value).forEach(([key, bundle]) => { + const normalizedTranslations: I18nTranslations = normalizeTranslations( + bundle.translations as unknown as Record>, + ); + if (merged[key]) { + merged[key] = { + ...merged[key], + translations: deepMerge(merged[key].translations, normalizedTranslations), + }; + } else { + merged[key] = {...bundle, translations: normalizedTranslations}; + } + }); + + // 3. User-provided bundles (highest priority) + if (props.preferences?.bundles) { + Object.entries(props.preferences.bundles).forEach(([key, userBundle]) => { + const normalizedTranslations: I18nTranslations = normalizeTranslations( + userBundle.translations as unknown as Record>, + ); + if (merged[key]) { + merged[key] = { + ...merged[key], + metadata: userBundle.metadata + ? {...merged[key].metadata, ...userBundle.metadata} + : merged[key].metadata, + translations: deepMerge(merged[key].translations, normalizedTranslations), + }; + } else { + merged[key] = {...userBundle, translations: normalizedTranslations}; + } + }); + } + + return merged; + }); + + const fallbackLanguage: string = + props.preferences?.fallbackLanguage || TranslationBundleConstants.FALLBACK_LOCALE; + + // Persist language changes to storage + watch(currentLanguage, (lang: string) => { + storage.write(lang); + }); + + const t = (key: string, params?: Record): string => { + let translation: string | undefined; + + const currentBundle = mergedBundles.value[currentLanguage.value]; + if (currentBundle?.translations[key]) { + translation = currentBundle.translations[key]; + } + + if (!translation && currentLanguage.value !== fallbackLanguage) { + const fallbackBundle = mergedBundles.value[fallbackLanguage]; + if (fallbackBundle?.translations[key]) { + translation = fallbackBundle.translations[key]; + } + } + + if (!translation) { + translation = key; + } + + if (params && Object.keys(params).length > 0) { + return Object.entries(params).reduce( + (acc, [paramKey, paramValue]) => acc.replace(new RegExp(`\\{${paramKey}\\}`, 'g'), String(paramValue)), + translation, + ); + } + + return translation; + }; + + const setLanguage = (language: string): void => { + if (mergedBundles.value[language]) { + currentLanguage.value = language; + } else { + logger.warn( + `Language '${language}' is not available. Available languages: ${Object.keys(mergedBundles.value).join(', ')}`, + ); + } + }; + + const context: I18nContextValue = { + bundles: readonly(mergedBundles), + currentLanguage: readonly(currentLanguage), + fallbackLanguage, + injectBundles, + setLanguage, + t, + }; + + provide(I18N_KEY, context); + + return () => h('div', {style: 'display:contents'}, slots['default']?.()); + }, +}); + +export default I18nProvider; diff --git a/packages/vue/src/providers/OrganizationProvider.ts b/packages/vue/src/providers/OrganizationProvider.ts new file mode 100644 index 000000000..effda0a7a --- /dev/null +++ b/packages/vue/src/providers/OrganizationProvider.ts @@ -0,0 +1,121 @@ +/** + * 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 { + AllOrganizationsApiResponse, + AsgardeoRuntimeError, + CreateOrganizationPayload, + Organization, + TokenResponse, +} from '@asgardeo/browser'; +import {defineComponent, h, provide, readonly, ref, type PropType, type Ref} from 'vue'; +import {ORGANIZATION_KEY} from '../keys'; +import type {OrganizationContextValue} from '../models/contexts'; + +/** + * OrganizationProvider manages organization state and makes it available + * via `useOrganization()`. + * + * @internal — This provider is mounted automatically by ``. + */ +const OrganizationProvider = defineComponent({ + name: 'OrganizationProvider', + props: { + /** Optional factory for creating a new sub-organization. */ + createOrganization: { + type: Function as PropType<(payload: CreateOrganizationPayload, sessionId: string) => Promise>, + default: undefined, + }, + /** The organization the user is currently operating in. */ + currentOrganization: {type: Object as PropType, default: null}, + /** Callback to fetch all organizations (paginated). */ + getAllOrganizations: { + type: Function as PropType<() => Promise>, + default: undefined, + }, + /** The list of organizations the user is a member of. */ + myOrganizations: {type: Array as PropType, default: () => []}, + /** Callback when an error occurs. */ + onError: {type: Function as PropType<(error: string) => void>, default: undefined}, + /** Callback that performs the actual organization switch (token exchange). */ + onOrganizationSwitch: { + type: Function as PropType<(organization: Organization) => Promise>, + default: undefined, + }, + /** Callback to re-fetch the user's organization list. */ + revalidateMyOrganizations: { + type: Function as PropType<() => Promise>, + default: async () => [], + }, + }, + setup(props, {slots}) { + const isLoading: Ref = ref(false); + const error: Ref = ref(null); + + const switchOrganization = async (organization: Organization): Promise => { + if (!props.onOrganizationSwitch) { + throw new AsgardeoRuntimeError( + 'onOrganizationSwitch callback is required', + 'OrganizationProvider-SwitchError-001', + 'vue', + 'The onOrganizationSwitch callback must be provided to handle organization switching.', + ); + } + + isLoading.value = true; + error.value = null; + + try { + await props.onOrganizationSwitch(organization); + } catch (switchError) { + const errorMessage = switchError instanceof Error ? switchError.message : 'Failed to switch organization'; + error.value = errorMessage; + if (props.onError) { + props.onError(errorMessage); + } + throw switchError; + } finally { + isLoading.value = false; + } + }; + + const getAllOrgs = async (): Promise => { + if (props.getAllOrganizations) { + return props.getAllOrganizations(); + } + return {organizations: []}; + }; + + const context: OrganizationContextValue = { + createOrganization: props.createOrganization, + currentOrganization: readonly(ref(props.currentOrganization)) as Readonly>, + error: readonly(error), + getAllOrganizations: getAllOrgs, + isLoading: readonly(isLoading), + myOrganizations: readonly(ref(props.myOrganizations)) as Readonly>, + revalidateMyOrganizations: props.revalidateMyOrganizations, + switchOrganization, + }; + + provide(ORGANIZATION_KEY, context); + + return () => h('div', {style: 'display:contents'}, slots['default']?.()); + }, +}); + +export default OrganizationProvider; diff --git a/packages/vue/src/providers/ThemeProvider.ts b/packages/vue/src/providers/ThemeProvider.ts new file mode 100644 index 000000000..51cb1247f --- /dev/null +++ b/packages/vue/src/providers/ThemeProvider.ts @@ -0,0 +1,251 @@ +/** + * 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 { + Theme, + ThemeConfig, + ThemeMode, + ThemePreferences, + RecursivePartial, + BrowserThemeDetection, + DEFAULT_THEME, + createTheme, + detectThemeMode, + createClassObserver, + createMediaQueryListener, + createPackageComponentLogger, +} from '@asgardeo/browser'; +import { + computed, + defineComponent, + h, + inject, + onBeforeUnmount, + onMounted, + provide, + readonly, + shallowReadonly, + ref, + watch, + type PropType, + type Ref, +} from 'vue'; +import {BRANDING_KEY, THEME_KEY} from '../keys'; +import type {ThemeContextValue} from '../models/contexts'; + +const logger = createPackageComponentLogger('@asgardeo/vue', 'ThemeProvider'); + +/** + * ThemeProvider manages theme state and provides it to child components via `useTheme()`. + * + * It supports: + * - Fixed color schemes (`light` | `dark`) + * - System preference detection (`system`) + * - CSS-class-based detection (`class`) + * - Branding-driven mode (`branding`) — inherits the active theme from `BrandingProvider` + * - Merging server branding theme with local overrides + * - CSS variable injection onto `document.documentElement` + * + * @example + * ```vue + * + * + * + * ``` + */ +const ThemeProvider = defineComponent({ + name: 'ThemeProvider', + props: { + /** Theme detection configuration (for 'class' or 'system' mode). */ + detection: {type: Object as PropType, default: () => ({})}, + /** Whether to inherit theme from Asgardeo branding preference. */ + inheritFromBranding: {type: Boolean, default: true as ThemePreferences['inheritFromBranding']}, + /** + * The theme mode: + * - `'light'` | `'dark'`: Fixed color scheme. + * - `'system'`: Follows OS preference. + * - `'class'`: Detects theme from CSS classes on ``. + * - `'branding'`: Follows the active theme from branding preference. + */ + mode: { + type: String as PropType, + default: DEFAULT_THEME as ThemeMode | 'branding', + }, + /** Optional partial theme overrides applied on top of the resolved theme. */ + theme: {type: Object as PropType>, default: undefined}, + }, + setup(props, {slots}) { + // Try to consume branding context – it is optional (BrandingProvider may not be mounted) + const brandingContext = inject(BRANDING_KEY, null); + + const initColorScheme = (): 'light' | 'dark' => { + if (props.mode === 'light' || props.mode === 'dark') return props.mode; + if (props.mode === 'branding') return detectThemeMode('system', props.detection); + return detectThemeMode(props.mode as ThemeMode, props.detection); + }; + + const colorScheme: Ref<'light' | 'dark'> = ref(initColorScheme()); + + // Update color scheme when branding's active theme is available + watch( + () => brandingContext?.activeTheme.value, + (brandingActiveTheme) => { + if (!props.inheritFromBranding || !brandingActiveTheme) return; + if (props.mode === 'branding') { + colorScheme.value = brandingActiveTheme; + } else if (props.mode === 'system' && !brandingContext?.isLoading.value) { + colorScheme.value = brandingActiveTheme; + } + }, + ); + + // Warn if inheritFromBranding is true but no BrandingProvider is present + if (props.inheritFromBranding && !brandingContext) { + logger.warn( + 'ThemeProvider: inheritFromBranding is enabled but BrandingProvider is not available. ' + + 'Make sure to wrap your app with BrandingProvider or AsgardeoProvider.', + ); + } + + // Merge branding theme with user-provided overrides + const finalThemeConfig = computed | undefined>(() => { + const themeConfig = props.theme; + const brandingTheme = props.inheritFromBranding ? brandingContext?.theme.value : null; + + if (!brandingTheme) return themeConfig; + + const brandingThemeConfig: RecursivePartial = { + borderRadius: brandingTheme.borderRadius, + colors: brandingTheme.colors, + components: brandingTheme.components, + images: brandingTheme.images, + shadows: brandingTheme.shadows, + spacing: brandingTheme.spacing, + }; + + return { + ...brandingThemeConfig, + ...themeConfig, + borderRadius: {...brandingThemeConfig.borderRadius, ...themeConfig?.borderRadius}, + colors: {...brandingThemeConfig.colors, ...themeConfig?.colors}, + components: {...brandingThemeConfig.components, ...themeConfig?.components}, + images: {...brandingThemeConfig.images, ...themeConfig?.images}, + shadows: {...brandingThemeConfig.shadows, ...themeConfig?.shadows}, + spacing: {...brandingThemeConfig.spacing, ...themeConfig?.spacing}, + }; + }); + + const resolvedTheme = computed(() => + createTheme(finalThemeConfig.value, colorScheme.value === 'dark'), + ); + + const direction = computed<'ltr' | 'rtl'>( + () => ((finalThemeConfig.value as any)?.direction as 'ltr' | 'rtl') || 'ltr', + ); + + const toggleTheme = (): void => { + colorScheme.value = colorScheme.value === 'light' ? 'dark' : 'light'; + }; + + // Apply CSS variables to DOM + const applyToDom = (theme: Theme) => { + if (typeof document === 'undefined') return; + const root = document.documentElement; + const flat = flattenTheme(theme); + Object.entries(flat).forEach(([key, value]) => { + root.style.setProperty(key, value); + }); + }; + + const flattenTheme = (theme: Theme, prefix = '--asgardeo'): Record => { + const result: Record = {}; + const traverse = (obj: any, path: string) => { + if (typeof obj !== 'object' || obj === null) return; + Object.entries(obj).forEach(([k, v]) => { + const newPath = `${path}-${k}`; + if (typeof v === 'string' || typeof v === 'number') { + result[newPath] = String(v); + } else if (typeof v === 'object') { + traverse(v, newPath); + } + }); + }; + traverse(theme, prefix); + return result; + }; + + watch(resolvedTheme, (theme) => applyToDom(theme), {immediate: true}); + + // Apply direction to document + watch( + direction, + (dir) => { + if (typeof document !== 'undefined') { + document.documentElement.dir = dir; + } + }, + {immediate: true}, + ); + + // Set up automatic theme detection listeners + let classObserver: MutationObserver | null = null; + let mediaQuery: MediaQueryList | null = null; + + const handleThemeChange = (isDark: boolean): void => { + colorScheme.value = isDark ? 'dark' : 'light'; + }; + + onMounted(() => { + if (props.mode === 'branding') return; + + if (props.mode === 'class') { + const targetElement = (props.detection as any).targetElement || document.documentElement; + if (targetElement) { + classObserver = createClassObserver(targetElement, handleThemeChange, props.detection); + } + } else if (props.mode === 'system') { + if (!props.inheritFromBranding || !brandingContext?.activeTheme.value) { + mediaQuery = createMediaQueryListener(handleThemeChange); + } + } + }); + + onBeforeUnmount(() => { + if (classObserver) classObserver.disconnect(); + if (mediaQuery?.removeEventListener) { + mediaQuery.removeEventListener('change', handleThemeChange as any); + } + }); + + const context: ThemeContextValue = { + brandingError: brandingContext?.error ?? readonly(ref(null)), + colorScheme: readonly(colorScheme), + direction: readonly(direction) as Readonly>, + inheritFromBranding: props.inheritFromBranding, + isBrandingLoading: brandingContext?.isLoading ?? readonly(ref(false)), + theme: shallowReadonly(resolvedTheme), + toggleTheme, + }; + + provide(THEME_KEY, context); + + return () => h('div', {style: 'display:contents'}, slots['default']?.()); + }, +}); + +export default ThemeProvider; diff --git a/packages/vue/src/providers/UserProvider.ts b/packages/vue/src/providers/UserProvider.ts new file mode 100644 index 000000000..1cd7d5b99 --- /dev/null +++ b/packages/vue/src/providers/UserProvider.ts @@ -0,0 +1,78 @@ +/** + * 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 {Schema, UpdateMeProfileConfig, User, UserProfile} from '@asgardeo/browser'; +import {defineComponent, h, provide, readonly, ref, type PropType, type Ref} from 'vue'; +import {USER_KEY} from '../keys'; +import type {UserContextValue} from '../models/contexts'; + +/** + * UserProvider manages user profile state and makes it available via `useUser()`. + * + * It is a thin wrapper that receives profile data from a parent (typically + * ``) and surfaces it through the Vue inject system. + * + * @internal — This provider is mounted automatically by ``. + */ +const UserProvider = defineComponent({ + name: 'UserProvider', + props: { + /** Optional callback run after the profile is updated locally. */ + onUpdateProfile: {type: Function as PropType<(payload: User) => void>, default: undefined}, + /** The full user profile data (nested + flat + schemas). */ + profile: {type: Object as PropType, default: null}, + /** Re-fetch the user profile from the server. */ + revalidateProfile: {type: Function as PropType<() => Promise>, default: async () => {}}, + /** Update the user profile via SCIM2 PATCH. */ + updateProfile: { + type: Function as PropType< + ( + requestConfig: UpdateMeProfileConfig, + sessionId?: string, + ) => Promise<{data: {user: User}; error: string; success: boolean}> + >, + default: undefined, + }, + /** The flattened profile (top-level attribute map). */ + flattenedProfile: {type: Object as PropType, default: null}, + /** The SCIM2 schemas describing user profile attributes. */ + schemas: {type: Array as PropType, default: null}, + }, + setup(props, {slots}) { + const context: UserContextValue = { + flattenedProfile: readonly(ref(props.flattenedProfile)) as Readonly>, + profile: readonly(ref(props.profile)) as Readonly>, + revalidateProfile: props.revalidateProfile, + schemas: readonly(ref(props.schemas)) as Readonly>, + updateProfile: + props.updateProfile ?? + (() => + Promise.resolve({ + data: {user: {} as User}, + error: 'updateProfile callback not provided', + success: false, + })), + }; + + provide(USER_KEY, context); + + return () => h('div', {style: 'display:contents'}, slots['default']?.()); + }, +}); + +export default UserProvider; From 4449e349f104e083599c8b2e214950fb213196a0 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Fri, 13 Mar 2026 00:56:35 +0530 Subject: [PATCH 03/22] (Phase 3) feat: add new UI components for primitives including Checkbox, DatePicker, Divider, Icons, Logo, OtpField, PasswordField, Select, Spinner, TextField, and Typography - Introduced Checkbox component for boolean input. - Added DatePicker component for date selection. - Created Divider component for visual separation. - Implemented Icons component with various SVG icons. - Developed Logo component for branding display. - Added OtpField component for one-time password input. - Created PasswordField component with toggle visibility feature. - Implemented Select component for dropdown selections. - Added Spinner component for loading indication. - Developed TextField component for text input. - Introduced Typography component for consistent text styling. Also updated index.ts to export new components. --- .../vue/src/components/AsgardeoProvider.ts | 30 ++- .../components/actions/BaseSignInButton.ts | 77 ++++++ .../components/actions/BaseSignOutButton.ts | 62 +++++ .../components/actions/BaseSignUpButton.ts | 64 +++++ .../src/components/actions/SignInButton.ts | 78 ++++++ .../src/components/actions/SignOutButton.ts | 68 +++++ .../src/components/actions/SignUpButton.ts | 89 +++++++ .../src/components/adapters/FacebookButton.ts | 66 +++++ .../src/components/adapters/GitHubButton.ts | 68 +++++ .../src/components/adapters/GoogleButton.ts | 84 +++++++ .../components/adapters/MicrosoftButton.ts | 63 +++++ packages/vue/src/components/auth/Callback.ts | 148 +++++++++++ .../vue/src/components/control/Loading.ts | 52 ++++ .../vue/src/components/control/SignedIn.ts | 52 ++++ .../vue/src/components/control/SignedOut.ts | 52 ++++ .../src/components/factories/FieldFactory.ts | 237 ++++++++++++++++++ .../components/presentation/AcceptInvite.ts | 67 +++++ .../presentation/BaseAcceptInvite.ts | 139 ++++++++++ .../presentation/BaseCreateOrganization.ts | 118 +++++++++ .../components/presentation/BaseInviteUser.ts | 158 ++++++++++++ .../presentation/BaseLanguageSwitcher.ts | 112 +++++++++ .../presentation/BaseOrganization.ts | 57 +++++ .../presentation/BaseOrganizationList.ts | 80 ++++++ .../presentation/BaseOrganizationProfile.ts | 146 +++++++++++ .../presentation/BaseOrganizationSwitcher.ts | 133 ++++++++++ .../src/components/presentation/BaseSignIn.ts | 132 ++++++++++ .../src/components/presentation/BaseSignUp.ts | 128 ++++++++++ .../src/components/presentation/BaseUser.ts | 43 ++++ .../presentation/BaseUserDropdown.ts | 120 +++++++++ .../presentation/BaseUserProfile.ts | 205 +++++++++++++++ .../presentation/CreateOrganization.ts | 58 +++++ .../src/components/presentation/InviteUser.ts | 56 +++++ .../presentation/LanguageSwitcher.ts | 63 +++++ .../components/presentation/Organization.ts | 50 ++++ .../presentation/OrganizationList.ts | 58 +++++ .../presentation/OrganizationProfile.ts | 52 ++++ .../presentation/OrganizationSwitcher.ts | 53 ++++ .../vue/src/components/presentation/SignIn.ts | 83 ++++++ .../vue/src/components/presentation/SignUp.ts | 70 ++++++ .../vue/src/components/presentation/User.ts | 37 +++ .../components/presentation/UserDropdown.ts | 53 ++++ .../components/presentation/UserProfile.ts | 62 +++++ .../vue/src/components/primitives/Alert.ts | 66 +++++ .../vue/src/components/primitives/Button.ts | 84 +++++++ .../vue/src/components/primitives/Card.ts | 49 ++++ .../vue/src/components/primitives/Checkbox.ts | 64 +++++ .../src/components/primitives/DatePicker.ts | 71 ++++++ .../vue/src/components/primitives/Divider.ts | 55 ++++ .../vue/src/components/primitives/Icons.ts | 92 +++++++ .../vue/src/components/primitives/Logo.ts | 65 +++++ .../vue/src/components/primitives/OtpField.ts | 98 ++++++++ .../components/primitives/PasswordField.ts | 92 +++++++ .../vue/src/components/primitives/Select.ts | 87 +++++++ .../vue/src/components/primitives/Spinner.ts | 73 ++++++ .../src/components/primitives/TextField.ts | 86 +++++++ .../src/components/primitives/Typography.ts | 67 +++++ packages/vue/src/index.ts | 87 +++++++ 57 files changed, 4758 insertions(+), 1 deletion(-) create mode 100644 packages/vue/src/components/actions/BaseSignInButton.ts create mode 100644 packages/vue/src/components/actions/BaseSignOutButton.ts create mode 100644 packages/vue/src/components/actions/BaseSignUpButton.ts create mode 100644 packages/vue/src/components/actions/SignInButton.ts create mode 100644 packages/vue/src/components/actions/SignOutButton.ts create mode 100644 packages/vue/src/components/actions/SignUpButton.ts create mode 100644 packages/vue/src/components/adapters/FacebookButton.ts create mode 100644 packages/vue/src/components/adapters/GitHubButton.ts create mode 100644 packages/vue/src/components/adapters/GoogleButton.ts create mode 100644 packages/vue/src/components/adapters/MicrosoftButton.ts create mode 100644 packages/vue/src/components/auth/Callback.ts create mode 100644 packages/vue/src/components/control/Loading.ts create mode 100644 packages/vue/src/components/control/SignedIn.ts create mode 100644 packages/vue/src/components/control/SignedOut.ts create mode 100644 packages/vue/src/components/factories/FieldFactory.ts create mode 100644 packages/vue/src/components/presentation/AcceptInvite.ts create mode 100644 packages/vue/src/components/presentation/BaseAcceptInvite.ts create mode 100644 packages/vue/src/components/presentation/BaseCreateOrganization.ts create mode 100644 packages/vue/src/components/presentation/BaseInviteUser.ts create mode 100644 packages/vue/src/components/presentation/BaseLanguageSwitcher.ts create mode 100644 packages/vue/src/components/presentation/BaseOrganization.ts create mode 100644 packages/vue/src/components/presentation/BaseOrganizationList.ts create mode 100644 packages/vue/src/components/presentation/BaseOrganizationProfile.ts create mode 100644 packages/vue/src/components/presentation/BaseOrganizationSwitcher.ts create mode 100644 packages/vue/src/components/presentation/BaseSignIn.ts create mode 100644 packages/vue/src/components/presentation/BaseSignUp.ts create mode 100644 packages/vue/src/components/presentation/BaseUser.ts create mode 100644 packages/vue/src/components/presentation/BaseUserDropdown.ts create mode 100644 packages/vue/src/components/presentation/BaseUserProfile.ts create mode 100644 packages/vue/src/components/presentation/CreateOrganization.ts create mode 100644 packages/vue/src/components/presentation/InviteUser.ts create mode 100644 packages/vue/src/components/presentation/LanguageSwitcher.ts create mode 100644 packages/vue/src/components/presentation/Organization.ts create mode 100644 packages/vue/src/components/presentation/OrganizationList.ts create mode 100644 packages/vue/src/components/presentation/OrganizationProfile.ts create mode 100644 packages/vue/src/components/presentation/OrganizationSwitcher.ts create mode 100644 packages/vue/src/components/presentation/SignIn.ts create mode 100644 packages/vue/src/components/presentation/SignUp.ts create mode 100644 packages/vue/src/components/presentation/User.ts create mode 100644 packages/vue/src/components/presentation/UserDropdown.ts create mode 100644 packages/vue/src/components/presentation/UserProfile.ts create mode 100644 packages/vue/src/components/primitives/Alert.ts create mode 100644 packages/vue/src/components/primitives/Button.ts create mode 100644 packages/vue/src/components/primitives/Card.ts create mode 100644 packages/vue/src/components/primitives/Checkbox.ts create mode 100644 packages/vue/src/components/primitives/DatePicker.ts create mode 100644 packages/vue/src/components/primitives/Divider.ts create mode 100644 packages/vue/src/components/primitives/Icons.ts create mode 100644 packages/vue/src/components/primitives/Logo.ts create mode 100644 packages/vue/src/components/primitives/OtpField.ts create mode 100644 packages/vue/src/components/primitives/PasswordField.ts create mode 100644 packages/vue/src/components/primitives/Select.ts create mode 100644 packages/vue/src/components/primitives/Spinner.ts create mode 100644 packages/vue/src/components/primitives/TextField.ts create mode 100644 packages/vue/src/components/primitives/Typography.ts diff --git a/packages/vue/src/components/AsgardeoProvider.ts b/packages/vue/src/components/AsgardeoProvider.ts index b092fb7ea..a0c61b26e 100644 --- a/packages/vue/src/components/AsgardeoProvider.ts +++ b/packages/vue/src/components/AsgardeoProvider.ts @@ -43,6 +43,13 @@ import AsgardeoVueClient from '../AsgardeoVueClient'; import {ASGARDEO_KEY} from '../keys'; import type {AsgardeoContext} from '../models/contexts'; import type {AsgardeoVueConfig} from '../models/config'; +import I18nProvider from '../providers/I18nProvider'; +import UserProvider from '../providers/UserProvider'; +import OrganizationProvider from '../providers/OrganizationProvider'; +import ThemeProvider from '../providers/ThemeProvider'; +import BrandingProvider from '../providers/BrandingProvider'; +import FlowProvider from '../providers/FlowProvider'; +import FlowMetaProvider from '../providers/FlowMetaProvider'; /** * Checks if the current URL contains authentication parameters. @@ -426,7 +433,28 @@ const AsgardeoProvider = defineComponent({ }); // ── Render ── - return () => (slots['default'] ? slots['default']() : undefined); + return () => + h(I18nProvider, null, { + default: () => + h(UserProvider, null, { + default: () => + h(OrganizationProvider, null, { + default: () => + h(ThemeProvider, null, { + default: () => + h(BrandingProvider, null, { + default: () => + h(FlowMetaProvider, null, { + default: () => + h(FlowProvider, null, { + default: () => slots['default']?.(), + }), + }), + }), + }), + }), + }), + }); }, }); diff --git a/packages/vue/src/components/actions/BaseSignInButton.ts b/packages/vue/src/components/actions/BaseSignInButton.ts new file mode 100644 index 000000000..fa369db25 --- /dev/null +++ b/packages/vue/src/components/actions/BaseSignInButton.ts @@ -0,0 +1,77 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; +import Button from '../primitives/Button'; + +/** + * BaseSignInButton — unstyled sign-in button. + * + * Uses the default slot for custom content. When no slot is provided, + * renders a default Button primitive. + */ +const BaseSignInButton = defineComponent({ + name: 'BaseSignInButton', + props: { + isLoading: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + const handleClick = (e: MouseEvent) => { + if (!props.disabled && !props.isLoading) { + emit('click', e); + } + }; + + return () => { + if (slots['default']) { + return h( + 'button', + { + type: 'button' as const, + class: [ + withVendorCSSClassPrefix('sign-in-button-wrapper'), + (attrs['class'] as string) || '', + ].filter(Boolean).join(' '), + style: attrs['style'], + disabled: props.disabled || props.isLoading, + onClick: handleClick, + }, + slots['default']({isLoading: props.isLoading}), + ); + } + + return h( + Button, + { + class: [withVendorCSSClassPrefix('sign-in-button'), (attrs['class'] as string) || ''].filter(Boolean).join(' '), + style: attrs['style'], + disabled: props.disabled || props.isLoading, + loading: props.isLoading, + type: 'button' as const, + onClick: handleClick, + }, + () => 'Sign In', + ); + }; + }, +}); + +export default BaseSignInButton; diff --git a/packages/vue/src/components/actions/BaseSignOutButton.ts b/packages/vue/src/components/actions/BaseSignOutButton.ts new file mode 100644 index 000000000..930d9afd4 --- /dev/null +++ b/packages/vue/src/components/actions/BaseSignOutButton.ts @@ -0,0 +1,62 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import Button from '../primitives/Button'; + +/** + * BaseSignOutButton — unstyled sign-out button. + * + * Uses the default slot for custom content. When no slot is provided, + * renders a default Button primitive. + */ +const BaseSignOutButton = defineComponent({ + name: 'BaseSignOutButton', + props: { + isLoading: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + return () => { + if (slots['default']) { + return h( + 'span', + {class: withVendorCSSClassPrefix('sign-out-button-wrapper')}, + slots['default']({isLoading: props.isLoading}), + ); + } + + return h( + Button, + { + class: [withVendorCSSClassPrefix('sign-out-button'), (attrs['class'] as string) || ''].filter(Boolean).join(' '), + style: attrs['style'], + disabled: props.disabled || props.isLoading, + loading: props.isLoading, + type: 'button' as const, + onClick: (e: MouseEvent) => emit('click', e), + }, + () => 'Sign Out', + ); + }; + }, +}); + +export default BaseSignOutButton; diff --git a/packages/vue/src/components/actions/BaseSignUpButton.ts b/packages/vue/src/components/actions/BaseSignUpButton.ts new file mode 100644 index 000000000..d5cfb19ed --- /dev/null +++ b/packages/vue/src/components/actions/BaseSignUpButton.ts @@ -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. + */ + +import {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import Button from '../primitives/Button'; + +/** + * BaseSignUpButton — unstyled sign-up button. + * + * Uses the default slot for custom content. When no slot is provided, + * renders a default Button primitive. + */ +const BaseSignUpButton = defineComponent({ + name: 'BaseSignUpButton', + props: { + isLoading: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + return () => { + if (slots['default']) { + return h( + 'span', + {class: withVendorCSSClassPrefix('sign-up-button-wrapper')}, + slots['default']({isLoading: props.isLoading}), + ); + } + + return h( + Button, + { + class: [withVendorCSSClassPrefix('sign-up-button'), (attrs['class'] as string) || ''].filter(Boolean).join(' '), + style: attrs['style'], + disabled: props.disabled || props.isLoading, + loading: props.isLoading, + type: 'button' as const, + color: 'primary' as const, + variant: 'solid' as const, + onClick: (e: MouseEvent) => emit('click', e), + }, + () => 'Sign Up', + ); + }; + }, +}); + +export default BaseSignUpButton; diff --git a/packages/vue/src/components/actions/SignInButton.ts b/packages/vue/src/components/actions/SignInButton.ts new file mode 100644 index 000000000..cbb2e02a0 --- /dev/null +++ b/packages/vue/src/components/actions/SignInButton.ts @@ -0,0 +1,78 @@ +/** + * 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 {AsgardeoRuntimeError, navigate} from '@asgardeo/browser'; +import {defineComponent, h, ref, type PropType} from 'vue'; +import BaseSignInButton from './BaseSignInButton'; +import useAsgardeo from '../../composables/useAsgardeo'; +import useI18n from '../../composables/useI18n'; + +/** + * SignInButton — triggers `signIn()` from the Asgardeo context. + * + * If a custom `signInUrl` is configured, navigates to it instead. + * Falls back to i18n translation for the button text. + */ +const SignInButton = defineComponent({ + name: 'SignInButton', + props: { + signInOptions: {type: Object as PropType>, default: undefined}, + }, + emits: ['click', 'error'], + setup(props, {slots, emit, attrs}) { + const {signIn, signInUrl, signInOptions: contextSignInOptions} = useAsgardeo(); + const {t} = useI18n(); + const isLoading = ref(false); + + const handleSignIn = async (e?: MouseEvent) => { + try { + isLoading.value = true; + if (signInUrl) { + navigate(signInUrl); + } else { + await signIn(props.signInOptions ?? contextSignInOptions); + } + if (e) emit('click', e); + } catch (error) { + emit('error', error); + throw new AsgardeoRuntimeError( + `Sign in failed: ${error instanceof Error ? error.message : String(error)}`, + 'SignInButton-handleSignIn-RuntimeError-001', + 'vue', + 'Something went wrong while trying to sign in. Please try again later.', + ); + } finally { + isLoading.value = false; + } + }; + + return () => + h( + BaseSignInButton, + { + class: attrs['class'], + style: attrs['style'], + isLoading: isLoading.value, + onClick: handleSignIn, + }, + slots['default'] ? () => slots['default']!({isLoading: isLoading.value}) : () => t('elements.buttons.signin.text'), + ); + }, +}); + +export default SignInButton; diff --git a/packages/vue/src/components/actions/SignOutButton.ts b/packages/vue/src/components/actions/SignOutButton.ts new file mode 100644 index 000000000..47cac8387 --- /dev/null +++ b/packages/vue/src/components/actions/SignOutButton.ts @@ -0,0 +1,68 @@ +/** + * 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 {AsgardeoRuntimeError} from '@asgardeo/browser'; +import {defineComponent, h, ref} from 'vue'; +import BaseSignOutButton from './BaseSignOutButton'; +import useAsgardeo from '../../composables/useAsgardeo'; +import useI18n from '../../composables/useI18n'; + +/** + * SignOutButton — triggers `signOut()` from the Asgardeo context. + */ +const SignOutButton = defineComponent({ + name: 'SignOutButton', + emits: ['click', 'error'], + setup(_, {slots, emit, attrs}) { + const {signOut} = useAsgardeo(); + const {t} = useI18n(); + const isLoading = ref(false); + + const handleSignOut = async (e?: MouseEvent) => { + try { + isLoading.value = true; + await signOut(); + if (e) emit('click', e); + } catch (error) { + emit('error', error); + throw new AsgardeoRuntimeError( + `Sign out failed: ${error instanceof Error ? error.message : String(error)}`, + 'SignOutButton-handleSignOut-RuntimeError-001', + 'vue', + 'Something went wrong while trying to sign out. Please try again later.', + ); + } finally { + isLoading.value = false; + } + }; + + return () => + h( + BaseSignOutButton, + { + class: attrs['class'], + style: attrs['style'], + isLoading: isLoading.value, + onClick: handleSignOut, + }, + slots['default'] ? () => slots['default']!({isLoading: isLoading.value}) : () => t('elements.buttons.signout.text'), + ); + }, +}); + +export default SignOutButton; diff --git a/packages/vue/src/components/actions/SignUpButton.ts b/packages/vue/src/components/actions/SignUpButton.ts new file mode 100644 index 000000000..0a9897e7f --- /dev/null +++ b/packages/vue/src/components/actions/SignUpButton.ts @@ -0,0 +1,89 @@ +/** + * 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 {AsgardeoRuntimeError, navigate, withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, ref} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; +import useI18n from '../../composables/useI18n'; +import Button from '../primitives/Button'; + +/** + * SignUpButton — styled sign-up button. + * + * When clicked, invokes `signUp()` from the Asgardeo context. + * If a `signUpUrl` is available, navigates to it instead. + */ +const SignUpButton = defineComponent({ + name: 'SignUpButton', + setup(_props, {slots}) { + const {signUp, signUpUrl} = useAsgardeo(); + const {t} = useI18n(); + + const loading = ref(false); + + const handleClick = async () => { + if (signUpUrl) { + navigate(signUpUrl); + + return; + } + + loading.value = true; + try { + await signUp(); + } catch (error: unknown) { + throw new AsgardeoRuntimeError( + 'vue-sign-up-button-error', + 'SignUpButton', + `Failed to initiate sign-up: ${error instanceof Error ? error.message : String(error)}`, + ); + } finally { + loading.value = false; + } + }; + + return () => { + if (slots['default']) { + return h( + 'span', + { + class: withVendorCSSClassPrefix('sign-up-button-wrapper'), + onClick: handleClick, + }, + slots['default']({isLoading: loading.value}), + ); + } + + return h( + Button, + { + class: withVendorCSSClassPrefix('sign-up-button'), + disabled: loading.value, + loading: loading.value, + type: 'button' as const, + color: 'primary' as const, + variant: 'solid' as const, + onClick: handleClick, + }, + () => t('common.sign-up') || 'Sign Up', + ); + }; + }, +}); + +export default SignUpButton; diff --git a/packages/vue/src/components/adapters/FacebookButton.ts b/packages/vue/src/components/adapters/FacebookButton.ts new file mode 100644 index 000000000..bf4df42fe --- /dev/null +++ b/packages/vue/src/components/adapters/FacebookButton.ts @@ -0,0 +1,66 @@ +/** + * 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 {defineComponent, h} from 'vue'; +import useI18n from '../../composables/useI18n'; +import Button from '../primitives/Button'; + +/** + * Facebook Sign-In Button Component. + * Handles authentication with Facebook identity provider. + */ +const FacebookButton = defineComponent({ + name: 'FacebookButton', + props: { + isLoading: {type: Boolean, default: false}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + const {t} = useI18n(); + + const facebookIcon = () => + h('svg', {width: '18', height: '18', viewBox: '0 0 512 512', xmlns: 'http://www.w3.org/2000/svg'}, [ + h('path', { + fill: '#1976D2', + d: 'M448,0H64C28.704,0,0,28.704,0,64v384c0,35.296,28.704,64,64,64h384c35.296,0,64-28.704,64-64V64C512,28.704,483.296,0,448,0z', + }), + h('path', { + fill: '#FAFAFA', + d: 'M432,256h-80v-64c0-17.664,14.336-16,32-16h32V96h-64l0,0c-53.024,0-96,42.976-96,96v64h-64v80h64v176h96V336h48L432,256z', + }), + ]); + + return () => + h( + Button, + { + ...attrs, + fullWidth: true, + type: 'button' as const, + color: 'primary' as const, + variant: 'solid' as const, + disabled: props.isLoading, + startIcon: facebookIcon(), + onClick: (e: MouseEvent) => emit('click', e), + }, + () => slots['default']?.() ?? (t('elements.buttons.facebook.text') || 'Sign in with Facebook'), + ); + }, +}); + +export default FacebookButton; diff --git a/packages/vue/src/components/adapters/GitHubButton.ts b/packages/vue/src/components/adapters/GitHubButton.ts new file mode 100644 index 000000000..009be70cf --- /dev/null +++ b/packages/vue/src/components/adapters/GitHubButton.ts @@ -0,0 +1,68 @@ +/** + * 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 {defineComponent, h} from 'vue'; +import useI18n from '../../composables/useI18n'; +import Button from '../primitives/Button'; + +/** + * GitHub Sign-In Button Component. + * Handles authentication with GitHub identity provider. + */ +const GitHubButton = defineComponent({ + name: 'GitHubButton', + props: { + isLoading: {type: Boolean, default: false}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + const {t} = useI18n(); + + const gitHubIcon = () => + h( + 'svg', + {width: '18', height: '18', viewBox: '0 0 67.91 66.233', xmlns: 'http://www.w3.org/2000/svg'}, + [ + h('g', {transform: 'translate(-386.96 658.072)'}, [ + h('path', { + d: 'M420.915-658.072a33.956,33.956,0,0,0-33.955,33.955,33.963,33.963,0,0,0,23.221,32.22c1.7.314,2.32-.737,2.32-1.633,0-.81-.031-3.484-.046-6.322-9.446,2.054-11.44-4.006-11.44-4.006-1.545-3.925-3.77-4.968-3.77-4.968-3.081-2.107.232-2.064.232-2.064,3.41.239,5.205,3.5,5.205,3.5,3.028,5.19,7.943,3.69,9.881,2.822a7.23,7.23,0,0,1,2.156-4.54c-7.542-.859-15.47-3.77-15.47-16.781a13.141,13.141,0,0,1,3.5-9.114,12.2,12.2,0,0,1,.329-8.986s2.851-.913,9.34,3.48a32.545,32.545,0,0,1,8.5-1.143,32.629,32.629,0,0,1,8.506,1.143c6.481-4.393,9.328-3.48,9.328-3.48a12.185,12.185,0,0,1,.333,8.986,13.115,13.115,0,0,1,3.495,9.114c0,13.042-7.943,15.913-15.5,16.754,1.218,1.054,2.3,3.12,2.3,6.288,0,4.543-.039,8.2-.039,9.318,0,.9.611,1.962,2.332,1.629a33.959,33.959,0,0,0,23.2-32.215,33.955,33.955,0,0,0-33.955-33.955', + fill: '#ffffff', + }), + ]), + ], + ); + + return () => + h( + Button, + { + ...attrs, + fullWidth: true, + type: 'button' as const, + color: 'secondary' as const, + variant: 'solid' as const, + disabled: props.isLoading, + startIcon: gitHubIcon(), + onClick: (e: MouseEvent) => emit('click', e), + }, + () => slots['default']?.() ?? (t('elements.buttons.github.text') || 'Sign in with GitHub'), + ); + }, +}); + +export default GitHubButton; diff --git a/packages/vue/src/components/adapters/GoogleButton.ts b/packages/vue/src/components/adapters/GoogleButton.ts new file mode 100644 index 000000000..5412aedce --- /dev/null +++ b/packages/vue/src/components/adapters/GoogleButton.ts @@ -0,0 +1,84 @@ +/** + * 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 {defineComponent, h} from 'vue'; +import useI18n from '../../composables/useI18n'; +import Button from '../primitives/Button'; + +/** + * Google Sign-In Button Component. + * Handles authentication with Google identity provider. + */ +const GoogleButton = defineComponent({ + name: 'GoogleButton', + props: { + isLoading: {type: Boolean, default: false}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + const {t} = useI18n(); + + const googleIcon = () => + h( + 'svg', + {width: '18', height: '18', viewBox: '0 0 67.91 67.901', xmlns: 'http://www.w3.org/2000/svg'}, + [ + h('g', {transform: 'translate(-0.001 -0.001)'}, [ + h('path', { + d: 'M15.049,160.965l-2.364,8.824-8.639.183a34.011,34.011,0,0,1-.25-31.7h0l7.691,1.41,3.369,7.645a20.262,20.262,0,0,0,.19,13.642Z', + transform: 'translate(0 -119.93)', + fill: '#fbbb00', + }), + h('path', { + d: 'M294.24,208.176A33.939,33.939,0,0,1,282.137,241h0l-9.687-.494-1.371-8.559a20.235,20.235,0,0,0,8.706-10.333H261.628V208.176Z', + transform: 'translate(-226.93 -180.567)', + fill: '#518ef8', + }), + h('path', { + d: 'M81.668,328.8h0a33.962,33.962,0,0,1-51.161-10.387l11-9.006a20.192,20.192,0,0,0,29.1,10.338Z', + transform: 'translate(-26.463 -268.374)', + fill: '#28b446', + }), + h('path', { + d: 'M80.451,7.816l-11,9A20.19,20.19,0,0,0,39.686,27.393l-11.06-9.055h0A33.959,33.959,0,0,1,80.451,7.816Z', + transform: 'translate(-24.828)', + fill: '#f14336', + }), + ]), + ], + ); + + return () => + h( + Button, + { + ...attrs, + fullWidth: true, + type: 'button' as const, + color: 'secondary' as const, + variant: 'solid' as const, + disabled: props.isLoading, + startIcon: googleIcon(), + onClick: (e: MouseEvent) => emit('click', e), + }, + () => slots['default']?.() ?? (t('elements.buttons.google.text') || 'Sign in with Google'), + ); + }, +}); + +export default GoogleButton; diff --git a/packages/vue/src/components/adapters/MicrosoftButton.ts b/packages/vue/src/components/adapters/MicrosoftButton.ts new file mode 100644 index 000000000..73db32570 --- /dev/null +++ b/packages/vue/src/components/adapters/MicrosoftButton.ts @@ -0,0 +1,63 @@ +/** + * 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 {defineComponent, h} from 'vue'; +import useI18n from '../../composables/useI18n'; +import Button from '../primitives/Button'; + +/** + * Microsoft Sign-In Button Component. + * Handles authentication with Microsoft identity provider. + */ +const MicrosoftButton = defineComponent({ + name: 'MicrosoftButton', + props: { + isLoading: {type: Boolean, default: false}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + const {t} = useI18n(); + + const microsoftIcon = () => + h('svg', {width: '14', height: '14', viewBox: '0 0 23 23', xmlns: 'http://www.w3.org/2000/svg'}, [ + h('path', {fill: '#f3f3f3', d: 'M0 0h23v23H0z'}), + h('path', {fill: '#f35325', d: 'M1 1h10v10H1z'}), + h('path', {fill: '#81bc06', d: 'M12 1h10v10H12z'}), + h('path', {fill: '#05a6f0', d: 'M1 12h10v10H1z'}), + h('path', {fill: '#ffba08', d: 'M12 12h10v10H12z'}), + ]); + + return () => + h( + Button, + { + ...attrs, + fullWidth: true, + type: 'button' as const, + color: 'secondary' as const, + variant: 'solid' as const, + disabled: props.isLoading, + startIcon: microsoftIcon(), + onClick: (e: MouseEvent) => emit('click', e), + }, + () => slots['default']?.() ?? (t('elements.buttons.microsoft.text') || 'Sign in with Microsoft'), + ); + }, +}); + +export default MicrosoftButton; diff --git a/packages/vue/src/components/auth/Callback.ts b/packages/vue/src/components/auth/Callback.ts new file mode 100644 index 000000000..3fa0d86dd --- /dev/null +++ b/packages/vue/src/components/auth/Callback.ts @@ -0,0 +1,148 @@ +/** + * 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 {navigate as browserNavigate} from '@asgardeo/browser'; +import {defineComponent, onMounted} from 'vue'; + +/** + * Callback — headless component that handles OAuth callback parameter forwarding. + * + * Extracts OAuth parameters (code, state, error) from the URL and forwards them + * to the original component that initiated the OAuth flow. + * + * Works standalone using the browser navigate utility (History API) for navigation by default. + * Pass an `onNavigate` prop to enable framework-specific navigation (e.g., via Vue Router). + * + * Flow: Extract OAuth parameters from URL -> Parse state parameter -> Redirect to original path with parameters + */ +const Callback = defineComponent({ + name: 'Callback', + props: { + onNavigate: {type: Function as unknown as () => (path: string) => void, default: undefined}, + onError: {type: Function as unknown as () => (error: Error) => void, default: undefined}, + }, + setup(props) { + const navigate = (path: string): void => { + if (props.onNavigate) { + props.onNavigate(path); + } else { + browserNavigate(path); + } + }; + + onMounted(() => { + let returnPath = '/'; + + try { + // 1. Extract OAuth parameters from URL + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get('code'); + const state = urlParams.get('state'); + const nonce = urlParams.get('nonce'); + const oauthError = urlParams.get('error'); + const errorDescription = urlParams.get('error_description'); + + // 2. Validate and retrieve OAuth state from sessionStorage + if (!state) { + throw new Error('Missing OAuth state parameter - possible security issue'); + } + + const storedData = sessionStorage.getItem(`asgardeo_oauth_${state}`); + if (!storedData) { + if (oauthError) { + const errorMsg = errorDescription || oauthError || 'OAuth authentication failed'; + const err = new Error(errorMsg); + props.onError?.(err); + + const params = new URLSearchParams(); + params.set('error', oauthError); + if (errorDescription) { + params.set('error_description', errorDescription); + } + + navigate(`/?${params.toString()}`); + + return; + } + throw new Error('Invalid OAuth state - possible CSRF attack'); + } + + const {path, timestamp} = JSON.parse(storedData); + returnPath = path || '/'; + + // 3. Validate state freshness + const MAX_STATE_AGE = 600000; // 10 minutes + if (Date.now() - timestamp > MAX_STATE_AGE) { + sessionStorage.removeItem(`asgardeo_oauth_${state}`); + throw new Error('OAuth state expired - please try again'); + } + + // 4. Clean up state + sessionStorage.removeItem(`asgardeo_oauth_${state}`); + + // 5. Handle OAuth error response + if (oauthError) { + const errorMsg = errorDescription || oauthError || 'OAuth authentication failed'; + const err = new Error(errorMsg); + props.onError?.(err); + + const params = new URLSearchParams(); + params.set('error', oauthError); + if (errorDescription) { + params.set('error_description', errorDescription); + } + + navigate(`${returnPath}?${params.toString()}`); + + return; + } + + // 6. Validate required parameters + if (!code) { + throw new Error('Missing OAuth authorization code'); + } + + // 7. Forward OAuth code to original component + const params = new URLSearchParams(); + params.set('code', code); + if (nonce) { + params.set('nonce', nonce); + } + + navigate(`${returnPath}?${params.toString()}`); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'OAuth callback processing failed'; + // eslint-disable-next-line no-console + console.error('OAuth callback error:', err); + + props.onError?.(err instanceof Error ? err : new Error(errorMessage)); + + const params = new URLSearchParams(); + params.set('error', 'callback_error'); + params.set('error_description', errorMessage); + + navigate(`${returnPath}?${params.toString()}`); + } + }); + + // Headless component — renders nothing + return () => null; + }, +}); + +export default Callback; diff --git a/packages/vue/src/components/control/Loading.ts b/packages/vue/src/components/control/Loading.ts new file mode 100644 index 000000000..55037a7a2 --- /dev/null +++ b/packages/vue/src/components/control/Loading.ts @@ -0,0 +1,52 @@ +/** + * 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 {type VNode, defineComponent, h, Fragment} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; + +/** + * A component that only renders its children when Asgardeo is loading. + * + * @example + * ```vue + * + *

Loading...

+ * + *
+ * ``` + */ +const Loading = defineComponent({ + name: 'Loading', + setup(_props, {slots}) { + const {isLoading} = useAsgardeo(); + + return (): VNode | VNode[] | null => { + if (!isLoading.value) { + const fallbackContent = slots.fallback?.(); + return fallbackContent ? h(Fragment, {}, fallbackContent) : null; + } + + const defaultContent = slots.default?.(); + return defaultContent ? h(Fragment, {}, defaultContent) : null; + }; + }, +}); + +export default Loading; diff --git a/packages/vue/src/components/control/SignedIn.ts b/packages/vue/src/components/control/SignedIn.ts new file mode 100644 index 000000000..13bf01ac6 --- /dev/null +++ b/packages/vue/src/components/control/SignedIn.ts @@ -0,0 +1,52 @@ +/** + * 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 {type VNode, defineComponent, h, Fragment} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; + +/** + * A component that only renders its children when the user is signed in. + * + * @example + * ```vue + * + *

Welcome! You are signed in.

+ * + *
+ * ``` + */ +const SignedIn = defineComponent({ + name: 'SignedIn', + setup(_props, {slots}) { + const {isSignedIn} = useAsgardeo(); + + return (): VNode | VNode[] | null => { + if (!isSignedIn.value) { + const fallbackContent = slots.fallback?.(); + return fallbackContent ? h(Fragment, {}, fallbackContent) : null; + } + + const defaultContent = slots.default?.(); + return defaultContent ? h(Fragment, {}, defaultContent) : null; + }; + }, +}); + +export default SignedIn; diff --git a/packages/vue/src/components/control/SignedOut.ts b/packages/vue/src/components/control/SignedOut.ts new file mode 100644 index 000000000..78349cb3c --- /dev/null +++ b/packages/vue/src/components/control/SignedOut.ts @@ -0,0 +1,52 @@ +/** + * 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 {type VNode, defineComponent, h, Fragment} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; + +/** + * A component that only renders its children when the user is signed out. + * + * @example + * ```vue + * + *

Please sign in to continue

+ * + *
+ * ``` + */ +const SignedOut = defineComponent({ + name: 'SignedOut', + setup(_props, {slots}) { + const {isSignedIn} = useAsgardeo(); + + return (): VNode | VNode[] | null => { + if (isSignedIn.value) { + const fallbackContent = slots.fallback?.(); + return fallbackContent ? h(Fragment, {}, fallbackContent) : null; + } + + const defaultContent = slots.default?.(); + return defaultContent ? h(Fragment, {}, defaultContent) : null; + }; + }, +}); + +export default SignedOut; diff --git a/packages/vue/src/components/factories/FieldFactory.ts b/packages/vue/src/components/factories/FieldFactory.ts new file mode 100644 index 000000000..44816d089 --- /dev/null +++ b/packages/vue/src/components/factories/FieldFactory.ts @@ -0,0 +1,237 @@ +/** + * 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 {FieldType} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h} from 'vue'; +import Checkbox from '../primitives/Checkbox'; +import DatePicker from '../primitives/DatePicker'; +import OtpField from '../primitives/OtpField'; +import PasswordField from '../primitives/PasswordField'; +import Select, {type SelectOption} from '../primitives/Select'; +import TextField from '../primitives/TextField'; + +/** + * Interface for field configuration. + */ +export interface FieldConfig { + className?: string; + disabled?: boolean; + error?: string; + label: string; + name: string; + onBlur?: () => void; + onChange: (value: string) => void; + options?: SelectOption[]; + placeholder?: string; + required: boolean; + touched?: boolean; + type: FieldType; + value: string; +} + +/** + * Utility function to validate field values based on type. + */ +export const validateFieldValue = ( + value: string, + type: FieldType, + required: boolean = false, + touched: boolean = false, +): string | null => { + if (required && touched && (!value || value.trim() === '')) { + return 'This field is required'; + } + + if (!value || value.trim() === '') { + return null; + } + + switch (type) { + case FieldType.Number: { + const numValue = parseInt(value, 10); + if (Number.isNaN(numValue)) { + return 'Please enter a valid number'; + } + break; + } + default: + break; + } + + return null; +}; + +/** + * Factory function to create form field VNodes based on FieldType. + */ +export const createField = (config: FieldConfig): VNode => { + const { + name, + type, + label, + required, + value, + onChange, + onBlur, + disabled = false, + error, + className, + options = [], + touched = false, + placeholder, + } = config; + + const validationError = error || validateFieldValue(value, type, required, touched); + + const commonProps: Record = { + class: className, + 'data-testid': `asgardeo-signin-${name}`, + disabled, + error: validationError, + label, + name, + onBlur, + placeholder, + required, + modelValue: value, + }; + + switch (type) { + case FieldType.Password: + return h(PasswordField, { + ...commonProps, + 'onUpdate:modelValue': onChange, + } as Record); + + case FieldType.Text: + return h(TextField, { + ...commonProps, + type: 'text', + autocomplete: 'off', + 'onUpdate:modelValue': onChange, + } as Record); + + case FieldType.Email: + return h(TextField, { + ...commonProps, + type: 'email', + autocomplete: 'email', + 'onUpdate:modelValue': onChange, + } as Record); + + case FieldType.Date: + return h(DatePicker, { + ...commonProps, + 'onUpdate:modelValue': onChange, + } as Record); + + case FieldType.Checkbox: { + const isChecked = value === 'true' || (value as unknown) === true; + + return h(Checkbox, { + ...commonProps, + modelValue: isChecked, + 'onUpdate:modelValue': (checked: boolean) => onChange(checked.toString()), + } as Record); + } + + case FieldType.Otp: + return h(OtpField, { + ...commonProps, + 'onUpdate:modelValue': onChange, + } as Record); + + case FieldType.Number: + return h(TextField, { + ...commonProps, + type: 'number', + helperText: 'Enter a numeric value', + 'onUpdate:modelValue': onChange, + } as Record); + + case FieldType.Select: { + const fieldOptions: SelectOption[] = options.length > 0 ? options : []; + + if (fieldOptions.length > 0) { + return h(Select, { + ...commonProps, + options: fieldOptions, + helperText: 'Select from available options', + 'onUpdate:modelValue': onChange, + } as Record); + } + + return h(TextField, { + ...commonProps, + type: 'text', + helperText: 'Enter multiple values separated by commas (e.g., value1, value2, value3)', + placeholder: 'value1, value2, value3', + 'onUpdate:modelValue': onChange, + } as Record); + } + + default: + return h(TextField, { + ...commonProps, + type: 'text', + helperText: 'Unknown field type, treating as text', + 'onUpdate:modelValue': onChange, + } as Record); + } +}; + +/** + * FieldFactory — Vue component wrapper for the field factory. + */ +const FieldFactory = defineComponent({ + name: 'FieldFactory', + props: { + name: {type: String, required: true}, + type: {type: String as PropType, required: true}, + label: {type: String, required: true}, + required: {type: Boolean, default: false}, + value: {type: String, default: ''}, + disabled: {type: Boolean, default: false}, + error: {type: String, default: undefined}, + className: {type: String, default: undefined}, + options: {type: Array as PropType, default: () => []}, + touched: {type: Boolean, default: false}, + placeholder: {type: String, default: undefined}, + }, + emits: ['change', 'blur'], + setup(props, {emit}) { + return () => + createField({ + className: props.className, + disabled: props.disabled, + error: props.error, + label: props.label, + name: props.name, + onBlur: () => emit('blur'), + onChange: (value: string) => emit('change', value), + options: props.options, + placeholder: props.placeholder, + required: props.required, + touched: props.touched, + type: props.type, + value: props.value, + }); + }, +}); + +export default FieldFactory; diff --git a/packages/vue/src/components/presentation/AcceptInvite.ts b/packages/vue/src/components/presentation/AcceptInvite.ts new file mode 100644 index 000000000..26266dfa1 --- /dev/null +++ b/packages/vue/src/components/presentation/AcceptInvite.ts @@ -0,0 +1,67 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, defineComponent, h} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; +import useI18n from '../../composables/useI18n'; +import BaseAcceptInvite from './BaseAcceptInvite'; + +/** + * AcceptInvite — styled invitation acceptance component. + * + * Connects to Asgardeo context for invitation acceptance flow. + */ +const AcceptInvite = defineComponent({ + name: 'AcceptInvite', + props: { + className: {type: String, default: ''}, + invitationCode: {type: String, default: ''}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + onSuccess: {type: Function as PropType<() => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + const {signIn, isLoading} = useAsgardeo(); + const {t} = useI18n(); + + const handleAccept = async (invitationCode: string) => { + await signIn({invitation_code: invitationCode, response_mode: 'direct'}); + }; + + return () => + h( + BaseAcceptInvite, + { + class: withVendorCSSClassPrefix('accept-invite--styled'), + className: props.className, + invitationCode: props.invitationCode, + isLoading: isLoading.value, + size: props.size, + variant: props.variant, + onAccept: handleAccept, + onSuccess: props.onSuccess, + onError: props.onError, + }, + slots, + ); + }, +}); + +export default AcceptInvite; diff --git a/packages/vue/src/components/presentation/BaseAcceptInvite.ts b/packages/vue/src/components/presentation/BaseAcceptInvite.ts new file mode 100644 index 000000000..bbadd46c9 --- /dev/null +++ b/packages/vue/src/components/presentation/BaseAcceptInvite.ts @@ -0,0 +1,139 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h, ref} from 'vue'; +import Alert from '../primitives/Alert'; +import Button from '../primitives/Button'; +import Card from '../primitives/Card'; +import Spinner from '../primitives/Spinner'; +import Typography from '../primitives/Typography'; + +export interface BaseAcceptInviteProps { + className?: string; + invitationCode?: string; + isLoading?: boolean; + onAccept?: (invitationCode: string) => Promise; + onError?: (error: Error) => void; + onSuccess?: () => void; + size?: 'small' | 'medium' | 'large'; + variant?: 'elevated' | 'outlined' | 'flat'; +} + +/** + * BaseAcceptInvite — unstyled invitation acceptance UI. + * + * Requires an invitationCode prop and calls onAccept when the user accepts. + */ +const BaseAcceptInvite = defineComponent({ + name: 'BaseAcceptInvite', + props: { + className: {type: String, default: ''}, + invitationCode: {type: String, default: ''}, + isLoading: {type: Boolean, default: false}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + onAccept: {type: Function as PropType<(invitationCode: string) => Promise>, default: undefined}, + onSuccess: {type: Function as PropType<() => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + const loading = ref(props.isLoading); + const error = ref(null); + + const handleAccept = async () => { + if (!props.invitationCode || !props.onAccept) return; + + loading.value = true; + error.value = null; + + try { + await props.onAccept(props.invitationCode); + props.onSuccess?.(); + } catch (err) { + const message = err instanceof Error ? err.message : 'Failed to accept invitation'; + error.value = message; + props.onError?.(err instanceof Error ? err : new Error(message)); + } finally { + loading.value = false; + } + }; + + return () => { + if (slots['default']) { + return slots['default']({isLoading: loading.value, error: error.value, accept: handleAccept}); + } + + const prefix = withVendorCSSClassPrefix; + const children: VNode[] = []; + + children.push( + h(Typography, {variant: 'h5', class: prefix('accept-invite__title')}, () => 'Accept Invitation'), + ); + + children.push( + h( + Typography, + {variant: 'body2', class: prefix('accept-invite__subtitle')}, + () => 'You have been invited to join an organization.', + ), + ); + + if (error.value) { + children.push(h(Alert, {severity: 'error' as const, dismissible: true}, () => error.value)); + } + + if (loading.value) { + children.push(h('div', {class: prefix('accept-invite__loading')}, [h(Spinner)])); + } + + if (slots['content']) { + children.push(h('div', {class: prefix('accept-invite__content')}, slots['content']())); + } + + children.push( + h('div', {class: prefix('accept-invite__actions')}, [ + h( + Button, + { + color: 'primary' as const, + variant: 'solid' as const, + disabled: loading.value || !props.invitationCode, + loading: loading.value, + onClick: handleAccept, + }, + () => 'Accept Invitation', + ), + ]), + ); + + return h( + Card, + { + class: [prefix('accept-invite'), prefix(`accept-invite--${props.size}`), props.className] + .filter(Boolean) + .join(' '), + variant: props.variant, + }, + () => children, + ); + }; + }, +}); + +export default BaseAcceptInvite; diff --git a/packages/vue/src/components/presentation/BaseCreateOrganization.ts b/packages/vue/src/components/presentation/BaseCreateOrganization.ts new file mode 100644 index 000000000..7072f295c --- /dev/null +++ b/packages/vue/src/components/presentation/BaseCreateOrganization.ts @@ -0,0 +1,118 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import type {PropType} from 'vue'; +import {defineComponent, h, ref} from 'vue'; +import Alert from '../primitives/Alert'; +import Button from '../primitives/Button'; +import Card from '../primitives/Card'; +import TextField from '../primitives/TextField'; +import Typography from '../primitives/Typography'; + +const cls = (name: string): string => withVendorCSSClassPrefix(`create-organization${name}`); + +/** + * BaseCreateOrganization — unstyled sub-organisation creation form. + * + * Provides a form with an org name input and create button. + */ +const BaseCreateOrganization = defineComponent({ + name: 'BaseCreateOrganization', + props: { + className: {type: String, default: ''}, + onCreate: {type: Function as PropType<(name: string) => Promise | void>, default: undefined}, + title: {type: String, default: 'Create Organization'}, + description: {type: String, default: 'Create a new sub-organization.'}, + }, + setup(props, {slots}) { + const orgName = ref(''); + const isSubmitting = ref(false); + const error = ref(null); + + const handleSubmit = async (): Promise => { + const name = orgName.value.trim(); + if (!name) { + error.value = 'Organization name is required.'; + return; + } + + error.value = null; + isSubmitting.value = true; + try { + await props.onCreate?.(name); + orgName.value = ''; + } catch (err: unknown) { + error.value = err instanceof Error ? err.message : 'Failed to create organization.'; + } finally { + isSubmitting.value = false; + } + }; + + return () => { + if (slots['default']) { + return slots['default']({ + orgName: orgName.value, + setOrgName: (v: string) => { + orgName.value = v; + }, + isSubmitting: isSubmitting.value, + error: error.value, + handleSubmit, + }); + } + + return h( + Card, + {class: [cls(''), props.className].filter(Boolean).join(' ')}, + () => [ + h(Typography, {variant: 'h6', class: cls('__title')}, () => props.title), + props.description + ? h(Typography, {variant: 'body2', class: cls('__description')}, () => props.description) + : null, + error.value + ? h(Alert, {type: 'error', class: cls('__error')}, () => error.value) + : null, + h(TextField, { + label: 'Organization Name', + modelValue: orgName.value, + 'onUpdate:modelValue': (v: string) => { + orgName.value = v; + }, + placeholder: 'Enter organization name', + class: cls('__input'), + }), + h( + Button, + { + variant: 'solid', + color: 'primary', + loading: isSubmitting.value, + disabled: isSubmitting.value, + onClick: handleSubmit, + class: cls('__submit'), + }, + () => 'Create', + ), + ], + ); + }; + }, +}); + +export default BaseCreateOrganization; diff --git a/packages/vue/src/components/presentation/BaseInviteUser.ts b/packages/vue/src/components/presentation/BaseInviteUser.ts new file mode 100644 index 000000000..673ad03bf --- /dev/null +++ b/packages/vue/src/components/presentation/BaseInviteUser.ts @@ -0,0 +1,158 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h, ref} from 'vue'; +import Alert from '../primitives/Alert'; +import Button from '../primitives/Button'; +import Card from '../primitives/Card'; +import Spinner from '../primitives/Spinner'; +import TextField from '../primitives/TextField'; +import Typography from '../primitives/Typography'; + +export interface BaseInviteUserProps { + className?: string; + isLoading?: boolean; + onError?: (error: Error) => void; + onInvite?: (email: string, roles?: string[]) => Promise; + onSuccess?: () => void; + size?: 'small' | 'medium' | 'large'; + variant?: 'elevated' | 'outlined' | 'flat'; +} + +/** + * BaseInviteUser — unstyled admin invite UI. + * + * Provides an email input and invite button for inviting users to an organization. + */ +const BaseInviteUser = defineComponent({ + name: 'BaseInviteUser', + props: { + className: {type: String, default: ''}, + isLoading: {type: Boolean, default: false}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + onInvite: {type: Function as PropType<(email: string, roles?: string[]) => Promise>, default: undefined}, + onSuccess: {type: Function as PropType<() => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + const loading = ref(props.isLoading); + const error = ref(null); + const email = ref(''); + const success = ref(false); + + const handleInvite = async () => { + if (!email.value || !props.onInvite) return; + + loading.value = true; + error.value = null; + success.value = false; + + try { + await props.onInvite(email.value); + success.value = true; + email.value = ''; + props.onSuccess?.(); + } catch (err) { + const message = err instanceof Error ? err.message : 'Failed to send invitation'; + error.value = message; + props.onError?.(err instanceof Error ? err : new Error(message)); + } finally { + loading.value = false; + } + }; + + return () => { + if (slots['default']) { + return slots['default']({ + isLoading: loading.value, + error: error.value, + email: email.value, + invite: handleInvite, + }); + } + + const prefix = withVendorCSSClassPrefix; + const children: VNode[] = []; + + children.push(h(Typography, {variant: 'h5', class: prefix('invite-user__title')}, () => 'Invite User')); + children.push( + h( + Typography, + {variant: 'body2', class: prefix('invite-user__subtitle')}, + () => 'Send an invitation to join your organization.', + ), + ); + + if (error.value) { + children.push(h(Alert, {severity: 'error' as const, dismissible: true}, () => error.value)); + } + + if (success.value) { + children.push(h(Alert, {severity: 'success' as const, dismissible: true}, () => 'Invitation sent successfully!')); + } + + children.push( + h('div', {class: prefix('invite-user__form')}, [ + h(TextField, { + label: 'Email Address', + type: 'email', + modelValue: email.value, + 'onUpdate:modelValue': (v: string) => (email.value = v), + placeholder: 'user@example.com', + required: true, + }), + ]), + ); + + if (slots['content']) { + children.push(h('div', {class: prefix('invite-user__extra')}, slots['content']())); + } + + children.push( + h('div', {class: prefix('invite-user__actions')}, [ + h( + Button, + { + color: 'primary' as const, + variant: 'solid' as const, + disabled: loading.value || !email.value, + loading: loading.value, + onClick: handleInvite, + }, + () => 'Send Invitation', + ), + ]), + ); + + return h( + Card, + { + class: [prefix('invite-user'), prefix(`invite-user--${props.size}`), props.className] + .filter(Boolean) + .join(' '), + variant: props.variant, + }, + () => children, + ); + }; + }, +}); + +export default BaseInviteUser; diff --git a/packages/vue/src/components/presentation/BaseLanguageSwitcher.ts b/packages/vue/src/components/presentation/BaseLanguageSwitcher.ts new file mode 100644 index 000000000..f23e54a09 --- /dev/null +++ b/packages/vue/src/components/presentation/BaseLanguageSwitcher.ts @@ -0,0 +1,112 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import type {PropType} from 'vue'; +import {defineComponent, h, ref} from 'vue'; +import Card from '../primitives/Card'; +import {ChevronDownIcon, GlobeIcon} from '../primitives/Icons'; +import type {SelectOption} from '../primitives/Select'; +import Typography from '../primitives/Typography'; + +const cls = (name: string): string => withVendorCSSClassPrefix(`language-switcher${name}`); + +/** + * BaseLanguageSwitcher — unstyled language selection component. + * + * Shows the current language and a dropdown to select another. + */ +const BaseLanguageSwitcher = defineComponent({ + name: 'BaseLanguageSwitcher', + props: { + className: {type: String, default: ''}, + currentLanguage: {type: String, default: 'en'}, + languages: {type: Array as PropType, default: () => [{label: 'English', value: 'en'}]}, + onLanguageChange: {type: Function as PropType<(lang: string) => void>, default: undefined}, + }, + setup(props, {slots}) { + const isOpen = ref(false); + + const toggle = (): void => { + isOpen.value = !isOpen.value; + }; + + const handleSelect = (lang: string): void => { + isOpen.value = false; + props.onLanguageChange?.(lang); + }; + + return () => { + if (slots['default']) { + return slots['default']({ + currentLanguage: props.currentLanguage, + languages: props.languages, + isOpen: isOpen.value, + toggle, + handleSelect, + }); + } + + const currentLabel = + props.languages.find((l) => l.value === props.currentLanguage)?.label ?? props.currentLanguage; + + const triggerButton = h( + 'button', + { + type: 'button', + class: cls('__trigger'), + onClick: toggle, + 'aria-haspopup': 'listbox', + 'aria-expanded': isOpen.value, + }, + [ + h(GlobeIcon, {size: 16}), + h(Typography, {variant: 'body2', class: cls('__trigger-label')}, () => currentLabel), + h(ChevronDownIcon, {size: 12}), + ], + ); + + const dropdownItems = props.languages.map((lang) => { + const isActive = lang.value === props.currentLanguage; + return h( + 'button', + { + type: 'button', + class: [cls('__item'), isActive ? cls('__item--active') : ''], + onClick: () => handleSelect(lang.value), + role: 'option', + 'aria-selected': isActive, + }, + [h(Typography, {variant: 'body2'}, () => lang.label)], + ); + }); + + const dropdown = isOpen.value + ? h('div', {class: cls('__dropdown'), role: 'listbox'}, dropdownItems) + : null; + + return h( + Card, + {class: [cls(''), props.className].filter(Boolean).join(' ')}, + () => [triggerButton, dropdown], + ); + }; + }, +}); + +export default BaseLanguageSwitcher; diff --git a/packages/vue/src/components/presentation/BaseOrganization.ts b/packages/vue/src/components/presentation/BaseOrganization.ts new file mode 100644 index 000000000..d48270b4e --- /dev/null +++ b/packages/vue/src/components/presentation/BaseOrganization.ts @@ -0,0 +1,57 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import type {Organization} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h} from 'vue'; +import Typography from '../primitives/Typography'; +import {BuildingIcon} from '../primitives/Icons'; + +/** + * BaseOrganization — unstyled organization display component (read-only). + * + * Shows current organization info. Uses scoped slots for customization. + */ +const BaseOrganization = defineComponent({ + name: 'BaseOrganization', + props: { + className: {type: String, default: ''}, + organization: {type: Object as PropType, default: null}, + }, + setup(props, {slots}) { + return () => { + if (slots['default']) { + return slots['default']({organization: props.organization}); + } + + if (!props.organization) { + return slots['fallback']?.() ?? null; + } + + const prefix = withVendorCSSClassPrefix; + const orgName = (props.organization['name'] || props.organization['displayName'] || 'Organization') as string; + + return h('div', {class: [prefix('organization'), props.className].filter(Boolean).join(' ')}, [ + h(BuildingIcon, {size: 20}), + h(Typography, {variant: 'body1', class: prefix('organization__name')}, () => orgName), + ]); + }; + }, +}); + +export default BaseOrganization; diff --git a/packages/vue/src/components/presentation/BaseOrganizationList.ts b/packages/vue/src/components/presentation/BaseOrganizationList.ts new file mode 100644 index 000000000..651ae20d1 --- /dev/null +++ b/packages/vue/src/components/presentation/BaseOrganizationList.ts @@ -0,0 +1,80 @@ +/** + * 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 {type Organization as IOrganization, withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h} from 'vue'; +import Spinner from '../primitives/Spinner'; +import Typography from '../primitives/Typography'; +import {BuildingIcon} from '../primitives/Icons'; + +/** + * BaseOrganizationList — unstyled list of organizations. + */ +const BaseOrganizationList = defineComponent({ + name: 'BaseOrganizationList', + props: { + className: {type: String, default: ''}, + organizations: {type: Array as PropType, default: () => []}, + isLoading: {type: Boolean, default: false}, + onSelect: {type: Function as PropType<(org: IOrganization) => void>, default: undefined}, + }, + setup(props, {slots}) { + return () => { + if (slots['default']) { + return slots['default']({organizations: props.organizations, isLoading: props.isLoading}); + } + + const prefix = withVendorCSSClassPrefix; + const children: VNode[] = []; + + if (props.isLoading) { + children.push(h('div', {class: prefix('organization-list__loading')}, [h(Spinner)])); + } else if (props.organizations.length === 0) { + children.push( + h(Typography, {variant: 'body2', class: prefix('organization-list__empty')}, () => 'No organizations found'), + ); + } else { + props.organizations.forEach((org) => { + children.push( + h( + 'button', + { + type: 'button', + key: org['id'], + class: prefix('organization-list__item'), + onClick: () => props.onSelect?.(org), + }, + [ + h(BuildingIcon, {size: 16}), + h(Typography, {variant: 'body1'}, () => org['name'] || org['id']), + ], + ), + ); + }); + } + + return h( + 'div', + {class: [prefix('organization-list'), props.className].filter(Boolean).join(' ')}, + children, + ); + }; + }, +}); + +export default BaseOrganizationList; diff --git a/packages/vue/src/components/presentation/BaseOrganizationProfile.ts b/packages/vue/src/components/presentation/BaseOrganizationProfile.ts new file mode 100644 index 000000000..17a09a329 --- /dev/null +++ b/packages/vue/src/components/presentation/BaseOrganizationProfile.ts @@ -0,0 +1,146 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import type {Organization} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h, ref} from 'vue'; +import Button from '../primitives/Button'; +import Card from '../primitives/Card'; +import Divider from '../primitives/Divider'; +import TextField from '../primitives/TextField'; +import Typography from '../primitives/Typography'; +import {BuildingIcon} from '../primitives/Icons'; + +/** + * BaseOrganizationProfile — unstyled organization details view/edit component. + */ +const BaseOrganizationProfile = defineComponent({ + name: 'BaseOrganizationProfile', + props: { + className: {type: String, default: ''}, + organization: {type: Object as PropType, default: null}, + editable: {type: Boolean, default: false}, + onUpdate: { + type: Function as PropType<(payload: Record) => Promise>, + default: undefined, + }, + }, + setup(props, {slots}) { + const isEditing = ref(false); + const editedName = ref(''); + + return () => { + if (slots['default']) { + return slots['default']({organization: props.organization, isEditing: isEditing.value}); + } + + if (!props.organization) { + return slots['fallback']?.() ?? null; + } + + const prefix = withVendorCSSClassPrefix; + const orgName = (props.organization['name'] || props.organization['displayName'] || '') as string; + const children: VNode[] = []; + + children.push( + h('div', {class: prefix('organization-profile__header')}, [ + h(BuildingIcon, {size: 24}), + isEditing.value + ? h(TextField, { + modelValue: editedName.value, + 'onUpdate:modelValue': (v: string) => (editedName.value = v), + label: 'Organization Name', + }) + : h(Typography, {variant: 'h5'}, () => orgName), + ]), + ); + + children.push(h(Divider)); + + // Display organization details + const details = Object.entries(props.organization).filter( + ([key]) => !['id', 'name', 'displayName'].includes(key), + ); + + details.forEach(([key, value]) => { + children.push( + h('div', {class: prefix('organization-profile__field'), key}, [ + h(Typography, {variant: 'subtitle2'}, () => + key + .split(/(?=[A-Z])|_/) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) + .join(' '), + ), + h(Typography, {variant: 'body1'}, () => (value != null ? String(value) : '—')), + ]), + ); + }); + + if (props.editable) { + children.push( + h('div', {class: prefix('organization-profile__actions')}, [ + isEditing.value + ? [ + h( + Button, + { + variant: 'solid' as const, + size: 'small' as const, + onClick: async () => { + await props.onUpdate?.({name: editedName.value}); + isEditing.value = false; + }, + }, + () => 'Save', + ), + h( + Button, + { + variant: 'text' as const, + size: 'small' as const, + onClick: () => (isEditing.value = false), + }, + () => 'Cancel', + ), + ] + : h( + Button, + { + variant: 'outline' as const, + size: 'small' as const, + onClick: () => { + editedName.value = orgName; + isEditing.value = true; + }, + }, + () => 'Edit', + ), + ]), + ); + } + + return h( + Card, + {class: [prefix('organization-profile'), props.className].filter(Boolean).join(' ')}, + () => children, + ); + }; + }, +}); + +export default BaseOrganizationProfile; diff --git a/packages/vue/src/components/presentation/BaseOrganizationSwitcher.ts b/packages/vue/src/components/presentation/BaseOrganizationSwitcher.ts new file mode 100644 index 000000000..7d4372248 --- /dev/null +++ b/packages/vue/src/components/presentation/BaseOrganizationSwitcher.ts @@ -0,0 +1,133 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import type {Organization} from '@asgardeo/browser'; +import type {PropType, VNode} from 'vue'; +import {defineComponent, h, ref} from 'vue'; +import Button from '../primitives/Button'; +import Card from '../primitives/Card'; +import Divider from '../primitives/Divider'; +import {BuildingIcon, ChevronDownIcon} from '../primitives/Icons'; +import Spinner from '../primitives/Spinner'; +import Typography from '../primitives/Typography'; + +const cls = (name: string): string => withVendorCSSClassPrefix(`organization-switcher${name}`); + +/** + * BaseOrganizationSwitcher — unstyled organisation dropdown switcher. + * + * Shows the current organization name and a dropdown list to switch. + */ +const BaseOrganizationSwitcher = defineComponent({ + name: 'BaseOrganizationSwitcher', + props: { + className: {type: String, default: ''}, + currentOrganization: {type: Object as PropType, default: null}, + organizations: {type: Array as PropType, default: () => []}, + isLoading: {type: Boolean, default: false}, + onSwitch: {type: Function as PropType<(org: Organization) => void>, default: undefined}, + }, + setup(props, {slots}) { + const isOpen = ref(false); + + const toggle = (): void => { + isOpen.value = !isOpen.value; + }; + + const handleSelect = (org: Organization): void => { + isOpen.value = false; + props.onSwitch?.(org); + }; + + return () => { + if (slots['default']) { + return slots['default']({ + currentOrganization: props.currentOrganization, + organizations: props.organizations, + isLoading: props.isLoading, + isOpen: isOpen.value, + toggle, + handleSelect, + }); + } + + const currentName = props.currentOrganization?.name ?? 'No Organization'; + + const triggerButton = h( + 'button', + { + type: 'button', + class: cls('__trigger'), + onClick: toggle, + 'aria-haspopup': 'listbox', + 'aria-expanded': isOpen.value, + }, + [ + h(BuildingIcon, {size: 16}), + h(Typography, {variant: 'body2', class: cls('__trigger-label')}, () => currentName), + h(ChevronDownIcon, {size: 12}), + ], + ); + + const dropdownChildren: VNode[] = []; + + if (props.isLoading) { + dropdownChildren.push( + h('div', {class: cls('__loading')}, [h(Spinner, {size: 'small'})]), + ); + } else if (props.organizations.length === 0) { + dropdownChildren.push( + h(Typography, {variant: 'body2', class: cls('__empty')}, () => 'No organizations available'), + ); + } else { + for (const org of props.organizations) { + const isActive = org['id'] === props.currentOrganization?.id; + dropdownChildren.push( + h( + 'button', + { + type: 'button', + class: [cls('__item'), isActive ? cls('__item--active') : ''], + onClick: () => handleSelect(org), + role: 'option', + 'aria-selected': isActive, + }, + [ + h(BuildingIcon, {size: 14}), + h(Typography, {variant: 'body2'}, () => org['name']), + ], + ), + ); + } + } + + const dropdown = isOpen.value + ? h('div', {class: cls('__dropdown'), role: 'listbox'}, dropdownChildren) + : null; + + return h( + Card, + {class: [cls(''), props.className].filter(Boolean).join(' ')}, + () => [triggerButton, dropdown], + ); + }; + }, +}); + +export default BaseOrganizationSwitcher; diff --git a/packages/vue/src/components/presentation/BaseSignIn.ts b/packages/vue/src/components/presentation/BaseSignIn.ts new file mode 100644 index 000000000..8f51bd509 --- /dev/null +++ b/packages/vue/src/components/presentation/BaseSignIn.ts @@ -0,0 +1,132 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h, ref} from 'vue'; +import Alert from '../primitives/Alert'; +import Button from '../primitives/Button'; +import Card from '../primitives/Card'; +import Divider from '../primitives/Divider'; +import Logo from '../primitives/Logo'; +import Spinner from '../primitives/Spinner'; +import Typography from '../primitives/Typography'; + +export interface BaseSignInProps { + afterSignInUrl?: string; + className?: string; + isLoading?: boolean; + onError?: (error: Error) => void; + onInitialize?: () => Promise; + onSubmit?: (payload: unknown, request?: unknown) => Promise; + onSuccess?: (authData: Record) => void; + showLogo?: boolean; + showSubtitle?: boolean; + showTitle?: boolean; + size?: 'small' | 'medium' | 'large'; + variant?: 'elevated' | 'outlined' | 'flat'; +} + +/** + * BaseSignIn — unstyled sign-in presentation component. + * + * Provides default slot for full customization, or renders a default + * sign-in card UI with flow support. + */ +const BaseSignIn = defineComponent({ + name: 'BaseSignIn', + props: { + className: {type: String, default: ''}, + isLoading: {type: Boolean, default: false}, + showLogo: {type: Boolean, default: true}, + showTitle: {type: Boolean, default: true}, + showSubtitle: {type: Boolean, default: true}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + afterSignInUrl: {type: String, default: undefined}, + onInitialize: {type: Function as PropType<() => Promise>, default: undefined}, + onSubmit: {type: Function as PropType<(payload: unknown, request?: unknown) => Promise>, default: undefined}, + onSuccess: {type: Function as PropType<(authData: Record) => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + const loading = ref(props.isLoading); + const error = ref(null); + + return () => { + if (slots['default']) { + return slots['default']({isLoading: loading.value, error: error.value}); + } + + const prefix = withVendorCSSClassPrefix; + + const children: VNode[] = []; + + if (props.showLogo) { + children.push(h('div', {class: prefix('sign-in__logo')}, [h(Logo)])); + } + + if (props.showTitle) { + children.push(h(Typography, {variant: 'h5', class: prefix('sign-in__title')}, () => 'Sign In')); + } + + if (props.showSubtitle) { + children.push( + h(Typography, {variant: 'body2', class: prefix('sign-in__subtitle')}, () => 'Sign in to your account'), + ); + } + + children.push(h(Divider, {class: prefix('sign-in__divider')})); + + if (error.value) { + children.push( + h(Alert, {severity: 'error' as const, class: prefix('sign-in__error'), dismissible: true}, () => error.value), + ); + } + + if (loading.value) { + children.push(h('div', {class: prefix('sign-in__loading')}, [h(Spinner)])); + } + + // Slot for flow content (form fields, social buttons, etc.) + if (slots['content']) { + children.push(h('div', {class: prefix('sign-in__content')}, slots['content']())); + } + + // Slot for actions + if (slots['actions']) { + children.push(h('div', {class: prefix('sign-in__actions')}, slots['actions']())); + } + + // Slot for footer content + if (slots['footer']) { + children.push(h('div', {class: prefix('sign-in__footer')}, slots['footer']())); + } + + return h( + Card, + { + class: [prefix('sign-in'), prefix(`sign-in--${props.size}`), props.className].filter(Boolean).join(' '), + variant: props.variant, + }, + () => children, + ); + }; + }, +}); + +export default BaseSignIn; diff --git a/packages/vue/src/components/presentation/BaseSignUp.ts b/packages/vue/src/components/presentation/BaseSignUp.ts new file mode 100644 index 000000000..57640298a --- /dev/null +++ b/packages/vue/src/components/presentation/BaseSignUp.ts @@ -0,0 +1,128 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h, ref} from 'vue'; +import Alert from '../primitives/Alert'; +import Card from '../primitives/Card'; +import Divider from '../primitives/Divider'; +import Logo from '../primitives/Logo'; +import Spinner from '../primitives/Spinner'; +import Typography from '../primitives/Typography'; + +export interface BaseSignUpProps { + afterSignUpUrl?: string; + className?: string; + isLoading?: boolean; + onComplete?: (redirectUrl: string) => void; + onError?: (error: Error) => void; + onInitialize?: () => Promise; + onSubmit?: (payload: unknown) => Promise; + onSuccess?: (response: Record) => void; + showLogo?: boolean; + showSubtitle?: boolean; + showTitle?: boolean; + size?: 'small' | 'medium' | 'large'; + variant?: 'elevated' | 'outlined' | 'flat'; +} + +/** + * BaseSignUp — unstyled sign-up presentation component. + * + * Provides slot-based customization for the sign-up form. + */ +const BaseSignUp = defineComponent({ + name: 'BaseSignUp', + props: { + className: {type: String, default: ''}, + isLoading: {type: Boolean, default: false}, + showLogo: {type: Boolean, default: true}, + showTitle: {type: Boolean, default: true}, + showSubtitle: {type: Boolean, default: true}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + afterSignUpUrl: {type: String, default: undefined}, + onInitialize: {type: Function as PropType<() => Promise>, default: undefined}, + onSubmit: {type: Function as PropType<(payload: unknown) => Promise>, default: undefined}, + onSuccess: {type: Function as PropType<(response: Record) => void>, default: undefined}, + onComplete: {type: Function as PropType<(redirectUrl: string) => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + const loading = ref(props.isLoading); + const error = ref(null); + + return () => { + if (slots['default']) { + return slots['default']({isLoading: loading.value, error: error.value}); + } + + const prefix = withVendorCSSClassPrefix; + const children: VNode[] = []; + + if (props.showLogo) { + children.push(h('div', {class: prefix('sign-up__logo')}, [h(Logo)])); + } + + if (props.showTitle) { + children.push(h(Typography, {variant: 'h5', class: prefix('sign-up__title')}, () => 'Create Account')); + } + + if (props.showSubtitle) { + children.push( + h(Typography, {variant: 'body2', class: prefix('sign-up__subtitle')}, () => 'Sign up for a new account'), + ); + } + + children.push(h(Divider, {class: prefix('sign-up__divider')})); + + if (error.value) { + children.push( + h(Alert, {severity: 'error' as const, class: prefix('sign-up__error'), dismissible: true}, () => error.value), + ); + } + + if (loading.value) { + children.push(h('div', {class: prefix('sign-up__loading')}, [h(Spinner)])); + } + + if (slots['content']) { + children.push(h('div', {class: prefix('sign-up__content')}, slots['content']())); + } + + if (slots['actions']) { + children.push(h('div', {class: prefix('sign-up__actions')}, slots['actions']())); + } + + if (slots['footer']) { + children.push(h('div', {class: prefix('sign-up__footer')}, slots['footer']())); + } + + return h( + Card, + { + class: [prefix('sign-up'), prefix(`sign-up--${props.size}`), props.className].filter(Boolean).join(' '), + variant: props.variant, + }, + () => children, + ); + }; + }, +}); + +export default BaseSignUp; diff --git a/packages/vue/src/components/presentation/BaseUser.ts b/packages/vue/src/components/presentation/BaseUser.ts new file mode 100644 index 000000000..c49204861 --- /dev/null +++ b/packages/vue/src/components/presentation/BaseUser.ts @@ -0,0 +1,43 @@ +/** + * 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 {type User as IUser} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h} from 'vue'; + +/** + * BaseUser — unstyled user component using render-prop (scoped slot) pattern. + * + * Renders default slot with user data, or fallback slot if no user is provided. + */ +const BaseUser = defineComponent({ + name: 'BaseUser', + props: { + user: {type: Object as PropType, default: null}, + }, + setup(props, {slots}) { + return (): VNode[] | null => { + if (!props.user) { + return slots['fallback']?.() ?? null; + } + + return slots['default']?.({user: props.user}) ?? null; + }; + }, +}); + +export default BaseUser; diff --git a/packages/vue/src/components/presentation/BaseUserDropdown.ts b/packages/vue/src/components/presentation/BaseUserDropdown.ts new file mode 100644 index 000000000..5efd3066e --- /dev/null +++ b/packages/vue/src/components/presentation/BaseUserDropdown.ts @@ -0,0 +1,120 @@ +/** + * 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 {type User, withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h, ref} from 'vue'; +import Button from '../primitives/Button'; +import Typography from '../primitives/Typography'; +import {ChevronDownIcon, LogOutIcon, UserIcon} from '../primitives/Icons'; + +export interface BaseUserDropdownProps { + className?: string; + onProfileClick?: () => void; + onSignOut?: () => void; + user?: User | null; +} + +/** + * BaseUserDropdown — unstyled user dropdown with avatar, profile link, sign-out. + */ +const BaseUserDropdown = defineComponent({ + name: 'BaseUserDropdown', + props: { + className: {type: String, default: ''}, + user: {type: Object as PropType, default: null}, + onSignOut: {type: Function as PropType<() => void>, default: undefined}, + onProfileClick: {type: Function as PropType<() => void>, default: undefined}, + }, + setup(props, {slots}) { + const isOpen = ref(false); + const prefix = withVendorCSSClassPrefix; + + return () => { + if (slots['default']) { + return slots['default']({user: props.user, isOpen: isOpen.value, toggle: () => (isOpen.value = !isOpen.value)}); + } + + const displayName: string | undefined = + (props.user as Record)?.['displayName'] as string | undefined ?? + (props.user as Record)?.['name'] as string | undefined ?? + (props.user as Record)?.['email'] as string | undefined; + + const children: VNode[] = []; + + // Trigger button + children.push( + h( + 'button', + { + type: 'button', + class: prefix('user-dropdown__trigger'), + onClick: () => (isOpen.value = !isOpen.value), + }, + [ + h('span', {class: prefix('user-dropdown__avatar')}, [ + h(UserIcon, {size: 20}), + ]), + displayName + ? h(Typography, {variant: 'body2', class: prefix('user-dropdown__name')}, () => displayName) + : null, + h(ChevronDownIcon, {size: 16}), + ], + ), + ); + + // Dropdown menu + if (isOpen.value) { + const menuItems: VNode[] = []; + + if (props.onProfileClick) { + menuItems.push( + h( + 'button', + {type: 'button', class: prefix('user-dropdown__item'), onClick: props.onProfileClick}, + [h(UserIcon, {size: 16}), h('span', null, 'Profile')], + ), + ); + } + + if (slots['items']) { + menuItems.push(...(slots['items']() ?? [])); + } + + if (props.onSignOut) { + menuItems.push( + h( + 'button', + {type: 'button', class: prefix('user-dropdown__item'), onClick: props.onSignOut}, + [h(LogOutIcon, {size: 16}), h('span', null, 'Sign Out')], + ), + ); + } + + children.push(h('div', {class: prefix('user-dropdown__menu')}, menuItems)); + } + + return h( + 'div', + {class: [prefix('user-dropdown'), props.className].filter(Boolean).join(' ')}, + children, + ); + }; + }, +}); + +export default BaseUserDropdown; diff --git a/packages/vue/src/components/presentation/BaseUserProfile.ts b/packages/vue/src/components/presentation/BaseUserProfile.ts new file mode 100644 index 000000000..076dc525f --- /dev/null +++ b/packages/vue/src/components/presentation/BaseUserProfile.ts @@ -0,0 +1,205 @@ +/** + * 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 {type User, type Schema, type UpdateMeProfileConfig, withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, type VNode, defineComponent, h, ref} from 'vue'; +import Alert from '../primitives/Alert'; +import Button from '../primitives/Button'; +import Card from '../primitives/Card'; +import Divider from '../primitives/Divider'; +import Spinner from '../primitives/Spinner'; +import TextField from '../primitives/TextField'; +import Typography from '../primitives/Typography'; + +export interface BaseUserProfileProps { + cardLayout?: boolean; + className?: string; + editable?: boolean; + error?: string | null; + flattenedProfile?: User; + hideFields?: string[]; + isLoading?: boolean; + onUpdate?: (requestConfig: UpdateMeProfileConfig, sessionId?: string) => Promise<{data: {user: User}; error: string; success: boolean}>; + profile?: User; + schemas?: Schema[]; + showFields?: string[]; + title?: string; +} + +const fieldsToSkip = [ + 'roles.default', + 'active', + 'groups', + 'accountLocked', + 'accountDisabled', + 'oneTimePassword', + 'userSourceId', + 'idpType', + 'localCredentialExists', + 'ResourceType', + 'ExternalID', + 'MetaData', + 'verifiedMobileNumbers', + 'verifiedEmailAddresses', + 'phoneNumbers.mobile', + 'emailAddresses', + 'preferredMFAOption', +]; + +const readonlyFields = ['username', 'userName', 'user_name']; + +/** + * BaseUserProfile — unstyled user profile component. + * + * Renders user profile fields with inline editing support. + */ +const BaseUserProfile = defineComponent({ + name: 'BaseUserProfile', + props: { + className: {type: String, default: ''}, + cardLayout: {type: Boolean, default: true}, + profile: {type: Object as PropType, default: null}, + flattenedProfile: {type: Object as PropType, default: null}, + schemas: {type: Array as PropType, default: () => []}, + editable: {type: Boolean, default: true}, + isLoading: {type: Boolean, default: false}, + error: {type: String as PropType, default: null}, + title: {type: String, default: 'My Profile'}, + onUpdate: {type: Function as PropType<(requestConfig: UpdateMeProfileConfig, sessionId?: string) => Promise<{data: {user: User}; error: string; success: boolean}>>, default: undefined}, + showFields: {type: Array as PropType, default: () => []}, + hideFields: {type: Array as PropType, default: () => []}, + }, + setup(props, {slots}) { + const editingFields = ref>({}); + const editedValues = ref>({}); + + const shouldShowField = (fieldName: string): boolean => { + if (fieldsToSkip.includes(fieldName)) return false; + if (props.hideFields.length > 0 && props.hideFields.includes(fieldName)) return false; + if (props.showFields.length > 0) return props.showFields.includes(fieldName); + + return true; + }; + + const formatLabel = (key: string): string => + key + .split(/(?=[A-Z])|_/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); + + return () => { + if (slots['default']) { + return slots['default']({ + profile: props.flattenedProfile || props.profile, + isLoading: props.isLoading, + error: props.error, + }); + } + + const prefix = withVendorCSSClassPrefix; + const children: VNode[] = []; + + children.push(h(Typography, {variant: 'h5', class: prefix('user-profile__title')}, () => props.title)); + children.push(h(Divider, {class: prefix('user-profile__divider')})); + + if (props.error) { + children.push(h(Alert, {severity: 'error' as const, class: prefix('user-profile__error')}, () => props.error)); + } + + if (props.isLoading) { + children.push(h('div', {class: prefix('user-profile__loading')}, [h(Spinner)])); + } else { + const data = props.flattenedProfile || props.profile; + if (data) { + const entries = Object.entries(data).filter(([key]) => shouldShowField(key)); + entries.forEach(([key, value]) => { + const isReadonly = readonlyFields.includes(key); + const isEditing = editingFields.value[key]; + + children.push( + h('div', {class: prefix('user-profile__field'), key}, [ + h('div', {class: prefix('user-profile__field-header')}, [ + h(Typography, {variant: 'subtitle2', class: prefix('user-profile__field-label')}, () => + formatLabel(key), + ), + props.editable && !isReadonly + ? h( + Button, + { + variant: 'text' as const, + size: 'small' as const, + onClick: () => { + editingFields.value = {...editingFields.value, [key]: !isEditing}; + if (!isEditing) { + editedValues.value = {...editedValues.value, [key]: String(value ?? '')}; + } + }, + }, + () => (isEditing ? 'Cancel' : 'Edit'), + ) + : null, + ]), + isEditing + ? h('div', {class: prefix('user-profile__field-edit')}, [ + h(TextField, { + modelValue: editedValues.value[key] ?? String(value ?? ''), + 'onUpdate:modelValue': (v: string) => { + editedValues.value = {...editedValues.value, [key]: v}; + }, + }), + h( + Button, + { + size: 'small' as const, + onClick: async () => { + if (props.onUpdate) { + await props.onUpdate({payload: {[key]: editedValues.value[key]}} as UpdateMeProfileConfig); + } + editingFields.value = {...editingFields.value, [key]: false}; + }, + }, + () => 'Save', + ), + ]) + : h(Typography, {variant: 'body1', class: prefix('user-profile__field-value')}, () => + value != null ? String(value) : '—', + ), + ]), + ); + }); + } + } + + if (slots['footer']) { + children.push(h('div', {class: prefix('user-profile__footer')}, slots['footer']())); + } + + if (props.cardLayout) { + return h( + Card, + {class: [prefix('user-profile'), props.className].filter(Boolean).join(' ')}, + () => children, + ); + } + + return h('div', {class: [prefix('user-profile'), props.className].filter(Boolean).join(' ')}, children); + }; + }, +}); + +export default BaseUserProfile; diff --git a/packages/vue/src/components/presentation/CreateOrganization.ts b/packages/vue/src/components/presentation/CreateOrganization.ts new file mode 100644 index 000000000..9f70f8103 --- /dev/null +++ b/packages/vue/src/components/presentation/CreateOrganization.ts @@ -0,0 +1,58 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import useOrganization from '../../composables/useOrganization'; +import BaseCreateOrganization from './BaseCreateOrganization'; + +/** + * CreateOrganization — styled sub-organisation creation component. + * + * Retrieves createOrganization from context and delegates to BaseCreateOrganization. + */ +const CreateOrganization = defineComponent({ + name: 'CreateOrganization', + props: { + className: {type: String, default: ''}, + title: {type: String, default: 'Create Organization'}, + description: {type: String, default: 'Create a new sub-organization.'}, + }, + setup(props, {slots}) { + const {createOrganization} = useOrganization(); + + return () => + h( + BaseCreateOrganization, + { + class: withVendorCSSClassPrefix('create-organization--styled'), + className: props.className, + title: props.title, + description: props.description, + onCreate: createOrganization + ? async (name: string) => { + await createOrganization({name, description: '', parentId: '', type: 'TENANT'}, ''); + } + : undefined, + }, + slots, + ); + }, +}); + +export default CreateOrganization; diff --git a/packages/vue/src/components/presentation/InviteUser.ts b/packages/vue/src/components/presentation/InviteUser.ts new file mode 100644 index 000000000..6105ee6fb --- /dev/null +++ b/packages/vue/src/components/presentation/InviteUser.ts @@ -0,0 +1,56 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, defineComponent, h} from 'vue'; +import BaseInviteUser from './BaseInviteUser'; + +/** + * InviteUser — styled admin invite component. + * + * Provides a form for inviting users to an organization. + */ +const InviteUser = defineComponent({ + name: 'InviteUser', + props: { + className: {type: String, default: ''}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + onInvite: {type: Function as PropType<(email: string, roles?: string[]) => Promise>, default: undefined}, + onSuccess: {type: Function as PropType<() => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + return () => + h( + BaseInviteUser, + { + class: withVendorCSSClassPrefix('invite-user--styled'), + className: props.className, + size: props.size, + variant: props.variant, + onInvite: props.onInvite, + onSuccess: props.onSuccess, + onError: props.onError, + }, + slots, + ); + }, +}); + +export default InviteUser; diff --git a/packages/vue/src/components/presentation/LanguageSwitcher.ts b/packages/vue/src/components/presentation/LanguageSwitcher.ts new file mode 100644 index 000000000..1a6d5459c --- /dev/null +++ b/packages/vue/src/components/presentation/LanguageSwitcher.ts @@ -0,0 +1,63 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import type {PropType} from 'vue'; +import {defineComponent, h} from 'vue'; +import useI18n from '../../composables/useI18n'; +import type {SelectOption} from '../primitives/Select'; +import BaseLanguageSwitcher from './BaseLanguageSwitcher'; + +/** + * LanguageSwitcher — styled language selection component. + * + * Retrieves current language and setLanguage from i18n context. + */ +const LanguageSwitcher = defineComponent({ + name: 'LanguageSwitcher', + props: { + className: {type: String, default: ''}, + languages: { + type: Array as PropType, + default: () => [ + {label: 'English', value: 'en'}, + {label: 'French', value: 'fr'}, + {label: 'Spanish', value: 'es'}, + {label: 'Portuguese', value: 'pt'}, + ], + }, + }, + setup(props, {slots}) { + const {currentLanguage, setLanguage} = useI18n(); + + return () => + h( + BaseLanguageSwitcher, + { + class: withVendorCSSClassPrefix('language-switcher--styled'), + className: props.className, + currentLanguage: currentLanguage?.value ?? 'en', + languages: props.languages, + onLanguageChange: setLanguage, + }, + slots, + ); + }, +}); + +export default LanguageSwitcher; diff --git a/packages/vue/src/components/presentation/Organization.ts b/packages/vue/src/components/presentation/Organization.ts new file mode 100644 index 000000000..4ccf859f5 --- /dev/null +++ b/packages/vue/src/components/presentation/Organization.ts @@ -0,0 +1,50 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import useOrganization from '../../composables/useOrganization'; +import BaseOrganization from './BaseOrganization'; + +/** + * Organization — styled organization display component. + * + * Retrieves current organization from context and delegates to BaseOrganization. + */ +const Organization = defineComponent({ + name: 'Organization', + props: { + className: {type: String, default: ''}, + }, + setup(props, {slots}) { + const {currentOrganization} = useOrganization(); + + return () => + h( + BaseOrganization, + { + class: withVendorCSSClassPrefix('organization--styled'), + className: props.className, + organization: currentOrganization?.value ?? null, + }, + slots, + ); + }, +}); + +export default Organization; diff --git a/packages/vue/src/components/presentation/OrganizationList.ts b/packages/vue/src/components/presentation/OrganizationList.ts new file mode 100644 index 000000000..32772bcb4 --- /dev/null +++ b/packages/vue/src/components/presentation/OrganizationList.ts @@ -0,0 +1,58 @@ +/** + * 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 {type Organization as IOrganization, withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import useOrganization from '../../composables/useOrganization'; +import BaseOrganizationList from './BaseOrganizationList'; + +/** + * OrganizationList — styled organization list component. + * + * Retrieves organization list from context and delegates to BaseOrganizationList. + */ +const OrganizationList = defineComponent({ + name: 'OrganizationList', + props: { + className: {type: String, default: ''}, + }, + emits: ['select'], + setup(props, {slots, emit}) { + const {myOrganizations, isLoading, switchOrganization} = useOrganization(); + + const handleSelect = async (org: IOrganization) => { + emit('select', org); + await switchOrganization(org); + }; + + return () => + h( + BaseOrganizationList, + { + class: withVendorCSSClassPrefix('organization-list--styled'), + className: props.className, + organizations: myOrganizations.value, + isLoading: isLoading.value, + onSelect: handleSelect, + }, + slots, + ); + }, +}); + +export default OrganizationList; diff --git a/packages/vue/src/components/presentation/OrganizationProfile.ts b/packages/vue/src/components/presentation/OrganizationProfile.ts new file mode 100644 index 000000000..bf26eb217 --- /dev/null +++ b/packages/vue/src/components/presentation/OrganizationProfile.ts @@ -0,0 +1,52 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import useOrganization from '../../composables/useOrganization'; +import BaseOrganizationProfile from './BaseOrganizationProfile'; + +/** + * OrganizationProfile — styled organisation details component. + * + * Retrieves current organization from context and delegates to BaseOrganizationProfile. + */ +const OrganizationProfile = defineComponent({ + name: 'OrganizationProfile', + props: { + className: {type: String, default: ''}, + editable: {type: Boolean, default: false}, + }, + setup(props, {slots}) { + const {currentOrganization} = useOrganization(); + + return () => + h( + BaseOrganizationProfile, + { + class: withVendorCSSClassPrefix('organization-profile--styled'), + className: props.className, + organization: currentOrganization?.value ?? null, + editable: props.editable, + }, + slots, + ); + }, +}); + +export default OrganizationProfile; diff --git a/packages/vue/src/components/presentation/OrganizationSwitcher.ts b/packages/vue/src/components/presentation/OrganizationSwitcher.ts new file mode 100644 index 000000000..71e04e92e --- /dev/null +++ b/packages/vue/src/components/presentation/OrganizationSwitcher.ts @@ -0,0 +1,53 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import useOrganization from '../../composables/useOrganization'; +import BaseOrganizationSwitcher from './BaseOrganizationSwitcher'; + +/** + * OrganizationSwitcher — styled organisation switcher component. + * + * Retrieves organisations from context and delegates to BaseOrganizationSwitcher. + */ +const OrganizationSwitcher = defineComponent({ + name: 'OrganizationSwitcher', + props: { + className: {type: String, default: ''}, + }, + setup(props, {slots}) { + const {currentOrganization, myOrganizations, isLoading, switchOrganization} = useOrganization(); + + return () => + h( + BaseOrganizationSwitcher, + { + class: withVendorCSSClassPrefix('organization-switcher--styled'), + className: props.className, + currentOrganization: currentOrganization?.value ?? null, + organizations: myOrganizations?.value ?? [], + isLoading: isLoading?.value ?? false, + onSwitch: switchOrganization, + }, + slots, + ); + }, +}); + +export default OrganizationSwitcher; diff --git a/packages/vue/src/components/presentation/SignIn.ts b/packages/vue/src/components/presentation/SignIn.ts new file mode 100644 index 000000000..5c0baf7cb --- /dev/null +++ b/packages/vue/src/components/presentation/SignIn.ts @@ -0,0 +1,83 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, defineComponent, h} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; +import useI18n from '../../composables/useI18n'; +import BaseSignIn from './BaseSignIn'; + +/** + * SignIn — styled sign-in presentation component. + * + * Connects to Asgardeo context for authentication flow handling. + * Wraps BaseSignIn with composable-provided data. + */ +const SignIn = defineComponent({ + name: 'SignIn', + props: { + className: {type: String, default: ''}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + onSuccess: {type: Function as PropType<(authData: Record) => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + const {signIn, afterSignInUrl, isLoading} = useAsgardeo(); + const {t} = useI18n(); + + const handleInitialize = async () => signIn({response_mode: 'direct'}); + + const handleSuccess = (authData: Record) => { + if (authData && afterSignInUrl) { + const url = new URL(afterSignInUrl, window.location.origin); + + Object.entries(authData).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + url.searchParams.append(key, String(value)); + } + }); + + window.location.href = url.toString(); + } + + props.onSuccess?.(authData); + }; + + return () => + h( + BaseSignIn, + { + class: withVendorCSSClassPrefix('sign-in--styled'), + className: props.className, + isLoading: isLoading.value, + size: props.size, + variant: props.variant, + showLogo: true, + showTitle: true, + showSubtitle: true, + onInitialize: handleInitialize, + onSuccess: handleSuccess, + onError: props.onError, + }, + slots, + ); + }, +}); + +export default SignIn; diff --git a/packages/vue/src/components/presentation/SignUp.ts b/packages/vue/src/components/presentation/SignUp.ts new file mode 100644 index 000000000..9a6524714 --- /dev/null +++ b/packages/vue/src/components/presentation/SignUp.ts @@ -0,0 +1,70 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, defineComponent, h} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; +import useI18n from '../../composables/useI18n'; +import BaseSignUp from './BaseSignUp'; + +/** + * SignUp — styled sign-up presentation component. + * + * Connects to Asgardeo context for registration flow handling. + */ +const SignUp = defineComponent({ + name: 'SignUp', + props: { + className: {type: String, default: ''}, + size: {type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium'}, + variant: {type: String as PropType<'elevated' | 'outlined' | 'flat'>, default: 'elevated'}, + afterSignUpUrl: {type: String, default: undefined}, + onSuccess: {type: Function as PropType<(response: Record) => void>, default: undefined}, + onComplete: {type: Function as PropType<(redirectUrl: string) => void>, default: undefined}, + onError: {type: Function as PropType<(error: Error) => void>, default: undefined}, + }, + setup(props, {slots}) { + const {signUp, isLoading} = useAsgardeo(); + const {t} = useI18n(); + + const handleInitialize = async () => signUp({response_mode: 'direct'}); + + return () => + h( + BaseSignUp, + { + class: withVendorCSSClassPrefix('sign-up--styled'), + className: props.className, + isLoading: isLoading.value, + size: props.size, + variant: props.variant, + showLogo: true, + showTitle: true, + showSubtitle: true, + afterSignUpUrl: props.afterSignUpUrl, + onInitialize: handleInitialize, + onSuccess: props.onSuccess, + onComplete: props.onComplete, + onError: props.onError, + }, + slots, + ); + }, +}); + +export default SignUp; diff --git a/packages/vue/src/components/presentation/User.ts b/packages/vue/src/components/presentation/User.ts new file mode 100644 index 000000000..d9796c344 --- /dev/null +++ b/packages/vue/src/components/presentation/User.ts @@ -0,0 +1,37 @@ +/** + * 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 {type VNode, defineComponent, h} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; +import BaseUser from './BaseUser'; + +/** + * User — styled user component. + * + * Retrieves user from Asgardeo context and delegates to BaseUser. + */ +const User = defineComponent({ + name: 'User', + setup(_props, {slots}) { + const {user} = useAsgardeo(); + + return (): VNode => h(BaseUser, {user: user.value}, slots); + }, +}); + +export default User; diff --git a/packages/vue/src/components/presentation/UserDropdown.ts b/packages/vue/src/components/presentation/UserDropdown.ts new file mode 100644 index 000000000..7dd147011 --- /dev/null +++ b/packages/vue/src/components/presentation/UserDropdown.ts @@ -0,0 +1,53 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; +import useAsgardeo from '../../composables/useAsgardeo'; +import BaseUserDropdown from './BaseUserDropdown'; + +/** + * UserDropdown — styled user dropdown component. + * + * Retrieves user and signOut from context and delegates to BaseUserDropdown. + */ +const UserDropdown = defineComponent({ + name: 'UserDropdown', + props: { + className: {type: String, default: ''}, + }, + emits: ['profileClick'], + setup(props, {slots, emit}) { + const {user, signOut} = useAsgardeo(); + + return () => + h( + BaseUserDropdown, + { + class: withVendorCSSClassPrefix('user-dropdown--styled'), + className: props.className, + user: user.value, + onSignOut: () => signOut(), + onProfileClick: () => emit('profileClick'), + }, + slots, + ); + }, +}); + +export default UserDropdown; diff --git a/packages/vue/src/components/presentation/UserProfile.ts b/packages/vue/src/components/presentation/UserProfile.ts new file mode 100644 index 000000000..94c97b091 --- /dev/null +++ b/packages/vue/src/components/presentation/UserProfile.ts @@ -0,0 +1,62 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {type PropType, defineComponent, h} from 'vue'; +import useUser from '../../composables/useUser'; +import BaseUserProfile from './BaseUserProfile'; + +/** + * UserProfile — styled user profile component. + * + * Retrieves user profile data from context and delegates to BaseUserProfile. + */ +const UserProfile = defineComponent({ + name: 'UserProfile', + props: { + className: {type: String, default: ''}, + editable: {type: Boolean, default: true}, + cardLayout: {type: Boolean, default: true}, + title: {type: String, default: 'My Profile'}, + showFields: {type: Array as PropType, default: () => []}, + hideFields: {type: Array as PropType, default: () => []}, + }, + setup(props, {slots}) { + const {flattenedProfile, schemas, updateProfile} = useUser(); + + return () => + h( + BaseUserProfile, + { + class: withVendorCSSClassPrefix('user-profile--styled'), + className: props.className, + flattenedProfile: flattenedProfile?.value, + schemas: schemas?.value, + editable: props.editable, + cardLayout: props.cardLayout, + title: props.title, + showFields: props.showFields, + hideFields: props.hideFields, + onUpdate: updateProfile, + }, + slots, + ); + }, +}); + +export default UserProfile; diff --git a/packages/vue/src/components/primitives/Alert.ts b/packages/vue/src/components/primitives/Alert.ts new file mode 100644 index 000000000..83954e6ab --- /dev/null +++ b/packages/vue/src/components/primitives/Alert.ts @@ -0,0 +1,66 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; + +const Alert = defineComponent({ + name: 'Alert', + props: { + severity: { + type: String as PropType<'success' | 'error' | 'warning' | 'info'>, + default: 'info', + }, + dismissible: {type: Boolean, default: false}, + }, + emits: ['dismiss'], + setup(props, {slots, emit, attrs}) { + return () => + h( + 'div', + { + role: 'alert', + class: [ + withVendorCSSClassPrefix('alert'), + withVendorCSSClassPrefix(`alert--${props.severity}`), + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '), + style: attrs['style'], + }, + [ + h('div', {class: withVendorCSSClassPrefix('alert__content')}, slots['default']?.()), + props.dismissible + ? h( + 'button', + { + type: 'button', + class: withVendorCSSClassPrefix('alert__dismiss'), + 'aria-label': 'Dismiss', + onClick: () => emit('dismiss'), + }, + '\u00d7', + ) + : null, + ], + ); + }, +}); + +export default Alert; diff --git a/packages/vue/src/components/primitives/Button.ts b/packages/vue/src/components/primitives/Button.ts new file mode 100644 index 000000000..66736f53f --- /dev/null +++ b/packages/vue/src/components/primitives/Button.ts @@ -0,0 +1,84 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType, type VNode} from 'vue'; + +const Button = defineComponent({ + name: 'Button', + props: { + variant: { + type: String as PropType<'solid' | 'outline' | 'ghost' | 'text'>, + default: 'solid', + }, + color: { + type: String as PropType<'primary' | 'secondary' | 'danger'>, + default: 'primary', + }, + size: { + type: String as PropType<'small' | 'medium' | 'large'>, + default: 'medium', + }, + disabled: {type: Boolean, default: false}, + loading: {type: Boolean, default: false}, + fullWidth: {type: Boolean, default: false}, + type: { + type: String as PropType<'button' | 'submit' | 'reset'>, + default: 'button', + }, + startIcon: {type: Object as PropType, default: undefined}, + endIcon: {type: Object as PropType, default: undefined}, + }, + emits: ['click'], + setup(props, {slots, emit, attrs}) { + return () => { + const cssClass = [ + withVendorCSSClassPrefix('button'), + withVendorCSSClassPrefix(`button--${props.variant}`), + withVendorCSSClassPrefix(`button--${props.color}`), + withVendorCSSClassPrefix(`button--${props.size}`), + props.fullWidth ? withVendorCSSClassPrefix('button--full-width') : '', + props.loading ? withVendorCSSClassPrefix('button--loading') : '', + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '); + + return h( + 'button', + { + class: cssClass, + style: attrs['style'], + type: props.type, + disabled: props.disabled || props.loading, + onClick: (e: MouseEvent) => emit('click', e), + }, + [ + props.startIcon ? h('span', {class: withVendorCSSClassPrefix('button__start-icon')}, [props.startIcon]) : null, + h('span', {class: withVendorCSSClassPrefix('button__content')}, slots['default']?.()), + props.endIcon ? h('span', {class: withVendorCSSClassPrefix('button__end-icon')}, [props.endIcon]) : null, + props.loading + ? h('span', {class: withVendorCSSClassPrefix('button__spinner'), 'aria-hidden': 'true'}) + : null, + ], + ); + }; + }, +}); + +export default Button; diff --git a/packages/vue/src/components/primitives/Card.ts b/packages/vue/src/components/primitives/Card.ts new file mode 100644 index 000000000..50450880c --- /dev/null +++ b/packages/vue/src/components/primitives/Card.ts @@ -0,0 +1,49 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; + +const Card = defineComponent({ + name: 'Card', + props: { + variant: { + type: String as PropType<'elevated' | 'outlined' | 'flat'>, + default: 'elevated', + }, + }, + setup(props, {slots, attrs}) { + return () => + h( + 'div', + { + class: [ + withVendorCSSClassPrefix('card'), + withVendorCSSClassPrefix(`card--${props.variant}`), + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '), + style: attrs['style'], + }, + slots['default']?.(), + ); + }, +}); + +export default Card; diff --git a/packages/vue/src/components/primitives/Checkbox.ts b/packages/vue/src/components/primitives/Checkbox.ts new file mode 100644 index 000000000..e1c4d0fb0 --- /dev/null +++ b/packages/vue/src/components/primitives/Checkbox.ts @@ -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. + */ + +import {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; + +const Checkbox = defineComponent({ + name: 'AsgardeoCheckbox', + props: { + modelValue: {type: Boolean, default: false}, + label: {type: String, default: undefined}, + name: {type: String, default: undefined}, + error: {type: String, default: undefined}, + required: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['update:modelValue'], + setup(props, {emit, attrs}) { + return () => { + const wrapperClass = [ + withVendorCSSClassPrefix('checkbox'), + props.error ? withVendorCSSClassPrefix('checkbox--error') : '', + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '); + + return h('div', {class: wrapperClass, style: attrs['style']}, [ + h('label', {class: withVendorCSSClassPrefix('checkbox__wrapper')}, [ + h('input', { + class: withVendorCSSClassPrefix('checkbox__input'), + type: 'checkbox', + name: props.name, + id: props.name, + checked: props.modelValue, + required: props.required, + disabled: props.disabled, + 'data-testid': attrs['data-testid'], + onChange: (e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).checked), + }), + props.label ? h('span', {class: withVendorCSSClassPrefix('checkbox__label')}, props.label) : null, + ]), + props.error ? h('span', {class: withVendorCSSClassPrefix('checkbox__error')}, props.error) : null, + ]); + }; + }, +}); + +export default Checkbox; diff --git a/packages/vue/src/components/primitives/DatePicker.ts b/packages/vue/src/components/primitives/DatePicker.ts new file mode 100644 index 000000000..46a88e329 --- /dev/null +++ b/packages/vue/src/components/primitives/DatePicker.ts @@ -0,0 +1,71 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; + +const DatePicker = defineComponent({ + name: 'AsgardeoDatePicker', + props: { + modelValue: {type: String, default: ''}, + label: {type: String, default: undefined}, + name: {type: String, default: undefined}, + placeholder: {type: String, default: undefined}, + error: {type: String, default: undefined}, + required: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['update:modelValue'], + setup(props, {emit, attrs}) { + return () => { + const hasError = !!props.error; + const wrapperClass = [ + withVendorCSSClassPrefix('date-picker'), + hasError ? withVendorCSSClassPrefix('date-picker--error') : '', + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '); + + return h('div', {class: wrapperClass, style: attrs['style']}, [ + props.label + ? h( + 'label', + {class: withVendorCSSClassPrefix('date-picker__label'), for: props.name}, + [props.label, props.required ? h('span', {class: withVendorCSSClassPrefix('date-picker__required')}, ' *') : null], + ) + : null, + h('input', { + class: withVendorCSSClassPrefix('date-picker__input'), + type: 'date', + name: props.name, + id: props.name, + value: props.modelValue, + placeholder: props.placeholder, + required: props.required, + disabled: props.disabled, + 'data-testid': attrs['data-testid'], + onInput: (e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).value), + }), + hasError ? h('span', {class: withVendorCSSClassPrefix('date-picker__error')}, props.error) : null, + ]); + }; + }, +}); + +export default DatePicker; diff --git a/packages/vue/src/components/primitives/Divider.ts b/packages/vue/src/components/primitives/Divider.ts new file mode 100644 index 000000000..f0fdf35f4 --- /dev/null +++ b/packages/vue/src/components/primitives/Divider.ts @@ -0,0 +1,55 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; + +const Divider = defineComponent({ + name: 'Divider', + props: { + orientation: { + type: String as PropType<'horizontal' | 'vertical'>, + default: 'horizontal', + }, + }, + setup(props, {slots, attrs}) { + return () => { + const hasContent = !!slots['default']; + const cssClass = [ + withVendorCSSClassPrefix('divider'), + withVendorCSSClassPrefix(`divider--${props.orientation}`), + hasContent ? withVendorCSSClassPrefix('divider--with-content') : '', + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '); + + if (hasContent) { + return h('div', {class: cssClass, style: attrs['style'], role: 'separator'}, [ + h('span', {class: withVendorCSSClassPrefix('divider__line')}), + h('span', {class: withVendorCSSClassPrefix('divider__content')}, slots['default']?.()), + h('span', {class: withVendorCSSClassPrefix('divider__line')}), + ]); + } + + return h('hr', {class: cssClass, style: attrs['style'], role: 'separator'}); + }; + }, +}); + +export default Divider; diff --git a/packages/vue/src/components/primitives/Icons.ts b/packages/vue/src/components/primitives/Icons.ts new file mode 100644 index 000000000..8eb80c042 --- /dev/null +++ b/packages/vue/src/components/primitives/Icons.ts @@ -0,0 +1,92 @@ +/** + * 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 {h, type VNode} from 'vue'; + +const defaultProps = { + width: '16', + height: '16', + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + xmlns: 'http://www.w3.org/2000/svg', +}; + +const icon = (paths: VNode[]): VNode => h('svg', {...defaultProps}, paths); + +export const CheckIcon = (): VNode => icon([h('polyline', {points: '20 6 9 17 4 12'})]); + +export const XIcon = (): VNode => + icon([h('line', {x1: '18', y1: '6', x2: '6', y2: '18'}), h('line', {x1: '6', y1: '6', x2: '18', y2: '18'})]); + +export const EyeIcon = (): VNode => + icon([h('path', {d: 'M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'}), h('circle', {cx: '12', cy: '12', r: '3'})]); + +export const EyeOffIcon = (): VNode => + icon([ + h('path', {d: 'M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94'}), + h('path', {d: 'M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19'}), + h('line', {x1: '1', y1: '1', x2: '23', y2: '23'}), + ]); + +export const CircleAlertIcon = (): VNode => + icon([h('circle', {cx: '12', cy: '12', r: '10'}), h('line', {x1: '12', y1: '8', x2: '12', y2: '12'}), h('line', {x1: '12', y1: '16', x2: '12.01', y2: '16'})]); + +export const CircleCheckIcon = (): VNode => + icon([h('path', {d: 'M22 11.08V12a10 10 0 1 1-5.93-9.14'}), h('polyline', {points: '22 4 12 14.01 9 11.01'})]); + +export const InfoIcon = (): VNode => + icon([h('circle', {cx: '12', cy: '12', r: '10'}), h('line', {x1: '12', y1: '16', x2: '12', y2: '12'}), h('line', {x1: '12', y1: '8', x2: '12.01', y2: '8'})]); + +export const TriangleAlertIcon = (): VNode => + icon([ + h('path', {d: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z'}), + h('line', {x1: '12', y1: '9', x2: '12', y2: '13'}), + h('line', {x1: '12', y1: '17', x2: '12.01', y2: '17'}), + ]); + +export const PlusIcon = (): VNode => icon([h('line', {x1: '12', y1: '5', x2: '12', y2: '19'}), h('line', {x1: '5', y1: '12', x2: '19', y2: '12'})]); + +export const LogOutIcon = (): VNode => + icon([h('path', {d: 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'}), h('polyline', {points: '16 17 21 12 16 7'}), h('line', {x1: '21', y1: '12', x2: '9', y2: '12'})]); + +export const UserIcon = (): VNode => + icon([h('path', {d: 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'}), h('circle', {cx: '12', cy: '7', r: '4'})]); + +export const ArrowLeftRightIcon = (): VNode => + icon([h('polyline', {points: '7 16 3 12 7 8'}), h('line', {x1: '21', y1: '12', x2: '3', y2: '12'}), h('polyline', {points: '17 8 21 12 17 16'})]); + +export const BuildingIcon = (): VNode => + icon([ + h('rect', {x: '4', y: '2', width: '16', height: '20', rx: '2', ry: '2'}), + h('line', {x1: '9', y1: '6', x2: '9', y2: '6.01'}), + h('line', {x1: '15', y1: '6', x2: '15', y2: '6.01'}), + h('line', {x1: '9', y1: '10', x2: '9', y2: '10.01'}), + h('line', {x1: '15', y1: '10', x2: '15', y2: '10.01'}), + h('line', {x1: '9', y1: '14', x2: '9', y2: '14.01'}), + h('line', {x1: '15', y1: '14', x2: '15', y2: '14.01'}), + h('line', {x1: '9', y1: '18', x2: '15', y2: '18'}), + ]); + +export const ChevronDownIcon = (): VNode => icon([h('polyline', {points: '6 9 12 15 18 9'})]); + +export const GlobeIcon = (): VNode => + icon([h('circle', {cx: '12', cy: '12', r: '10'}), h('line', {x1: '2', y1: '12', x2: '22', y2: '12'}), h('path', {d: 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z'})]); diff --git a/packages/vue/src/components/primitives/Logo.ts b/packages/vue/src/components/primitives/Logo.ts new file mode 100644 index 000000000..07839d306 --- /dev/null +++ b/packages/vue/src/components/primitives/Logo.ts @@ -0,0 +1,65 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h} from 'vue'; + +const Logo = defineComponent({ + name: 'Logo', + props: { + src: {type: String, default: undefined}, + alt: {type: String, default: 'Logo'}, + href: {type: String, default: undefined}, + width: {type: [String, Number], default: undefined}, + height: {type: [String, Number], default: undefined}, + }, + setup(props, {attrs}) { + return () => { + const img = h('img', { + class: withVendorCSSClassPrefix('logo__image'), + src: props.src, + alt: props.alt, + width: props.width, + height: props.height, + }); + + if (props.href) { + return h( + 'a', + { + class: [withVendorCSSClassPrefix('logo'), (attrs['class'] as string) || ''].filter(Boolean).join(' '), + style: attrs['style'], + href: props.href, + }, + [img], + ); + } + + return h( + 'div', + { + class: [withVendorCSSClassPrefix('logo'), (attrs['class'] as string) || ''].filter(Boolean).join(' '), + style: attrs['style'], + }, + [img], + ); + }; + }, +}); + +export default Logo; diff --git a/packages/vue/src/components/primitives/OtpField.ts b/packages/vue/src/components/primitives/OtpField.ts new file mode 100644 index 000000000..1774fec1c --- /dev/null +++ b/packages/vue/src/components/primitives/OtpField.ts @@ -0,0 +1,98 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, ref, nextTick} from 'vue'; + +const OtpField = defineComponent({ + name: 'OtpField', + props: { + modelValue: {type: String, default: ''}, + length: {type: Number, default: 6}, + label: {type: String, default: undefined}, + name: {type: String, default: undefined}, + error: {type: String, default: undefined}, + required: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['update:modelValue'], + setup(props, {emit, attrs}) { + const inputRefs = ref([]); + + const setRef = (el: any, index: number) => { + if (el) inputRefs.value[index] = el as HTMLInputElement; + }; + + const handleInput = (index: number, e: Event) => { + const target = e.target as HTMLInputElement; + const val = target.value.replace(/\D/g, '').slice(0, 1); + target.value = val; + + const current = (props.modelValue || '').split(''); + while (current.length < props.length) current.push(''); + current[index] = val; + emit('update:modelValue', current.join('')); + + if (val && index < props.length - 1) { + nextTick(() => inputRefs.value[index + 1]?.focus()); + } + }; + + const handleKeydown = (index: number, e: KeyboardEvent) => { + if (e.key === 'Backspace' && !(e.target as HTMLInputElement).value && index > 0) { + nextTick(() => inputRefs.value[index - 1]?.focus()); + } + }; + + return () => { + const digits = (props.modelValue || '').split(''); + while (digits.length < props.length) digits.push(''); + + return h('div', {class: [withVendorCSSClassPrefix('otp-field'), (attrs['class'] as string) || ''].filter(Boolean).join(' '), style: attrs['style']}, [ + props.label + ? h('label', {class: withVendorCSSClassPrefix('otp-field__label')}, [ + props.label, + props.required ? h('span', {class: withVendorCSSClassPrefix('otp-field__required')}, ' *') : null, + ]) + : null, + h( + 'div', + {class: withVendorCSSClassPrefix('otp-field__inputs')}, + Array.from({length: props.length}, (_, i) => + h('input', { + ref: (el: any) => setRef(el, i), + key: i, + class: withVendorCSSClassPrefix('otp-field__digit'), + type: 'text', + inputmode: 'numeric', + maxlength: 1, + value: digits[i], + disabled: props.disabled, + 'aria-label': `Digit ${i + 1}`, + onInput: (e: Event) => handleInput(i, e), + onKeydown: (e: KeyboardEvent) => handleKeydown(i, e), + }), + ), + ), + props.error ? h('span', {class: withVendorCSSClassPrefix('otp-field__error')}, props.error) : null, + ]); + }; + }, +}); + +export default OtpField; diff --git a/packages/vue/src/components/primitives/PasswordField.ts b/packages/vue/src/components/primitives/PasswordField.ts new file mode 100644 index 000000000..d31e6576b --- /dev/null +++ b/packages/vue/src/components/primitives/PasswordField.ts @@ -0,0 +1,92 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, ref} from 'vue'; + +const PasswordField = defineComponent({ + name: 'PasswordField', + props: { + modelValue: {type: String, default: ''}, + label: {type: String, default: undefined}, + name: {type: String, default: undefined}, + placeholder: {type: String, default: undefined}, + error: {type: String, default: undefined}, + required: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['update:modelValue', 'blur'], + setup(props, {emit, attrs}) { + const visible = ref(false); + + return () => { + const hasError = !!props.error; + const wrapperClass = [ + withVendorCSSClassPrefix('password-field'), + hasError ? withVendorCSSClassPrefix('password-field--error') : '', + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '); + + return h('div', {class: wrapperClass, style: attrs['style']}, [ + props.label + ? h( + 'label', + { + class: withVendorCSSClassPrefix('password-field__label'), + for: props.name, + }, + [props.label, props.required ? h('span', {class: withVendorCSSClassPrefix('password-field__required')}, ' *') : null], + ) + : null, + h('div', {class: withVendorCSSClassPrefix('password-field__wrapper')}, [ + h('input', { + class: withVendorCSSClassPrefix('password-field__input'), + type: visible.value ? 'text' : 'password', + name: props.name, + id: props.name, + value: props.modelValue, + placeholder: props.placeholder, + required: props.required, + disabled: props.disabled, + 'data-testid': attrs['data-testid'], + onInput: (e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).value), + onBlur: () => emit('blur'), + }), + h( + 'button', + { + type: 'button', + class: withVendorCSSClassPrefix('password-field__toggle'), + 'aria-label': visible.value ? 'Hide password' : 'Show password', + tabindex: -1, + onClick: () => { + visible.value = !visible.value; + }, + }, + visible.value ? '\u25CF' : '\u25CB', + ), + ]), + hasError ? h('span', {class: withVendorCSSClassPrefix('password-field__error')}, props.error) : null, + ]); + }; + }, +}); + +export default PasswordField; diff --git a/packages/vue/src/components/primitives/Select.ts b/packages/vue/src/components/primitives/Select.ts new file mode 100644 index 000000000..de0051a38 --- /dev/null +++ b/packages/vue/src/components/primitives/Select.ts @@ -0,0 +1,87 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; + +export interface SelectOption { + label: string; + value: string; +} + +const Select = defineComponent({ + name: 'AsgardeoSelect', + props: { + modelValue: {type: String, default: ''}, + label: {type: String, default: undefined}, + name: {type: String, default: undefined}, + options: {type: Array as PropType, default: () => []}, + placeholder: {type: String, default: undefined}, + error: {type: String, default: undefined}, + helperText: {type: String, default: undefined}, + required: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + }, + emits: ['update:modelValue'], + setup(props, {emit, attrs}) { + return () => { + const hasError = !!props.error; + const wrapperClass = [ + withVendorCSSClassPrefix('select'), + hasError ? withVendorCSSClassPrefix('select--error') : '', + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '); + + return h('div', {class: wrapperClass, style: attrs['style']}, [ + props.label + ? h( + 'label', + {class: withVendorCSSClassPrefix('select__label'), for: props.name}, + [props.label, props.required ? h('span', {class: withVendorCSSClassPrefix('select__required')}, ' *') : null], + ) + : null, + h( + 'select', + { + class: withVendorCSSClassPrefix('select__input'), + name: props.name, + id: props.name, + value: props.modelValue, + required: props.required, + disabled: props.disabled, + 'data-testid': attrs['data-testid'], + onChange: (e: Event) => emit('update:modelValue', (e.target as HTMLSelectElement).value), + }, + [ + props.placeholder ? h('option', {value: '', disabled: true}, props.placeholder) : null, + ...props.options.map((opt) => h('option', {value: opt.value, key: opt.value}, opt.label)), + ], + ), + hasError + ? h('span', {class: withVendorCSSClassPrefix('select__error')}, props.error) + : props.helperText + ? h('span', {class: withVendorCSSClassPrefix('select__helper')}, props.helperText) + : null, + ]); + }; + }, +}); + +export default Select; diff --git a/packages/vue/src/components/primitives/Spinner.ts b/packages/vue/src/components/primitives/Spinner.ts new file mode 100644 index 000000000..a469ea7d9 --- /dev/null +++ b/packages/vue/src/components/primitives/Spinner.ts @@ -0,0 +1,73 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; + +const Spinner = defineComponent({ + name: 'Spinner', + props: { + size: { + type: String as PropType<'small' | 'medium' | 'large'>, + default: 'medium', + }, + }, + setup(props, {attrs}) { + return () => + h( + 'div', + { + class: [ + withVendorCSSClassPrefix('spinner'), + withVendorCSSClassPrefix(`spinner--${props.size}`), + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '), + style: attrs['style'], + role: 'status', + 'aria-label': 'Loading', + }, + [ + h( + 'svg', + { + class: withVendorCSSClassPrefix('spinner__svg'), + viewBox: '0 0 24 24', + xmlns: 'http://www.w3.org/2000/svg', + fill: 'none', + }, + [ + h('circle', { + class: withVendorCSSClassPrefix('spinner__circle'), + cx: '12', + cy: '12', + r: '10', + stroke: 'currentColor', + 'stroke-width': '3', + 'stroke-linecap': 'round', + 'stroke-dasharray': '31.4 31.4', + }), + ], + ), + ], + ); + }, +}); + +export default Spinner; diff --git a/packages/vue/src/components/primitives/TextField.ts b/packages/vue/src/components/primitives/TextField.ts new file mode 100644 index 000000000..fc13eae97 --- /dev/null +++ b/packages/vue/src/components/primitives/TextField.ts @@ -0,0 +1,86 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; + +const TextField = defineComponent({ + name: 'TextField', + props: { + modelValue: {type: String, default: ''}, + type: { + type: String as PropType<'text' | 'email' | 'number' | 'tel' | 'url'>, + default: 'text', + }, + label: {type: String, default: undefined}, + name: {type: String, default: undefined}, + placeholder: {type: String, default: undefined}, + error: {type: String, default: undefined}, + helperText: {type: String, default: undefined}, + required: {type: Boolean, default: false}, + disabled: {type: Boolean, default: false}, + autoComplete: {type: String, default: undefined}, + }, + emits: ['update:modelValue', 'blur'], + setup(props, {emit, attrs}) { + return () => { + const hasError = !!props.error; + const wrapperClass = [ + withVendorCSSClassPrefix('text-field'), + hasError ? withVendorCSSClassPrefix('text-field--error') : '', + (attrs['class'] as string) || '', + ] + .filter(Boolean) + .join(' '); + + return h('div', {class: wrapperClass, style: attrs['style']}, [ + props.label + ? h( + 'label', + { + class: withVendorCSSClassPrefix('text-field__label'), + for: props.name, + }, + [props.label, props.required ? h('span', {class: withVendorCSSClassPrefix('text-field__required')}, ' *') : null], + ) + : null, + h('input', { + class: withVendorCSSClassPrefix('text-field__input'), + type: props.type, + name: props.name, + id: props.name, + value: props.modelValue, + placeholder: props.placeholder, + required: props.required, + disabled: props.disabled, + autocomplete: props.autoComplete, + 'data-testid': attrs['data-testid'], + onInput: (e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).value), + onBlur: () => emit('blur'), + }), + hasError + ? h('span', {class: withVendorCSSClassPrefix('text-field__error')}, props.error) + : props.helperText + ? h('span', {class: withVendorCSSClassPrefix('text-field__helper')}, props.helperText) + : null, + ]); + }; + }, +}); + +export default TextField; diff --git a/packages/vue/src/components/primitives/Typography.ts b/packages/vue/src/components/primitives/Typography.ts new file mode 100644 index 000000000..0acd906d5 --- /dev/null +++ b/packages/vue/src/components/primitives/Typography.ts @@ -0,0 +1,67 @@ +/** + * 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 {withVendorCSSClassPrefix} from '@asgardeo/browser'; +import {defineComponent, h, type PropType} from 'vue'; + +const Typography = defineComponent({ + name: 'Typography', + props: { + variant: { + type: String as PropType<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'subtitle1' | 'subtitle2' | 'body1' | 'body2' | 'caption' | 'overline'>, + default: 'body1', + }, + component: { + type: String as PropType, + default: undefined, + }, + }, + setup(props, {slots, attrs}) { + return () => { + const tagMap: Record = { + h1: 'h1', + h2: 'h2', + h3: 'h3', + h4: 'h4', + h5: 'h5', + h6: 'h6', + subtitle1: 'h6', + subtitle2: 'h6', + body1: 'p', + body2: 'p', + caption: 'span', + overline: 'span', + }; + + const tag = props.component || tagMap[props.variant] || 'p'; + + return h( + tag, + { + class: [withVendorCSSClassPrefix('typography'), withVendorCSSClassPrefix(`typography--${props.variant}`), (attrs['class'] as string) || ''] + .filter(Boolean) + .join(' '), + style: attrs['style'], + }, + slots['default']?.(), + ); + }; + }, +}); + +export default Typography; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index fcaaa104e..bb5e9a7d1 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -71,6 +71,93 @@ export type { UserContextValue, } from './models/contexts'; +// ── UI Components — Primitives ── +export {default as Button} from './components/primitives/Button'; +export {default as Card} from './components/primitives/Card'; +export {default as Alert} from './components/primitives/Alert'; +export {default as TextField} from './components/primitives/TextField'; +export {default as PasswordField} from './components/primitives/PasswordField'; +export {default as Select} from './components/primitives/Select'; +export type {SelectOption} from './components/primitives/Select'; +export {default as Checkbox} from './components/primitives/Checkbox'; +export {default as DatePicker} from './components/primitives/DatePicker'; +export {default as OtpField} from './components/primitives/OtpField'; +export {default as Typography} from './components/primitives/Typography'; +export {default as Divider} from './components/primitives/Divider'; +export {default as Logo} from './components/primitives/Logo'; +export {default as Spinner} from './components/primitives/Spinner'; +export { + UserIcon, + EyeIcon, + EyeOffIcon, + ChevronDownIcon, + CheckIcon, + CircleAlertIcon, + CircleCheckIcon, + InfoIcon, + TriangleAlertIcon, + XIcon, + PlusIcon, + LogOutIcon, + ArrowLeftRightIcon, + BuildingIcon, + GlobeIcon, +} from './components/primitives/Icons'; + +// ── UI Components — Actions ── +export {default as SignInButton} from './components/actions/SignInButton'; +export {default as BaseSignInButton} from './components/actions/BaseSignInButton'; +export {default as SignOutButton} from './components/actions/SignOutButton'; +export {default as BaseSignOutButton} from './components/actions/BaseSignOutButton'; +export {default as SignUpButton} from './components/actions/SignUpButton'; +export {default as BaseSignUpButton} from './components/actions/BaseSignUpButton'; + +// ── UI Components — Auth Flow ── +export {default as Callback} from './components/auth/Callback'; + +// ── UI Components — Control ── +export {default as SignedIn} from './components/control/SignedIn'; +export {default as SignedOut} from './components/control/SignedOut'; +export {default as Loading} from './components/control/Loading'; + +// ── UI Components — Presentation ── +export {default as SignIn} from './components/presentation/SignIn'; +export {default as BaseSignIn} from './components/presentation/BaseSignIn'; +export {default as SignUp} from './components/presentation/SignUp'; +export {default as BaseSignUp} from './components/presentation/BaseSignUp'; +export {default as UserComponent} from './components/presentation/User'; +export {default as BaseUser} from './components/presentation/BaseUser'; +export {default as UserProfileComponent} from './components/presentation/UserProfile'; +export {default as BaseUserProfile} from './components/presentation/BaseUserProfile'; +export {default as UserDropdown} from './components/presentation/UserDropdown'; +export {default as BaseUserDropdown} from './components/presentation/BaseUserDropdown'; +export {default as AcceptInvite} from './components/presentation/AcceptInvite'; +export {default as BaseAcceptInvite} from './components/presentation/BaseAcceptInvite'; +export {default as InviteUser} from './components/presentation/InviteUser'; +export {default as BaseInviteUser} from './components/presentation/BaseInviteUser'; +export {default as OrganizationComponent} from './components/presentation/Organization'; +export {default as BaseOrganization} from './components/presentation/BaseOrganization'; +export {default as OrganizationList} from './components/presentation/OrganizationList'; +export {default as BaseOrganizationList} from './components/presentation/BaseOrganizationList'; +export {default as OrganizationProfile} from './components/presentation/OrganizationProfile'; +export {default as BaseOrganizationProfile} from './components/presentation/BaseOrganizationProfile'; +export {default as OrganizationSwitcher} from './components/presentation/OrganizationSwitcher'; +export {default as BaseOrganizationSwitcher} from './components/presentation/BaseOrganizationSwitcher'; +export {default as CreateOrganization} from './components/presentation/CreateOrganization'; +export {default as BaseCreateOrganization} from './components/presentation/BaseCreateOrganization'; +export {default as LanguageSwitcher} from './components/presentation/LanguageSwitcher'; +export {default as BaseLanguageSwitcher} from './components/presentation/BaseLanguageSwitcher'; + +// ── UI Components — Adapters ── +export {default as GoogleButton} from './components/adapters/GoogleButton'; +export {default as GitHubButton} from './components/adapters/GitHubButton'; +export {default as MicrosoftButton} from './components/adapters/MicrosoftButton'; +export {default as FacebookButton} from './components/adapters/FacebookButton'; + +// ── Factories ── +export {default as FieldFactory, createField, validateFieldValue} from './components/factories/FieldFactory'; +export type {FieldConfig} from './components/factories/FieldFactory'; + // ── Re-exports from @asgardeo/browser ── export { type AllOrganizationsApiResponse, From 6ac9c38de8201f8e23557550a5aa83516df08f3a Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Fri, 13 Mar 2026 15:18:33 +0530 Subject: [PATCH 04/22] feat: enhance button components and loading states with isLoading prop support --- packages/vue/src/components/adapters/FacebookButton.ts | 2 +- packages/vue/src/components/adapters/GitHubButton.ts | 2 +- packages/vue/src/components/adapters/GoogleButton.ts | 2 +- packages/vue/src/components/adapters/MicrosoftButton.ts | 2 +- packages/vue/src/components/auth/Callback.ts | 6 ++++++ packages/vue/src/components/control/Loading.ts | 4 ++-- packages/vue/src/components/control/SignedIn.ts | 4 ++-- packages/vue/src/components/control/SignedOut.ts | 4 ++-- 8 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/vue/src/components/adapters/FacebookButton.ts b/packages/vue/src/components/adapters/FacebookButton.ts index bf4df42fe..d88233912 100644 --- a/packages/vue/src/components/adapters/FacebookButton.ts +++ b/packages/vue/src/components/adapters/FacebookButton.ts @@ -58,7 +58,7 @@ const FacebookButton = defineComponent({ startIcon: facebookIcon(), onClick: (e: MouseEvent) => emit('click', e), }, - () => slots['default']?.() ?? (t('elements.buttons.facebook.text') || 'Sign in with Facebook'), + () => slots['default']?.({isLoading: props.isLoading}) ?? (t('elements.buttons.facebook.text') || 'Sign in with Facebook'), ); }, }); diff --git a/packages/vue/src/components/adapters/GitHubButton.ts b/packages/vue/src/components/adapters/GitHubButton.ts index 009be70cf..8be36f6cf 100644 --- a/packages/vue/src/components/adapters/GitHubButton.ts +++ b/packages/vue/src/components/adapters/GitHubButton.ts @@ -60,7 +60,7 @@ const GitHubButton = defineComponent({ startIcon: gitHubIcon(), onClick: (e: MouseEvent) => emit('click', e), }, - () => slots['default']?.() ?? (t('elements.buttons.github.text') || 'Sign in with GitHub'), + () => slots['default']?.({isLoading: props.isLoading}) ?? (t('elements.buttons.github.text') || 'Sign in with GitHub'), ); }, }); diff --git a/packages/vue/src/components/adapters/GoogleButton.ts b/packages/vue/src/components/adapters/GoogleButton.ts index 5412aedce..b22c1256e 100644 --- a/packages/vue/src/components/adapters/GoogleButton.ts +++ b/packages/vue/src/components/adapters/GoogleButton.ts @@ -76,7 +76,7 @@ const GoogleButton = defineComponent({ startIcon: googleIcon(), onClick: (e: MouseEvent) => emit('click', e), }, - () => slots['default']?.() ?? (t('elements.buttons.google.text') || 'Sign in with Google'), + () => slots['default']?.({isLoading: props.isLoading}) ?? (t('elements.buttons.google.text') || 'Sign in with Google'), ); }, }); diff --git a/packages/vue/src/components/adapters/MicrosoftButton.ts b/packages/vue/src/components/adapters/MicrosoftButton.ts index 73db32570..985b778f3 100644 --- a/packages/vue/src/components/adapters/MicrosoftButton.ts +++ b/packages/vue/src/components/adapters/MicrosoftButton.ts @@ -55,7 +55,7 @@ const MicrosoftButton = defineComponent({ startIcon: microsoftIcon(), onClick: (e: MouseEvent) => emit('click', e), }, - () => slots['default']?.() ?? (t('elements.buttons.microsoft.text') || 'Sign in with Microsoft'), + () => slots['default']?.({isLoading: props.isLoading}) ?? (t('elements.buttons.microsoft.text') || 'Sign in with Microsoft'), ); }, }); diff --git a/packages/vue/src/components/auth/Callback.ts b/packages/vue/src/components/auth/Callback.ts index 3fa0d86dd..8a30dee40 100644 --- a/packages/vue/src/components/auth/Callback.ts +++ b/packages/vue/src/components/auth/Callback.ts @@ -57,6 +57,12 @@ const Callback = defineComponent({ const oauthError = urlParams.get('error'); const errorDescription = urlParams.get('error_description'); + // If no OAuth parameters are present, this component is not on a real callback + // route — do nothing and return early. + if (!code && !state && !oauthError) { + return; + } + // 2. Validate and retrieve OAuth state from sessionStorage if (!state) { throw new Error('Missing OAuth state parameter - possible security issue'); diff --git a/packages/vue/src/components/control/Loading.ts b/packages/vue/src/components/control/Loading.ts index 55037a7a2..78acf1282 100644 --- a/packages/vue/src/components/control/Loading.ts +++ b/packages/vue/src/components/control/Loading.ts @@ -39,11 +39,11 @@ const Loading = defineComponent({ return (): VNode | VNode[] | null => { if (!isLoading.value) { - const fallbackContent = slots.fallback?.(); + const fallbackContent = slots['fallback']?.(); return fallbackContent ? h(Fragment, {}, fallbackContent) : null; } - const defaultContent = slots.default?.(); + const defaultContent = slots['default']?.(); return defaultContent ? h(Fragment, {}, defaultContent) : null; }; }, diff --git a/packages/vue/src/components/control/SignedIn.ts b/packages/vue/src/components/control/SignedIn.ts index 13bf01ac6..07726add5 100644 --- a/packages/vue/src/components/control/SignedIn.ts +++ b/packages/vue/src/components/control/SignedIn.ts @@ -39,11 +39,11 @@ const SignedIn = defineComponent({ return (): VNode | VNode[] | null => { if (!isSignedIn.value) { - const fallbackContent = slots.fallback?.(); + const fallbackContent = slots['fallback']?.(); return fallbackContent ? h(Fragment, {}, fallbackContent) : null; } - const defaultContent = slots.default?.(); + const defaultContent = slots['default']?.(); return defaultContent ? h(Fragment, {}, defaultContent) : null; }; }, diff --git a/packages/vue/src/components/control/SignedOut.ts b/packages/vue/src/components/control/SignedOut.ts index 78349cb3c..33a17141e 100644 --- a/packages/vue/src/components/control/SignedOut.ts +++ b/packages/vue/src/components/control/SignedOut.ts @@ -39,11 +39,11 @@ const SignedOut = defineComponent({ return (): VNode | VNode[] | null => { if (isSignedIn.value) { - const fallbackContent = slots.fallback?.(); + const fallbackContent = slots['fallback']?.(); return fallbackContent ? h(Fragment, {}, fallbackContent) : null; } - const defaultContent = slots.default?.(); + const defaultContent = slots['default']?.(); return defaultContent ? h(Fragment, {}, defaultContent) : null; }; }, From fa761001d4a110236d2461258e4fbb9425373bd7 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Fri, 13 Mar 2026 16:58:36 +0530 Subject: [PATCH 05/22] fix: primitive components styling issue is resolved - add injectStyles function for dynamic CSS variable injection in Asgardeo components --- packages/vue/src/plugins/AsgardeoPlugin.ts | 2 + packages/vue/src/providers/ThemeProvider.ts | 22 +- packages/vue/src/styles/injectStyles.ts | 968 ++++++++++++++++++++ 3 files changed, 973 insertions(+), 19 deletions(-) create mode 100644 packages/vue/src/styles/injectStyles.ts diff --git a/packages/vue/src/plugins/AsgardeoPlugin.ts b/packages/vue/src/plugins/AsgardeoPlugin.ts index 42c51ff68..4502c7092 100644 --- a/packages/vue/src/plugins/AsgardeoPlugin.ts +++ b/packages/vue/src/plugins/AsgardeoPlugin.ts @@ -19,6 +19,7 @@ import type {Plugin} from 'vue'; import AsgardeoProvider from '../components/AsgardeoProvider'; import type {AsgardeoVueConfig} from '../models/config'; +import {injectStyles} from '../styles/injectStyles'; /** * Vue plugin for Asgardeo authentication. @@ -48,6 +49,7 @@ import type {AsgardeoVueConfig} from '../models/config'; */ const AsgardeoPlugin: Plugin = { install(app) { + injectStyles(); app.component('AsgardeoProvider', AsgardeoProvider); }, }; diff --git a/packages/vue/src/providers/ThemeProvider.ts b/packages/vue/src/providers/ThemeProvider.ts index 51cb1247f..c456eb988 100644 --- a/packages/vue/src/providers/ThemeProvider.ts +++ b/packages/vue/src/providers/ThemeProvider.ts @@ -166,29 +166,13 @@ const ThemeProvider = defineComponent({ const applyToDom = (theme: Theme) => { if (typeof document === 'undefined') return; const root = document.documentElement; - const flat = flattenTheme(theme); - Object.entries(flat).forEach(([key, value]) => { + // Use the pre-computed cssVariables map from createTheme() which contains + // correctly-named CSS variables (e.g. --asgardeo-color-primary-main). + Object.entries(theme.cssVariables).forEach(([key, value]) => { root.style.setProperty(key, value); }); }; - const flattenTheme = (theme: Theme, prefix = '--asgardeo'): Record => { - const result: Record = {}; - const traverse = (obj: any, path: string) => { - if (typeof obj !== 'object' || obj === null) return; - Object.entries(obj).forEach(([k, v]) => { - const newPath = `${path}-${k}`; - if (typeof v === 'string' || typeof v === 'number') { - result[newPath] = String(v); - } else if (typeof v === 'object') { - traverse(v, newPath); - } - }); - }; - traverse(theme, prefix); - return result; - }; - watch(resolvedTheme, (theme) => applyToDom(theme), {immediate: true}); // Apply direction to document diff --git a/packages/vue/src/styles/injectStyles.ts b/packages/vue/src/styles/injectStyles.ts new file mode 100644 index 000000000..46de3b6fc --- /dev/null +++ b/packages/vue/src/styles/injectStyles.ts @@ -0,0 +1,968 @@ +/** + * 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. + */ + +const STYLE_ID = 'asgardeo-vue-styles'; + +/** + * Component styles for all Asgardeo Vue primitive components. + * + * Uses CSS custom properties (variables) that are set by ThemeProvider. + * The :root block provides default fallback values so components render + * correctly even without a ThemeProvider in the tree. + */ +const STYLES = ` +/* ============================================================ + Asgardeo Vue SDK – Default CSS variable fallbacks + (ThemeProvider will override these when present) + ============================================================ */ +:root { + --asgardeo-color-primary-main: #1a73e8; + --asgardeo-color-primary-dark: #174ea6; + --asgardeo-color-primary-contrastText: #ffffff; + --asgardeo-color-secondary-main: #424242; + --asgardeo-color-secondary-contrastText: #ffffff; + --asgardeo-color-background-surface: #ffffff; + --asgardeo-color-background-disabled: #f0f0f0; + --asgardeo-color-text-primary: #1a1a1a; + --asgardeo-color-text-secondary: #666666; + --asgardeo-color-border: #e0e0e0; + --asgardeo-color-action-hover: rgba(0, 0, 0, 0.04); + --asgardeo-color-action-selected: rgba(0, 0, 0, 0.08); + --asgardeo-color-action-focus: rgba(0, 0, 0, 0.12); + --asgardeo-color-action-disabled: rgba(0, 0, 0, 0.26); + --asgardeo-color-action-disabledBackground: rgba(0, 0, 0, 0.12); + --asgardeo-color-error-main: #d32f2f; + --asgardeo-color-error-contrastText: #b71c1c; + --asgardeo-color-success-main: #4caf50; + --asgardeo-color-success-contrastText: #1b5e20; + --asgardeo-color-warning-main: #ff9800; + --asgardeo-color-warning-contrastText: #e65100; + --asgardeo-color-info-main: #bbebff; + --asgardeo-color-info-contrastText: #01579b; + --asgardeo-spacing-unit: 8px; + --asgardeo-border-radius-small: 4px; + --asgardeo-border-radius-medium: 8px; + --asgardeo-border-radius-large: 16px; + --asgardeo-shadow-small: 0 2px 8px rgba(0, 0, 0, 0.1); + --asgardeo-shadow-medium: 0 4px 16px rgba(0, 0, 0, 0.15); + --asgardeo-shadow-large: 0 8px 32px rgba(0, 0, 0, 0.2); + --asgardeo-typography-fontFamily: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --asgardeo-typography-fontSize-xs: 0.75rem; + --asgardeo-typography-fontSize-sm: 0.875rem; + --asgardeo-typography-fontSize-md: 1rem; + --asgardeo-typography-fontSize-lg: 1.125rem; + --asgardeo-typography-fontSize-xl: 1.25rem; + --asgardeo-typography-fontSize-2xl: 1.5rem; + --asgardeo-typography-fontSize-3xl: 2.125rem; + --asgardeo-typography-fontWeight-normal: 400; + --asgardeo-typography-fontWeight-medium: 500; + --asgardeo-typography-fontWeight-semibold: 600; + --asgardeo-typography-fontWeight-bold: 700; + --asgardeo-typography-lineHeight-tight: 1.2; + --asgardeo-typography-lineHeight-normal: 1.4; + --asgardeo-typography-lineHeight-relaxed: 1.6; +} + +/* ============================================================ + BUTTON + ============================================================ */ + +.asgardeo-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: calc(var(--asgardeo-spacing-unit) * 1); + border-radius: var(--asgardeo-border-radius-medium); + font-family: var(--asgardeo-typography-fontFamily); + font-weight: var(--asgardeo-typography-fontWeight-medium); + cursor: pointer; + outline: none; + text-decoration: none; + white-space: nowrap; + border-width: 1px; + border-style: solid; + box-sizing: border-box; + transition: background-color 0.2s, color 0.2s, border-color 0.2s, opacity 0.2s, filter 0.2s; + position: relative; + vertical-align: middle; +} + +.asgardeo-button:focus-visible { + outline: 2px solid var(--asgardeo-color-primary-main); + outline-offset: 2px; +} + +/* -- Sizes -- */ + +.asgardeo-button--small { + padding: calc(var(--asgardeo-spacing-unit) * 0.5) calc(var(--asgardeo-spacing-unit) * 1); + font-size: var(--asgardeo-typography-fontSize-sm); + min-height: calc(var(--asgardeo-spacing-unit) * 3); +} + +.asgardeo-button--medium { + padding: calc(var(--asgardeo-spacing-unit) * 1) calc(var(--asgardeo-spacing-unit) * 2); + font-size: var(--asgardeo-typography-fontSize-md); + min-height: calc(var(--asgardeo-spacing-unit) * 4); +} + +.asgardeo-button--large { + padding: calc(var(--asgardeo-spacing-unit) * 1.5) calc(var(--asgardeo-spacing-unit) * 3); + font-size: var(--asgardeo-typography-fontSize-lg); + min-height: calc(var(--asgardeo-spacing-unit) * 5); +} + +/* -- Modifiers -- */ + +.asgardeo-button--full-width { + width: 100%; +} + +.asgardeo-button--loading, +.asgardeo-button:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +/* -- Solid variants -- */ + +.asgardeo-button--solid.asgardeo-button--primary { + background-color: var(--asgardeo-color-primary-main); + color: var(--asgardeo-color-primary-contrastText); + border-color: var(--asgardeo-color-primary-main); +} +.asgardeo-button--solid.asgardeo-button--primary:hover:not(:disabled) { + background-color: var(--asgardeo-color-primary-dark); + border-color: var(--asgardeo-color-primary-dark); +} + +.asgardeo-button--solid.asgardeo-button--secondary { + background-color: var(--asgardeo-color-secondary-main); + color: var(--asgardeo-color-secondary-contrastText); + border-color: var(--asgardeo-color-secondary-main); +} +.asgardeo-button--solid.asgardeo-button--secondary:hover:not(:disabled) { + filter: brightness(1.15); +} + +.asgardeo-button--solid.asgardeo-button--danger { + background-color: var(--asgardeo-color-error-main); + color: #ffffff; + border-color: var(--asgardeo-color-error-main); +} +.asgardeo-button--solid.asgardeo-button--danger:hover:not(:disabled) { + filter: brightness(0.9); +} + +/* -- Outline variants -- */ + +.asgardeo-button--outline.asgardeo-button--primary { + background-color: transparent; + color: var(--asgardeo-color-primary-main); + border-color: var(--asgardeo-color-primary-main); +} +.asgardeo-button--outline.asgardeo-button--primary:hover:not(:disabled) { + background-color: var(--asgardeo-color-primary-main); + color: var(--asgardeo-color-primary-contrastText); +} + +.asgardeo-button--outline.asgardeo-button--secondary { + background-color: transparent; + color: var(--asgardeo-color-secondary-main); + border-color: var(--asgardeo-color-secondary-main); +} +.asgardeo-button--outline.asgardeo-button--secondary:hover:not(:disabled) { + background-color: var(--asgardeo-color-secondary-main); + color: var(--asgardeo-color-secondary-contrastText); +} + +.asgardeo-button--outline.asgardeo-button--danger { + background-color: transparent; + color: var(--asgardeo-color-error-main); + border-color: var(--asgardeo-color-error-main); +} +.asgardeo-button--outline.asgardeo-button--danger:hover:not(:disabled) { + background-color: var(--asgardeo-color-error-main); + color: #ffffff; +} + +/* -- Ghost variants -- */ + +.asgardeo-button--ghost.asgardeo-button--primary { + background-color: transparent; + color: var(--asgardeo-color-primary-main); + border-color: transparent; +} +.asgardeo-button--ghost.asgardeo-button--primary:hover:not(:disabled) { + background-color: var(--asgardeo-color-action-hover); + border-color: transparent; +} + +.asgardeo-button--ghost.asgardeo-button--secondary { + background-color: transparent; + color: var(--asgardeo-color-secondary-main); + border-color: transparent; +} +.asgardeo-button--ghost.asgardeo-button--secondary:hover:not(:disabled) { + background-color: var(--asgardeo-color-action-hover); + border-color: transparent; +} + +.asgardeo-button--ghost.asgardeo-button--danger { + background-color: transparent; + color: var(--asgardeo-color-error-main); + border-color: transparent; +} +.asgardeo-button--ghost.asgardeo-button--danger:hover:not(:disabled) { + background-color: rgba(211, 47, 47, 0.08); + border-color: transparent; +} + +/* -- Text variants -- */ + +.asgardeo-button--text { + border-color: transparent; + background-color: transparent; + padding-left: 0; + padding-right: 0; +} + +.asgardeo-button--text.asgardeo-button--primary { + color: var(--asgardeo-color-primary-main); +} +.asgardeo-button--text.asgardeo-button--primary:hover:not(:disabled) { + text-decoration: underline; +} + +.asgardeo-button--text.asgardeo-button--secondary { + color: var(--asgardeo-color-secondary-main); +} +.asgardeo-button--text.asgardeo-button--secondary:hover:not(:disabled) { + text-decoration: underline; +} + +.asgardeo-button--text.asgardeo-button--danger { + color: var(--asgardeo-color-error-main); +} +.asgardeo-button--text.asgardeo-button--danger:hover:not(:disabled) { + text-decoration: underline; +} + +/* -- Inner elements -- */ + +.asgardeo-button__start-icon, +.asgardeo-button__end-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.asgardeo-button__content { + display: inline-flex; + align-items: center; +} + +.asgardeo-button__spinner { + display: inline-block; + width: 1em; + height: 1em; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: asgardeo-spin 0.6s linear infinite; + margin-left: calc(var(--asgardeo-spacing-unit) * 0.5); +} + +@keyframes asgardeo-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* ============================================================ + CARD + ============================================================ */ + +.asgardeo-card { + background-color: var(--asgardeo-color-background-surface); + border-radius: var(--asgardeo-border-radius-medium); + padding: calc(var(--asgardeo-spacing-unit) * 3); + box-sizing: border-box; +} + +.asgardeo-card--elevated { + box-shadow: var(--asgardeo-shadow-medium); +} + +.asgardeo-card--outlined { + border: 1px solid var(--asgardeo-color-border); +} + +/* .asgardeo-card--flat: no shadow or border */ + +/* ============================================================ + TYPOGRAPHY + ============================================================ */ + +.asgardeo-typography { + font-family: var(--asgardeo-typography-fontFamily); + color: var(--asgardeo-color-text-primary); + margin: 0; + line-height: var(--asgardeo-typography-lineHeight-normal); +} + +.asgardeo-typography--h1 { + font-size: var(--asgardeo-typography-fontSize-3xl); + font-weight: var(--asgardeo-typography-fontWeight-bold); + line-height: var(--asgardeo-typography-lineHeight-tight); +} + +.asgardeo-typography--h2 { + font-size: var(--asgardeo-typography-fontSize-2xl); + font-weight: var(--asgardeo-typography-fontWeight-bold); + line-height: var(--asgardeo-typography-lineHeight-tight); +} + +.asgardeo-typography--h3 { + font-size: var(--asgardeo-typography-fontSize-xl); + font-weight: var(--asgardeo-typography-fontWeight-semibold); +} + +.asgardeo-typography--h4 { + font-size: var(--asgardeo-typography-fontSize-lg); + font-weight: var(--asgardeo-typography-fontWeight-semibold); +} + +.asgardeo-typography--h5 { + font-size: var(--asgardeo-typography-fontSize-md); + font-weight: var(--asgardeo-typography-fontWeight-semibold); +} + +.asgardeo-typography--h6 { + font-size: var(--asgardeo-typography-fontSize-sm); + font-weight: var(--asgardeo-typography-fontWeight-semibold); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.asgardeo-typography--subtitle1 { + font-size: var(--asgardeo-typography-fontSize-lg); + font-weight: var(--asgardeo-typography-fontWeight-medium); +} + +.asgardeo-typography--subtitle2 { + font-size: var(--asgardeo-typography-fontSize-md); + font-weight: var(--asgardeo-typography-fontWeight-medium); + color: var(--asgardeo-color-text-secondary); +} + +.asgardeo-typography--body1 { + font-size: var(--asgardeo-typography-fontSize-md); + font-weight: var(--asgardeo-typography-fontWeight-normal); + line-height: var(--asgardeo-typography-lineHeight-relaxed); +} + +.asgardeo-typography--body2 { + font-size: var(--asgardeo-typography-fontSize-sm); + font-weight: var(--asgardeo-typography-fontWeight-normal); + line-height: var(--asgardeo-typography-lineHeight-relaxed); + color: var(--asgardeo-color-text-secondary); +} + +.asgardeo-typography--caption { + font-size: var(--asgardeo-typography-fontSize-xs); + font-weight: var(--asgardeo-typography-fontWeight-normal); + color: var(--asgardeo-color-text-secondary); +} + +.asgardeo-typography--overline { + font-size: var(--asgardeo-typography-fontSize-xs); + font-weight: var(--asgardeo-typography-fontWeight-medium); + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--asgardeo-color-text-secondary); +} + +/* ============================================================ + ALERT + ============================================================ */ + +.asgardeo-alert { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: calc(var(--asgardeo-spacing-unit) * 1); + padding: calc(var(--asgardeo-spacing-unit) * 1.5) calc(var(--asgardeo-spacing-unit) * 2); + border-radius: var(--asgardeo-border-radius-small); + border: 1px solid transparent; + font-family: var(--asgardeo-typography-fontFamily); + font-size: var(--asgardeo-typography-fontSize-sm); + box-sizing: border-box; + width: 100%; + line-height: var(--asgardeo-typography-lineHeight-normal); +} + +.asgardeo-alert__content { + flex: 1; +} + +.asgardeo-alert--info { + background-color: #e3f2fd; + border-color: var(--asgardeo-color-info-contrastText); + color: var(--asgardeo-color-info-contrastText); +} + +.asgardeo-alert--success { + background-color: #e8f5e9; + border-color: #388e3c; + color: var(--asgardeo-color-success-contrastText); +} + +.asgardeo-alert--warning { + background-color: #fff8e1; + border-color: #f57c00; + color: var(--asgardeo-color-warning-contrastText); +} + +.asgardeo-alert--error { + background-color: #ffebee; + border-color: var(--asgardeo-color-error-main); + color: var(--asgardeo-color-error-contrastText); +} + +.asgardeo-alert__dismiss { + background: none; + border: none; + cursor: pointer; + font-size: 1.2em; + line-height: 1; + padding: 0; + color: inherit; + opacity: 0.7; + flex-shrink: 0; +} +.asgardeo-alert__dismiss:hover { + opacity: 1; +} + +/* ============================================================ + TEXT FIELD + ============================================================ */ + +.asgardeo-text-field { + display: flex; + flex-direction: column; + gap: calc(var(--asgardeo-spacing-unit) * 0.5); + font-family: var(--asgardeo-typography-fontFamily); + width: 100%; + box-sizing: border-box; +} + +.asgardeo-text-field__label { + font-size: var(--asgardeo-typography-fontSize-sm); + font-weight: var(--asgardeo-typography-fontWeight-medium); + color: var(--asgardeo-color-text-primary); + display: block; +} + +.asgardeo-text-field__required { + color: var(--asgardeo-color-error-main); + margin-left: 2px; +} + +.asgardeo-text-field__input { + width: 100%; + padding: calc(var(--asgardeo-spacing-unit) * 1) calc(var(--asgardeo-spacing-unit) * 1.5); + border: 1px solid var(--asgardeo-color-border); + border-radius: var(--asgardeo-border-radius-small); + font-family: var(--asgardeo-typography-fontFamily); + font-size: var(--asgardeo-typography-fontSize-md); + color: var(--asgardeo-color-text-primary); + background-color: var(--asgardeo-color-background-surface); + box-sizing: border-box; + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; +} +.asgardeo-text-field__input:focus { + border-color: var(--asgardeo-color-primary-main); + box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.15); +} +.asgardeo-text-field__input::placeholder { + color: var(--asgardeo-color-text-secondary); +} +.asgardeo-text-field__input:disabled { + background-color: var(--asgardeo-color-background-disabled); + cursor: not-allowed; +} + +.asgardeo-text-field--error .asgardeo-text-field__input { + border-color: var(--asgardeo-color-error-main); +} +.asgardeo-text-field--error .asgardeo-text-field__input:focus { + box-shadow: 0 0 0 2px rgba(211, 47, 47, 0.15); +} + +.asgardeo-text-field__error { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-error-contrastText); +} + +.asgardeo-text-field__helper { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-text-secondary); +} + +/* ============================================================ + PASSWORD FIELD + ============================================================ */ + +.asgardeo-password-field { + display: flex; + flex-direction: column; + gap: calc(var(--asgardeo-spacing-unit) * 0.5); + font-family: var(--asgardeo-typography-fontFamily); + width: 100%; + box-sizing: border-box; +} + +.asgardeo-password-field__label { + font-size: var(--asgardeo-typography-fontSize-sm); + font-weight: var(--asgardeo-typography-fontWeight-medium); + color: var(--asgardeo-color-text-primary); + display: block; +} + +.asgardeo-password-field__required { + color: var(--asgardeo-color-error-main); + margin-left: 2px; +} + +.asgardeo-password-field__wrapper { + display: flex; + align-items: center; + border: 1px solid var(--asgardeo-color-border); + border-radius: var(--asgardeo-border-radius-small); + background-color: var(--asgardeo-color-background-surface); + transition: border-color 0.2s, box-shadow 0.2s; + overflow: hidden; +} +.asgardeo-password-field__wrapper:focus-within { + border-color: var(--asgardeo-color-primary-main); + box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.15); +} +.asgardeo-password-field--error .asgardeo-password-field__wrapper { + border-color: var(--asgardeo-color-error-main); +} + +.asgardeo-password-field__input { + flex: 1; + padding: calc(var(--asgardeo-spacing-unit) * 1) calc(var(--asgardeo-spacing-unit) * 1.5); + border: none; + outline: none; + font-family: var(--asgardeo-typography-fontFamily); + font-size: var(--asgardeo-typography-fontSize-md); + color: var(--asgardeo-color-text-primary); + background: transparent; + width: 100%; + box-sizing: border-box; + min-width: 0; +} +.asgardeo-password-field__input::placeholder { + color: var(--asgardeo-color-text-secondary); +} +.asgardeo-password-field__input:disabled { + cursor: not-allowed; +} + +.asgardeo-password-field__toggle { + background: none; + border: none; + cursor: pointer; + padding: 0 calc(var(--asgardeo-spacing-unit) * 1.5); + color: var(--asgardeo-color-text-secondary); + font-size: var(--asgardeo-typography-fontSize-md); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + height: 100%; + min-height: calc(var(--asgardeo-spacing-unit) * 4); +} +.asgardeo-password-field__toggle:hover { + color: var(--asgardeo-color-text-primary); +} + +.asgardeo-password-field__error { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-error-contrastText); +} + +/* ============================================================ + SELECT + ============================================================ */ + +.asgardeo-select { + display: flex; + flex-direction: column; + gap: calc(var(--asgardeo-spacing-unit) * 0.5); + font-family: var(--asgardeo-typography-fontFamily); + width: 100%; + box-sizing: border-box; +} + +.asgardeo-select__label { + font-size: var(--asgardeo-typography-fontSize-sm); + font-weight: var(--asgardeo-typography-fontWeight-medium); + color: var(--asgardeo-color-text-primary); + display: block; +} + +.asgardeo-select__required { + color: var(--asgardeo-color-error-main); + margin-left: 2px; +} + +.asgardeo-select__input { + width: 100%; + padding: calc(var(--asgardeo-spacing-unit) * 1) calc(var(--asgardeo-spacing-unit) * 4) calc(var(--asgardeo-spacing-unit) * 1) calc(var(--asgardeo-spacing-unit) * 1.5); + border: 1px solid var(--asgardeo-color-border); + border-radius: var(--asgardeo-border-radius-small); + font-family: var(--asgardeo-typography-fontFamily); + font-size: var(--asgardeo-typography-fontSize-md); + color: var(--asgardeo-color-text-primary); + background-color: var(--asgardeo-color-background-surface); + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right calc(var(--asgardeo-spacing-unit) * 1.5) center; + cursor: pointer; + box-sizing: border-box; + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; + line-height: var(--asgardeo-typography-lineHeight-normal); +} +.asgardeo-select__input:focus { + border-color: var(--asgardeo-color-primary-main); + box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.15); +} +.asgardeo-select__input:disabled { + background-color: var(--asgardeo-color-background-disabled); + cursor: not-allowed; +} + +.asgardeo-select--error .asgardeo-select__input { + border-color: var(--asgardeo-color-error-main); +} + +.asgardeo-select__error { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-error-contrastText); +} + +.asgardeo-select__helper { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-text-secondary); +} + +/* ============================================================ + CHECKBOX + ============================================================ */ + +.asgardeo-checkbox { + display: flex; + flex-direction: column; + gap: calc(var(--asgardeo-spacing-unit) * 0.5); + font-family: var(--asgardeo-typography-fontFamily); +} + +.asgardeo-checkbox__wrapper { + display: flex; + align-items: center; + gap: calc(var(--asgardeo-spacing-unit) * 1); + cursor: pointer; +} + +.asgardeo-checkbox__input { + width: 16px; + height: 16px; + cursor: pointer; + accent-color: var(--asgardeo-color-primary-main); + flex-shrink: 0; +} +.asgardeo-checkbox__input:disabled { + cursor: not-allowed; +} + +.asgardeo-checkbox__label { + font-size: var(--asgardeo-typography-fontSize-md); + color: var(--asgardeo-color-text-primary); +} + +.asgardeo-checkbox__error { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-error-contrastText); +} + +/* ============================================================ + DATE PICKER + ============================================================ */ + +.asgardeo-date-picker { + display: flex; + flex-direction: column; + gap: calc(var(--asgardeo-spacing-unit) * 0.5); + font-family: var(--asgardeo-typography-fontFamily); + width: 100%; + box-sizing: border-box; +} + +.asgardeo-date-picker__label { + font-size: var(--asgardeo-typography-fontSize-sm); + font-weight: var(--asgardeo-typography-fontWeight-medium); + color: var(--asgardeo-color-text-primary); + display: block; +} + +.asgardeo-date-picker__required { + color: var(--asgardeo-color-error-main); + margin-left: 2px; +} + +.asgardeo-date-picker__input { + width: 100%; + padding: calc(var(--asgardeo-spacing-unit) * 1) calc(var(--asgardeo-spacing-unit) * 1.5); + border: 1px solid var(--asgardeo-color-border); + border-radius: var(--asgardeo-border-radius-small); + font-family: var(--asgardeo-typography-fontFamily); + font-size: var(--asgardeo-typography-fontSize-md); + color: var(--asgardeo-color-text-primary); + background-color: var(--asgardeo-color-background-surface); + box-sizing: border-box; + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; + cursor: pointer; +} +.asgardeo-date-picker__input:focus { + border-color: var(--asgardeo-color-primary-main); + box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.15); +} +.asgardeo-date-picker--error .asgardeo-date-picker__input { + border-color: var(--asgardeo-color-error-main); +} +.asgardeo-date-picker__input:disabled { + background-color: var(--asgardeo-color-background-disabled); + cursor: not-allowed; +} + +.asgardeo-date-picker__error { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-error-contrastText); +} + +/* ============================================================ + OTP FIELD + ============================================================ */ + +.asgardeo-otp-field { + display: flex; + flex-direction: column; + gap: calc(var(--asgardeo-spacing-unit) * 1); + font-family: var(--asgardeo-typography-fontFamily); +} + +.asgardeo-otp-field__label { + font-size: var(--asgardeo-typography-fontSize-sm); + font-weight: var(--asgardeo-typography-fontWeight-medium); + color: var(--asgardeo-color-text-primary); + display: block; +} + +.asgardeo-otp-field__required { + color: var(--asgardeo-color-error-main); + margin-left: 2px; +} + +.asgardeo-otp-field__inputs { + display: flex; + gap: calc(var(--asgardeo-spacing-unit) * 1); +} + +.asgardeo-otp-field__digit { + width: calc(var(--asgardeo-spacing-unit) * 5); + height: calc(var(--asgardeo-spacing-unit) * 5); + text-align: center; + border: 1px solid var(--asgardeo-color-border); + border-radius: var(--asgardeo-border-radius-small); + font-family: var(--asgardeo-typography-fontFamily); + font-size: var(--asgardeo-typography-fontSize-lg); + font-weight: var(--asgardeo-typography-fontWeight-semibold); + color: var(--asgardeo-color-text-primary); + background-color: var(--asgardeo-color-background-surface); + box-sizing: border-box; + outline: none; + transition: border-color 0.2s, box-shadow 0.2s; +} +.asgardeo-otp-field__digit:focus { + border-color: var(--asgardeo-color-primary-main); + box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.15); +} +.asgardeo-otp-field__digit:disabled { + background-color: var(--asgardeo-color-background-disabled); + cursor: not-allowed; +} + +.asgardeo-otp-field__error { + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-error-contrastText); +} + +/* ============================================================ + DIVIDER + ============================================================ */ + +.asgardeo-divider { + box-sizing: border-box; +} + +.asgardeo-divider--horizontal { + width: 100%; + border: none; + border-top: 1px solid var(--asgardeo-color-border); + margin: calc(var(--asgardeo-spacing-unit) * 1) 0; +} + +.asgardeo-divider--vertical { + display: inline-block; + width: 1px; + height: 100%; + min-height: 1em; + border: none; + background-color: var(--asgardeo-color-border); + margin: 0 calc(var(--asgardeo-spacing-unit) * 1); + align-self: stretch; +} + +.asgardeo-divider--with-content { + display: flex; + align-items: center; + gap: calc(var(--asgardeo-spacing-unit) * 1); + border: none; + margin: calc(var(--asgardeo-spacing-unit) * 1) 0; +} + +.asgardeo-divider__line { + flex: 1; + height: 1px; + background-color: var(--asgardeo-color-border); +} + +.asgardeo-divider__content { + flex-shrink: 0; + font-size: var(--asgardeo-typography-fontSize-xs); + color: var(--asgardeo-color-text-secondary); + padding: 0 calc(var(--asgardeo-spacing-unit) * 0.5); +} + +/* ============================================================ + LOGO + ============================================================ */ + +.asgardeo-logo { + display: inline-flex; + align-items: center; + text-decoration: none; +} + +.asgardeo-logo__image { + display: block; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; +} + +/* ============================================================ + SPINNER + ============================================================ */ + +.asgardeo-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--asgardeo-color-primary-main); +} + +.asgardeo-spinner--small { + width: calc(var(--asgardeo-spacing-unit) * 2); + height: calc(var(--asgardeo-spacing-unit) * 2); +} + +.asgardeo-spinner--medium { + width: calc(var(--asgardeo-spacing-unit) * 3); + height: calc(var(--asgardeo-spacing-unit) * 3); +} + +.asgardeo-spinner--large { + width: calc(var(--asgardeo-spacing-unit) * 4); + height: calc(var(--asgardeo-spacing-unit) * 4); +} + +.asgardeo-spinner__svg { + width: 100%; + height: 100%; + animation: asgardeo-spin 1.4s linear infinite; +} + +.asgardeo-spinner__circle { + stroke-dasharray: 80, 200; + stroke-dashoffset: 0; + animation: asgardeo-spinner-dash 1.4s ease-in-out infinite; +} + +@keyframes asgardeo-spinner-dash { + 0% { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -35px; + } + 100% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -124px; + } +} +`; + +/** + * Injects Asgardeo Vue component styles into the document `` once. + * Subsequent calls are no-ops (idempotent). + */ +export function injectStyles(): void { + if (typeof document === 'undefined') return; + if (document.getElementById(STYLE_ID)) return; + + const style = document.createElement('style'); + style.id = STYLE_ID; + style.textContent = STYLES; + document.head.appendChild(style); +} + +export default injectStyles; From 7fd2916be80d388d25dbb539edf46f478710504e Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Fri, 13 Mar 2026 18:05:02 +0530 Subject: [PATCH 06/22] fix: type errors resolved in vuejs sdk - update Vue dependency to version 3.5.30 and add optional disabled property to SelectOption interface --- packages/vue/package.json | 2 +- packages/vue/src/components/primitives/Select.ts | 1 + packages/vue/src/plugins/AsgardeoPlugin.ts | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vue/package.json b/packages/vue/package.json index 72f6bc69e..01be49786 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -58,7 +58,7 @@ "rimraf": "6.1.0", "typescript": "5.7.2", "vitest": "3.1.3", - "vue": "3.5.13" + "vue": "3.5.30" }, "peerDependencies": { "vue": ">=3.5.0" diff --git a/packages/vue/src/components/primitives/Select.ts b/packages/vue/src/components/primitives/Select.ts index de0051a38..7eec8081d 100644 --- a/packages/vue/src/components/primitives/Select.ts +++ b/packages/vue/src/components/primitives/Select.ts @@ -22,6 +22,7 @@ import {defineComponent, h, type PropType} from 'vue'; export interface SelectOption { label: string; value: string; + disabled?: boolean; } const Select = defineComponent({ diff --git a/packages/vue/src/plugins/AsgardeoPlugin.ts b/packages/vue/src/plugins/AsgardeoPlugin.ts index 4502c7092..b538134ce 100644 --- a/packages/vue/src/plugins/AsgardeoPlugin.ts +++ b/packages/vue/src/plugins/AsgardeoPlugin.ts @@ -18,7 +18,6 @@ import type {Plugin} from 'vue'; import AsgardeoProvider from '../components/AsgardeoProvider'; -import type {AsgardeoVueConfig} from '../models/config'; import {injectStyles} from '../styles/injectStyles'; /** @@ -47,7 +46,7 @@ import {injectStyles} from '../styles/injectStyles'; * * ``` */ -const AsgardeoPlugin: Plugin = { +const AsgardeoPlugin: Plugin = { install(app) { injectStyles(); app.component('AsgardeoProvider', AsgardeoProvider); From f93313432045cd9cd1d5e17c14716e61aa8112c2 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Fri, 13 Mar 2026 20:22:40 +0530 Subject: [PATCH 07/22] feat: enhance button components with unstyled mode for full customization support --- .../components/actions/BaseSignInButton.ts | 27 +++++-- .../components/actions/BaseSignOutButton.ts | 45 +++++++++--- .../components/actions/BaseSignUpButton.ts | 45 +++++++++--- .../src/components/actions/SignInButton.ts | 11 ++- .../src/components/actions/SignOutButton.ts | 11 ++- .../src/components/actions/SignUpButton.ts | 73 ++++++++----------- 6 files changed, 141 insertions(+), 71 deletions(-) diff --git a/packages/vue/src/components/actions/BaseSignInButton.ts b/packages/vue/src/components/actions/BaseSignInButton.ts index fa369db25..0c20a3e8b 100644 --- a/packages/vue/src/components/actions/BaseSignInButton.ts +++ b/packages/vue/src/components/actions/BaseSignInButton.ts @@ -21,16 +21,29 @@ import {defineComponent, h, type PropType} from 'vue'; import Button from '../primitives/Button'; /** - * BaseSignInButton — unstyled sign-in button. + * BaseSignInButton — styled sign-in button with customization support. * - * Uses the default slot for custom content. When no slot is provided, - * renders a default Button primitive. + * By default, renders a styled Button primitive with contents from the slot or fallback text. + * Set `unstyled={true}` to render a plain