diff --git a/app/features/user-page/routes/u.$identifier.edit-widgets.module.css b/app/features/user-page/routes/u.$identifier.edit-widgets.module.css index 67369eb30..6d5dea18c 100644 --- a/app/features/user-page/routes/u.$identifier.edit-widgets.module.css +++ b/app/features/user-page/routes/u.$identifier.edit-widgets.module.css @@ -22,6 +22,8 @@ .actions { display: flex; + flex-direction: column; + align-items: flex-end; gap: var(--s-2); } diff --git a/app/features/user-page/routes/u.$identifier.edit-widgets.tsx b/app/features/user-page/routes/u.$identifier.edit-widgets.tsx index c67f2a819..8839f4eee 100644 --- a/app/features/user-page/routes/u.$identifier.edit-widgets.tsx +++ b/app/features/user-page/routes/u.$identifier.edit-widgets.tsx @@ -22,6 +22,7 @@ import { Link, useFetcher, useLoaderData, useMatches } from "react-router"; import * as R from "remeda"; import * as v from "valibot"; import { SendouButton } from "~/components/elements/Button"; +import { FormMessage } from "~/components/FormMessage"; import { Image } from "~/components/Image"; import { Input } from "~/components/Input"; import { MainSlotIcon } from "~/components/icons/MainSlot"; @@ -69,6 +70,7 @@ export default function EditWidgetsPage() { Array >(data.currentWidgets); const [expandedWidgetId, setExpandedWidgetId] = useState(null); + const [emptySaveAttempted, setEmptySaveAttempted] = useState(false); const hasUnsavedChangesRef = useRef< Parameters[0]["current"] @@ -144,6 +146,11 @@ export default function EditWidgetsPage() { }; const handleSubmit = () => { + if (selectedWidgets.length === 0) { + setEmptySaveAttempted(true); + return; + } + const invalidWidgetIds = computeInvalidWidgetIds(selectedWidgets); const firstInvalid = selectedWidgets.find((w) => invalidWidgetIds.has(w.id), @@ -198,6 +205,11 @@ export default function EditWidgetsPage() { {t("common:actions.save")} + {emptySaveAttempted && selectedWidgets.length === 0 ? ( + + {t("user:widgets.emptyError")} + + ) : null} diff --git a/app/features/user-page/user-page-schemas.test.ts b/app/features/user-page/user-page-schemas.test.ts index 202657640..295a2c298 100644 --- a/app/features/user-page/user-page-schemas.test.ts +++ b/app/features/user-page/user-page-schemas.test.ts @@ -11,4 +11,12 @@ describe("widgetsEditSchema", () => { expect(result.success).toBe(true); }); + + test("rejects an empty widget list", () => { + const result = v.safeParse(widgetsEditSchema(false), { + widgets: JSON.stringify([]), + }); + + expect(result.success).toBe(false); + }); }); diff --git a/app/features/user-page/user-page-schemas.ts b/app/features/user-page/user-page-schemas.ts index 82feb1209..a81d1db42 100644 --- a/app/features/user-page/user-page-schemas.ts +++ b/app/features/user-page/user-page-schemas.ts @@ -177,6 +177,7 @@ export const widgetsEditSchema = (isSupporter: boolean) => { safeJSONParse, v.pipe( v.array(widgetSettingsSchema), + v.minLength(1), v.maxLength(max.main + max.side), v.check((widgets) => { let mainCount = 0; diff --git a/locales/da/user.json b/locales/da/user.json index e75deb222..e7a506c17 100644 --- a/locales/da/user.json +++ b/locales/da/user.json @@ -74,6 +74,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/de/user.json b/locales/de/user.json index 7a0393b8f..973cc9530 100644 --- a/locales/de/user.json +++ b/locales/de/user.json @@ -74,6 +74,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/en/user.json b/locales/en/user.json index a115ce49a..075378cb7 100644 --- a/locales/en/user.json +++ b/locales/en/user.json @@ -74,6 +74,7 @@ "widgets.add": "Add", "widgets.remove": "Remove", "widgets.maxReached": "Maximum widgets reached", + "widgets.emptyError": "Your profile needs at least one widget", "widgets.search": "Search widgets...", "widgets.category.misc": "Miscellaneous", "widgets.category.trophies": "Trophies", diff --git a/locales/es-ES/user.json b/locales/es-ES/user.json index bc2b64570..13060eca6 100644 --- a/locales/es-ES/user.json +++ b/locales/es-ES/user.json @@ -75,6 +75,7 @@ "widgets.add": "Añadir", "widgets.remove": "Quitar", "widgets.maxReached": "Límite máximo de widgets alcanzado", + "widgets.emptyError": "", "widgets.search": "Buscar widgets...", "widgets.category.misc": "Miscelánea", "widgets.category.trophies": "Trofeos", diff --git a/locales/es-US/user.json b/locales/es-US/user.json index e33bdd32f..56dce33e0 100644 --- a/locales/es-US/user.json +++ b/locales/es-US/user.json @@ -75,6 +75,7 @@ "widgets.add": "Añadir", "widgets.remove": "Quitar", "widgets.maxReached": "Límite máximo de widgets alcanzado", + "widgets.emptyError": "", "widgets.search": "Buscar widgets...", "widgets.category.misc": "Miscelánea", "widgets.category.trophies": "Trofeos", diff --git a/locales/fr-CA/user.json b/locales/fr-CA/user.json index 874b18d9e..bd76f5e79 100644 --- a/locales/fr-CA/user.json +++ b/locales/fr-CA/user.json @@ -75,6 +75,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/fr-EU/user.json b/locales/fr-EU/user.json index dc6e154e2..5ed021ba4 100644 --- a/locales/fr-EU/user.json +++ b/locales/fr-EU/user.json @@ -75,6 +75,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/he/user.json b/locales/he/user.json index 94c63e5fd..19c2294f8 100644 --- a/locales/he/user.json +++ b/locales/he/user.json @@ -75,6 +75,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/it/user.json b/locales/it/user.json index efe9d5e9b..aef395197 100644 --- a/locales/it/user.json +++ b/locales/it/user.json @@ -75,6 +75,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/ja/user.json b/locales/ja/user.json index a84d1fe9b..f0029e268 100644 --- a/locales/ja/user.json +++ b/locales/ja/user.json @@ -72,6 +72,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/ko/user.json b/locales/ko/user.json index 4499f0926..cc6b1c8ef 100644 --- a/locales/ko/user.json +++ b/locales/ko/user.json @@ -72,6 +72,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/nl/user.json b/locales/nl/user.json index c287b4808..49ebcfb01 100644 --- a/locales/nl/user.json +++ b/locales/nl/user.json @@ -74,6 +74,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/pl/user.json b/locales/pl/user.json index eee6cfa71..deebfb7c8 100644 --- a/locales/pl/user.json +++ b/locales/pl/user.json @@ -76,6 +76,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/pt-BR/user.json b/locales/pt-BR/user.json index 02ed6dcba..3a47774c4 100644 --- a/locales/pt-BR/user.json +++ b/locales/pt-BR/user.json @@ -75,6 +75,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/ru/user.json b/locales/ru/user.json index e567f77f5..e55a2a510 100644 --- a/locales/ru/user.json +++ b/locales/ru/user.json @@ -76,6 +76,7 @@ "widgets.add": "", "widgets.remove": "", "widgets.maxReached": "", + "widgets.emptyError": "", "widgets.search": "", "widgets.category.misc": "", "widgets.category.trophies": "", diff --git a/locales/zh/user.json b/locales/zh/user.json index da07a02fa..81c27fd5b 100644 --- a/locales/zh/user.json +++ b/locales/zh/user.json @@ -72,6 +72,7 @@ "widgets.add": "添加", "widgets.remove": "移除", "widgets.maxReached": "已达到小组件上限", + "widgets.emptyError": "", "widgets.search": "搜索小组件...", "widgets.category.misc": "其他", "widgets.category.trophies": "",