fix: random bug fixes and a bit of metadata reimplementation

This commit is contained in:
trafficlunar 2026-04-21 20:12:39 +01:00
parent 9e712530b0
commit b6839c8d21
3 changed files with 68 additions and 12 deletions

View file

@ -25,6 +25,7 @@ export default async function Reports({ searchParams }: { searchParams: { status
]); ]);
const totalPages = Math.ceil(total / PAGE_SIZE); const totalPages = Math.ceil(total / PAGE_SIZE);
const FRONTEND_URL = process.env.NEXT_PUBLIC_FRONTEND_URL;
const updateStatus = async (formData: FormData) => { const updateStatus = async (formData: FormData) => {
"use server"; "use server";
@ -88,21 +89,24 @@ export default async function Reports({ searchParams }: { searchParams: { status
<div className="grid grid-cols-4 text-xs text-zinc-600 mt-4 max-sm:grid-cols-2"> <div className="grid grid-cols-4 text-xs text-zinc-600 mt-4 max-sm:grid-cols-2">
<div> <div>
<p>Target ID</p> <p>Target ID</p>
<a href={report.reportType === "MII" ? `/mii/${report.targetId}` : `/profile/${report.targetId}`} className="text-blue-600 text-sm"> <a
href={report.reportType === "MII" ? `${FRONTEND_URL}/mii/${report.targetId}` : `${FRONTEND_URL}/profile/${report.targetId}`}
className="text-blue-600 text-sm"
>
{report.targetId} {report.targetId}
</a> </a>
</div> </div>
<div> <div>
<p>Creator ID</p> <p>Creator ID</p>
<a href={`/profile/${report.creatorId}`} className="text-blue-600 text-sm"> <a href={`${FRONTEND_URL}/profile/${report.creatorId}`} className="text-blue-600 text-sm">
{report.creatorId} {report.creatorId}
</a> </a>
</div> </div>
<div> <div>
<p>Reporter</p> <p>Reporter</p>
<a href={`/profile/${report.authorId}`} className="text-blue-600 text-sm"> <a href={`${FRONTEND_URL}/profile/${report.authorId}`} className="text-blue-600 text-sm">
{report.authorId} {report.authorId}
</a> </a>
</div> </div>

View file

@ -54,8 +54,36 @@ export default function MiiPage() {
if (loading || !mii) return <div className="p-6 text-center">Loading...</div>; if (loading || !mii) return <div className="p-6 text-center">Loading...</div>;
const images = [...Array.from({ length: mii.imageCount ?? 0 }, (_, index) => `${API_URL}/mii/${mii.id}/image?type=image${index}`)]; const images = [...Array.from({ length: mii.imageCount ?? 0 }, (_, index) => `${API_URL}/mii/${mii.id}/image?type=image${index}`)];
const metaTitle = `${mii.name} - TomodachiShare`;
const platformLabel = mii.platform === "SWITCH" ? "Switch Living the Dream" : "3DS";
const metaDescription = `Check out '${mii.name}', a ${platformLabel} Tomodachi Life Mii created by ${mii.user.name} on TomodachiShare with ${mii.likeCount ?? 0} likes.`;
const metaImage = `${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=metadata`;
const metaImageAlt = `${mii.name}, ${mii.tags?.join(", ")} ${mii.gender} Mii character`;
return ( return (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<title>{metaTitle}</title>
<meta name="description" content={metaDescription} />
<meta name="keywords" content={["mii", "tomodachi life", "nintendo", "tomodachishare", ...mii.tags].join(", ")} />
<link rel="canonical" href={`${import.meta.env.VITE_BASE_URL}/mii/${mii.id}`} />
{/* Open Graph */}
<meta property="og:type" content="article" />
<meta property="og:title" content={metaTitle} />
<meta property="og:description" content={metaDescription} />
<meta property="og:image" content={metaImage} />
<meta property="og:image:alt" content={metaImageAlt} />
<meta property="article:published_time" content={new Date(mii.createdAt).toISOString()} />
<meta property="article:author" content={`@${mii.user.name}`} />
{/* Twitter / X */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={metaTitle} />
<meta name="twitter:description" content={metaDescription} />
<meta name="twitter:image" content={metaImage} />
<meta name="twitter:image:alt" content={metaImageAlt} />
<meta name="twitter:creator" content={`@${mii.user.name}`} />
<div className="max-w-5xl w-full flex flex-col gap-4"> <div className="max-w-5xl w-full flex flex-col gap-4">
{mii.quarantined && ( {mii.quarantined && (
<div className="bg-red-100 border-2 border-red-400 rounded-2xl shadow-lg p-4 flex items-center gap-3 text-red-700"> <div className="bg-red-100 border-2 border-red-400 rounded-2xl shadow-lg p-4 flex items-center gap-3 text-red-700">

View file

@ -13,10 +13,10 @@ export default function ProfileLayout() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const $session = useStore(session); const $session = useStore(session);
const userId = Number($session ? id : $session?.user?.id);
useEffect(() => { useEffect(() => {
if ($session === undefined) return; // session still loading if ($session === undefined) return; // session still loading
const userId = id ?? $session?.user?.id;
if (!userId) { if (!userId) {
navigate("/404"); navigate("/404");
return; return;
@ -44,14 +44,38 @@ export default function ProfileLayout() {
return <div className="p-6 text-center">Loading...</div>; return <div className="p-6 text-center">Loading...</div>;
} }
const sessionUserId = $session?.user?.id ? Number($session.user.id) : null; const isAdmin = userId === Number(import.meta.env.VITE_ADMIN_USER_ID);
const page = location.pathname;
const isAdmin = sessionUserId === Number(import.meta.env.VITE_ADMIN_USER_ID);
const isContributor = import.meta.env.VITE_CONTRIBUTORS_USER_IDS?.split(",").includes(String(user?.id)); const isContributor = import.meta.env.VITE_CONTRIBUTORS_USER_IDS?.split(",").includes(String(user?.id));
const isOwnProfile = sessionUserId === user?.id; const isOwnProfile = userId === user?.id;
const joinDate = new Date(user.createdAt).toLocaleDateString("en-US", { month: "long", year: "numeric" });
const metaTitle = `${user.name} - TomodachiShare`;
const metaDescription = `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`;
const metaImage = user.image.startsWith("/profile")
? `${import.meta.env.VITE_API_URL}${user.image}`
: (user.image ?? `${import.meta.env.VITE_API_URL}/guest.png`);
return ( return (
<div> <div>
<title>{metaTitle}</title>
<meta name="description" content={metaDescription} />
<meta name="keywords" content="mii, tomodachi life, nintendo, mii creator, mii collection, profile" />
<link rel="canonical" href={`${import.meta.env.VITE_BASE_URL}/profile/${user.id}`} />
{/* Open Graph */}
<meta property="og:type" content="profile" />
<meta property="og:title" content={metaTitle} />
<meta property="og:description" content={metaDescription} />
<meta property="og:image" content={metaImage} />
<meta property="og:profile:username" content={user.name} />
{/* Twitter / X */}
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content={metaTitle} />
<meta name="twitter:description" content={metaDescription} />
<meta name="twitter:image" content={metaImage} />
<meta name="twitter:creator" content={user.name} />
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4 flex gap-4 mb-2 max-md:flex-col"> <div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4 flex gap-4 mb-2 max-md:flex-col">
<div className="flex w-full gap-4 overflow-x-scroll"> <div className="flex w-full gap-4 overflow-x-scroll">
{/* Profile picture */} {/* Profile picture */}
@ -110,19 +134,19 @@ export default function ProfileLayout() {
<span>Admin</span> <span>Admin</span>
</a> </a>
)} )}
{isOwnProfile && page !== "/profile/likes" && ( {isOwnProfile && location.pathname !== "/profile/likes" && (
<Link aria-label="Go to My Likes" to="/profile/likes"> <Link aria-label="Go to My Likes" to="/profile/likes">
<Icon icon="icon-park-solid:like" /> <Icon icon="icon-park-solid:like" />
<span>My Likes</span> <span>My Likes</span>
</Link> </Link>
)} )}
{isOwnProfile && page !== "/profile/settings" && ( {isOwnProfile && location.pathname !== "/profile/settings" && (
<Link aria-label="Go to Settings" to="/profile/settings"> <Link aria-label="Go to Settings" to="/profile/settings">
<Icon icon="material-symbols:settings-rounded" /> <Icon icon="material-symbols:settings-rounded" />
<span>Settings</span> <span>Settings</span>
</Link> </Link>
)} )}
{(page === "/profile/likes" || page === "/profile/settings") && ( {(location.pathname === "/profile/likes" || location.pathname === "/profile/settings") && (
<Link aria-label="Go Back to Profile" to={`/profile/${user.id}`}> <Link aria-label="Go Back to Profile" to={`/profile/${user.id}`}>
<Icon icon="tabler:chevron-left" /> <Icon icon="tabler:chevron-left" />
<span>Back</span> <span>Back</span>