Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: share buttons and metadata #25

Merged
merged 10 commits into from
Jul 12, 2024
Binary file modified bun.lockb
Binary file not shown.
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"react-hook-form": "^7.52.0",
"react-infinite-scroll-component": "^6.1.0",
"react-select": "^5.8.0",
"react-share": "^5.1.0",
"server-only": "^0.0.1",
"sharp": "^0.33.3",
"sonner": "^1.5.0",
Expand Down
105 changes: 79 additions & 26 deletions src/app/[username]/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,59 @@
import { type Metadata, type ResolvingMetadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import parse from "html-react-parser";

import { Navbar } from "@/components/common/navbar";
import {
CopyLink,
Facebook,
Instapaper,
Line,
Linkedin,
Telegram,
Twitter,
Whatsapp,
} from "@/components/common/share-buttons";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { abbreviation, calculateReadTime, momentId } from "@/lib/utils";
import { getServerAuthSession } from "@/server/auth";
import { api } from "@/trpc/server";

export default async function PostPage({
params,
}: {
type PostPageProps = {
params: {
username: string;
slug: string;
};
}) {
};

export async function generateMetadata(
{ params }: PostPageProps,
parent: ResolvingMetadata,
): Promise<Metadata> {
const username = params.username.substring(3);
const { post, author } = await api.post.byParams.query({
slug: params.slug,
username: username,
});

const previousImages = (await parent).openGraph?.images ?? [];

const description =
(post?.content?.length ?? 0) > 160
? `${post?.content.slice(0, 160)}...`
: post?.content;

return {
title: `${post?.metaTitle} oleh ${author?.name}`,
description: description,
openGraph: {
images: [`${post?.image}`, ...previousImages],
},
};
}

export default async function PostPage({ params }: PostPageProps) {
const session = await getServerAuthSession();
const username = params.username.substring(3);
const { post, author } = await api.post.byParams.query({
Expand All @@ -32,7 +69,7 @@ export default async function PostPage({
<>
<Navbar session={session} />
<main className="container mt-20 flex min-h-screen flex-col gap-y-4 pb-16 md:px-24 lg:px-48">
<h2 className="mt-2 scroll-m-20 text-pretty font-serif text-5xl font-bold tracking-tight">
<h2 className="mt-2 scroll-m-20 text-pretty font-serif text-4xl font-bold tracking-wider sm:text-5xl">
{post.title}
</h2>
<div className="flex flex-wrap gap-2">
Expand All @@ -45,33 +82,49 @@ export default async function PostPage({
))}
</div>

<div className="mt-4 flex gap-x-4">
<Link href={`/@${author.username}`}>
<Avatar className="h-12 w-12">
<AvatarImage
src={author.image ?? ""}
alt={author.name + " profile picture"}
/>
<AvatarFallback>{abbreviation(author.name)}</AvatarFallback>
</Avatar>
</Link>
<div className="flex flex-col justify-end gap-y-1">
<div className="mt-4 flex flex-col justify-between gap-x-3 gap-y-6 sm:flex-row">
<div className="flex items-center gap-x-4">
<Link href={`/@${author.username}`}>
<p className="text-sm">{author.name}</p>
<Avatar className="h-12 w-12">
<AvatarImage
src={author.image ?? ""}
alt={author.name + " profile picture"}
/>
<AvatarFallback>{abbreviation(author.name)}</AvatarFallback>
</Avatar>
</Link>
<div className="flex items-center gap-x-2">
<span className="text-sm text-muted-foreground">
{calculateReadTime(post.content)} menit baca
</span>
<span className="text-sm text-muted-foreground">•</span>
<span className="text-sm text-muted-foreground">
{momentId(post.publishedAt).fromNow()}
</span>
<div className="flex flex-col justify-end gap-y-1">
<Link href={`/@${author.username}`}>
<p className="text-sm">{author.name}</p>
</Link>
<div className="flex items-center gap-x-2">
<span className="text-sm text-muted-foreground">
{calculateReadTime(post.content)} menit baca
</span>
<span className="text-sm text-muted-foreground">•</span>
<span className="text-sm text-muted-foreground">
{momentId(post.publishedAt).fromNow()}
</span>
</div>
</div>
</div>

<div className="flex flex-col gap-x-4 gap-y-1 sm:flex-row sm:items-end">
<p className="text-sm text-muted-foreground">Bagikan ke: </p>
<div className="flex flex-wrap items-start sm:max-w-40 lg:max-w-80">
<Whatsapp />
<Telegram />
<Line />
<Linkedin />
<Instapaper />
<Facebook />
<Twitter />
<CopyLink />
</div>
</div>
</div>

<article className="prose mt-4 min-h-screen max-w-none dark:prose-invert lg:prose-xl prose-headings:border-b prose-headings:font-serif prose-headings:font-semibold prose-headings:tracking-tight">
<article className="prose mt-4 min-h-screen max-w-none dark:prose-invert lg:prose-xl prose-headings:border-b prose-headings:border-border prose-headings:font-serif prose-headings:font-semibold prose-headings:tracking-tight prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-2xl prose-h4:text-xl prose-h5:text-lg sm:prose-h1:text-6xl sm:prose-h2:text-5xl sm:prose-h3:text-3xl sm:prose-h4:text-2xl sm:prose-h5:text-xl ">
{parse(post.rawHtml)}
</article>
</main>
Expand Down
28 changes: 23 additions & 5 deletions src/app/[username]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type Metadata, type ResolvingMetadata } from "next";
import Image from "next/image";
import Link from "next/link";

Expand All @@ -8,11 +9,28 @@ import { cn } from "@/lib/utils";
import { getServerAuthSession } from "@/server/auth";
import { api } from "@/trpc/server";

export default async function UserPage({
params,
}: {
type UserPageProps = {
params: { username: string };
}) {
};

export async function generateMetadata(
{ params }: UserPageProps,
parent: ResolvingMetadata,
): Promise<Metadata> {
const username = params.username.replace("%40", "");
const user = await api.user.byUsername.query(username);
const previousImages = (await parent).openGraph?.images ?? [];

return {
title: `${user?.name} ${user?.position?.toUpperCase()} ${user?.department?.acronym.toUpperCase()}`,
description: `Mengenal lebih dekat ${user?.name}, simak selengkapnya di sini!`,
openGraph: {
images: [`${user?.image}`, ...previousImages],
},
};
}

export default async function UserPage({ params }: UserPageProps) {
const session = await getServerAuthSession();
const username = params.username.replace("%40", "");

Expand All @@ -39,7 +57,7 @@ export default async function UserPage({
<p className="mb-1 text-lg capitalize">{`${user?.position}`}</p>
) : (
<p className="mb-1 text-lg capitalize">
{`${user?.position} ${user?.department?.name}`}
{`${user?.position} ${user?.department?.acronym}`}
</p>
)}

Expand Down
22 changes: 22 additions & 0 deletions src/app/tag/[tag-slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
import { type Metadata } from "next";

import { TagResult } from "@/components/tag/tag-result";
import { api } from "@/trpc/server";

type TagSlugPage = {
params: {
"tag-slug": string;
};
};

export async function generateMetadata({
params,
}: TagSlugPage): Promise<Metadata> {
const tag = await api.postTag.searchUnique.query({
slug: params["tag-slug"],
});

return {
title: `LABEL ${tag?.title.toUpperCase()} | HIMARPL`,
description: `Lihat semua artikel yang berkaitan dengan ${tag?.title} hanya di HIMARPL!`,
};
}

export default function TagSlugPage() {
return (
Expand Down
Loading