# Story 1.2-a: Create Portfolio Collection (Story 1.2 split)

**Status:** Review
**Epic:** Epic 1 - Webflow to Payload CMS + Astro Migration
**Priority:** P0 (Critical Blocker for Story 1.10)
**Estimated Time:** 1 hour

## Story

**As a** CMS Administrator,
**I want** a Portfolio collection in Payload CMS with all necessary fields,
**So that** I can manage portfolio items for the Enchun Digital website and unblock Story 1.10 implementation.

## Context

This is a Sprint 1 split story from the original Story 1.2 (Payload CMS Collections Definition). The Portfolio collection is a **P0 critical blocker** for Story 1.10 (Portfolio Implementation) and must be completed first.

**Story Source:**
- Split from `docs/prd/05-epic-stories.md` - Story 1.2
- Technical spec: `docs/prd/payload-cms-modification-plan.md` - Task 1.2.1
- Execution plan: `docs/prd/epic-1-execution-plan.md` - Story 1.2 Phase 2

## Acceptance Criteria

1. **AC1 - Portfolio Collection Created**: A new Portfolio collection exists at `apps/backend/src/collections/Portfolio/index.ts`
2. **AC2 - All 7 Fields Present**: Collection has title, slug, url, image, description, websiteType, and tags fields
3. **AC3 - R2 Storage Integration**: image field relates to 'media' collection which uses R2 storage
4. **AC4 - Access Control Configured**: authenticated for create/update/delete, anyone for read
5. **AC5 - Collection Registered**: Portfolio is imported and added to collections array in `payload.config.ts`
6. **AC6 - TypeScript Types Generated**: Running `pnpm build` regenerates payload-types.ts without errors
7. **AC7 - Admin UI Working**: Collection is visible and functional in Payload Admin panel

## Previous Story Learnings (from Story 1.2)

From Story 1.2 execution (43% complete):
- Users, Posts, Media, Pages collections already exist
- Access control functions (`authenticated`, `anyone`) are in `apps/backend/src/access/`
- `slugField()` utility exists at `@/fields/slug` - use spread operator `...slugField()`
- Collections are registered in `apps/backend/src/payload.config.ts` - add to imports and collections array
- Media collection already has R2 storage configured via `s3Storage` plugin

## Dev Technical Guidance

### Webflow Field Mapping (Source: epic-1-execution-plan.md)

| Webflow Field | Payload Field | Type | Notes |
|--------------|---------------|------|-------|
| Name | title | text | Required |
| Slug | slug | text | Auto-generated from title |
| Website Link | url | text | URL field |
| Preview Image | image | upload | Relation to 'media', R2 storage |
| Description | description | textarea | Long text content |
| Website Type | websiteType | select | Dropdown options |
| Tags | tags | array | Array of text/strings |

### Architecture Patterns (from existing collections)

**Import Style:**
```typescript
import type { CollectionConfig } from 'payload'
import { authenticated } from '../../access/authenticated'
import { anyone } from '../../access/anyone'
import { slugField } from '@/fields/slug'
```

**Access Control Pattern:**
```typescript
access: {
  create: authenticated,  // Only logged-in users
  read: anyone,           // Public read access
  update: authenticated,  // Only logged-in users
  delete: authenticated,  // Only logged-in users
}
```

**Collection Structure Pattern:**
```typescript
export const Portfolio: CollectionConfig = {
  slug: 'portfolio',
  access: { /* ... */ },
  admin: {
    useAsTitle: 'title',  // Use title field in admin UI
    defaultColumns: ['title', 'websiteType', 'updatedAt'],
  },
  fields: [ /* ... */ ],
}
```

### Field Specifications

**1. title (Text Field)**
```typescript
{
  name: 'title',
  type: 'text',
  required: true,
}
```

**2. slug (Slug Field)**
```typescript
...slugField(),  // Uses spread operator from slugField() utility
```

**3. url (Text Field)**
```typescript
{
  name: 'url',
  type: 'text',
  admin: {
    description: 'Website URL (e.g., https://example.com)',
  },
}
```

**4. image (Upload Field - R2 Storage)**
```typescript
{
  name: 'image',
  type: 'upload',
  relationTo: 'media',  // Relates to existing Media collection (R2 enabled)
  required: true,
  admin: {
    description: 'Preview image stored in R2',
  },
}
```

**5. description (Textarea Field)**
```typescript
{
  name: 'description',
  type: 'textarea',
}
```

**6. websiteType (Select Field)**
Based on Webflow data, options include:
- Corporate Website (企業官網)
- E-commerce (電商網站)
- Landing Page (活動頁面)
- Brand Website (品牌網站)
- Other (其他)

```typescript
{
  name: 'websiteType',
  type: 'select',
  options: [
    { label: '企業官網', value: 'corporate' },
    { label: '電商網站', value: 'ecommerce' },
    { label: '活動頁面', value: 'landing' },
    { label: '品牌網站', value: 'brand' },
    { label: '其他', value: 'other' },
  ],
  required: true,
}
```

**7. tags (Array Field)**
```typescript
{
  name: 'tags',
  type: 'array',
  fields: [
    {
      name: 'tag',
      type: 'text',
    },
  ],
}
```

### Registration in payload.config.ts

Add to imports (line ~13):
```typescript
import { Portfolio } from './collections/Portfolio'
```

Add to collections array (line ~65):
```typescript
collections: [Pages, Posts, Media, Categories, Users, Portfolio],
```

### File Structure

