apps/web: Next.js 16 (App Router), Tailwind 4 and shadcn/ui, light and dark from the system. Pages render on the server and call the API with Eden Treaty (typed by @t5de/api), forwarding the visitor's cookie; forms use server actions; sign-up, sign-in and sign-out use Better Auth's React client. /api is rewritten to the API so its cookies are first-party. Who's online follows the API's event stream (Next's gzip is off: it held the stream back). Standalone output with a Dockerfile; deploys to Vercel as is. Co-Authored-By: Claude Opus 5.5 <[email protected]>
61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
"use server";
|
|
import { revalidatePath } from "next/cache";
|
|
import { api, unwrap } from "@/lib/api";
|
|
import { attempt, field, type ActionState } from "@/lib/actions";
|
|
|
|
const done = () => revalidatePath("/account");
|
|
|
|
/** Starts linking an IMVU account: the API makes a code to put in its tagline. */
|
|
export async function linkAccount(_: ActionState, form: FormData): Promise<ActionState> {
|
|
return attempt(async () => {
|
|
unwrap(await api.account.links.post({ username: field(form, "username") }));
|
|
done();
|
|
});
|
|
}
|
|
|
|
/** A pending link: check the tagline for the code, or cancel. */
|
|
export async function challengeAction(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const imvu = field(form, "imvu");
|
|
return attempt(async () => {
|
|
if (field(form, "intent") === "cancel") {
|
|
unwrap(await api.account.challenges({ imvu }).delete());
|
|
} else {
|
|
unwrap(await api.account.links({ imvu }).check.post());
|
|
}
|
|
done();
|
|
});
|
|
}
|
|
|
|
/** A linked account: check its age status with IMVU again, or unlink it. */
|
|
export async function linkAction(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const imvu = field(form, "imvu");
|
|
return attempt(async () => {
|
|
if (field(form, "intent") === "unlink") {
|
|
unwrap(await api.account.links({ imvu }).delete());
|
|
done();
|
|
return;
|
|
}
|
|
unwrap(await api.account.links({ imvu }).recheck.post());
|
|
done();
|
|
return "Checked with IMVU just now.";
|
|
});
|
|
}
|
|
|
|
export async function saveSettings(
|
|
imvu: number,
|
|
settings: { showBadge?: boolean; showPresence?: boolean },
|
|
): Promise<ActionState> {
|
|
return attempt(async () => {
|
|
unwrap(await api.account.links({ imvu }).patch(settings));
|
|
done();
|
|
});
|
|
}
|
|
|
|
export async function signOutComputers(_: ActionState, _form: FormData): Promise<ActionState> {
|
|
return attempt(async () => {
|
|
unwrap(await api.account["signout-computers"].post());
|
|
done();
|
|
return "Signed out everywhere.";
|
|
});
|
|
}
|