diff --git a/apps/engineeringblog/app/[slug]/page.tsx b/apps/engineeringblog/app/[slug]/page.tsx
index 204421b25..6ffb5f4ac 100644
--- a/apps/engineeringblog/app/[slug]/page.tsx
+++ b/apps/engineeringblog/app/[slug]/page.tsx
@@ -1,12 +1,11 @@
-import { ArticleContent } from "@/engineeringblog/components/Article";
import { Box } from "@mui/material";
-import { getContent } from "@/engineeringblog/utils";
+
+import Article from "@/engineeringblog/components/Article";
+import { ArticleProps, getContent } from "@/engineeringblog/utils";
export default async function Page({ params }: { params: { slug: string } }) {
- const post = await getContent(params.slug);
- return (
-
-
-
- );
+ const post: ArticleProps = await getContent(params.slug);
+
+ // TODO: Check that the post does exist, return 404 otherwise
+ return ;
}
diff --git a/apps/engineeringblog/app/layout.tsx b/apps/engineeringblog/app/layout.tsx
index 32ce49b59..f92b117da 100644
--- a/apps/engineeringblog/app/layout.tsx
+++ b/apps/engineeringblog/app/layout.tsx
@@ -5,7 +5,6 @@ import type { Metadata } from "next";
import NavBar from "@/engineeringblog/components/NavBar";
import theme from "@/engineeringblog/theme";
-// TODO: blurWidth/blueHeight https://github.com/vercel/next.js/issues/56511
import logoLight from "@/engineeringblog/assets/images/logo-light.png";
export const metadata: Metadata = {
@@ -19,8 +18,10 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
+ // TODO: blurWidth/blurHeight https://github.com/vercel/next.js/issues/56511
+ const { blurWidth, blurHeight, ...logoProps } = logoLight;
const logo = {
- ...logoLight,
+ ...logoProps,
alt: "Technology | Code for Africa",
title: "Technology",
};
diff --git a/apps/engineeringblog/app/page.tsx b/apps/engineeringblog/app/page.tsx
index c8c47b1df..27b4dcf5f 100644
--- a/apps/engineeringblog/app/page.tsx
+++ b/apps/engineeringblog/app/page.tsx
@@ -1,23 +1,19 @@
-import { Container } from "@mui/material";
-import { ArticleList } from "@/engineeringblog/components/Article";
+import { Section } from "@commons-ui/core";
+
+import ArticleList from "@/engineeringblog/components/ArticleList";
import { getAllContents } from "@/engineeringblog/utils";
-import NoPosts from "@/engineeringblog/components/NoPosts";
export default async function index() {
const posts = await getAllContents();
- if (!posts.length) {
- return ;
- }
-
return (
-
-
+
);
}
diff --git a/apps/engineeringblog/components/Article/Article.tsx b/apps/engineeringblog/components/Article/Article.tsx
new file mode 100644
index 000000000..e8e6c7f79
--- /dev/null
+++ b/apps/engineeringblog/components/Article/Article.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import { Section } from "@commons-ui/core";
+import React from "react";
+
+import Markdown from "@/engineeringblog/components/Markdown";
+import type { ArticleSxProps } from "./ArticleSxProps";
+import ArticleHeader from "./ArticleHeader";
+
+const Article = React.forwardRef(function Article(
+ props: ArticleSxProps,
+ ref: React.Ref,
+) {
+ const { content, excerpt, publishedDate, sx, title } = props;
+
+ return (
+
+ );
+});
+
+export default Article;
diff --git a/apps/engineeringblog/components/Article/ArticleContent.tsx b/apps/engineeringblog/components/Article/ArticleContent.tsx
deleted file mode 100644
index 5a8ac1de6..000000000
--- a/apps/engineeringblog/components/Article/ArticleContent.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-"use client";
-import { Section } from "@commons-ui/core";
-import React from "react";
-import { Figure } from "@commons-ui/next";
-import { Box } from "@mui/material";
-import ArticleHeader from "./ArticleHeader";
-import { Article } from "@/engineeringblog/utils";
-import Markdown from "@/engineeringblog/components/Markdown";
-const ArticleContent = React.forwardRef(function ArticleContent(
- {
- article,
- }: {
- article: Article;
- },
- ref: React.Ref,
-) {
- const { title, description, featuredImage, date, content } = article;
-
- return (
-
-
-
-
- );
-});
-
-export default ArticleContent;
diff --git a/apps/engineeringblog/components/Article/ArticleHeader.tsx b/apps/engineeringblog/components/Article/ArticleHeader.tsx
index 2ac65c42a..d66201a1a 100644
--- a/apps/engineeringblog/components/Article/ArticleHeader.tsx
+++ b/apps/engineeringblog/components/Article/ArticleHeader.tsx
@@ -1,37 +1,32 @@
"use client";
-import { Section } from "@commons-ui/core";
import { Typography } from "@mui/material";
+import { styled } from "@mui/material/styles";
import React from "react";
+import type { ArticleSxProps } from "./ArticleSxProps";
+
+type ArticleHeaderSxProps = Omit<
+ ArticleSxProps,
+ "content" | "featuredImage" | "slug"
+>;
+
+const ArticleHeaderRoot = styled("header")({});
+
const ArticleHeader = React.forwardRef(function ArticleHeader(
- {
- date,
- excerpt,
- sx,
- title,
- }: { date: string; excerpt: string; sx: any; title: string },
- ref,
+ props: ArticleHeaderSxProps,
+ ref: React.Ref,
) {
+ const { publishedDate, excerpt, sx, title } = props;
+
return (
-
+
- {date}
+ {publishedDate}
{title}
@@ -47,7 +42,7 @@ const ArticleHeader = React.forwardRef(function ArticleHeader(
>
{excerpt}
-
+
);
});
diff --git a/apps/engineeringblog/components/Article/ArticleSxProps.tsx b/apps/engineeringblog/components/Article/ArticleSxProps.tsx
new file mode 100644
index 000000000..7a64a420e
--- /dev/null
+++ b/apps/engineeringblog/components/Article/ArticleSxProps.tsx
@@ -0,0 +1,9 @@
+import { SxProps } from "@mui/material/styles";
+
+import { ArticleProps } from "@/engineeringblog/utils";
+
+interface ArticleSxProps extends ArticleProps {
+ sx?: SxProps;
+}
+
+export type { ArticleSxProps };
diff --git a/apps/engineeringblog/components/Article/index.ts b/apps/engineeringblog/components/Article/index.ts
index 6f8a1fc9a..655f96df3 100644
--- a/apps/engineeringblog/components/Article/index.ts
+++ b/apps/engineeringblog/components/Article/index.ts
@@ -1,5 +1,5 @@
-import ArticleList from "./ArticleList";
-import ArticleCard from "./ArticleCard";
-import ArticleContent from "./ArticleContent";
+import Article from "./Article";
+import ArticleHeader from "./ArticleHeader";
-export { ArticleList, ArticleCard, ArticleContent };
+export { ArticleHeader };
+export default Article;
diff --git a/apps/engineeringblog/components/Article/ArticleCard.tsx b/apps/engineeringblog/components/ArticleList/ArticleCard.tsx
similarity index 87%
rename from apps/engineeringblog/components/Article/ArticleCard.tsx
rename to apps/engineeringblog/components/ArticleList/ArticleCard.tsx
index 1dcf20aa8..c0aa8980e 100644
--- a/apps/engineeringblog/components/Article/ArticleCard.tsx
+++ b/apps/engineeringblog/components/ArticleList/ArticleCard.tsx
@@ -1,6 +1,6 @@
"use client";
+
import { StyledLink } from "@/commons-ui/next/Link";
-import { ArticleWithoutContent } from "@/engineeringblog/utils";
import {
Card,
CardActionArea,
@@ -10,8 +10,10 @@ import {
} from "@mui/material";
import React from "react";
+import { ArticleWithoutContentProps } from "@/engineeringblog/utils";
+
const ArticleCard = React.forwardRef(function ArticleCard(
- { title, publishDate, featuredImage, slug }: ArticleWithoutContent,
+ { title, publishedDate, featuredImage, slug }: ArticleWithoutContentProps,
ref: React.Ref,
) {
return (
@@ -29,6 +31,7 @@ const ArticleCard = React.forwardRef(function ArticleCard(
border: "1px solid #DAD5D5",
filter: "drop-shadow(0px 4px 8px rgba(0, 0, 0, 0.1))",
}}
+ ref={ref}
>
- {publishDate}
+ {publishedDate}
diff --git a/apps/engineeringblog/components/Article/ArticleList.tsx b/apps/engineeringblog/components/ArticleList/ArticleList.tsx
similarity index 74%
rename from apps/engineeringblog/components/Article/ArticleList.tsx
rename to apps/engineeringblog/components/ArticleList/ArticleList.tsx
index 718257bb7..93e179d72 100644
--- a/apps/engineeringblog/components/Article/ArticleList.tsx
+++ b/apps/engineeringblog/components/ArticleList/ArticleList.tsx
@@ -3,17 +3,21 @@
import { Section } from "@commons-ui/core";
import { Grid } from "@mui/material";
import React from "react";
+
+import { ArticleWithoutContentProps } from "@/engineeringblog/utils";
import ArticleCard from "./ArticleCard";
-import { ArticleWithoutContent } from "@/engineeringblog/utils";
const ArticleList = React.forwardRef(function ArtilceList(
{
articles,
}: {
- articles: ArticleWithoutContent[];
+ articles: ArticleWithoutContentProps[];
},
ref: React.Ref,
) {
+ if (!articles?.length) {
+ return null;
+ }
return (
{articles?.map((article) => (
-
+
))}
diff --git a/apps/engineeringblog/components/ArticleList/index.ts b/apps/engineeringblog/components/ArticleList/index.ts
new file mode 100644
index 000000000..c66f68313
--- /dev/null
+++ b/apps/engineeringblog/components/ArticleList/index.ts
@@ -0,0 +1,5 @@
+import ArticleCard from "./ArticleCard";
+import ArticleList from "./ArticleList";
+
+export { ArticleCard };
+export default ArticleList;
diff --git a/apps/engineeringblog/content/exploring-next-js.mdx b/apps/engineeringblog/content/exploring-next-js.mdx
index 558887ea2..f6457a2a8 100644
--- a/apps/engineeringblog/content/exploring-next-js.mdx
+++ b/apps/engineeringblog/content/exploring-next-js.mdx
@@ -1,7 +1,7 @@
---
title: "Exploring Next.js: The React Framework for Production"
-publishDate: "2020-01-01"
-description: "Next.js has rapidly become one of the most popular frameworks for building modern web applications. Created by Vercel, it extends the capabilities of React, providing developers with a powerful toolkit to build fast, scalable, and SEO-friendly websites with ease."
+publishedDate: "2020-01-01"
+excerpt: "Next.js has rapidly become one of the most popular frameworks for building modern web applications. Created by Vercel, it extends the capabilities of React, providing developers with a powerful toolkit to build fast, scalable, and SEO-friendly websites with ease."
featuredImage: "/blog/exploring-next-js.png"
---
diff --git a/apps/engineeringblog/content/javascript-generators.mdx b/apps/engineeringblog/content/javascript-generators.mdx
index 8e3cda480..1eaebf47f 100644
--- a/apps/engineeringblog/content/javascript-generators.mdx
+++ b/apps/engineeringblog/content/javascript-generators.mdx
@@ -1,7 +1,7 @@
---
title: "Understanding JavaScript Generators: A Deep Dive"
-publishDate: "2020-02-02"
-description: "JavaScript is a versatile language, and one of its lesser-known yet powerful features is Generators. Introduced in ECMAScript 6 (ES6), Generators provide a unique way to handle functions, offering more control over the execution flow. In this blog, we'll explore what generators are, how they work, and practical use cases."
+publishedDate: "2020-02-02"
+excerpt: "JavaScript is a versatile language, and one of its lesser-known yet powerful features is Generators. Introduced in ECMAScript 6 (ES6), Generators provide a unique way to handle functions, offering more control over the execution flow. In this blog, we'll explore what generators are, how they work, and practical use cases."
featuredImage: "/blog/javascript-generators.jpeg"
---
@@ -11,7 +11,7 @@ Generators are a special type of function in JavaScript that can be paused and r
### Syntax
-A generator function is defined using the `function* `syntax, where the asterisk (\*) distinguishes it from a regular function. Inside the function body, the yield keyword is used to pause the function and return a value.
+A generator function is defined using the `function*`syntax, where the asterisk (\*) distinguishes it from a regular function. Inside the function body, the yield keyword is used to pause the function and return a value.
```javascript
function* myGenerator() {
diff --git a/apps/engineeringblog/content/mastering-docker.mdx b/apps/engineeringblog/content/mastering-docker.mdx
index 7cee1f987..79210c88e 100644
--- a/apps/engineeringblog/content/mastering-docker.mdx
+++ b/apps/engineeringblog/content/mastering-docker.mdx
@@ -1,7 +1,7 @@
---
title: "Mastering Docker: A Comprehensive Guide for Developers"
-publishDate: "2020-03-03"
-description: "Docker has revolutionized the way we build, ship, and run applications. It simplifies software development by creating isolated environments, known as containers, that bundle an application and its dependencies together. This ensures consistency across multiple environments and reduces the 'it works on my machine' problem. In this article, we'll dive deep into Docker, exploring its architecture, benefits, common use cases, and best practices."
+publishedDate: "2020-03-03"
+excerpt: "Docker has revolutionized the way we build, ship, and run applications. It simplifies software development by creating isolated environments, known as containers, that bundle an application and its dependencies together. This ensures consistency across multiple environments and reduces the 'it works on my machine' problem. In this article, we'll dive deep into Docker, exploring its architecture, benefits, common use cases, and best practices."
featuredImage: "/blog/mastering-docker.png"
---
diff --git a/apps/engineeringblog/utils/index.ts b/apps/engineeringblog/utils/index.ts
index 2693105d2..3046a8851 100644
--- a/apps/engineeringblog/utils/index.ts
+++ b/apps/engineeringblog/utils/index.ts
@@ -1,59 +1,69 @@
-import fs from "fs";
-import path from "path";
-import matter from "gray-matter";
import { format } from "date-fns";
-export type Article = {
- slug: string;
+import { promises as fs } from "fs";
+import matter from "gray-matter";
+import path from "path";
+
+type MdFileContentProps = {
title: string;
- description: string;
- publishDate: string;
+ excerpt: string;
+ publishedDate: string;
featuredImage: string;
content: string;
};
-export type ArticleWithoutContent = Omit;
-
-export function getAllContents(): ArticleWithoutContent[] {
- const postsDirectory = path.join(process.cwd(), "content");
- const fileNames = fs
- .readdirSync(postsDirectory)
- .filter((fileName) => fileName.endsWith(".mdx"));
-
- const posts = fileNames.map((fileName) => {
- const filePath = path.join(postsDirectory, fileName);
- const fileContents = fs.readFileSync(filePath, "utf8");
- const { data } = matter(fileContents);
-
- return {
- slug: fileName.replace(/\.mdx$/, ""),
- title: data.title,
- description: data.description,
- publishDate: data.publishDate,
- formattedDate: format(new Date(data.publishDate), "MMM dd, yyyy"),
- featuredImage: data?.featuredImage,
- };
- });
-
- posts.sort(
- (a, b) =>
- new Date(b.publishDate).getTime() - new Date(a.publishDate).getTime(),
- );
-
- return posts;
+export interface ArticleProps extends MdFileContentProps {
+ slug: string;
}
-export async function getContent(slug: string): Promise {
- const postsDirectory = path.join(process.cwd(), "content");
- const filePath = path.join(postsDirectory, `${slug}.mdx`);
- const fileContents = fs.readFileSync(filePath, "utf8");
- const { data, content } = matter(fileContents);
+export type ArticleWithoutContentProps = Omit;
+
+async function readMdFile(filePath: string): Promise {
+ const fileContent = await fs.readFile(filePath, "utf8");
+ const { data, content } = matter(fileContent);
return {
- slug,
title: data.title,
- description: data.description,
- publishDate: format(new Date(data.publishDate), "MMM dd, yyyy"),
+ excerpt: data.excerpt,
+ publishedDate: format(new Date(data.publishedDate), "MMM dd, yyyy"),
featuredImage: data.featuredImage,
content,
};
}
+
+export async function getAllContents(): Promise {
+ const contentDir = path.join(process.cwd(), "content");
+ const files = await fs.readdir(contentDir);
+ const contentsPromises = files
+ .filter((fileName) => fileName.endsWith(".mdx"))
+ .map(async (fileName) => {
+ const filePath = path.join(contentDir, fileName);
+ const { content, ...fileContent } = await readMdFile(filePath);
+
+ return {
+ ...fileContent,
+ slug: fileName.replace(/\.mdx$/, ""),
+ };
+ });
+ const contents = (
+ await Promise.allSettled(contentsPromises)
+ )
+ // TODO: log/send to Sentry those that fail
+ .filter((p) => p.status === "fulfilled")
+ .map((p) => p.value)
+ .sort(
+ (a, b) =>
+ new Date(b.publishedDate).getTime() -
+ new Date(a.publishedDate).getTime(),
+ );
+ return contents;
+}
+
+export async function getContent(slug: string): Promise {
+ const filePath = path.join(process.cwd(), "content", `${slug}.mdx`);
+ const fileContent = await readMdFile(filePath);
+
+ return {
+ ...fileContent,
+ slug,
+ };
+}
diff --git a/packages/commons-ui-core/src/ImageButton/ImageButton.js b/packages/commons-ui-core/src/ImageButton/ImageButton.js
index 2a1e8e8ae..e0666c0e9 100644
--- a/packages/commons-ui-core/src/ImageButton/ImageButton.js
+++ b/packages/commons-ui-core/src/ImageButton/ImageButton.js
@@ -1,3 +1,5 @@
+"use client";
+
import ButtonBase from "@mui/material/ButtonBase";
import { styled } from "@mui/material/styles";
import PropTypes from "prop-types";
@@ -53,10 +55,4 @@ ImageButton.propTypes = {
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};
-ImageButton.defaultProps = {
- height: undefined,
- src: undefined,
- width: undefined,
-};
-
export default ImageButton;
diff --git a/packages/commons-ui-core/src/NavBar/NavBar.js b/packages/commons-ui-core/src/NavBar/NavBar.js
index 5247b7bad..429e01e25 100644
--- a/packages/commons-ui-core/src/NavBar/NavBar.js
+++ b/packages/commons-ui-core/src/NavBar/NavBar.js
@@ -1,3 +1,5 @@
+"use client";
+
import AppBar from "@mui/material/AppBar";
import { styled } from "@mui/material/styles";
import Toolbar from "@mui/material/Toolbar";
diff --git a/packages/commons-ui-core/src/NavList/NavList.js b/packages/commons-ui-core/src/NavList/NavList.js
index e3e16cf42..93d8a9e7a 100644
--- a/packages/commons-ui-core/src/NavList/NavList.js
+++ b/packages/commons-ui-core/src/NavList/NavList.js
@@ -1,3 +1,5 @@
+"use client";
+
import { styled } from "@mui/material/styles";
import PropTypes from "prop-types";
import React from "react";
diff --git a/packages/commons-ui-core/src/RichTypography/RichTypography.js b/packages/commons-ui-core/src/RichTypography/RichTypography.js
index 1cc6dc4ec..bb87e8b84 100644
--- a/packages/commons-ui-core/src/RichTypography/RichTypography.js
+++ b/packages/commons-ui-core/src/RichTypography/RichTypography.js
@@ -1,3 +1,5 @@
+"use client";
+
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import PropTypes from "prop-types";
@@ -65,10 +67,4 @@ RichTypography.propTypes = {
component: PropTypes.elementType,
};
-RichTypography.defaultProps = {
- children: undefined,
- html: undefined,
- component: undefined,
-};
-
export default RichTypography;
diff --git a/packages/commons-ui-core/src/Section/Section.js b/packages/commons-ui-core/src/Section/Section.js
index 2d083d100..adfed692c 100644
--- a/packages/commons-ui-core/src/Section/Section.js
+++ b/packages/commons-ui-core/src/Section/Section.js
@@ -1,3 +1,5 @@
+"use client";
+
import { Container } from "@mui/material";
import { styled } from "@mui/material/styles";
import PropTypes from "prop-types";
@@ -37,7 +39,7 @@ const Section = React.forwardRef(function Section(props, ref) {
return (