diff --git a/backend/package.json b/backend/package.json
index f08fd13..f45b104 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -13,42 +13,30 @@
"dependencies": {
"@2toad/profanity": "^3.3.0",
"@auth/prisma-adapter": "2.11.1",
- "@bprogress/next": "^3.2.12",
- "@hello-pangea/dnd": "^18.0.1",
"@prisma/client": "^6.19.2",
"bit-buffer": "^0.3.0",
- "canvas-confetti": "^1.9.4",
"dayjs": "^1.11.20",
"downshift": "^9.3.2",
- "embla-carousel-react": "^8.6.0",
"file-type": "^22.0.1",
- "jsqr": "^1.4.0",
"next": "16.2.3",
"next-auth": "5.0.0-beta.30",
"qrcode-generator": "^2.0.4",
"react": "^19.2.5",
"react-dom": "^19.2.5",
- "react-dropzone": "^15.0.0",
- "react-image-crop": "^11.0.10",
"redis": "^5.11.0",
"satori": "^0.26.0",
- "seedrandom": "^3.0.5",
"sharp": "^0.34.5",
"sjcl-with-all": "1.0.8",
- "swr": "^2.4.1",
"zod": "^4.3.6",
- "@tomodachi-share/shared": "workspace:*"
-
+ "@tomodachi-share/shared": "workspace:*"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@iconify/react": "^6.0.2",
"@tailwindcss/postcss": "^4.2.2",
- "@types/canvas-confetti": "^1.9.0",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
- "@types/seedrandom": "^3.0.8",
"@types/sjcl": "^1.0.34",
"eslint": "^10.2.0",
"eslint-config-next": "16.2.3",
diff --git a/backend/src/app/api/mii/list/route.ts b/backend/src/app/api/mii/list/route.ts
index f3293a4..d3b0465 100644
--- a/backend/src/app/api/mii/list/route.ts
+++ b/backend/src/app/api/mii/list/route.ts
@@ -2,10 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { auth } from "@/lib/auth";
import { searchSchema } from "@tomodachi-share/shared/schemas";
-import { RateLimit } from "@/lib/rate-limit";
import { Prisma } from "@prisma/client";
-import crypto from "crypto";
-import seedrandom from "seedrandom";
export async function GET(request: NextRequest) {
const session = await auth();
@@ -94,75 +91,37 @@ export async function GET(request: NextRequest) {
},
};
- const skip = (page - 1) * limit;
-
let totalCount: number;
- let filteredCount: number;
let miis: Prisma.MiiGetPayload<{ select: typeof select }>[];
- if (sort === "random") {
- // Get all IDs that match the where conditions
- const matchingIds = await prisma.mii.findMany({
- where,
- select: { id: true },
- });
+ // Sorting by likes, newest, or oldest
+ let orderBy: Prisma.MiiOrderByWithRelationInput[];
- totalCount = matchingIds.length;
- filteredCount = Math.max(0, Math.min(limit, totalCount - skip));
-
- if (matchingIds.length === 0) return;
-
- // Use seed for consistent random results
- const randomSeed = seed || crypto.randomInt(0, 1_000_000_000);
- const rng = seedrandom(randomSeed.toString());
-
- // Randomize all IDs using the Durstenfeld algorithm
- for (let i = matchingIds.length - 1; i > 0; i--) {
- const j = Math.floor(rng() * (i + 1));
- [matchingIds[i], matchingIds[j]] = [matchingIds[j], matchingIds[i]];
- }
-
- // Convert to number[] array
- const selectedIds = matchingIds.slice(skip, skip + limit).map((i) => i.id);
-
- miis = await prisma.mii.findMany({
- where: {
- id: { in: selectedIds },
- },
- select,
- });
+ if (sort === "likes") {
+ orderBy = [{ likedBy: { _count: "desc" } }, { name: "asc" }];
+ } else if (sort === "oldest") {
+ orderBy = [{ createdAt: "asc" }, { name: "asc" }];
} else {
- // Sorting by likes, newest, or oldest
- let orderBy: Prisma.MiiOrderByWithRelationInput[];
-
- if (sort === "likes") {
- orderBy = [{ likedBy: { _count: "desc" } }, { name: "asc" }];
- } else if (sort === "oldest") {
- orderBy = [{ createdAt: "asc" }, { name: "asc" }];
- } else {
- // default to newest
- orderBy = [{ createdAt: "desc" }, { name: "asc" }];
- }
-
- [totalCount, filteredCount, miis] = await Promise.all([
- prisma.mii.count({ where: { ...where } }), // TODO: User id
- prisma.mii.count({ where, skip, take: limit }),
- prisma.mii.findMany({
- where,
- orderBy,
- select,
- skip: (page - 1) * limit,
- take: limit,
- }),
- ]);
+ // default to newest
+ orderBy = [{ createdAt: "desc" }, { name: "asc" }];
}
+ [totalCount, miis] = await Promise.all([
+ prisma.mii.count({ where: { ...where } }), // TODO: User id
+ prisma.mii.findMany({
+ where,
+ orderBy,
+ select,
+ skip: (page - 1) * limit,
+ take: limit,
+ }),
+ ]);
+
const lastPage = Math.ceil(totalCount / limit);
return NextResponse.json({
miis,
totalCount,
- filteredCount,
lastPage,
});
}
diff --git a/backend/src/lib/auth.ts b/backend/src/lib/auth.ts
index 08da09d..9a04006 100644
--- a/backend/src/lib/auth.ts
+++ b/backend/src/lib/auth.ts
@@ -43,7 +43,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
},
async redirect({ url, baseUrl }) {
- return process.env.FRONTEND_URL ?? "http://localhost:4321";
+ return process.env.NEXT_PUBLIC_FRONTEND_URL ?? "http://localhost:4321";
},
},
});
diff --git a/frontend/package.json b/frontend/package.json
index e1c82fd..6905b0e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -30,7 +30,6 @@
"react-dropzone": "^15.0.0",
"react-image-crop": "^11.0.10",
"react-router": "^7.14.1",
- "seedrandom": "^3.0.5",
"tailwindcss": "^4.2.2",
"zod": "^4.3.6"
},
diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx
index 19b8020..72a2ba0 100644
--- a/frontend/src/components/header.tsx
+++ b/frontend/src/components/header.tsx
@@ -31,8 +31,7 @@ export default function Header() {
- {" "}
- Submit{" "}
+ Submit
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 23cc42b..fcaac8f 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,4 +1,4 @@
-import { Suspense, useEffect, useState } from "react";
+import { Suspense, useEffect, useMemo, useState } from "react";
import FilterMenu from "../components/mii/list/filter-menu";
import SortSelect from "../components/mii/list/sort-select";
import MiiGrid from "../components/mii/list/mii-grid";
@@ -7,13 +7,12 @@ import Skeleton from "../components/mii/list/skeleton";
interface ApiResponse {
totalCount: number;
- filteredCount: number;
miis: any[];
lastPage: number;
}
export default function IndexPage() {
- const searchParams = new URLSearchParams(location.search);
+ const searchParams = useMemo(() => new URLSearchParams(location.search), []);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
@@ -31,7 +30,7 @@ export default function IndexPage() {
console.error(err);
setLoading(false);
});
- }, []);
+ }, [searchParams]);
return (
<>
@@ -46,19 +45,8 @@ export default function IndexPage() {
- {data.totalCount == data.filteredCount ? (
- <>
- {data.totalCount}
- {data.totalCount === 1 ? "Mii" : "Miis"}
- >
- ) : (
- <>
- {data.filteredCount}
- of
- {data.totalCount}
- Miis
- >
- )}
+ {data.totalCount}
+ {data.totalCount === 1 ? "Mii" : "Miis"}
diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx
index 69836f2..29d838e 100644
--- a/frontend/src/pages/login.tsx
+++ b/frontend/src/pages/login.tsx
@@ -1,6 +1,14 @@
import { Icon } from "@iconify/react";
+import { useStore } from "@nanostores/react";
+import { useNavigate } from "react-router";
+import { session } from "../session";
export default function LoginPage() {
+ const navigate = useNavigate();
+ const $session = useStore(session);
+
+ if ($session) navigate("/");
+
const API_URL = import.meta.env.VITE_API_URL;
return (
diff --git a/frontend/src/pages/submit.tsx b/frontend/src/pages/submit.tsx
index 7365afa..95926ab 100644
--- a/frontend/src/pages/submit.tsx
+++ b/frontend/src/pages/submit.tsx
@@ -1,5 +1,12 @@
-import SubmitForm from "../components/submit-form";
-
-export default function SubmitPage() {
- return ;
-}
\ No newline at end of file
+import { useStore } from "@nanostores/react";
+import SubmitForm from "../components/submit-form";
+import { session } from "../session";
+import { useNavigate } from "react-router";
+
+export default function SubmitPage() {
+ const navigate = useNavigate();
+ const $session = useStore(session);
+
+ if (!$session) navigate("/login");
+ return ;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ee9035a..89db52f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,12 +16,6 @@ importers:
'@auth/prisma-adapter':
specifier: 2.11.1
version: 2.11.1(@prisma/client@6.19.3(prisma@6.19.3(typescript@6.0.2))(typescript@6.0.2))
- '@bprogress/next':
- specifier: ^3.2.12
- version: 3.2.12(next@16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- '@hello-pangea/dnd':
- specifier: ^18.0.1
- version: 18.0.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@prisma/client':
specifier: ^6.19.2
version: 6.19.3(prisma@6.19.3(typescript@6.0.2))(typescript@6.0.2)
@@ -31,24 +25,15 @@ importers:
bit-buffer:
specifier: ^0.3.0
version: 0.3.0
- canvas-confetti:
- specifier: ^1.9.4
- version: 1.9.4
dayjs:
specifier: ^1.11.20
version: 1.11.20
downshift:
specifier: ^9.3.2
version: 9.3.2(react@19.2.5)
- embla-carousel-react:
- specifier: ^8.6.0
- version: 8.6.0(react@19.2.5)
file-type:
specifier: ^22.0.1
version: 22.0.1
- jsqr:
- specifier: ^1.4.0
- version: 1.4.0
next:
specifier: 16.2.3
version: 16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -64,30 +49,18 @@ importers:
react-dom:
specifier: ^19.2.5
version: 19.2.5(react@19.2.5)
- react-dropzone:
- specifier: ^15.0.0
- version: 15.0.0(react@19.2.5)
- react-image-crop:
- specifier: ^11.0.10
- version: 11.0.10(react@19.2.5)
redis:
specifier: ^5.11.0
version: 5.12.1
satori:
specifier: ^0.26.0
version: 0.26.0
- seedrandom:
- specifier: ^3.0.5
- version: 3.0.5
sharp:
specifier: ^0.34.5
version: 0.34.5
sjcl-with-all:
specifier: 1.0.8
version: 1.0.8
- swr:
- specifier: ^2.4.1
- version: 2.4.1(react@19.2.5)
zod:
specifier: ^4.3.6
version: 4.3.6
@@ -101,9 +74,6 @@ importers:
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
- '@types/canvas-confetti':
- specifier: ^1.9.0
- version: 1.9.0
'@types/node':
specifier: ^25.6.0
version: 25.6.0
@@ -113,9 +83,6 @@ importers:
'@types/react-dom':
specifier: ^19.2.3
version: 19.2.3(@types/react@19.2.14)
- '@types/seedrandom':
- specifier: ^3.0.8
- version: 3.0.8
'@types/sjcl':
specifier: ^1.0.34
version: 1.0.34
@@ -200,9 +167,6 @@ importers:
react-router:
specifier: ^7.14.1
version: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- seedrandom:
- specifier: ^3.0.5
- version: 3.0.5
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
@@ -392,13 +356,6 @@ packages:
'@bprogress/core@1.3.4':
resolution: {integrity: sha512-q/AqpurI/1uJzOrQROuZWixn/+ARekh+uvJGwLCP6HQ/EqAX4SkvNf618tSBxL4NysC0MwqAppb/mRw6Tzi61w==}
- '@bprogress/next@3.2.12':
- resolution: {integrity: sha512-/ZvNwbAd0ty9QiQwCfT2AfwWVdAaEyCPx5RUz3CfiiJS/OLBohhDz/IC/srhwK9GnXeXavvtiUrpKzN5GJDwlw==}
- peerDependencies:
- next: '>=13.0.0'
- react: '>=18.0.0'
- react-dom: '>=18.0.0'
-
'@bprogress/react@1.2.7':
resolution: {integrity: sha512-MqJfHW+R5CQeWqyqrLxUjdBRHk24Xl63OkBLo5DMWqUqocUikRTfCIc/jtQQbPk7BRfdr5OP3Lx7YlfQ9QOZMQ==}
peerDependencies:
@@ -1717,10 +1674,6 @@ packages:
defu@6.1.7:
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
- dequal@2.0.3:
- resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
- engines: {node: '>=6'}
-
destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
@@ -2801,9 +2754,6 @@ packages:
schema-dts@2.0.0:
resolution: {integrity: sha512-t7NoCy3Rn5GHGx6p7s1qIYK/AeIb8ZxJNR9WUNFkwMv2CiiGZBmqqYWc2FlZVm5ZbiHMY4OvBWhj7QtyrFO2Jw==}
- seedrandom@3.0.5:
- resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
-
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -2936,11 +2886,6 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
- swr@2.4.1:
- resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==}
- peerDependencies:
- react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-
tailwindcss@4.2.2:
resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
@@ -3277,14 +3222,6 @@ snapshots:
'@bprogress/core@1.3.4': {}
- '@bprogress/next@3.2.12(next@16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
- dependencies:
- '@bprogress/core': 1.3.4
- '@bprogress/react': 1.2.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- next: 16.2.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- react: 19.2.5
- react-dom: 19.2.5(react@19.2.5)
-
'@bprogress/react@1.2.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
dependencies:
'@bprogress/core': 1.3.4
@@ -4429,8 +4366,6 @@ snapshots:
defu@6.1.7: {}
- dequal@2.0.3: {}
-
destr@2.0.5: {}
detect-libc@2.1.2: {}
@@ -5722,8 +5657,6 @@ snapshots:
transitivePeerDependencies:
- typescript
- seedrandom@3.0.5: {}
-
semver@6.3.1: {}
semver@7.7.4: {}
@@ -5908,12 +5841,6 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
- swr@2.4.1(react@19.2.5):
- dependencies:
- dequal: 2.0.3
- react: 19.2.5
- use-sync-external-store: 1.6.0(react@19.2.5)
-
tailwindcss@4.2.2: {}
tapable@2.3.2: {}