Skip to content

Commit

Permalink
feat(extensions:view): make the page dynamic (ISR)
Browse files Browse the repository at this point in the history
  • Loading branch information
virtual-designer committed Feb 7, 2024
1 parent 4988b58 commit a8cca97
Show file tree
Hide file tree
Showing 18 changed files with 296 additions and 128 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"extends": "next/core-web-vitals"
"extends": "next/core-web-vitals",
"rules": { "react/no-unescaped-entities": "off" }
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@next/mdx": "^14.1.0",
"@types/mdx": "^2.0.11",
"date-fns": "^3.3.1",
"firebase-admin": "^12.0.0",
"next": "14.1.0",
"next-mdx-remote": "^4.4.1",
"react": "^18",
Expand All @@ -39,4 +40,4 @@
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
}
175 changes: 111 additions & 64 deletions src/app/extension/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,119 @@
import ExtensionControls from "@/components/Extension/ExtensionControls";
import ExtensionInfoList from "@/components/Extension/ExtensionInfoList";
import ExtensionSecurity from "@/components/Extension/ExtensionSecurity";
import ExtensionIcon from "@/components/Extension/Extensionicon";
import { Divider } from "@/components/Layout/Divider";
import { INDEX_URL } from "@/config/urls";
import { getDB } from "@/firebase/app";
import { APIExtension } from "@/types/APIExtension";
import { ServerSidePageProps } from "@/types/ServerSidePageProps";
import { Button, Container } from "@mui/material";
import { Metadata } from "next";
import { MDXRemote } from "next-mdx-remote/rsc";
import { MdDownload } from "react-icons/md";
import { notFound } from "next/navigation";
import { HiShieldCheck } from "react-icons/hi2";

export const revalidate = 3600;

const getExtensionDoc = async (id: string) => {
const db = getDB();
const extensionRef = db.collection("extensions").doc(id);
const doc = await extensionRef.get();
const data = doc.data();
return { doc, data: doc.exists && data ? data : null };
};

