127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
/**
|
|
* Renders the page a digest's unsubscribe link opens, where the reader turns
|
|
* that digest off with one click, signed in or not.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { useState } from "react";
|
|
import { Link, useSearchParams } from "react-router";
|
|
import { t } from "@lingui/core/macro";
|
|
import { Trans } from "@lingui/react/macro";
|
|
// A label shared with the emails is named by its id, its words being in
|
|
// locales/ only; the macro's Trans takes the English in place.
|
|
import { Trans as Message } from "@lingui/react";
|
|
import { FormProvider } from "react-hook-form";
|
|
|
|
import { API_URL } from "../config/api.ts";
|
|
import { useDocumentTitle } from "../hooks/shared/useDocumentTitle.ts";
|
|
import { useAuth } from "../hooks/auth/useAuth.ts";
|
|
import { useApiForm } from "../hooks/form/useApiForm.ts";
|
|
import { PageShell } from "../components/layout/PageShell.tsx";
|
|
import { FormError } from "../components/form/FormError.tsx";
|
|
import { SubmitButton } from "../components/form/SubmitButton.tsx";
|
|
import { expectOk } from "../utils/shared/api.ts";
|
|
import { readUnsubscribeCadence } from "../utils/auth/unsubscribe.ts";
|
|
|
|
/**
|
|
* Renders `/unsubscribe?token=`, for anyone holding a digest's link. It asks
|
|
* before turning the digest off, since mail scanners open every link in an
|
|
* email, then posts the token to `POST /api/users/unsubscribe`.
|
|
*/
|
|
export function Unsubscribe(): React.JSX.Element {
|
|
useDocumentTitle(t`Unsubscribe`);
|
|
const [params] = useSearchParams();
|
|
const { user } = useAuth();
|
|
const token = params.get("token") ?? "";
|
|
const cadence = readUnsubscribeCadence(token);
|
|
const [done, setDone] = useState(false);
|
|
const form = useApiForm<Record<string, never>>({ defaultValues: {} });
|
|
|
|
const onSubmit = form.submit(async () => {
|
|
await expectOk(
|
|
await fetch(
|
|
`${API_URL}/api/users/unsubscribe?token=${encodeURIComponent(token)}`,
|
|
{ method: "POST" },
|
|
),
|
|
);
|
|
setDone(true);
|
|
});
|
|
|
|
const settingsLink = user && (
|
|
<p className="auth-card-footer">
|
|
<Link to={`/users/${user.username}/~/settings`}>
|
|
<Trans>Your settings</Trans>
|
|
</Link>
|
|
</p>
|
|
);
|
|
|
|
if (!cadence) {
|
|
return (
|
|
<PageShell centered>
|
|
<div className="auth-card">
|
|
<h1 className="auth-card-title">
|
|
<Trans>Invalid link</Trans>
|
|
</h1>
|
|
<p>
|
|
<Trans>This unsubscribe link is missing or malformed.</Trans>
|
|
</p>
|
|
{settingsLink}
|
|
</div>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
const weekly = cadence === "weekly";
|
|
if (done) {
|
|
return (
|
|
<PageShell centered>
|
|
<div className="auth-card">
|
|
<h1 className="auth-card-title">
|
|
<Trans>Unsubscribed</Trans>
|
|
</h1>
|
|
<p>
|
|
{weekly
|
|
? (
|
|
<Trans>
|
|
You won't get the weekly digest any more. You can turn it back
|
|
on in your settings.
|
|
</Trans>
|
|
)
|
|
: (
|
|
<Trans>
|
|
You won't get the monthly recap any more. You can turn it back
|
|
on in your settings.
|
|
</Trans>
|
|
)}
|
|
</p>
|
|
{settingsLink}
|
|
</div>
|
|
</PageShell>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<PageShell centered>
|
|
<div className="auth-card">
|
|
<h1 className="auth-card-title">
|
|
{weekly
|
|
? <Trans>Turn off the weekly digest?</Trans>
|
|
: <Trans>Turn off the monthly recap?</Trans>}
|
|
</h1>
|
|
<FormProvider {...form}>
|
|
<form onSubmit={onSubmit} className="form">
|
|
<FormError title={t`Couldn't unsubscribe`} />
|
|
<SubmitButton pendingLabel={<Trans>Turning off…</Trans>}>
|
|
{weekly
|
|
? <Message id="digest.weekly.off" />
|
|
: <Message id="digest.monthly.off" />}
|
|
</SubmitButton>
|
|
</form>
|
|
</FormProvider>
|
|
{settingsLink}
|
|
</div>
|
|
</PageShell>
|
|
);
|
|
}
|