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
14 changes: 6 additions & 8 deletions apps/docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ title: Welcome!
description: changelog made smarter, faster, and user-focused.
---

Looking to setup our widget?
## Getting Started

<Card title="Setup Widget" href="/docs/widget" />

## 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:

<Card title="API Reference" href="/docs/api/page" />
<Cards>
<Card title="Setup Widget" href="/docs/widget" />
<Card title="SDKs" href="/docs/sdk" />
<Card title="API Reference" href="/docs/api/page" />
</Cards>
61 changes: 61 additions & 0 deletions apps/docs/content/docs/sdk/core.mdx
Original file line number Diff line number Diff line change
@@ -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"`).
18 changes: 18 additions & 0 deletions apps/docs/content/docs/sdk/index.mdx
Original file line number Diff line number Diff line change
@@ -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.

<Cards>
<Card title="Core SDK" href="/docs/sdk/core" />
<Card title="React SDK" href="/docs/sdk/react" />
</Cards>

## Next.js Examples

<Cards>
<Card title="App Router" href="/docs/sdk/nextjs-app-router" />
<Card title="Pages Router" href="/docs/sdk/nextjs-pages-router" />
</Cards>
174 changes: 174 additions & 0 deletions apps/docs/content/docs/sdk/nextjs-app-router.mdx
Original file line number Diff line number Diff line change
@@ -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 <ChangelogList initialData={initialData} />;
}
```

```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<ReturnType<typeof client.getPosts>> }) {
const { posts, hasMore, loading, loadMore } = usePosts({
client,
initialData,
});

return (
<div>
{posts.map((post) => (
<article key={post.id}>
<div>
{post.tags.map((tag) => (
<span key={tag}>{getTagLabel(tag)}</span>
))}
</div>
<h2>{post.title}</h2>
<Markdown>{post.plain_text_content}</Markdown>
</article>
))}
{hasMore && (
<button type="button" onClick={loadMore} disabled={loading}>
{loading ? "Loading..." : "Load More"}
</button>
)}
</div>
);
}
```

## 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 <div>Loading...</div>;
}

return (
<div>
{posts.map((post) => (
<article key={post.id}>
<div>
{post.tags.map((tag) => (
<span key={tag}>{getTagLabel(tag)}</span>
))}
</div>
<h2>{post.title}</h2>
<Markdown>{post.plain_text_content}</Markdown>
</article>
))}
{hasMore && (
<button type="button" onClick={loadMore} disabled={loading}>
{loading ? "Loading..." : "Load More"}
</button>
)}
</div>
);
}
```

## 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<PostTag, string> = {
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 (
<div className="flex justify-center py-12">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-gray-800" />
</div>
);
}

return (
<div className="mx-auto max-w-2xl space-y-8 px-4 py-8">
{posts.map((post) => (
<article key={post.id} className="rounded-lg border border-gray-200 p-6">
<div className="mb-3 flex flex-wrap gap-2">
{post.tags.map((tag) => (
<span
key={tag}
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${tagColors[tag]}`}
>
{getTagLabel(tag)}
</span>
))}
</div>
<h2 className="mb-2 text-xl font-semibold text-gray-900">{post.title}</h2>
<div className="prose prose-sm text-gray-600">
<Markdown>{post.plain_text_content}</Markdown>
</div>
</article>
))}
{hasMore && (
<button
type="button"
onClick={loadMore}
disabled={loading}
className="w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
>
{loading ? "Loading..." : "Load More"}
</button>
)}
</div>
);
}
```
Loading