Skip to content

Commit

Permalink
feat(saas): Change log System (#17)
Browse files Browse the repository at this point in the history
## Feature: Implement Changelog System

This pull request introduces a new changelog system to track and display updates and changes made to the application. 

**Key changes:**

* **Changelog Content:** Added MDX files for each changelog entry, allowing for rich formatting and content organization.
* **Changelog Page:** Created a dedicated page to display the changelog entries, sorted by date.
* **Navigation:** Added changelog links to the header and sidebar navigation for easy access. 
* **Data Fetching:** Implemented functions to fetch and parse changelog data from the MDX files.
* **Validation:** Included validation for changelog metadata using Zod schemas to ensure data consistency. 
* **Styling:**  Designed the changelog page and individual entries with clear and consistent styling.

**Additional notes:**

* The changelog system is easily extendable to accommodate future updates.
* This PR includes minor bug fixes and improvements to existing components. 

**Testing:**

* Manually tested the changelog page and navigation functionality. 

**Screenshot:** 

![Screenshot 2024-04-26 at 5.26.34 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/Rpcpuk9yNO4KuEizCWzY/9807d5f8-65c9-4d46-9d19-bc0c966c9127.png)
  • Loading branch information
alifarooq9 authored Apr 26, 2024
2 parents f53a4e7 + 28e9521 commit 7073f4c
Show file tree
Hide file tree
Showing 18 changed files with 198 additions and 21 deletions.
2 changes: 1 addition & 1 deletion starterkits/saas/src/app/(app)/_components/sidebar-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function SidebarNav({

const pathname = usePathname();

const sidebarNavitems = sidebarConfig.filterNavItems({
const sidebarNavitems = sidebarConfig.filteredNavItems({
removeIds: sidebarNavRemoveIds,
includedIds: sidebarNavIncludeIds,
});
Expand Down
5 changes: 3 additions & 2 deletions starterkits/saas/src/app/(web)/_components/header-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { usePathname } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { cn, isLinkActive } from "@/lib/utils";
import { navigation } from "@/config/header";
import { buttonVariants } from "@/components/ui/button";

/**
* For adding a new navigation item:
Expand All @@ -17,13 +18,13 @@ export function WebHeaderNav() {

return (
<nav className="flex items-center justify-center">
<ul className="flex items-center gap-8">
<ul className="flex items-center">
{navigation.map((item) => (
<li key={item.id}>
<Link
href={item.href}
className={cn(
"text-sm hover:underline hover:underline-offset-4",
buttonVariants({ variant: "link" }),
isLinkActive(item.href, pathname)
? "font-semibold"
: "font-medium",
Expand Down
2 changes: 1 addition & 1 deletion starterkits/saas/src/app/(web)/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default async function BlogSlugPage({ params }: BlogSlugPageProps) {

<div className="relative aspect-video max-h-[350px] w-full overflow-hidden rounded-md bg-muted/60">
<Image
src={blog.metaData.tumbnail}
src={blog.metaData.thumbnail}
alt={blog.metaData.title}
className="rounded-md"
fill
Expand Down
2 changes: 1 addition & 1 deletion starterkits/saas/src/app/(web)/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default async function BlogsPage() {
>
<div className="relative h-screen max-h-[350px] w-full overflow-hidden rounded-md bg-muted/60">
<Image
src={blog.metaData.tumbnail}
src={blog.metaData.thumbnail}
alt={blog.metaData.title}
fill
className="object-cover"
Expand Down
76 changes: 76 additions & 0 deletions starterkits/saas/src/app/(web)/changelog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
WebPageHeader,
WebPageWrapper,
} from "@/app/(web)/_components/general-components";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { siteConfig } from "@/config/site";
import { getChangelogs } from "@/server/actions/changelog";
import { format } from "date-fns";
import Image from "next/image";

export const dynamic = "force-static";

export default async function ChangeLogPage() {
//filter changelogs by date
const changelogs = (await getChangelogs()).sort(
(a, b) =>
Number(new Date(b.metaData.publishedAt)) -
Number(new Date(a.metaData.publishedAt)),
);

return (
<WebPageWrapper>
<WebPageHeader title="Change Log">
<p className="text-center text-base">
<span>
All the latest features, fixes and work to{" "}
{siteConfig.name}.
</span>
</p>
</WebPageHeader>
<div className="grid w-full max-w-4xl gap-8">
{changelogs.map((changelog) => (
<ChangeLogCard
key={changelog.metaData.slug}
{...changelog}
/>
))}
</div>
</WebPageWrapper>
);
}

type ChangeLogCardProps = Awaited<ReturnType<typeof getChangelogs>>[number];

function ChangeLogCard({ metaData, content }: ChangeLogCardProps) {
return (
<Card className="overflow-hidden">
<div className="relative h-[400px] w-full">
<Image
src={metaData.thumbnail}
alt={metaData.title}
fill
className="object-cover"
/>
</div>
<CardHeader>
<CardTitle className="text-3xl">v{metaData.version}</CardTitle>
<CardTitle className="text-xl">{metaData.title}</CardTitle>
<CardDescription>{metaData.description}</CardDescription>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
Published on {format(new Date(metaData.publishedAt), "PPP")}
</p>

{content}
</CardContent>
</Card>
);
}
5 changes: 5 additions & 0 deletions starterkits/saas/src/config/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ export const navigation: NavigationItem[] = [
href: siteUrls.docs,
label: "Docs",
},
{
id: "changelog",
href: siteUrls.changelog,
label: "Changelog",
},
];
4 changes: 2 additions & 2 deletions starterkits/saas/src/config/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ type FilterNavItemsProps = {
* @returns The filtered navigation items for the sidebar.
* */

export function filterNavItems({
export function filteredNavItems({
removeIds = [],
includedIds = [],
}: FilterNavItemsProps) {
Expand Down Expand Up @@ -209,5 +209,5 @@ export function filterNavItems({
export const sidebarConfig = {
navIds,
navigation,
filterNavItems,
filteredNavItems,
} as const;
1 change: 1 addition & 0 deletions starterkits/saas/src/config/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const siteUrls = {
support: "/support",
blog: "/blog",
docs: "/docs/introduction",
changelog: "/changelog",
maintenance: "/maintenance",
rapidlaunch: "https://www.rapidlaunch.xyz",
dashboard: {
Expand Down
2 changes: 1 addition & 1 deletion starterkits/saas/src/content/blog/create-saas-in-1-day.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ updatedAt: 2024-05-01
readTime: 5 min
tags: ["saas", "introduction"]
description: This is the introduction
tumbnail: https://fakeimg.pl/700x400/d1d1d1/6b6b6b
thumbnail: https://fakeimg.pl/700x400/d1d1d1/6b6b6b
---


Expand Down
2 changes: 1 addition & 1 deletion starterkits/saas/src/content/blog/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ publishedAt: 2022-01-01
readTime: 5 min
tags: ["introduction", "saas"]
description: This is the introduction
tumbnail: https://fakeimg.pl/700x400/d1d1d1/6b6b6b
thumbnail: https://fakeimg.pl/700x400/d1d1d1/6b6b6b
---

## Introduction
Expand Down
22 changes: 22 additions & 0 deletions starterkits/saas/src/content/changelogs/version-0.0.0.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: "Launch of RapidLaunch"
slug: "launch-of-rapidlaunch"
description: "RapidLaunch is a platform to create and launch SaaS products quickly."
version: "0.0.0"
publishedAt: 2024-04-01
thumbnail: "https://fakeimg.pl/896x400/d1d1d1/6b6b6b"
---

You can now change the monitors visibility to public. This will allow you to
share the monitor's metrics _(the overview page)_ with your users. The period is
restricted to **1d** and **7d** for now.

The monitor can be accessed either by the **public URL** or/and by embedding the
monitor within a **status page**.

- Public URL:
[openstatus.dev/public/monitors/1](https://openstatus.dev/public/monitors/1)
- Status Page URL:
[status.openstatus.dev/monitors/1](https://status.openstatus.dev/monitors/1)

You can enable public mode from the monitor _Danger_ section setting.
22 changes: 22 additions & 0 deletions starterkits/saas/src/content/changelogs/version-0.1.0.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: "Launch of RapidLaunch 2"
slug: "launch-of-rapidlaunch-2"
description: "RapidLaunch is a platform to create and launch SaaS products quickly."
version: "0.1.0"
publishedAt: 2024-08-01
thumbnail: "https://fakeimg.pl/896x400/d1d1d1/6b6b6b"
---

You can now change the monitors visibility to public. This will allow you to
share the monitor's metrics _(the overview page)_ with your users. The period is
restricted to **1d** and **7d** for now.

The monitor can be accessed either by the **public URL** or/and by embedding the
monitor within a **status page**.

- Public URL:
[openstatus.dev/public/monitors/1](https://openstatus.dev/public/monitors/1)
- Status Page URL:
[status.openstatus.dev/monitors/1](https://status.openstatus.dev/monitors/1)

You can enable public mode from the monitor _Danger_ section setting.
22 changes: 22 additions & 0 deletions starterkits/saas/src/content/changelogs/version-0.2.4.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: "Launch of RapidLaunch 2"
slug: "launch-of-rapidlaunch-2"
description: "RapidLaunch is a platform to create and launch SaaS products quickly."
version: "0.4.0"
publishedAt: 2024-10-06
thumbnail: "https://fakeimg.pl/896x400/d1d1d1/6b6b6b"
---

You can now change the monitors visibility to public. This will allow you to
share the monitor's metrics _(the overview page)_ with your users. The period is
restricted to **1d** and **7d** for now.

The monitor can be accessed either by the **public URL** or/and by embedding the
monitor within a **status page**.

- Public URL:
[openstatus.dev/public/monitors/1](https://openstatus.dev/public/monitors/1)
- Status Page URL:
[status.openstatus.dev/monitors/1](https://status.openstatus.dev/monitors/1)

You can enable public mode from the monitor _Danger_ section setting.
8 changes: 3 additions & 5 deletions starterkits/saas/src/lib/mdx.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { compileMDX } from "next-mdx-remote/rsc";
import { readdir, readFile } from "fs/promises";
import { docsMetaSchema } from "@/validations/mdx";
import path from "path";
import { getTableOfContents } from "@/lib/toc";
import { mdxComponents } from "@/components/mdx-components";
import { AutoIdsToHeading } from "@/lib/rehype-plugins";
import rehypePrism from "rehype-prism-plus";
import type { SomeZodObject } from "zod";

export async function getMDXData<MetaData>(dir: string) {
export async function getMDXData<MetaData>(dir: string, schema: SomeZodObject) {
const files = (await readdir(dir, "utf-8")).filter(
(file) => path.extname(file) === ".mdx",
);
Expand All @@ -30,9 +30,7 @@ export async function getMDXData<MetaData>(dir: string) {
components,
});

const validate = await docsMetaSchema.safeParseAsync(
mdxData.frontmatter,
);
const validate = await schema.safeParseAsync(mdxData.frontmatter);

if (!validate.success) {
throw new Error(
Expand Down
6 changes: 4 additions & 2 deletions starterkits/saas/src/server/actions/blog.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import "server-only";

import { getMDXData } from "@/lib/mdx";
import type { BlogMetaData } from "@/validations/mdx";
import { type BlogMetaData, blogMetaSchema } from "@/validations/mdx-content";

export async function getBlogs() {
const dir = "src/content/blog";
return await getMDXData<BlogMetaData>(dir);
return (await getMDXData<BlogMetaData>(dir, blogMetaSchema)).filter(
(blog) => !blog.metaData.isDraft,
);
}
14 changes: 14 additions & 0 deletions starterkits/saas/src/server/actions/changelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "server-only";

import { getMDXData } from "@/lib/mdx";
import {
type ChangelogMetaData,
changelogMetaSchema,
} from "@/validations/mdx-content";

export async function getChangelogs() {
const dir = "src/content/changelogs";
return (
await getMDXData<ChangelogMetaData>(dir, changelogMetaSchema)
).filter((changelog) => !changelog.metaData.isDraft);
}
6 changes: 4 additions & 2 deletions starterkits/saas/src/server/actions/docs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import "server-only";

import { getMDXData } from "@/lib/mdx";
import type { DocsMetaData } from "@/validations/mdx";
import { type DocsMetaData, docsMetaSchema } from "@/validations/mdx-content";

export async function getDocs() {
const dir = "src/content/docs";
return await getMDXData<DocsMetaData>(dir);
return (await getMDXData<DocsMetaData>(dir, docsMetaSchema)).filter(
(doc) => !doc.metaData.isDraft,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,26 @@ export type DocsMetaData = z.infer<typeof docsMetaSchema>;
export const blogMetaSchema = z.object({
title: z.string(),
slug: z.string(),
publishedAt: z.string().datetime(),
updatedAt: z.string().datetime().optional(),
publishedAt: z.date(),
updatedAt: z.date().optional(),
readTime: z.string(),
tags: z.array(z.string()).optional(),
description: z.string(),
tumbnail: z.string().url(),
thumbnail: z.string().url(),
featured: z.boolean().optional(),
isDraft: z.boolean().optional(),
});

export type BlogMetaData = z.infer<typeof blogMetaSchema>;

export const changelogMetaSchema = z.object({
title: z.string(),
slug: z.string(),
publishedAt: z.date(),
thumbnail: z.string().url(),
description: z.string(),
version: z.string(),
isDraft: z.boolean().optional(),
});

export type ChangelogMetaData = z.infer<typeof changelogMetaSchema>;

0 comments on commit 7073f4c

Please sign in to comment.