Skip to content

Commit

Permalink
Merge pull request #386 from mfts/feat/help-center
Browse files Browse the repository at this point in the history
feat: add help articles
  • Loading branch information
mfts authored Apr 24, 2024
2 parents e42ba96 + 1a5c18e commit 5babc33
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 23 deletions.
128 changes: 128 additions & 0 deletions app/(static)/help/article/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { getHelpArticles, getHelpArticle } from "@/lib/content/help";
import { ContentBody } from "@/components/mdx/post-body";
import { notFound } from "next/navigation";
import Link from "next/link";
import BlurImage from "@/components/blur-image";
import { constructMetadata, formatDate } from "@/lib/utils";
import { Metadata } from "next";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import TableOfContents from "@/components/mdx/table-of-contents";

export async function generateStaticParams() {
const articles = await getHelpArticles();
return articles.map((article) => ({ slug: article?.data.slug }));
}

export const generateMetadata = async ({
params,
}: {
params: {
slug: string;
};
}): Promise<Metadata> => {
const article = (await getHelpArticles()).find(
(article) => article?.data.slug === params.slug,
);
const { title, summary: description, image } = article?.data || {};

return constructMetadata({
title: `${title} - Papermark`,
description,
image,
});
};

export default async function BlogPage({
params,
}: {
params: { slug: string };
}) {
const article = await getHelpArticle(params.slug);
if (!article) return notFound();

// const category = article.data.categories ? article.data.categories[0] : "";

return (
<>
<div className="max-w-7xl w-full mx-auto px-4 md:px-8 mb-10">
<div className="flex max-w-screen-sm flex-col space-y-4 pt-16">
<div className="flex items-center space-x-4">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage>Help Center</BreadcrumbPage>
</BreadcrumbItem>
{/* <BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/help/category/${category}`}>{category}</Link>
</BreadcrumbLink>
</BreadcrumbItem> */}
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>{article.data.title}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
<h1 className="text-4xl md:text-5xl text-balance">
{article.data.title}
</h1>
<p className="text-lg text-gray-600">{article.data.summary}</p>

<div className="items-center space-x-4 flex flex-col self-start">
<Link
href={`https://twitter.com/mfts0`}
className="group flex items-center space-x-3"
target="_blank"
rel="noopener noreferrer"
>
<BlurImage
src={`https://pbs.twimg.com/profile_images/1176854646343852032/iYnUXJ-m_400x400.jpg`}
alt={`Marc Seitz`}
width={40}
height={40}
className="rounded-full transition-all group-hover:brightness-90"
/>
<div className="flex flex-col">
<p className="font-semibold text-gray-700">Marc Seitz</p>
<p className="text-sm text-gray-500">@mfts0</p>
</div>
</Link>
</div>
<div className="flex items-center space-x-4 text-gray-700">
<time dateTime={article.data.publishedAt} className="text-sm">
Last updated {formatDate(article.data.publishedAt, true)}
</time>
</div>
</div>
</div>

<div className="relative">
<div className="grid grid-cols-4 gap-10 py-10 max-w-7xl w-full mx-auto px-4 md:px-8">
<div className="relative col-span-4 mb-10 flex flex-col space-y-8 bg-white md:col-span-3 sm:border-r sm:border-orange-500">
<div
data-mdx-container
className="prose prose-h2:mb-2 first:prose-h2:mt-0 prose-h2:mt-10 prose-headings:font-medium sm:max-w-screen-md sm:pr-2 md:pr-0"
>
<ContentBody>{article.body}</ContentBody>
</div>
</div>

<div className="sticky top-14 col-span-1 hidden flex-col divide-y divide-gray-200 self-start sm:flex">
<div className="flex flex-col space-y-4">
<TableOfContents items={article.toc} />
</div>
</div>
</div>
</div>
</>
);
}
6 changes: 3 additions & 3 deletions app/(static)/solutions/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default async function PagePage({
</p>
<div className="pt-8 space-x-2">
<Link href="/login">
<Button className="text-white text-balance bg-gray-900 rounded-3xl hover:bg-gray-800 justify-center text-balance">
<Button className="text-white text-balance bg-gray-900 rounded-3xl hover:bg-gray-800 justify-center">
{page.button}
</Button>
</Link>
Expand Down Expand Up @@ -99,7 +99,7 @@ export default async function PagePage({
</div>
<div className="pt-8 space-x-2">
<Link href="/login">
<Button className="text-white text-balance bg-gray-900 rounded-3xl hover:bg-gray-800 justify-center text-balance">
<Button className="text-white text-balance bg-gray-900 rounded-3xl hover:bg-gray-800 justify-center">
{page.button}
</Button>
</Link>
Expand Down Expand Up @@ -171,7 +171,7 @@ export default async function PagePage({
</div>
<div className="pt-8 space-x-2">
<Link href="/login">
<Button className="text-white text-balance bg-gray-900 rounded-3xl hover:bg-gray-800 justify-center text-balance">
<Button className="text-white text-balance bg-gray-900 rounded-3xl hover:bg-gray-800 justify-center">
{page.button}
</Button>
</Link>
Expand Down
13 changes: 12 additions & 1 deletion app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { getPosts, getAlternatives, getPages } from "@/lib/content";
import {
getPosts,
getAlternatives,
getPages,
getHelpArticles,
} from "@/lib/content";
import { MetadataRoute } from "next";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts();
const solutions = await getPages();
const alternatives = await getAlternatives();
const helpArticles = await getHelpArticles();
const blogLinks = posts.map((post) => ({
url: `https://www.papermark.io/blog/${post?.data.slug}`,
lastModified: new Date().toISOString().split("T")[0],
Expand All @@ -17,6 +23,10 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
url: `https://www.papermark.io/alternatives/${alternative?.slug}`,
lastModified: new Date().toISOString().split("T")[0],
}));
const helpArticleLinks = helpArticles.map((article) => ({
url: `https://www.papermark.io/help/article/${article?.data.slug}`,
lastModified: new Date().toISOString().split("T")[0],
}));

return [
{
Expand Down Expand Up @@ -54,5 +64,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
...blogLinks,
...solutionLinks,
...alternativeLinks,
...helpArticleLinks,
];
}
18 changes: 18 additions & 0 deletions components/mdx/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,23 @@ export const mdxComponents: MDXComponents = {
</Link>
);
},
h2: ({ children, ...props }) => (
<h2
data-mdx-heading
className="text-2xl font-semibold text-black"
id={props.id}
>
{children}
</h2>
),
h3: ({ children, ...props }) => (
<h3
data-mdx-heading
className="text-xl font-semibold text-black"
id={props.id}
>
{children}
</h3>
),
// any other components you want to use in your markdown
};
105 changes: 105 additions & 0 deletions components/mdx/table-of-contents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"use client";

// import useCurrentAnchor from "#/lib/hooks/use-current-anchor";
import { cn } from "@/lib/utils";
import slugify from "@sindresorhus/slugify";
import Link from "next/link";

export default function TableOfContents({
items,
}: {
items: {
text: string;
level: number;
}[];
}) {
const currentAnchor = useCurrentAnchor();

return (
<div className="grid gap-4 -ml-[2.55rem] pl-10 border-l border-orange-500">
{items &&
items.map((item, idx) => {
const itemId = slugify(item.text);
return (
<Link
key={itemId}
href={`#${itemId}`}
className={cn("text-sm text-gray-500 ", {
"border-l-2 border-black -ml-[2.6rem] pl-10 text-black":
currentAnchor ? currentAnchor === itemId : idx === 0,
})}
>
{item.text}
</Link>
);
})}
</div>
);
}

import { useEffect, useState } from "react";

function useCurrentAnchor() {
const [currentAnchor, setCurrentAnchor] = useState<string | null>(null);

useEffect(() => {
const mdxContainer: HTMLElement | null = document.querySelector(
"[data-mdx-container]",
);

if (!mdxContainer) return;

const offsetTop = 200;

const observer = new IntersectionObserver(
(entries) => {
let currentEntry = entries[0];
if (!currentEntry) return;

const offsetBottom =
(currentEntry.rootBounds?.height || 0) * 0.3 + offsetTop;

for (let i = 1; i < entries.length; i++) {
const entry = entries[i];
if (!entry) break;

if (
entry.boundingClientRect.top <
currentEntry.boundingClientRect.top ||
currentEntry.boundingClientRect.bottom < offsetTop
) {
currentEntry = entry;
}
}

let target: Element | undefined = currentEntry.target;

// if the target is too high up, we need to find the next sibling
while (target && target.getBoundingClientRect().bottom < offsetTop) {
target = siblings.get(target)?.next;
}

// if the target is too low, we need to find the previous sibling
while (target && target.getBoundingClientRect().top > offsetBottom) {
target = siblings.get(target)?.prev;
}
if (target) setCurrentAnchor(target.id);
},
{
threshold: 1,
rootMargin: `-${offsetTop}px 0px 0px 0px`,
},
);

const siblings = new Map();

const anchors = mdxContainer?.querySelectorAll("[data-mdx-heading]");
anchors.forEach((anchor) => observer.observe(anchor));

return () => {
observer.disconnect();
};
}, []);

return currentAnchor?.replace("#", "");
}
4 changes: 4 additions & 0 deletions lib/content/alternative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type Alternative = {
// this means getPosts() will only be called once per page build, even though we may call it multiple times
// when rendering the page.
export const getAlternatives = async () => {
if (!process.env.CONTENT_BASE_URL) {
return [];
}

const response = await fetch(
`${process.env.CONTENT_BASE_URL}/api/alternatives`,
{
Expand Down
4 changes: 4 additions & 0 deletions lib/content/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const GITHUB_CONTENT_TOKEN = process.env.GITHUB_CONTENT_TOKEN;
const GITHUB_CONTENT_REPO = process.env.GITHUB_CONTENT_REPO;

export const getPostsRemote = cache(async () => {
if (!GITHUB_CONTENT_REPO || !GITHUB_CONTENT_TOKEN) {
return [];
}

const apiUrl = `https://api.github.com/repos/${GITHUB_CONTENT_REPO}/contents/content/blog`;
const headers = {
Authorization: `Bearer ${GITHUB_CONTENT_TOKEN}`,
Expand Down
Loading

0 comments on commit 5babc33

Please sign in to comment.