feat: page metadata
This commit is contained in:
parent
2e9e4db6cb
commit
dc38d2bc28
12 changed files with 182 additions and 4 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue