src/components/accept-invite-form.tsx
Rama main · réplica de lectura del repositorio original.
"use client";
import { useActionState } from "react";
import { motion } from "framer-motion";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
AuthCard,
AuthError,
GoogleButton,
PasswordField,
SubmitButton,
authGroup,
authRise,
} from "@/components/login-form";
import { acceptInviteAction } from "@/server/actions";
export function AcceptInviteForm({
token,
email,
inviter,
instanceName,
googleReady,
passwordSet,
mustChangePassword,
}: {
token: string;
email: string;
inviter: string;
instanceName: string;
googleReady: boolean;
/** The inviter chose the password, so this page asks only for a name. */
passwordSet: boolean;
/** ...and asked that it be replaced immediately, which is worth warning about. */
mustChangePassword: boolean;
}) {
const [state, formAction] = useActionState(acceptInviteAction, undefined);
return (
<AuthCard
title={`Join ${instanceName}`}
subtitle={
passwordSet
? `${inviter} invited you and set your password. Put your name in and it's yours.`
: googleReady
? `${inviter} invited you. Continue with Google, or pick a password.`
: `${inviter} invited you. Pick a password and you're in.`
}
>
{/* What this actually is.
Somebody arriving here got a link in an email from a friend and has
never heard of the product. The card told them who invited them and
asked for a password, and nothing on the way in ever said what they
were signing up to — which is a strange thing to ask of a person, and
the sort of thing they close the tab over. One sentence, before the
fields, in the words the tour uses on the other side of the door. */}
<motion.p variants={authRise} className="text-muted-foreground -mt-2 mb-5 text-[13px] leading-relaxed">
It keeps a job search in one place: every job you go for, everything you have ever done,
and the resumes you build out of it.
</motion.p>
{/* No token travels with this. The callback finds the outstanding
invitation by the verified email Google hands back, so the button is
the same one as on the sign-in page and cannot accept an invitation
addressed to somebody else. */}
{googleReady && (
<motion.div variants={authRise}>
<GoogleButton label="Continue with Google" />
{/* No token travels with the button, so the callback matches the
invitation by the address Google hands back — which means a
personal Gmail that is not the invited address quietly matches
nothing and lands them on a sign-in screen for an account that
does not exist. Naming the address prevents it instead of
diagnosing it afterwards. */}
<p className="text-faint mt-2 text-center text-[12px]">
Use the Google account for {email}. Another address will not match this invitation.
</p>
<div className="my-5 flex items-center gap-3">
<span className="auth-rule bg-border h-px flex-1" />
<span className="text-faint text-[11px] tracking-wide uppercase">or</span>
<span className="auth-rule bg-border h-px flex-1" />
</div>
</motion.div>
)}
<motion.form variants={authGroup} action={formAction} className="space-y-4">
<input type="hidden" name="token" value={token} />
<motion.div variants={authRise} className="space-y-2">
<Label>Email</Label>
<Input value={email} readOnly disabled className="opacity-70" />
</motion.div>
<motion.div variants={authRise} className="space-y-2">
<Label htmlFor="name">Your name</Label>
<Input id="name" name="name" required autoFocus placeholder="Ada Lovelace" />
</motion.div>
{/* Nothing to ask for when the invitation already carries one. The
field is absent rather than disabled-and-prefilled: a real input
holding a real password would put it in the page source, and this
page is served to anyone holding the link. */}
{passwordSet ? (
<motion.p variants={authRise} className="text-muted-foreground text-[13px] leading-relaxed">
{inviter} already set your password — you'll have been sent it separately, not in the
invitation email.{" "}
{mustChangePassword
? "The next screen asks you to replace it with one only you know."
: "It's yours from here, and you can change it any time from Settings."}
</motion.p>
) : (
<motion.div variants={authRise} className="space-y-2">
<PasswordField
id="password"
label="Choose a password"
autoComplete="new-password"
placeholder="At least 10 characters"
minLength={10}
/>
</motion.div>
)}
<AuthError message={state?.error} />
<motion.div variants={authRise}>
<SubmitButton label="Create my account" pendingLabel="Creating…" />
</motion.div>
</motion.form>
<motion.p variants={authRise} className="text-muted-foreground mt-6 text-center text-xs">
You get your own private space. Nobody else can see your career history, resumes or applications.
</motion.p>
</AuthCard>
);
}