diff --git a/package.json b/package.json
index c7fb56bb..8d1dcfc3 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@astrojs/rss": "^4.0.7",
"@astrolib/seo": "1.0.0-beta.8",
"@astropub/md": "^1.0.0",
+ "@notionhq/client": "^2.2.15",
"@radix-ui/react-slot": "^1.1.0",
"@resvg/resvg-js": "^2.6.2",
"@rive-app/canvas": "^2.21.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cacc66cb..491ee388 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
'@astropub/md':
specifier: ^1.0.0
version: 1.0.0(@astrojs/markdown-remark@5.3.0)
+ '@notionhq/client':
+ specifier: ^2.2.15
+ version: 2.2.15
'@radix-ui/react-slot':
specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.11)(react@18.3.1)
@@ -867,6 +870,10 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+ '@notionhq/client@2.2.15':
+ resolution: {integrity: sha512-XhdSY/4B1D34tSco/GION+23GMjaS9S2zszcqYkMHo8RcWInymF6L1x+Gk7EmHdrSxNFva2WM8orhC4BwQCwgw==}
+ engines: {node: '>=12'}
+
'@oslojs/encoding@1.1.0':
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
@@ -1178,6 +1185,9 @@ packages:
'@types/nlcst@2.0.3':
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
+ '@types/node-fetch@2.6.11':
+ resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
+
'@types/node@17.0.45':
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
@@ -2796,6 +2806,15 @@ packages:
node-fetch-native@1.6.4:
resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==}
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
node-releases@2.0.18:
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
@@ -3572,6 +3591,9 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
@@ -3891,6 +3913,9 @@ packages:
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@@ -3902,6 +3927,9 @@ packages:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
@@ -4750,6 +4778,13 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
+ '@notionhq/client@2.2.15':
+ dependencies:
+ '@types/node-fetch': 2.6.11
+ node-fetch: 2.7.0
+ transitivePeerDependencies:
+ - encoding
+
'@oslojs/encoding@1.1.0': {}
'@pagefind/darwin-arm64@1.1.1':
@@ -5003,6 +5038,11 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
+ '@types/node-fetch@2.6.11':
+ dependencies:
+ '@types/node': 17.0.45
+ form-data: 4.0.1
+
'@types/node@17.0.45': {}
'@types/normalize-package-data@2.4.4': {}
@@ -7151,6 +7191,10 @@ snapshots:
node-fetch-native@1.6.4: {}
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
node-releases@2.0.18: {}
normalize-package-data@2.5.0:
@@ -7988,6 +8032,8 @@ snapshots:
totalist@3.0.1: {}
+ tr46@0.0.3: {}
+
tr46@1.0.1:
dependencies:
punycode: 2.3.1
@@ -8300,6 +8346,8 @@ snapshots:
web-namespaces@2.0.1: {}
+ webidl-conversions@3.0.1: {}
+
webidl-conversions@4.0.2: {}
whatwg-encoding@3.1.1:
@@ -8308,6 +8356,11 @@ snapshots:
whatwg-mimetype@4.0.0: {}
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
whatwg-url@7.1.0:
dependencies:
lodash.sortby: 4.7.0
diff --git a/src/components/planning/episode-card.astro b/src/components/planning/episode-card.astro
new file mode 100644
index 00000000..c76ff02f
--- /dev/null
+++ b/src/components/planning/episode-card.astro
@@ -0,0 +1,118 @@
+---
+import type { NotionEpisodeProperties } from "@/lib/notion/types";
+
+const formatDate = (dateStr: string) => {
+ if (!dateStr) return "";
+ return new Date(dateStr).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
+};
+
+interface Props {
+ episode: NotionEpisodeProperties;
+}
+
+const { episode } = Astro.props as Props;
+---
+
+
+
+
+ {episode.title}
+
+
+
+ {
+ episode.date && (
+
+
+
{formatDate(episode.date)}
+
+ )
+ }
+
+ {
+ episode.assignedTo && (
+
+
+
{episode.assignedTo}
+
+ )
+ }
+
+
+
+
+
+
+
+
+
diff --git a/src/components/planning/index.astro b/src/components/planning/index.astro
new file mode 100644
index 00000000..facf9f45
--- /dev/null
+++ b/src/components/planning/index.astro
@@ -0,0 +1,75 @@
+---
+import { getGeekBlablaEpisodesPlannings } from "@/lib/notion";
+import EpisodeCard from "./episode-card.astro";
+const episodes = await getGeekBlablaEpisodesPlannings();
+
+// Group episodes by status
+const scheduled = episodes
+ .filter(ep => ep.status === "scheduled")
+ .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
+const nextUp = episodes.filter(ep => ep.status.toLowerCase() === "next up");
+const backlog = episodes.filter(ep => ep.status === "backlog");
+---
+
+
+
+
+ GeekBlabla Episodes Planning
+
+
+ Track and manage upcoming episodes
+
+
+
+
+
+
+
+ 📝Backlog
+
+
+ {backlog.length}
+
+
+
+ {backlog.map(episode => )}
+
+
+
+
+
+
+
+ 🎯Next Up
+
+
+ {nextUp.length}
+
+
+
+ {nextUp.map(episode => )}
+
+
+
+
+
+
+ 📅Scheduled
+
+
+ {scheduled.length}
+
+
+
+ {scheduled.map(episode => )}
+
+
+
+
+
diff --git a/src/lib/notion/index.ts b/src/lib/notion/index.ts
new file mode 100644
index 00000000..b105a6a8
--- /dev/null
+++ b/src/lib/notion/index.ts
@@ -0,0 +1,120 @@
+import { Client } from "@notionhq/client";
+import type {
+ NotionResponse,
+ NotionNormalizedResponse,
+ NotionEpisodeCategory,
+ NotionEpisodeStatus,
+ NotionEpisodeProperties,
+ NotionEpisodeProperty,
+} from "./types";
+
+const databaseId = import.meta.env.GEEKSBLALA_NOTION_DATABASE_ID;
+const apiKey = import.meta.env.NOTION_API_KEY;
+
+let notionClient: Client;
+
+const getNotionClient = () => {
+ if (!notionClient) {
+ notionClient = new Client({ auth: apiKey });
+ }
+ return notionClient;
+};
+
+export async function getGeekBlablaEpisodesPlannings(): Promise {
+ if (import.meta.env.DEV) {
+ if (!apiKey || !databaseId) {
+ const data = await import("./notion-mock.json");
+ return data.default as unknown as NotionNormalizedResponse;
+ }
+ }
+
+ const episodes = (await getNotionClient().databases.query({
+ database_id: databaseId,
+ filter: {
+ or: [
+ {
+ property: "Status",
+ select: {
+ equals: "Backlog",
+ },
+ },
+ {
+ property: "Status",
+ select: {
+ equals: "Scheduled",
+ },
+ },
+ {
+ property: "Status",
+ select: {
+ equals: "Next Up",
+ },
+ },
+ ],
+ },
+ })) as NotionResponse;
+ return normalizeNotionResponse(episodes);
+}
+
+export function normalizeEpisodeProperties(properties: {
+ [key: string]: NotionEpisodeProperty;
+}): NotionEpisodeProperties {
+ return {
+ title:
+ properties.title?.type === "title"
+ ? (properties.title.title[0]?.plain_text ?? "")
+ : "",
+
+ date:
+ properties.Date?.type === "date"
+ ? (properties.Date.date?.start ?? "")
+ : "",
+
+ guests:
+ properties.Guests?.type === "relation"
+ ? [] // Since guests are coming as empty relations in the sample
+ : [],
+
+ description:
+ properties["Description "]?.type === "rich_text"
+ ? (properties["Description "].rich_text[0]?.plain_text ?? "")
+ : "",
+
+ youtubeUrl:
+ properties["Youtube URL"]?.type === "url"
+ ? (properties["Youtube URL"].url ?? "")
+ : "",
+
+ category:
+ properties["Category "]?.type === "select"
+ ? ((properties[
+ "Category "
+ ].select?.name.toLowerCase() as NotionEpisodeCategory) ?? "dev")
+ : "dev",
+
+ hosts:
+ properties.Hosts?.type === "relation"
+ ? [] // Since hosts are coming as empty relations in the sample
+ : [],
+
+ assignedTo:
+ properties["Assign to "]?.type === "people"
+ ? (properties["Assign to "].people[0]?.name ?? "")
+ : "",
+
+ status:
+ properties.Status?.type === "select"
+ ? ((properties.Status.select?.name.toLowerCase() as NotionEpisodeStatus) ??
+ "backlog")
+ : "backlog",
+ };
+}
+
+export function normalizeNotionResponse(
+ response: NotionResponse
+): NotionNormalizedResponse {
+ return response.results.map(page => {
+ const properties = page.properties;
+ return normalizeEpisodeProperties(properties);
+ });
+}
diff --git a/src/lib/notion/notion-mock.json b/src/lib/notion/notion-mock.json
new file mode 100644
index 00000000..c67a761a
--- /dev/null
+++ b/src/lib/notion/notion-mock.json
@@ -0,0 +1,244 @@
+[
+ {
+ "title": "Understanding LLMs from Scratch Using Middle School Math",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "next up"
+ },
+ {
+ "title": "New Episode",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "next up"
+ },
+ {
+ "title": "Robotics and Embedded systems ",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "AI in sport ",
+ "date": "2024-11-10",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "Mourad Mtouaa",
+ "status": "scheduled"
+ },
+ {
+ "title": "AMA & Tech News ",
+ "date": "2024-11-24",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "scheduled"
+ },
+ {
+ "title": "AI for good",
+ "date": "2024-11-17",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "ai",
+ "hosts": [],
+ "assignedTo": "Mohammed Daoudi",
+ "status": "scheduled"
+ },
+ {
+ "title": "Fintech in Morocco ",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Startups in morocco",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Golang",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Women in Tech",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "وهم الإنتاجية وتحدي \n- `burnout`",
+ "date": "2024-11-03T20:00:00.000+01:00",
+ "guests": [],
+ "description": "السلام 👋حلقة جديدة الأحد القادم 🚩\n\nالحلقة تهم الجميع فيها غادي نتحدثتوا على الهوس الحديث بالإنتاجية و الأخطار بحال الإرهاق و تدهور الصحية النفسية\n\nغادي ناقشو الحلول كذلك باش نتقدموا فمساراتنا المهنية بدون منهدموا صحاتنا النفسية ✅\n\nكونوا فالموعد و متنساوش أن حضوركم فالتعليقات فالبث المباشر كيساهم فإغناء الدرشة",
+ "youtubeUrl": "https://www.youtube.com/watch?v=3JsNCcz1Hf0",
+ "category": "career",
+ "hosts": [],
+ "assignedTo": "Abdelati EL ASRI",
+ "status": "scheduled"
+ },
+ {
+ "title": "New Episode based on twitter thread ",
+ "date": "2023-10-01",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "next up"
+ },
+ {
+ "title": "Oracle Cloud Infrastructure: Deep Dive",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Azure Cloud: Deep Dive",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "software in 2030",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Quality Assurance ",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "MNCP episode ",
+ "date": "2022-12-04",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Legal things for the programmer",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Evolution of the web",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "Otmane Fettal",
+ "status": "backlog"
+ },
+ {
+ "title": "Quality vs idea vs time managment ",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "dev",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "212 founders for developers",
+ "date": "",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "career",
+ "hosts": [],
+ "assignedTo": "",
+ "status": "backlog"
+ },
+ {
+ "title": "Book ?",
+ "date": "2024-05-19",
+ "guests": [],
+ "description": "",
+ "youtubeUrl": "",
+ "category": "book",
+ "hosts": [],
+ "assignedTo": "Meriem Zaid",
+ "status": "next up"
+ }
+]
diff --git a/src/lib/notion/types.ts b/src/lib/notion/types.ts
new file mode 100644
index 00000000..8f16a229
--- /dev/null
+++ b/src/lib/notion/types.ts
@@ -0,0 +1,196 @@
+interface NotionUser {
+ object: "user";
+ id: string;
+ name?: string;
+ avatar_url?: string | null;
+ type: "person";
+ person?: {
+ email: string;
+ };
+}
+
+interface NotionIcon {
+ type: "emoji";
+ emoji: string;
+}
+
+interface NotionParent {
+ type: "database_id";
+ database_id: string;
+}
+
+interface NotionProperty {
+ id: string;
+ type: string;
+}
+
+interface NotionUrlProperty extends NotionProperty {
+ type: "url";
+ url: string | null;
+}
+
+interface NotionRelationProperty extends NotionProperty {
+ type: "relation";
+ relation: unknown[];
+ has_more: boolean;
+}
+
+interface NotionCreatedByProperty extends NotionProperty {
+ type: "created_by";
+ created_by: NotionUser;
+}
+
+interface NotionRollupProperty extends NotionProperty {
+ type: "rollup";
+ rollup: {
+ type: "array";
+ array: unknown[];
+ function: string;
+ };
+}
+
+interface NotionMultiSelectProperty extends NotionProperty {
+ type: "multi_select";
+ multi_select: Array<{
+ id: string;
+ name: string;
+ color: string;
+ }>;
+}
+
+interface NotionSelectProperty extends NotionProperty {
+ type: "select";
+ select: {
+ id: string;
+ name: string;
+ color: string;
+ } | null;
+}
+
+interface NotionFormulaProperty extends NotionProperty {
+ type: "formula";
+ formula: {
+ type: "string";
+ string: string | null;
+ };
+}
+
+interface NotionPeopleProperty extends NotionProperty {
+ type: "people";
+ people: NotionUser[];
+}
+
+interface NotionDateProperty extends NotionProperty {
+ type: "date";
+ date: {
+ start: string;
+ end: string | null;
+ time_zone: string | null;
+ };
+}
+
+interface NotionRichTextProperty extends NotionProperty {
+ type: "rich_text";
+ rich_text: Array<{
+ type: "text";
+ text: {
+ content: string;
+ link: string | null;
+ };
+ annotations: {
+ bold: boolean;
+ italic: boolean;
+ strikethrough: boolean;
+ underline: boolean;
+ code: boolean;
+ color: string;
+ };
+ plain_text: string;
+ href: string | null;
+ }>;
+}
+
+interface NotionTitleProperty extends NotionProperty {
+ type: "title";
+ title: Array<{
+ type: "text";
+ text: {
+ content: string;
+ link: string | null;
+ };
+ annotations: {
+ bold: boolean;
+ italic: boolean;
+ strikethrough: boolean;
+ underline: boolean;
+ code: boolean;
+ color: string;
+ };
+ plain_text: string;
+ href: string | null;
+ }>;
+}
+
+export type NotionEpisodeProperty =
+ | NotionUrlProperty
+ | NotionRelationProperty
+ | NotionCreatedByProperty
+ | NotionRollupProperty
+ | NotionMultiSelectProperty
+ | NotionSelectProperty
+ | NotionFormulaProperty
+ | NotionPeopleProperty
+ | NotionDateProperty
+ | NotionRichTextProperty
+ | NotionTitleProperty;
+
+interface NotionPage {
+ object: "page";
+ id: string;
+ created_time: string;
+ last_edited_time: string;
+ created_by: NotionUser;
+ last_edited_by: NotionUser;
+ cover: null;
+ icon: NotionIcon;
+ parent: NotionParent;
+ archived: boolean;
+ in_trash: boolean;
+ properties: {
+ [key: string]: NotionEpisodeProperty;
+ };
+ url: string;
+ public_url: string;
+}
+
+export interface NotionResponse {
+ object: "list";
+ results: NotionPage[];
+ next_cursor: string | null;
+ has_more: boolean;
+ type: "page_or_database";
+ page_or_database: Record;
+ request_id: string;
+}
+
+export type NotionEpisodeStatus =
+ | "scheduled"
+ | "next up"
+ | "backlog"
+ | "done"
+ | "archived";
+export type NotionEpisodeCategory = "dev" | "ai" | "ama" | "career" | "mss";
+
+export type NotionEpisodeProperties = {
+ title: string;
+ date: string;
+ guests: string[];
+ description: string;
+ youtubeUrl: string;
+ category: NotionEpisodeCategory;
+ hosts: string[];
+ assignedTo: string;
+ status: NotionEpisodeStatus;
+};
+
+export type NotionNormalizedResponse = NotionEpisodeProperties[];
diff --git a/src/pages/planning.astro b/src/pages/planning.astro
new file mode 100644
index 00000000..e85d6f79
--- /dev/null
+++ b/src/pages/planning.astro
@@ -0,0 +1,10 @@
+---
+import Header from "@/components/header.astro";
+import Layout from "@/components/layout.astro";
+import PlanningList from "@/components/planning/index.astro";
+---
+
+
+
+
+
diff --git a/src/pages/podcast/[...slug].astro b/src/pages/podcast/[...slug].astro
index 521e39cd..1f86199e 100644
--- a/src/pages/podcast/[...slug].astro
+++ b/src/pages/podcast/[...slug].astro
@@ -36,5 +36,12 @@ const episode = Astro.props;
+
+ Planning
+