npm package: @fnd-platform/cms
Description: Projen project class for generating CMS admin packages in fnd-platform
Latest version: 1.0.0-alpha.18

# @fnd-platform/cms

Projen project class for generating headless CMS admin interfaces with Remix, shadcn/ui, content modeling, and media management in fnd-platform monorepos.

## Installation

```bash
npm install -D @fnd-platform/cms
# or
pnpm add -D @fnd-platform/cms
```

## Quick Start

Add a CMS package to your monorepo in `.projenrc.ts`:

```typescript
import { FndMonorepoProject } from '@fnd-platform/core';
import { FndApiProject } from '@fnd-platform/api';
import { FndCmsProject } from '@fnd-platform/cms';

const monorepo = new FndMonorepoProject({
  name: 'my-app',
  defaultReleaseBranch: 'main',
});

const api = new FndApiProject({
  parent: monorepo,
  name: 'api',
  outdir: 'packages/api',
});

const cms = new FndCmsProject({
  parent: monorepo,
  name: 'cms',
  outdir: 'packages/cms',
  api: api, // Link to API for content operations
  port: 3001,
  mediaUpload: true,
  richTextEditor: 'tiptap',
});

monorepo.synth();
```

## Configuration Options

| Option           | Type                      | Default           | Description                  |
| ---------------- | ------------------------- | ----------------- | ---------------------------- |
| `parent`         | `FndMonorepoProject`      | **required**      | Parent monorepo              |
| `name`           | `string`                  | **required**      | Package name                 |
| `api`            | `FndApiProject`           | **required**      | Linked API project           |
| `outdir`         | `string`                  | `packages/{name}` | Output directory             |
| `port`           | `number`                  | `3001`            | Development server port      |
| `mediaUpload`    | `boolean`                 | `true`            | Enable media upload features |
| `richTextEditor` | `'tiptap'` \| `'lexical'` | `'tiptap'`        | Rich text editor library     |
| `shadcnTheme`    | `ShadcnTheme`             | `'zinc'`          | Theme color                  |

## What Gets Generated

```
packages/cms/
├── app/
│   ├── routes/
│   │   ├── _admin.tsx                  # Admin layout (protected)
│   │   ├── _admin._index.tsx           # Dashboard
│   │   ├── _admin.content.tsx          # Content list
│   │   ├── _admin.content.$type.tsx    # Content by type
│   │   ├── _admin.content.$type.$id.tsx # Content editor
│   │   ├── _admin.content.$type.new.tsx # New content
│   │   ├── _admin.media.tsx            # Media library
│   │   ├── _admin.settings.tsx         # Settings
│   │   └── login.tsx                   # Admin login
│   ├── components/
│   │   ├── admin/                      # Admin UI components
│   │   ├── editor/                     # Rich text editor
│   │   └── ui/                         # shadcn/ui components
│   ├── lib/
│   │   ├── api.server.ts               # API client
│   │   ├── auth.server.ts              # Auth utilities
│   │   └── content-types.ts            # Content type definitions
│   └── content-types/                  # Content type definitions
├── package.json
├── tailwind.config.js
├── vite.config.ts
└── tsconfig.json
```

## Features

- **Admin Interface** - Clean, modern admin UI built with shadcn/ui
- **Content Modeling** - Define flexible content types with various field types
- **Draft/Publish Workflow** - Content review and publishing workflow
- **Media Library** - Upload and manage images, videos, and files
- **Rich Text Editor** - Tiptap or Lexical-based content editing
- **Role-Based Access** - Admin, editor, and viewer roles

## Examples

### Content Type Definition

```typescript
// app/content-types/blog-post.ts
import { defineContentType } from '~/lib/content-types';

export const blogPost = defineContentType({
  name: 'blog-post',
  label: 'Blog Post',
  icon: 'FileText',

  fields: [
    {
      name: 'title',
      type: 'text',
      label: 'Title',
      required: true,
      maxLength: 200,
    },
    {
      name: 'slug',
      type: 'slug',
      label: 'URL Slug',
      required: true,
      source: 'title',
    },
    {
      name: 'content',
      type: 'richtext',
      label: 'Content',
      required: true,
    },
    {
      name: 'featuredImage',
      type: 'media',
      label: 'Featured Image',
      accept: ['image/*'],
    },
    {
      name: 'publishedAt',
      type: 'datetime',
      label: 'Publish Date',
    },
  ],
});
```

