diff --git a/PRD-cms-functionality.md b/PRD-cms-functionality.md index 7a527ea..ba8daf4 100644 --- a/PRD-cms-functionality.md +++ b/PRD-cms-functionality.md @@ -417,27 +417,31 @@ const handleImageUpload = async (file) => { ### Dependencies -- [ ] `edra` (Edra editor) -- [ ] `@prisma/client` or `postgres` driver -- [ ] `exifr` for EXIF data extraction -- [ ] `sharp` or Cloudinary SDK for image processing -- [ ] Form validation library (Zod/Valibot) +- [x] `edra` (Edra editor) - Integrated and configured +- [x] `@prisma/client` - Set up with complete schema +- [x] `cloudinary` - SDK integrated for image processing and storage +- [x] Form validation with built-in validation +- [ ] `exifr` for EXIF data extraction (needed for photos system) ### Admin Interface -- [ ] Admin layout and navigation -- [ ] Content type switcher -- [ ] List views for each content type -- [ ] Form builders for Projects -- [ ] Edra wrapper for Posts -- [ ] Photo uploader with drag-and-drop -- [ ] Media library browser +- [x] Admin layout and navigation +- [x] Content type switcher (Dashboard, Projects, Universe, Media) +- [x] List views for projects and posts +- [x] Complete form system for Projects (metadata, branding, styling) +- [x] Edra wrapper for Posts with all post types +- [x] Comprehensive admin component library +- [ ] Photo uploader with drag-and-drop (for albums system) +- [ ] Media library browser modal ### APIs -- [ ] CRUD endpoints for all content types -- [ ] Media upload with progress -- [ ] Bulk operations (delete, publish) +- [x] CRUD endpoints for projects and posts +- [x] Media upload with progress +- [x] Bulk upload operations for media +- [x] Media usage tracking endpoints +- [ ] Albums CRUD endpoints (schema ready, UI needed) +- [ ] Bulk operations (delete, publish) for content - [ ] Search and filtering endpoints ### Public Display @@ -468,36 +472,50 @@ Based on requirements discussion: ## Current Status (December 2024) ### Completed + - ✅ Database setup with Prisma and PostgreSQL - ✅ Media management system with Cloudinary integration - ✅ Admin foundation (layout, navigation, auth, forms, data tables) - ✅ Edra rich text editor integration for case studies - ✅ Edra image uploads configured to use media API - ✅ Local development mode for media uploads (no Cloudinary usage) -- ✅ Project CRUD system with metadata fields -- ✅ Project list view in admin +- ✅ Project CRUD system with metadata fields and enhanced schema +- ✅ Project list view in admin with enhanced UI +- ✅ Project forms with branding (logo, colors) and styling +- ✅ Posts CRUD system with all post types (blog, microblog, link, photo, album) +- ✅ Posts list view and editor in admin +- ✅ Complete database schema matching PRD requirements +- ✅ Media API endpoints with upload, bulk upload, and usage tracking +- ✅ Component library for admin interface (buttons, inputs, modals, etc.) - ✅ Test page for verifying upload functionality ### In Progress -- 🔄 Posts System - Core functionality implemented + +- 🔄 Albums/Photos System - Schema implemented, UI components needed ### Next Steps -1. **Posts System Enhancements** - - Media library modal for photo/album post types - - Auto-save functionality - - Preview mode for posts - - Tags/categories management UI -2. **Projects System Enhancements** - - Technology tag selector - - Featured image picker with media library - - Gallery manager for project images - - Project ordering/display order +1. **Media Library System** (Critical dependency for other features) -4. **Photos & Albums System** - - Album creation and management - - Bulk photo upload interface + - Media library modal component + - Integration with existing media APIs + - Search and filter functionality within media browser + +2. **Albums & Photos Management Interface** + + - Album creation and management UI + - Bulk photo upload interface with progress - Photo ordering within albums + - Album cover selection + - EXIF data extraction and display + +3. **Enhanced Content Features** + + - Photo/album post selectors using media library + - Featured image picker for projects + - Technology tag selector for projects + - Auto-save functionality for all editors + - Gallery manager for project images ## Phased Implementation Plan @@ -534,9 +552,10 @@ Based on requirements discussion: - [x] Create admin layout component - [x] Build admin navigation with content type switcher - [x] Implement admin authentication (basic for now) -- [x] Create reusable form components +- [x] Create reusable form components (Button, Input, Modal, etc.) - [x] Build data table component for list views - [x] Add loading and error states +- [x] Create comprehensive admin UI component library - [ ] Create media library modal component ### Phase 4: Posts System (All Types) @@ -549,29 +568,34 @@ Based on requirements discussion: - [x] Create posts list view in admin - [x] Implement post CRUD APIs - [x] Post editor page with type-specific fields -- [ ] Create photo post selector -- [ ] Build album post selector +- [x] Complete posts database schema with all post types +- [x] Posts administration interface +- [ ] Create photo post selector (needs media library modal) +- [ ] Build album post selector (needs albums system) - [ ] Add auto-save functionality ### Phase 5: Projects System - [x] Build project form with all metadata fields -- [ ] Create technology tag selector -- [ ] Implement featured image picker -- [ ] Build gallery manager with drag-and-drop ordering +- [x] Enhanced schema with branding fields (logo, colors) +- [x] Project branding and styling forms - [x] Add optional Edra editor for case studies - [x] Create project CRUD APIs -- [x] Build project list view with thumbnails +- [x] Build project list view with enhanced UI +- [ ] Create technology tag selector +- [ ] Implement featured image picker (needs media library modal) +- [ ] Build gallery manager with drag-and-drop ordering - [ ] Add project ordering functionality ### Phase 6: Photos & Albums System +- [x] Complete database schema for albums and photos +- [x] Photo/album CRUD API endpoints (albums endpoint exists) - [ ] Create album management interface - [ ] Build bulk photo uploader with progress - [ ] Implement EXIF data extraction for photos - [ ] Implement drag-and-drop photo ordering - [ ] Add individual photo publishing UI -- [ ] Create photo/album CRUD APIs - [ ] Build photo metadata editor - [ ] Implement album cover selection - [ ] Add "show in universe" toggle for albums diff --git a/PRD-media-library.md b/PRD-media-library.md new file mode 100644 index 0000000..5a930d6 --- /dev/null +++ b/PRD-media-library.md @@ -0,0 +1,534 @@ +# Product Requirements Document: Media Library Modal System + +## Overview + +Implement a comprehensive Media Library modal system that provides a unified interface for browsing, selecting, and managing media across all admin forms. **The primary workflow is direct upload from computer within forms**, with the Media Library serving as a secondary browsing interface and management tool for previously uploaded content. + +## 📋 Updated Approach Summary + +**🎯 Primary Focus**: Direct upload components that allow users to drag-and-drop or browse files directly within project/post/album forms, with immediate preview and alt text capture. + +**🎯 Secondary Feature**: Media Library modal for selecting previously uploaded content when needed. + +**🎯 Key Addition**: Alt text storage and editing capabilities for accessibility compliance and SEO. + +## Goals + +### Primary Goals (Direct Upload Workflow) +- **Enable direct file upload within forms** where content will be used (projects, posts, albums) +- **Provide immediate upload and preview** without requiring navigation to separate media management +- **Store comprehensive metadata** including alt text for accessibility and SEO +- **Support drag-and-drop and click-to-browse** for intuitive file selection + +### Secondary Goals (Media Library Browser) +- Create a reusable media browser for **selecting previously uploaded content** +- Provide **media management interface** showing where files are referenced +- Enable **bulk operations** and **metadata editing** (especially alt text) +- Support **file organization** and **usage tracking** + +### Technical Goals +- Maintain consistent UX across all media interactions +- Support different file type filtering based on context +- Integrate seamlessly with existing admin components + +## Current State Analysis + +### ✅ What We Have +- Complete media API (`/api/media`, `/api/media/upload`, `/api/media/bulk-upload`) +- Media management page with grid/list views and search/filtering +- Modal base component (`Modal.svelte`) +- Complete admin UI component library (Button, Input, etc.) +- Media upload infrastructure with Cloudinary integration +- Pagination and search functionality + +### 🎯 What We Need + +#### High Priority (Direct Upload Focus) +- **Enhanced upload components** with immediate preview and metadata capture +- **Alt text input fields** for accessibility compliance +- **Direct upload integration** in form components (ImagePicker, GalleryManager) +- **Metadata management** during upload process + +#### Medium Priority (Media Library Browser) +- Reusable MediaLibraryModal component for browsing existing content +- Selection state management for previously uploaded files +- Usage tracking and reference management + +#### Database Updates Required +- Add `alt_text` field to Media table +- Add `usage_references` or similar tracking for where media is used + +## Workflow Priorities + +### 🥇 Primary Workflow: Direct Upload in Forms +This is the **main workflow** that users will use 90% of the time: + +1. **User creates content** (project, post, album) +2. **User uploads files directly** in the form where they'll be used +3. **Files are immediately processed** and previewed +4. **Alt text and metadata** are captured during upload +5. **Content is saved** with proper media references + +**Key Components**: +- `ImageUploader` - Direct drag-and-drop/click upload with preview +- `GalleryUploader` - Multiple file upload with immediate gallery preview +- `MediaMetadataForm` - Alt text and description capture during upload + +### 🥈 Secondary Workflow: Browse Existing Media +This workflow is for **reusing previously uploaded content**: + +1. **User needs to select existing media** (rare case) +2. **User clicks "Browse Library"** (secondary button) +3. **Media Library Modal opens** showing all uploaded files +4. **User selects from existing content** +5. **Media references are updated** + +**Key Components**: +- `MediaLibraryModal` - Browse and select existing media +- `MediaSelector` - Grid interface for selection +- `MediaManager` - Edit alt text and view usage + +## Technical Requirements + +### 1. Enhanced Upload Components (Primary) + +#### ImageUploader Component +**Purpose**: Direct image upload with immediate preview and metadata capture + +```typescript +interface ImageUploaderProps { + label: string + value?: Media | null + onUpload: (media: Media) => void + aspectRatio?: string + required?: boolean + error?: string + allowAltText?: boolean // Enable alt text input + maxFileSize?: number // MB limit +} +``` + +**Features**: +- Drag-and-drop upload zone with visual feedback +- Click to browse files from computer +- Immediate image preview with proper aspect ratio +- Alt text input field (when enabled) +- Upload progress indicator +- File validation with helpful error messages +- Replace/remove functionality + +#### GalleryUploader Component +**Purpose**: Multiple file upload with gallery preview and reordering + +```typescript +interface GalleryUploaderProps { + label: string + value?: Media[] + onUpload: (media: Media[]) => void + onReorder?: (media: Media[]) => void + maxItems?: number + allowAltText?: boolean + required?: boolean + error?: string +} +``` + +**Features**: +- Multiple file drag-and-drop +- Immediate gallery preview grid +- Individual alt text inputs for each image +- Drag-and-drop reordering +- Individual remove buttons +- Bulk upload progress + +### 2. MediaLibraryModal Component (Secondary) + +**Purpose**: Main modal component that wraps the media browser functionality + +**Props Interface**: +```typescript +interface MediaLibraryModalProps { + isOpen: boolean + mode: 'single' | 'multiple' + fileType?: 'image' | 'video' | 'all' + onSelect: (media: Media | Media[]) => void + onClose: () => void + selectedIds?: number[] // Pre-selected items + title?: string // Modal title + confirmText?: string // Confirm button text +} +``` + +**Features**: +- Modal overlay with proper focus management +- Header with title and close button +- Media browser grid with selection indicators +- Search and filter controls +- Upload area with drag-and-drop +- Footer with selection count and action buttons +- Responsive design (desktop and tablet) + +### 2. MediaSelector Component + +**Purpose**: The actual media browsing interface within the modal + +**Features**: +- Grid layout with thumbnail previews +- Individual item selection with visual feedback +- Keyboard navigation support +- Loading states and error handling +- "Select All" / "Clear Selection" bulk actions (for multiple mode) + +**Item Display**: +- Thumbnail image +- Filename (truncated) +- File size and dimensions +- Usage indicator (if used elsewhere) +- Selection checkbox/indicator + +### 3. MediaUploader Component + +**Purpose**: Handle file uploads within the modal + +**Features**: +- Drag-and-drop upload zone +- Click to browse files +- Upload progress indicators +- Error handling and validation +- Multiple file upload support +- Automatic refresh of media grid after upload + +**Validation**: +- File type restrictions based on context +- File size limits (10MB per file) +- Maximum number of files for bulk upload + +### 4. Form Integration Components + +#### MediaInput Component +**Purpose**: Generic input field that opens media library modal + +```typescript +interface MediaInputProps { + label: string + value?: Media | Media[] | null + mode: 'single' | 'multiple' + fileType?: 'image' | 'video' | 'all' + onSelect: (media: Media | Media[] | null) => void + placeholder?: string + required?: boolean + error?: string +} +``` + +**Display**: +- Label and optional required indicator +- Preview of selected media (thumbnail + filename) +- "Browse" button to open modal +- "Clear" button to remove selection +- Error state display + +#### ImagePicker Component +**Purpose**: Specialized single image selector with enhanced preview + +```typescript +interface ImagePickerProps { + label: string + value?: Media | null + onSelect: (media: Media | null) => void + aspectRatio?: string // e.g., "16:9", "1:1" + placeholder?: string + required?: boolean + error?: string +} +``` + +**Display**: +- Large preview area with placeholder +- Image preview with proper aspect ratio +- Overlay with "Change" and "Remove" buttons on hover +- Upload progress indicator + +#### GalleryManager Component +**Purpose**: Multiple image selection with drag-and-drop reordering + +```typescript +interface GalleryManagerProps { + label: string + value?: Media[] + onSelect: (media: Media[]) => void + onReorder?: (media: Media[]) => void + maxItems?: number + required?: boolean + error?: string +} +``` + +**Display**: +- Grid of selected images with reorder handles +- "Add Images" button to open modal +- Individual remove buttons on each image +- Drag-and-drop reordering with visual feedback + +## User Experience Flows + +### 🥇 Primary Flow: Direct Upload in Forms + +#### 1. Single Image Upload (Project Featured Image) +1. **User creates/edits project** and reaches featured image field +2. **User drags image file** directly onto ImageUploader component OR clicks to browse +3. **File is immediately uploaded** with progress indicator +4. **Image preview appears** with proper aspect ratio +5. **Alt text input field appears** below preview (if enabled) +6. **User enters alt text** for accessibility +7. **Form can be saved** with media reference and metadata + +#### 2. Multiple Image Upload (Project Gallery) +1. **User reaches gallery section** of project form +2. **User drags multiple files** onto GalleryUploader OR clicks to browse multiple +3. **Upload progress shown** for each file individually +4. **Gallery grid appears** with all uploaded images +5. **Alt text inputs available** for each image +6. **User can reorder** images with drag-and-drop +7. **User can remove** individual images with X button +8. **Form saves** with complete gallery and metadata + +#### 3. Media Management and Alt Text Editing +1. **User visits Media Library page** to manage uploaded content +2. **User clicks on any media item** to open details modal +3. **User can edit alt text** and other metadata +4. **User can see usage references** (which projects/posts use this media) +5. **Changes are saved** and reflected wherever media is used + +### 🥈 Secondary Flow: Browse Existing Media + +#### 1. Selecting Previously Uploaded Image +1. **User clicks "Browse Library"** button (secondary option in forms) +2. **MediaLibraryModal opens** showing all previously uploaded media +3. **User browses or searches** existing content +4. **User selects image** and confirms selection +5. **Modal closes** and form shows selected media with existing alt text + +#### 2. Managing Media Library +1. **User visits dedicated Media Library page** +2. **User can view all uploaded media** in grid/list format +3. **User can edit metadata** including alt text for any media +4. **User can see usage tracking** - which content references each media +5. **User can perform bulk operations** like deleting unused media + +## Design Specifications + +### Modal Layout +- **Width**: 1200px max, responsive on smaller screens +- **Height**: 80vh max with scroll +- **Grid**: 4-6 columns depending on screen size +- **Item Size**: 180px × 140px thumbnails + +### Visual States +- **Default**: Border with subtle background +- **Selected**: Blue border and checkmark overlay +- **Hover**: Slight scale and shadow effect +- **Loading**: Skeleton loader animation +- **Upload**: Progress overlay with percentage + +### Colors (Using Existing Variables) +- **Selection**: `$blue-60` for selected state +- **Hover**: `$grey-10` background +- **Upload Progress**: `$green-60` for success, `$red-60` for error + +## API Integration + +### Endpoints Used +- `GET /api/media` - Browse media with search/filter/pagination +- `POST /api/media/upload` - Single file upload +- `POST /api/media/bulk-upload` - Multiple file upload + +### Search and Filtering +- **Search**: By filename (case-insensitive) +- **Filter by Type**: image/*, video/*, all +- **Filter by Usage**: unused only, all +- **Sort**: Most recent first + +### Pagination +- 24 items per page +- Infinite scroll or traditional pagination +- Loading states during page changes + +## Implementation Plan + +### Phase 1: Database Schema Updates (Required First) +1. **Add Alt Text Support** + ```sql + ALTER TABLE media ADD COLUMN alt_text TEXT; + ALTER TABLE media ADD COLUMN description TEXT; + ``` + +2. **Add Usage Tracking (Optional)** + ```sql + -- Track where media is referenced + CREATE TABLE media_usage ( + id SERIAL PRIMARY KEY, + media_id INTEGER REFERENCES media(id), + content_type VARCHAR(50), -- 'project', 'post', 'album' + content_id INTEGER, + field_name VARCHAR(100), -- 'featured_image', 'gallery', etc. + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + ``` + +### Phase 2: Direct Upload Components (High Priority) +1. **ImageUploader Component** + - Drag-and-drop upload zone with visual feedback + - Immediate upload and preview functionality + - Alt text input integration + - Replace existing ImagePicker with upload-first approach + +2. **GalleryUploader Component** + - Multiple file drag-and-drop + - Individual alt text inputs per image + - Drag-and-drop reordering + - Remove individual images functionality + +3. **Upload API Enhancement** + - Accept alt text in upload request + - Return complete media object with metadata + - Handle batch uploads with individual alt text + +### Phase 3: Form Integration (High Priority) +1. **Project Forms Enhancement** + - Replace logo field with ImageUploader + - Add featured image with ImageUploader + - Implement gallery section with GalleryUploader + - Add secondary "Browse Library" buttons + +2. **Post Forms Enhancement** + - Photo post type with GalleryUploader + - Album creation with GalleryUploader + - Featured image selection for text posts + +### Phase 4: Media Library Management (Medium Priority) +1. **Enhanced Media Library Page** + - Alt text editing for existing media + - Usage tracking display (shows where media is used) + - Bulk alt text editing + - Search and filter by alt text + +2. **MediaLibraryModal for Selection** + - Browse existing media interface + - Single and multiple selection modes + - Integration as secondary option in forms + +### Phase 5: Polish and Advanced Features (Low Priority) +1. **Advanced Upload Features** + - Image resizing/optimization options + - Automatic alt text suggestions (AI integration) + - Bulk upload with CSV metadata import + +2. **Usage Analytics** + - Dashboard showing media usage statistics + - Unused media cleanup tools + - Duplicate detection and management + +## Success Criteria + +### Functional Requirements + +#### Primary Workflow (Direct Upload) +- [ ] **Drag-and-drop upload works** in all form components +- [ ] **Click-to-browse file selection** works reliably +- [ ] **Immediate upload and preview** happens without page navigation +- [ ] **Alt text input appears** and saves with uploaded media +- [ ] **Upload progress** is clearly indicated with percentage +- [ ] **Error handling** provides helpful feedback for failed uploads +- [ ] **Multiple file upload** works with individual progress tracking +- [ ] **Gallery reordering** works with drag-and-drop after upload + +#### Secondary Workflow (Media Library) +- [ ] **Media Library Modal** opens and closes properly with smooth animations +- [ ] **Single and multiple selection** modes work correctly +- [ ] **Search and filtering** return accurate results +- [ ] **Usage tracking** shows where media is referenced +- [ ] **Alt text editing** works in Media Library management +- [ ] **All components are keyboard accessible** + +### Performance Requirements +- [ ] Modal opens in under 200ms +- [ ] Media grid loads in under 1 second +- [ ] Search results appear in under 500ms +- [ ] Upload progress updates in real-time +- [ ] No memory leaks when opening/closing modal multiple times + +### UX Requirements +- [ ] Interface is intuitive without instruction +- [ ] Visual feedback is clear for all interactions +- [ ] Error messages are helpful and actionable +- [ ] Mobile/tablet interface is fully functional +- [ ] Loading states prevent user confusion + +## Technical Considerations + +### State Management +- Use Svelte runes for reactive state +- Maintain selection state during modal lifecycle +- Handle API loading and error states properly + +### Accessibility +- Proper ARIA labels and roles +- Keyboard navigation support +- Focus management when modal opens/closes +- Screen reader announcements for state changes + +### Performance +- Lazy load thumbnails as they come into view +- Debounce search input to prevent excessive API calls +- Efficient reordering without full re-renders +- Memory cleanup when modal is closed + +### Error Handling +- Network failure recovery +- Upload failure feedback +- File validation error messages +- Graceful degradation for missing thumbnails + +## Future Enhancements + +### Nice-to-Have Features +- **Bulk Operations**: Delete multiple files, bulk tag editing +- **Advanced Search**: Search by tags, date range, file size +- **Preview Mode**: Full-size preview with navigation +- **Folder Organization**: Create folders/categories for organization +- **Smart Suggestions**: Recently used, similar images +- **Crop Tool**: Basic cropping interface within modal +- **Alt Text Editor**: Quick alt text editing for accessibility + +### Integration Opportunities +- **CDN Optimization**: Automatic image optimization settings +- **AI Tagging**: Automatic tag generation for uploaded images +- **Duplicate Detection**: Warn about similar/duplicate uploads +- **Usage Analytics**: Track which media is used most frequently + +## Development Checklist + +### Core Components +- [ ] MediaLibraryModal base structure +- [ ] MediaSelector with grid layout +- [ ] MediaUploader with drag-and-drop +- [ ] Search and filter interface +- [ ] Pagination implementation + +### Form Integration +- [ ] MediaInput generic component +- [ ] ImagePicker specialized component +- [ ] GalleryManager with reordering +- [ ] Integration with existing project forms +- [ ] Integration with post forms + +### Polish and Testing +- [ ] Responsive design implementation +- [ ] Accessibility testing and fixes +- [ ] Performance optimization +- [ ] Error state handling +- [ ] Cross-browser testing +- [ ] Mobile device testing + +This Media Library system will serve as the foundation for all media-related functionality in the CMS, enabling rich content creation across projects, posts, and albums. \ No newline at end of file diff --git a/PRD-storybook-integration.md b/PRD-storybook-integration.md new file mode 100644 index 0000000..cbb3082 --- /dev/null +++ b/PRD-storybook-integration.md @@ -0,0 +1,397 @@ +# Product Requirements Document: Storybook Integration + +## Overview + +Implement Storybook as our component development and documentation platform to improve development workflow, component testing, and design system consistency across the jedmund-svelte project. + +## Goals + +- **Isolated Component Development**: Build and test components in isolation from business logic +- **Visual Documentation**: Create a living style guide for all UI components +- **Design System Consistency**: Ensure consistent component behavior across different states +- **Developer Experience**: Improve development workflow with hot reloading and component playground +- **Quality Assurance**: Test component edge cases and various prop combinations +- **Team Collaboration**: Provide a central place for designers and developers to review components + +## Current State Analysis + +### ✅ What We Have +- Comprehensive admin UI component library (Button, Input, Modal, etc.) +- Media Library components (MediaLibraryModal, ImagePicker, GalleryManager, etc.) +- SCSS-based styling system with global variables +- SvelteKit project with Svelte 5 runes mode +- TypeScript configuration +- Vite build system + +### 🎯 What We Need +- Storybook installation and configuration +- Stories for existing components +- Visual regression testing setup +- Component documentation standards +- Integration with existing SCSS variables and themes + +## Technical Requirements + +### 1. Storybook Installation + +**Installation Method**: Manual setup (not template-based since we have an existing project) + +```bash +# Install Storybook CLI and initialize +npx storybook@latest init + +# Or manual installation for better control +npm install --save-dev @storybook/svelte-vite @storybook/addon-essentials +``` + +**Expected File Structure**: +``` +.storybook/ +├── main.js # Storybook configuration +├── preview.js # Global decorators and parameters +└── manager.js # Storybook UI customization + +src/ +├── stories/ # Component stories +│ ├── Button.stories.js +│ ├── Input.stories.js +│ └── ... +└── components/ # Existing components +``` + +### 2. Configuration Requirements + +#### Main Configuration (.storybook/main.js) +```javascript +export default { + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|svelte)'], + addons: [ + '@storybook/addon-essentials', // Controls, actions, viewport, etc. + '@storybook/addon-svelte-csf', // Svelte Component Story Format + '@storybook/addon-a11y', // Accessibility testing + '@storybook/addon-design-tokens', // Design system tokens + ], + framework: { + name: '@storybook/svelte-vite', + options: {} + }, + viteFinal: async (config) => { + // Integrate with existing Vite config + // Import SCSS variables and aliases + return mergeConfig(config, { + resolve: { + alias: { + '$lib': path.resolve('./src/lib'), + '$components': path.resolve('./src/lib/components'), + '$icons': path.resolve('./src/assets/icons'), + '$illos': path.resolve('./src/assets/illos'), + } + }, + css: { + preprocessorOptions: { + scss: { + additionalData: ` + @import './src/assets/styles/variables.scss'; + @import './src/assets/styles/fonts.scss'; + @import './src/assets/styles/themes.scss'; + @import './src/assets/styles/globals.scss'; + ` + } + } + } + }); + } +}; +``` + +#### Preview Configuration (.storybook/preview.js) +```javascript +import '../src/assets/styles/reset.css'; +import '../src/assets/styles/globals.scss'; + +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#333333' }, + { name: 'admin', value: '#f5f5f5' }, + ], + }, + viewport: { + viewports: { + mobile: { name: 'Mobile', styles: { width: '375px', height: '667px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1440px', height: '900px' } }, + }, + }, +}; +``` + +### 3. Component Story Standards + +#### Story File Format +Each component should have a corresponding `.stories.js` file following this structure: + +```javascript +// Button.stories.js +import Button from '../lib/components/admin/Button.svelte'; + +export default { + title: 'Admin/Button', + component: Button, + tags: ['autodocs'], + argTypes: { + variant: { + control: { type: 'select' }, + options: ['primary', 'secondary', 'ghost', 'danger'] + }, + size: { + control: { type: 'select' }, + options: ['small', 'medium', 'large'] + }, + disabled: { + control: 'boolean' + }, + onclick: { action: 'clicked' } + } +}; + +export const Primary = { + args: { + variant: 'primary', + children: 'Primary Button' + } +}; + +export const Secondary = { + args: { + variant: 'secondary', + children: 'Secondary Button' + } +}; + +export const AllVariants = { + render: () => ({ + Component: ButtonShowcase, + props: {} + }) +}; +``` + +#### Story Organization +``` +src/stories/ +├── admin/ # Admin interface components +│ ├── Button.stories.js +│ ├── Input.stories.js +│ ├── Modal.stories.js +│ └── forms/ # Form-specific components +│ ├── MediaInput.stories.js +│ ├── ImagePicker.stories.js +│ └── GalleryManager.stories.js +├── public/ # Public-facing components +│ ├── Header.stories.js +│ └── Footer.stories.js +└── examples/ # Complex examples and compositions + ├── AdminDashboard.stories.js + └── MediaLibraryFlow.stories.js +``` + +## Implementation Plan + +### Phase 1: Initial Setup (1-2 days) +1. **Install and Configure Storybook** + - Run `npx storybook@latest init` + - Configure Vite integration for SCSS and aliases + - Set up TypeScript support + - Configure preview with global styles + +2. **Test Basic Setup** + - Create simple Button story + - Verify SCSS variables work + - Test hot reloading + +### Phase 2: Core Component Stories (3-4 days) +1. **Basic UI Components** + - Button (all variants, states, sizes) + - Input (text, textarea, validation states) + - Modal (different sizes, content types) + - LoadingSpinner (different sizes) + +2. **Form Components** + - MediaInput (single/multiple modes) + - ImagePicker (different aspect ratios) + - GalleryManager (with/without items) + +3. **Complex Components** + - MediaLibraryModal (with mock data) + - DataTable (with sample data) + - AdminNavBar (active states) + +### Phase 3: Advanced Features (2-3 days) +1. **Mock Data Setup** + - Create mock Media objects + - Set up API mocking for components that need data + - Create realistic test scenarios + +2. **Accessibility Testing** + - Add @storybook/addon-a11y + - Test keyboard navigation + - Verify screen reader compatibility + +3. **Visual Regression Testing** + - Set up Chromatic (optional) + - Create baseline screenshots + - Configure CI integration + +### Phase 4: Documentation and Polish (1-2 days) +1. **Component Documentation** + - Add JSDoc comments to components + - Create usage examples + - Document props and events + +2. **Design System Documentation** + - Color palette showcase + - Typography scale + - Spacing system + - Icon library + +## Success Criteria + +### Functional Requirements +- [ ] Storybook runs successfully with `npm run storybook` +- [ ] All existing components have basic stories +- [ ] SCSS variables and global styles work correctly +- [ ] Components render properly in isolation +- [ ] Hot reloading works for both component and story changes +- [ ] TypeScript support is fully functional + +### Quality Requirements +- [ ] Stories cover all major component variants +- [ ] Interactive controls work for all props +- [ ] Actions are properly logged for events +- [ ] Accessibility addon reports no critical issues +- [ ] Components are responsive across viewport sizes + +### Developer Experience Requirements +- [ ] Story creation is straightforward and documented +- [ ] Mock data is easily accessible and realistic +- [ ] Component API is clearly documented +- [ ] Common patterns have reusable templates + +## Integration with Existing Workflow + +### Development Workflow +1. **Component Development**: Start new components in Storybook +2. **Testing**: Test all states and edge cases in stories +3. **Documentation**: Stories serve as living documentation +4. **Review**: Use Storybook for design/code reviews + +### Project Structure Integration +``` +package.json # Add storybook scripts +├── "storybook": "storybook dev -p 6006" +├── "build-storybook": "storybook build" + +.storybook/ # Storybook configuration +src/ +├── lib/components/ # Existing components (unchanged) +├── stories/ # New: component stories +└── assets/styles/ # Existing styles (used by Storybook) +``` + +### Scripts and Commands +```json +{ + "scripts": { + "dev": "vite dev", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", + "storybook:test": "test-storybook" + } +} +``` + +## Technical Considerations + +### SCSS Integration +- Import global variables in Storybook preview +- Ensure component styles render correctly +- Test responsive breakpoints + +### SvelteKit Compatibility +- Handle SvelteKit-specific imports (like `$app/stores`) +- Mock SvelteKit modules when needed +- Ensure aliases work in Storybook context + +### TypeScript Support +- Configure proper type checking +- Use TypeScript for story definitions where beneficial +- Ensure IntelliSense works for story arguments + +### Performance +- Optimize bundle size for faster story loading +- Use lazy loading for large story collections +- Configure appropriate caching + +## Future Enhancements + +### Advanced Testing +- **Visual Regression Testing**: Use Chromatic for automated visual testing +- **Interaction Testing**: Add @storybook/addon-interactions for user flow testing +- **Accessibility Automation**: Automated a11y testing in CI/CD + +### Design System Evolution +- **Design Tokens**: Implement design tokens addon +- **Figma Integration**: Connect with Figma designs +- **Component Status**: Track component implementation status + +### Collaboration Features +- **Published Storybook**: Deploy Storybook for team access +- **Design Review Process**: Use Storybook for design approvals +- **Documentation Site**: Evolve into full design system documentation + +## Risks and Mitigation + +### Technical Risks +- **Build Conflicts**: Vite configuration conflicts + - *Mitigation*: Careful configuration merging and testing +- **SCSS Import Issues**: Global styles not loading + - *Mitigation*: Test SCSS integration early in setup + +### Workflow Risks +- **Adoption Resistance**: Team not using Storybook + - *Mitigation*: Start with high-value components, show immediate benefits +- **Maintenance Overhead**: Stories become outdated + - *Mitigation*: Include story updates in component change process + +## Success Metrics + +### Development Efficiency +- Reduced time to develop new components +- Faster iteration on component designs +- Fewer bugs in component edge cases + +### Code Quality +- Better component API consistency +- Improved accessibility compliance +- More comprehensive component testing + +### Team Collaboration +- Faster design review cycles +- Better communication between design and development +- More consistent component usage across the application + +## Conclusion + +Implementing Storybook will significantly improve our component development workflow, provide better documentation, and create a foundation for a mature design system. The investment in setup and story creation will pay dividends in development speed, component quality, and team collaboration. + +The implementation should be done incrementally, starting with the most commonly used components and gradually expanding coverage. This approach minimizes risk while providing immediate value to the development process. \ No newline at end of file diff --git a/src/lib/components/admin/AlbumForm.svelte b/src/lib/components/admin/AlbumForm.svelte new file mode 100644 index 0000000..a47ecc4 --- /dev/null +++ b/src/lib/components/admin/AlbumForm.svelte @@ -0,0 +1,367 @@ + + + + + + + + + + + 📸 {mode === 'create' ? 'New Album' : 'Edit Album'} + + + {#if mode === 'create'} + + Cancel + + handleSave('draft')} disabled={!isValid || isSaving}> + {isSaving ? 'Saving...' : 'Save Draft'} + + handleSave('published')} disabled={!isValid || isSaving}> + {isSaving ? 'Publishing...' : 'Publish Album'} + + {:else} + + Cancel + + handleSave()} disabled={!isValid || isSaving}> + {isSaving ? 'Saving...' : 'Save Changes'} + + {/if} + + + + + {#if error} + + {error} + + {/if} + + + + + + + + + + + + + Description + + + + + + + + + + + + \ No newline at end of file diff --git a/src/lib/components/admin/EditorWithUpload.svelte b/src/lib/components/admin/EditorWithUpload.svelte index 2a95455..7b5e346 100644 --- a/src/lib/components/admin/EditorWithUpload.svelte +++ b/src/lib/components/admin/EditorWithUpload.svelte @@ -34,6 +34,10 @@ import { IFramePlaceholder } from '$lib/components/edra/extensions/iframe/IFramePlaceholder.js' import { IFrameExtended } from '$lib/components/edra/extensions/iframe/IFrameExtended.js' import IFrameExtendedComponent from '$lib/components/edra/headless/components/IFrameExtended.svelte' + import { GalleryPlaceholder } from '$lib/components/edra/extensions/gallery/GalleryPlaceholder.js' + import GalleryPlaceholderComponent from '$lib/components/edra/headless/components/GalleryPlaceholder.svelte' + import { GalleryExtended } from '$lib/components/edra/extensions/gallery/GalleryExtended.js' + import GalleryExtendedComponent from '$lib/components/edra/headless/components/GalleryExtended.svelte' // Import Edra styles import '$lib/components/edra/headless/style.css' @@ -296,11 +300,13 @@ }), AudioPlaceholder(AudioPlaceholderComponent), ImagePlaceholder(ImageUploadPlaceholder), // Use our custom component + GalleryPlaceholder(GalleryPlaceholderComponent), IFramePlaceholder(IFramePlaceholderComponent), IFrameExtended(IFrameExtendedComponent), VideoPlaceholder(VideoPlaceholderComponent), AudioExtended(AudioExtendedComponent), ImageExtended(ImageExtendedComponent), + GalleryExtended(GalleryExtendedComponent), VideoExtended(VideoExtendedComponent), ...(showSlashCommands ? [slashcommand(SlashCommandList)] : []) ], @@ -500,6 +506,46 @@ Image + { + editor?.chain().focus().insertGalleryPlaceholder().run() + showMediaDropdown = false + }} + > + + + + + + + Gallery + { diff --git a/src/lib/components/admin/ImageUploadPlaceholder.svelte b/src/lib/components/admin/ImageUploadPlaceholder.svelte index 3cf5fd1..6b1211b 100644 --- a/src/lib/components/admin/ImageUploadPlaceholder.svelte +++ b/src/lib/components/admin/ImageUploadPlaceholder.svelte @@ -1,32 +1,55 @@ @@ -130,38 +168,124 @@ style="display: none;" /> - - - - - {isDragging ? 'Drop image here' : 'Click to upload or drag & drop'} - - or paste from clipboard - + + {#if isUploading} + + + Uploading image... + + {:else} + + + Upload Image + + + + + Browse Library + + {/if} + + + + diff --git a/src/lib/components/admin/PhotoPostForm.svelte b/src/lib/components/admin/PhotoPostForm.svelte new file mode 100644 index 0000000..12367bb --- /dev/null +++ b/src/lib/components/admin/PhotoPostForm.svelte @@ -0,0 +1,355 @@ + + + + + + {mode === 'edit' ? 'Edit Photo Post' : 'New Photo Post'} + Share a photo with a caption and description + + + + {#if !isSaving} + goto('/admin/posts')}> + Cancel + + + Save Draft + + + {isSaving ? 'Publishing...' : 'Publish'} + + {/if} + + + + + {#if error} + {error} + {/if} + + + + + + + + + + + + + + + Caption & Description + Add a caption or tell the story behind this photo + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/lib/components/admin/UniverseComposer.svelte b/src/lib/components/admin/UniverseComposer.svelte index ea692d6..e2259e1 100644 --- a/src/lib/components/admin/UniverseComposer.svelte +++ b/src/lib/components/admin/UniverseComposer.svelte @@ -7,7 +7,11 @@ import FormFieldWrapper from './FormFieldWrapper.svelte' import Button from './Button.svelte' import Input from './Input.svelte' + import MediaLibraryModal from './MediaLibraryModal.svelte' + import MediaDetailsModal from './MediaDetailsModal.svelte' + import SmartImage from '../SmartImage.svelte' import type { JSONContent } from '@tiptap/core' + import type { Media } from '@prisma/client' export let isOpen = false export let initialMode: 'modal' | 'page' = 'modal' @@ -37,6 +41,15 @@ let essayTags = '' let essayTab = 0 + // Photo attachment state + let attachedPhotos: Media[] = [] + let isMediaLibraryOpen = false + let fileInput: HTMLInputElement + + // Media details modal state + let selectedMedia: Media | null = null + let isMediaDetailsOpen = false + const CHARACTER_LIMIT = 280 const dispatch = createEventDispatcher() @@ -50,7 +63,7 @@ } function hasContent(): boolean { - return characterCount > 0 || linkUrl.length > 0 + return characterCount > 0 || linkUrl.length > 0 || attachedPhotos.length > 0 } function resetComposer() { @@ -65,6 +78,7 @@ linkDescription = '' showLinkFields = false characterCount = 0 + attachedPhotos = [] if (editorInstance) { editorInstance.clear() } @@ -90,6 +104,83 @@ showLinkFields = !showLinkFields } + function handlePhotoUpload() { + fileInput.click() + } + + async function handleFileUpload(event: Event) { + const input = event.target as HTMLInputElement + const files = input.files + if (!files || files.length === 0) return + + for (const file of files) { + if (!file.type.startsWith('image/')) continue + + const formData = new FormData() + formData.append('file', file) + formData.append('type', 'image') + + // Add auth header if needed + const auth = localStorage.getItem('admin_auth') + const headers: Record = {} + if (auth) { + headers.Authorization = `Basic ${auth}` + } + + try { + const response = await fetch('/api/media/upload', { + method: 'POST', + headers, + body: formData + }) + + if (response.ok) { + const media = await response.json() + attachedPhotos = [...attachedPhotos, media] + } else { + console.error('Failed to upload image:', response.status) + } + } catch (error) { + console.error('Error uploading image:', error) + } + } + + // Clear the input + input.value = '' + } + + function handleMediaSelect(media: Media | Media[]) { + const mediaArray = Array.isArray(media) ? media : [media] + const currentIds = attachedPhotos.map(p => p.id) + const newMedia = mediaArray.filter(m => !currentIds.includes(m.id)) + attachedPhotos = [...attachedPhotos, ...newMedia] + } + + function handleMediaLibraryClose() { + isMediaLibraryOpen = false + } + + function removePhoto(photoId: number) { + attachedPhotos = attachedPhotos.filter(p => p.id !== photoId) + } + + function handlePhotoClick(photo: Media) { + selectedMedia = photo + isMediaDetailsOpen = true + } + + function handleMediaDetailsClose() { + isMediaDetailsOpen = false + selectedMedia = null + } + + function handleMediaUpdate(updatedMedia: Media) { + // Update the photo in the attachedPhotos array + attachedPhotos = attachedPhotos.map(photo => + photo.id === updatedMedia.id ? updatedMedia : photo + ) + } + function getTextFromContent(json: JSONContent): number { if (!json || !json.content) return 0 @@ -114,7 +205,8 @@ let postData: any = { content, - status: 'published' + status: 'published', + attachedPhotos: attachedPhotos.map(photo => photo.id) } if (postType === 'essay') { @@ -137,7 +229,7 @@ } else { postData = { ...postData, - type: 'microblog' + type: attachedPhotos.length > 0 ? 'photo' : 'microblog' } } @@ -165,7 +257,7 @@ $: isOverLimit = characterCount > CHARACTER_LIMIT $: canSave = - (postType === 'post' && characterCount > 0 && !isOverLimit) || + (postType === 'post' && (characterCount > 0 || attachedPhotos.length > 0) && !isOverLimit) || (showLinkFields && linkUrl.length > 0) || (postType === 'essay' && essayTitle.length > 0 && content) @@ -238,6 +330,42 @@ {/if} + + {#if attachedPhotos.length > 0} + + {#each attachedPhotos as photo} + + handlePhotoClick(photo)} + title="View media details" + > + + + removePhoto(photo.id)} + title="Remove photo" + > + + + + + + {/each} + + {/if} +
Share a photo with a caption and description
Add a caption or tell the story behind this photo