Handle adding org name that already exists gracefully

Closes #3045
This commit is contained in:
Kalle 2026-05-09 12:40:55 +03:00
parent 31710f4f8d
commit 93bcaff5e9
18 changed files with 44 additions and 5 deletions

View File

@ -1,21 +1,26 @@
import { type ActionFunction, redirect } from "react-router";
import { requireUser } from "~/features/auth/core/user.server";
import { parseFormData } from "~/form/parse.server";
import { requireRole } from "~/modules/permissions/guards.server";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import { errorToastIfFalsy } from "~/utils/remix.server";
import { tournamentOrganizationPage } from "~/utils/urls";
import * as TournamentOrganizationRepository from "../TournamentOrganizationRepository.server";
import { TOURNAMENT_ORGANIZATION } from "../tournament-organization-constants";
import { newOrganizationSchema } from "../tournament-organization-schemas";
import { newOrganizationSchemaServer } from "../tournament-organization-schemas.server";
export const action: ActionFunction = async ({ request }) => {
const user = requireUser();
requireRole("TOURNAMENT_ADDER");
const data = await parseRequestPayload({
const result = await parseFormData({
request,
schema: newOrganizationSchema,
schema: newOrganizationSchemaServer,
});
if (!result.success) {
return { fieldErrors: result.fieldErrors };
}
const orgCount =
await TournamentOrganizationRepository.countOrganizationsByUserId(user.id);
@ -25,7 +30,7 @@ export const action: ActionFunction = async ({ request }) => {
);
const org = await TournamentOrganizationRepository.create({
name: data.name,
name: result.data.name,
ownerId: user.id,
});

View File

@ -0,0 +1,18 @@
import { z } from "zod";
import { mySlugify } from "~/utils/urls";
import * as TournamentOrganizationRepository from "./TournamentOrganizationRepository.server";
import { newOrganizationSchema } from "./tournament-organization-schemas";
export const newOrganizationSchemaServer = z.object({
...newOrganizationSchema.shape,
name: newOrganizationSchema.shape.name.refine(
async (name) => {
const existing = await TournamentOrganizationRepository.findBySlug(
mySlugify(name),
);
return !existing;
},
{ message: "forms:errors.duplicateOrgName" },
),
});

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Holdnavnet er taget af et andet hold",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Våbenpulje",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Es gibt bereits ein Team mit diesem Namen",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Waffenpool",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "Contains not allowed characters",
"errors.atLeastOneOption": "At least one option must be selected",
"errors.duplicateName": "There is already a team with this name",
"errors.duplicateOrgName": "There is already an organization with this name",
"errors.noOnlySpecialCharacters": "Name can't be only special characters",
"labels.weaponPool": "Weapon pool",
"placeholders.weaponPoolFull": "Pool full - remove a weapon to add more",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "Contiene caracteres no permitidos",
"errors.atLeastOneOption": "Debe seleccionarse al menos una opción",
"errors.duplicateName": "Ya existe un equipo con ese nombre",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "El nombre no puede ser solo caracteres especiales",
"labels.weaponPool": "Selección de armas",
"placeholders.weaponPoolFull": "Selección llena - elimina un arma para añadir más",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Ya existe un equipo con ese nombre",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Grupo de armas",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Il y a déjà une équipe avec ce nom",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Armes jouées",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Il y a déjà une équipe avec ce nom",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Armes jouées",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "יש כבר צוות בשם הזה",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "מאגר נשקים",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Esiste già un team con questo nome",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Pool armi",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "そのチーム名はすでに使用されています",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "使用ブキ",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Istnieje już drużyna o tym imieniu",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Pula broni",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Já existe um time com esse nome",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Seleção de armas",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "Уже существует команда с таким названием",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "Используемое оружие",
"placeholders.weaponPoolFull": "",

View File

@ -31,6 +31,7 @@
"errors.notAllowedCharacters": "",
"errors.atLeastOneOption": "",
"errors.duplicateName": "该队名已被使用",
"errors.duplicateOrgName": "",
"errors.noOnlySpecialCharacters": "",
"labels.weaponPool": "武器池",
"placeholders.weaponPoolFull": "",