From 4a291bcc81068c7fb2614825d53a0ba891f21037 Mon Sep 17 00:00:00 2001 From: Nitzperetz Date: Sun, 1 Dec 2024 18:53:13 +0200 Subject: [PATCH] Add custom fields route and use for custom fields mixin --- .../src/lib/widget/api/apiPaths.ts | 1 + .../src/lib/widget/api/sdk/createUserSdk.ts | 16 ++++++++++- .../src/lib/widget/api/sdk/index.ts | 2 +- .../src/lib/widget/api/types.ts | 13 +++++++-- .../initWidgetRootMixin.ts | 6 +++- .../lib/widget/mixins/stateManagementMixin.ts | 4 ++- .../state/asyncActions/getCustomAttributes.ts | 25 +++++++++++++++++ .../lib/widget/state/asyncActions/index.ts | 1 + .../src/lib/widget/state/initialState.ts | 5 ++++ .../src/lib/widget/state/selectors.ts | 28 ++++++++++++++++++- .../src/lib/widget/state/types.ts | 6 ++++ 11 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/getCustomAttributes.ts diff --git a/packages/widgets/user-profile-widget/src/lib/widget/api/apiPaths.ts b/packages/widgets/user-profile-widget/src/lib/widget/api/apiPaths.ts index 630e9c7c2..72717ea69 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/api/apiPaths.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/api/apiPaths.ts @@ -1,5 +1,6 @@ export const apiPaths = { user: { me: '/v1/auth/me', + customAttributes: '/v1/mgmt/user/customattributes', }, }; diff --git a/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/createUserSdk.ts b/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/createUserSdk.ts index 5f4c59730..cfc3aeec0 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/createUserSdk.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/createUserSdk.ts @@ -1,5 +1,5 @@ import { apiPaths } from '../apiPaths'; -import { HttpClient } from '../types'; +import { CustomAttr, HttpClient } from '../types'; import { withErrorHandler } from './helpers'; import { user } from './mocks'; @@ -21,7 +21,21 @@ export const createUserSdk = ({ return res.json(); }; + const getCustomAttributes = async (): Promise => { + if (mock) { + return []; + } + const res = await httpClient.get(apiPaths.user.customAttributes); + + await withErrorHandler(res); + + const json = await res.json(); + + return json.data; + }; + return { me, + getCustomAttributes, }; }; diff --git a/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/index.ts b/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/index.ts index ae7056d06..c3ee420d0 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/index.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/api/sdk/index.ts @@ -1,6 +1,6 @@ +import '@descope/core-js-sdk'; import createWebSdk from '@descope/web-js-sdk'; import { createUserSdk } from './createUserSdk'; -import '@descope/core-js-sdk'; declare const BUILD_VERSION: string; diff --git a/packages/widgets/user-profile-widget/src/lib/widget/api/types.ts b/packages/widgets/user-profile-widget/src/lib/widget/api/types.ts index 71b03677e..87bf8b7fa 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/api/types.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/api/types.ts @@ -4,7 +4,7 @@ export type Sdk = ReturnType; type CustomAttributeType = string | boolean | number; -type CustomAttributes = Record; +export type CustomAttributes = Record; type UserStatus = 'enabled' | 'disabled' | 'invited'; @@ -94,7 +94,7 @@ export type CreateUserConfig = { export type CustomAttr = { name: string; - type: number; + type: AttributeType; options: string[]; displayName: string; defaultValue: Record; @@ -102,3 +102,12 @@ export type CustomAttr = { EditPermissions: string[]; editable: boolean; }; + +export enum AttributeType { + text = 1, + number = 2, + boolean = 3, + singleSelect = 4, + array = 5, + date = 6, +} diff --git a/packages/widgets/user-profile-widget/src/lib/widget/mixins/initMixin/initComponentsMixins/initWidgetRootMixin.ts b/packages/widgets/user-profile-widget/src/lib/widget/mixins/initMixin/initComponentsMixins/initWidgetRootMixin.ts index 9c38ba0bd..3523e4314 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/mixins/initMixin/initComponentsMixins/initWidgetRootMixin.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/mixins/initMixin/initComponentsMixins/initWidgetRootMixin.ts @@ -37,7 +37,11 @@ export const initWidgetRootMixin = createSingletonMixin( async init() { await super.init?.(); - await Promise.all([this.actions.getMe(), this.#initWidgetRoot()]); + await Promise.all([ + this.actions.getMe(), + this.actions.getCustomAttributes(), + this.#initWidgetRoot(), + ]); this.onWidgetRootReady(); } diff --git a/packages/widgets/user-profile-widget/src/lib/widget/mixins/stateManagementMixin.ts b/packages/widgets/user-profile-widget/src/lib/widget/mixins/stateManagementMixin.ts index e629a10da..21e817aad 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/mixins/stateManagementMixin.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/mixins/stateManagementMixin.ts @@ -5,7 +5,7 @@ import { initLifecycleMixin, loggerMixin, } from '@descope/sdk-mixins'; -import { getMe, logout } from '../state/asyncActions'; +import { getCustomAttributes, getMe, logout } from '../state/asyncActions'; import { initialState } from '../state/initialState'; import { apiMixin } from './apiMixin'; @@ -19,10 +19,12 @@ export const stateManagementMixin = createSingletonMixin( extraReducers: (builder) => { getMe.reducer(builder); logout.reducer(builder); + getCustomAttributes.reducer(builder); }, asyncActions: { getMe: getMe.action, logout: logout.action, + getCustomAttributes: getCustomAttributes.action, }, }), initLifecycleMixin, diff --git a/packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/getCustomAttributes.ts b/packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/getCustomAttributes.ts new file mode 100644 index 000000000..e8aad89a1 --- /dev/null +++ b/packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/getCustomAttributes.ts @@ -0,0 +1,25 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable @typescript-eslint/no-shadow */ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { Sdk } from '../../api/sdk'; +import { FirstParameter, State, ThunkConfigExtraApi } from '../types'; +import { buildAsyncReducer, withRequestStatus } from './helpers'; + +const action = createAsyncThunk< + any, + FirstParameter, + ThunkConfigExtraApi +>('customAttributes', (arg, { extra: { api } }) => + api.user.getCustomAttributes(), +); + +const reducer = buildAsyncReducer(action)( + { + onFulfilled: (state, action) => { + state.customAttributes.data = action.payload; + }, + }, + withRequestStatus((state: State) => state.customAttributes), +); + +export const getCustomAttributes = { action, reducer }; diff --git a/packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/index.ts b/packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/index.ts index 367786931..5f6b679ef 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/index.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/state/asyncActions/index.ts @@ -1,2 +1,3 @@ export * from './getMe'; export * from './logout'; +export * from './getCustomAttributes'; diff --git a/packages/widgets/user-profile-widget/src/lib/widget/state/initialState.ts b/packages/widgets/user-profile-widget/src/lib/widget/state/initialState.ts index a0c18d0e2..e30f151a7 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/state/initialState.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/state/initialState.ts @@ -6,4 +6,9 @@ export const initialState: State = { error: null, data: {}, }, + customAttributes: { + loading: false, + error: null, + data: [], + }, }; diff --git a/packages/widgets/user-profile-widget/src/lib/widget/state/selectors.ts b/packages/widgets/user-profile-widget/src/lib/widget/state/selectors.ts index ae96b2170..054ca9b2f 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/state/selectors.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/state/selectors.ts @@ -1,4 +1,5 @@ import { createSelector } from 'reselect'; +import { AttributeType } from '../api/types'; import { State } from './types'; export const getMe = (state: State) => state.me.data; @@ -18,7 +19,32 @@ export const getIsPhoneVerified = createSelector( export const getHasPasskey = createSelector(getMe, (me) => me.webauthn); export const getHasPassword = createSelector(getMe, (me) => me.password); +export const getCustomAttributes = (state: State) => + state.customAttributes.data; + export const getUserCustomAttrs = createSelector( getMe, - (me) => me.customAttributes, + getCustomAttributes, + (userData, allCustomAttrs = []) => { + const res: Record = {}; + const userCustomAttributes = userData['customAttributes'] || {}; + + Object.keys(userCustomAttributes).forEach((key: string) => { + const type = + allCustomAttrs.find((attr) => attr.name === key)?.type || + AttributeType.text; + if (type === AttributeType.date && userCustomAttributes[key]) { + // to full date time + res[key] = new Date(userCustomAttributes[key]).toLocaleString(); + } else if ( + type === AttributeType.boolean && + userCustomAttributes[key] !== undefined + ) { + res[key] = !userCustomAttributes[key] ? 'False' : 'True'; + } else { + res[key] = (userCustomAttributes[key] || '').toString(); + } + }); + return res; + }, ); diff --git a/packages/widgets/user-profile-widget/src/lib/widget/state/types.ts b/packages/widgets/user-profile-widget/src/lib/widget/state/types.ts index 1d509bb8f..c1242e94e 100644 --- a/packages/widgets/user-profile-widget/src/lib/widget/state/types.ts +++ b/packages/widgets/user-profile-widget/src/lib/widget/state/types.ts @@ -1,5 +1,6 @@ import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; import { Sdk } from '../api/sdk'; +import { CustomAttr } from '../api/types'; export type State = { me: { @@ -7,6 +8,11 @@ export type State = { error: unknown; data: Record; }; + customAttributes: { + loading: boolean; + error: unknown; + data: CustomAttr[]; + }; }; type First = T extends [infer U, ...any[]] ? U : never;