hooks#useLoggedIn TypeScript Examples

The following examples show how to use hooks#useLoggedIn. 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: 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 #2
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 #3
Source File: media-server.tsx    From livepeer-com with MIT License 6 votes vote down vote up
MediaServer = () => {
  useLoggedIn();
  const { user } = useApi();

  if (!user) {
    return <Layout />;
  }
  return (
    <Layout
      id="developers/media-server"
      breadcrumbs={[
        { title: "Developers", href: "/dashboard/developers/media-server" },
        { title: "Media Server" },
      ]}
      {...Content.metaData}>
      <Box css={{ p: "$6" }}>
        <Box css={{ mb: "$8" }}>
          <MediaServerTable />
        </Box>
      </Box>
    </Layout>
  );
}
Example #4
Source File: index.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/webhooks"
      breadcrumbs={[
        { title: "Developers", href: "/dashboard/developers/webhooks" },
        { title: "Webhooks" },
      ]}>
      <Box css={{ p: "$6" }}>
        <Box css={{ mb: "$8" }}>
          <WebhooksTable />
        </Box>
      </Box>
    </Layout>
  );
}
Example #5
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 #6
Source File: index.tsx    From livepeer-com with MIT License 6 votes vote down vote up
Sessions = () => {
  useLoggedIn();
  const { user } = useApi();

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

  return (
    <Layout id="streams/sessions" breadcrumbs={[{ title: "Sessions" }]}>
      <Box
        css={{
          pb: "$9",
          px: "$6",
          pt: "$6",
          "@bp4": {
            p: "$6",
          },
        }}>
        <AllSessionsTable />
      </Box>
    </Layout>
  );
}
Example #7
Source File: index.tsx    From livepeer-com with MIT License 6 votes vote down vote up
Streams = () => {
  useLoggedIn();
  const { user } = useApi();

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

  return (
    <Layout
      id="streams"
      breadcrumbs={[{ title: "Streams" }]}
      {...Content.metaData}>
      <Box
        css={{
          pb: "$9",
          px: "$6",
          pt: "$6",
          "@bp4": {
            p: "$6",
          },
        }}>
        <StreamsTable
          title="Streams"
          userId={user.id}
          pageSize={20}
          tableId="streamsTable"
        />
      </Box>
    </Layout>
  );
}
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: forgot-password.tsx    From livepeer-com with MIT License 5 votes vote down vote up
ForgotPasswordPage = () => {
  useLoggedIn(false);
  const [errors, setErrors] = useState([]);
  const [loading, setLoading] = useState(false);
  const [success, setSuccess] = useState(false);
  const { makePasswordResetToken } = useApi();
  const onSubmit = async ({ email }) => {
    setLoading(true);
    setErrors([]);
    const res = await makePasswordResetToken(email);
    if (res.errors) {
      setLoading(false);
      setErrors(res.errors);
    } else {
      setSuccess(true);
    }
  };

  return (
    <Layout {...Content.metaData}>
      <Guides backgroundColor="$neutral2" />
      {success ? (
        <Box
          css={{
            minHeight: "calc(100vh - 510px)",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            zIndex: 1,
          }}>
          Password reset link sent to your email.
        </Box>
      ) : (
        <Box css={{ position: "relative" }}>
          <Container
            size="3"
            css={{
              px: "$6",
              py: "$7",
              width: "100%",
              "@bp3": {
                py: "$8",
                px: "$4",
              },
            }}>
            <Flex
              align="center"
              justify="center"
              css={{
                flexGrow: 1,
                flexDirection: "column",
              }}>
              <Heading size="3" as="h1" css={{ mb: "$5" }}>
                Reset your password
              </Heading>
              <Login
                id="forgot-password"
                showEmail={true}
                showPassword={false}
                buttonText="Get reset link"
                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 #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: streamDetail.tsx    From livepeer-com with MIT License 4 votes vote down vote up
StreamDetail = ({
  breadcrumbs,
  children,
  stream,
  streamHealth,
  invalidateStream,
  setSwitchTab,
  activeTab = "Overview",
}) => {
  useLoggedIn();
  const { user, getIngest, getAdminStreams } = useApi();
  const userIsAdmin = user && user.admin;
  const router = useRouter();
  const { query } = router;
  const id = query.id;
  const [_, setIngest] = useState([]);
  const [notFound, setNotFound] = useState(false);
  const [isCopied, setCopied] = useState(0);
  const [keyRevealed, setKeyRevealed] = useState(false);
  const [lastSession, setLastSession] = useState(null);
  const [lastSessionLoading, setLastSessionLoading] = useState(false);

  useEffect(() => {
    if (user && user.admin && stream && !lastSessionLoading) {
      setLastSessionLoading(true);
      getAdminStreams({
        sessionsonly: true,
        limit: 1,
        order: "createdAt-true",
        filters: [{ id: "parentId", value: stream.id }],
        userId: stream.userId,
      })
        .then((res) => {
          const [streamsOrError] = res;
          if (Array.isArray(streamsOrError) && streamsOrError.length > 0) {
            setLastSession(streamsOrError[0]);
          }
        })
        .catch((e) => console.log(e))
        .finally(() => setLastSessionLoading(false));
    }
  }, [user, stream]);

  useEffect(() => {
    if (isCopied) {
      const timeout = setTimeout(() => {
        setCopied(0);
      }, isCopied);
      return () => clearTimeout(timeout);
    }
  }, [isCopied]);

  useEffect(() => {
    getIngest(true)
      .then((ingest) => {
        if (Array.isArray(ingest)) {
          ingest.sort((a, b) => a.base.localeCompare(b.base));
        }
        setIngest(ingest);
      })
      .catch((err) => console.error(err)); // todo: surface this
  }, [id]);

  const healthState = useMemo(() => {
    if (!stream?.isActive) return null;

    const activeCond = streamHealth?.conditions.find(
      (c) => c.type === "Active"
    );
    const healthyCond = streamHealth?.healthy;
    const healthValid =
      activeCond?.status &&
      healthyCond?.status != null &&
      healthyCond.lastProbeTime >= activeCond.lastTransitionTime;
    return !healthValid
      ? StatusVariant.Pending
      : healthyCond.status
      ? StatusVariant.Healthy
      : StatusVariant.Unhealthy;
  }, [stream?.isActive, streamHealth]);

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

  let { broadcasterHost, region } = stream || {};
  if (!broadcasterHost && lastSession && lastSession.broadcasterHost) {
    broadcasterHost = lastSession.broadcasterHost;
  }
  if (!region && lastSession && lastSession.region) {
    region = lastSession.region;
  }

  const playbackId = (stream || {}).playbackId || "";
  const domain = isStaging() ? "monster" : "com";
  const globalIngestUrl = `rtmp://rtmp.livepeer.${domain}/live`;
  const globalPlaybackUrl = `https://livepeercdn.${domain}/hls/${playbackId}/index.m3u8`;
  const globalSrtIngestUrl = `srt://rtmp.livepeer.${domain}:2935?streamid=${
    stream?.streamKey || ""
  }`;

  return (
    <Layout id="streams" breadcrumbs={breadcrumbs}>
      <Box css={{ px: "$6", py: "$7" }}>
        {stream ? (
          <>
            <Flex>
              <Box
                css={{
                  minWidth: 424,
                  flex: "0 0 33%",
                }}>
                <Flex
                  justify="between"
                  align="end"
                  css={{
                    pb: "$3",
                    mb: "$5",
                    width: "100%",
                  }}>
                  <Heading size="2">
                    <Flex css={{ ai: "center" }}>
                      <Box
                        css={{
                          fontWeight: 600,
                          letterSpacing: "0",
                          mr: "$2",
                        }}>
                        {stream.name}
                      </Box>
                      {!healthState ? null : (
                        <StatusBadge
                          variant={healthState}
                          timestamp={streamHealth?.healthy?.lastProbeTime}
                          css={{ mt: "$1", letterSpacing: 0 }}
                        />
                      )}
                      {stream.suspended && (
                        <Badge
                          size="2"
                          variant="red"
                          css={{
                            ml: "$1",
                            mt: "$1",
                            letterSpacing: 0,
                          }}>
                          <Box css={{ mr: 5 }}>
                            <PauseIcon />
                          </Box>
                          Suspended
                        </Badge>
                      )}
                    </Flex>
                  </Heading>
                </Flex>

                <Box>
                  <Box
                    css={{
                      maxWidth: "470px",
                      justifySelf: "flex-end",
                      width: "100%",
                    }}>
                    <Box
                      css={{
                        borderRadius: "$3",
                        overflow: "hidden",
                        position: "relative",
                        mb: "$7",
                      }}>
                      {stream.isActive ? (
                        <>
                          <Badge
                            size="2"
                            variant="green"
                            css={{
                              position: "absolute",
                              zIndex: 1,
                              left: 10,
                              top: 10,
                              letterSpacing: 0,
                            }}>
                            <Box css={{ mr: 5 }}>
                              <Status size="1" variant="green" />
                            </Box>
                            Active
                          </Badge>
                          <Player src={globalPlaybackUrl} />
                        </>
                      ) : (
                        <Box
                          css={{
                            width: "100%",
                            height: 265,
                            borderRadius: "$2",
                            overflow: "hidden",
                            position: "relative",
                            bc: "#28282c",
                          }}>
                          <Badge
                            size="2"
                            css={{
                              backgroundColor: "$primary7",
                              position: "absolute",
                              zIndex: 1,
                              left: 10,
                              top: 10,
                              letterSpacing: 0,
                            }}>
                            <Box css={{ mr: 5 }}>
                              <Status
                                css={{ backgroundColor: "$primary9" }}
                                size="1"
                              />
                            </Box>
                            Idle
                          </Badge>
                        </Box>
                      )}
                    </Box>
                  </Box>
                  <Box
                    css={{
                      borderBottom: "1px solid",
                      borderColor: "$neutral6",
                      pb: "$2",
                      mb: "$4",
                      width: "100%",
                    }}>
                    <Heading size="1" css={{ fontWeight: 600 }}>
                      Details
                    </Heading>
                  </Box>
                  <Flex
                    css={{
                      justifyContent: "flex-start",
                      alignItems: "baseline",
                      flexDirection: "column",
                    }}>
                    <Box
                      css={{
                        display: "grid",
                        alignItems: "center",
                        gridTemplateColumns: "10em auto",
                        width: "100%",
                        fontSize: "$2",
                        position: "relative",
                      }}>
                      <Cell css={{ color: "$primary11" }}>Stream name</Cell>
                      <Cell>{stream.name}</Cell>
                      <Cell css={{ color: "$primary11" }}>Stream ID</Cell>
                      <Cell>
                        <ClipBut text={stream.id} />
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>Stream key</Cell>
                      <Cell>
                        {keyRevealed ? (
                          <Flex>
                            <ClipBut text={stream.streamKey} />
                          </Flex>
                        ) : (
                          <Button
                            type="button"
                            variant="primary"
                            onClick={() => setKeyRevealed(true)}>
                            Reveal stream key
                          </Button>
                        )}
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>RTMP ingest URL</Cell>
                      <Cell css={{ cursor: "pointer" }}>
                        <ShowURL url={globalIngestUrl} anchor={false} />
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>SRT ingest URL</Cell>
                      <Cell css={{ cursor: "pointer" }}>
                        <ShowURL
                          url={globalSrtIngestUrl}
                          shortendUrl={globalSrtIngestUrl.replace(
                            globalSrtIngestUrl.slice(38),
                            "…"
                          )}
                          anchor={false}
                        />
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>Playback URL</Cell>
                      <Cell css={{ cursor: "pointer" }}>
                        <ShowURL
                          url={globalPlaybackUrl}
                          shortendUrl={globalPlaybackUrl.replace(
                            globalPlaybackUrl.slice(29, 45),
                            "…"
                          )}
                          anchor={false}
                        />
                        <Tooltip
                          content={
                            <Box>
                              We changed our playback domain, but
                              cdn.livepeer.com is still working.
                            </Box>
                          }>
                          <Help />
                        </Tooltip>
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>Record sessions</Cell>
                      <Cell>
                        <Flex css={{ position: "relative", top: "2px" }}>
                          <Box css={{ mr: "$2" }}>
                            <Record
                              stream={stream}
                              invalidate={invalidateStream}
                            />
                          </Box>
                          <Tooltip
                            multiline
                            content={
                              <Box>
                                When enabled, transcoded streaming sessions will
                                be recorded and stored by Livepeer.com. Each
                                recorded session will have a recording .m3u8 URL
                                for playback and an MP4 download link. This
                                feature is currently free.
                              </Box>
                            }>
                            <Help />
                          </Tooltip>
                        </Flex>
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>Created at</Cell>
                      <Cell>
                        <RelativeTime
                          id="cat"
                          prefix="createdat"
                          tm={stream.createdAt}
                          swap={true}
                        />
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>Last seen</Cell>
                      <Cell>
                        <RelativeTime
                          id="last"
                          prefix="lastSeen"
                          tm={stream.lastSeen}
                          swap={true}
                        />
                      </Cell>
                      <Cell css={{ color: "$primary11" }}>Status</Cell>
                      <Cell>{stream.isActive ? "Active" : "Idle"}</Cell>
                      <Cell css={{ color: "$primary11" }}>Suspended</Cell>
                      <Cell>{stream.suspended ? "Suspended" : "Normal"}</Cell>
                    </Box>
                  </Flex>
                </Box>
              </Box>
              <Box css={{ flexGrow: 1, ml: "$8" }}>
                <Flex
                  justify="between"
                  css={{
                    borderBottom: "1px solid",
                    borderColor: "$neutral6",
                    mb: "$4",
                    width: "100%",
                  }}>
                  <Box css={{ display: "flex" }}>
                    <Box
                      as="div"
                      onClick={() => setSwitchTab("Overview")}
                      css={{
                        pb: "$2",
                        width: "100%",
                        cursor: "pointer",
                        textDecoration: "none",
                        borderBottom: "2px solid",
                        borderColor:
                          activeTab === "Overview" ? "$violet9" : "transparent",
                        mr: "$5",
                        "&:hover": {
                          textDecoration: "none",
                        },
                      }}>
                      Overview
                    </Box>

                    <Box
                      as="div"
                      onClick={() => setSwitchTab("Health")}
                      css={{
                        textDecoration: "none",
                        pb: "$2",
                        width: "100%",
                        cursor: "pointer",
                        borderBottom: "2px solid",
                        borderColor:
                          activeTab === "Health" ? "$violet9" : "transparent",
                        "&:hover": {
                          textDecoration: "none",
                        },
                      }}>
                      Health
                    </Box>
                  </Box>
                  <Box css={{ position: "relative", top: "-8px" }}>
                    <DropdownMenu>
                      <DropdownMenuTrigger asChild>
                        <Button
                          variant="primary"
                          size="2"
                          css={{ display: "flex", ai: "center", mr: "$1" }}>
                          Actions
                          <Box as={ChevronDownIcon} css={{ ml: "$1" }} />
                        </Button>
                      </DropdownMenuTrigger>
                      <DropdownMenuContent align="end">
                        <DropdownMenuGroup>
                          <Record
                            stream={stream}
                            invalidate={invalidateStream}
                            isSwitch={false}
                          />
                          <Suspend
                            stream={stream}
                            invalidate={invalidateStream}
                          />
                          <Delete
                            stream={stream}
                            invalidate={invalidateStream}
                          />
                          {userIsAdmin && stream.isActive && (
                            <>
                              <DropdownMenuSeparator />
                              <DropdownMenuLabel>Admin only</DropdownMenuLabel>
                              <Terminate
                                stream={stream}
                                invalidate={invalidateStream}
                              />
                            </>
                          )}
                        </DropdownMenuGroup>
                      </DropdownMenuContent>
                    </DropdownMenu>
                  </Box>
                </Flex>
                <Box css={{ py: "$4" }}>{children}</Box>
              </Box>
            </Flex>
          </>
        ) : notFound ? (
          <Box>Not found</Box>
        ) : (
          <Flex
            css={{
              height: "calc(100vh - 300px)",
              justifyContent: "center",
              alignItems: "center",
            }}>
            <Spinner />
          </Flex>
        )}
      </Box>
    </Layout>
  );
}
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: [id].tsx    From livepeer-com with MIT License 4 votes vote down vote up
WebhookDetail = () => {
  useLoggedIn();
  const { user, getWebhook, deleteWebhook, updateWebhook } = useApi();
  const [deleting, setDeleting] = useState(false);
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
  const router = useRouter();
  const dialogState = useToggleState();
  const { id } = router.query;

  const fetcher = useCallback(async () => {
    const webhook = await getWebhook(id);
    return webhook;
  }, [id]);

  const { data } = useQuery([id], () => fetcher());
  const queryClient = useQueryClient();

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

  return !user ? null : (
    <Layout
      id="developers/webhooks"
      breadcrumbs={[
        { title: "Developers" },
        { title: "Webhooks", href: "/dashboard/developers/webhooks" },
        { title: data?.name },
      ]}>
      <Box css={{ p: "$6" }}>
        <Box css={{ mb: "$8" }}>
          {data && (
            <Box
              css={{
                borderRadius: 6,
                border: "1px solid $colors$primary7",
              }}>
              <Flex
                css={{
                  p: "$3",
                  width: "100%",
                  borderBottom: "1px solid $colors$primary7",
                  gap: "$3",
                  fd: "row",
                  ai: "center",
                  jc: "space-between",
                }}>
                <Heading
                  size="2"
                  css={{
                    whiteSpace: "nowrap",
                    overflow: "hidden",
                    textOverflow: "ellipsis",
                    width: "100%",
                    ai: "flex-start",
                  }}>
                  {data.url}
                </Heading>
                <Flex css={{ ai: "flex-end", fg: "0", fs: "0", pl: "$3" }}>
                  <AlertDialog open={deleteDialogOpen}>
                    <Button
                      onClick={() => {
                        setDeleteDialogOpen(true);
                      }}
                      size="2"
                      css={{ mr: "$2", display: "flex", ai: "center" }}
                      variant="red">
                      <StyledCross />
                      Delete
                    </Button>
                    <AlertDialogContent
                      css={{ maxWidth: 450, px: "$5", pt: "$4", pb: "$4" }}>
                      <AlertDialogTitle asChild>
                        <Heading size="1">Delete Webhook</Heading>
                      </AlertDialogTitle>
                      <AlertDialogDescription asChild>
                        <Text
                          size="3"
                          variant="gray"
                          css={{ mt: "$2", lineHeight: "22px" }}>
                          Are you sure you want to delete this webhook?
                        </Text>
                      </AlertDialogDescription>
                      <Flex css={{ jc: "flex-end", gap: "$2", mt: "$5" }}>
                        <Button
                          onClick={() => setDeleteDialogOpen(false)}
                          size="2"
                          ghost>
                          Cancel
                        </Button>
                        <AlertDialogAction asChild>
                          <Button
                            size="2"
                            disabled={deleting}
                            onClick={async () => {
                              setDeleting(true);
                              await deleteWebhook(data.id);
                              await invalidateQuery();
                              setDeleting(false);
                              setDeleteDialogOpen(false);
                              router.push("/dashboard/developers/webhooks");
                            }}
                            variant="red">
                            {deleting && (
                              <Spinner
                                css={{
                                  width: 16,
                                  height: 16,
                                  mr: "$2",
                                }}
                              />
                            )}
                            Delete
                          </Button>
                        </AlertDialogAction>
                      </Flex>
                    </AlertDialogContent>
                  </AlertDialog>
                  <WebhookDialog
                    button={
                      <Button
                        size="2"
                        css={{ display: "flex", ai: "center" }}
                        onClick={() => dialogState.onToggle()}>
                        <StyledPencil />
                        Update details
                      </Button>
                    }
                    webhook={data}
                    action={Action.Update}
                    isOpen={dialogState.on}
                    onOpenChange={dialogState.onToggle}
                    onSubmit={async ({ events, name, url, sharedSecret }) => {
                      delete data.event; // remove deprecated field before updating
                      await updateWebhook(data.id, {
                        ...data,
                        events: events ? events : data.events,
                        name: name ? name : data.name,
                        url: url ? url : data.url,
                        sharedSecret: sharedSecret ?? data.sharedSecret,
                      });
                      await invalidateQuery();
                    }}
                  />
                </Flex>
              </Flex>

              <Box
                css={{
                  display: "grid",
                  gridTemplateColumns: "12em auto",
                  width: "100%",
                  fontSize: "$2",
                  position: "relative",
                  p: "$3",
                  borderBottomLeftRadius: 6,
                  borderBottomRightRadius: 6,
                  backgroundColor: "$panel",
                }}>
                <Cell variant="gray">URL</Cell>
                <Cell>{data.url}</Cell>
                <Cell variant="gray">Name</Cell>
                <Cell>{data.name}</Cell>
                <Cell variant="gray">Secret</Cell>
                <Cell>{data.sharedSecret}</Cell>
                <Cell variant="gray">Created</Cell>
                <Cell>{format(data.createdAt, "MMMM dd, yyyy h:mm a")}</Cell>
                <Cell variant="gray">Event types</Cell>
                <Cell css={{ display: "flex", fw: "wrap" }}>
                  {data.events.map((e) => (
                    <Badge
                      variant="primary"
                      size="2"
                      css={{ fontWeight: 600, mr: "$1", mb: "$1" }}>
                      {e}
                    </Badge>
                  ))}
                </Cell>
                <Cell variant="gray">Last trigger</Cell>
                <Cell>
                  {data.status
                    ? format(
                        data.status?.lastTriggeredAt,
                        "MMMM dd, yyyy h:mm:ss a"
                      )
                    : "Never"}
                </Cell>
                <Cell variant="gray">Last failure</Cell>
                <Cell>
                  {!data.status
                    ? "Never"
                    : data.status.lastFailure
                    ? format(
                        data.status.lastFailure.timestamp,
                        "MMMM dd, yyyy h:mm:ss a"
                      )
                    : "Never"}
                </Cell>
                {data.status ? (
                  data.status.lastFailure?.statusCode ? (
                    <>
                      <Cell variant="gray">Error Status Code</Cell>
                      <Cell>{`${data.status.lastFailure.statusCode} 
                        ${
                          STATUS_CODES[data.status.lastFailure.statusCode]
                        }`}</Cell>
                    </>
                  ) : data.status.lastFailure ? (
                    <>
                      <Cell variant="gray">Error message</Cell>
                      <Cell
                        css={{
                          fontFamily: "monospace",
                        }}>
                        {data.status.lastFailure.error ?? "unknown"}
                      </Cell>
                    </>
                  ) : (
                    ""
                  )
                ) : (
                  ""
                )}
                {data.status?.lastFailure?.response ? (
                  <>
                    <Cell variant="gray">Error response</Cell>
                    <Cell
                      css={{
                        fontFamily: "monospace",
                      }}>
                      {data.status.lastFailure.response}
                    </Cell>
                  </>
                ) : (
                  ""
                )}
              </Box>
            </Box>
          )}
        </Box>
      </Box>
    </Layout>
  );
}
Example #14
Source File: index.tsx    From livepeer-com with MIT License 4 votes vote down vote up
export default function MintNFT() {
  const { status, connect, account, chainId, ethereum } = useMetaMask();
  const { user, token: jwt, endpoint } = useApi();
  const videoNft = useMemo(
    () =>
      new minter.FullMinter({ auth: { jwt }, endpoint }, { ethereum, chainId }),
    [ethereum, chainId, jwt, endpoint]
  );
  const isMinting = useToggleState();
  const isUploading = useToggleState();

  const initState = useMemo(() => {
    if (typeof window === "undefined") {
      return {};
    }
    const searchParams = new URLSearchParams(window.location.search);
    return {
      file: null as File,
      contractAddress: searchParams.get("contractAddress"),
      tokenUri: searchParams.get("tokenUri"),
      recipient: searchParams.get("recipient"),
    };
  }, [typeof window !== "undefined" && window?.location?.search]);
  const defaultContractAddress = useMemo<string>(
    () => chains.getBuiltinChain(chainId)?.defaultContract,
    [chainId]
  );
  const [state, setState] = useState(initState);
  type State = typeof state;
  const setStateProp = <T extends keyof State>(prop: T, value: State[T]) => {
    setState({ ...state, [prop]: value });
  };

  const [logs, setLogs] = useState<JSX.Element[]>([]);
  const addLog = useCallback(
    (log: JSX.Element | string) =>
      setLogs((prev) => [
        ...prev,
        <>
          [{new Date().toLocaleTimeString()}] {log}
        </>,
      ]),
    [setLogs]
  );

  const onClickMint = useCallback(async () => {
    isMinting.onOn();
    try {
      await richMintNft(
        videoNft.web3,
        state.contractAddress ?? defaultContractAddress,
        state.recipient ?? account,
        state.tokenUri,
        addLog,
        chains.getBuiltinChain(chainId)
      );
    } finally {
      isMinting.onOff();
    }
  }, [state, videoNft.web3, defaultContractAddress, account, addLog, chainId]);

  const onClickSwitchNetwork = (chainId: string) => () => {
    setLogs([]);
    return richSwitchChain(
      ethereum,
      chains.getBuiltinChain(chainId).spec,
      addLog
    );
  };

  const onClickConnect = useCallback(() => {
    setLogs([]);
    return connect();
  }, [setLogs, connect]);

  if (!initState.tokenUri) {
    useLoggedIn();
  }
  return (
    <Layout {...Content.metaData} css={{ minHeight: "100vh" }}>
      <Guides backgroundColor="$neutral2" />
      <Box css={{ position: "relative", flex: 1 }}>
        <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",
            }}>
            <AlertDialog open={true}>
              <AlertDialogContent
                css={{ maxWidth: 450, px: "$5", pt: "$4", pb: "$4" }}
                onOpenAutoFocus={(e) => e.preventDefault()}>
                <AlertDialogTitle asChild>
                  <Heading size="1">Mint a Video NFT</Heading>
                </AlertDialogTitle>

                <Box
                  css={{ mt: "$3" }}
                  as="form"
                  onSubmit={(e) => {
                    e.preventDefault();
                    return onClickMint();
                  }}>
                  <Flex direction="column" gap="2">
                    {state?.tokenUri || !user ? undefined : (
                      <>
                        <Label htmlFor="file">File {state?.tokenUri}</Label>
                        <Flex direction="row" gap="2">
                          <Text>{state.file?.name}</Text>
                          <Button
                            css={{ display: "flex", ai: "center" }}
                            type="button"
                            size="2"
                            variant="primary"
                            onClick={async () => {
                              setStateProp(
                                "file",
                                await videoNft.uploader.pickFile()
                              );
                            }}>
                            Pick a file
                          </Button>
                        </Flex>
                      </>
                    )}

                    <Label htmlFor="contractAddress">
                      Contract Address (optional)
                    </Label>
                    <Tooltip content="Defaults to Livepeer-owned Video NFT contract.">
                      <TextField
                        size="2"
                        type="text"
                        id="contractAddress"
                        value={
                          state.contractAddress === defaultContractAddress
                            ? ""
                            : state.contractAddress
                        }
                        disabled={
                          isMinting.on ||
                          status !== "connected" ||
                          !chains.isChainBuiltin(chainId)
                        }
                        placeholder={
                          !defaultContractAddress
                            ? "Unsupported network"
                            : `Livepeer Video NFT (${defaultContractAddress})`
                        }
                        onChange={(e) =>
                          setStateProp("contractAddress", e.target.value)
                        }
                      />
                    </Tooltip>

                    <Label htmlFor="tokenUri">Token URI</Label>
                    <TextField
                      required={true}
                      autoFocus
                      size="2"
                      type="url"
                      pattern="^ipfs://.+"
                      id="tokenUri"
                      value={state.tokenUri}
                      disabled={true}
                      onChange={(e) => setStateProp("tokenUri", e.target.value)}
                      placeholder="ipfs://..."
                    />
                  </Flex>

                  <AlertDialogDescription asChild>
                    <Text
                      size="3"
                      variant="gray"
                      css={{ mt: "$2", fontSize: "$2", mb: "$4" }}>
                      <Box
                        css={{
                          overflow: "scroll",
                          p: "$4",
                          height: 200,
                          borderRadius: 6,
                        }}>
                        {(() => {
                          switch (status) {
                            case "initializing":
                              return (
                                <div>
                                  Synchronisation with MetaMask ongoing...
                                </div>
                              );
                            case "unavailable":
                              return (
                                <div>
                                  MetaMask not available. Install it at{" "}
                                  <a
                                    href="https://metamask.io/download"
                                    target="_blank">
                                    metamask.io
                                  </a>
                                </div>
                              );
                            case "notConnected":
                              return (
                                <div>Connect your MetaMask wallet below.</div>
                              );
                            case "connecting":
                              return <div>Connecting to MetaMask...</div>;
                            default:
                              return (
                                <div>Unknown MetaMask status: ${status}.</div>
                              );
                            case "connected":
                              if (!chains.isChainBuiltin(chainId)) {
                                return (
                                  <div>
                                    Only Polygon network is supported right now.
                                    Click below to switch or add it to MetaMask.
                                  </div>
                                );
                              }
                              return (
                                <>
                                  <Box css={{ mb: "$2" }}>
                                    Connected to {displayAddr(account)} on{" "}
                                    {
                                      chains.getBuiltinChain(chainId).spec
                                        .chainName
                                    }{" "}
                                    (<code>{parseInt(chainId, 16)}</code>)
                                  </Box>
                                  {logs.map((log, idx) => (
                                    <Box key={`log-${idx}`}>{log}</Box>
                                  ))}
                                </>
                              );
                          }
                        })()}
                      </Box>
                    </Text>
                  </AlertDialogDescription>

                  <Flex css={{ jc: "flex-end", gap: "$3", mt: "$4" }}>
                    {status === "notConnected" ? (
                      <Button
                        css={{ display: "flex", ai: "center" }}
                        type="button"
                        size="2"
                        disabled={status !== "notConnected"}
                        variant="primary"
                        onClick={onClickConnect}>
                        Connect to MetaMask
                      </Button>
                    ) : status === "connected" &&
                      !chains.isChainBuiltin(chainId) ? (
                      <>
                        <Button
                          css={{ display: "flex", ai: "center" }}
                          type="button"
                          size="2"
                          variant="primary"
                          onClick={onClickSwitchNetwork("0x13881")}>
                          Polygon Testnet
                        </Button>
                        <Button
                          css={{ display: "flex", ai: "center" }}
                          type="button"
                          size="2"
                          variant="primary"
                          onClick={onClickSwitchNetwork("0x89")}>
                          Polygon Mainnet
                        </Button>
                      </>
                    ) : user && jwt && state.file && !state.tokenUri ? (
                      <Button
                        css={{ display: "flex", ai: "center" }}
                        type="button"
                        size="2"
                        disabled={isUploading.on}
                        variant="primary"
                        onClick={async () => {
                          isUploading.onOn();
                          try {
                            const { file } = state;
                            addLog("Uploading file...");
                            let asset = await videoNft.api.createAsset(
                              file.name,
                              file
                            );
                            addLog("Normalizing for NFT...");
                            asset = await videoNft.api.nftNormalize(asset);
                            addLog("Exporting to IPFS...");
                            const { nftMetadataUrl } =
                              await videoNft.api.exportToIPFS(asset.id);
                            addLog("Done! NFT token URI: " + nftMetadataUrl);
                            setStateProp("tokenUri", nftMetadataUrl);
                          } catch (err) {
                            addLog("Error uploading file: " + err.message);
                          } finally {
                            isUploading.onOff();
                          }
                        }}>
                        {isUploading.on && (
                          <Spinner
                            css={{
                              color: "$hiContrast",
                              width: 16,
                              height: 16,
                              mr: "$2",
                            }}
                          />
                        )}
                        {isUploading.on ? "Uploading..." : "Upload file"}
                      </Button>
                    ) : (
                      <Button
                        css={{ display: "flex", ai: "center" }}
                        type="submit"
                        size="2"
                        disabled={isMinting.on || status !== "connected"}
                        variant="primary">
                        {isMinting.on && (
                          <Spinner
                            css={{
                              color: "$hiContrast",
                              width: 16,
                              height: 16,
                              mr: "$2",
                            }}
                          />
                        )}
                        {isMinting.on ? "Minting..." : "Mint NFT"}
                      </Button>
                    )}
                  </Flex>
                </Box>
              </AlertDialogContent>
            </AlertDialog>
          </Flex>
        </Container>
      </Box>
    </Layout>
  );
}