From b8a4808595e5e851abbf53f744437a365ef56477 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Tue, 31 Mar 2026 11:52:44 +0100 Subject: [PATCH] feat: edit gender for switch miis --- src/app/api/mii/[id]/edit/route.ts | 9 +++-- src/components/countdown.tsx | 2 +- src/components/submit-form/edit-form.tsx | 47 +++++++++++++++++++++++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/app/api/mii/[id]/edit/route.ts b/src/app/api/mii/[id]/edit/route.ts index cb91818..9f51e9d 100644 --- a/src/app/api/mii/[id]/edit/route.ts +++ b/src/app/api/mii/[id]/edit/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import * as Sentry from "@sentry/nextjs"; import { z } from "zod"; -import { Mii, MiiMakeup, Prisma } from "@prisma/client"; +import { Mii, MiiGender, MiiMakeup, Prisma } from "@prisma/client"; import fs from "fs/promises"; import path from "path"; @@ -27,6 +27,7 @@ const editSchema = z.object({ .enum(["true", "false"]) .transform((v) => v === "true") .optional(), + gender: z.enum(MiiGender).optional(), makeup: z.enum(MiiMakeup).optional(), miiPortraitImage: z.union([z.instanceof(File), z.any()]).optional(), miiFeaturesImage: z.union([z.instanceof(File), z.any()]).optional(), @@ -41,7 +42,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); Sentry.setUser({ id: session.user?.id, name: session.user?.name }); - const rateLimit = new RateLimit(request, 3); // no grouped pathname; edit each mii 1 time a minute + const rateLimit = new RateLimit(request, 2); // no grouped pathname; edit each mii 2 times a minute const check = await rateLimit.handle(); if (check) return check; @@ -82,6 +83,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< tags: rawTags, description: formData.get("description") ?? undefined, quarantined: formData.get("quarantined") ?? undefined, + gender: formData.get("gender") ?? undefined, makeup: formData.get("makeup") ?? undefined, miiPortraitImage: formData.get("miiPortraitImage"), miiFeaturesImage: formData.get("miiFeaturesImage"), @@ -92,7 +94,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< }); if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400); - const { name, tags, description, quarantined, makeup, miiPortraitImage, miiFeaturesImage, instructions, image1, image2, image3 } = parsed.data; + const { name, tags, description, quarantined, gender, makeup, miiPortraitImage, miiFeaturesImage, instructions, image1, image2, image3 } = parsed.data; // Validate image files const images: File[] = []; @@ -129,6 +131,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< if (tags !== undefined) updateData.tags = tags.map((t) => profanity.censor(t)); if (description !== undefined) updateData.description = profanity.censor(description); if (quarantined !== undefined) updateData.quarantined = quarantined; + if (mii.platform === "SWITCH" && gender !== undefined) updateData.gender = gender; if (makeup !== undefined) updateData.makeup = makeup; if (instructions !== undefined) updateData.instructions = instructions; if (images.length > 0) updateData.imageCount = images.length; diff --git a/src/components/countdown.tsx b/src/components/countdown.tsx index a6d5caa..a541f57 100644 --- a/src/components/countdown.tsx +++ b/src/components/countdown.tsx @@ -8,7 +8,7 @@ export default function Countdown() { const [minutes, setMinutes] = useState(59); const [seconds, setSeconds] = useState(59); - const targetDate = new Date("2026-04-16T00:00:00Z").getTime(); + const targetDate = new Date("2026-04-16T12:00:00Z").getTime(); useEffect(() => { const interval = setInterval(() => { diff --git a/src/components/submit-form/edit-form.tsx b/src/components/submit-form/edit-form.tsx index 71794e3..7501d11 100644 --- a/src/components/submit-form/edit-form.tsx +++ b/src/components/submit-form/edit-form.tsx @@ -4,7 +4,7 @@ import { redirect } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { FileWithPath } from "react-dropzone"; -import { Mii, MiiMakeup } from "@prisma/client"; +import { Mii, MiiGender, MiiMakeup } from "@prisma/client"; import { useSession } from "next-auth/react"; import { nameSchema, tagsSchema } from "@/lib/schemas"; @@ -65,6 +65,7 @@ export default function EditForm({ mii, likes }: Props) { const [name, setName] = useState(mii.name); const [tags, setTags] = useState(mii.tags); const [description, setDescription] = useState(mii.description); + const [gender, setGender] = useState(mii.gender ?? "MALE"); const [makeup, setMakeup] = useState(mii.makeup ?? "PARTIAL"); const [miiPortraitUri, setMiiPortraitUri] = useState(`/mii/${mii.id}/image?type=mii`); const [miiFeaturesUri, setMiiFeaturesUri] = useState(`/mii/${mii.id}/image?type=features`); @@ -91,6 +92,7 @@ export default function EditForm({ mii, likes }: Props) { if (name != mii.name) formData.append("name", name); if (tags != mii.tags) formData.append("tags", JSON.stringify(tags)); if (description && description != mii.description) formData.append("description", description); + if (gender != mii.gender) formData.append("gender", gender); if (makeup != mii.makeup) formData.append("makeup", makeup); if (miiPortraitUri) formData.append("miiPortraitUri", miiPortraitUri); if (quarantined != mii.quarantined) formData.append("quarantined", JSON.stringify(quarantined)); @@ -266,6 +268,49 @@ export default function EditForm({ mii, likes }: Props) { {/* Makeup/Images/Instructions (Switch only) */} {mii.platform === "SWITCH" && ( <> +
+ +
+ + + + + +
+
+