diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 7e8d3e4..5376e85 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -71,4 +71,3 @@ jobs:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
-
diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml
new file mode 100644
index 0000000..66f2e64
--- /dev/null
+++ b/.github/workflows/pr-preview.yml
@@ -0,0 +1,50 @@
+name: PR Preview
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, closed]
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ preview:
+ runs-on: ubuntu-latest
+ env:
+ NOTION_SECRET: ${{ secrets.NOTION_SECRET }}
+ ARENA_UID: ${{ secrets.ARENA_UID }}
+ ARENA_SECRET: ${{ secrets.ARENA_SECRET }}
+ ARENA_PERSONAL_ACCESS_TOKEN: ${{ secrets.ARENA_PERSONAL_ACCESS_TOKEN }}
+ SUBSTACK_API_KEY: ${{ secrets.SUBSTACK_API_KEY }}
+ SUBSTACK_HANDLE: ${{ vars.SUBSTACK_HANDLE }}
+ NODE_ENV: production
+ LOG_LEVEL: error
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Use Node.js 22.9.0
+ if: github.event.action != 'closed'
+ uses: actions/setup-node@v2
+ with:
+ node-version: '22.9.0'
+
+ - name: Install dependencies
+ if: github.event.action != 'closed'
+ run: npm install --include=dev
+
+ - name: Build
+ if: github.event.action != 'closed'
+ run: npm run build && npm run export
+ env:
+ # Set base path to the PR preview subdir so assets resolve correctly
+ NEXT_PUBLIC_BASE_PATH: /pr-preview/pr-${{ github.event.pull_request.number }}
+
+ - name: Deploy / remove preview
+ uses: rossjrw/pr-preview-action@v1
+ with:
+ source-dir: ./out
+ preview-branch: gh-pages
+ umbrella-dir: pr-preview
+ action: auto
diff --git a/lib/resolve-arena-channels.ts b/lib/resolve-arena-channels.ts
index 6cf72c1..f5d1991 100644
--- a/lib/resolve-arena-channels.ts
+++ b/lib/resolve-arena-channels.ts
@@ -8,7 +8,7 @@ import { log } from "./log";
const ARENA_USER = {
slug: "korede-aderele",
id: 60392,
- token: getEnv("ARENA_PERSONAL_ACCESS_TOKEN"),
+ token: getEnv("ARENA_PERSONAL_ACCESS_TOKEN", ""),
};
export const all_channels: Record = {};
diff --git a/package-lock.json b/package-lock.json
index eb70f3f..ef5e113 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54,9 +54,9 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.4",
- "@types/node": "^17.0.23",
+ "@types/node": "17.0.45",
"@types/node-fetch": "^3.0.3",
- "@types/react": "^18.0.15",
+ "@types/react": "18.3.28",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
@@ -70,7 +70,7 @@
"npm-run-all": "^4.1.5",
"parcel": "2.6.x",
"prettier": "^2.7.1",
- "typescript": "^4.9.5",
+ "typescript": "4.9.5",
"yarn": "1.22.x"
},
"engines": {
@@ -2568,14 +2568,14 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "18.3.23",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
- "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
- "csstype": "^3.0.2"
+ "csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
@@ -4408,9 +4408,9 @@
}
},
"node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/csv2geojson": {
diff --git a/package.json b/package.json
index a04a6bb..014021b 100644
--- a/package.json
+++ b/package.json
@@ -86,9 +86,9 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.4",
- "@types/node": "^17.0.23",
+ "@types/node": "17.0.45",
"@types/node-fetch": "^3.0.3",
- "@types/react": "^18.0.15",
+ "@types/react": "18.3.28",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
@@ -102,7 +102,7 @@
"npm-run-all": "^4.1.5",
"parcel": "2.6.x",
"prettier": "^2.7.1",
- "typescript": "^4.9.5",
+ "typescript": "4.9.5",
"yarn": "1.22.x"
},
"resolutions": {
diff --git a/pages/_document.tsx b/pages/_document.tsx
index fd3642b..842b7ba 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -10,8 +10,8 @@ export default class MyDocument extends Document {
- {getEnv("NODE_ENV") === "production" ? (
-
+ {getEnv("NODE_ENV") === "production" && getEnv("NEXT_PUBLIC_BASE_PATH", "") ? (
+
) : (
<>>
)}
diff --git a/pages/index.tsx b/pages/index.tsx
index cfad325..fa9611b 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -22,8 +22,6 @@ import {
layoutDefaultClasses,
StyleClasses,
} from "components/styles";
-import { log } from "lib/log";
-
const classes: StyleClasses = {
...globalClasses,
...layoutDefaultClasses,
@@ -36,18 +34,16 @@ export const getStaticProps = async () => {
const channels = await resolveArenaChannels();
const siteMap = await getSiteMap();
- // Fetch Substack essays - use environment variables for Substack handle and API key
+ // Fetch Substack essays - public API works without a key; key is optional enhancement
const substackHandle = process.env.SUBSTACK_HANDLE || "suruleredotdev";
const substackApiKey = process.env.SUBSTACK_API_KEY;
let substackEssays: SubstackEssay[] = [];
- if (substackHandle && substackApiKey) {
+ if (substackHandle) {
substackEssays = await getSubstackEssays(
substackHandle,
10,
substackApiKey
);
- } else {
- log("ERROR", "Missing Substack handle or API key")
}
const props = {
@@ -55,6 +51,7 @@ export const getStaticProps = async () => {
channels,
siteMap,
substackEssays,
+ substackHandle,
};
return { props, revalidate: 3600 }; // Revalidate every hour for fresh Substack content
@@ -82,6 +79,7 @@ const textVersion = 0;
interface HomePageContentProps extends types.PageProps {
substackEssays?: SubstackEssay[];
+ substackHandle?: string;
}
export const HomePageContent: React.FC = ({
@@ -91,6 +89,7 @@ export const HomePageContent: React.FC = ({
channels,
siteMap,
substackEssays = [],
+ substackHandle = "suruleredotdev",
}) => {
// TODO: render from root page block
const posts = getSitePosts({
@@ -116,74 +115,110 @@ export const HomePageContent: React.FC = ({
ESSAYS
- {/* {JSON.stringify(posts.slice(0, 3), null, 2)} */}
-
- {/* Combine and sort local posts with Substack essays by publication date */}
- {[
+ {(() => {
+ const allItems = [
...posts
?.filter((post) => post.public == true)
- .map((post) => ({ ...post, isExternal: false })),
+ .map((post) => ({ ...post, isExternal: false, image: undefined as string | undefined })),
...substackEssays.map((essay) => ({
...essay,
- title: essay.title,
- description: essay.description,
- published: essay.published,
isExternal: true,
id: `substack-${essay.id}`,
})),
- ]
- ?.sort((a, b) => b.published - a.published)
- .map((item: any, i) => (
-
-
- {item.title}
- {item.isExternal && }
-
-
-
- —{" "}
- {new Date(item.published).toLocaleDateString("en-US", {
- year: "numeric",
- month: "long",
- day: "numeric",
- })}
-
+ ].sort((a, b) => b.published - a.published);
-
+ return (
+ <>
+ {allItems.length > 0 && (
+
+ {allItems.map((item: any, i) => {
+ const href = item.isExternal ? item.url : "/" + idToPagePath[item.id];
+ const dateStr = new Date(item.published).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
-
- {item.description?.length > 200
- ? item.description?.substring(0, 197) + "..."
- : item.description}
-
- {item.tags && !item.isExternal ? (
- <>
- {/* TODO: implement tags
-
- {item.tags?.map((tag, i) => (
-
- {tag}
-
- ))} */}
- >
- ) : (
- <>>
- )}
-
- ))}
-
+ return (
+
+
+ {item.image && (
+
+ )}
+
+
+ {item.title}
+ {item.isExternal && }
+
+
+ — {dateStr}
+
+ {item.isExternal && (
+
+ Substack
+
+ )}
+
+
+ {item.description?.length > 200
+ ? item.description?.substring(0, 197) + "..."
+ : item.description}
+
+
+
+
+ );
+ })}
+
+ )}
+ {substackEssays.length === 0 && (
+
+ )}
+ >
+ );
+ })()}
TOOLS
@@ -278,6 +313,7 @@ interface IndexPageProps {
channels: any;
siteMap: any;
substackEssays?: SubstackEssay[];
+ substackHandle?: string;
}
const IndexPage: React.FC = (props) => {
@@ -289,6 +325,7 @@ const IndexPage: React.FC = (props) => {
channels: arenaChannels,
siteMap,
substackEssays,
+ substackHandle,
} = props;
const { isDarkMode } = useDarkMode();
@@ -326,6 +363,7 @@ const IndexPage: React.FC = (props) => {
rootPageBlock={block}
channels={arenaChannels}
substackEssays={substackEssays}
+ substackHandle={substackHandle}
>
diff --git a/tsconfig.json b/tsconfig.json
index 73d6662..3471573 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "es2016",
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -15,9 +19,18 @@
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
- "typeRoots": ["./node_modules/@types"],
+ "typeRoots": [
+ "./node_modules/@types"
+ ],
"incremental": true
},
- "exclude": ["node_modules"],
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "site.config.ts"]
+ "exclude": [
+ "node_modules"
+ ],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ "site.config.ts"
+ ]
}