Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,3 @@ jobs:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

50 changes: 50 additions & 0 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/resolve-arena-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number, Arena.Channel> = {};
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
4 changes: 2 additions & 2 deletions pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export default class MyDocument extends Document {
<IconContext.Provider value={{ style: { verticalAlign: "middle" } }}>
<Html lang="en">
<Head>
{getEnv("NODE_ENV") === "production" ? (
<base href={getEnv("NEXT_PUBLIC_BASE_PATH")} />
{getEnv("NODE_ENV") === "production" && getEnv("NEXT_PUBLIC_BASE_PATH", "") ? (
<base href={getEnv("NEXT_PUBLIC_BASE_PATH", "")} />
) : (
<></>
)}
Expand Down
168 changes: 103 additions & 65 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import {
layoutDefaultClasses,
StyleClasses,
} from "components/styles";
import { log } from "lib/log";

const classes: StyleClasses = {
...globalClasses,
...layoutDefaultClasses,
Expand All @@ -36,25 +34,24 @@ 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 = {
...notionProps,
channels,
siteMap,
substackEssays,
substackHandle,
};

return { props, revalidate: 3600 }; // Revalidate every hour for fresh Substack content
Expand Down Expand Up @@ -82,6 +79,7 @@ const textVersion = 0;

interface HomePageContentProps extends types.PageProps {
substackEssays?: SubstackEssay[];
substackHandle?: string;
}

export const HomePageContent: React.FC<HomePageContentProps> = ({
Expand All @@ -91,6 +89,7 @@ export const HomePageContent: React.FC<HomePageContentProps> = ({
channels,
siteMap,
substackEssays = [],
substackHandle = "suruleredotdev",
}) => {
// TODO: render from root page block
const posts = getSitePosts({
Expand All @@ -116,74 +115,110 @@ export const HomePageContent: React.FC<HomePageContentProps> = ({
</div>

<b className={classes.postsTitle}>ESSAYS</b>
{/* {JSON.stringify(posts.slice(0, 3), null, 2)} */}
<ul className={classes.postsList}>
{/* 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) => (
<p key={i}>
<a
className={classes.postLink}
href={item.isExternal ? item.url : "/" + idToPagePath[item.id]}
target={item.isExternal ? "_blank" : undefined}
rel={item.isExternal ? "noopener noreferrer" : undefined}
>
{item.title}
{item.isExternal && <ExternalLinkIcon />}
</a>

<small
className={classes.postDate}
style={{ fontSize: ".60rem" }}
>
&nbsp; &mdash;{" "}
{new Date(item.published).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</small>
].sort((a, b) => b.published - a.published);

<br />
return (
<>
{allItems.length > 0 && (
<ul className={classes.postsList} style={{ listStyle: "none", padding: 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",
});

<span className={classes.postDescription}>
{item.description?.length > 200
? item.description?.substring(0, 197) + "..."
: item.description}
</span>
{item.tags && !item.isExternal ? (
<>
{/* TODO: implement tags
<br />
{item.tags?.map((tag, i) => (
<a
key={i}
className={classes.postTag}
href={"/blog/tag/" + tag}
>
<em>{tag}</em>
</a>
))} */}
</>
) : (
<></>
)}
</p>
))}
</ul>
return (
<li key={i} style={{ marginBottom: "1.5rem" }}>
<a
href={href}
target={item.isExternal ? "_blank" : undefined}
rel={item.isExternal ? "noopener noreferrer" : undefined}
style={{ textDecoration: "none", display: "flex", gap: "0.75rem", alignItems: "flex-start" }}
>
{item.image && (
<img
src={item.image}
alt=""
style={{
width: "72px",
height: "56px",
objectFit: "cover",
flexShrink: 0,
borderRadius: "2px",
marginTop: "2px",
}}
/>
)}
<div style={{ flex: 1, minWidth: 0 }}>
<span className={classes.postLink} style={{ display: "inline" }}>
{item.title}
{item.isExternal && <ExternalLinkIcon />}
</span>
<span className={classes.postDate} style={{ fontSize: ".60rem", marginLeft: "0.4rem" }}>
&mdash; {dateStr}
</span>
{item.isExternal && (
<span
style={{
display: "inline-block",
fontSize: "0.55rem",
letterSpacing: "0.05em",
textTransform: "uppercase",
border: "1px solid currentColor",
borderRadius: "2px",
padding: "0 3px",
marginLeft: "0.4rem",
opacity: 0.5,
verticalAlign: "middle",
}}
>
Substack
</span>
)}
<br />
<span className={classes.postDescription}>
{item.description?.length > 200
? item.description?.substring(0, 197) + "..."
: item.description}
</span>
</div>
</a>
</li>
);
})}
</ul>
)}
{substackEssays.length === 0 && (
<iframe
src={`https://${substackHandle}.substack.com/embed`}
width="100%"
height="320"
style={{
border: "1px solid #eee",
background: "white",
maxWidth: "480px",
display: "block",
margin: "1rem 0",
}}
frameBorder="0"
scrolling="no"
/>
)}
</>
);
})()}
<br />

<b className={classes.postsTitle}>TOOLS</b>
Expand Down Expand Up @@ -278,6 +313,7 @@ interface IndexPageProps {
channels: any;
siteMap: any;
substackEssays?: SubstackEssay[];
substackHandle?: string;
}

const IndexPage: React.FC<IndexPageProps> = (props) => {
Expand All @@ -289,6 +325,7 @@ const IndexPage: React.FC<IndexPageProps> = (props) => {
channels: arenaChannels,
siteMap,
substackEssays,
substackHandle,
} = props;

const { isDarkMode } = useDarkMode();
Expand Down Expand Up @@ -326,6 +363,7 @@ const IndexPage: React.FC<IndexPageProps> = (props) => {
rootPageBlock={block}
channels={arenaChannels}
substackEssays={substackEssays}
substackHandle={substackHandle}
></HomePageContent>
<Footer page={undefined} isBlogPost={isBlogPost}></Footer>
</NotionContextProvider>
Expand Down
Loading
Loading