mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 11:13:16 +00:00
feat: nonbinary miis
This commit is contained in:
parent
0b1516e930
commit
d45eb07879
10 changed files with 117 additions and 62 deletions
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterEnum
|
||||
ALTER TYPE "MiiGender" ADD VALUE 'NONBINARY';
|
||||
|
|
@ -164,6 +164,7 @@ enum MiiPlatform {
|
|||
enum MiiGender {
|
||||
MALE
|
||||
FEMALE
|
||||
NONBINARY
|
||||
}
|
||||
|
||||
enum ReportType {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ export async function POST(request: NextRequest) {
|
|||
const check = await rateLimit.handle();
|
||||
if (check) return check;
|
||||
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`);
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`);
|
||||
const { value } = await response.json();
|
||||
if (!value) return rateLimit.sendResponse({ error: "Submissions are temporarily disabled" }, 503);
|
||||
|
|
@ -239,6 +238,14 @@ export async function POST(request: NextRequest) {
|
|||
return rateLimit.sendResponse({ error: "Failed to process and store Mii files" }, 500);
|
||||
}
|
||||
|
||||
try {
|
||||
await generateMetadataImage(miiRecord, session.user.name!);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Sentry.captureException(error, { extra: { miiId: miiRecord.id, stage: "metadata-image" } });
|
||||
return rateLimit.sendResponse({ error: `Failed to generate 'metadata' type image for mii ${miiRecord.id}` }, 500);
|
||||
}
|
||||
|
||||
// Compress and store user images
|
||||
try {
|
||||
await Promise.all(
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export default async function MiiPage({ params }: Props) {
|
|||
</div>
|
||||
|
||||
<div
|
||||
className={`rounded-xl flex justify-center items-center size-16 text-4xl border-2 shadow-sm ${
|
||||
className={`rounded-xl flex justify-center items-center size-13 text-3xl border-2 shadow-sm ${
|
||||
mii.platform === "THREE_DS" ? "bg-sky-100 border-sky-400" : "bg-white border-gray-300"
|
||||
}`}
|
||||
>
|
||||
|
|
@ -186,7 +186,7 @@ export default async function MiiPage({ params }: Props) {
|
|||
</div>
|
||||
|
||||
<div
|
||||
className={`rounded-xl flex justify-center items-center size-16 text-4xl border-2 shadow-sm ${
|
||||
className={`rounded-xl flex justify-center items-center size-13 text-3xl border-2 shadow-sm ${
|
||||
mii.platform === "SWITCH" ? "bg-red-100 border-red-400" : "bg-white border-gray-300"
|
||||
}`}
|
||||
>
|
||||
|
|
@ -201,17 +201,21 @@ export default async function MiiPage({ params }: Props) {
|
|||
<hr className="grow border-zinc-300" />
|
||||
</div>
|
||||
|
||||
<div data-tooltip-span title={mii.gender ?? "NULL"} className="grid grid-cols-2 gap-2">
|
||||
<div data-tooltip-span title={mii.gender ?? "NULL"} className="flex gap-1">
|
||||
<div
|
||||
className={`tooltip mt-1! ${
|
||||
mii.gender === "MALE" ? "bg-blue-400! border-blue-400! before:border-b-blue-400!" : "bg-pink-400! border-pink-400! before:border-b-pink-400!"
|
||||
mii.gender === "MALE"
|
||||
? "bg-blue-400! border-blue-400! before:border-b-blue-400!"
|
||||
: mii.gender === "FEMALE"
|
||||
? "bg-pink-400! border-pink-400! before:border-b-pink-400!"
|
||||
: "bg-purple-400! border-purple-400! before:border-b-purple-400!"
|
||||
}`}
|
||||
>
|
||||
{mii.gender === "MALE" ? "Male" : "Female"}
|
||||
{mii.gender === "MALE" ? "Male" : mii.gender === "FEMALE" ? "Female" : "Nonbinary"}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`rounded-xl flex justify-center items-center size-16 text-5xl border-2 shadow-sm ${
|
||||
className={`rounded-xl flex justify-center items-center size-13 text-5xl border-2 shadow-sm ${
|
||||
mii.gender === "MALE" ? "bg-blue-100 border-blue-400" : "bg-white border-gray-300"
|
||||
}`}
|
||||
>
|
||||
|
|
@ -219,12 +223,20 @@ export default async function MiiPage({ params }: Props) {
|
|||
</div>
|
||||
|
||||
<div
|
||||
className={`rounded-xl flex justify-center items-center size-16 text-5xl border-2 shadow-sm ${
|
||||
className={`rounded-xl flex justify-center items-center size-13 text-5xl border-2 shadow-sm ${
|
||||
mii.gender === "FEMALE" ? "bg-pink-100 border-pink-400" : "bg-white border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<Icon icon="foundation:female" className="text-pink-400" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`rounded-xl flex justify-center items-center size-13 text-5xl border-2 shadow-sm ${
|
||||
mii.gender === "NONBINARY" ? "bg-purple-100 border-purple-400" : "bg-white border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<Icon icon="mdi:gender-non-binary" className="text-purple-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,26 +19,26 @@ export const metadata: Metadata = {
|
|||
};
|
||||
|
||||
export default async function SubmitPage() {
|
||||
// const session = await auth();
|
||||
const session = await auth();
|
||||
|
||||
// if (!session) redirect("/login");
|
||||
// if (!session.user.username) redirect("/create-username");
|
||||
// const activePunishment = await prisma.punishment.findFirst({
|
||||
// where: {
|
||||
// userId: Number(session?.user.id),
|
||||
// returned: false,
|
||||
// },
|
||||
// });
|
||||
// if (activePunishment) redirect("/off-the-island");
|
||||
if (!session) redirect("/login");
|
||||
if (!session.user.username) redirect("/create-username");
|
||||
const activePunishment = await prisma.punishment.findFirst({
|
||||
where: {
|
||||
userId: Number(session?.user.id),
|
||||
returned: false,
|
||||
},
|
||||
});
|
||||
if (activePunishment) redirect("/off-the-island");
|
||||
|
||||
// Check if submissions are disabled
|
||||
let value: boolean | null = true;
|
||||
// try {
|
||||
// const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`);
|
||||
// value = await response.json();
|
||||
// } catch (error) {
|
||||
// return <p>An error occurred!</p>;
|
||||
// }
|
||||
try {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`);
|
||||
value = await response.json();
|
||||
} catch (error) {
|
||||
return <p>An error occurred!</p>;
|
||||
}
|
||||
|
||||
if (!value)
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import { useSearchParams } from "next/navigation";
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
import { MiiGender } from "@prisma/client";
|
||||
import { MiiGender, MiiPlatform } from "@prisma/client";
|
||||
|
||||
import PlatformSelect from "./platform-select";
|
||||
import TagFilter from "./tag-filter";
|
||||
import GenderSelect from "./gender-select";
|
||||
import OtherFilters from "./other-filters";
|
||||
|
|
@ -16,9 +17,10 @@ export default function FilterMenu() {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const platform = (searchParams.get("platform") as MiiPlatform) || undefined;
|
||||
const gender = (searchParams.get("gender") as MiiGender) || undefined;
|
||||
const rawTags = searchParams.get("tags") || "";
|
||||
const rawExclude = searchParams.get("exclude") || "";
|
||||
const gender = (searchParams.get("gender") as MiiGender) || undefined;
|
||||
const allowCopying = (searchParams.get("allowCopying") as unknown as boolean) || false;
|
||||
|
||||
const tags = useMemo(
|
||||
|
|
@ -61,11 +63,12 @@ export default function FilterMenu() {
|
|||
// Count all active filters
|
||||
useEffect(() => {
|
||||
let count = tags.length + exclude.length;
|
||||
if (platform) count++;
|
||||
if (gender) count++;
|
||||
if (allowCopying) count++;
|
||||
|
||||
setFilterCount(count);
|
||||
}, [tags, exclude, gender, allowCopying]);
|
||||
}, [tags, exclude, platform, gender, allowCopying]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
|
|
@ -84,6 +87,20 @@ export default function FilterMenu() {
|
|||
<div className="absolute bottom-full left-1/6 -translate-x-1/2 size-0 border-8 border-transparent border-b-amber-500"></div>
|
||||
|
||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium w-full mb-2">
|
||||
<hr className="grow border-zinc-300" />
|
||||
<span>Platform</span>
|
||||
<hr className="grow border-zinc-300" />
|
||||
</div>
|
||||
<PlatformSelect />
|
||||
|
||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium w-full mt-2 mb-1">
|
||||
<hr className="grow border-zinc-300" />
|
||||
<span>Gender</span>
|
||||
<hr className="grow border-zinc-300" />
|
||||
</div>
|
||||
<GenderSelect />
|
||||
|
||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium w-full mt-2 mb-2">
|
||||
<hr className="grow border-zinc-300" />
|
||||
<span>Tags Include</span>
|
||||
<hr className="grow border-zinc-300" />
|
||||
|
|
@ -97,19 +114,16 @@ export default function FilterMenu() {
|
|||
</div>
|
||||
<TagFilter isExclude />
|
||||
|
||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium w-full mt-2 mb-1">
|
||||
<hr className="grow border-zinc-300" />
|
||||
<span>Gender</span>
|
||||
<hr className="grow border-zinc-300" />
|
||||
</div>
|
||||
<GenderSelect />
|
||||
|
||||
{platform !== "SWITCH" && (
|
||||
<>
|
||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium w-full mt-2 mb-1">
|
||||
<hr className="grow border-zinc-300" />
|
||||
<span>Other</span>
|
||||
<hr className="grow border-zinc-300" />
|
||||
</div>
|
||||
<OtherFilters />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,16 +3,15 @@
|
|||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useState, useTransition } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { MiiGender } from "@prisma/client";
|
||||
import { MiiGender, MiiPlatform } from "@prisma/client";
|
||||
|
||||
export default function GenderSelect() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [, startTransition] = useTransition();
|
||||
|
||||
const [selected, setSelected] = useState<MiiGender | null>(
|
||||
(searchParams.get("gender") as MiiGender) ?? null
|
||||
);
|
||||
const [selected, setSelected] = useState<MiiGender | null>((searchParams.get("gender") as MiiGender) ?? null);
|
||||
const platform = (searchParams.get("platform") as MiiPlatform) || undefined;
|
||||
|
||||
const handleClick = (gender: MiiGender) => {
|
||||
const filter = selected === gender ? null : gender;
|
||||
|
|
@ -33,20 +32,16 @@ export default function GenderSelect() {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-0.5 w-fit">
|
||||
<div className="flex gap-0.5 w-fit">
|
||||
<button
|
||||
onClick={() => handleClick("MALE")}
|
||||
aria-label="Filter for Male Miis"
|
||||
data-tooltip-span
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-13 text-5xl border-2 transition-all ${
|
||||
selected === "MALE"
|
||||
? "bg-blue-100 border-blue-400 shadow-md"
|
||||
: "bg-white border-gray-300 hover:border-gray-400"
|
||||
selected === "MALE" ? "bg-blue-100 border-blue-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
<div className="tooltip bg-blue-400! border-blue-400! before:border-b-blue-400!">
|
||||
Male
|
||||
</div>
|
||||
<div className="tooltip bg-blue-400! border-blue-400! before:border-b-blue-400!">Male</div>
|
||||
<Icon icon="foundation:male" className="text-blue-400" />
|
||||
</button>
|
||||
|
||||
|
|
@ -55,16 +50,26 @@ export default function GenderSelect() {
|
|||
aria-label="Filter for Female Miis"
|
||||
data-tooltip-span
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-13 text-5xl border-2 transition-all ${
|
||||
selected === "FEMALE"
|
||||
? "bg-pink-100 border-pink-400 shadow-md"
|
||||
: "bg-white border-gray-300 hover:border-gray-400"
|
||||
selected === "FEMALE" ? "bg-pink-100 border-pink-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
<div className="tooltip bg-pink-400! border-pink-400! before:border-b-pink-400!">
|
||||
Female
|
||||
</div>
|
||||
<div className="tooltip bg-pink-400! border-pink-400! before:border-b-pink-400!">Female</div>
|
||||
<Icon icon="foundation:female" className="text-pink-400" />
|
||||
</button>
|
||||
|
||||
{platform !== "THREE_DS" && (
|
||||
<button
|
||||
onClick={() => handleClick("NONBINARY")}
|
||||
aria-label="Filter for Nonbinary Miis"
|
||||
data-tooltip-span
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-13 text-5xl border-2 transition-all ${
|
||||
selected === "NONBINARY" ? "bg-purple-100 border-purple-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
<div className="tooltip bg-purple-400! border-purple-400! before:border-b-purple-400!">Nonbinary</div>
|
||||
<Icon icon="mdi:gender-non-binary" className="text-purple-400" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default function PlatformSelect() {
|
|||
selected === "THREE_DS" ? "bg-sky-100 border-sky-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
<div className="tooltip !bg-sky-400 !border-sky-400 before:!border-b-sky-400">3DS</div>
|
||||
<div className="tooltip bg-sky-400! border-sky-400! before:border-b-sky-400!">3DS</div>
|
||||
<Icon icon="cib:nintendo-3ds" className="text-sky-400" />
|
||||
</button>
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ export default function PlatformSelect() {
|
|||
selected === "SWITCH" ? "bg-red-100 border-red-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
<div className="tooltip !bg-red-400 !border-red-400 before:!border-b-red-400">Switch</div>
|
||||
<div className="tooltip bg-red-400! border-red-400! before:border-b-red-400!">Switch</div>
|
||||
<Icon icon="cib:nintendo-switch" className="text-red-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -279,7 +279,8 @@ export default function SubmitForm() {
|
|||
type="button"
|
||||
onClick={() => setGender("MALE")}
|
||||
aria-label="Filter for Male Miis"
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-11 text-4xl border-2 transition-all ${
|
||||
data-tooltip="Male"
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-11 text-4xl border-2 transition-all after:bg-blue-400! after:border-blue-400! before:border-b-blue-400! ${
|
||||
gender === "MALE" ? "bg-blue-100 border-blue-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
|
|
@ -290,12 +291,25 @@ export default function SubmitForm() {
|
|||
type="button"
|
||||
onClick={() => setGender("FEMALE")}
|
||||
aria-label="Filter for Female Miis"
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-11 text-4xl border-2 transition-all ${
|
||||
data-tooltip="Female"
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-11 text-4xl border-2 transition-all after:bg-pink-400! after:border-pink-400! before:border-b-pink-400! ${
|
||||
gender === "FEMALE" ? "bg-pink-100 border-pink-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
<Icon icon="foundation:female" className="text-pink-400" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setGender("NONBINARY")}
|
||||
aria-label="Filter for Nonbinary Miis"
|
||||
data-tooltip="Nonbinary"
|
||||
className={`cursor-pointer rounded-xl flex justify-center items-center size-11 text-4xl border-2 transition-all after:bg-purple-400! after:border-purple-400! before:border-b-purple-400! ${
|
||||
gender === "NONBINARY" ? "bg-purple-100 border-purple-400 shadow-md" : "bg-white border-gray-300 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
<Icon icon="mdi:gender-non-binary" className="text-purple-400" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export const searchSchema = z.object({
|
|||
.filter((tag) => tag.length > 0),
|
||||
),
|
||||
platform: z.enum(MiiPlatform, { error: "Platform must be either 'THREE_DS', or 'SWITCH'" }).optional(),
|
||||
gender: z.enum(MiiGender, { error: "Gender must be either 'MALE', or 'FEMALE'" }).optional(),
|
||||
gender: z.enum(MiiGender, { error: "Gender must be either 'MALE', 'FEMALE', or 'NONBINARY' if on Switch platform" }).optional(),
|
||||
allowCopying: z.coerce.boolean({ error: "Allow Copying must be either true or false" }).optional(),
|
||||
// todo: incorporate tagsSchema
|
||||
// Pages
|
||||
|
|
|
|||
Loading…
Reference in a new issue