From 4541d7cd24dc83bfa9a7ebfdd45d4ce9cc50e1bc Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sun, 21 Dec 2025 13:24:43 +1100 Subject: [PATCH 1/2] Add docs for SDK --- apps/docs/content/docs/index.mdx | 14 +- apps/docs/content/docs/sdk/core.mdx | 61 +++++ apps/docs/content/docs/sdk/index.mdx | 18 ++ .../content/docs/sdk/nextjs-app-router.mdx | 174 +++++++++++++++ .../content/docs/sdk/nextjs-pages-router.mdx | 211 ++++++++++++++++++ apps/docs/content/docs/sdk/react.mdx | 85 +++++++ packages/react-sdk/package.json | 4 +- pnpm-lock.yaml | 9 +- 8 files changed, 564 insertions(+), 12 deletions(-) create mode 100644 apps/docs/content/docs/sdk/core.mdx create mode 100644 apps/docs/content/docs/sdk/index.mdx create mode 100644 apps/docs/content/docs/sdk/nextjs-app-router.mdx create mode 100644 apps/docs/content/docs/sdk/nextjs-pages-router.mdx create mode 100644 apps/docs/content/docs/sdk/react.mdx diff --git a/apps/docs/content/docs/index.mdx b/apps/docs/content/docs/index.mdx index 88c2338..cad7ef8 100644 --- a/apps/docs/content/docs/index.mdx +++ b/apps/docs/content/docs/index.mdx @@ -3,12 +3,10 @@ title: Welcome! description: changelog made smarter, faster, and user-focused. --- -Looking to setup our widget? +## Getting Started - - -## Want to deep dive? - -Dive a little deeper and start exploring our API reference to get an idea of everything that's possible with the API: - - + + + + + diff --git a/apps/docs/content/docs/sdk/core.mdx b/apps/docs/content/docs/sdk/core.mdx new file mode 100644 index 0000000..18dac46 --- /dev/null +++ b/apps/docs/content/docs/sdk/core.mdx @@ -0,0 +1,61 @@ +--- +title: Core SDK +description: Framework-agnostic JavaScript SDK for fetching changelog data. +--- + +## Installation + +```bash +npm install @changespage/core +``` + +## Usage + +```typescript +import { createChangesPageClient } from "@changespage/core"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +const { posts, totalCount, hasMore } = await client.getPosts({ limit: 10 }); + +const latestPost = await client.getLatestPost(); + +const pinnedPost = await client.getPinnedPost(); +``` + +## API + +### `createChangesPageClient(config)` + +Creates a client instance. + +| Option | Type | Description | +| --------- | -------- | ------------------------ | +| `baseUrl` | `string` | Your changes.page URL | + +### Client Methods + +#### `getPosts(options?)` + +Fetches paginated posts. + +| Option | Type | Default | Description | +| -------- | -------- | ------- | -------------------------- | +| `limit` | `number` | 10 | Number of posts (max: 50) | +| `offset` | `number` | 0 | Pagination offset | + +Returns `{ posts, totalCount, hasMore }`. + +#### `getLatestPost()` + +Returns the most recent post or `null`. + +#### `getPinnedPost()` + +Returns the pinned post or `null`. + +### `getTagLabel(tag)` + +Converts a tag to display label (e.g., `"fix"` → `"Fix"`). diff --git a/apps/docs/content/docs/sdk/index.mdx b/apps/docs/content/docs/sdk/index.mdx new file mode 100644 index 0000000..7ffe16f --- /dev/null +++ b/apps/docs/content/docs/sdk/index.mdx @@ -0,0 +1,18 @@ +--- +title: SDKs +description: JavaScript and React SDKs for integrating changelogs into your application. +--- + +We provide official SDKs to help you integrate changelogs directly into your application. + + + + + + +## Next.js Examples + + + + + diff --git a/apps/docs/content/docs/sdk/nextjs-app-router.mdx b/apps/docs/content/docs/sdk/nextjs-app-router.mdx new file mode 100644 index 0000000..bb76825 --- /dev/null +++ b/apps/docs/content/docs/sdk/nextjs-app-router.mdx @@ -0,0 +1,174 @@ +--- +title: Next.js App Router +description: Using the React SDK with Next.js App Router. +--- + +## Installation + +```bash +npm install @changespage/react react-markdown +``` + +## Server Component with Client Hydration + +```tsx title="app/changelog/page.tsx" +import { createChangesPageClient } from "@changespage/react"; +import { ChangelogList } from "./changelog-list"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +export default async function ChangelogPage() { + const initialData = await client.getPosts({ limit: 10 }); + + return ; +} +``` + +```tsx title="app/changelog/changelog-list.tsx" +"use client"; + +import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +export function ChangelogList({ initialData }: { initialData: Awaited> }) { + const { posts, hasMore, loading, loadMore } = usePosts({ + client, + initialData, + }); + + return ( +
+ {posts.map((post) => ( +
+
+ {post.tags.map((tag) => ( + {getTagLabel(tag)} + ))} +
+

{post.title}

+ {post.plain_text_content} +
+ ))} + {hasMore && ( + + )} +
+ ); +} +``` + +## Client-Only + +```tsx title="app/changelog/page.tsx" +"use client"; + +import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +export default function ChangelogPage() { + const { posts, hasMore, loading, loadMore } = usePosts({ client }); + + if (loading && posts.length === 0) { + return
Loading...
; + } + + return ( +
+ {posts.map((post) => ( +
+
+ {post.tags.map((tag) => ( + {getTagLabel(tag)} + ))} +
+

{post.title}

+ {post.plain_text_content} +
+ ))} + {hasMore && ( + + )} +
+ ); +} +``` + +## With Tailwind CSS + +```tsx title="app/changelog/page.tsx" +"use client"; + +import { createChangesPageClient, usePosts, getTagLabel, type PostTag } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +const tagColors: Record = { + new: "bg-green-100 text-green-800", + fix: "bg-red-100 text-red-800", + improvement: "bg-blue-100 text-blue-800", + announcement: "bg-purple-100 text-purple-800", + alert: "bg-yellow-100 text-yellow-800", +}; + +export default function ChangelogPage() { + const { posts, hasMore, loading, loadMore } = usePosts({ client }); + + if (loading && posts.length === 0) { + return ( +
+
+
+ ); + } + + return ( +
+ {posts.map((post) => ( +
+
+ {post.tags.map((tag) => ( + + {getTagLabel(tag)} + + ))} +
+

{post.title}

+
+ {post.plain_text_content} +
+
+ ))} + {hasMore && ( + + )} +
+ ); +} +``` diff --git a/apps/docs/content/docs/sdk/nextjs-pages-router.mdx b/apps/docs/content/docs/sdk/nextjs-pages-router.mdx new file mode 100644 index 0000000..766e897 --- /dev/null +++ b/apps/docs/content/docs/sdk/nextjs-pages-router.mdx @@ -0,0 +1,211 @@ +--- +title: Next.js Pages Router +description: Using the React SDK with Next.js Pages Router. +--- + +## Installation + +```bash +npm install @changespage/react react-markdown +``` + +## With SSR (getServerSideProps) + +```tsx title="pages/changelog.tsx" +import type { InferGetServerSidePropsType } from "next"; +import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +export async function getServerSideProps() { + const initialData = await client.getPosts({ limit: 10 }); + return { props: { initialData } }; +} + +export default function ChangelogPage({ + initialData, +}: InferGetServerSidePropsType) { + const { posts, hasMore, loading, loadMore } = usePosts({ + client, + initialData, + }); + + return ( +
+ {posts.map((post) => ( +
+
+ {post.tags.map((tag) => ( + {getTagLabel(tag)} + ))} +
+

{post.title}

+ {post.plain_text_content} +
+ ))} + {hasMore && ( + + )} +
+ ); +} +``` + +## With SSG (getStaticProps) + +```tsx title="pages/changelog.tsx" +import type { InferGetStaticPropsType } from "next"; +import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +export async function getStaticProps() { + const initialData = await client.getPosts({ limit: 10 }); + return { + props: { initialData }, + revalidate: 60, + }; +} + +export default function ChangelogPage({ + initialData, +}: InferGetStaticPropsType) { + const { posts, hasMore, loading, loadMore } = usePosts({ + client, + initialData, + }); + + return ( +
+ {posts.map((post) => ( +
+
+ {post.tags.map((tag) => ( + {getTagLabel(tag)} + ))} +
+

{post.title}

+ {post.plain_text_content} +
+ ))} + {hasMore && ( + + )} +
+ ); +} +``` + +## Client-Only + +```tsx title="pages/changelog.tsx" +import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +export default function ChangelogPage() { + const { posts, hasMore, loading, loadMore } = usePosts({ client }); + + if (loading && posts.length === 0) { + return
Loading...
; + } + + return ( +
+ {posts.map((post) => ( +
+
+ {post.tags.map((tag) => ( + {getTagLabel(tag)} + ))} +
+

{post.title}

+ {post.plain_text_content} +
+ ))} + {hasMore && ( + + )} +
+ ); +} +``` + +## With Tailwind CSS + +```tsx title="pages/changelog.tsx" +import { createChangesPageClient, usePosts, getTagLabel, type PostTag } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +const tagColors: Record = { + new: "bg-green-100 text-green-800", + fix: "bg-red-100 text-red-800", + improvement: "bg-blue-100 text-blue-800", + announcement: "bg-purple-100 text-purple-800", + alert: "bg-yellow-100 text-yellow-800", +}; + +export default function ChangelogPage() { + const { posts, hasMore, loading, loadMore } = usePosts({ client }); + + if (loading && posts.length === 0) { + return ( +
+
+
+ ); + } + + return ( +
+ {posts.map((post) => ( +
+
+ {post.tags.map((tag) => ( + + {getTagLabel(tag)} + + ))} +
+

{post.title}

+
+ {post.plain_text_content} +
+
+ ))} + {hasMore && ( + + )} +
+ ); +} +``` diff --git a/apps/docs/content/docs/sdk/react.mdx b/apps/docs/content/docs/sdk/react.mdx new file mode 100644 index 0000000..2371963 --- /dev/null +++ b/apps/docs/content/docs/sdk/react.mdx @@ -0,0 +1,85 @@ +--- +title: React SDK +description: React components and hooks for embedding changelogs. +--- + +## Installation + +```bash +npm install @changespage/react react-markdown +``` + +## Usage + +```tsx +"use client"; + +import { createChangesPageClient, usePosts, ChangelogPost } from "@changespage/react"; +import Markdown from "react-markdown"; + +const client = createChangesPageClient({ + baseUrl: "https://your-page.changes.page", +}); + +function Changelog() { + const { posts, hasMore, loading, loadMore } = usePosts({ client }); + + if (loading) return
Loading...
; + + return ( +
+ {posts.map((post) => ( + + {({ title, plainText, tags, publicationDate }) => ( +
+

{title}

+ + {plainText} +
+ )} +
+ ))} + {hasMore && } +
+ ); +} +``` + +## API + +### `usePosts(options)` + +Hook for fetching and managing posts. + +| Option | Type | Description | +| ------------- | --------------------- | ------------------------------ | +| `client` | `ChangesPageClient` | Client instance | +| `initialData` | `UsePostsInitialData` | SSR initial data (optional) | +| `limit` | `number` | Posts per page (default: 10) | + +Returns: + +| Property | Type | Description | +| ---------- | ------------ | ------------------------ | +| `posts` | `Post[]` | Fetched posts | +| `hasMore` | `boolean` | More posts available | +| `loading` | `boolean` | Loading state | +| `error` | `Error` | Error if any | +| `loadMore` | `() => void` | Load next page | +| `refetch` | `() => void` | Refresh posts | + +### `` + +Render prop component for displaying posts. + +```tsx + + {({ id, title, content, plainText, tags, publicationDate, url }) => ( + // Your custom UI + )} + +``` + +### Re-exports + +The React SDK re-exports everything from `@changespage/core` including `createChangesPageClient`, `getTagLabel`, and all types. diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index 803a8c1..0b0efb9 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@changespage/react", - "version": "0.3.0", + "version": "0.4.0", "type": "module", "module": "./dist/index.js", "types": "./dist/index.d.ts", @@ -18,7 +18,7 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@changespage/core": "workspace:*" + "@changespage/core": "0.2.0" }, "devDependencies": { "@types/react": "^18.3.18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abdc0da..755825a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -467,8 +467,8 @@ importers: packages/react-sdk: dependencies: '@changespage/core': - specifier: workspace:* - version: link:../core-sdk + specifier: 0.2.0 + version: 0.2.0 devDependencies: '@types/react': specifier: ^18.3.18 @@ -648,6 +648,9 @@ packages: '@bufbuild/protobuf@1.10.0': resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} + '@changespage/core@0.2.0': + resolution: {integrity: sha512-Uwp0iECx5qMpZL5DDBxBvwEvNo0RJ+qjfbGTN7N28DoB3ir9MiZGT1U4/r1WFL+8Sb4wRKmw7nv3rFfAKw9nVA==} + '@changespage/react@0.3.0': resolution: {integrity: sha512-wdGFb/om5/lJg2F2XBBJIrvBWfnnxgvGy9mFDn61dJNjSj6NHK6o8IRLWem+8GshY7Rwpj2LFpFbzgTVGQspLw==} peerDependencies: @@ -6770,6 +6773,8 @@ snapshots: '@bufbuild/protobuf@1.10.0': {} + '@changespage/core@0.2.0': {} + '@changespage/react@0.3.0(react@18.3.1)': dependencies: '@changespage/core': link:packages/core-sdk From 0a111bc2ffafa10069e3a9be67934123e7de01b3 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Sun, 21 Dec 2025 14:41:44 +1100 Subject: [PATCH 2/2] Update revalidate time --- .../content/docs/sdk/nextjs-pages-router.mdx | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/apps/docs/content/docs/sdk/nextjs-pages-router.mdx b/apps/docs/content/docs/sdk/nextjs-pages-router.mdx index 766e897..90fc235 100644 --- a/apps/docs/content/docs/sdk/nextjs-pages-router.mdx +++ b/apps/docs/content/docs/sdk/nextjs-pages-router.mdx @@ -13,7 +13,11 @@ npm install @changespage/react react-markdown ```tsx title="pages/changelog.tsx" import type { InferGetServerSidePropsType } from "next"; -import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import { + createChangesPageClient, + usePosts, + getTagLabel, +} from "@changespage/react"; import Markdown from "react-markdown"; const client = createChangesPageClient({ @@ -60,7 +64,11 @@ export default function ChangelogPage({ ```tsx title="pages/changelog.tsx" import type { InferGetStaticPropsType } from "next"; -import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import { + createChangesPageClient, + usePosts, + getTagLabel, +} from "@changespage/react"; import Markdown from "react-markdown"; const client = createChangesPageClient({ @@ -71,7 +79,7 @@ export async function getStaticProps() { const initialData = await client.getPosts({ limit: 10 }); return { props: { initialData }, - revalidate: 60, + revalidate: 86400, }; } @@ -109,7 +117,11 @@ export default function ChangelogPage({ ## Client-Only ```tsx title="pages/changelog.tsx" -import { createChangesPageClient, usePosts, getTagLabel } from "@changespage/react"; +import { + createChangesPageClient, + usePosts, + getTagLabel, +} from "@changespage/react"; import Markdown from "react-markdown"; const client = createChangesPageClient({ @@ -149,7 +161,12 @@ export default function ChangelogPage() { ## With Tailwind CSS ```tsx title="pages/changelog.tsx" -import { createChangesPageClient, usePosts, getTagLabel, type PostTag } from "@changespage/react"; +import { + createChangesPageClient, + usePosts, + getTagLabel, + type PostTag, +} from "@changespage/react"; import Markdown from "react-markdown"; const client = createChangesPageClient({ @@ -178,7 +195,10 @@ export default function ChangelogPage() { return (
{posts.map((post) => ( -
+
{post.tags.map((tag) => ( ))}
-

{post.title}

+

+ {post.title} +

{post.plain_text_content}