hooks#useApi TypeScript Examples

The following examples show how to use hooks#useApi. 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: Header.tsx    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
function Header() {
  const { isApiReady } = useApi();

  return (
    <header className={styles.header}>
      <Logo />
      <OnLogin>
        <Menu />
      </OnLogin>
      {isApiReady && <Account />}
    </header>
  );
}
Example #2
Source File: index.tsx    From livepeer-com with MIT License 6 votes vote down vote up
DashboardPage = () => {
  useLoggedIn();
  const { user } = useApi();

  if (!user) {
    return <Layout />;
  }

  return (
    <Layout id="home" breadcrumbs={[{ title: "Home" }]} {...Content.metaData}>
      <Dashboard />
    </Layout>
  );
}
Example #3
Source File: Provider.tsx    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
useBlocks = () => {
  const { api } = useApi();
  const [blocks, setBlocks] = useState<Blocks>([]);

  const getTime = (timestamp: number) => new Date(timestamp).toLocaleTimeString();

  const getBlock = ({ hash, number }: Header, time: string) => ({
    hash: hash.toHex(),
    number: number.toNumber(),
    time,
  });

  const updateBlocks = (block: Block) => {
    setBlocks((prevBlocks) => {
      const blocksTail = prevBlocks.length > 9 ? prevBlocks.slice(0, -1) : prevBlocks;
      return [block, ...blocksTail];
    });
  };

  const handleSubscription = (header: Header) =>
    api.blocks
      .getBlockTimestamp(header.hash)
      .then((timestamp) => getTime(timestamp.toNumber()))
      .then((time) => getBlock(header, time))
      .then(updateBlocks);

  const subscribeToBlocks = () => api.gearEvents.subscribeToNewBlocks(handleSubscription);
  useSubscription(subscribeToBlocks);

  return blocks;
}
Example #4
Source File: api-keys.tsx    From livepeer-com with MIT License 6 votes vote down vote up
ApiKeys = () => {
  useLoggedIn();
  const { user } = useApi();

  if (!user) {
    return <Layout />;
  }
  return (
    <Layout
      id="developers"
      breadcrumbs={[
        { title: "Developers", href: "/dashboard/developers/api-keys" },
        { title: "API Keys" },
      ]}
      {...Content.metaData}>
      <Box css={{ p: "$6" }}>
        <Box css={{ mb: "$8" }}>
          <TokenTable userId={user.id} />
        </Box>
      </Box>
    </Layout>
  );
}
Example #5
Source File: Message.tsx    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
Message = ({ message }: Props) => {
  const { id } = message;

  const { api } = useApi();
  const { account } = useAccount();
  const alert = useAlert();

  const showErrorAlert = (error: string) => {
    alert.error(error);
  };

  const showSuccessAlert = (data: ISubmittableResult) => {
    if (!data.status.isBroadcast) {
      alert.success(`Status: ${data.status}`);
    }
  };

  const handleClaimButtonClick = () => {
    if (account) {
      const { address, meta } = account;

      api.claimValueFromMailbox.submit(id);
      web3FromSource(meta.source)
        .then(({ signer }) => api.claimValueFromMailbox.signAndSend(address, { signer }, showSuccessAlert))
        .catch((error: Error) => showErrorAlert(error.message));
    }
  };

  return (
    <div className={styles.message}>
      <pre className={styles.pre}>{getPreformattedText(message)}</pre>
      <div>
        <ReplyLink to={id} />
        <Button text="Claim value" icon={claimIcon} color="secondary" size="small" onClick={handleClaimButtonClick} />
      </div>
    </div>
  );
}
Example #6
Source File: index.tsx    From livepeer-com with MIT License 6 votes vote down vote up
Assets = () => {
  useLoggedIn();
  const { user } = useApi();

  if (!user) {
    return <Layout />;
  }
  return (
    <Layout
      id="assets"
      breadcrumbs={[{ title: "Assets" }]}
      {...Content.metaData}>
      <Box css={{ p: "$6" }}>
        <Box css={{ mb: "$8" }}>
          <AssetsTable userId={user.id} tableId="dashboardAssetsTable" />
        </Box>
      </Box>
    </Layout>
  );
}
Example #7
Source File: Account.tsx    From gear-js with GNU General Public License v3.0 6 votes vote down vote up
function AccountProvider({ children }: Props) {
  const { api } = useApi();

  const [account, setAccount] = useState<Account>();

  const getBalance = (balance: Balance) => {
    const [value, unit] = balance.toHuman().split(' ');
    return { value, unit };
  };

  const getAccount = (_account: InjectedAccountWithMeta, balance: Balance) => ({
    ..._account,
    balance: getBalance(balance),
    decodedAddress: GearKeyring.decodeAddress(_account.address),
  });

  const switchAccount = (_account: InjectedAccountWithMeta) => {
    api?.balance
      .findOut(_account.address)
      .then((balance) => getAccount(_account, balance))
      .then(setAccount);
  };

  const updateBalance = (balance: Balance) => {
    setAccount((prevAccount) => (prevAccount ? { ...prevAccount, balance: getBalance(balance) } : prevAccount));
  };

  const logout = () => {
    setAccount(undefined);
  };

  const { Provider } = AccountContext;
  const value = { account, switchAccount, updateBalance, logout };

  return <Provider value={value}>{children}</Provider>;
}
Example #8
Source File: plans.tsx    From livepeer-com with MIT License 5 votes vote down vote up
PlansPage = () => {
  useLoggedIn();
  const { user } = useApi();

  if (!user) {
    return <Layout />;
  }
  return (
    <Layout
      id="billing/plans"
      breadcrumbs={[
        { title: "Billing", href: "/dashboard/billing" },
        { title: "Plans" },
      ]}
      {...Content.metaData}>
      <Box css={{ p: "$6" }}>
        <Box css={{ mb: "$6" }}>
          <Flex
            justify="between"
            align="end"
            css={{
              borderBottom: "1px solid",
              borderColor: "$neutral6",
              pb: "$4",
              mb: "$5",
              width: "100%",
            }}>
            <Heading size="2">
              <Flex>
                <Box
                  css={{
                    mr: "$3",
                    fontWeight: 600,
                    letterSpacing: "0",
                  }}>
                  Plans
                </Box>
              </Flex>
            </Heading>
          </Flex>
        </Box>
        <Plans
          dashboard={true}
          stripeProductId={
            user?.stripeProductId ? user.stripeProductId : "prod_0"
          }
        />
      </Box>
    </Layout>
  );
}
Example #9
Source File: Account.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
function AccountProvider({ children }: Props) {
  const { api } = useApi();

  const [accounts, setAccounts] = useState<InjectedAccountWithMeta[]>();
  const [account, setAccount] = useState<Account>();

  const getAccounts = (extensions: InjectedExtension[]) => (extensions.length > 0 ? web3Accounts() : undefined);

  useEffect(() => {
    setTimeout(() => {
      web3Enable('Gear App').then(getAccounts).then(setAccounts);
    }, 300);
  }, []);

  const getBalance = (balance: Balance) => {
    const [value, unit] = balance.toHuman().split(' ');
    return { value, unit };
  };

  const getAccount = (_account: InjectedAccountWithMeta, balance: Balance) => ({
    ..._account,
    balance: getBalance(balance),
    decodedAddress: GearKeyring.decodeAddress(_account.address),
  });

  const switchAccount = (_account: InjectedAccountWithMeta) => {
    api?.balance
      .findOut(_account.address)
      .then((balance) => getAccount(_account, balance))
      .then(setAccount);
  };

  const logout = () => {
    setAccount(undefined);
  };

  useEffect(() => {
    const loggedInAccount = accounts?.find(isLoggedIn);
    if (loggedInAccount) switchAccount(loggedInAccount);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [api, accounts]);

  const updateBalance = (balance: Balance) => {
    setAccount((prevAccount) => ({ ...prevAccount!, balance: getBalance(balance) }));
  };

  useEffect(() => {
    let unsub: UnsubscribePromise | undefined;

    if (account) {
      unsub = api?.gearEvents.subscribeToBalanceChange(account.address, updateBalance);
    }

    return () => {
      if (unsub) unsub.then((callback) => callback());
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [api, account]);

  const { Provider } = AccountContext;
  const value = { accounts, account, switchAccount, logout };

  return <Provider value={value}>{children}</Provider>;
}
Example #10
Source File: reset-password.tsx    From livepeer-com with MIT License 5 votes vote down vote up
ResetPasswordPage = () => {
  useLoggedIn(false);
  const [errors, setErrors] = useState([]);
  const [loading, setLoading] = useState(false);
  const { resetPassword } = useApi();
  const router = useRouter();
  const { email, resetToken } = router.query;

  const onSubmit = async ({ password }) => {
    setLoading(true);
    setErrors([]);
    const res = await resetPassword(email, resetToken, password);
    // Don't need to worry about the success case, we'll redirect
    if (res.errors) {
      setLoading(false);
      setErrors(res.errors);
    }
  };

  return (
    <Layout {...Content.metaData}>
      <Guides backgroundColor="$neutral2" />
      <Box css={{ position: "relative" }}>
        <Container
          size="3"
          css={{
            px: "$6",
            py: "$7",
            width: "100%",
            "@bp3": {
              py: "$8",
              px: "$4",
            },
          }}>
          <Flex
            css={{
              alignItems: "center",
              justifyContent: "center",
              flexGrow: 1,
              flexDirection: "column",
              py: "$5",
            }}>
            <Heading size="3" as="h1" css={{ mb: "$5" }}>
              Reset your password
            </Heading>
            <Login
              id="reset-password"
              showEmail={false}
              showPassword={true}
              buttonText="Change password"
              onSubmit={onSubmit}
              errors={errors}
              loading={loading}
            />
            <Box>
              Nevermind!&nbsp;
              <Link href="/login" passHref>
                <A>Take me back to log in</A>
              </Link>
            </Box>
          </Flex>
        </Container>
      </Box>
    </Layout>
  );
}
Example #11
Source File: Block.tsx    From gear-js with GNU General Public License v3.0 5 votes vote down vote up
Block = ({ blockId }: Props) => {
  const { api } = useApi();
  const [block, setBlock] = useState<DotBlock>();
  const [eventRecords, setEventRecords] = useState<EventRecords>();
  const [error, setError] = useState('');

  const resetState = () => {
    setBlock(undefined);
    setEventRecords(undefined);
    setError('');
  };

  useEffect(() => {
    if (api) {
      resetState();

      const isBlockHash = isHex(blockId);
      const id = isBlockHash ? (blockId as `0x${string}`) : Number(blockId);

      api.blocks
        .get(id)
        .then(({ block: newBlock }) => {
          api.blocks.getEvents(newBlock.hash).then(setEventRecords);
          setBlock(newBlock);
        })
        .catch(({ message }: Error) => {
          setError(message);
        });
    }
  }, [api, blockId]);

  if (error) {
    return <p className={styles.message}>Something went wrong. {error}</p>;
  }

  return (
    <div className={styles.block}>
      {block && eventRecords ? (
        <>
          <Summary block={block} />
          <MainTable extrinsics={block.extrinsics} eventRecords={eventRecords} />
          <System eventRecords={eventRecords} />
        </>
      ) : (
        <Spinner />
      )}
    </div>
  );
}
Example #12
Source File: index.tsx    From livepeer-com with MIT License 4 votes vote down vote up
Billing = () => {
  useLoggedIn();
  const { user, getUsage, getSubscription, getInvoices, getPaymentMethod } =
    useApi();
  const [usage, setUsage] = useState(null);
  const [subscription, setSubscription] = useState(null);
  const [invoices, setInvoices] = useState(null);

  const fetcher = useCallback(async () => {
    if (user?.stripeCustomerPaymentMethodId) {
      const [_res, paymentMethod] = await getPaymentMethod(
        user.stripeCustomerPaymentMethodId
      );
      return paymentMethod;
    }
  }, [user?.stripeCustomerPaymentMethodId]);

  const queryKey = useMemo(() => {
    return [user?.stripeCustomerPaymentMethodId];
  }, [user?.stripeCustomerPaymentMethodId]);

  const { data, isLoading } = useQuery([queryKey], () => fetcher());

  const queryClient = useQueryClient();

  const invalidateQuery = useCallback(() => {
    return queryClient.invalidateQueries(queryKey);
  }, [queryClient, queryKey]);

  useEffect(() => {
    const doGetInvoices = async (stripeCustomerId) => {
      const [res, invoices] = await getInvoices(stripeCustomerId);
      if (res.status == 200) {
        setInvoices(invoices);
      }
    };

    const doGetUsage = async (fromTime, toTime, userId) => {
      const [res, usage] = await getUsage(fromTime, toTime, userId);
      if (res.status == 200) {
        setUsage(usage);
      }
    };
    const getSubscriptionAndUsage = async (subscriptionId) => {
      const [res, subscription] = await getSubscription(subscriptionId);
      if (res.status == 200) {
        setSubscription(subscription);
      }
      doGetUsage(
        subscription?.current_period_start,
        subscription?.current_period_end,
        user.id
      );
    };

    if (user) {
      doGetInvoices(user.stripeCustomerId);
      getSubscriptionAndUsage(user.stripeCustomerSubscriptionId);
    }
  }, [user]);

  if (!user) {
    return <Layout />;
  }
  return (
    <Layout
      id="billing"
      breadcrumbs={[{ title: "Billing" }]}
      {...Content.metaData}>
      <Box css={{ p: "$6" }}>
        <Box css={{ mb: "$7" }}>
          <Flex
            justify="between"
            align="end"
            css={{
              borderBottom: "1px solid",
              borderColor: "$neutral6",
              pb: "$4",
              mb: "$5",
              width: "100%",
            }}>
            <Heading size="2">
              <Flex>
                <Box
                  css={{
                    mr: "$3",
                    fontWeight: 600,
                    letterSpacing: "0",
                  }}>
                  Billing
                </Box>
              </Flex>
            </Heading>
            <Flex css={{ fontSize: "$3", color: "$primary9" }}>
              Current billing period (
              {subscription && (
                <Flex>
                  {new Date(
                    subscription.current_period_start * 1000
                  ).toLocaleDateString("en-US", {
                    month: "short",
                    day: "numeric",
                  })}{" "}
                  to{" "}
                  {new Date(
                    subscription.current_period_end * 1000
                  ).toLocaleDateString("en-US", {
                    month: "short",
                    day: "numeric",
                  })}{" "}
                </Flex>
              )}
              )
            </Flex>
          </Flex>
        </Box>
        <Box css={{ mb: "$8" }}>
          <Flex
            justify="between"
            align="end"
            css={{
              borderBottom: "1px solid",
              borderColor: "$neutral6",
              pb: "$3",
              mb: "$4",
              width: "100%",
            }}>
            <Heading size="1">
              <Flex align="center">
                <Box
                  css={{
                    mr: "$3",
                    fontWeight: 600,
                    letterSpacing: "0",
                  }}>
                  Current Plan
                </Box>
              </Flex>
            </Heading>
          </Flex>
          <Flex
            justify="between"
            align="center"
            css={{ fontSize: "$3", color: "$hiContrast" }}>
            <Text variant="gray">
              You are currently on the
              <Badge
                size="1"
                variant="primary"
                css={{ mx: "$1", fontWeight: 700, letterSpacing: 0 }}>
                {user?.stripeProductId
                  ? products[user.stripeProductId]?.name
                  : products["prod_0"]?.name}
              </Badge>
              plan.
            </Text>
            <Link href="/dashboard/billing/plans" passHref>
              <A
                variant="primary"
                css={{ display: "flex", alignItems: "center" }}>
                View Plans & Upgrade <ArrowRightIcon />
              </A>
            </Link>
          </Flex>
        </Box>
        <Box css={{ mb: "$9" }}>
          <Flex
            justify="between"
            align="end"
            css={{
              borderBottom: "1px solid",
              borderColor: "$neutral6",
              pb: "$3",
              mb: "$5",
              width: "100%",
            }}>
            <Heading size="1">
              <Flex align="center" justify="between">
                <Box
                  css={{
                    mr: "$3",
                    fontWeight: 600,
                    letterSpacing: "0",
                  }}>
                  Payment Method
                </Box>
              </Flex>
            </Heading>
            <PaymentMethodDialog invalidateQuery={invalidateQuery} />
          </Flex>
          <Flex
            css={{
              ".rccs__card__background": {
                background:
                  "linear-gradient(to right, $colors$violet11, $colors$indigo11) !important",
              },
              ".rccs__card--front": {
                color: "white !important",
              },
            }}>
            {user?.stripeCustomerPaymentMethodId ? (
              <>
                <PaymentMethod data={data} />
              </>
            ) : (
              "No payment method on file."
            )}
          </Flex>
        </Box>
        <Box css={{ mb: "$9" }}>
          <Flex
            justify="between"
            align="end"
            css={{
              mb: "$4",
              width: "100%",
            }}>
            <Heading size="1">
              <Flex align="center">
                <Box
                  css={{
                    mr: "$3",
                    fontWeight: 600,
                    letterSpacing: "0",
                  }}>
                  Upcoming Invoice
                </Box>
              </Flex>
            </Heading>
          </Flex>
          {!products[user.stripeProductId].order ? (
            <Text variant="gray">
              The Personal plan is free of charge up to 1000 minutes per month
              and limited to 10 concurrent viewers per account.
            </Text>
          ) : (
            subscription && (
              <UpcomingInvoiceTable
                subscription={subscription}
                usage={usage}
                prices={products[user.stripeProductId].usage}
              />
            )
          )}
        </Box>

        {invoices?.data.filter((invoice) => invoice.lines.total_count > 1)
          .length > 0 && (
          <Box css={{ mb: "$6" }}>
            <Flex
              justify="between"
              align="end"
              css={{
                mb: "$4",
                width: "100%",
              }}>
              <Heading size="1">
                <Flex align="center">
                  <Box
                    css={{
                      mr: "$3",
                      fontWeight: 600,
                      letterSpacing: "0",
                    }}>
                    Past Invoices
                  </Box>
                </Flex>
              </Heading>
            </Flex>
            <PastInvoicesTable invoices={invoices} />
          </Box>
        )}
      </Box>
    </Layout>
  );
}
Example #13
Source File: ProgramSwitch.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
ProgramSwitch: VFC<Props> = ({ pageType }) => {
  const { api } = useApi();
  const alert = useAlert();
  const { account: currentAccount } = useAccount();

  const [captchaToken, setCaptchaToken] = useState('');
  const captchaRef = useRef<HCaptcha>(null);

  const handleTransferBalance = async () => {
    try {
      if (!currentAccount) {
        throw new Error(`WALLET NOT CONNECTED`);
      }

      const apiRequest = new ServerRPCRequestService();
      const response = await apiRequest.callRPC(RPC_METHODS.GET_TEST_BALANCE, {
        address: `${currentAccount.address}`,
        token: captchaToken,
      });

      if (response.error) {
        alert.error(`${response.error.message}`);
      }
    } catch (error) {
      alert.error(`${error}`);
    }
  };

  useEffect(() => {
    if (captchaToken) {
      handleTransferBalance();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [captchaToken]);

  const handleTestBalanceClick = () => {
    if (captchaToken) {
      handleTransferBalance();
    } else {
      captchaRef.current?.execute();
    }
  };

  const handleTransferBalanceFromAlice = () => {
    try {
      if (!currentAccount) {
        throw new Error(`WALLET NOT CONNECTED`);
      }

      if (api) {
        api.balance.transferFromAlice(currentAccount.address, GEAR_BALANCE_TRANSFER_VALUE);
      }
    } catch (error) {
      alert.error(`${error}`);
    }
  };

  return (
    <div className="switch-block">
      <div className="switch-block--wrapper">
        <div className="switch-buttons">
          <Link
            to={routes.main}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.UPLOAD_PROGRAM && 'switch-buttons__item--active'
            )}
          >
            Upload program
          </Link>
          <Link
            to={routes.uploadedPrograms}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.UPLOADED_PROGRAMS && 'switch-buttons__item--active'
            )}
          >
            My programs
          </Link>
          <Link
            to={routes.allPrograms}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.ALL_PROGRAMS && 'switch-buttons__item--active'
            )}
          >
            All programs
          </Link>
          <Link
            to={routes.messages}
            className={clsx(
              'switch-buttons__item',
              pageType === SWITCH_PAGE_TYPES.ALL_MESSAGES && 'switch-buttons__item--active'
            )}
          >
            Messages
          </Link>
        </div>
        {currentAccount && (
          <>
            <Button
              className="test-balance-button"
              text="Get test balance"
              onClick={isDevChain() ? handleTransferBalanceFromAlice : handleTestBalanceClick}
            />
            <HCaptcha
              sitekey={HCAPTCHA_SITE_KEY}
              onVerify={setCaptchaToken}
              onExpire={() => setCaptchaToken('')}
              ref={captchaRef}
              theme="dark"
              size="invisible"
            />
          </>
        )}
      </div>
      <BlocksSummary />
    </div>
  );
}
Example #14
Source File: index.tsx    From livepeer-com with MIT License 4 votes vote down vote up
AdminObjectStoreTable = ({ id }: { id: string }) => {
  const [message, setMessage] = useState("");
  const [objectStoreName, setObjectStoreName] = useState("");
  const [objectStoreUrl, setObjectStoreUrl] = useState("");
  const [objectStorePubUrl, setObjectStorePubUrl] = useState("");

  const [createModal, setCreateModal] = useState(false);
  const [selectedObjectStore, setSelectedObjectStore] = useState(null);
  const [objectStores, setObjectStores] = useState([]);
  const [nextCursor, setNextCursor] = useState("");
  const [lastCursor, setLastCursor] = useState("");
  const [lastOrder, setLastOrder] = useState("");
  const [lastFilters, setLastFilters] = useState([]);
  const [loading, setLoading] = useState(true);
  const [loadingError, setLoadingError] = useState("");
  const { getObjectStore, createObjectStore, disableObjectStore } = useApi();

  const columns: any = useMemo(
    () => [
      {
        Header: "User Name",
        accessor: "user.email",
      },
      {
        Header: "Name",
        accessor: "name",
      },
      {
        Header: "Url",
        accessor: "url",
        Cell: (cell) => {
          return <Box>{dispUrl(cell.value)}</Box>;
        },
      },
      {
        Header: "Public URL",
        accessor: "publicUrl",
      },
      {
        Header: "Disabled",
        accessor: "disabled",
        Cell: (cell) => {
          return <Checkbox value={cell.value} />;
        },
      },
      {
        Header: "Created",
        accessor: "createdAt",
      },
    ],
    [nextCursor, lastFilters]
  );

  const filtersDesc = useMemo(
    () => [
      { id: "user.email", placeholder: "user's email" },
      { id: "name", placeholder: "name" },
      { id: "url", placeholder: "url" },
      { id: "publicUrl", placeholder: "public url" },
    ],
    []
  );

  const fetchData = ({ order, cursor, filters }, refetch: boolean = false) => {
    setLoading(true);
    if (!refetch) {
      setLastCursor(cursor);
      setLastFilters(filters);
      setLastOrder(order);
    }
    getObjectStore(undefined, order, filters, ROWS_PER_PAGE, cursor)
      .then((result) => {
        const [hooks, nextCursor, resp] = result;
        if (resp.ok && Array.isArray(hooks)) {
          setLoadingError("");
          setNextCursor(nextCursor);
          setObjectStores(hooks);
        } else {
          const errors = JSON.stringify(hooks["errors"] || resp.statusText);
          setLoadingError(errors);
          console.error(errors);
        }
      })
      .catch((err) => {
        setLoadingError(`${err}`);
        console.error(err);
      })
      .finally(() => setLoading(false));
  };

  const refetch = () => {
    fetchData(
      { order: lastOrder, cursor: lastCursor, filters: lastFilters },
      true
    );
  };

  const close = () => {
    setCreateModal(false);
    setSelectedObjectStore(null);
    setObjectStoreName("");
    setObjectStoreUrl("");
    setObjectStorePubUrl("");
  };

  const doCreateObjectStore = () => {
    close();
    if (!objectStoreUrl) {
      setMessage("Object store URL must be specified");
      return;
    }
    if (!objectStoreName) {
      setMessage("Object store name must be specified");
      return;
    }
    setMessage("Creating object store");
    createObjectStore({
      name: objectStoreName,
      url: objectStoreUrl,
      publicUrl: objectStorePubUrl,
    })
      .then(() => {
        setMessage("Object store created");
        refetch();
      })
      .catch((e) => setMessage(`Error: ${e}`));
  };
  const disableOS = (id: string, disabled: boolean) => {
    setSelectedObjectStore(null);
    disableObjectStore(id, disabled)
      .then(() => {
        refetch();
      })
      .catch((e) => setMessage(`Error: ${e}`));
  };

  return (
    <Container
      id={id}
      sx={{
        mb: 5,
        mt: 2,
      }}>
      {createModal && (
        <Modal onClose={close} maxWidth="1000px">
          <h3>Create object store</h3>
          <Grid
            gap={2}
            columns={[3, "1fr 3fr 3fr"]}
            sx={{
              alignItems: "center",
            }}>
            <Box>Name</Box>
            <Box>
              <Input
                autoFocus={true}
                label="Object store name"
                value={objectStoreName}
                sx={{
                  border: "white",
                  borderBottom: "2px solid black",
                  borderRadius: "0px",
                }}
                onChange={(e) => setObjectStoreName(e.target.value)}
                placeholder="new object store name"></Input>
            </Box>
            <Box>(a-z, A-Z, 0-9, -, _, ~ only)</Box>
            <Box>URL</Box>
            <Box>
              <Input
                autoFocus={true}
                label="Object store url"
                value={objectStoreUrl}
                sx={{
                  border: "white",
                  borderBottom: "2px solid black",
                  borderRadius: "0px",
                }}
                onChange={(e) => setObjectStoreUrl(e.target.value)}
                placeholder="gs://bucket"></Input>
            </Box>
            <Box>(a-z, A-Z, 0-9, -, _, ~ only)</Box>
            <Box>Public URL</Box>
            <Box>
              <Input
                autoFocus={true}
                label="Public object store url"
                value={objectStorePubUrl}
                sx={{
                  border: "white",
                  borderBottom: "2px solid black",
                  borderRadius: "0px",
                }}
                onChange={(e) => setObjectStorePubUrl(e.target.value)}
                placeholder="https://public.domain"></Input>
            </Box>
            <Box>(a-z, A-Z, 0-9, -, _, ~ only)</Box>
          </Grid>

          <Flex sx={{ justifyContent: "flex-end" }}>
            <Button
              type="button"
              variant="outlineSmall"
              onClick={close}
              sx={{ mr: 2, mt: 2 }}>
              Cancel
            </Button>
            <Button
              type="button"
              variant="primarySmall"
              onClick={doCreateObjectStore}>
              Create
            </Button>
          </Flex>
        </Modal>
      )}
      <Box sx={{ mt: "2em" }}>{message}</Box>
      <CommonAdminTable
        onRowSelected={setSelectedObjectStore}
        setNextCursor={setNextCursor}
        onFetchData={fetchData}
        loading={loading}
        data={objectStores}
        nextCursor={nextCursor}
        rowsPerPage={ROWS_PER_PAGE}
        err={loadingError}
        columns={columns}
        filtersDesc={filtersDesc}>
        <Button
          variant="outlineSmall"
          sx={{ margin: 2 }}
          onClick={() => {
            setMessage("");
            setCreateModal(true);
          }}>
          Create
        </Button>
        <Button
          variant="primarySmall"
          aria-label="Disable object store button"
          disabled={
            !selectedObjectStore ||
            (selectedObjectStore && selectedObjectStore.disabled)
          }
          sx={{ margin: 2, mb: 4 }}
          onClick={() =>
            selectedObjectStore && disableOS(selectedObjectStore.id, true)
          }>
          Disable
        </Button>
        <Button
          variant="primarySmall"
          aria-label="Enable object store button"
          disabled={
            !selectedObjectStore ||
            (selectedObjectStore && !selectedObjectStore.disabled)
          }
          sx={{ margin: 2, mb: 4 }}
          onClick={() =>
            selectedObjectStore && disableOS(selectedObjectStore.id, false)
          }>
          Enable
        </Button>
      </CommonAdminTable>
    </Container>
  );
}