shoc-frontend-new/src/app/invite/index.tsx

61 lines
2 KiB
TypeScript

import { useState } from "react";
import { Link, useLocation } from "react-router";
import { Box, CircularProgress } from "@mui/material";
import { ChevronLeft } from "lucide-react";
import { AuthBrandLogo } from "@/app/(auth)/_components/auth-brand-logo";
import { AuthCardHeader } from "@/app/(auth)/_components/auth-card-header";
import { AuthPageShell } from "@/app/(auth)/_components/auth-page-shell";
import { INVALID_INVITE_MESSAGE } from "@/domain/team-member-invite/types/team-member-invite";
import { useTeamMemberInviteDetails } from "@/domain/team-member-invite/use-cases/use-team-member-invite";
import { InviteWizard } from "./_components/invite-wizard";
/** The invite email links to `/invite#<token>`; a fragment never reaches a server or a Referer. */
function readInviteToken(hash: string): string {
return hash.replace(/^#/, "").trim();
}
function InviteBody({ token }: { token: string }) {
const details = useTeamMemberInviteDetails(token);
const [inviteEnded, setInviteEnded] = useState(false);
if (token.length > 0 && details.isPending) {
return (
<Box className="flex justify-center py-8" role="status" aria-label="Loading invite">
<CircularProgress />
</Box>
);
}
if (inviteEnded || details.data === undefined) {
return <AuthCardHeader title="Invite unavailable" subtitle={INVALID_INVITE_MESSAGE} />;
}
return (
<InviteWizard
token={token}
details={details.data}
onInviteInvalid={() => setInviteEnded(true)}
/>
);
}
export default function InviteRegistrationPage() {
const { hash } = useLocation();
const token = readInviteToken(hash);
return (
<AuthPageShell>
<AuthBrandLogo className="mb-8" />
<Link
to="/login"
className="mb-6 inline-flex items-center gap-1 text-(length:--text-xs) text-(--color-text-muted) no-underline hover:text-foreground"
>
<ChevronLeft size={13} aria-hidden="true" />
Back to login
</Link>
<InviteBody token={token} />
</AuthPageShell>
);
}