66 lines
2.7 KiB
TypeScript
66 lines
2.7 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
// The page an invitation link leads to: /invitations/<organization>/<token> (spec §3.1).
|
|
//
|
|
// Opening it changes nothing. A visitor who is not signed in is asked to sign in -- with the address
|
|
// the invitation was sent to -- and comes back here; a signed-in person sees who they are signed in
|
|
// as and a button, and the button is what accepts. The page does not look the token up: only the
|
|
// invitee's own account may learn anything about it, and the answer to accepting is worded per state
|
|
// (used, expired, already a member) for exactly that account and as a plain "not valid" for everyone
|
|
// else (kit-tenancy), so this page cannot be used to find out which tokens or organizations exist.
|
|
|
|
import type { Metadata } from "next"
|
|
import type { ReactElement } from "react"
|
|
import { signInHref } from "../../../../src/server/guard"
|
|
import { pageContext } from "../../../../src/server/page"
|
|
import { InviteAccept } from "../../../../src/ui/invite-accept"
|
|
import { SignOut } from "../../../../src/ui/sign-out"
|
|
import { StateView } from "../../../../src/ui/state-view"
|
|
|
|
export const dynamic = "force-dynamic"
|
|
|
|
// The token is the credential: it must not leave in a Referer header, and the page is not for search.
|
|
export const metadata: Metadata = { referrer: "no-referrer", robots: { index: false, follow: false } }
|
|
|
|
export default async function Invitation({
|
|
params,
|
|
}: {
|
|
params: Promise<{ org: string; token: string }>
|
|
}): Promise<ReactElement> {
|
|
const { org, token } = await params
|
|
const { i18n, signedIn, user, path } = await pageContext()
|
|
const message = (key: string, values?: Record<string, string | number>): string => i18n.message(key, values)
|
|
|
|
return (
|
|
<StateView title={message("invite.title")}>
|
|
<p>{message("invite.intro", { org })}</p>
|
|
{signedIn ? (
|
|
<>
|
|
{user ? <p>{message("invite.signedInAs", { email: user.email })}</p> : null}
|
|
<InviteAccept
|
|
org={org}
|
|
token={token}
|
|
labels={{
|
|
accept: message("invite.accept"),
|
|
accepted: message("invite.accepted", { org }),
|
|
open: message("invite.open", { org }),
|
|
invalid: message("invite.error.invalid"),
|
|
used: message("invite.error.used"),
|
|
expired: message("invite.error.expired"),
|
|
member: message("invite.error.member"),
|
|
failed: message("invite.error.failed"),
|
|
}}
|
|
/>
|
|
<SignOut label={message("invite.switchAccount")} />
|
|
</>
|
|
) : (
|
|
<>
|
|
<p>{message("invite.signInFirst")}</p>
|
|
<p>
|
|
<a href={signInHref(path)}>{message("app.signIn")}</a>
|
|
</p>
|
|
</>
|
|
)}
|
|
</StateView>
|
|
)
|
|
}
|