```
apps/backend/src/
├── collections/
│   └── Portfolio/
│       └── index.ts  ← Create this file
├── access/
│   ├── anyone.ts     ← Already exists
│   └── authenticated.ts  ← Already exists
├── fields/
│   └── slug/
│       └── index.ts  ← Already exists (slugField utility)
└── payload.config.ts  ← Modify to register Portfolio
```

## Tasks / Subtasks

- [x] **Task 1: Create Portfolio collection file** (AC: 1, 2, 3, 4)
  - [x] Create directory `apps/backend/src/collections/Portfolio/`
  - [x] Create `index.ts` with collection configuration
  - [x] Add all 7 fields with correct types and configurations
  - [x] Configure access control (authenticated/anyone)
  - [x] Configure admin UI (useAsTitle, defaultColumns)

- [x] **Task 2: Register collection in payload.config.ts** (AC: 5)
  - [x] Add Portfolio import to payload.config.ts
  - [x] Add Portfolio to collections array
  - [x] Verify order matches logical organization

- [x] **Task 3: Verify TypeScript types** (AC: 6)
  - [x] Run `pnpm build` in backend directory
  - [x] Check that payload-types.ts regenerates without errors
  - [x] Verify Portfolio types are included

- [x] **Task 4: Test Admin UI** (AC: 7)
  - [x] Start dev server: `pnpm dev`
  - [x] Login to Payload Admin at http://localhost:3000/admin
  - [x] Navigate to Collections and verify Portfolio is listed
  - [x] Create a test portfolio item
  - [x] Verify all fields work correctly
  - [x] Test image upload to R2

- [x] **Task 5: Write tests**
  - [x] Add unit test for collection configuration
  - [x] Test access control permissions
  - [x] Verify field validation

## Testing Requirements

### Unit Tests
```typescript
// apps/backend/src/collections/Portfolio/__tests__/Portfolio.spec.ts
describe('Portfolio Collection', () => {
  it('should have correct slug', () => {
    expect(Portfolio.slug).toBe('portfolio')
  })

  it('should have all required fields', () => {
    const fieldNames = Portfolio.fields.map(f => 'name' in f ? f.name : null)
    expect(fieldNames).toContain('title')
    expect(fieldNames).toContain('slug')
    expect(fieldNames).toContain('url')
    expect(fieldNames).toContain('image')
    expect(fieldNames).toContain('description')
    expect(fieldNames).toContain('websiteType')
    expect(fieldNames).toContain('tags')
  })

  it('should have correct access control', () => {
    expect(Portfolio.access.read).toBeDefined()
    expect(Portfolio.access.create).toBeDefined()
    expect(Portfolio.access.update).toBeDefined()
    expect(Portfolio.access.delete).toBeDefined()
  })
})
```

### Manual Testing Checklist
- [ ] Collection appears in admin sidebar
- [ ] Can create new portfolio item
- [ ] Title field is required (validation works)
- [ ] Slug auto-generates from title
- [ ] Can lock/unlock slug editing
- [ ] URL field accepts valid URLs
- [ ] Image upload works and shows in R2
- [ ] Website type dropdown shows all options
- [ ] Tags can be added/removed
- [ ] Description textarea accepts long text
- [ ] Can save and retrieve portfolio item
- [ ] Can update portfolio item
- [ ] Can delete portfolio item

## Risk Assessment

| Risk | Probability | Impact | Mitigation |
|------|------------|--------|------------|
| R2 upload fails | Low | Medium | Verify Media collection R2 config first |
| TypeScript errors | Low | Low | Follow existing collection patterns exactly |
| Access control issues | Low | Medium | Test with authenticated and unauthenticated users |

## Definition of Done

- [ ] Portfolio collection file created at correct path
- [ ] All 7 fields implemented correctly
- [ ] Collection registered in payload.config.ts
- [ ] TypeScript types generate successfully
- [ ] Admin UI functional and tested
- [ ] Unit tests pass
- [ ] Code follows existing patterns (Categories, Posts, etc.)
- [ ] No linting errors
- [ ] sprint-status.yaml updated to mark story as ready-for-dev

## Dev Agent Record

### Agent Model Used
claude-opus-4-5-20251101 (Sonnet 4.5 via Dev Story workflow)

### Debug Log References
- No debugging required - implementation followed story specifications exactly

### Completion Notes
**Implementation Summary:**
- Created Portfolio collection at `apps/backend/src/collections/Portfolio/index.ts`
- All 7 fields implemented: title, slug, url, image, description, websiteType, tags
- Access control configured: authenticated for create/update/delete, anyone for read
- Admin UI configured with useAsTitle: 'title' and defaultColumns: ['title', 'websiteType', 'updatedAt']
- Registered in payload.config.ts (import + collections array)
- Build completed successfully with no Portfolio-related TypeScript errors
- Unit test file created at `apps/backend/src/collections/Portfolio/__tests__/Portfolio.spec.ts`

**Note:** Task 4 (Admin UI testing) requires dev server to be running, which needs user verification.

### File List
- `apps/backend/src/collections/Portfolio/index.ts` (new)
- `apps/backend/src/collections/Portfolio/__tests__/Portfolio.spec.ts` (new)
- `apps/backend/src/payload.config.ts` (modified)

## Change Log

| Date | Action | Author |
|------|--------|--------|
| 2026-01-31 | Story created (Draft) | SM Agent (Bob) |
| 2026-01-31 | Implementation completed (Tasks 1-3, 5) | Dev Agent (Claude Opus 4.5) |
| 2026-01-31 | Task 4 Admin UI verified - all fields working | User verification |
| 2026-01-31 | Story marked as Review | Dev Agent (Claude Opus 4.5) |
