From e1a158d070ef49b554362b178f338baedf7956b2 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Sat, 19 Jul 2025 17:12:38 +0100 Subject: [PATCH] feat: improve mii list filtering, add gender filter - added react transitions when redirecting, should improve UI looks and smoothness when filtering/sorting - small refactor - put @types/sjcl in devDependencies --- package.json | 2 +- pnpm-lock.yaml | 6 +-- src/app/profile/[id]/page.tsx | 8 ++- src/app/profile/likes/page.tsx | 14 ++--- src/components/mii-list/filter-select.tsx | 62 ----------------------- src/components/mii-list/gender-select.tsx | 54 ++++++++++++++++++++ src/components/mii-list/index.tsx | 20 +++++--- src/components/mii-list/skeleton.tsx | 2 +- src/components/mii-list/sort-select.tsx | 14 +++-- src/components/mii-list/tag-filter.tsx | 50 ++++++++++++++++++ src/components/tag-selector.tsx | 2 +- 11 files changed, 141 insertions(+), 93 deletions(-) delete mode 100644 src/components/mii-list/filter-select.tsx create mode 100644 src/components/mii-list/gender-select.tsx create mode 100644 src/components/mii-list/tag-filter.tsx diff --git a/package.json b/package.json index 624eac6..a226af1 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "@bprogress/next": "^3.2.12", "@hello-pangea/dnd": "^18.0.1", "@prisma/client": "^6.11.1", - "@types/sjcl": "^1.0.34", "bit-buffer": "^0.2.5", "canvas-confetti": "^1.9.3", "dayjs": "^1.11.13", @@ -47,6 +46,7 @@ "@types/node": "^24.0.13", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", + "@types/sjcl": "^1.0.34", "eslint": "^9.31.0", "eslint-config-next": "15.3.5", "prisma": "^6.11.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 940af48..390b290 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,6 @@ importers: '@prisma/client': specifier: ^6.11.1 version: 6.11.1(prisma@6.11.1(typescript@5.8.3))(typescript@5.8.3) - '@types/sjcl': - specifier: ^1.0.34 - version: 1.0.34 bit-buffer: specifier: ^0.2.5 version: 0.2.5 @@ -108,6 +105,9 @@ importers: '@types/react-dom': specifier: ^19.1.6 version: 19.1.6(@types/react@19.1.8) + '@types/sjcl': + specifier: ^1.0.34 + version: 1.0.34 eslint: specifier: ^9.31.0 version: 9.31.0(jiti@2.4.2) diff --git a/src/app/profile/[id]/page.tsx b/src/app/profile/[id]/page.tsx index 66e512e..d621992 100644 --- a/src/app/profile/[id]/page.tsx +++ b/src/app/profile/[id]/page.tsx @@ -78,11 +78,9 @@ export default async function ProfilePage({ searchParams, params }: Props) { return (
-
- }> - - -
+ }> + +
); } diff --git a/src/app/profile/likes/page.tsx b/src/app/profile/likes/page.tsx index 98fa49e..abce75e 100644 --- a/src/app/profile/likes/page.tsx +++ b/src/app/profile/likes/page.tsx @@ -29,20 +29,16 @@ export default async function ProfileSettingsPage({ searchParams }: Props) { return (
-
+

My Likes

View every Mii you have liked on TomodachiShare.

- -
-
-
- - }> - -
+ + }> + +
); } diff --git a/src/components/mii-list/filter-select.tsx b/src/components/mii-list/filter-select.tsx deleted file mode 100644 index 3f1238f..0000000 --- a/src/components/mii-list/filter-select.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import { redirect, useSearchParams } from "next/navigation"; -import { useEffect, useMemo, useState } from "react"; -import TagSelector from "../tag-selector"; - -export default function FilterSelect() { - const searchParams = useSearchParams(); - - const rawTags = searchParams.get("tags"); - const preexistingTags = useMemo( - () => - rawTags - ? rawTags - .split(",") - .map((tag) => tag.trim()) - .filter((tag) => tag.length > 0) - : [], - [rawTags] - ); - - const [isOpen, setIsOpen] = useState(false); - const [tags, setTags] = useState(preexistingTags); - - const handleSubmit = () => { - redirect(`/?tags=${encodeURIComponent(tags.join(","))}`); - }; - - useEffect(() => { - setTags(preexistingTags); - }, [preexistingTags]); - - return ( -
- - -
-
- - -
- - -
-
- ); -} diff --git a/src/components/mii-list/gender-select.tsx b/src/components/mii-list/gender-select.tsx new file mode 100644 index 0000000..25997cf --- /dev/null +++ b/src/components/mii-list/gender-select.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { useState, useTransition } from "react"; +import { Icon } from "@iconify/react"; +import { MiiGender } from "@prisma/client"; + +export default function GenderSelect() { + const router = useRouter(); + const searchParams = useSearchParams(); + const [, startTransition] = useTransition(); + + const [selected, setSelected] = useState((searchParams.get("gender") as MiiGender) ?? null); + + const handleClick = (gender: MiiGender) => { + const filter = selected === gender ? null : gender; + setSelected(filter); + + const params = new URLSearchParams(searchParams); + if (filter) { + params.set("gender", filter); + } else { + params.delete("gender"); + } + + startTransition(() => { + router.push(`?${params.toString()}`); + }); + }; + + return ( +
+ + + +
+ ); +} diff --git a/src/components/mii-list/index.tsx b/src/components/mii-list/index.tsx index 82d943d..a01c318 100644 --- a/src/components/mii-list/index.tsx +++ b/src/components/mii-list/index.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; -import { Prisma } from "@prisma/client"; +import { MiiGender, Prisma } from "@prisma/client"; import { Icon } from "@iconify/react"; import { z } from "zod"; @@ -8,7 +8,8 @@ import { querySchema } from "@/lib/schemas"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; -import FilterSelect from "./filter-select"; +import GenderSelect from "./gender-select"; +import TagFilter from "./tag-filter"; import SortSelect from "./sort-select"; import Carousel from "../carousel"; import LikeButton from "../like-button"; @@ -33,6 +34,7 @@ const searchSchema = z.object({ .map((tag) => tag.trim()) .filter((tag) => tag.length > 0) ), + gender: z.enum(MiiGender, { error: "Gender must be either 'MALE', or 'FEMALE'" }).optional(), // todo: incorporate tagsSchema // Pages limit: z.coerce @@ -54,8 +56,9 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro const parsed = searchSchema.safeParse(searchParams); if (!parsed.success) return

{parsed.error.issues[0].message}

; - const { q: query, sort, tags, page = 1, limit = 24 } = parsed.data; + const { q: query, sort, tags, gender, page = 1, limit = 24 } = parsed.data; + // My Likes page let miiIdsLiked: number[] | undefined = undefined; if (inLikesPage && session?.user.id) { @@ -75,6 +78,8 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro }), // Tag filtering ...(tags && tags.length > 0 && { tags: { hasEvery: tags } }), + // Gender + ...(gender && { gender: { equals: gender } }), // Profiles ...(userId && { userId }), }; @@ -136,8 +141,8 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro return (
-
-

+

+

{totalCount == filteredCount ? ( <> {totalCount} Miis @@ -149,8 +154,9 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro )}

-
- +
+ +
diff --git a/src/components/mii-list/skeleton.tsx b/src/components/mii-list/skeleton.tsx index 9747ecc..3ec8ee5 100644 --- a/src/components/mii-list/skeleton.tsx +++ b/src/components/mii-list/skeleton.tsx @@ -1,4 +1,4 @@ -import FilterSelect from "./filter-select"; +import FilterSelect from "./tag-filter"; import SortSelect from "./sort-select"; import Pagination from "./pagination"; diff --git a/src/components/mii-list/sort-select.tsx b/src/components/mii-list/sort-select.tsx index 90d8fcf..828dd0c 100644 --- a/src/components/mii-list/sort-select.tsx +++ b/src/components/mii-list/sort-select.tsx @@ -1,15 +1,18 @@ "use client"; -import { Icon } from "@iconify/react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useTransition } from "react"; import { useSelect } from "downshift"; -import { redirect, useSearchParams } from "next/navigation"; +import { Icon } from "@iconify/react"; type Sort = "newest" | "likes" | "oldest"; const items = ["newest", "likes", "oldest"]; export default function SortSelect() { + const router = useRouter(); const searchParams = useSearchParams(); + const [, startTransition] = useTransition(); const currentSort = (searchParams.get("sort") as Sort) || "newest"; @@ -21,12 +24,15 @@ export default function SortSelect() { const params = new URLSearchParams(searchParams); params.set("sort", selectedItem); - redirect(`?${params.toString()}`); + + startTransition(() => { + router.push(`?${params.toString()}`); + }); }, }); return ( -
+
{/* Toggle button to open the dropdown */}