feat: page metadata

This commit is contained in:
trafficlunar 2025-04-25 21:09:42 +01:00
parent 2e9e4db6cb
commit dc38d2bc28
12 changed files with 182 additions and 4 deletions

View file

@ -1,6 +1,7 @@
DATABASE_URL="postgresql://postgres:frieren@localhost:5432/tomodachi-share?schema=public"
BASE_URL=https://tomodachi-share.trafficlunar.net
NEXTAUTH_URL=https://tomodachi-share.trafficlunar.net
NEXTAUTH_URL=https://tomodachi-share.trafficlunar.net # This should be the same as BASE_URL
AUTH_SECRET=XXXXXXXXXXXXXXXX
AUTH_DISCORD_ID=XXXXXXXXXXXXXXXX

View file

@ -1,7 +1,13 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import UsernameForm from "@/components/username-form";
export const metadata: Metadata = {
title: "Create your Username - TomodachiShare",
description: "Pick a unique username to start using TomodachiShare",
};
export default async function CreateUsernamePage() {
const session = await auth();

View file

@ -1,3 +1,4 @@
import { Metadata, ResolvingMetadata } from "next";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
@ -8,6 +9,21 @@ interface Props {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise<Metadata> {
const { slug } = await params;
const mii = await prisma.mii.findUnique({
where: {
id: Number(slug),
},
});
return {
title: `${mii?.name} - TomodachiShare`,
description: `Edit the name, tags, and images of '${mii?.name}'`,
};
}
export default async function MiiPage({ params }: Props) {
const { slug } = await params;
const session = await auth();

View file

@ -3,9 +3,10 @@ import Script from "next/script";
import { Lexend } from "next/font/google";
import "./globals.css";
import Providers from "./provider";
import Header from "@/components/header";
import Footer from "@/components/footer";
import Providers from "./provider";
const lexend = Lexend({
subsets: ["latin"],
@ -13,7 +14,7 @@ const lexend = Lexend({
export const metadata: Metadata = {
title: "TomodachiShare",
description: "Share your Tomodachi Life Miis",
description: "Discover and share Mii residents for your Tomodachi Life island!",
};
export default function RootLayout({

View file

@ -1,7 +1,13 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import LoginButtons from "@/components/login-buttons";
export const metadata: Metadata = {
title: "Login - TomodachiShare",
description: "Sign in with Discord or GitHub to upload Miis, and like others' creations",
};
export default async function LoginPage() {
const session = await auth();

View file

@ -1,3 +1,4 @@
import { Metadata, ResolvingMetadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";
@ -15,6 +16,65 @@ interface Props {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise<Metadata> {
const { slug } = await params;
const mii = await prisma.mii.findUnique({
where: {
id: Number(slug),
},
include: {
user: {
select: {
username: true,
},
},
_count: {
select: { likedBy: true }, // Get total like count
},
},
});
// Bots get redirected anyways
if (!mii) return {};
const miiImageUrl = `/mii/${mii.id}/mii.webp`;
const qrCodeUrl = `/mii/${mii.id}/qrcode.webp`;
const username = `@${mii.user.username}`;
return {
metadataBase: new URL(process.env.BASE_URL!),
title: `${mii.name} - TomodachiShare`,
description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${username} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`,
keywords: [`mii`, `tomodachi life`, `nintendo`, ...mii.tags],
creator: username,
category: "Gaming",
openGraph: {
locale: "en_US",
type: "article",
images: [miiImageUrl, qrCodeUrl],
siteName: "TomodachiShare",
publishedTime: mii.createdAt.toISOString(),
authors: username,
},
twitter: {
card: "summary_large_image",
title: `${mii.name} - TomodachiShare`,
description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${username} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`,
images: [miiImageUrl, qrCodeUrl],
creator: username,
},
alternates: {
canonical: `/mii/${mii.id}`,
},
robots: {
index: true,
follow: true,
},
};
}
export default async function MiiPage({ params }: Props) {
const { slug } = await params;
const session = await auth();
@ -26,7 +86,6 @@ export default async function MiiPage({ params }: Props) {
include: {
user: {
select: {
id: true,
username: true,
},
},

View file

@ -1,5 +1,11 @@
import Link from "next/link";
import { Icon } from "@iconify/react";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Not Found - TomodachiShare",
description: "The requested page could not be found on TomodachiShare",
};
export default function NotFound() {
return (

View file

@ -1,3 +1,10 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Privacy Policy - TomodachiShare",
description: "Learn how TomodachiShare collects, uses, and protects your data",
};
export default function PrivacyPage() {
return (
<div>

View file

@ -1,3 +1,4 @@
import { Metadata, ResolvingMetadata } from "next";
import { redirect } from "next/navigation";
import Image from "next/image";
import Link from "next/link";
@ -13,6 +14,62 @@ interface Props {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise<Metadata> {
const { slug } = await params;
const user = await prisma.user.findUnique({
where: {
id: Number(slug),
},
include: {
_count: {
select: {
miis: true,
},
},
},
});
// Bots get redirected anyways
if (!user) return {};
const joinDate = user.createdAt.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
});
return {
metadataBase: new URL(process.env.BASE_URL!),
title: `${user.name} (@${user.username}) - TomodachiShare`,
description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`,
keywords: [`tomodachi life`, `mii creator`, `nintendo`, `mii collection`, `profile`],
creator: user.username,
category: "Gaming",
openGraph: {
locale: "en_US",
type: "profile",
images: [user.image ?? "/missing.webp"],
siteName: "TomodachiShare",
username: user.username,
firstName: user.name,
},
twitter: {
card: "summary",
title: `${user.name} (@${user.username}) - TomodachiShare`,
description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`,
images: [user.image ?? "/missing.webp"],
creator: user.username!,
},
alternates: {
canonical: `/profile/${user.id}`,
},
robots: {
index: true,
follow: true,
},
};
}
export default async function ProfilePage({ params }: Props) {
const session = await auth();
const { slug } = await params;

View file

@ -1,3 +1,4 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";
import Image from "next/image";
import Link from "next/link";
@ -9,6 +10,11 @@ import { prisma } from "@/lib/prisma";
import ProfileSettings from "@/components/profile-settings";
export const metadata: Metadata = {
title: "Profile Settings - TomodachiShare",
description: "Change your account info or delete it",
};
export default async function ProfileSettingsPage() {
const session = await auth();

View file

@ -1,7 +1,13 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import SubmitForm from "@/components/submit-form";
export const metadata: Metadata = {
title: "Submit a Mii - TomodachiShare",
description: "Upload your Tomodachi Life Mii through its QR code and share it with others",
};
export default async function SubmitPage() {
const session = await auth();

View file

@ -1,3 +1,10 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Terms of Service - TomodachiShare",
description: "Review the rules and guidelines for using TomodachiShare",
};
export default function PrivacyPage() {
return (
<div>