async function getExtensionInformation(
id: string
): Promise<APIExtension | null> {
const response = await fetch(INDEX_URL, {
next: {
revalidate: 180,
},
});
const index = await response.json();
const extensionInfo = index[id];

if (!extensionInfo) {
return null;
}

const { doc, data } = await getExtensionDoc(id);

if (!data) {
return null;
}

const extensionUpdateTime = new Date(extensionInfo.updatedAt);
const readmeContents = extensionInfo.readmeFileURL
? await fetch(extensionInfo.readmeFileURL, {
next: {
revalidate: 180,
},
}).then((res) => res.text())
: undefined;

async function getExtensionInformation(id: string): Promise<APIExtension> {
return {
id,
name: "Anti RickRoll",
downloads: 183435,
name: extensionInfo.name,
downloads: data.downloads,
author: {
name: "Ar Rakin",
isVerified: true,
name: data.author.name,
isVerified: data.author.isVerified,
github: data.author.github
? `https://github.com/${encodeURIComponent(data.author.github)}`
: undefined,
},
description: extensionInfo.description,
homepage: extensionInfo.homepage,
issues: extensionInfo.issues,
repository: extensionInfo.repository,
license: extensionInfo.license,
licenseURL: extensionInfo.licenseURL,
icon: extensionInfo.iconURL,
security: data.security,
lastUpdated:
extensionUpdateTime.getTime() > doc.updateTime!.toMillis()
? extensionUpdateTime
: doc.updateTime!.toDate(),
readmeFileName: extensionInfo.readmeFileName,
readmeContents,
};
}

export async function generateMetadata({
params,
}: ServerSidePageProps): Promise<Metadata> {
const { id } = params;
const extension = await getExtensionInformation(id);

if (!extension) {
return {};
}

return {
title: `${extension.name} - SudoBot Extension Marketplace`,
description: extension.description,
openGraph: {
images: extension.icon,
},
description: "Prevent rickroll links from being sent in your server.",
homepage: "https://example.com",
issues: "https://github.com/onesoftnet/antirickroll/issues",
repository: "https://github.com/onesoftnet/antirickroll",
license: "GPL-3.0-or-later",
licenseURL: "https://spdx.org/licenses/GPL-3.0-or-later.html",
icon: "https://res.cloudinary.com/rakinar2/image/upload/v1707213937/antirickroll_qqvfgg.png",
security: "safe",
lastUpdated: new Date("2021-10-05T00:00:00Z"),
readmeFileName: "README.md",
readmeContents: `# Anti RickRoll\n\nPrevent rickroll links from being sent in your server.\n\n## Installation\n\n1. Go to the [homepage](https://example.com) of the extension.\n2. Click on the "Install" button.\n\n## License\n\nThis extension is licensed under the [GPL-3.0-or-later](https://spdx.org/licenses/GPL-3.0-or-later.html) license.\n\n## Issues\n\nIf you find any issues with the extension, please report them [here](https://example.com).`,
};
}

export default async function ExtensionPage({ params }: ServerSidePageProps) {
const { id } = params;
const extension = await getExtensionInformation(id);

if (!extension) {
notFound();
}

return (
<Container>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<main className="my-5 lg:my-10">
<div className="grid grid-cols-[80px_auto] md:grid-cols-[1fr_3fr_1fr] gap-3 md:gap-5">
<ExtensionIcon icon={extension.icon} />

<div className="relative md:hidden">
<h1 className="text-2xl md:text-3xl lg:text-4xl">
{extension.name}
</h1>
<p className="text-[#555] dark:text-[#999] text-sm md:text-base break-all pr-2 max-w-[100%]">
<code className="font-mono">{extension.id}</code>
</p>
</div>
<div>
<div className="relative hidden md:block">
<div className="flex flex-col md:flex-row items-start justify-between gap-3 md:gap-5">
<div className="flex items-start gap-3 md:gap-5 lg:gap-7">
<ExtensionIcon icon={extension.icon} />
<div>
<h1 className="text-2xl md:text-3xl lg:text-4xl">
{extension.name}
</h1>
Expand All @@ -59,39 +122,23 @@ export default async function ExtensionPage({ params }: ServerSidePageProps) {
{extension.id}
</code>
</p>
</div>
<ExtensionInfoList
className="hidden md:block"
extension={extension}
/>
</div>
<div className="hidden md:flex md:justify-end items-start">
{extension.security === "unsafe" ? (
<div className="flex px-2 py-2 items-center gap-2 cursor-not-allowed text-[#555] dark:text-[#999]">
<MdDownload className="inline" />
Install
</div>
) : (
<Button startIcon={<MdDownload />}>Install</Button>
)}
</div>
</div>
{extension.security === "safe" && (
<p className="flex items-center gap-1 mt-1 text-green-500 dark:text-green-400 text-sm md:text-base break-all pr-2 max-w-[100%]">
<HiShieldCheck className="inline" /> Secure
</p>
)}

<div className="md:hidden">
<ExtensionInfoList extension={extension} />
<br />
<div className="flex md:justify-end items-start">
{extension.security === "unsafe" ? (
<div className="flex text-center px-2 py-2 items-center gap-2 cursor-not-allowed text-[#555] dark:text-[#999]">
<MdDownload className="inline" />
Install
</div>
) : (
<Button fullWidth startIcon={<MdDownload />}>
Install
</Button>
)}
<ExtensionInfoList
extension={extension}
className="hidden md:block"
/>
</div>
</div>
<ExtensionInfoList
extension={extension}
className="md:hidden"
/>
<ExtensionControls extension={extension} />
</div>

<br />
Expand Down Expand Up @@ -128,6 +175,6 @@ export default async function ExtensionPage({ params }: ServerSidePageProps) {
to their respective owners.
</p>
</main>
</Container>
</div>
);
}
7 changes: 4 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import Navbar from "@/components/Navbar/Navbar";
import RouteChangeProgress from "@/components/Routing/RouteChangeProgress";
import { RouterContextProvider } from "@/contexts/RouterContext";
import "@/styles/globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import Providers from "./providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "SudoBot Extensions",
description:
"Explore and install officially supported extensions for SudoBot",
metadataBase: new URL(process.env.NEXT_PUBLIC_METADATA_BASEURL!),
};

export default function RootLayout({
Expand All @@ -21,11 +22,11 @@ export default function RootLayout({
return (
<html lang="en">
<body className={inter.className}>
<RouterContextProvider>
<Providers>
<RouteChangeProgress />
<Navbar />
{children}
</RouterContextProvider>
</Providers>
</body>
</html>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/mui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const darkTheme = createTheme({
});
export default function MUIProvider({ children }: PropsWithChildren) {
const colorScheme = useColorScheme();

return (
<ThemeProvider theme={colorScheme === "light" ? lightTheme : darkTheme}>
{children}
Expand Down
18 changes: 18 additions & 0 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";

import { ComponentProps } from "react";

export default function Button({
className,
children,
...props
}: ComponentProps<"button">) {
return (
<button
className={`bg-[rgba(0,0,0,0.07)] dark:bg-[rgba(255,255,255,0.17)] hover:bg-[#bbb] dark:hover:bg-[#222] focus:outline dark:focus:outline-[#999] focus:outline-2 cursor-pointer font-semibold py-2 px-4 rounded ${className}`}
{...props}
>
{children}
</button>
);
}
5 changes: 4 additions & 1 deletion src/components/Extension/ExtensionAuthor.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
"use client";

import { APIExtension } from "@/types/APIExtension";
import { useRouter } from "next/navigation";
import { FC } from "react";

interface ExtensionAuthorProps {
author: APIExtension["author"];
}

const ExtensionAuthor: FC<ExtensionAuthorProps> = ({ author }) => {
const router = useRouter();

return (
<span
className={`text-black dark:text-white ${
author?.github ? "hover:underline" : ""
}`}
onClick={
author?.github
? () => window.location.replace(author?.github!)
? () => router.replace(author?.github!)
: undefined
}
>
Expand Down
35 changes: 35 additions & 0 deletions src/components/Extension/ExtensionControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import Button from "@/components/Button/Button";
import { APIExtension } from "@/types/APIExtension";
import { MdDownload } from "react-icons/md";
import DeployedCodeUpdate from "../Icons/DeployedCodeUpdate";

export default function ExtensionControls({
extension,
}: {
extension: APIExtension;
}) {
return (
<div className="block w-[100%] md:w-auto md:flex md:justify-end md:items-start md:gap-2">
{extension.security === "unsafe" ? (
<div className="flex text-center px-2 py-2 items-center gap-2 cursor-not-allowed text-[#555] dark:text-[#999]">
<MdDownload className="inline" />
Install
</div>
) : (
<>
<Button className="flex items-center gap-1 pr-5">
<MdDownload className="inline" />
Download
</Button>
<div className="md:hidden pt-2"></div>
<Button className="flex items-center gap-1 pr-5">
<DeployedCodeUpdate />
Install
</Button>
</>
)}
</div>
);
}
Loading

0 comments on commit a8cca97

Please sign in to comment.