fix: bunch of fixes

This commit is contained in:
trafficlunar 2026-03-25 21:07:28 +00:00
parent 74139dd54e
commit 86c655d7d0
10 changed files with 251 additions and 225 deletions

View file

@ -100,12 +100,7 @@ export async function POST(request: NextRequest) {
for (const key in object) {
const value = object[key as keyof SwitchMiiInstructions];
if (value === null || value === undefined) {
delete object[key as keyof SwitchMiiInstructions];
continue;
}
if (DEFAULT_ZERO_FIELDS.has(key) && value === 0) {
if (!value || (DEFAULT_ZERO_FIELDS.has(key) && value === 0)) {
delete object[key as keyof SwitchMiiInstructions];
continue;
}

View file

@ -16,16 +16,15 @@ export default function DatingPreferencesViewer({ data, onChecked }: Props) {
const genderEnum = gender.toUpperCase() as MiiGender;
return (
<div className="flex gap-1.5">
<div key={gender} className="flex gap-1.5">
<input
key={gender}
type="checkbox"
id={gender}
className="checkbox"
checked={data.includes(genderEnum)}
onChange={(e) => {
if (onChecked) onChecked(e, genderEnum);
}}
{...(typeof window !== "undefined" && onChecked
? { onChange: (e: ChangeEvent<HTMLInputElement>) => onChecked(e, genderEnum) }
: { readOnly: true })}
/>
<label htmlFor={gender} className="text-sm select-none">
{gender}

View file

@ -76,7 +76,7 @@ function TableCell({ label, children }: TableCellProps) {
}
function Section({ name, instructions, children, isSubSection }: SectionProps) {
if (typeof instructions !== "object") return null;
if (typeof instructions !== "object" || !instructions) return null;
const type = "type" in instructions ? instructions.type : undefined;
const color = "color" in instructions ? instructions.color : undefined;
@ -87,7 +87,7 @@ function Section({ name, instructions, children, isSubSection }: SectionProps) {
const stretch = "stretch" in instructions ? instructions.stretch : undefined;
return (
<div className={`p-3 ${isSubSection ? "mt-2" : "border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5"}`}>
<div className={`p-3 ${isSubSection ? "not-first:mt-2 pt-0!" : "border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5"}`}>
<h3 className="font-semibold text-xl text-amber-800 mb-1">{name}</h3>
<table className="w-full">
@ -154,41 +154,13 @@ export default function MiiInstructions({ instructions }: Props) {
{eyebrows && <Section name="Eyebrows" instructions={eyebrows}></Section>}
{eyes && (
<Section name="Eyes" instructions={eyes}>
{eyes.eyesType && (
<TableCell label="Eyes Type">
<GridPosition index={eyes.eyesType} />
</TableCell>
)}
{eyes.eyelashesTop && (
<TableCell label="Eyelashes Top Type">
<GridPosition index={eyes.eyelashesTop} />
</TableCell>
)}
{eyes.eyelashesBottom && (
<TableCell label="Eyelashes Bottom Type">
<GridPosition index={eyes.eyelashesBottom} />
</TableCell>
)}
{eyes.eyelidTop && (
<TableCell label="Eyelid Top Type">
<GridPosition index={eyes.eyelidTop} />
</TableCell>
)}
{eyes.eyelidBottom && (
<TableCell label="Eyelid Bottom Type">
<GridPosition index={eyes.eyelidBottom} />
</TableCell>
)}
{eyes.eyeliner && (
<TableCell label="Eyeliner Type">
<GridPosition index={eyes.eyeliner} />
</TableCell>
)}
{eyes.pupil && (
<TableCell label="Pupil Type">
<GridPosition index={eyes.pupil} />
</TableCell>
)}
<Section isSubSection name="Main" instructions={eyes.main} />
<Section isSubSection name="Eyelashes Top" instructions={eyes.eyelashesTop} />
<Section isSubSection name="Eyelashes Bottom" instructions={eyes.eyelashesBottom} />
<Section isSubSection name="Eyelid Top" instructions={eyes.eyelidTop} />
<Section isSubSection name="Eyelid Bottom" instructions={eyes.eyelidBottom} />
<Section isSubSection name="Eyeliner" instructions={eyes.eyeliner} />
<Section isSubSection name="Pupil" instructions={eyes.pupil} />
</Section>
)}
{nose && <Section name="Nose" instructions={nose}></Section>}

View file

@ -192,7 +192,7 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
{miis.map((mii) => (
<div
key={mii.id}
className={`flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-[0.8rem] transition hover:scale-105 hover:bg-cyan-100 hover:border-cyan-600 ${mii.platform === "SWITCH" ? "border-red-300" : "border-blue-200"}`}
className="flex flex-col relative bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-[0.8rem] transition hover:scale-105 hover:bg-cyan-100 hover:border-cyan-600"
>
<Carousel
images={[
@ -203,8 +203,15 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
/>
<div className="p-4 flex flex-col gap-1 h-full">
<Link href={`/mii/${mii.id}`} className="font-bold text-2xl line-clamp-1" title={mii.name}>
<Link href={`/mii/${mii.id}`} className="relative font-bold text-2xl line-clamp-1" title={mii.name}>
{mii.name}
<div className="absolute right-0 top-1/2 -translate-y-1/2 text-[1.25rem] opacity-25">
{mii.platform === "SWITCH" ? (
<Icon icon="cib:nintendo-switch" className="text-red-400" />
) : (
<Icon icon="cib:nintendo-3ds" className="text-sky-400" />
)}
</div>
</Link>
<div id="tags" className="flex flex-wrap gap-1">
{mii.tags.map((tag) => (

View file

@ -50,47 +50,47 @@ export default function SubmitForm() {
const [platform, setPlatform] = useState<MiiPlatform>("SWITCH");
const [gender, setGender] = useState<MiiGender>("MALE");
const instructions = useRef<Partial<SwitchMiiInstructions>>({
head: { type: 1, skinColor: 1 },
const instructions = useRef<SwitchMiiInstructions>({
head: { type: null, skinColor: null },
hair: {
setType: 43,
setType: null,
bangsType: null,
backType: null,
color: 0,
color: null,
subColor: null,
subColor2: null,
style: 1,
style: null,
isFlipped: false,
},
eyebrows: { type: 28, color: 0, height: 0, distance: 0, rotation: 0, size: 0, stretch: 0 },
eyebrows: { type: null, color: null, height: null, distance: null, rotation: null, size: null, stretch: null },
eyes: {
main: { type: 6, color: 0, height: 0, distance: 0, rotation: 0, size: 0, stretch: 0 },
eyelashesTop: { type: 1, height: 0, distance: 0, rotation: 0, size: 0, stretch: 0 },
eyelashesBottom: { type: 1, height: 0, distance: 0, rotation: 0, size: 0, stretch: 0 },
eyelidTop: { type: 1, height: 0, distance: 0, rotation: 0, size: 0, stretch: 0 },
eyelidBottom: { type: 1, height: 0, distance: 0, rotation: 0, size: 0, stretch: 0 },
eyeliner: { type: 1, color: 0 },
pupil: { type: 1, height: 0, distance: 0, rotation: 0, size: 0, stretch: 0 },
main: { type: null, color: null, height: null, distance: null, rotation: null, size: null, stretch: null },
eyelashesTop: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
eyelashesBottom: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
eyelidTop: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
eyelidBottom: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
eyeliner: { type: null, color: null },
pupil: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
},
nose: { type: 6, height: 0, size: 0 },
lips: { type: 2, color: 0, height: 0, rotation: 0, size: 0, stretch: 0, hasLipstick: false },
ears: { type: 1, height: 0, size: 0 },
glasses: { type: 1, ringColor: 0, shadesColor: 0, height: 0, size: 0, stretch: 0 },
nose: { type: null, height: null, size: null },
lips: { type: null, color: null, height: null, rotation: null, size: null, stretch: null, hasLipstick: false },
ears: { type: null, height: null, size: null },
glasses: { type: null, ringColor: null, shadesColor: null, height: null, size: null, stretch: null },
other: {
wrinkles1: { type: 1, height: 0, distance: 0, size: 0, stretch: 0 },
wrinkles2: { type: 1, height: 0, distance: 0, size: 0, stretch: 0 },
beard: { type: 1, color: 0 },
moustache: { type: 1, color: 0, height: 0, isFlipped: false, size: 0, stretch: 0 },
goatee: { type: 1, color: 0 },
mole: { type: 1, color: 0, height: 0, distance: 0, size: 0 },
eyeShadow: { type: 1, color: 0, height: 0, distance: 0, size: 0, stretch: 0 },
blush: { type: 1, color: 0, height: 0, distance: 0, size: 0, stretch: 0 },
wrinkles1: { type: null, height: null, distance: null, size: null, stretch: null },
wrinkles2: { type: null, height: null, distance: null, size: null, stretch: null },
beard: { type: null, color: null },
moustache: { type: null, color: null, height: null, isFlipped: false, size: null, stretch: null },
goatee: { type: null, color: null },
mole: { type: null, color: null, height: null, distance: null, size: null },
eyeShadow: { type: null, color: null, height: null, distance: null, size: null, stretch: null },
blush: { type: null, color: null, height: null, distance: null, size: null, stretch: null },
},
height: 0,
weight: 0,
height: null,
weight: null,
datingPreferences: [],
voice: { speed: 0, pitch: 0, depth: 0, delivery: 0, tone: 0 },
personality: { movement: 0, speech: 0, energy: 0, thinking: 0, overall: 0 },
voice: { speed: null, pitch: null, depth: null, delivery: null, tone: null },
personality: { movement: null, speech: null, energy: null, thinking: null, overall: null },
});
const [error, setError] = useState<string | undefined>(undefined);

View file

@ -1,5 +1,6 @@
import { SwitchMiiInstructions } from "@/types";
import React, { useState } from "react";
import { Icon } from "@iconify/react";
import HeadTab from "./tabs/head";
import HairTab from "./tabs/hair";
@ -11,10 +12,9 @@ import EarsTab from "./tabs/ears";
import GlassesTab from "./tabs/glasses";
import OtherTab from "./tabs/other";
import MiscTab from "./tabs/misc";
import { Icon } from "@iconify/react";
interface Props {
instructions: React.RefObject<Partial<SwitchMiiInstructions>>;
instructions: React.RefObject<SwitchMiiInstructions>;
}
type Tab = "head" | "hair" | "eyebrows" | "eyes" | "nose" | "lips" | "ears" | "glasses" | "other" | "misc";
@ -48,8 +48,6 @@ export const TAB_COMPONENTS: Record<Tab, React.ComponentType<any>> = {
export default function MiiEditor({ instructions }: Props) {
const [tab, setTab] = useState<Tab>("head");
const ActiveTab = TAB_COMPONENTS[tab];
return (
<>
<div className="w-full aspect-video flex bg-orange-100 border-2 border-orange-200 rounded-xl overflow-hidden">

View file

@ -15,7 +15,7 @@ export default function NumberInputs({ target }: Props) {
return (
<div className="grid grid-rows-5 min-h-0">
{target.height != undefined && (
{target.height !== undefined && (
<div className="w-full">
<label htmlFor="height" className="text-xs">
Height
@ -36,7 +36,7 @@ export default function NumberInputs({ target }: Props) {
</div>
)}
{target.distance != undefined && (
{target.distance !== undefined && (
<div className="w-full">
<label htmlFor="distance" className="text-xs">
Distance
@ -57,7 +57,7 @@ export default function NumberInputs({ target }: Props) {
</div>
)}
{target.rotation != undefined && (
{target.rotation !== undefined && (
<div className="w-full">
<label htmlFor="rotation" className="text-xs">
Rotation
@ -78,7 +78,7 @@ export default function NumberInputs({ target }: Props) {
</div>
)}
{target.size != undefined && (
{target.size !== undefined && (
<div className="w-full">
<label htmlFor="size" className="text-xs">
Size
@ -99,7 +99,7 @@ export default function NumberInputs({ target }: Props) {
</div>
)}
{target.stretch != undefined && (
{target.stretch !== undefined && (
<div className="w-full">
<label htmlFor="stretch" className="text-xs">
Stretch

View file

@ -93,10 +93,11 @@ export default function HeadTab({ instructions }: Props) {
<DatingPreferencesViewer
data={datingPreferences}
onChecked={(e, gender) => {
setDatingPreferences((prev) =>
e.target.checked ? (prev.includes(gender) ? prev : [...prev, gender]) : prev.filter((p) => p !== gender),
);
instructions.current.datingPreferences = datingPreferences;
setDatingPreferences((prev) => {
const updated = e.target.checked ? (prev.includes(gender) ? prev : [...prev, gender]) : prev.filter((p) => p !== gender);
instructions.current.datingPreferences = updated;
return updated;
});
}}
/>
</div>

View file

@ -113,19 +113,73 @@ export const switchMiiInstructionsSchema = z
.optional(),
eyes: z
.object({
eyesType: z.number().int().min(0).max(120).optional(),
eyelashesTop: z.number().int().min(0).max(5).optional(),
eyelashesBottom: z.number().int().min(0).max(1).optional(),
eyelidTop: z.number().int().min(0).max(2).optional(),
eyelidBottom: z.number().int().min(0).max(2).optional(),
eyeliner: z.number().int().min(0).max(1).optional(),
pupil: z.number().int().min(0).max(9).optional(),
color: colorSchema,
height: geometrySchema,
distance: geometrySchema,
rotation: geometrySchema,
size: geometrySchema,
stretch: geometrySchema,
main: z
.object({
type: z.number().int().min(0).max(120).optional(),
color: colorSchema,
height: geometrySchema,
distance: geometrySchema,
rotation: geometrySchema,
size: geometrySchema,
stretch: geometrySchema,
})
.optional(),
eyelashesTop: z
.object({
type: z.number().int().min(0).max(5).optional(),
height: geometrySchema,
distance: geometrySchema,
rotation: geometrySchema,
size: geometrySchema,
stretch: geometrySchema,
})
.optional(),
eyelashesBottom: z
.object({
type: z.number().int().min(0).max(1).optional(),
height: geometrySchema,
distance: geometrySchema,
rotation: geometrySchema,
size: geometrySchema,
stretch: geometrySchema,
})
.optional(),
eyelidTop: z
.object({
type: z.number().int().min(0).max(2).optional(),
height: geometrySchema,
distance: geometrySchema,
rotation: geometrySchema,
size: geometrySchema,
stretch: geometrySchema,
})
.optional(),
eyelidBottom: z
.object({
type: z.number().int().min(0).max(2).optional(),
height: geometrySchema,
distance: geometrySchema,
rotation: geometrySchema,
size: geometrySchema,
stretch: geometrySchema,
})
.optional(),
eyeliner: z
.object({
type: z.number().int().min(0).max(1).optional(),
color: colorSchema,
})
.optional(),
pupil: z
.object({
type: z.number().int().min(0).max(9).optional(),
height: geometrySchema,
distance: geometrySchema,
rotation: geometrySchema,
size: geometrySchema,
stretch: geometrySchema,
})
.optional(),
})
.optional(),
nose: z
@ -261,11 +315,11 @@ export const switchMiiInstructionsSchema = z
.optional(),
personality: z
.object({
movement: z.number().int().min(1).max(8).optional(),
speech: z.number().int().min(1).max(8).optional(),
energy: z.number().int().min(1).max(8).optional(),
thinking: z.number().int().min(1).max(8).optional(),
overall: z.number().int().min(1).max(8).optional(),
movement: z.number().int().min(0).max(5).optional(),
speech: z.number().int().min(0).max(5).optional(),
energy: z.number().int().min(0).max(5).optional(),
thinking: z.number().int().min(0).max(5).optional(),
overall: z.number().int().min(0).max(5).optional(),
})
.optional(),
})

230
src/types.d.ts vendored
View file

@ -4,182 +4,182 @@ import { DefaultSession } from "next-auth";
// Some types have different options disabled, we're ignoring them for now
interface SwitchMiiInstructions {
head: {
type: number; // 16 types, default is 2
skinColor: number; // Additional 14 are not in color menu, default is 2
type: number | null; // 16 types, default is 2
skinColor: number | null; // Additional 14 are not in color menu, default is 2
};
hair: {
setType: number | null; // 245 types, default is 43
bangsType: number | null; // 83 types, default is none, if a set is selected, set bangs and back to none and vice-versa
backType: number | null; // 111 types, default is none, same here (set related)
color: number;
color: number | null;
subColor: number | null; // Default is none
subColor2: number | null; // Only used when bangs/back is selected
style: number | null; // is this different for each hair?
isFlipped: boolean; // Only for sets and fringe
};
eyebrows: {
type: number; // 1 is None, 43 types, default is 28
color: number;
height: number;
distance: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 1 is None, 43 types, default is 28
color: number | null;
height: number | null;
distance: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
};
eyes: {
main: {
type: number; // 1 is None, 121 types default is 6
color: number;
height: number;
distance: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 1 is None, 121 types default is 6
color: number | null;
height: number | null;
distance: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
};
eyelashesTop: {
type: number; // 6 types, default is 1
height: number;
distance: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 6 types, default is 1
height: number | null;
distance: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
};
eyelashesBottom: {
type: number; // 2 types, default is 1
height: number;
distance: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 2 types, default is 1
height: number | null;
distance: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
};
eyelidTop: {
type: number; // 3 types, default is 1
height: number;
distance: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 3 types, default is 1
height: number | null;
distance: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
};
eyelidBottom: {
type: number; // 3 types, default is 1
height: number;
distance: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 3 types, default is 1
height: number | null;
distance: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
};
eyeliner: {
type: number; // 2 types, default is 1
color: number;
type: number | null; // 2 types, default is 1
color: number | null;
};
pupil: {
type: number; // 10 types, default is 1
height: number;
distance: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 10 types, default is 1
height: number | null;
distance: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
};
};
nose: {
type: number; // 1 is None, 32 types, default is 6
height: number;
size: number;
type: number | null; // 1 is None, 32 types, default is 6
height: number | null;
size: number | null;
};
lips: {
type: number; // 1 is None, 53 types, default is 2
color: number;
height: number;
rotation: number;
size: number;
stretch: number;
type: number | null; // 1 is None, 53 types, default is 2
color: number | null;
height: number | null;
rotation: number | null;
size: number | null;
stretch: number | null;
hasLipstick: boolean;
};
ears: {
type: number; // 5 types, default is 1
height: number; // Does not work for default
size: number; // Does not work for default
type: number | null; // 5 types, default is 1
height: number | null; // Does not work for default
size: number | null; // Does not work for default
};
glasses: {
type: number; // NOTE: THERE IS A GAP AT 40!!! 1 is None, 58 types, default is 1
ringColor: number;
shadesColor: number; // Only works after gap
height: number;
size: number;
stretch: number;
type: number | null; // NOTE: THERE IS A GAP AT 40!!! 1 is None, 58 types, default is 1
ringColor: number | null;
shadesColor: number | null; // Only works after gap
height: number | null;
size: number | null;
stretch: number | null;
};
other: {
// names were assumed
wrinkles1: {
type: number; // 9 types, default is 1
height: number;
distance: number;
size: number;
stretch: number;
type: number | null; // 9 types, default is 1
height: number | null;
distance: number | null;
size: number | null;
stretch: number | null;
};
wrinkles2: {
type: number; // 15 types, default is 1
height: number;
distance: number;
size: number;
stretch: number;
type: number | null; // 15 types, default is 1
height: number | null;
distance: number | null;
size: number | null;
stretch: number | null;
};
beard: {
type: number; // 15 types, default is 1
color: number;
type: number | null; // 15 types, default is 1
color: number | null;
};
moustache: {
type: number; // 16 types, default is 1
color: number; // is this same as hair?
height: number;
type: number | null; // 16 types, default is 1
color: number | null; // is this same as hair?
height: number | null;
isFlipped: boolean;
size: number;
stretch: number;
size: number | null;
stretch: number | null;
};
goatee: {
type: number; // 14 types, default is 1
color: number;
type: number | null; // 14 types, default is 1
color: number | null;
};
mole: {
type: number; // 2 types, default is 1
color: number; // is this same as hair?
height: number;
distance: number;
size: number;
type: number | null; // 2 types, default is 1
color: number | null; // is this same as hair?
height: number | null;
distance: number | null;
size: number | null;
};
eyeShadow: {
type: number; // 4 types, default is 1
color: number;
height: number;
distance: number;
size: number;
stretch: number;
type: number | null; // 4 types, default is 1
color: number | null;
height: number | null;
distance: number | null;
size: number | null;
stretch: number | null;
};
blush: {
type: number; // 8 types, default is 1
color: number;
height: number;
distance: number;
size: number;
stretch: number;
type: number | null; // 8 types, default is 1
color: number | null;
height: number | null;
distance: number | null;
size: number | null;
stretch: number | null;
};
};
// makeup, use video?
height: number;
weight: number;
height: number | null;
weight: number | null;
datingPreferences: MiiGender[];
voice: {
speed: number;
pitch: number;
depth: number;
delivery: number;
tone: number; // 1 to 6
speed: number | null;
pitch: number | null;
depth: number | null;
delivery: number | null;
tone: number | null; // 1 to 6
};
personality: {
movement: number; // 8 levels, slow to quick
speech: number; // 8 levels, polite to honest
energy: number; // 8 levels, flat to varied
thinking: number; // 8 levels, serious to chill
overall: number; // 8 levels, normal to quirky
movement: number | null; // 8 levels, slow to quick
speech: number | null; // 8 levels, polite to honest
energy: number | null; // 8 levels, flat to varied
thinking: number | null; // 8 levels, serious to chill
overall: number | null; // 8 levels, normal to quirky
};
}