npm package: @jjjjjia11/content-client
Description: React hooks for blog management
Author: Shumbird
Latest version: 1.0.0

# @jjjjjia11/content-client

React hooks for blog management - fetch and manage blog posts with ease.

## Installation

```bash
npm install @jjjjjia11/content-client
```

## Quick Start

```typescript
import { createBlogHooks } from '@jjjjjia11/content-client';

// Initialize hooks
const blogHooks = createBlogHooks({
  apiBaseUrl: '/blog', // or 'https://api.yoursite.com/blog'
  getAuthToken: async () => localStorage.getItem('token') // Optional for admin operations
});

// Destructure the hooks
const { useBlogPosts, useBlogPost } = blogHooks;

// Use in your components
function BlogList() {
  const { data, loading, error } = useBlogPosts({ page: 1, limit: 10 });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data?.posts.map(post => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.description}</p>
        </div>
      ))}
    </div>
  );
}
```

## Available Hooks

### `useBlogPosts(params?)`

Fetch published blog posts with pagination and filtering.

**Parameters:**
- `category?: string` - Filter by category
- `featured?: boolean` - Show only featured posts
- `search?: string` - Search in title/description
- `language?: string` - Filter by language (en/zh)
- `page?: number` - Page number (default: 1)
- `limit?: number` - Posts per page (default: 10)

**Returns:** `{ data, loading, error }`

**Example:**
```typescript
const { data, loading } = useBlogPosts({
  category: 'Tech',
  featured: true,
  page: 1,
  limit: 10
});
```

### `useBlogPost(slug: string)`

Fetch a single blog post by slug.

**Returns:** `{ data, loading, error }`

**Example:**
```typescript
const { data: post, loading } = useBlogPost('my-blog-post');
```

### `useAdminBlogPosts(params?)` (requires auth)

Fetch all posts including unpublished (admin only).

**Parameters:**
- `published?: boolean` - Filter by published status
- `language?: string` - Filter by language
- `page?: number` - Page number
- `limit?: number` - Posts per page

**Returns:** `{ data, loading, error, refetch }`

**Example:**
```typescript
const { data, loading, refetch } = useAdminBlogPosts({
  published: false, // Show only drafts
  page: 1
});
```

## Available Functions

### `saveBlogPost(post, id?)`

Create or update a blog post (requires auth).

**Example:**
```typescript
// Create new post
const newPost = await saveBlogPost({
  title: 'My Post',
  description: 'Description',
  content: 'Markdown content...',
  slug: 'my-post',
  published: true,
  category: 'Tech',
  tags: ['javascript']
});

// Update existing post
const updated = await saveBlogPost({
  title: 'Updated Title'
}, postId);
```

### `deleteBlogPost(id)`

Delete a blog post by ID (requires auth).

**Example:**
```typescript
await deleteBlogPost(postId);
```

## Complete Example

```typescript
import React from 'react';
import { createBlogHooks } from '@jjjjjia11/content-client';

// Initialize hooks
const { useBlogPosts, useBlogPost, useAdminBlogPosts, saveBlogPost, deleteBlogPost } = createBlogHooks({
  apiBaseUrl: '/blog',
  getAuthToken: async () => localStorage.getItem('token')
});

// Public blog list
function BlogList() {
  const [page, setPage] = React.useState(1);
  const { data, loading } = useBlogPosts({ page, limit: 10 });

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      {data?.posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.description}</p>
          <a href={`/blog/${post.slug}`}>Read more</a>
        </article>
      ))}

      {/* Pagination */}
      <button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
        Previous
      </button>
      <span>Page {page} of {data?.pagination.totalPages}</span>
      <button
        onClick={() => setPage(p => p + 1)}
        disabled={page >= (data?.pagination.totalPages || 1)}
      >
        Next
      </button>
    </div>
  );
}

// Single post view
function BlogPost({ slug }: { slug: string }) {
  const { data: post, loading } = useBlogPost(slug);

  if (loading) return <div>Loading...</div>;
  if (!post) return <div>Post not found</div>;

  return (
    <article>
      <h1>{post.title}</h1>
      {post.image && <img src={post.image} alt={post.title} />}
      <div dangerouslySetInnerHTML={{ __html: post.content || '' }} />
      <div>
        {post.tags?.map(tag => <span key={tag}>#{tag}</span>)}
      </div>
    </article>
  );
}

// Admin dashboard
function AdminDashboard() {
  const { data, loading, refetch } = useAdminBlogPosts({ page: 1, limit: 50 });

  const handleDelete = async (id: string | number) => {
    if (confirm('Delete this post?')) {
      await deleteBlogPost(id);
      refetch();
    }
  };

  const handleCreate = async () => {
    await saveBlogPost({
      title: 'New Post',
      description: 'Description',
      content: 'Content...',
      slug: 'new-post',
      published: false
    });
    refetch();
  };

  return (
    <div>
      <button onClick={handleCreate}>Create Post</button>

      <table>
        <thead>
          <tr>
            <th>Title</th>
            <th>Status</th>
            <th>Views</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {data?.posts.map(post => (
            <tr key={post.id}>
              <td>{post.title}</td>
              <td>{post.published ? 'Published' : 'Draft'}</td>
              <td>{post.views || 0}</td>
              <td>
                <button onClick={() => handleDelete(post.id)}>Delete</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
```

## TypeScript Types

```typescript
import type {
  IBlogPost,
  PaginationInfo,
  BlogListResponse,
  BlogClientConfig
} from '@jjjjjia11/content-client';

// Blog post structure
interface IBlogPost {
  id: string | number;
  title: string;
  description: string;
  content?: string;
  image?: string;
  author?: string;
  readTime?: number;
  slug: string;
  createdAt: string;
  publishedAt?: string;
  category?: string;
  tags?: string[];
  views?: number;
  published?: boolean;
  featured?: boolean;
  language?: string;
  originalPostId?: string | number;
  uploadedAssets?: string[];
}
```

## License

MIT