Error if saving a profile without widgets

This commit is contained in:
Kalle
2026-09-06 11:39:02 +03:00
parent ddce861703
commit 5bdbeb644a
20 changed files with 39 additions and 0 deletions

View File

@@ -22,6 +22,8 @@
.actions {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: var(--s-2);
}

View File

@@ -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<Tables["UserWidget"]["widget"]>
>(data.currentWidgets);
const [expandedWidgetId, setExpandedWidgetId] = useState<string | null>(null);
const [emptySaveAttempted, setEmptySaveAttempted] = useState(false);
const hasUnsavedChangesRef = useRef<
Parameters<typeof useUnsavedChangesChecker>[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() {
<SendouButton onClick={handleSubmit}>
{t("common:actions.save")}
</SendouButton>
{emptySaveAttempted && selectedWidgets.length === 0 ? (
<FormMessage type="error" spaced={false}>
{t("user:widgets.emptyError")}
</FormMessage>
) : null}
</div>
</header>

View File

@@ -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);
});
});

View File

@@ -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;

View File

@@ -74,6 +74,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -74,6 +74,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -75,6 +75,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -75,6 +75,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -75,6 +75,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -75,6 +75,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -72,6 +72,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -72,6 +72,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -74,6 +74,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -76,6 +76,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -75,6 +75,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -76,6 +76,7 @@
"widgets.add": "",
"widgets.remove": "",
"widgets.maxReached": "",
"widgets.emptyError": "",
"widgets.search": "",
"widgets.category.misc": "",
"widgets.category.trophies": "",

View File

@@ -72,6 +72,7 @@
"widgets.add": "添加",
"widgets.remove": "移除",
"widgets.maxReached": "已达到小组件上限",
"widgets.emptyError": "",
"widgets.search": "搜索小组件...",
"widgets.category.misc": "其他",
"widgets.category.trophies": "",