diff --git a/src/components/organizations/CreateOrganizationDialog.tsx b/src/components/organizations/CreateOrganizationDialog.tsx new file mode 100644 index 0000000000..2d3534be46 --- /dev/null +++ b/src/components/organizations/CreateOrganizationDialog.tsx @@ -0,0 +1,108 @@ +import { gql, useApolloClient } from '@apollo/client' +import { useFormik } from 'formik' +import { forwardRef } from 'react' +import { object, string } from 'yup' + +import { Button } from '~/components/designSystem/Button' +import { Dialog, DialogRef } from '~/components/designSystem/Dialog' +import { TextInputField } from '~/components/form' +import { addToast, hasDefinedGQLError, switchCurrentOrganization } from '~/core/apolloClient' +import { HOME_ROUTE, useNavigate } from '~/core/router' +// NOTE: `useCreateOrganizationMutation` is produced by graphql-codegen from the +// `gql` block below once the matching `createOrganization` mutation exists in +// the API schema (see ThinkfleetAI/lago-api feat/create-organization-mutation). +// Run `pnpm codegen` after deploying that API change. +import { LagoApiError, useCreateOrganizationMutation } from '~/generated/graphql' +import { useInternationalization } from '~/hooks/core/useInternationalization' + +gql` + mutation createOrganization($name: String!) { + createOrganization(name: $name) { + id + name + } + } +` + +export type CreateOrganizationDialogRef = DialogRef + +export const CreateOrganizationDialog = forwardRef((_props, ref) => { + const { translate } = useInternationalization() + const apolloClient = useApolloClient() + const navigate = useNavigate() + + const [createOrganization] = useCreateOrganizationMutation({ + context: { silentErrorCodes: [LagoApiError.UnprocessableEntity] }, + onCompleted: async ({ createOrganization: organization }) => { + if (!organization?.id) return + + addToast({ + severity: 'success', + // Plain message (no translation key yet); add a key when localizing. + message: translate('text_create_organization_success') || 'Organization created', + }) + + // Re-scope the whole app to the brand-new org, then land on its home. + await switchCurrentOrganization(apolloClient, organization.id) + navigate(HOME_ROUTE) + }, + }) + + const formikProps = useFormik<{ name: string }>({ + initialValues: { name: '' }, + validationSchema: object().shape({ + name: string().required(''), + }), + validateOnMount: true, + enableReinitialize: true, + onSubmit: async ({ name }, { resetForm }) => { + const result = await createOrganization({ variables: { name: name.trim() } }) + + if (result.errors) { + if (hasDefinedGQLError('ValueAlreadyExist', result.errors, 'name')) { + formikProps.setFieldError('name', 'Organization name is already used') + } + return + } + + resetForm() + }, + }) + + return ( + formikProps.resetForm()} + actions={({ closeDialog }) => ( + <> + + + + )} + > +
+ +
+
+ ) +}) + +CreateOrganizationDialog.displayName = 'CreateOrganizationDialog' diff --git a/src/layouts/MainNavLayout/OrganizationSwitcher.tsx b/src/layouts/MainNavLayout/OrganizationSwitcher.tsx index 02d8009495..51cdbaa913 100644 --- a/src/layouts/MainNavLayout/OrganizationSwitcher.tsx +++ b/src/layouts/MainNavLayout/OrganizationSwitcher.tsx @@ -1,16 +1,18 @@ import { ApolloClient, ApolloError } from '@apollo/client' import { captureException } from '@sentry/react' import { ConditionalWrapper, Icon } from 'lago-design-system' -import { useState } from 'react' +import { useRef, useState } from 'react' import { useParams } from 'react-router-dom' import { Avatar } from '~/components/designSystem/Avatar' import { Button } from '~/components/designSystem/Button' +import { DialogRef } from '~/components/designSystem/Dialog' import { Popper } from '~/components/designSystem/Popper' import { Skeleton } from '~/components/designSystem/Skeleton' import { Tooltip } from '~/components/designSystem/Tooltip' import { Typography } from '~/components/designSystem/Typography' import { VerticalMenuSectionTitle } from '~/components/designSystem/VerticalMenu' +import { CreateOrganizationDialog } from '~/components/organizations/CreateOrganizationDialog' import { addToast, logOut, switchCurrentOrganization } from '~/core/apolloClient' import { authenticationMethodsMapping } from '~/core/constants/authenticationMethodsMapping' import { HOME_ROUTE, useNavigate } from '~/core/router' @@ -61,6 +63,7 @@ export const OrganizationSwitcher = ({ const navigate = useNavigate() const { organizationSlug } = useParams<{ organizationSlug: string }>() const [isSwitchingOrg, setIsSwitchingOrg] = useState(false) + const createOrgDialogRef = useRef(null) const organizationList: OrganizationFromMembership[] | undefined = currentUser?.memberships.map( (membership) => membership.organization, @@ -266,6 +269,22 @@ export const OrganizationSwitcher = ({ )} +
+ +
+