react-query#useMutation TypeScript Examples

The following examples show how to use react-query#useMutation. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useCreateAddress.ts    From wildduck-ui with MIT License 8 votes vote down vote up
useCreateAddress = () => {
	const queryClient = useQueryClient();

	return useMutation(
		({ userId, addressDetails }: IProp) => api.addressApi.createUserAddress(userId, addressDetails),
		{
			onError: () => {
				AppEvents.publish(Events.Error, 'Error');
			},
			onSuccess: ({ data }) => {
				handleError(data);
				queryClient.invalidateQueries('useAddress');
			},
		},
	);
}
Example #2
Source File: index.tsx    From RareCamp with Apache License 2.0 6 votes vote down vote up
AccountMenu = () => {
  const router = useRouter()
  const mutation = useMutation(() => Auth.signOut({ global: true }), {
    onSuccess: router.reload,
    onError: (err: Error) =>
      notification.error({
        message: 'Can not logout',
        description: err.message,
        placement: 'topRight',
        duration: 1.5,
      }),
  })
  return (
    <OFMenu>
      <Menu.Item>
        <Link href="/auth/password">Update Password</Link>
      </Menu.Item>
      <Menu.Item>
        <Button
          loading={mutation.isLoading}
          onClick={() => mutation.mutate()}
        >
          Logout
        </Button>
      </Menu.Item>
    </OFMenu>
  )
}
Example #3
Source File: useApiMutation.ts    From kinopub.webos with MIT License 6 votes vote down vote up
function useApiMutation<TMethod extends Method, TData = Methods[TMethod], TError = string, TVariables = Parameters<ApiClient[TMethod]>>(
  method: TMethod,
) {
  const client = useMemo(() => new ApiClient(), []);

  // @ts-expect-error
  const mutation = useMutation<TData, TError, TVariables>([client, method], (params) => client[method](...params));

  return Object.assign({}, mutation, {
    [method]: mutation.mutate,
    [`${method}Async`]: mutation.mutateAsync,
  });
}
Example #4
Source File: useMutateResource.ts    From ke with MIT License 6 votes vote down vote up
export function useMutateResource<ResourceData, SourceData, TError = unknown, TContext = unknown>(
  userConfigOrKey: ResourceOptionsOrKey<MutateResourceOptions<ResourceData, SourceData, TError, TContext>>,
  requestOptions: MutationOptions<ResourceData, SourceData, TError, TContext> = {}
): MutationResult<ResourceData, SourceData, TError, TContext> {
  const userConfig = useConfigResolver(userConfigOrKey)
  const {
    mutate: { fn },
  } = useDefaultResourceConfig<ResourceData, SourceData>()
  const { key, ...options } = userConfig

  const { requestConfig, overrideGlobalOnError, mutationFn, ...mutationOptions } = deepmerge(options, requestOptions)
  const queryClient = useQueryClient()
  const globalOnError = queryClient.getDefaultOptions()?.mutations?.onError

  if (!!mutationOptions.onError && globalOnError && !overrideGlobalOnError) {
    mutationOptions.onError = injectInCallback(mutationOptions.onError, globalOnError)
  }

  const mutateFunction = mutationFn || ((data: SourceData) => fn(key, data, requestConfig))

  return useMutation<ResourceData, TError, SourceData, TContext>(
    mutateFunction,
    mutationOptions as UseMutationOptions<ResourceData, TError, SourceData, TContext>
  )
}
Example #5
Source File: usePersistentContext.ts    From apps with GNU Affero General Public License v3.0 6 votes vote down vote up
export default function usePersistentContext<T>(
  key: string,
  valueWhenCacheEmpty?: T,
  validValues?: T[],
  fallbackValue?: T,
): [T, (value: T) => Promise<void>, boolean] {
  const queryKey = [key, valueWhenCacheEmpty];
  const queryClient = useQueryClient();

  const { data, isFetched } = useQuery<unknown, unknown, T>(queryKey, () =>
    getAsyncCache<T>(key, valueWhenCacheEmpty, validValues),
  );

  const { mutateAsync: updateValue } = useMutation<void, unknown, unknown, T>(
    (value: T) => setCache(key, value),
    {
      onMutate: (mutatedData) => {
        const current = data;
        queryClient.setQueryData(queryKey, mutatedData);
        return current;
      },
      onError: (_, __, rollback) => {
        queryClient.setQueryData(queryKey, rollback);
      },
    },
  );

  return [data ?? fallbackValue, updateValue, isFetched];
}
Example #6
Source File: bookmark-hooks.tsx    From nyxo-website with MIT License 6 votes vote down vote up
useDeleteBookmark = () => {
  return useMutation(removeBookmark, {
    onSuccess: ({ slug }, { type }) =>
      queryCache.setQueryData([type, { slug: slug }], {
        bookmarked: false,
        id: "",
      }),
  })
}
Example #7
Source File: api.ts    From mui-toolpad with MIT License 6 votes vote down vote up
function createClient<D extends MethodsOf<any>>(endpoint: string): ApiClient<D> {
  const query = createFetcher(endpoint, 'query');
  const mutation = createFetcher(endpoint, 'mutation');

  return {
    query,
    mutation,
    useQuery: (key, params, options) => {
      return useQuery({
        ...options,
        enabled: !!params,
        queryKey: [key, params],
        queryFn: () => {
          if (!params) {
            throw new Error(`Invariant: "enabled" prop of useQuery should prevent this call'`);
          }
          return query[key](...params);
        },
      });
    },
    useMutation: (key, options) => useMutation((params) => mutation[key](...params), options),
    refetchQueries(key, params?) {
      return queryClient.refetchQueries(params ? [key, params] : [key]);
    },
  };
}
Example #8
Source File: donation.ts    From frontend with MIT License 6 votes vote down vote up
export function useDonationSession() {
  const { t } = useTranslation()
  const mutation = useMutation<
    AxiosResponse<CheckoutSessionResponse>,
    AxiosError<ApiErrors>,
    CheckoutSessionInput
  >({
    mutationFn: createCheckoutSession,
    onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
    onSuccess: () => AlertStore.show(t('common:alerts.message-sent'), 'success'),
  })
  return mutation
}
Example #9
Source File: useCreateAllowedDomain.ts    From wildduck-ui with MIT License 6 votes vote down vote up
useCreateAllowedDomain = () => {
	const queryClient = useQueryClient();

	return useMutation(
		(domain: { tag: string; domain: string }) =>
			api.domainAccessApi.createAllowedDomain(domain.tag, { domain: domain.domain }),
		{
			onSuccess: () => {
				AppEvents.publish(Events.Success, 'Success');
				queryClient.invalidateQueries('query-allowList');
				queryClient.invalidateQueries('query-blockList');
			},
			onError: () => {
				AppEvents.publish(Events.Error, 'Error');
			},
		},
	);
}
Example #10
Source File: password.tsx    From RareCamp with Apache License 2.0 5 votes vote down vote up
export default function App() {
  const router = useRouter()
  const mutation = useMutation(
    ({ username }: { username: string }) =>
      Auth.forgotPassword(username),
    {
      onSuccess: async (_, variables) => {
        notification.success({
          message: 'Instructions sent to your emails',
          description: 'Account confirmed successfully!',
          placement: 'topRight',
          duration: 1.5,
        })
        await router.push(
          `/auth/resetpassword?username=${variables.username}`,
        )
      },
      onError: async (err: Error) => {
        notification.error({
          message: 'User confirmation failed',
          description: err.message,
          placement: 'topRight',
          duration: 3,
        })
      },
    },
  )

  return (
    <AuthLayout>
      <div>
        <Title level={3}>Forgot your password?</Title>
        <p>
          We'll send you an email with instructions.
          <p>
            or
            <Button
              type="link"
              onClick={() => router.push('/auth/login')}
            >
              Login
            </Button>
          </p>
        </p>
      </div>
      <Form
        layout="vertical"
        name="forgot_password_form"
        onFinish={mutation.mutate}
      >
        <Form.Item
          label={<span style={{ fontWeight: 500 }}>Email</span>}
          name="username"
          required={false}
          rules={[
            {
              required: true,
              message: 'Please input your email',
              type: 'email',
            },
          ]}
        >
          <Input placeholder="[email protected]" />
        </Form.Item>
        <Form.Item>
          <Button
            loading={mutation.isLoading}
            type="primary"
            htmlType="submit"
            block
            className="login-form-button"
          >
            Reset Password
          </Button>
        </Form.Item>
      </Form>
    </AuthLayout>
  )
}
Example #11
Source File: index.tsx    From strapi-plugin-comments with MIT License 5 votes vote down vote up
DiscussionThreadItemApprovalFlowActions = ({
  id,
  allowedActions: { canModerate },
  queryToInvalidate,
}) => {
  const toggleNotification = useNotification();
  const queryClient = useQueryClient();
  const { lockApp, unlockApp } = useOverlayBlocker();

  const onSuccess = (message) => async () => {
    if (queryToInvalidate) {
      await queryClient.invalidateQueries(queryToInvalidate);
    }
    toggleNotification({
      type: "success",
      message: `${pluginId}.${message}`,
    });
    unlockApp();
  };

  const onError = (err) => {
    handleAPIError(err, toggleNotification);
  };

  const approveItemMutation = useMutation(approveItem, {
    onSuccess: onSuccess(
      "page.details.actions.comment.approve.confirmation.success"
    ),
    onError,
    refetchActive: false,
  });
  const rejectItemMutation = useMutation(rejectItem, {
    onSuccess: onSuccess(
      "page.details.actions.comment.reject.confirmation.success"
    ),
    onError,
    refetchActive: false,
  });

  const handleApproveClick = () => {
    if (canModerate) {
      lockApp();
      approveItemMutation.mutate(id);
    }
  };

  const handleRejectClick = () => {
    if (canModerate) {
      lockApp();
      rejectItemMutation.mutate(id);
    }
  };

  if (canModerate) {
    return (
      <>
        <IconButton
          icon={<Check />}
          label={getMessage("page.details.actions.comment.approve", "Approve")}
          onClick={handleApproveClick}
        />
        <IconButton
          icon={<Cross />}
          label={getMessage("page.details.actions.comment.reject", "Reject")}
          onClick={handleRejectClick}
        />
      </>
    );
  }
  return null;
}
Example #12
Source File: Manual.tsx    From NextPay with MIT License 5 votes vote down vote up
Manual = ({ max, min }: { max: number; min: number }) => {
  const [amount, setAmount] = useState<number>(Math.max(0, min));
  const [isCopied, copy] = useCopyClipboard({ successDuration: 3000 });

  const mutation = useMutation(getInvoice);

  if (mutation.error) {
    return <div>Error getting invoice. Please try again</div>;
  }

  if (mutation.data?.pr) {
    return (
      <>
        <IndexStyles.copyButton onClick={() => copy(mutation.data.pr)}>
          {isCopied ? (
            <IndexStyles.copySuccess>Copied</IndexStyles.copySuccess>
          ) : (
            <IndexStyles.copy>Click QR to copy</IndexStyles.copy>
          )}
          <QRcode value={mutation.data.pr} size={240} />
        </IndexStyles.copyButton>
        <IndexStyles.info>Scan with any lightning wallet</IndexStyles.info>
      </>
    );
  }

  return (
    <>
      <S.separation>
        <S.singleLine>
          <S.darkTitle>Max</S.darkTitle>
          <div>{`${max} sats`}</div>
        </S.singleLine>
        <S.singleLine>
          <S.darkTitle>Min</S.darkTitle>
          <div>{`${min} sats`}</div>
        </S.singleLine>
      </S.separation>
      <S.singleLine>
        <S.darkTitle>Amount</S.darkTitle>
        <div>{`${amount} sats`}</div>
      </S.singleLine>
      <S.input
        onBlur={() => {
          if (amount < min) {
            setAmount(min);
          } else if (amount > max) {
            setAmount(max);
          }
        }}
        placeholder={'Sats'}
        value={amount}
        type={'number'}
        onChange={e => setAmount(Number(e.target.value))}
      />
      <S.button
        disabled={mutation.isLoading}
        onClick={() => mutation.mutate({ amount })}
      >
        Get Invoice
      </S.button>
    </>
  );
}
Example #13
Source File: EmailLogin.tsx    From vsinder with Apache License 2.0 5 votes vote down vote up
EmailLogin: React.FC<AuthStackNav<"EmailLogin">> = ({
  navigation,
}) => {
  const [mutate, { isLoading }] = useMutation(defaultMutationFn);
  return (
    <ScreenWrapper>
      <KeyboardAwareScrollView keyboardShouldPersistTaps="handled">
        <Formik
          validationSchema={schema}
          validateOnBlur={false}
          validateOnChange={false}
          initialValues={{ email: "", password: "" }}
          onSubmit={async (values) => {
            const tokens = await mutate(["/email/login", values, "POST"]);
            navigation.navigate("tokens", tokens);
          }}
        >
          {({ handleSubmit }) => (
            <>
              <FormSpacer>
                <TextField
                  label="Email"
                  textContentType="emailAddress"
                  autoCapitalize="none"
                  name="email"
                />
              </FormSpacer>
              <FormSpacer>
                <TextField
                  autoCapitalize="none"
                  label="Password"
                  secureTextEntry
                  textContentType="password"
                  name="password"
                />
              </FormSpacer>
              <LoadingButton
                isLoading={isLoading}
                onPress={() => handleSubmit()}
              >
                Login
              </LoadingButton>
            </>
          )}
        </Formik>
      </KeyboardAwareScrollView>
    </ScreenWrapper>
  );
}
Example #14
Source File: EmailLogin.tsx    From vsinder-app with Apache License 2.0 5 votes vote down vote up
EmailLogin: React.FC<AuthStackNav<"EmailLogin">> = ({
  navigation,
}) => {
  const [mutate, { isLoading }] = useMutation(defaultMutationFn);
  return (
    <ScreenWrapper>
      <KeyboardAwareScrollView keyboardShouldPersistTaps="handled">
        <Formik
          validationSchema={schema}
          validateOnBlur={false}
          validateOnChange={false}
          initialValues={{ email: "", password: "" }}
          onSubmit={async (values) => {
            const tokens = await mutate(["/email/login", values, "POST"]);
            navigation.navigate("tokens", tokens);
          }}
        >
          {({ handleSubmit }) => (
            <>
              <FormSpacer>
                <TextField
                  label="email"
                  textContentType="emailAddress"
                  autoCapitalize="none"
                  name="email"
                />
              </FormSpacer>
              <FormSpacer>
                <TextField
                  autoCapitalize="none"
                  label="password"
                  secureTextEntry
                  textContentType="password"
                  name="password"
                />
              </FormSpacer>
              <LoadingButton
                isLoading={isLoading}
                onPress={() => handleSubmit()}
              >
                login
              </LoadingButton>
            </>
          )}
        </Formik>
      </KeyboardAwareScrollView>
    </ScreenWrapper>
  );
}
Example #15
Source File: useDropAllExpired.ts    From homebase-app with MIT License 5 votes vote down vote up
useDropAllExpired = () => {
  const queryClient = useQueryClient();
  const openNotification = useNotification();
  const { network, tezos, account, connect } = useTezos();

  return useMutation<
    any | Error,
    Error,
    { dao: BaseDAO; expiredProposalIds: string[] }
    >(
    async (params) => {
      const { key: dropNotification, closeSnackbar: closeFlushNotification } =
        openNotification({
          message: "Please sign the transaction to drop all expired proposals",
          persist: true,
          variant: "info",
        });
      try {
        let tezosToolkit = tezos;

        if (!account) {
          tezosToolkit = await connect();
        }

        const data = await params.dao.dropAllExpired(
          params.expiredProposalIds,
          tezosToolkit
        );
        closeFlushNotification(dropNotification);

        await data.confirmation(1);
        openNotification({
          message: "Execute transaction confirmed!",
          autoHideDuration: 5000,
          variant: "success",
          detailsLink: `https://${networkNameMap[network]}.tzkt.io/` + data.opHash,
        });

        return data;
      } catch (e) {
        closeFlushNotification(dropNotification);
        openNotification({
          message: "An error has happened with execute transaction!",
          variant: "error",
          autoHideDuration: 5000,
        });
        return new Error((e as Error).message);
      }
    },
    {
      onSuccess: () => {
        queryClient.resetQueries();
      },
    }
  );
}
Example #16
Source File: useSettingsMigration.ts    From apps with GNU Affero General Public License v3.0 5 votes vote down vote up
export default function useSettingsMigration(
  user: LoggedUser,
  tokenRefreshed: boolean,
): UseSettingsMigrationRet {
  const [migrationCompleted, setMigrationCompleted] = useState(false);
  const [settings, setSettings] = useState<SettingsV2>();
  const [postpone, setPostpone] = usePersistentState(
    'postponeSettingsMigration',
    null,
    POSTPONE_DONT,
  );
  const [migrateAfterSignIn, setMigrateAfterSignIn] = usePersistentState(
    'migrateAfterSignIn',
    false,
    false,
  );

  const { mutateAsync: migrate, isLoading: isMigrating } = useMutation(
    () =>
      request(`${apiUrl}/graphql`, generateQuery(settings), {
        bookmarks: {
          postIds:
            settings?.state?.feed?.bookmarks?.map?.((post) => post.id) ?? [],
        },
        filters: {
          includeTags: objectToEnabledKeys(settings?.state?.feed?.enabledTags),
          excludeSources: objectToEnabledKeys(
            settings?.state?.feed?.disabledPublications,
          ),
        },
      }),
    {
      onSuccess: async () => {
        await setSettings(null);
        await browser.storage.local.set({ [SETTINGS_V2_KEY]: {} });
        setMigrationCompleted(true);
      },
    },
  );

  useEffect(() => {
    browser.storage.local.get(SETTINGS_V2_KEY).then(setSettings);
  }, []);

  const hasSettings = checkIfHasSettings(settings);

  useEffect(() => {
    if (user && tokenRefreshed && migrateAfterSignIn && hasSettings) {
      migrate();
    }
  }, [user, tokenRefreshed, migrateAfterSignIn, hasSettings]);

  return {
    hasSettings,
    showMigrationModal:
      hasSettings &&
      !migrateAfterSignIn &&
      postpone !== null &&
      ((!user && postpone !== POSTPONE_ANONYMOUS) ||
        (user && postpone !== POSTPONE_LOGGED_IN)),
    migrateAfterSignIn: () => setMigrateAfterSignIn(true),
    postponeMigration: () => {
      if (user) {
        return setPostpone(POSTPONE_LOGGED_IN);
      }
      return setPostpone(POSTPONE_ANONYMOUS);
    },
    ackMigrationCompleted: () => setMigrationCompleted(false),
    forceMigrationModal: () => setPostpone(POSTPONE_DONT),
    isMigrating,
    migrationCompleted,
    migrate,
    postponed: hasSettings && postpone !== null && postpone !== POSTPONE_DONT,
  };
}
Example #17
Source File: useCoaching.ts    From nyxo-app with GNU General Public License v3.0 5 votes vote down vote up
useCreateCoaching = () => {
  return useMutation(createCoaching)
}
Example #18
Source File: useCoaching.ts    From nyxo-website with MIT License 5 votes vote down vote up
useCreateCoaching = () => {
  return useMutation(createCoaching)
}
Example #19
Source File: index.tsx    From interbtc-ui with Apache License 2.0 5 votes vote down vote up
ClaimRewardsButton = ({
  className,
  claimableRewardAmount,
  ...rest
}: CustomProps & InterlayDenimOrKintsugiMidnightContainedButtonProps): JSX.Element => {
  const { address } = useSelector((state: StoreType) => state.general);

  const queryClient = useQueryClient();

  const claimRewardsMutation = useMutation<void, Error, void>(
    () => {
      return window.bridge.escrow.withdrawRewards();
    },
    {
      onSuccess: () => {
        queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewardEstimate', address]);
        queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewards', address]);
      }
    }
  );

  const handleClaimRewards = () => {
    claimRewardsMutation.mutate();
  };

  return (
    <>
      <InterlayDenimOrKintsugiSupernovaContainedButton
        className={clsx('w-full', 'px-6', 'py-3', 'text-base', 'rounded-md', className)}
        onClick={handleClaimRewards}
        pending={claimRewardsMutation.isLoading}
        {...rest}
      >
        Claim {claimableRewardAmount} {GOVERNANCE_TOKEN_SYMBOL} Rewards
      </InterlayDenimOrKintsugiSupernovaContainedButton>
      {claimRewardsMutation.isError && (
        <ErrorModal
          open={claimRewardsMutation.isError}
          onClose={() => {
            claimRewardsMutation.reset();
          }}
          title='Error'
          description={claimRewardsMutation.error?.message || ''}
        />
      )}
    </>
  );
}
Example #20
Source File: UpdateBirthdayModal.tsx    From frontend with MIT License 5 votes vote down vote up
function UpdateBirthdayModal({
  isOpen,
  handleClose,
  person,
}: {
  isOpen: boolean
  handleClose: (data?: Person) => void
  person: UpdatePerson
}) {
  const { t } = useTranslation()

  const dateBefore18Years = new Date(new Date().setFullYear(new Date().getFullYear() - 18))

  const initialValues: BirthdayFormData = {
    birthday: format(new Date(person.birthday ?? dateBefore18Years), formatString),
  }

  const mutation = useMutation<AxiosResponse<Person>, AxiosError<ApiErrors>, UpdatePerson>({
    mutationFn: updateCurrentPerson(),
    onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
    onSuccess: () => AlertStore.show(t('common:alerts.success'), 'success'),
  })

  const onSubmit = async (
    values: BirthdayFormData,
    { setFieldError }: FormikHelpers<BirthdayFormData>,
  ) => {
    try {
      const birthDate = new Date(values?.birthday)
      await mutation.mutateAsync({ ...person, birthday: birthDate }).then((data) => {
        handleClose(data.data)
      })
    } catch (error) {
      console.error(error)
      if (isAxiosError(error)) {
        const { response } = error as AxiosError<ApiErrors>
        response?.data.message.map(({ property, constraints }) => {
          setFieldError(property, t(matchValidator(constraints)))
        })
      }
    }
  }

  return (
    <StyledModal
      open={isOpen}
      onClose={() => handleClose()}
      aria-labelledby="modal-modal-title"
      aria-describedby="modal-modal-description">
      <Box className={classes.modal}>
        <IconButton className={classes.close} onClick={() => handleClose()}>
          <CloseIcon />
        </IconButton>
        <h2>Обнови рожден ден</h2>
        <GenericForm<BirthdayFormData>
          onSubmit={onSubmit}
          initialValues={initialValues}
          validationSchema={validationSchema}>
          <Grid container spacing={3}>
            <Grid item xs={12} sm={8}>
              <FormTextField
                type="date"
                name="birthday"
                label="Кога е твоят рожден ден?"
                InputLabelProps={{
                  shrink: true,
                }}
              />
            </Grid>
            <Grid item xs={6}>
              <SubmitButton fullWidth />
            </Grid>
          </Grid>
        </GenericForm>
      </Box>
    </StyledModal>
  )
}
Example #21
Source File: index.tsx    From RareCamp with Apache License 2.0 4 votes vote down vote up
export default function ContactServiceProviderModal({
  visible,
  hide,
  serviceProvider,
  task,
}) {
  const contactMutation = useMutation(
    (message) =>
      axios.post(
        `/projects/${task.projectId}/tasks/${task.taskId}/contactUs`,
        { message, task, spName: serviceProvider.name },
      ),
    {
      onSuccess: async () => {
        notification.success({
          duration: 2,
          message: `Message has been sent successfully`,
        })
      },
      onError: (err: Error) =>
        notification.error({
          duration: 2,
          message: `Message was not sent`,
          description: err.message,
        }),
    },
  )

  const onFinish = ({ message }) => contactMutation.mutate(message)

  return (
    <Modal
      centered
      visible={visible}
      footer={null}
      onOk={hide}
      onCancel={hide}
      width={850}
    >
      <Row gutter={[60, 60]}>
        <Col span={10}>
          <Space
            size={8}
            style={{ textAlign: 'center' }}
            direction="vertical"
          >
            <img
              width="337px"
              src="/OPenGT-contact-service-provider.png"
              alt="OPenGT contact service provider "
            />
            <h3 style={{ color: '#3e3465' }}>
              We are here to help you move along
            </h3>
            <p>
              We will meet with you to understand where you are and
              help you move along to the next step
            </p>
          </Space>
        </Col>
        <Col span={14}>
          <Space
            direction="vertical"
            style={{ width: '100%', height: '100%' }}
          >
            <h1>
              Send Message to
              {` ${serviceProvider.name}`}
            </h1>
            <b>Briefly describe what you want to discuss</b>
            <Form layout="vertical" onFinish={onFinish}>
              <Form.Item
                name="message"
                required={false}
                rules={[
                  {
                    required: true,
                    message: 'Please input your message',
                  },
                ]}
              >
                <Input.TextArea
                  style={{ height: 238 }}
                  rows={8}
                  required={false}
                />
              </Form.Item>
              <Button
                loading={contactMutation.isLoading}
                block
                htmlType="submit"
                type="primary"
              >
                Send
              </Button>
            </Form>
          </Space>
        </Col>
      </Row>
    </Modal>
  )
}