mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-05-13 13:17:45 +00:00
fix: better admin queue
This commit is contained in:
parent
50bad620ce
commit
0bd2d6d565
5 changed files with 53 additions and 13 deletions
|
|
@ -89,6 +89,10 @@ export async function GET(request: NextRequest) {
|
||||||
_count: {
|
_count: {
|
||||||
select: { likedBy: true },
|
select: { likedBy: true },
|
||||||
},
|
},
|
||||||
|
// Admin
|
||||||
|
...(parentPage === "admin" && {
|
||||||
|
description: true,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let totalCount: number;
|
let totalCount: number;
|
||||||
|
|
|
||||||
1
frontend/public/logo.svg
Normal file
1
frontend/public/logo.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="130.734" height="105.615" viewBox="0 0 34.59 27.944"><rect width="32.208" height="25.562" x="1.191" y="1.191" rx="1.874" fill="#f8f8f8" stroke="#ff8904" stroke-width="2.381" paint-order="stroke fill markers"/><rect width="29.369" height="22.49" x="2.611" y="2.727" rx=".966" fill="#c8c8c8" paint-order="stroke fill markers"/><g fill="#fef3c6"><rect width="13.371" height="20.989" x="17.918" y="3.478" rx=".423" paint-order="stroke fill markers"/><rect width="13.371" height="20.989" x="3.301" y="3.478" rx=".423" paint-order="stroke fill markers"/></g><g fill="#ff8904"><use href="#B" paint-order="stroke fill markers"/><circle cx="9.986" cy="13.076" r="5.512" paint-order="stroke fill markers"/><use href="#B" x="14.204" y="-0.093" paint-order="stroke fill markers"/><circle cx="24.191" cy="12.983" r="5.512" paint-order="stroke fill markers"/></g><g fill="none" stroke="#c8c8c8" stroke-linejoin="round"><rect width="13.791" height="20.704" x="17.295" y="3.62" ry="1.146" rx="1.095" stroke-width="1.786" paint-order="stroke fill markers"/><rect width="13.366" height="21.167" x="3.301" y="3.389" ry="1.146" rx="1.095" stroke-width="1.323" paint-order="stroke fill markers"/></g><defs ><path id="B" d="M15.03 24.516c0-2.307-.961-4.439-2.522-5.592s-3.483-1.153-5.044 0-2.522 3.285-2.522 5.592h5.044z"/></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -9,7 +9,7 @@ import { Icon } from "@iconify/react";
|
||||||
import LikeButton from "../../like-button";
|
import LikeButton from "../../like-button";
|
||||||
import { useStore } from "@nanostores/react";
|
import { useStore } from "@nanostores/react";
|
||||||
import { session } from "../../../session";
|
import { session } from "../../../session";
|
||||||
import Carousel from "../../carousel";
|
import Description from "../../description";
|
||||||
|
|
||||||
interface ApiResponse {
|
interface ApiResponse {
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
|
|
@ -26,6 +26,7 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [data, setData] = useState<ApiResponse | null>(null);
|
const [data, setData] = useState<ApiResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [acceptingAll, setAcceptingAll] = useState(false);
|
||||||
|
|
||||||
const $session = useStore(session);
|
const $session = useStore(session);
|
||||||
|
|
||||||
|
|
@ -49,6 +50,28 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
});
|
});
|
||||||
}, [searchParams, userId, parentPage]);
|
}, [searchParams, userId, parentPage]);
|
||||||
|
|
||||||
|
async function handleAcceptAll() {
|
||||||
|
if (!data) return;
|
||||||
|
setAcceptingAll(true);
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
data.miis.map((mii) =>
|
||||||
|
fetch(`${import.meta.env.VITE_API_URL}/api/admin/accept-mii?id=${mii.id}`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
if (userId) params.append("userId", userId.toString());
|
||||||
|
if (parentPage) params.append("parentPage", parentPage);
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_API_URL}/api/mii/list?${params.toString()}`, { credentials: "include" });
|
||||||
|
if (res.ok) setData(await res.json());
|
||||||
|
} finally {
|
||||||
|
setAcceptingAll(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|
@ -62,6 +85,16 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative flex items-center justify-end gap-2 w-full md:max-w-2/3 max-md:justify-center">
|
<div className="relative flex items-center justify-end gap-2 w-full md:max-w-2/3 max-md:justify-center">
|
||||||
|
{parentPage === "admin" && data.miis.length > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={handleAcceptAll}
|
||||||
|
disabled={acceptingAll}
|
||||||
|
className="pill button flex items-center gap-1.5 px-3 py-1.5 bg-green-500! border-green-600! hover:bg-green-600! disabled:opacity-60 disabled:cursor-not-allowed text-white text-sm font-semibold rounded-xl shadow transition-colors"
|
||||||
|
>
|
||||||
|
<Icon icon="material-symbols:check-circle-rounded" className="text-base" />
|
||||||
|
{acceptingAll ? "Accepting…" : `Accept All`}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<FilterMenu />
|
<FilterMenu />
|
||||||
<SortSelect />
|
<SortSelect />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,15 +124,17 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<Carousel
|
<div className="grid grid-cols-2 gap-1 rounded-xl bg-zinc-200">
|
||||||
images={[
|
{[
|
||||||
`${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=mii`,
|
`${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=mii`,
|
||||||
...(mii.platform === "THREE_DS"
|
mii.platform === "THREE_DS"
|
||||||
? [`${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=qr-code`]
|
? `${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=qr-code`
|
||||||
: [`${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=features`]),
|
: `${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=features`,
|
||||||
...Array.from({ length: mii.imageCount }, (_, index) => `${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=image${index}`),
|
...Array.from({ length: mii.imageCount }, (_, i) => `${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=image${i}`),
|
||||||
]}
|
].map((src, i) => (
|
||||||
/>
|
<img key={i} src={src} alt="mii image" className="w-full bg-zinc-200" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="p-4 flex flex-col gap-1 h-full">
|
<div className="p-4 flex flex-col gap-1 h-full">
|
||||||
|
|
@ -115,6 +150,7 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tags" className="flex flex-wrap gap-1">
|
<div id="tags" className="flex flex-wrap gap-1">
|
||||||
{mii.tags.map((tag: string) => (
|
{mii.tags.map((tag: string) => (
|
||||||
<Link to={`?tags=${tag}`} key={tag} className="px-2 py-1 bg-orange-300 rounded-full text-xs">
|
<Link to={`?tags=${tag}`} key={tag} className="px-2 py-1 bg-orange-300 rounded-full text-xs">
|
||||||
|
|
@ -123,6 +159,8 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{parentPage === "admin" && mii.description && <Description text={mii.description} />}
|
||||||
|
|
||||||
<div className="mt-auto grid grid-cols-2 items-center">
|
<div className="mt-auto grid grid-cols-2 items-center">
|
||||||
<LikeButton likes={mii._count.likedBy} miiId={mii.id} isLiked={false} abbreviate />
|
<LikeButton likes={mii._count.likedBy} miiId={mii.id} isLiked={false} abbreviate />
|
||||||
|
|
||||||
|
|
@ -141,7 +179,6 @@ export default function MiiList({ parentPage, userId }: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Admin Controls */}
|
|
||||||
{parentPage === "admin" && (
|
{parentPage === "admin" && (
|
||||||
<div className="flex justify-between w-full col-span-2 mt-2">
|
<div className="flex justify-between w-full col-span-2 mt-2">
|
||||||
<div className="flex gap-1 text-3xl justify-center">
|
<div className="flex gap-1 text-3xl justify-center">
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,6 @@ import { Navigate } from "react-router";
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
const $session = useStore(session);
|
const $session = useStore(session);
|
||||||
if ($session === undefined) return <div className="p-6 text-center">Loading...</div>;
|
if ($session === undefined) return <div className="p-6 text-center">Loading...</div>;
|
||||||
if ($session === null || ($session && Number($session?.user?.id) !== import.meta.env.VITE_ADMIN_USER_ID)) return <Navigate to="/404" replace />;
|
if ($session === null || Number($session?.user?.id) != import.meta.env.VITE_ADMIN_USER_ID) return <Navigate to="/404" replace />;
|
||||||
return <MiiList parentPage="admin" />;
|
return <MiiList parentPage="admin" />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ export default function ProfileLayout() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = id ? id : $session.user!.id;
|
|
||||||
|
|
||||||
fetch(`${import.meta.env.VITE_API_URL}/api/profile/${userId}/info`)
|
fetch(`${import.meta.env.VITE_API_URL}/api/profile/${userId}/info`)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!res.ok) throw new Error("Failed to fetch profile");
|
if (!res.ok) throw new Error("Failed to fetch profile");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue