diff --git a/build/api/react-client-tenant.api.md b/build/api/react-client-tenant.api.md index 6e74bcff6d..4c23f26c5c 100644 --- a/build/api/react-client-tenant.api.md +++ b/build/api/react-client-tenant.api.md @@ -4,18 +4,1489 @@ ```ts +import { ChangeMyPasswordErrorCode } from '@contember/graphql-client-tenant'; +import { ConfirmOtpErrorCode } from '@contember/graphql-client-tenant'; +import { Context } from 'react'; +import { CreateApiKeyErrorCode } from '@contember/graphql-client-tenant'; +import { CreatePasswordResetRequestErrorCode } from '@contember/graphql-client-tenant'; +import { CreateSessionTokenErrorCode } from '@contember/graphql-client-tenant'; import { Fetcher } from 'graphql-ts-client-api'; +import { InitSignInIDPErrorCode } from '@contember/graphql-client-tenant'; +import { InviteErrorCode } from '@contember/graphql-client-tenant'; +import { InviteOptions } from '@contember/graphql-client-tenant'; +import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { MembershipInput } from '@contember/graphql-client-tenant'; import { ModelType } from 'graphql-ts-client-api'; +import { MutationFetcher } from '@contember/graphql-client-tenant'; +import { ReactElement } from 'react'; +import { ReactNode } from 'react'; +import { ResetPasswordErrorCode } from '@contember/graphql-client-tenant'; +import { SetStateAction } from 'react'; +import { SignInErrorCode } from '@contember/graphql-client-tenant'; +import { SignInIDPErrorCode } from '@contember/graphql-client-tenant'; +import * as TenantApi from '@contember/graphql-client-tenant'; +import { UpdateProjectMemberErrorCode } from '@contember/graphql-client-tenant'; + +// @public (undocumented) +export const addProjectMemberMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.AddProjectMemberErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + } & { + readonly membershipValidation?: readonly ({ + readonly code: TenantApi.MembershipValidationErrorCode; + } & { + readonly role: string; + } & { + readonly variable?: string | undefined; + })[] | undefined; + }) | undefined; + }) | undefined; +}, { + readonly projectSlug: string; + readonly identityId: string; + readonly memberships: readonly TenantApi.MembershipInput[]; +}>; + +// @public (undocumented) +export type AddProjectMemberMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const ChangeMyPasswordForm: ({ children, onSuccess }: ChangeMyPasswordFormProps) => JSX_2.Element; + +// @public (undocumented) +export type ChangeMyPasswordFormContextValue = FormContextValue; + +// @public (undocumented) +export type ChangeMyPasswordFormError = FormError; + +// @public (undocumented) +export type ChangeMyPasswordFormErrorCode = ChangeMyPasswordErrorCode | 'FIELD_REQUIRED' | 'INVALID_VALUE' | 'PASSWORD_MISMATCH' | 'UNKNOWN_ERROR'; + +// @public (undocumented) +export interface ChangeMyPasswordFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + onSuccess?: () => void; +} + +// @public (undocumented) +export type ChangeMyPasswordFormState = FormState; + +// @public (undocumented) +export type ChangeMyPasswordFormValues = { + currentPassword: string; + newPassword: string; + passwordConfirmation: string; +}; + +// @public (undocumented) +export const changeMyPasswordMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.ChangeMyPasswordErrorCode; + } & { + readonly developerMessage: string; + }) | undefined; + }) | undefined; +}, { + readonly currentPassword: string; + readonly newPassword: string; +}>; + +// @public (undocumented) +export type ChangeMyPasswordMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const confirmOtpMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.ConfirmOtpErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + }) | undefined; +}, { + readonly otpToken: string; +}>; + +// @public (undocumented) +export type ConfirmOtpMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const CreateApiKeyForm: ({ children, onSuccess, projectSlug, initialMemberships }: CreateApiKeyFormProps) => JSX_2.Element; + +// @public (undocumented) +export type CreateApiKeyFormContextValue = FormContextValue; + +// @public (undocumented) +export type CreateApiKeyFormError = FormError; + +// @public (undocumented) +export type CreateApiKeyFormErrorCode = CreateApiKeyErrorCode | 'UNKNOWN_ERROR' | 'FIELD_REQUIRED'; + +// @public (undocumented) +export interface CreateApiKeyFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + initialMemberships?: readonly MembershipInput[]; + // (undocumented) + onSuccess?: (args: { + result: CreateApiKeyMutationResult; + }) => void; + // (undocumented) + projectSlug: string; +} + +// @public (undocumented) +export type CreateApiKeyFormState = FormState; + +// @public (undocumented) +export type CreateApiKeyFormValues = { + description: string; + memberships: readonly MembershipInput[]; +}; + +// @public (undocumented) +export const createApiKeyMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.CreateApiKeyErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + } & { + readonly membershipValidation?: readonly ({ + readonly code: TenantApi.MembershipValidationErrorCode; + } & { + readonly role: string; + } & { + readonly variable?: string | undefined; + })[] | undefined; + }) | undefined; + } & { + readonly result?: { + readonly apiKey: { + readonly id: string; + } & { + readonly token?: string | undefined; + } & { + readonly identity: { + readonly id: string; + } & { + readonly description?: string | undefined; + } & { + readonly roles?: readonly string[] | undefined; + }; + }; + } | undefined; + }) | undefined; +}, { + readonly projectSlug: string; + readonly memberships: readonly TenantApi.MembershipInput[]; + readonly description: string; + readonly tokenHash?: string | undefined; +}>; + +// Warning: (ae-forgotten-export) The symbol "createApiKeyMutationResult" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type CreateApiKeyMutationResult = ModelType; + +// @public (undocumented) +export type CreateApiKeyMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const createGlobalApiKeyMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.CreateApiKeyErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + } & { + readonly result?: { + readonly apiKey: { + readonly id: string; + } & { + readonly token?: string | undefined; + } & { + readonly identity: { + readonly id: string; + } & { + readonly description?: string | undefined; + } & { + readonly roles?: readonly string[] | undefined; + }; + }; + } | undefined; + }) | undefined; +}, { + readonly description: string; + readonly roles?: readonly string[] | undefined; + readonly tokenHash?: string | undefined; +}>; + +// Warning: (ae-forgotten-export) The symbol "createGlobalApiKeyMutationResult" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type CreateGlobalApiKeyMutationResult = ModelType; + +// @public (undocumented) +export type CreateGlobalApiKeyMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const createResetPasswordRequestMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: "PERSON_NOT_FOUND"; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + }) | undefined; +}, { + readonly email: string; + readonly options?: TenantApi.CreateResetPasswordRequestOptions | undefined; +}>; + +// @public (undocumented) +export type CreateResetPasswordRequestMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const CreateSessionTokenForm: ({ children, onSuccess, expiration, apiToken }: CreateSessionTokenFormProps) => JSX_2.Element; + +// @public (undocumented) +export type CreateSessionTokenFormContextValue = FormContextValue; + +// @public (undocumented) +export type CreateSessionTokenFormError = FormError; + +// @public (undocumented) +export type CreateSessionTokenFormErrorCode = CreateSessionTokenErrorCode | 'UNKNOWN_ERROR' | 'FIELD_REQUIRED'; + +// @public (undocumented) +export interface CreateSessionTokenFormProps { + // (undocumented) + apiToken?: string; + // (undocumented) + children: ReactElement; + // (undocumented) + expiration?: number; + // (undocumented) + onSuccess?: (args: { + result: CreateSessionTokenMutationResult; + }) => void; +} + +// @public (undocumented) +export type CreateSessionTokenFormState = FormState; + +// @public (undocumented) +export type CreateSessionTokenFormValues = { + email: string; +}; + +// @public (undocumented) +export const createSessionTokenMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.CreateSessionTokenErrorCode; + } & { + readonly developerMessage: string; + }) | undefined; + } & { + readonly result?: ({ + readonly token: string; + } & { + readonly person: { + readonly id: string; + } & { + readonly email?: string | undefined; + } & { + readonly name?: string | undefined; + } & { + readonly otpEnabled: boolean; + }; + }) | undefined; + }) | undefined; +}, { + readonly email?: string | undefined; + readonly personId?: string | undefined; + readonly expiration?: number | undefined; +}>; + +// Warning: (ae-forgotten-export) The symbol "createSessionTokenMutationResult" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type CreateSessionTokenMutationResult = ModelType; + +// @public (undocumented) +export type CreateSessionTokenMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const createTenantMutation: (fetcher: MutationFetcher, TVariables>, defaultOptions?: TenantApiOptions) => ({ headers, apiToken }?: TenantApiOptions) => (variables: TVariables) => Promise>; + +// @public (undocumented) +export const disableApiKeyMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: "KEY_NOT_FOUND"; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + }) | undefined; +}, { + readonly id: string; +}>; + +// @public (undocumented) +export type DisableApiKeyMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const disableOtpMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: "OTP_NOT_ACTIVE"; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + }) | undefined; +}, {}>; + +// @public (undocumented) +export type DisableOtpMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const DisableOtpTrigger: ({ onSuccess, ...props }: DisableOtpTriggerProps) => JSX_2.Element; + +// @public (undocumented) +export interface DisableOtpTriggerProps { + // (undocumented) + children: ReactElement; + // (undocumented) + onError?: (e: unknown) => void; + // (undocumented) + onSuccess?: () => void; +} + +// @internal (undocumented) +export const FormContext: Context>; + +// Warning: (ae-forgotten-export) The symbol "FormValueType" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export interface FormContextValue { + // (undocumented) + errors: FormError[]; + // (undocumented) + setValue: (field: F, value: V[F]) => void; + // (undocumented) + setValues: (values: SetStateAction) => void; + // (undocumented) + state: FormState | S; + // (undocumented) + values: V; +} + +// @public (undocumented) +export type FormError = { + field?: keyof V; + code: FormErrorCode | E; + developerMessage?: string; +}; + +// @public (undocumented) +export type FormErrorCode = 'UNKNOWN_ERROR'; + +// @public (undocumented) +export type FormState = 'loading' | 'initial' | 'submitting' | 'error' | 'success'; + +// @public (undocumented) +export interface Identity { + // (undocumented) + readonly id: string; + // (undocumented) + readonly permissions: { + readonly canCreateProject: boolean; + }; + // (undocumented) + readonly person?: Person; + // (undocumented) + readonly projects: IdentityProject[]; +} + +// @internal (undocumented) +export const IdentityContext: Context; + +// @public (undocumented) +export interface IdentityMethods { + // (undocumented) + clearIdentity: () => void; + // (undocumented) + refreshIdentity: () => Promise; +} + +// @internal (undocumented) +export const IdentityMethodsContext: Context; + +// @public (undocumented) +export interface IdentityProject { + // (undocumented) + readonly name: string; + // (undocumented) + readonly roles: readonly string[]; + // (undocumented) + readonly slug: string; +} + +// @public (undocumented) +export const IdentityProvider: React.FC; + +// @public (undocumented) +export interface IdentityProviderProps { + // (undocumented) + children: ReactNode; +} + +// @public (undocumented) +export const IdentityState: ({ state, children }: IdentityStateProps) => JSX_2.Element | null; + +// @internal (undocumented) +export const IdentityStateContext: Context; + +// @public (undocumented) +export interface IdentityStateProps { + // (undocumented) + children: ReactNode; + // (undocumented) + state: IdentityStateValue | IdentityStateValue[]; +} + +// @public (undocumented) +export type IdentityStateValue = 'none' | 'loading' | 'failed' | 'cleared' | 'success'; + +// @public (undocumented) +export const IDP: ({ children, onResponseError, onInitError, onLogin }: IDPProps) => JSX_2.Element; + +// @public (undocumented) +export type IDPInitError = InitSignInIDPErrorCode | 'UNKNOWN_ERROR'; + +// @public (undocumented) +export const IDPInitTrigger: ({ identityProvider, ...props }: IDPInitTriggerProps) => JSX_2.Element; + +// @public (undocumented) +export interface IDPInitTriggerProps { + // (undocumented) + children: ReactElement; + // (undocumented) + identityProvider: string; +} + +// @public (undocumented) +export type IDPMethods = { + initRedirect: (args: { + provider: string; + }) => Promise<{ + ok: true; + } | { + ok: false; + error: IDPInitError; + }>; +}; + +// @internal (undocumented) +export const IDPMethodsContextProvider: Context; + +// @public (undocumented) +export interface IDPProps { + // (undocumented) + children: ReactNode; + // (undocumented) + onInitError?: (error: IDPInitError) => void; + // (undocumented) + onLogin?: () => void; + // (undocumented) + onResponseError?: (error: IDPResponseError) => void; +} + +// @public (undocumented) +export type IDPResponseError = SignInIDPErrorCode | 'INVALID_LOCAL_STATE' | 'UNKNOWN_ERROR'; + +// @public (undocumented) +export const IDPState: ({ state, children }: IDPStateProps) => JSX_2.Element | null; + +// @internal (undocumented) +export const IDPStateContextProvider: Context; + +// @public (undocumented) +export interface IDPStateProps { + // (undocumented) + children: ReactNode; + // (undocumented) + state: IDPStateType | IDPStateType[]; +} + +// @public (undocumented) +export type IDPStateType = IDPStateValue['type']; + +// @public (undocumented) +export type IDPStateValue = { + type: 'nothing'; +} | { + type: 'processing_init'; +} | { + type: 'processing_response'; +} | { + type: 'success'; +} | { + type: 'init_failed'; + error: IDPInitError; +} | { + type: 'response_failed'; + error: IDPResponseError; +}; + +// @public (undocumented) +export type InitSignInIDPMutationResult = ModelType; + +// @public (undocumented) +export type InitSignInIDPMutationVariables = { + identityProvider: string; + data: { + redirectUrl?: string; + } & { + [key: string]: string; + }; +}; + +// @public (undocumented) +export const InviteForm: ({ children, onSuccess, projectSlug, initialMemberships, inviteOptions }: InviteFormProps) => JSX_2.Element; + +// @public (undocumented) +export type InviteFormContextValue = FormContextValue; + +// @public (undocumented) +export type InviteFormError = FormError; + +// @public (undocumented) +export type InviteFormErrorCode = InviteErrorCode | 'UNKNOWN_ERROR' | 'FIELD_REQUIRED'; + +// @public (undocumented) +export interface InviteFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + initialMemberships?: readonly MembershipInput[]; + // (undocumented) + inviteOptions?: InviteOptions; + // (undocumented) + onSuccess?: (args: { + result: InviteMutationResult; + }) => void; + // (undocumented) + projectSlug: string; +} + +// @public (undocumented) +export type InviteFormState = FormState; + +// @public (undocumented) +export type InviteFormValues = { + email: string; + name: string; + memberships: readonly MembershipInput[]; +}; + +// @public (undocumented) +export const inviteMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.InviteErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + } & { + readonly membershipValidation?: readonly ({ + readonly code: TenantApi.MembershipValidationErrorCode; + } & { + readonly role: string; + } & { + readonly variable?: string | undefined; + })[] | undefined; + }) | undefined; + } & { + readonly result?: ({ + readonly isNew: boolean; + } & { + readonly person: { + readonly id: string; + } & { + readonly email?: string | undefined; + } & { + readonly name?: string | undefined; + } & { + readonly otpEnabled: boolean; + } & { + readonly identity: { + readonly id: string; + } & { + readonly description?: string | undefined; + } & { + readonly roles?: readonly string[] | undefined; + }; + }; + }) | undefined; + }) | undefined; +}, { + readonly email: string; + readonly name?: string | undefined; + readonly projectSlug: string; + readonly memberships: readonly TenantApi.MembershipInput[]; + readonly options?: TenantApi.InviteOptions | undefined; +}>; + +// Warning: (ae-forgotten-export) The symbol "inviteMutationResult" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type InviteMutationResult = ModelType; + +// @public (undocumented) +export type InviteMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const LoginForm: ({ children, expiration, onSuccess }: LoginFormProps) => JSX_2.Element; + +// @public (undocumented) +export type LoginFormContextValue = FormContextValue; + +// @public (undocumented) +export type LoginFormError = FormError; + +// @public (undocumented) +export type LoginFormErrorCode = SignInErrorCode | 'FIELD_REQUIRED' | 'INVALID_VALUE' | 'UNKNOWN_ERROR'; + +// @public (undocumented) +export interface LoginFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + expiration?: number; + // (undocumented) + onSuccess?: () => void; +} + +// @public (undocumented) +export type LoginFormState = FormState | 'otp-required'; + +// @public (undocumented) +export type LoginFormValues = { + email: string; + password: string; + otpToken: string; +}; + +// @public (undocumented) +export const LoginToken: unique symbol; + +// @public (undocumented) +export const LogoutTrigger: ({ children }: { + children: ReactNode; +}) => JSX_2.Element; + +// Warning: (ae-forgotten-export) The symbol "identityFragment" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type MeQueryData = ModelType; export { ModelType } // @public (undocumented) -export const useTenantApi: () => (fetcher: Fetcher<'Query' | 'Mutation', TData, TVariables>, options?: { +export const OtpConfirmForm: ({ children, onSuccess }: OtpConfirmFormProps) => JSX_2.Element; + +// @public (undocumented) +export type OtpConfirmFormContextValue = FormContextValue; + +// @public (undocumented) +export type OtpConfirmFormError = FormError; + +// @public (undocumented) +export type OtpConfirmFormErrorCode = ConfirmOtpErrorCode | 'FIELD_REQUIRED' | 'UNKNOWN_ERROR'; + +// @public (undocumented) +export interface OtpConfirmFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + onSuccess?: () => void; +} + +// @public (undocumented) +export type OtpConfirmFormState = FormState; + +// @public (undocumented) +export type OtpConfirmFormValues = { + otpToken: string; +}; + +// @public (undocumented) +export const OtpPrepareForm: ({ children, onSuccess }: OtpPrepareFormProps) => JSX_2.Element; + +// @public (undocumented) +export type OtpPrepareFormContextValue = FormContextValue; + +// @public (undocumented) +export type OtpPrepareFormError = FormError; + +// @public (undocumented) +export type OtpPrepareFormErrorCode = 'UNKNOWN_ERROR'; + +// @public (undocumented) +export interface OtpPrepareFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + onSuccess?: (args: { + result: PrepareOtpMutationResult; + }) => void; +} + +// @public (undocumented) +export type OtpPrepareFormState = FormState; + +// @public (undocumented) +export type OtpPrepareFormValues = { + label: string; +}; + +// @public (undocumented) +export const PasswordResetForm: ({ children, onSuccess, token }: PasswordResetFormProps) => JSX_2.Element; + +// @public (undocumented) +export type PasswordResetFormContextValue = FormContextValue; + +// @public (undocumented) +export type PasswordResetFormError = FormError; + +// @public (undocumented) +export type PasswordResetFormErrorCode = ResetPasswordErrorCode | 'FIELD_REQUIRED' | 'INVALID_VALUE' | 'PASSWORD_MISMATCH' | 'UNKNOWN_ERROR'; + +// @public (undocumented) +export interface PasswordResetFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + onSuccess?: () => void; + // (undocumented) + token?: string; +} + +// @public (undocumented) +export type PasswordResetFormState = FormState; + +// @public (undocumented) +export type PasswordResetFormValues = { + token: string; + password: string; + passwordConfirmation: string; +}; + +// @public (undocumented) +export const PasswordResetRequestForm: ({ children, onSuccess }: PasswordResetRequestFormProps) => JSX_2.Element; + +// @public (undocumented) +export type PasswordResetRequestFormContextValue = FormContextValue; + +// @public (undocumented) +export type PasswordResetRequestFormError = FormError; + +// @public (undocumented) +export type PasswordResetRequestFormErrorCode = CreatePasswordResetRequestErrorCode | 'FIELD_REQUIRED' | 'INVALID_VALUE' | 'UNKNOWN_ERROR'; + +// @public (undocumented) +export interface PasswordResetRequestFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + onSuccess?: () => void; +} + +// @public (undocumented) +export type PasswordResetRequestFormState = FormState; + +// @public (undocumented) +export type PasswordResetRequestFormValues = { + email: string; +}; + +// @public (undocumented) +export interface Person { + // (undocumented) + readonly email?: string; + // (undocumented) + readonly id: string; + // (undocumented) + readonly otpEnabled: boolean; +} + +// @public (undocumented) +export const prepareOtpMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly result?: ({ + readonly otpUri: string; + } & { + readonly otpSecret: string; + }) | undefined; + }) | undefined; +}, { + readonly label?: string | undefined; +}>; + +// @public (undocumented) +export type PrepareOtpMutationResult = ModelType; + +// @public (undocumented) +export type PrepareOtpMutationVariables = Parameters>[0]; + +// Warning: (ae-forgotten-export) The symbol "projectMembershipsFragment" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type ProjectMembershipsQueryResult = readonly ModelType[]; + +// @public (undocumented) +export type ProjectMembershipsQueryVariables = { + projectSlug: string; + identityId: string; +}; + +// Warning: (ae-forgotten-export) The symbol "projectIdentityRelationFragment" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type ProjectMembersQueryResult = readonly ModelType[]; + +// @public (undocumented) +export type ProjectMembersQueryVariables = { + projectSlug: string; +} & TenantApi.ProjectMembersInput; + +// Warning: (ae-forgotten-export) The symbol "projectRolesDefinitionFragment" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type ProjectRoleDefinition = ModelType; + +// @public (undocumented) +export type ProjectRolesDefinitionQueryResult = readonly ProjectRoleDefinition[]; + +// @public (undocumented) +export interface ProjectRolesDefinitionQueryVariables { + // (undocumented) + slug: string; +} + +// @public (undocumented) +export const removeProjectMemberMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.RemoveProjectMemberErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + }) | undefined; +}, { + readonly projectSlug: string; + readonly identityId: string; +}>; + +// @public (undocumented) +export type RemoveProjectMemberMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const RemoveProjectMemberTrigger: ({ identityId, projectSlug, ...props }: RemoveProjectMemberTriggerProps) => JSX_2.Element; + +// @public (undocumented) +export type RemoveProjectMemberTriggerProps = RemoveProjectMemberMutationVariables & { + children: ReactElement; + onSuccess?: () => void; + onError?: (e: unknown) => void; +}; + +// @public (undocumented) +export const resetPasswordMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.ResetPasswordErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + }) | undefined; +}, { + readonly token: string; + readonly password: string; +}>; + +// @public (undocumented) +export type ResetPasswordMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const signInIDPMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.SignInIDPErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + } & { + readonly result?: ({ + readonly token: string; + } & { + readonly person: { + readonly id: string; + } & { + readonly email?: string | undefined; + } & { + readonly name?: string | undefined; + } & { + readonly otpEnabled: boolean; + }; + }) | undefined; + }) | undefined; +}, { + readonly identityProvider: string; + readonly data?: unknown; + readonly expiration?: number | undefined; + readonly idpResponse?: TenantApi.IDPResponseInput | undefined; + readonly redirectUrl?: string | undefined; + readonly sessionData?: unknown; +}>; + +// Warning: (ae-forgotten-export) The symbol "signInIdpFragment" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type SignInIDPMutationResult = ModelType; + +// @public (undocumented) +export type SignInIDPMutationVariables = { + identityProvider: string; + expiration?: number; + data: { + url?: string; + redirectUrl?: string; + sessionData?: any; + } & { + [key: string]: any; + }; +}; + +// @public (undocumented) +export const signInMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.SignInErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + } & { + readonly result?: ({ + readonly token: string; + } & { + readonly person: { + readonly id: string; + } & { + readonly email?: string | undefined; + } & { + readonly name?: string | undefined; + } & { + readonly otpEnabled: boolean; + }; + }) | undefined; + }) | undefined; +}, { + readonly email: string; + readonly password: string; + readonly expiration?: number | undefined; + readonly otpToken?: string | undefined; +}>; + +// Warning: (ae-forgotten-export) The symbol "signInResultFragment" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type SignInMutationResult = ModelType; + +// @public (undocumented) +export type SignInMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const signOutMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.SignOutErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + }) | undefined; + }) | undefined; +}, { + readonly all?: boolean | undefined; +}>; + +// @public (undocumented) +export type SignOutMutationVariables = Parameters>[0]; + +// @public (undocumented) +export type TenantApiOptions = { + readonly headers?: Record; + readonly apiToken?: string | typeof LoginToken; +}; + +// @public (undocumented) +export type TenantMutation = { + readonly mutation?: { + readonly ok: boolean; + readonly error?: { + readonly code: Error; + readonly developerMessage: string; + }; + readonly result?: Result; + }; +}; + +// @public (undocumented) +export type TenantMutationErrorResponse = { + ok: false; + error: Error; + developerMessage?: string; +}; + +// @public (undocumented) +export type TenantMutationOkResponse = { + ok: true; + result: Result; +}; + +// @public (undocumented) +export type TenantMutationResponse = TenantMutationOkResponse | TenantMutationErrorResponse; + +// @public (undocumented) +export type TenantQueryLoaderMethods = { + refresh: () => void; +}; + +// @public (undocumented) +export type TenantQueryLoaderState = { + state: 'loading'; +} | { + state: 'error'; + error: unknown; +} | { + state: 'success'; + data: Result; +} | { + state: 'refreshing'; + data: Result; +}; + +// @public (undocumented) +export const UpdateProjectMemberForm: ({ children, onSuccess, identityId, projectSlug }: UpdateProjectMemberFormProps) => JSX_2.Element; + +// @public (undocumented) +export type UpdateProjectMemberFormContextValue = FormContextValue; + +// @public (undocumented) +export type UpdateProjectMemberFormError = FormError; + +// @public (undocumented) +export type UpdateProjectMemberFormErrorCode = UpdateProjectMemberErrorCode | 'UNKNOWN_ERROR' | 'FIELD_REQUIRED'; + +// @public (undocumented) +export interface UpdateProjectMemberFormProps { + // (undocumented) + children: ReactElement; + // (undocumented) + identityId: string; + // (undocumented) + onSuccess?: (args: {}) => void; + // (undocumented) + projectSlug: string; +} + +// @public (undocumented) +export type UpdateProjectMemberFormState = FormState; + +// @public (undocumented) +export type UpdateProjectMemberFormValues = { + memberships: readonly MembershipInput[]; +}; + +// @public (undocumented) +export const updateProjectMemberMutation: TenantApi.MutationFetcher<{ + readonly mutation?: ({ + readonly ok: boolean; + } & { + readonly error?: ({ + readonly code: TenantApi.UpdateProjectMemberErrorCode; + } & { + readonly developerMessage: string; + } & { + readonly endUserMessage?: string | undefined; + } & { + readonly membershipValidation?: readonly ({ + readonly code: TenantApi.MembershipValidationErrorCode; + } & { + readonly role: string; + } & { + readonly variable?: string | undefined; + })[] | undefined; + }) | undefined; + }) | undefined; +}, { + readonly projectSlug: string; + readonly identityId: string; + readonly memberships: readonly TenantApi.MembershipInput[]; +}>; + +// @public (undocumented) +export type UpdateProjectMemberMutationVariables = Parameters>[0]; + +// @public (undocumented) +export const useAddProjectMemberMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly projectSlug: string; + readonly identityId: string; + readonly memberships: readonly TenantApi.MembershipInput[]; +}) => Promise>; + +// @public (undocumented) +export const useChangeMyPasswordForm: () => ChangeMyPasswordFormContextValue; + +// @public (undocumented) +export const useChangeMyPasswordMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly currentPassword: string; + readonly newPassword: string; +}) => Promise>; + +// @public (undocumented) +export const useConfirmOtpMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly otpToken: string; +}) => Promise>; + +// @public (undocumented) +export const useCreateApiKeyForm: () => CreateApiKeyFormContextValue; + +// @public (undocumented) +export const useCreateApiKeyMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly projectSlug: string; + readonly memberships: readonly TenantApi.MembershipInput[]; + readonly description: string; + readonly tokenHash?: string | undefined; +}) => Promise>; + +// @public (undocumented) +export const useCreateGlobalApiKeyMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly description: string; + readonly roles?: readonly string[] | undefined; + readonly tokenHash?: string | undefined; +}) => Promise>; + +// @public (undocumented) +export const useCreateResetPasswordRequestMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly email: string; + readonly options?: TenantApi.CreateResetPasswordRequestOptions | undefined; +}) => Promise>; + +// @public (undocumented) +export const useCreateSessionTokenForm: () => CreateSessionTokenFormContextValue; + +// @public (undocumented) +export const useCreateSessionTokenMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly email?: string | undefined; + readonly personId?: string | undefined; + readonly expiration?: number | undefined; +}) => Promise>; + +// @public (undocumented) +export const useDisableApiKeyMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly id: string; +}) => Promise>; + +// @public (undocumented) +export const useDisableOtpMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: {}) => Promise>; + +// @public (undocumented) +export const useFetchIdentity: () => [{ + state: IdentityStateValue; + identity: Identity | undefined; +}, IdentityMethods]; + +// @public (undocumented) +export const useForm: () => FormContextValue; + +// @public (undocumented) +export const useIdentity: () => Identity | undefined; + +// @public (undocumented) +export const useIdentityMethods: () => IdentityMethods; + +// @public (undocumented) +export const useIdentityState: () => IdentityStateValue; + +// @public (undocumented) +export const useIDPMethods: () => IDPMethods; + +// @public (undocumented) +export const useIDPState: () => IDPStateValue; + +// @public (undocumented) +export const useInitSignInIDPMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: InitSignInIDPMutationVariables) => Promise>; + +// @public (undocumented) +export const useInviteForm: () => InviteFormContextValue; + +// @public (undocumented) +export const useInviteMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly email: string; + readonly name?: string | undefined; + readonly projectSlug: string; + readonly memberships: readonly TenantApi.MembershipInput[]; + readonly options?: TenantApi.InviteOptions | undefined; +}) => Promise>; + +// @public (undocumented) +export const useLoginForm: () => LoginFormContextValue; + +// @public (undocumented) +export const useLogout: () => ({ noRedirect }?: { + noRedirect?: boolean | undefined; +}) => Promise; + +// @public (undocumented) +export const useMeQuery: (options?: TenantApiOptions) => ({}: {}) => Promise; + +// @public (undocumented) +export const useOtpConfirmForm: () => OtpConfirmFormContextValue; + +// @public (undocumented) +export const useOtpPrepareForm: () => OtpPrepareFormContextValue; + +// @public (undocumented) +export const usePasswordResetForm: () => PasswordResetFormContextValue; + +// @public (undocumented) +export const usePasswordResetRequestForm: () => PasswordResetRequestFormContextValue; + +// @public (undocumented) +export const usePrepareOtpMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly label?: string | undefined; +}) => Promise>; + +// @public (undocumented) +export const useProjectMembershipsQuery: (options?: TenantApiOptions) => (input: ProjectMembershipsQueryVariables) => Promise; + +// @public (undocumented) +export const useProjectMembersQuery: ({ headers, apiToken }?: TenantApiOptions) => ({ projectSlug, ...membersInput }: ProjectMembersQueryVariables) => Promise; + +// @public (undocumented) +export const useProjectRolesDefinitionQuery: ({ headers, apiToken }?: TenantApiOptions) => (variables: ProjectRolesDefinitionQueryVariables) => Promise; + +// @public (undocumented) +export const useRemoveProjectMemberMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly projectSlug: string; + readonly identityId: string; +}) => Promise>; + +// @public (undocumented) +export const useResetPasswordMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly token: string; + readonly password: string; +}) => Promise>; + +// @public (undocumented) +export const useSignInIDPMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: SignInIDPMutationVariables) => Promise>; + +// @public (undocumented) +export const useSignInMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly email: string; + readonly password: string; + readonly expiration?: number | undefined; + readonly otpToken?: string | undefined; +}) => Promise>; + +// @public (undocumented) +export const useSignOutMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly all?: boolean | undefined; +}) => Promise>; + +// @public (undocumented) +export const useTenantApi: ({ headers, apiToken }?: TenantApiOptions) => (fetcher: Fetcher<'Query' | 'Mutation', TData, TVariables>, options?: { readonly variables?: TVariables; readonly headers?: Record; - readonly apiToken?: string; + readonly apiToken?: string | typeof LoginToken; }) => Promise; +// @public (undocumented) +export const useTenantMutation: (fetcher: MutationFetcher, TVariables>, { headers, apiToken }?: TenantApiOptions) => (variables: TVariables) => Promise>; + +// @public (undocumented) +export const useTenantQueryLoader: (fetcher: (variables: TVariables) => Promise, variables: TVariables) => [TenantQueryLoaderState, TenantQueryLoaderMethods]; + +// @public (undocumented) +export const useUpdateProjectMemberForm: () => UpdateProjectMemberFormContextValue; + +// @public (undocumented) +export const useUpdateProjectMemberMutation: ({ headers, apiToken }?: TenantApiOptions) => (variables: { + readonly projectSlug: string; + readonly identityId: string; + readonly memberships: readonly TenantApi.MembershipInput[]; +}) => Promise>; + // (No @packageDocumentation comment for this package) ``` diff --git a/build/api/react-identity.api.md b/build/api/react-identity.api.md index fd11848e09..a34b6f5f05 100644 --- a/build/api/react-identity.api.md +++ b/build/api/react-identity.api.md @@ -5,7 +5,7 @@ ```ts import { Environment } from '@contember/react-binding'; -import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { Identity } from '@contember/react-client-tenant'; import { NamedExoticComponent } from 'react'; import { ReactNode } from 'react'; @@ -20,79 +20,18 @@ export interface HasRoleProps { role: RoleCondition; } -// @public (undocumented) -export interface Identity { - // (undocumented) - readonly id: string; - // (undocumented) - readonly permissions: { - readonly canCreateProject: boolean; - }; - // (undocumented) - readonly person?: Person; - // (undocumented) - readonly projects: IdentityProject[]; -} - // @public (undocumented) export const identityEnvironmentExtension: Environment.Extension; // @public (undocumented) -export interface IdentityMethods { - // (undocumented) - clearIdentity: () => void; - // (undocumented) - refreshIdentity: () => Promise; -} - -// @public (undocumented) -export interface IdentityProject { - // (undocumented) - readonly name: string; - // (undocumented) - readonly roles: readonly string[]; - // (undocumented) - readonly slug: string; -} - -// @public (undocumented) -export const IdentityProvider: React.FC; - -// @public (undocumented) -export interface IdentityProviderProps { - // (undocumented) - children: ReactNode; -} - -// @public (undocumented) -export const IdentityState: ({ state, children }: IdentityStateProps) => JSX_2.Element | null; +export const IdentityEnvironmentProvider: React.FC; // @public (undocumented) -export interface IdentityStateProps { - // (undocumented) - children: ReactNode; +export interface IdentityEnvironmentProviderProps { // (undocumented) - state: IdentityStateValue | IdentityStateValue[]; -} - -// @public (undocumented) -export type IdentityStateValue = 'none' | 'loading' | 'failed' | 'cleared' | 'success'; - -// @public (undocumented) -export const LogoutTrigger: ({ children }: { children: ReactNode; -}) => JSX_2.Element; - -// @public (undocumented) -export interface Person { - // (undocumented) - readonly email?: string; - // (undocumented) - readonly id: string; - // (undocumented) - readonly otpEnabled: boolean; } // @public (undocumented) @@ -107,27 +46,10 @@ export type ProjectUserRoles = Set; export type RoleCondition = string | ((roles: Set) => boolean); // @public (undocumented) -export const useFetchIdentity: () => [{ - state: IdentityStateValue; - identity: Identity | undefined; -}, IdentityMethods]; - -// @public (undocumented) -export const useIdentity: () => Identity | undefined; - -// @public (undocumented) -export const useIdentityMethods: () => IdentityMethods; - -// @public (undocumented) -export const useIdentityState: () => IdentityStateValue; +export const useProjectUserRoles: () => ProjectUserRoles; -// @public (undocumented) -export const useLogout: () => ({ noRedirect }?: { - noRedirect?: boolean | undefined; -}) => Promise; -// @public (undocumented) -export const useProjectUserRoles: () => ProjectUserRoles; +export * from "@contember/react-client-tenant"; // (No @packageDocumentation comment for this package) diff --git a/build/api/react-routing.api.md b/build/api/react-routing.api.md index f252d2a82e..25ad208c9d 100644 --- a/build/api/react-routing.api.md +++ b/build/api/react-routing.api.md @@ -273,6 +273,14 @@ export class RoutingParameter { // @public (undocumented) export type RoutingParameterResolver = (name: string) => RequestParameterValue | undefined; +// @public (undocumented) +export const RoutingProvider: ({ children, ...props }: RoutingProviderProps) => JSX_2.Element; + +// @public (undocumented) +export type RoutingProviderProps = Partial & { + children: ReactNode; +}; + // @public (undocumented) export interface SelectedDimension { // (undocumented) diff --git a/contember.yaml b/contember.yaml index dca5d5c11b..fe9c5e7ef6 100644 --- a/contember.yaml +++ b/contember.yaml @@ -1,3 +1,2 @@ projects: - admin-sandbox: packages/admin-sandbox playground: packages/playground diff --git a/docker-compose.yaml b/docker-compose.yaml index fd57d5e15d..52e377557c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -144,6 +144,7 @@ services: CONTEMBER_SKIP_VERSION_CHECK: 1 CONTEMBER_API_URL: 'http://contember-engine:4000/' CONTEMBER_API_TOKEN: '0000000000000000000000000000000000000000' + CONTEMBER_PROJECT_NAME: playground volumes: - ./:/src diff --git a/packages/admin/src/components/Identity/IdentityProvider.tsx b/packages/admin/src/components/Identity/IdentityProvider.tsx index ab4c746435..19783d8ebd 100644 --- a/packages/admin/src/components/Identity/IdentityProvider.tsx +++ b/packages/admin/src/components/Identity/IdentityProvider.tsx @@ -2,7 +2,7 @@ import { Message, SpinnerOverlay } from '@contember/ui' import { ReactNode, useEffect } from 'react' import { MiscPageLayout } from '../MiscPageLayout' import { InvalidIdentityFallback } from './InvalidIdentityFallback' -import { IdentityProvider as BaseIdentityProvider, IdentityState } from '@contember/react-identity' +import { IdentityProvider as BaseIdentityProvider, IdentityEnvironmentProvider, IdentityState } from '@contember/react-identity' export interface IdentityProviderProps { children: ReactNode @@ -28,21 +28,23 @@ const ClearIdentityHandler = ({ onInvalidIdentity }: { export const IdentityProvider: React.FC = ({ children, onInvalidIdentity, allowUnauthenticated }) => { return ( - - - - Logging out… - - - - - - - - - - {children} - + + + + + Logging out… + + + + + + + + + + {children} + + ) } diff --git a/packages/interface/src/bootstrap/ApplicationEntrypoint.tsx b/packages/interface/src/bootstrap/ApplicationEntrypoint.tsx index 6a1c9b2bdc..f78b49eef3 100644 --- a/packages/interface/src/bootstrap/ApplicationEntrypoint.tsx +++ b/packages/interface/src/bootstrap/ApplicationEntrypoint.tsx @@ -1,9 +1,9 @@ import { Environment, EnvironmentContext, EnvironmentExtensionProvider } from '@contember/react-binding' import { ContemberClient, ContemberClientProps } from '@contember/react-client' import { ReactNode } from 'react' -import { RequestProvider, RouteMap, RoutingContext, RoutingContextValue, SelectedDimension } from '@contember/react-routing' +import { RequestProvider, RouteMap, RoutingContext, RoutingContextValue, RoutingProvider, SelectedDimension } from '@contember/react-routing' import { DataViewPageNameKeyProvider } from './DataViewPageNameKeyProvider' -import { IdentityProvider, projectEnvironmentExtension } from '@contember/react-identity' +import { IdentityEnvironmentProvider, IdentityProvider, projectEnvironmentExtension } from '@contember/react-identity' export interface ApplicationEntrypointProps extends ContemberClientProps { basePath: string @@ -36,25 +36,25 @@ export const ApplicationEntrypoint = (props: ApplicationEntrypointProps) => { return ( - - - - - - + + + + + + {props.children} - - - - - - + + + + + + ) } diff --git a/packages/playground/admin/.env.development b/packages/playground/admin/.env.development index 313e4539c8..ae8b9bc7b6 100644 --- a/packages/playground/admin/.env.development +++ b/packages/playground/admin/.env.development @@ -1,3 +1,4 @@ VITE_CONTEMBER_ADMIN_API_BASE_URL=http://localhost:3001 VITE_CONTEMBER_ADMIN_SESSION_TOKEN=0000000000000000000000000000000000000000 +VITE_CONTEMBER_ADMIN_LOGIN_TOKEN=1111111111111111111111111111111111111111 VITE_CONTEMBER_ADMIN_PROJECT_NAME=playground diff --git a/packages/playground/admin/.env.production b/packages/playground/admin/.env.production index c91bc3e887..9a64004ec7 100644 --- a/packages/playground/admin/.env.production +++ b/packages/playground/admin/.env.production @@ -1,2 +1,3 @@ VITE_CONTEMBER_ADMIN_API_BASE_URL=/_api VITE_CONTEMBER_ADMIN_SESSION_TOKEN=__SESSION_TOKEN__ +VITE_CONTEMBER_ADMIN_LOGIN_TOKEN=__LOGIN_TOKEN__ diff --git a/packages/playground/admin/app/components/navigation.tsx b/packages/playground/admin/app/components/navigation.tsx index 88de28ffda..9c43b8673c 100644 --- a/packages/playground/admin/app/components/navigation.tsx +++ b/packages/playground/admin/app/components/navigation.tsx @@ -1,4 +1,4 @@ -import { ArchiveIcon, BrushIcon, FormInputIcon, GripVertical, HomeIcon, KanbanIcon, LanguagesIcon, PencilIcon, TableIcon, UploadIcon } from 'lucide-react' +import { ArchiveIcon, BrushIcon, FormInputIcon, GripVertical, HomeIcon, KanbanIcon, KeyRoundIcon, LanguagesIcon, LockKeyholeIcon, PencilIcon, TableIcon, UploadIcon, UserIcon, UsersIcon } from 'lucide-react' import { Menu, MenuItem } from '../../lib/components/ui/menu' @@ -8,6 +8,11 @@ export const Navigation = () => {
} label={'Home'} to={'index'} /> + } label={'Tenant'}> + } label={'Security'} to={'tenant/security'} /> + } label={'Members'} to={'tenant/members'} /> + } label={'API keys'} to={'tenant/apiKeys'} /> + } label={'UI'}> diff --git a/packages/playground/admin/config.ts b/packages/playground/admin/app/config.ts similarity index 85% rename from packages/playground/admin/config.ts rename to packages/playground/admin/app/config.ts index fe47dbbf02..7dd15ebfd8 100644 --- a/packages/playground/admin/config.ts +++ b/packages/playground/admin/app/config.ts @@ -1,14 +1,13 @@ export const getConfig = () => { let project = import.meta.env.VITE_CONTEMBER_ADMIN_PROJECT_NAME + if (project === '__PROJECT_SLUG__') { project = window.location.pathname.split('/')[1] } - let basePath = import.meta.env.BASE_URL ?? '/' - if (basePath === './') { - basePath = `/${project}/` - } + const basePath = `/${window.location.pathname.split('/')[1]}/` + const apiBaseUrl = import.meta.env.VITE_CONTEMBER_ADMIN_API_BASE_URL as string if (!apiBaseUrl) { throw new Error('VITE_CONTEMBER_ADMIN_API_BASE_URL is not set') diff --git a/packages/playground/admin/app/index.html b/packages/playground/admin/app/index.html new file mode 100644 index 0000000000..d6c212019e --- /dev/null +++ b/packages/playground/admin/app/index.html @@ -0,0 +1,14 @@ + + + + + + + + + Contember Interface + + + + + diff --git a/packages/playground/admin/app/index.tsx b/packages/playground/admin/app/index.tsx new file mode 100644 index 0000000000..3778c76c8e --- /dev/null +++ b/packages/playground/admin/app/index.tsx @@ -0,0 +1,40 @@ +import { ApplicationEntrypoint, PageModule, Pages } from '@contember/interface' +import { SlotsProvider } from '@contember/react-slots' +import { Layout } from './components/layout' +import '../index.css' +import { Toaster } from '../lib/components/ui/toast' +import { createErrorHandler, DevBar, DevPanel } from '@contember/react-devbar' +import { LogInIcon } from 'lucide-react' +import { LoginWithEmail } from '../lib/components/dev/login-panel' +import { createRoot } from 'react-dom/client' +import { getConfig } from './config' +import { OutdatedApplicationDialog } from '../lib/components/outdated-application-dialog' + +const errorHandler = createErrorHandler((dom, react, onRecoverableError) => createRoot(dom, { onRecoverableError }).render(react)) + +const rootEl = document.body.appendChild(document.createElement('div')) + +errorHandler(onRecoverableError => createRoot(rootEl, { onRecoverableError }).render(<> + + + + ( + './pages/**/*.tsx', + { eager: true }, + )} + /> + {import.meta.env.DEV && + }> + } + + + + } + /> + +)) diff --git a/packages/playground/admin/app/pages/tenant.tsx b/packages/playground/admin/app/pages/tenant.tsx new file mode 100644 index 0000000000..11771f728c --- /dev/null +++ b/packages/playground/admin/app/pages/tenant.tsx @@ -0,0 +1,125 @@ +import { ChangeMyPasswordForm, CreateApiKeyForm, InviteForm } from '@contember/react-identity' +import { Card, CardContent, CardHeader, CardTitle } from '../../lib/components/ui/card' +import { ChangeMyPasswordFormFields } from '../../lib/components/tenant/changeMyPasswordForm' +import { ToastContent, useShowToast } from '../../lib/components/ui/toast' +import { OtpSetup } from '../../lib/components/tenant/otpSetup' +import { PersonList } from '../../lib/components/tenant/personList' +import { InviteFormFields } from '../../lib/components/tenant/inviteForm' +import { useProjectSlug } from '@contember/react-client' +import { Input } from '../../lib/components/ui/input' +import { CreateApiKeyFormFields } from '../../lib/components/tenant/createApiKeyForm' +import { ApiKeyList } from '../../lib/components/tenant/apiKeyList' +import { useRef } from 'react' +import { MemberListController } from '../../lib/components/tenant/memberList' + +export const Security = () => { + const showToast = useShowToast() + return ( +
+ + + Change Password + + + showToast(Password changed, { type: 'success' })}> +
+ + +
+
+
+ + + Two-factor setup + + + + + +
+ ) +} + + +export const Members = () => { + const projectSlug = useProjectSlug()! + const showToast = useShowToast() + const memberListController = useRef() + return ( +
+
+ + + Members + + + + + +
+
+ + + Invite + + + { + showToast(Invitation sent to {args.result.person?.email}, { type: 'success' }) + memberListController.current?.refresh() + }} + > +
+ + +
+
+
+
+
+ ) +} + +export const ApiKeys = () => { + const projectSlug = useProjectSlug()! + const showToast = useShowToast() + const memberListController = useRef() + return ( +
+
+ + + API keys + + + + + +
+
+ + + Create API key + + + { + showToast(, { type: 'success' }) + memberListController.current?.refresh() + }} + > +
+ + +
+
+ +
+
+
+ ) +} diff --git a/packages/playground/admin/app/pages/ui/button.tsx b/packages/playground/admin/app/pages/ui/button.tsx index 58f1c5811c..f9c84a88ec 100644 --- a/packages/playground/admin/app/pages/ui/button.tsx +++ b/packages/playground/admin/app/pages/ui/button.tsx @@ -3,7 +3,7 @@ import { HomeIcon } from 'lucide-react' export default <> -
+

Button variants

diff --git a/packages/playground/admin/index.html b/packages/playground/admin/index.html index 189bd7f3c9..07d3d87b7c 100644 --- a/packages/playground/admin/index.html +++ b/packages/playground/admin/index.html @@ -4,10 +4,10 @@ - - + + Contember Interface - + diff --git a/packages/playground/admin/index.tsx b/packages/playground/admin/index.tsx index 0c9bc56b30..f164f3eeaf 100644 --- a/packages/playground/admin/index.tsx +++ b/packages/playground/admin/index.tsx @@ -1,40 +1,251 @@ -import { ApplicationEntrypoint, PageModule, Pages } from '@contember/interface' -import { SlotsProvider } from '@contember/react-slots' -import { Layout } from './app/components/layout' import './index.css' -import { Toaster } from './lib/components/ui/toast' -import { createErrorHandler, DevBar, DevPanel } from '@contember/react-devbar' -import { LogInIcon } from 'lucide-react' -import { LoginWithEmail } from './lib/components/dev/login-panel' +import { createErrorHandler } from '@contember/react-devbar' import { createRoot } from 'react-dom/client' -import { getConfig } from './config' -import { OutdatedApplicationDialog } from './lib/components/outdated-application-dialog' +import { LoginFormFields } from './lib/components/tenant/loginForm' +import { ContemberClient } from '@contember/react-client' +import { IdentityProvider, IdentityState, IDP, IDPInitTrigger, IDPState, LoginForm, LogoutTrigger, PasswordResetForm, PasswordResetRequestForm } from '@contember/react-identity' +import { Link, RoutingProvider, useCurrentRequest, useRedirect } from '@contember/react-routing' +import { Pages, useIdentity } from '@contember/interface' +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './lib/components/ui/card' +import { AnchorButton, Button } from './lib/components/ui/button' +import { PasswordResetRequestFormFields } from './lib/components/tenant/passwordResetRequestForm' +import { MailIcon } from 'lucide-react' +import { PasswordResetFormFields } from './lib/components/tenant/passwordResetForm' +import { ToastContent, Toaster, useShowToast } from './lib/components/ui/toast' +import { Loader } from './lib/components/ui/loader' +import { Overlay } from './lib/components/ui/overlay' +import { useEffect } from 'react' +import { dict } from './lib/dict' const errorHandler = createErrorHandler((dom, react, onRecoverableError) => createRoot(dom, { onRecoverableError }).render(react)) const rootEl = document.body.appendChild(document.createElement('div')) +const Idps = { + google: 'Login with Google', +} + +const hasTokenFromEnv = import.meta.env.VITE_CONTEMBER_ADMIN_SESSION_TOKEN !== '__SESSION_TOKEN__' +const appUrl = '/app' + +const Login = () => { + const showToast = useShowToast() + return <> + showToast({dict.tenant.login.idpInitError} {error}, { type: 'error' })} + onResponseError={error => showToast({dict.tenant.login.idpResponseError} {error}, { type: 'error' })} + > + + + + {dict.tenant.login.title} + + {dict.tenant.login.description} + + + + {hasTokenFromEnv && + Continue as default user + } + +
+ + +
+ + {Object.entries(Idps).map(([idp, label]) => ( + + + + ))} +
+ + + +
+
+ +} + +const LoggedIn = () => { + const identity = useIdentity() + useEffect(() => { + setTimeout(() => { + window.location.href = appUrl + }, 500) + }, []) + + return ( + + + Logged in + + as {identity?.person?.email ?? 'unknown'} + + + + + ) + +} + +const IndexPage = () => { + return ( + + + + + + + + + + + + +
+

+ Failed to load identity. +

+ + + +
+
+
+
+ ) +} + +const PasswordResetRequestPage = () => { + const redirect = useRedirect() + return ( + + + {dict.tenant.passwordResetRequest.title} + + {dict.tenant.passwordResetRequest.description} + + + + redirect('resetRequestSuccess')}> +
+ + +
+
+ + + + {dict.tenant.login.backToLogin} + + + +
+ ) +} + +const PasswordResetPage = () => { + const request = useCurrentRequest() + const redirect = useRedirect() + const showToast = useShowToast() + const token = request?.parameters.token as string | undefined + return ( + + + {dict.tenant.passwordReset.title} + + {dict.tenant.passwordReset.description} + + + + { + showToast( + Password has been reset + , { type: 'success' }) + redirect('index') + }} token={token}> +
+ + +
+
+ + + + {dict.tenant.login.backToLogin} + + + +
+ ) +} + +const PasswordResetRequestSuccessPage = () => ( + + + {dict.tenant.passwordResetRequest.title} + + {dict.tenant.passwordResetRequest.description} + + + +
+ +
+ An email with password reset instructions has been sent to your email address. +
+ +
+
+ + + + {dict.tenant.login.backToLogin} + + + +
+) + +const Layout = ({ children }: { children?: React.ReactNode }) => ( +
+
+ {children} +
+
+
+
Welcome to your app
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, sem eget ultricies ultricies, sapien urna tristique eros, ac tincidunt felis lacus nec nunc.

+
+
+
+) + + errorHandler(onRecoverableError => createRoot(rootEl, { onRecoverableError }).render(<> - - - - ( - './app/pages/**/*.tsx', - { eager: true }, - )} - /> - - {import.meta.env.DEV && - }> - } - - - } - /> - + + + + + + + + )) + diff --git a/packages/playground/admin/lib/components/dev/login-panel.tsx b/packages/playground/admin/lib/components/dev/login-panel.tsx index 14050d8b15..b94a8ffe1e 100644 --- a/packages/playground/admin/lib/components/dev/login-panel.tsx +++ b/packages/playground/admin/lib/components/dev/login-panel.tsx @@ -1,50 +1,56 @@ -import { SyntheticEvent, useCallback, useState } from 'react' -import { ToastContent, useShowToast } from '../ui/toast' -import { StandaloneFormContainer } from '../form' -import { Input } from '../ui/input' import { Button } from '../ui/button' import { useSessionTokenWithMeta, useSetSessionToken } from '@contember/react-client' -import * as TenantApi from '@contember/graphql-client-tenant' -import { useTenantApi } from '@contember/react-client-tenant' +import { CreateSessionTokenForm, useCreateSessionTokenForm } from '@contember/react-client-tenant' +import { Loader } from '../ui/loader' +import { TenantFormError, TenantFormField } from '../tenant/common' export const LoginWithEmail = () => { - const [email, setEmail] = useState('') const sessionToken = useSessionTokenWithMeta() - const addToast = useShowToast() - const [isSubmitting, setSubmitting] = useState(false) const setSessionToken = useSetSessionToken() - const api = useTenantApi() - - const submit = useCallback(async (e: SyntheticEvent) => { - e.preventDefault() - - setSubmitting(true) - const response = await api(TenantApi.mutation$.createSessionToken(TenantApi.createSessionTokenResponse$$.error(TenantApi.createSessionTokenError$$).result(TenantApi.createSessionTokenResult$$)), { - variables: { - email, - }, - apiToken: sessionToken.propsToken, - }) - setSubmitting(false) - - if (!response.createSessionToken?.ok) { - switch (response.createSessionToken?.error?.code) { - case 'UNKNOWN_EMAIL': - return addToast(, { type: 'error' }) - } - } else { - setSessionToken(response.createSessionToken.result!.token) - } - }, [addToast, api, email, sessionToken.propsToken, setSessionToken]) return <> -
-
- - setEmail(e.target.value)} /> - - -
-
+ { + setSessionToken(it.result.token) + }}> +
+ + +
} + + +const CreateSessionTokenFormFields = () => { + const form = useCreateSessionTokenForm() + + const messages = { + FIELD_REQUIRED: 'This field is required', + UNKNOWN_ERROR: 'An unknown error occurred', + PERSON_DISABLED: 'Person is disabled', + UNKNOWN_EMAIL: 'Person with given email not found', + UNKNOWN_PERSON_ID: 'Person with given ID not found', + } + return ( +
+ {form.state === 'submitting' ? : null} + + + + + E-mail + + + +
+ ) +} diff --git a/packages/playground/admin/lib/components/form/ui.tsx b/packages/playground/admin/lib/components/form/ui.tsx index ef720857e9..820eff3981 100644 --- a/packages/playground/admin/lib/components/form/ui.tsx +++ b/packages/playground/admin/lib/components/form/ui.tsx @@ -20,7 +20,7 @@ export const FormLabelWrapperUI = uic('div', { displayName: 'FormLabelWrapper', }) export const FormLabelUI = uic(Label, { - baseClass: 'data-[invalid]:text-destructive text-left', + baseClass: 'text-left', displayName: 'FormLabel', }) export const FormContainerUI = uic('div', { diff --git a/packages/playground/admin/lib/components/tenant/apiKeyList.tsx b/packages/playground/admin/lib/components/tenant/apiKeyList.tsx new file mode 100644 index 0000000000..ae000a2f36 --- /dev/null +++ b/packages/playground/admin/lib/components/tenant/apiKeyList.tsx @@ -0,0 +1,25 @@ +import { ProjectMembersFilter } from '@contember/graphql-client-tenant' +import * as React from 'react' +import { TableCell } from '../ui/table' +import { dict } from '../../dict' +import { MemberList, MemberListController } from './memberList' + +const filter: ProjectMembersFilter = { + memberType: 'API_KEY', +} + +export const ApiKeyList = (props: { controller?: { current?: MemberListController } }) => ( + <> + {it.identity.description ?? dict.tenant.apiKeyList.unnamed} + } + /> +) diff --git a/packages/playground/admin/lib/components/tenant/changeMyPasswordForm.tsx b/packages/playground/admin/lib/components/tenant/changeMyPasswordForm.tsx new file mode 100644 index 0000000000..2fd06f05bb --- /dev/null +++ b/packages/playground/admin/lib/components/tenant/changeMyPasswordForm.tsx @@ -0,0 +1,40 @@ +import { ChangeMyPasswordFormErrorCode, useChangeMyPasswordForm } from '@contember/react-identity' +import { Button } from '../ui/button' +import { Loader } from '../ui/loader' +import { TenantFormError, TenantFormField } from './common' +import { dict } from '../../dict' + + +export const ChangeMyPasswordFormFields = () => { + const form = useChangeMyPasswordForm() + return ( +
+ {form.state === 'submitting' ? : null} + + + {dict.tenant.changePassword.currentPassword} + + + {dict.tenant.changePassword.newPassword} + + + {dict.tenant.changePassword.confirmPassword} + + + +
+ ) +} diff --git a/packages/playground/admin/lib/components/tenant/common.tsx b/packages/playground/admin/lib/components/tenant/common.tsx new file mode 100644 index 0000000000..9790717247 --- /dev/null +++ b/packages/playground/admin/lib/components/tenant/common.tsx @@ -0,0 +1,94 @@ +import { FormContextValue } from '@contember/react-identity' +import { FormErrorUI } from '../form' +import { Input } from '../ui/input' +import { HTMLInputTypeAttribute, useState } from 'react' +import { dataAttribute } from '@contember/utilities' +import { Label } from '../ui/label' + +export interface TenantFormErrorsProps> { + form: CtxValue + field?: keyof CtxValue['values'] + messages: Record ? E : never, string> +} + +export const TenantFormError = >({ form, field, messages }: TenantFormErrorsProps) => { + return ( +
+ {form.errors + .filter(error => error.field === field) + .filter(it => !(it.code in messages && ((messages as any)[it.code] === undefined))) + .map(error => [error.code, { error: (messages as any)[error.code] || 'Unknown error', developerMessage: error.developerMessage }]) + .map(([code, error]) => { + return () + })} +
+ ) +} + +const TenantFormSingleError = ({ error, developerMessage }: { error: string, developerMessage?: string }) => { + const [showDeveloperMessage, setShowDeveloperMessage] = useState(false) + return <> + {error} + {/*{developerMessage &&
*/} + {/* {showDeveloperMessage*/} + {/* ?
{developerMessage}
*/} + {/* : }*/} + {/*
}*/} + +} + +export type TenantFormInputProps> = + & { + form: CtxValue + type: HTMLInputTypeAttribute + field: keyof CtxValue['values'] & string + } + & Omit>, 'form' | 'field'> + +export const TenantFormInput = >({ form, field, ...props }: TenantFormInputProps) => { + return ( + form.setValue(field, e.target.value)} + value={form.values[field]} + data-invalid={dataAttribute(form.errors.some(it => it.field === field))} + {...props} + /> + ) +} + +export type TenantFormLabelProps> = + & { + form: CtxValue + field: keyof CtxValue['values'] & string + } + & Omit>, 'form' | 'htmlFor'> + +export const TenantFormLabel = >({ form, field, ...props }: TenantFormLabelProps) => { + return ( +