mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-06-28 14:44:15 +00:00
feat: controversial miis
at the time of writing, the poll is at 74% on option 2 (this one) with 14 hours to go. i keep getting reports so it's coming early
This commit is contained in:
parent
576cb698d2
commit
7925c9e2f5
14 changed files with 111 additions and 54 deletions
|
|
@ -31,7 +31,7 @@ export default async function Reports() {
|
|||
<div className="flex gap-1 w-max">
|
||||
<span
|
||||
className={`text-xs font-semibold px-2 py-1 rounded-full border ${
|
||||
report.reportType == "USER" ? "bg-red-200 text-red-800 border-orange-400" : "bg-cyan-200 text-cyan-800 border-cyan-400"
|
||||
report.reportType == "USER" ? "bg-red-200 text-red-800 border-red-400" : "bg-cyan-200 text-cyan-800 border-cyan-400"
|
||||
}`}
|
||||
>
|
||||
{report.reportType}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default function Carousel({ images, className }: Props) {
|
|||
<div className="flex">
|
||||
{images.map((src, index) => (
|
||||
<div key={index} className="shrink-0 w-full">
|
||||
<ImageViewer src={src} alt="mii image" width={480} height={320} className="w-full h-auto aspect-3/2 object-contain" images={images} />
|
||||
<ImageViewer src={src} alt="mii image" width={240} height={160} className="w-full h-auto aspect-3/2 object-contain" images={images} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -127,16 +127,13 @@ export default function FilterMenu() {
|
|||
<MakeupSelect />
|
||||
</>
|
||||
)}
|
||||
{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 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>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
|
|||
const parsed = searchSchema.safeParse(searchParams);
|
||||
if (!parsed.success) return <h1>{parsed.error.issues[0].message}</h1>;
|
||||
|
||||
const { q: query, sort, tags, exclude, platform, gender, makeup, allowCopying, page = 1, limit = 24, seed } = parsed.data;
|
||||
const { q: query, sort, tags, exclude, platform, gender, makeup, allowCopying, quarantined, page = 1, limit = 24, seed } = parsed.data;
|
||||
|
||||
// My Likes page
|
||||
let miiIdsLiked: number[] | undefined = undefined;
|
||||
|
|
@ -59,6 +59,8 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
|
|||
...(allowCopying && { allowedCopying: true }),
|
||||
// Makeup
|
||||
...(makeup && { makeup: { equals: makeup } }),
|
||||
// Quarantined
|
||||
...(!quarantined && { quarantined: false }),
|
||||
// Profiles
|
||||
...(userId && { userId }),
|
||||
};
|
||||
|
|
@ -82,6 +84,7 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
|
|||
gender: true,
|
||||
makeup: true,
|
||||
allowedCopying: true,
|
||||
quarantined: true,
|
||||
// Mii liked check
|
||||
...(session?.user?.id && {
|
||||
likedBy: {
|
||||
|
|
@ -194,7 +197,7 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
|
|||
{miis.map((mii) => (
|
||||
<div
|
||||
key={mii.id}
|
||||
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"
|
||||
className={`flex flex-col relative bg-zinc-50 rounded-3xl border-2 shadow-lg p-[0.8rem] transition hover:scale-105 hover:bg-cyan-100 hover:border-cyan-600 ${mii.quarantined ? "border-red-300" : "border-zinc-300"}`}
|
||||
>
|
||||
<Carousel
|
||||
images={[
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { MiiPlatform } from "@prisma/client";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React, { ChangeEvent, ChangeEventHandler, useState, useTransition } from "react";
|
||||
import { ChangeEvent, useState, useTransition } from "react";
|
||||
|
||||
export default function OtherFilters() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [, startTransition] = useTransition();
|
||||
|
||||
const platform = (searchParams.get("platform") as MiiPlatform) || undefined;
|
||||
const [allowCopying, setAllowCopying] = useState<boolean>((searchParams.get("allowCopying") as unknown as boolean) ?? false);
|
||||
const [quarantined, setQuarantined] = useState<boolean>((searchParams.get("quarantined") as unknown as boolean) ?? false);
|
||||
|
||||
const handleChangeAllowCopying = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setAllowCopying(e.target.checked);
|
||||
|
|
@ -27,12 +31,39 @@ export default function OtherFilters() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleChangeQuarantined = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setQuarantined(e.target.checked);
|
||||
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set("page", "1");
|
||||
|
||||
if (!quarantined) {
|
||||
params.set("quarantined", "true");
|
||||
} else {
|
||||
params.delete("quarantined");
|
||||
}
|
||||
|
||||
startTransition(() => {
|
||||
router.push(`?${params.toString()}`, { scroll: false });
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<label htmlFor="allowCopying" className="text-sm">
|
||||
Allow Copying
|
||||
</label>
|
||||
<input type="checkbox" id="allowCopying" className="checkbox-alt" checked={allowCopying} onChange={handleChangeAllowCopying} />
|
||||
</div>
|
||||
<>
|
||||
{platform === "THREE_DS" && (
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<label htmlFor="allowCopying" className="text-sm">
|
||||
Allow Copying
|
||||
</label>
|
||||
<input type="checkbox" id="allowCopying" className="checkbox-alt" checked={allowCopying} onChange={handleChangeAllowCopying} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<label htmlFor="quarantined" className="text-sm">
|
||||
Show Controversial Miis
|
||||
</label>
|
||||
<input type="checkbox" id="quarantined" className="checkbox-alt" checked={quarantined} onChange={handleChangeQuarantined} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,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 { useSession } from "next-auth/react";
|
||||
|
||||
import { nameSchema, tagsSchema } from "@/lib/schemas";
|
||||
import { defaultInstructions, minifyInstructions } from "@/lib/switch";
|
||||
|
|
@ -46,6 +47,7 @@ function deepMerge<T>(target: T, source: Partial<T>): T {
|
|||
}
|
||||
|
||||
export default function EditForm({ mii, likes }: Props) {
|
||||
const session = useSession();
|
||||
const [files, setFiles] = useState<FileWithPath[]>([]);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
|
|
@ -67,9 +69,10 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
const [miiPortraitUri, setMiiPortraitUri] = useState<string | undefined>(`/mii/${mii.id}/image?type=mii`);
|
||||
const [miiFeaturesUri, setMiiFeaturesUri] = useState<string | undefined>(`/mii/${mii.id}/image?type=features`);
|
||||
const hasFilesChanged = useRef(false);
|
||||
|
||||
const instructions = useRef<SwitchMiiInstructions>(deepMerge(defaultInstructions, (mii.instructions as object) ?? {}));
|
||||
|
||||
const [quarantined, setQuarantined] = useState(mii.quarantined);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Validate before sending request
|
||||
const nameValidation = nameSchema.safeParse(name);
|
||||
|
|
@ -90,6 +93,7 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
if (description && description != mii.description) formData.append("description", description);
|
||||
if (makeup != mii.makeup) formData.append("makeup", makeup);
|
||||
if (miiPortraitUri) formData.append("miiPortraitUri", miiPortraitUri);
|
||||
if (quarantined != mii.quarantined) formData.append("quarantined", JSON.stringify(quarantined));
|
||||
if (minifyInstructions(structuredClone(instructions.current)) !== (mii.instructions as object))
|
||||
formData.append("instructions", JSON.stringify(instructions.current));
|
||||
|
||||
|
|
@ -245,6 +249,20 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{session.data?.user?.id == process.env.NEXT_PUBLIC_ADMIN_USER_ID && (
|
||||
<>
|
||||
<div className="w-full grid grid-cols-3 items-center">
|
||||
<label htmlFor="quarantined" className="font-semibold py-2">
|
||||
Quarantined
|
||||
</label>
|
||||
|
||||
<div className="col-span-2 flex gap-1">
|
||||
<input type="checkbox" id="quarantined" className="checkbox-alt" checked={quarantined} onChange={(e) => setQuarantined(e.target.checked)} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Makeup/Images/Instructions (Switch only) */}
|
||||
{mii.platform === "SWITCH" && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ interface NumberFieldProps {
|
|||
}
|
||||
|
||||
function NumberField({ label, value, onChange }: NumberFieldProps) {
|
||||
const MIN = -15;
|
||||
const MAX = 15;
|
||||
const MIN = -100;
|
||||
const MAX = 100;
|
||||
|
||||
const decrement = () => onChange(Math.max(MIN, value - 1));
|
||||
const increment = () => onChange(Math.min(MAX, value + 1));
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ interface Props {
|
|||
instructions: React.RefObject<SwitchMiiInstructions>;
|
||||
}
|
||||
|
||||
const TABS: { name: keyof SwitchMiiInstructions["eyes"]; length: number; colorsDisabled?: boolean }[] = [
|
||||
{ name: "main", length: 76 },
|
||||
{ name: "eyelashesTop", length: 6, colorsDisabled: true },
|
||||
{ name: "eyelashesBottom", length: 2, colorsDisabled: true },
|
||||
{ name: "eyelidTop", length: 3, colorsDisabled: true },
|
||||
{ name: "eyelidBottom", length: 3, colorsDisabled: true },
|
||||
{ name: "eyeliner", length: 2 },
|
||||
{ name: "pupil", length: 10, colorsDisabled: true },
|
||||
const TABS: { name: keyof SwitchMiiInstructions["eyes"]; colorsDisabled?: boolean }[] = [
|
||||
{ name: "main" },
|
||||
{ name: "eyelashesTop", colorsDisabled: true },
|
||||
{ name: "eyelashesBottom", colorsDisabled: true },
|
||||
{ name: "eyelidTop", colorsDisabled: true },
|
||||
{ name: "eyelidBottom", colorsDisabled: true },
|
||||
{ name: "eyeliner" },
|
||||
{ name: "pupil", colorsDisabled: true },
|
||||
];
|
||||
|
||||
export default function EyesTab({ instructions }: Props) {
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ interface Props {
|
|||
instructions: React.RefObject<SwitchMiiInstructions>;
|
||||
}
|
||||
|
||||
const TABS: { name: keyof SwitchMiiInstructions["other"]; length: number; defaultColor?: number }[] = [
|
||||
{ name: "wrinkles1", length: 9 },
|
||||
{ name: "wrinkles2", length: 15 },
|
||||
{ name: "beard", length: 15 },
|
||||
{ name: "moustache", length: 16 },
|
||||
{ name: "goatee", length: 14 },
|
||||
{ name: "mole", length: 2 },
|
||||
{ name: "eyeShadow", length: 4, defaultColor: 139 },
|
||||
{ name: "blush", length: 8 },
|
||||
const TABS: { name: keyof SwitchMiiInstructions["other"]; defaultColor?: number }[] = [
|
||||
{ name: "wrinkles1" },
|
||||
{ name: "wrinkles2" },
|
||||
{ name: "beard" },
|
||||
{ name: "moustache" },
|
||||
{ name: "goatee" },
|
||||
{ name: "mole" },
|
||||
{ name: "eyeShadow", defaultColor: 139 },
|
||||
{ name: "blush" },
|
||||
];
|
||||
|
||||
export default function OtherTab({ instructions }: Props) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue