sendou.ink/app/features/user-card/actions/user-card.edit.server.ts
Kalle 621fe301f1
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
User card only allow unverified if also verified exists
2026-07-05 18:01:31 +03:00

112 lines
2.9 KiB
TypeScript

import { type ActionFunction, redirect } from "react-router";
import type { HideableUserCardStat } from "~/db/tables";
import { requireUser } from "~/features/auth/core/user.server";
import * as XRankPlacementRepository from "~/features/top-search/XRankPlacementRepository.server";
import { parseFormDataWithImages } from "~/form/parse.server";
import { userPage } from "~/utils/urls";
import * as UserCardRepository from "../UserCardRepository.server";
import { updateUserCardSchema } from "../user-card-schemas";
import { isValidUnverifiedXp } from "../user-card-utils";
export const action: ActionFunction = async ({ request }) => {
const user = requireUser();
const returnTo = safeReturnTo(
new URL(request.url).searchParams.get("returnTo"),
);
const result = await parseFormDataWithImages({
request,
schema: updateUserCardSchema,
});
if (!result.success) {
return { fieldErrors: result.fieldErrors };
}
const data = result.data;
if (data.unverifiedXpPoints) {
const verifiedPeakXp =
await XRankPlacementRepository.peakVerifiedXpByUserId(user.id);
if (
!isValidUnverifiedXp({
unverified: data.unverifiedXpPoints,
verified: verifiedPeakXp,
})
) {
return {
fieldErrors: {
unverifiedXpPoints: "forms:errors.unverifiedXpNotAboveVerified",
},
};
}
}
const isSupporter = Boolean(user.roles?.includes("SUPPORTER"));
await UserCardRepository.updateOwnCard({
shortBio: data.shortBio || null,
...resolveBanner({ ...data, isSupporter }),
unverifiedPeakXP: data.unverifiedXpPoints
? {
overall: data.unverifiedXpPoints,
tentatek:
data.unverifiedXpDivision === "WEST"
? data.unverifiedXpPoints
: null,
takoroka:
data.unverifiedXpDivision === "JPN"
? data.unverifiedXpPoints
: null,
}
: null,
hiddenCardStats: resolveHiddenStats(data),
});
throw redirect(returnTo ?? userPage(user));
};
function safeReturnTo(value: string | null) {
if (!value) return null;
if (!value.startsWith("/") || value.startsWith("//")) return null;
return value;
}
function resolveBanner({
bannerType,
bannerColor,
bannerStageId,
bannerImage,
isSupporter,
}: {
bannerType: "COLOR" | "STAGE" | "URL";
bannerColor: string;
bannerStageId: number;
bannerImage: number | null;
isSupporter: boolean;
}): { bannerPresetImg: string | null; bannerImgId: number | null } {
switch (bannerType) {
case "STAGE":
return { bannerPresetImg: String(bannerStageId), bannerImgId: null };
case "URL":
return {
bannerPresetImg: null,
bannerImgId: isSupporter ? bannerImage : null,
};
default:
return { bannerPresetImg: bannerColor, bannerImgId: null };
}
}
function resolveHiddenStats(data: {
hideXp: boolean;
hideDiv: boolean;
}): Array<HideableUserCardStat> {
return [
data.hideXp ? ("XP" as const) : null,
data.hideDiv ? ("DIV" as const) : null,
].filter((stat) => stat !== null);
}