"use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { api, ApiError, 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 { 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 { 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 { 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; listRooms?: boolean }, ): Promise { return attempt(async () => { unwrap(await api.account.links({ imvu }).patch(settings)); done(); }); } export async function signOutComputers(_: ActionState, _form: FormData): Promise { return attempt(async () => { unwrap(await api.account["signout-computers"].post()); done(); return "Signed out everywhere."; }); } /** Starts an age check with a provider that checks the person, and sends them to its page. */ export async function startAgeCheck(_: ActionState, form: FormData): Promise { let url: string | undefined; const state = await attempt(async () => { const started = unwrap(await api.age({ provider: field(form, "provider") }).start.post({ returnURL: "/account?age=back" })); if (started.kind === "redirect") url = started.url; done(); }); if (url) redirect(url); return state; } /** What making a token answers: the token itself, shown once. */ export type TokenState = { error?: string; token?: string; name?: string } | null; /** Makes an API token. `grants` is JSON: [{permission, scope}]. */ export async function createToken(_: TokenState, form: FormData): Promise { let grants: { permission: string; scope: { type: "hub" } | { type: "catalog" | "product"; id: string } }[]; try { grants = JSON.parse(field(form, "grants") || "[]"); } catch { return { error: "Choose what it may do." }; } if (!Array.isArray(grants) || grants.length === 0) return { error: "Choose what it may do." }; try { const made = unwrap(await api.account.tokens.post({ name: field(form, "name"), days: Number(field(form, "days")), grants })); done(); return { token: made.token, name: made.info.name }; } catch (e) { if (e instanceof ApiError) return { error: e.message }; throw e; } } /** Revokes an API token. */ export async function revokeToken(_: ActionState, form: FormData): Promise { return attempt(async () => { unwrap(await api.account.tokens({ id: field(form, "id") }).delete()); done(); return "Revoked."; }); }