### Admin Dashboard Route

```typescript
// app/routes/_admin._index.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { requireAdmin } from '~/lib/auth.server';
import { contentApi } from '~/lib/api.server';

export const loader = async ({ request }: LoaderFunctionArgs) => {
  await requireAdmin(request);

  const [stats, recentContent] = await Promise.all([
    contentApi.getStats(),
    contentApi.list({ limit: 10, sort: '-updatedAt' }),
  ]);

  return json({ stats, recentContent });
};

export default function Dashboard() {
  const { stats, recentContent } = useLoaderData<typeof loader>();

  return (
    <div className="space-y-6">
      <h1 className="text-2xl font-bold">Dashboard</h1>
      {/* Dashboard content */}
    </div>
  );
}
```

### Content Editor Route

```typescript
// app/routes/_admin.content.$type.$id.tsx
import { json, redirect, type ActionFunctionArgs } from '@remix-run/node';
import { requireAdmin } from '~/lib/auth.server';
import { contentApi } from '~/lib/api.server';

export const action = async ({ request, params }: ActionFunctionArgs) => {
  await requireAdmin(request);

  const formData = await request.formData();
  const intent = formData.get('intent');

  if (intent === 'save') {
    const data = Object.fromEntries(formData);
    await contentApi.update(params.id!, data);
    return json({ success: true });
  }

  if (intent === 'publish') {
    await contentApi.publish(params.id!);
    return json({ success: true });
  }

  if (intent === 'delete') {
    await contentApi.delete(params.id!);
    return redirect(`/admin/content/${params.type}`);
  }

  return json({ error: 'Invalid action' }, { status: 400 });
};
```

## Field Types

| Type        | Description       | Options                |
| ----------- | ----------------- | ---------------------- |
| `text`      | Single line text  | `maxLength`, `pattern` |
| `textarea`  | Multi-line text   | `maxLength`, `rows`    |
| `richtext`  | Rich text editor  | `toolbar`, `formats`   |
| `number`    | Numeric input     | `min`, `max`, `step`   |
| `boolean`   | Toggle/checkbox   | `default`              |
| `select`    | Dropdown select   | `options`              |
| `date`      | Date picker       | `min`, `max`           |
| `datetime`  | Date and time     | `min`, `max`           |
| `slug`      | URL slug          | `source`               |
| `media`     | Media picker      | `accept`, `multiple`   |
| `reference` | Content reference | `referenceType`        |
| `tags`      | Tag input         | `suggestions`          |

## Content Status

| Status      | Description              |
| ----------- | ------------------------ |
| `draft`     | Being edited, not public |
| `published` | Live and visible         |
| `scheduled` | Will publish at set time |
| `archived`  | Hidden but preserved     |

## Development

```bash
# Start development server
cd packages/cms
pnpm dev

# Build for production
pnpm build

# Run tests
pnpm test
```

## API Reference

See the [full API documentation](../../docs/api/modules/cms_src.html) for detailed type definitions and examples.

### Project Class

- `FndCmsProject` - Projen project class for CMS packages

### Types

```typescript
import type { FndCmsProjectOptions } from '@fnd-platform/cms';
```

## Requirements

- Node.js 20+
- pnpm 8+
- @fnd-platform/core
- @fnd-platform/api (for API linking)

## Related

- [@fnd-platform/core](../core/README.md) - Core Projen project classes
- [@fnd-platform/api](../api/README.md) - API package for backend
- [@fnd-platform/frontend](../frontend/README.md) - Public-facing frontend
- [@fnd-platform/cognito-auth](../cognito-auth/README.md) - Authentication

## License

MIT