Path: blob/master/src/packages/next/pages/ephemeral.tsx
2442 views
/*1* This file is part of CoCalc: Copyright © 2024 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { useEffect, useState } from "react";6import { Alert, Button, Card, Input, Layout, Space, Typography } from "antd";7import Footer from "components/landing/footer";8import Header from "components/landing/header";9import Head from "components/landing/head";10import { Icon } from "@cocalc/frontend/components/icon";11import apiPost from "lib/api/post";12import { Customize } from "lib/customize";13import withCustomize from "lib/with-customize";14import { useRouter } from "next/router";1516const { Paragraph, Text, Title } = Typography;1718type Status = "idle" | "verifying" | "redirecting";1920interface Props {21customize;22token?: string;23}2425export default function EphemeralPage({ customize, token }: Props) {26const router = useRouter();27const [registrationToken, setRegistrationToken] = useState<string>(28token ?? "",29);30const [status, setStatus] = useState<Status>("idle");31const [info, setInfo] = useState<string>("");32const [error, setError] = useState<string>("");3334useEffect(() => {35if (token) {36setRegistrationToken(token);37}38}, [token]);3940const trimmedToken = registrationToken.trim();41const working = status !== "idle";42const disabled = trimmedToken.length === 0 || working;4344async function handleConfirm(): Promise<void> {45if (disabled) return;46setStatus("verifying");47setInfo("");48setError("");49try {50const result = await apiPost("/auth/ephemeral", {51registrationToken: trimmedToken,52});53setStatus("redirecting");54setInfo("Success! Redirecting you to your workspace…");55if (result?.project_id) {56await router.push(57`/static/app.html?target=projects/${result.project_id}/files/`,58);59} else {60await router.push("/static/app.html?target=projects");61}62} catch (err) {63setError(err?.message ?? `${err}`);64setStatus("idle");65}66}6768return (69<Customize value={customize}>70<Head title="Create Ephemeral Account" />71<Layout>72<Header />73<Layout.Content style={{ backgroundColor: "white", minHeight: "60vh" }}>74<div75style={{76maxWidth: "640px",77margin: "8vh auto 10vh auto",78padding: "0 15px",79}}80>81<Card>82<Space83direction="vertical"84style={{ width: "100%" }}85size="large"86>87<Space88align="center"89direction="vertical"90style={{ width: "100%" }}91>92<Icon name="user" style={{ fontSize: "60px" }} />93<Title level={2} style={{ marginBottom: 0 }}>94Enter Registration Token95</Title>96<Paragraph style={{ textAlign: "center", marginBottom: 0 }}>97Provide the registration token supplied for your exam or98event. You can land directly on this page using{" "}99<code>/ephemeral?token=YOUR_TOKEN</code>, or paste the code100below.101</Paragraph>102</Space>103104<div>105<Text strong>Registration Token</Text>106<Input107allowClear108autoFocus109size="large"110placeholder="abc123..."111value={registrationToken}112onChange={(e) => {113setRegistrationToken(e.target.value);114if (info) setInfo("");115if (error) setError("");116}}117onPressEnter={disabled ? undefined : handleConfirm}118/>119</div>120121{error && (122<Alert123type="error"124message="Unable to create account"125description={error}126showIcon127closable128onClose={() => setError("")}129/>130)}131132{info && (133<Alert134type="info"135message={info}136showIcon137closable138onClose={() => setInfo("")}139/>140)}141142<Button143type="primary"144size="large"145disabled={disabled}146loading={working}147onClick={handleConfirm}148block149>150{status === "redirecting" ? "Redirecting…" : "Continue"}151</Button>152153<Alert154type="warning"155showIcon156message="Ephemeral accounts"157description={158<Paragraph style={{ marginBottom: 0 }}>159Ephemeral accounts automatically expire after the duration160configured with their registration token. When the token161is valid you'll be signed in automatically and redirected162to your workspace with a cookie that expires at the same163time.164</Paragraph>165}166/>167</Space>168</Card>169</div>170</Layout.Content>171<Footer />172</Layout>173</Customize>174);175}176177export async function getServerSideProps(context) {178const token =179typeof context?.query?.token === "string" ? context.query.token : "";180return await withCustomize({181context,182props: { token },183});184}185186187