diff --git a/.changeset/early-boxes-hide.md b/.changeset/early-boxes-hide.md new file mode 100644 index 00000000..92d67424 --- /dev/null +++ b/.changeset/early-boxes-hide.md @@ -0,0 +1,6 @@ +--- +'@asgardeo/nextjs': patch +'@asgardeo/react': patch +--- + +Fix issues in next components diff --git a/packages/nextjs/src/client/components/presentation/CreateOrganization/CreateOrganization.tsx b/packages/nextjs/src/client/components/presentation/CreateOrganization/CreateOrganization.tsx index 08899b8c..a2d24afe 100644 --- a/packages/nextjs/src/client/components/presentation/CreateOrganization/CreateOrganization.tsx +++ b/packages/nextjs/src/client/components/presentation/CreateOrganization/CreateOrganization.tsx @@ -18,13 +18,11 @@ 'use client'; -import {FC, ReactElement, useState} from 'react'; - +import {CreateOrganizationPayload, AsgardeoRuntimeError} from '@asgardeo/node'; import {BaseCreateOrganization, BaseCreateOrganizationProps, useOrganization} from '@asgardeo/react'; -import {CreateOrganizationPayload} from '@asgardeo/node'; -import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; -import createOrganizationAction from '../../../../server/actions/createOrganizationAction'; +import {FC, ReactElement, useState} from 'react'; import getSessionId from '../../../../server/actions/getSessionId'; +import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; /** * Props interface for the CreateOrganization component. @@ -80,7 +78,7 @@ export const CreateOrganization: FC = ({ ...props }: CreateOrganizationProps): ReactElement => { const {isSignedIn, baseUrl} = useAsgardeo(); - const {currentOrganization, revalidateMyOrganizations} = useOrganization(); + const {currentOrganization, revalidateMyOrganizations, createOrganization} = useOrganization(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -104,14 +102,22 @@ export const CreateOrganization: FC = ({ let result: any; if (onCreateOrganization) { - // Use the provided custom creation function result = await onCreateOrganization(payload); } else { - // Use the default API if (!baseUrl) { throw new Error('Base URL is required for organization creation'); } - result = await createOrganizationAction( + + if (!createOrganization) { + throw new AsgardeoRuntimeError( + `createOrganization function is not available.`, + 'CreateOrganization-handleSubmit-RuntimeError-001', + 'nextjs', + 'The createOrganization function must be provided by the Organization context.', + ); + } + + result = await createOrganization( { ...payload, parentId, @@ -121,7 +127,9 @@ export const CreateOrganization: FC = ({ } // Refresh organizations list to include the new organization - await revalidateMyOrganizations(); + if (revalidateMyOrganizations) { + await revalidateMyOrganizations(); + } // Call success callback if provided if (onSuccess) { diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx index fbdc54ba..7666dcae 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx @@ -20,7 +20,6 @@ import { AllOrganizationsApiResponse, - AsgardeoRuntimeError, EmbeddedFlowExecuteRequestConfig, EmbeddedFlowExecuteRequestPayload, EmbeddedSignInFlowHandleRequestPayload, @@ -30,6 +29,8 @@ import { User, UserProfile, BrandingPreference, + TokenResponse, + CreateOrganizationPayload, } from '@asgardeo/node'; import { I18nProvider, @@ -40,8 +41,8 @@ import { OrganizationProvider, BrandingProvider, } from '@asgardeo/react'; -import {FC, PropsWithChildren, RefObject, useEffect, useMemo, useRef, useState} from 'react'; import {useRouter, useSearchParams} from 'next/navigation'; +import {FC, PropsWithChildren, RefObject, useEffect, useMemo, useRef, useState} from 'react'; import AsgardeoContext, {AsgardeoContextProps} from './AsgardeoContext'; /** @@ -49,29 +50,30 @@ import AsgardeoContext, {AsgardeoContextProps} from './AsgardeoContext'; */ export type AsgardeoClientProviderProps = Partial> & Pick & { - signOut: AsgardeoContextProps['signOut']; - signIn: AsgardeoContextProps['signIn']; - signUp: AsgardeoContextProps['signUp']; applicationId: AsgardeoContextProps['applicationId']; - organizationHandle: AsgardeoContextProps['organizationHandle']; + brandingPreference?: BrandingPreference | null; + createOrganization: (payload: CreateOrganizationPayload, sessionId: string) => Promise; + currentOrganization: Organization; + getAllOrganizations: (options?: any, sessionId?: string) => Promise; handleOAuthCallback: ( code: string, state: string, sessionState?: string, - ) => Promise<{success: boolean; error?: string; redirectUrl?: string}>; + ) => Promise<{error?: string; redirectUrl?: string; success: boolean}>; isSignedIn: boolean; - userProfile: UserProfile; - currentOrganization: Organization; - user: User | null; + myOrganizations: Organization[]; + organizationHandle: AsgardeoContextProps['organizationHandle']; + revalidateMyOrganizations?: (sessionId?: string) => Promise; + signIn: AsgardeoContextProps['signIn']; + signOut: AsgardeoContextProps['signOut']; + signUp: AsgardeoContextProps['signUp']; + switchOrganization: (organization: Organization, sessionId?: string) => Promise; updateProfile: ( requestConfig: UpdateMeProfileConfig, sessionId?: string, - ) => Promise<{success: boolean; data: {user: User}; error: string}>; - getAllOrganizations: (options?: any, sessionId?: string) => Promise; - myOrganizations: Organization[]; - revalidateMyOrganizations?: (sessionId?: string) => Promise; - brandingPreference?: BrandingPreference | null; - switchOrganization: (organization: Organization, sessionId?: string) => Promise; + ) => Promise<{data: {user: User}; error: string; success: boolean}>; + user: User | null; + userProfile: UserProfile; }; const AsgardeoClientProvider: FC> = ({ @@ -81,6 +83,7 @@ const AsgardeoClientProvider: FC> signOut, signUp, handleOAuthCallback, + createOrganization, preferences, isSignedIn, signInUrl, @@ -297,10 +300,11 @@ const AsgardeoClientProvider: FC> {children} diff --git a/packages/nextjs/src/server/AsgardeoProvider.tsx b/packages/nextjs/src/server/AsgardeoProvider.tsx index d5671c4b..409e3155 100644 --- a/packages/nextjs/src/server/AsgardeoProvider.tsx +++ b/packages/nextjs/src/server/AsgardeoProvider.tsx @@ -18,34 +18,27 @@ 'use server'; +import {BrandingPreference, AsgardeoRuntimeError, Organization, User, UserProfile} from '@asgardeo/node'; +import {AsgardeoProviderProps} from '@asgardeo/react'; import {FC, PropsWithChildren, ReactElement} from 'react'; -import { - BrandingPreference, - AllOrganizationsApiResponse, - AsgardeoRuntimeError, - Organization, - User, - UserProfile, - IdToken, -} from '@asgardeo/node'; -import AsgardeoClientProvider from '../client/contexts/Asgardeo/AsgardeoProvider'; -import AsgardeoNextClient from '../AsgardeoNextClient'; -import signInAction from './actions/signInAction'; -import signOutAction from './actions/signOutAction'; -import {AsgardeoNextConfig} from '../models/config'; -import isSignedIn from './actions/isSignedIn'; -import getUserAction from './actions/getUserAction'; +import createOrganization from './actions/createOrganization'; +import getAllOrganizations from './actions/getAllOrganizations'; +import getBrandingPreference from './actions/getBrandingPreference'; +import getCurrentOrganizationAction from './actions/getCurrentOrganizationAction'; +import getMyOrganizations from './actions/getMyOrganizations'; import getSessionId from './actions/getSessionId'; +import getUserAction from './actions/getUserAction'; import getUserProfileAction from './actions/getUserProfileAction'; -import signUpAction from './actions/signUpAction'; import handleOAuthCallbackAction from './actions/handleOAuthCallbackAction'; -import {AsgardeoProviderProps} from '@asgardeo/react'; -import getCurrentOrganizationAction from './actions/getCurrentOrganizationAction'; -import updateUserProfileAction from './actions/updateUserProfileAction'; -import getMyOrganizations from './actions/getMyOrganizations'; -import getAllOrganizations from './actions/getAllOrganizations'; -import getBrandingPreference from './actions/getBrandingPreference'; +import isSignedIn from './actions/isSignedIn'; +import signInAction from './actions/signInAction'; +import signOutAction from './actions/signOutAction'; +import signUpAction from './actions/signUpAction'; import switchOrganization from './actions/switchOrganization'; +import updateUserProfileAction from './actions/updateUserProfileAction'; +import AsgardeoNextClient from '../AsgardeoNextClient'; +import AsgardeoClientProvider from '../client/contexts/Asgardeo/AsgardeoProvider'; +import {AsgardeoNextConfig} from '../models/config'; /** * Props interface of {@link AsgardeoServerProvider} @@ -150,26 +143,6 @@ const AsgardeoServerProvider: FC> } } - const handleGetAllOrganizations = async ( - options?: any, - _sessionId?: string, - ): Promise => { - 'use server'; - return await getAllOrganizations(options, sessionId); - }; - - const handleSwitchOrganization = async (organization: Organization, _sessionId?: string): Promise => { - 'use server'; - await switchOrganization(organization, sessionId); - - // After switching organization, we need to refresh the page to get updated session data - // This is because server components don't maintain state between function calls - const {revalidatePath} = await import('next/cache'); - - // Revalidate the current path to refresh the component with new data - revalidatePath('/'); - }; - return ( > updateProfile={updateUserProfileAction} isSignedIn={_isSignedIn} myOrganizations={myOrganizations} - getAllOrganizations={handleGetAllOrganizations} - switchOrganization={handleSwitchOrganization} + getAllOrganizations={getAllOrganizations} + switchOrganization={switchOrganization} brandingPreference={brandingPreference} + createOrganization={createOrganization} > {children} diff --git a/packages/nextjs/src/server/actions/createOrganizationAction.ts b/packages/nextjs/src/server/actions/createOrganization.ts similarity index 52% rename from packages/nextjs/src/server/actions/createOrganizationAction.ts rename to packages/nextjs/src/server/actions/createOrganization.ts index 83a1b0fc..49dc852b 100644 --- a/packages/nextjs/src/server/actions/createOrganizationAction.ts +++ b/packages/nextjs/src/server/actions/createOrganization.ts @@ -18,26 +18,25 @@ 'use server'; -import {CreateOrganizationPayload, Organization} from '@asgardeo/node'; +import {CreateOrganizationPayload, Organization, AsgardeoAPIError} from '@asgardeo/node'; +import getSessionId from './getSessionId'; import AsgardeoNextClient from '../../AsgardeoNextClient'; /** * Server action to create an organization. */ -const createOrganizationAction = async (payload: CreateOrganizationPayload, sessionId: string) => { +const createOrganization = async (payload: CreateOrganizationPayload, sessionId: string): Promise => { try { - const client = AsgardeoNextClient.getInstance(); - const organization: Organization = await client.createOrganization(payload, sessionId); - return {success: true, data: {organization}, error: null}; + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + return await client.createOrganization(payload, sessionId ?? ((await getSessionId()) as string)); } catch (error) { - return { - success: false, - data: { - user: {}, - }, - error: 'Failed to create organization', - }; + throw new AsgardeoAPIError( + `Failed to create the organization: ${error instanceof Error ? error.message : String(error)}`, + 'createOrganization-ServerActionError-001', + 'nextjs', + error instanceof AsgardeoAPIError ? error.statusCode : undefined, + ); } }; -export default createOrganizationAction; +export default createOrganization; diff --git a/packages/nextjs/src/server/actions/getAllOrganizations.ts b/packages/nextjs/src/server/actions/getAllOrganizations.ts index 9902f8bf..4fcbef8d 100644 --- a/packages/nextjs/src/server/actions/getAllOrganizations.ts +++ b/packages/nextjs/src/server/actions/getAllOrganizations.ts @@ -18,16 +18,20 @@ 'use server'; -import {AllOrganizationsApiResponse, AsgardeoAPIError, Organization} from '@asgardeo/node'; +import {AllOrganizationsApiResponse, AsgardeoAPIError} from '@asgardeo/node'; +import getSessionId from './getSessionId'; import AsgardeoNextClient from '../../AsgardeoNextClient'; /** * Server action to get organizations. */ -const getAllOrganizations = async (options?: any, sessionId?: string | undefined): Promise => { +const getAllOrganizations = async ( + options?: any, + sessionId?: string | undefined, +): Promise => { try { - const client = AsgardeoNextClient.getInstance(); - return client.getAllOrganizations(options, sessionId); + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + return await client.getAllOrganizations(options, sessionId ?? ((await getSessionId()) as string)); } catch (error) { throw new AsgardeoAPIError( `Failed to get all the organizations for the user: ${error instanceof Error ? error.message : String(error)}`, diff --git a/packages/nextjs/src/server/actions/switchOrganization.ts b/packages/nextjs/src/server/actions/switchOrganization.ts index 4441d58d..38d7b8a3 100644 --- a/packages/nextjs/src/server/actions/switchOrganization.ts +++ b/packages/nextjs/src/server/actions/switchOrganization.ts @@ -18,21 +18,35 @@ 'use server'; -import {Organization, AsgardeoAPIError, AsgardeoRuntimeError, TokenResponse} from '@asgardeo/node'; +import {Organization, AsgardeoAPIError, TokenResponse} from '@asgardeo/node'; +import getSessionId from './getSessionId'; import AsgardeoNextClient from '../../AsgardeoNextClient'; /** * Server action to switch organization. */ -const switchOrganization = async (organization: Organization, sessionId: string): Promise => { +const switchOrganization = async ( + organization: Organization, + sessionId: string | undefined, +): Promise => { try { - const client = AsgardeoNextClient.getInstance(); - return await client.switchOrganization(organization, sessionId); + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + const response: TokenResponse | Response = await client.switchOrganization( + organization, + sessionId ?? ((await getSessionId()) as string), + ); + + // After switching organization, we need to refresh the page to get updated session data + // This is because server components don't maintain state between function calls + const {revalidatePath} = await import('next/cache'); + + // Revalidate the current path to refresh the component with new data + revalidatePath('/'); + + return response; } catch (error) { throw new AsgardeoAPIError( - `Failed to switch the organizations: ${ - error instanceof AsgardeoRuntimeError ? error.message : error instanceof Error ? error.message : String(error) - }`, + `Failed to switch the organizations: ${error instanceof Error ? error.message : String(error)}`, 'switchOrganization-ServerActionError-001', 'nextjs', error instanceof AsgardeoAPIError ? error.statusCode : undefined, diff --git a/packages/react/src/contexts/Organization/OrganizationContext.ts b/packages/react/src/contexts/Organization/OrganizationContext.ts index a268a6da..c7c6ece7 100644 --- a/packages/react/src/contexts/Organization/OrganizationContext.ts +++ b/packages/react/src/contexts/Organization/OrganizationContext.ts @@ -16,37 +16,42 @@ * under the License. */ -import {AllOrganizationsApiResponse, Organization} from '@asgardeo/browser'; +import {AllOrganizationsApiResponse, Organization, CreateOrganizationPayload} from '@asgardeo/browser'; import {Context, createContext} from 'react'; /** * Props interface of {@link OrganizationContext} */ export type OrganizationContextProps = { + /** + * Function to create a new organization. + */ + createOrganization?: (payload: CreateOrganizationPayload, sessionId: string) => Promise; currentOrganization: Organization | null; error: string | null; + getAllOrganizations: () => Promise; isLoading: boolean; myOrganizations: Organization[]; - switchOrganization: (organization: Organization) => Promise; revalidateMyOrganizations: () => Promise; - getAllOrganizations: () => Promise; + switchOrganization: (organization: Organization) => Promise; }; /** * Context object for managing organization data and related operations. */ const OrganizationContext: Context = createContext({ + createOrganization: () => null, currentOrganization: null, error: null, - isLoading: false, - myOrganizations: null, - switchOrganization: () => Promise.resolve(), - revalidateMyOrganizations: () => Promise.resolve([]), getAllOrganizations: () => Promise.resolve({ count: 0, organizations: [], }), + isLoading: false, + myOrganizations: null, + revalidateMyOrganizations: () => Promise.resolve([]), + switchOrganization: () => Promise.resolve(), }); OrganizationContext.displayName = 'OrganizationContext'; diff --git a/packages/react/src/contexts/Organization/OrganizationProvider.tsx b/packages/react/src/contexts/Organization/OrganizationProvider.tsx index e2c5068e..960bf4f6 100644 --- a/packages/react/src/contexts/Organization/OrganizationProvider.tsx +++ b/packages/react/src/contexts/Organization/OrganizationProvider.tsx @@ -16,7 +16,12 @@ * under the License. */ -import {AsgardeoRuntimeError, Organization, AllOrganizationsApiResponse} from '@asgardeo/browser'; +import { + AsgardeoRuntimeError, + Organization, + AllOrganizationsApiResponse, + CreateOrganizationPayload, +} from '@asgardeo/browser'; import {FC, PropsWithChildren, ReactElement, useCallback, useMemo, useState} from 'react'; import OrganizationContext, {OrganizationContextProps} from './OrganizationContext'; @@ -28,10 +33,18 @@ export interface OrganizationProviderProps { * Whether to automatically fetch organizations on mount */ autoFetch?: boolean; + /** + * Function to create a new organization. + */ + createOrganization?: (payload: CreateOrganizationPayload, sessionId: string) => Promise; /** * Initial current organization */ currentOrganization?: Organization | null; + /** + * Initial list of organizations + */ + getAllOrganizations?: () => Promise; /** * List of organizations the signed-in user belongs to. */ @@ -44,10 +57,6 @@ export interface OrganizationProviderProps { * Callback function called when switching organizations */ onOrganizationSwitch?: (organization: Organization) => Promise; - /** - * Initial list of organizations - */ - getAllOrganizations?: () => Promise; /** * Refetch the my organizations list. * @returns @@ -97,6 +106,7 @@ const OrganizationProvider: FC> = ( onOrganizationSwitch, revalidateMyOrganizations, getAllOrganizations, + createOrganization, }: PropsWithChildren): ReactElement => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -138,13 +148,14 @@ const OrganizationProvider: FC> = ( const contextValue: OrganizationContextProps = useMemo( () => ({ + createOrganization, currentOrganization, error, + getAllOrganizations, isLoading, myOrganizations, - switchOrganization, revalidateMyOrganizations, - getAllOrganizations, + switchOrganization, }), [ currentOrganization, @@ -154,6 +165,7 @@ const OrganizationProvider: FC> = ( switchOrganization, revalidateMyOrganizations, getAllOrganizations, + createOrganization, ], );