From 93bcaff5e9fb6efb0f193cbba8c3fdb5d0809fed Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 9 May 2026 12:40:55 +0300 Subject: [PATCH] Handle adding org name that already exists gracefully Closes #3045 --- .../actions/org.new.server.ts | 15 ++++++++++----- .../tournament-organization-schemas.server.ts | 18 ++++++++++++++++++ locales/da/forms.json | 1 + locales/de/forms.json | 1 + locales/en/forms.json | 1 + locales/es-ES/forms.json | 1 + locales/es-US/forms.json | 1 + locales/fr-CA/forms.json | 1 + locales/fr-EU/forms.json | 1 + locales/he/forms.json | 1 + locales/it/forms.json | 1 + locales/ja/forms.json | 1 + locales/ko/forms.json | 1 + locales/nl/forms.json | 1 + locales/pl/forms.json | 1 + locales/pt-BR/forms.json | 1 + locales/ru/forms.json | 1 + locales/zh/forms.json | 1 + 18 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 app/features/tournament-organization/tournament-organization-schemas.server.ts diff --git a/app/features/tournament-organization/actions/org.new.server.ts b/app/features/tournament-organization/actions/org.new.server.ts index 7b10740bb..8c9b1fcfe 100644 --- a/app/features/tournament-organization/actions/org.new.server.ts +++ b/app/features/tournament-organization/actions/org.new.server.ts @@ -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, }); diff --git a/app/features/tournament-organization/tournament-organization-schemas.server.ts b/app/features/tournament-organization/tournament-organization-schemas.server.ts new file mode 100644 index 000000000..d0f5b11a7 --- /dev/null +++ b/app/features/tournament-organization/tournament-organization-schemas.server.ts @@ -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" }, + ), +}); diff --git a/locales/da/forms.json b/locales/da/forms.json index 5cfe6d1c1..28150d066 100644 --- a/locales/da/forms.json +++ b/locales/da/forms.json @@ -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": "", diff --git a/locales/de/forms.json b/locales/de/forms.json index bec561d9e..9576f3f2f 100644 --- a/locales/de/forms.json +++ b/locales/de/forms.json @@ -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": "", diff --git a/locales/en/forms.json b/locales/en/forms.json index e1186c98a..481edee16 100644 --- a/locales/en/forms.json +++ b/locales/en/forms.json @@ -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", diff --git a/locales/es-ES/forms.json b/locales/es-ES/forms.json index 5738fd04e..a6adbc92d 100644 --- a/locales/es-ES/forms.json +++ b/locales/es-ES/forms.json @@ -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", diff --git a/locales/es-US/forms.json b/locales/es-US/forms.json index 457e0aa0c..6b8f24867 100644 --- a/locales/es-US/forms.json +++ b/locales/es-US/forms.json @@ -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": "", diff --git a/locales/fr-CA/forms.json b/locales/fr-CA/forms.json index e7cb063b4..e1f3f760c 100644 --- a/locales/fr-CA/forms.json +++ b/locales/fr-CA/forms.json @@ -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": "", diff --git a/locales/fr-EU/forms.json b/locales/fr-EU/forms.json index 6796beb32..827686623 100644 --- a/locales/fr-EU/forms.json +++ b/locales/fr-EU/forms.json @@ -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": "", diff --git a/locales/he/forms.json b/locales/he/forms.json index 65896bad0..e66526d44 100644 --- a/locales/he/forms.json +++ b/locales/he/forms.json @@ -31,6 +31,7 @@ "errors.notAllowedCharacters": "", "errors.atLeastOneOption": "", "errors.duplicateName": "יש כבר צוות בשם הזה", + "errors.duplicateOrgName": "", "errors.noOnlySpecialCharacters": "", "labels.weaponPool": "מאגר נשקים", "placeholders.weaponPoolFull": "", diff --git a/locales/it/forms.json b/locales/it/forms.json index f77d1f390..279316f92 100644 --- a/locales/it/forms.json +++ b/locales/it/forms.json @@ -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": "", diff --git a/locales/ja/forms.json b/locales/ja/forms.json index 78e493d6f..f9b9ff1cb 100644 --- a/locales/ja/forms.json +++ b/locales/ja/forms.json @@ -31,6 +31,7 @@ "errors.notAllowedCharacters": "", "errors.atLeastOneOption": "", "errors.duplicateName": "そのチーム名はすでに使用されています", + "errors.duplicateOrgName": "", "errors.noOnlySpecialCharacters": "", "labels.weaponPool": "使用ブキ", "placeholders.weaponPoolFull": "", diff --git a/locales/ko/forms.json b/locales/ko/forms.json index 3b3419e77..5a70e3771 100644 --- a/locales/ko/forms.json +++ b/locales/ko/forms.json @@ -31,6 +31,7 @@ "errors.notAllowedCharacters": "", "errors.atLeastOneOption": "", "errors.duplicateName": "", + "errors.duplicateOrgName": "", "errors.noOnlySpecialCharacters": "", "labels.weaponPool": "", "placeholders.weaponPoolFull": "", diff --git a/locales/nl/forms.json b/locales/nl/forms.json index 095174289..37239bb98 100644 --- a/locales/nl/forms.json +++ b/locales/nl/forms.json @@ -31,6 +31,7 @@ "errors.notAllowedCharacters": "", "errors.atLeastOneOption": "", "errors.duplicateName": "", + "errors.duplicateOrgName": "", "errors.noOnlySpecialCharacters": "", "labels.weaponPool": "", "placeholders.weaponPoolFull": "", diff --git a/locales/pl/forms.json b/locales/pl/forms.json index d6d7c5ce5..cc60da373 100644 --- a/locales/pl/forms.json +++ b/locales/pl/forms.json @@ -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": "", diff --git a/locales/pt-BR/forms.json b/locales/pt-BR/forms.json index 8d3957031..aefb1b092 100644 --- a/locales/pt-BR/forms.json +++ b/locales/pt-BR/forms.json @@ -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": "", diff --git a/locales/ru/forms.json b/locales/ru/forms.json index 98c0040b3..bb93cffa3 100644 --- a/locales/ru/forms.json +++ b/locales/ru/forms.json @@ -31,6 +31,7 @@ "errors.notAllowedCharacters": "", "errors.atLeastOneOption": "", "errors.duplicateName": "Уже существует команда с таким названием", + "errors.duplicateOrgName": "", "errors.noOnlySpecialCharacters": "", "labels.weaponPool": "Используемое оружие", "placeholders.weaponPoolFull": "", diff --git a/locales/zh/forms.json b/locales/zh/forms.json index 1705f1dd7..dea5f6563 100644 --- a/locales/zh/forms.json +++ b/locales/zh/forms.json @@ -31,6 +31,7 @@ "errors.notAllowedCharacters": "", "errors.atLeastOneOption": "", "errors.duplicateName": "该队名已被使用", + "errors.duplicateOrgName": "", "errors.noOnlySpecialCharacters": "", "labels.weaponPool": "武器池", "placeholders.weaponPoolFull": "",