sendou.ink/app/features/team/team-schemas.ts

119 lines
2.9 KiB
TypeScript

import { z } from "zod";
import {
array,
fieldset,
idConstant,
image,
select,
selectOptional,
stringConstant,
textAreaOptional,
textFieldOptional,
textFieldRequired,
toggle,
} from "~/form/fields";
import { mySlugify } from "~/utils/urls";
import {
CUSTOM_ROLE_MAX_LENGTH,
TEAM,
TEAM_MEMBER_ROLES,
} from "./team-constants";
const teamNameValidate = {
func: (teamName: string) =>
mySlugify(teamName).length > 0 && mySlugify(teamName) !== "new",
message: "forms:errors.noOnlySpecialCharacters",
} as const;
export const createTeamSchema = z.object({
name: textFieldRequired({
label: "labels.name",
minLength: TEAM.NAME_MIN_LENGTH,
maxLength: TEAM.NAME_MAX_LENGTH,
validate: teamNameValidate,
}),
});
export const editTeamFormSchema = z.object({
_action: stringConstant("EDIT"),
name: textFieldRequired({
label: "labels.name",
bottomText: "bottomTexts.name",
minLength: TEAM.NAME_MIN_LENGTH,
maxLength: TEAM.NAME_MAX_LENGTH,
validate: teamNameValidate,
}),
tag: textFieldOptional({
label: "labels.tag",
bottomText: "bottomTexts.tag",
maxLength: TEAM.TAG_MAX_LENGTH,
}),
bsky: textFieldOptional({
label: "labels.teamBsky",
leftAddon: "https://bsky.app/profile/",
maxLength: TEAM.BSKY_MAX_LENGTH,
}),
bio: textAreaOptional({
label: "labels.bio",
maxLength: TEAM.BIO_MAX_LENGTH,
}),
logo: image({ label: "labels.logo" }),
banner: image({ label: "labels.banner", dimensions: "thick-banner" }),
});
/** Sentinel `role` value selected to switch a member to a free-text custom role. Never stored. */
export const CUSTOM_ROLE_VALUE = "CUSTOM";
export const updateRosterSchema = z
.object({
_action: stringConstant("UPDATE_ROSTER"),
members: array({
max: TEAM.MAX_MEMBER_COUNT,
addable: false,
sortable: true,
field: fieldset({
fields: z.object({
userId: idConstant(),
role: selectOptional({
label: "labels.teamMemberRole",
items: [
...TEAM_MEMBER_ROLES.map((role) => ({
value: role,
label: `options.teamMemberRole.${role}` as const,
})),
{
value: CUSTOM_ROLE_VALUE,
label: "options.teamMemberRole.CUSTOM" as const,
},
],
}),
customRole: textFieldOptional({
label: "labels.teamMemberCustomRole",
maxLength: CUSTOM_ROLE_MAX_LENGTH,
}),
roleType: select({
label: "labels.teamMemberRoleType",
items: [
{ value: "PLAYER", label: "options.teamMemberRoleType.PLAYER" },
{ value: "OTHER", label: "options.teamMemberRoleType.OTHER" },
],
}),
isManager: toggle({ label: "labels.teamEditor" }),
}),
}),
}),
})
.superRefine((data, ctx) => {
for (const [index, member] of data.members.entries()) {
const isCustom = member.role === CUSTOM_ROLE_VALUE;
if (isCustom && !member.customRole) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["members", index, "customRole"],
message: "forms:errors.customRoleRequired",
});
}
}
});