122 lines
3.9 KiB
JavaScript
122 lines
3.9 KiB
JavaScript
import { ArrowRight } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import { acceptInvite, previewInvite } from "../api";
|
|
import AuthLayout from "./AuthLayout";
|
|
import PasswordField from "./PasswordField";
|
|
|
|
function inviteTokenFromPath() {
|
|
const match = window.location.pathname.match(/^\/invite\/([^/]+)/);
|
|
return match ? decodeURIComponent(match[1]) : "";
|
|
}
|
|
|
|
export default function InviteAcceptPage() {
|
|
const token = inviteTokenFromPath();
|
|
const [invite, setInvite] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [loadError, setLoadError] = useState("");
|
|
const [status, setStatus] = useState({ loading: false, error: "" });
|
|
|
|
useEffect(() => {
|
|
if (!token) {
|
|
setLoadError("This invitation link is invalid.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
previewInvite(token)
|
|
.then((data) => setInvite(data))
|
|
.catch((error) => setLoadError(error.message))
|
|
.finally(() => setLoading(false));
|
|
}, [token]);
|
|
|
|
async function handleSubmit(event) {
|
|
event.preventDefault();
|
|
setStatus({ loading: true, error: "" });
|
|
const form = new FormData(event.currentTarget);
|
|
const password = form.get("password");
|
|
const confirm = form.get("confirm_password");
|
|
if (password !== confirm) {
|
|
setStatus({ loading: false, error: "Passwords do not match." });
|
|
return;
|
|
}
|
|
try {
|
|
const result = await acceptInvite(token, { password });
|
|
sessionStorage.setItem("agmail_access_token", result.access_token);
|
|
sessionStorage.setItem("agmail_account", JSON.stringify(result.account));
|
|
window.location.assign("/app");
|
|
} catch (error) {
|
|
setStatus({ loading: false, error: error.message });
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<AuthLayout
|
|
eyebrow="Invitation"
|
|
title={<>Setting up<br /><em>your account.</em></>}
|
|
description="Verifying your invitation link…"
|
|
>
|
|
<p>Loading invitation…</p>
|
|
</AuthLayout>
|
|
);
|
|
}
|
|
|
|
if (loadError || !invite) {
|
|
return (
|
|
<AuthLayout
|
|
eyebrow="Invitation"
|
|
title={<>This link<br /><em>is not valid.</em></>}
|
|
description="The invitation may have expired or already been used."
|
|
>
|
|
<p className="form-error" role="alert">{loadError || "Invitation not found."}</p>
|
|
<p className="auth-switch"><a href="/login">Go to sign in</a></p>
|
|
</AuthLayout>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<AuthLayout
|
|
eyebrow="Invitation"
|
|
title={<>Join <em>{invite.organization}</em></>}
|
|
description={`${invite.invited_by} invited you to set up your work email.`}
|
|
>
|
|
<>
|
|
<div className="card-heading">
|
|
<h2>Set up your account</h2>
|
|
<p>
|
|
Your work email is <strong>{invite.work_email}</strong>.
|
|
Choose a password to activate your inbox.
|
|
</p>
|
|
</div>
|
|
<form className="auth-form" onSubmit={handleSubmit}>
|
|
<label>
|
|
Password
|
|
<PasswordField
|
|
name="password"
|
|
placeholder="Create a password"
|
|
required
|
|
minLength={8}
|
|
autoComplete="new-password"
|
|
/>
|
|
</label>
|
|
<label>
|
|
Confirm password
|
|
<PasswordField
|
|
name="confirm_password"
|
|
placeholder="Repeat your password"
|
|
required
|
|
minLength={8}
|
|
autoComplete="new-password"
|
|
/>
|
|
</label>
|
|
<p className="auth-switch" style={{ marginTop: 0 }}>
|
|
Must include uppercase, lowercase, and a number.
|
|
</p>
|
|
{status.error && <p className="form-error" role="alert">{status.error}</p>}
|
|
<button className="button button-coral auth-submit" disabled={status.loading}>
|
|
{status.loading ? "Activating…" : "Set up your account"} <ArrowRight size={16} />
|
|
</button>
|
|
</form>
|
|
</>
|
|
</AuthLayout>
|
|
);
|
|
}
|