diff --git a/USERFLOWS.md b/USERFLOWS.md new file mode 100644 index 0000000..5db4dde --- /dev/null +++ b/USERFLOWS.md @@ -0,0 +1,54 @@ +# User Flows I want the solution to support + +## Flow #1 - Presentation Management +**Adding, removing, and editing existing presentations** + +### Create New Presentation +- [x] User navigates to create new presentation +- [x] User enters presentation details (title, description) +- [ ] User selects aspect ratio (16:9, 4:3, 16:10) for presentation +- [x] User selects a theme from available options +- [x] User creates presentation and is taken to editor + +### View All Presentations +- [x] User can view list of all saved presentations +- [x] User can see presentation metadata (name, description, theme, slide count) +- [x] User can access presentations from navigation + +### Edit Existing Presentation +- [x] User can open existing presentation for editing +- [x] User can navigate between slides in editor +- [x] User can access presentation settings and metadata + +### Delete Presentation +- [x] User can delete presentation from list view +- [x] User gets confirmation dialog before deletion +- [x] Presentation is removed from storage + +## Flow #2 - Slide Management +**Adding, removing, and editing individual slides within presentations** + +### Add New Slide +- [x] User clicks "Add Slide" from presentation editor +- [x] User can select layout for new slide (with themed previews) +- [x] User can add content to slide slots (text, images) +- [x] User can add presentation notes to slide +- [ ] User can see miniature preview of slide live while editing +- [ ] User can save slide (auto-saves presentation) + +### Edit Existing Slide +- [ ] User can click on existing slide to edit +- [ ] User can modify slide content in all slots +- [ ] User can change slide layout +- [ ] User can edit presentation notes +- [ ] Changes auto-save to presentation + +### Remove Slide +- [ ] User can delete slides from presentation +- [ ] User gets confirmation before slide deletion +- [ ] Slide order adjusts automatically + +### Preview Slides +- [ ] User can preview individual slides +- [ ] User can view slides in presentation mode +- [ ] User can navigate between slides in preview diff --git a/public/themes-manifest.json b/public/themes-manifest.json index ce5c747..543a7c4 100644 --- a/public/themes-manifest.json +++ b/public/themes-manifest.json @@ -11,5 +11,5 @@ "hasMasterSlide": true } }, - "generated": "2025-08-20T18:50:35.588Z" + "generated": "2025-08-20T19:28:24.594Z" } \ No newline at end of file diff --git a/public/themes/default/style.css b/public/themes/default/style.css index 8e19167..40f9f06 100644 --- a/public/themes/default/style.css +++ b/public/themes/default/style.css @@ -127,7 +127,7 @@ font-size: clamp(2rem, 5vw, 4rem); margin-bottom: 2rem; width: 80%; - + color: var(--theme-primary); } diff --git a/src/App.css b/src/App.css index e38ce7f..a092c76 100644 --- a/src/App.css +++ b/src/App.css @@ -1,3 +1,6 @@ +/* Import aspect ratio system for theme engine */ +@import './styles/aspectRatios.css'; + /* App Layout */ .app-root { width: 100%; @@ -9,14 +12,75 @@ } .app-header { - padding: 2rem; - text-align: center; border-bottom: 1px solid #e5e7eb; background: #ffffff; width: 100%; box-sizing: border-box; } +.app-nav { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + border-bottom: 1px solid #f1f5f9; +} + +.app-logo { + font-size: 1.25rem; + font-weight: 700; + color: #1e293b; + text-decoration: none; + transition: color 0.2s ease; +} + +.app-logo:hover { + color: #3b82f6; +} + +.nav-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.nav-button { + padding: 0.5rem 1rem; + border-radius: 0.375rem; + font-weight: 500; + font-size: 0.875rem; + text-decoration: none; + transition: all 0.2s ease; + border: none; + cursor: pointer; +} + +.nav-button.primary { + background: #3b82f6; + color: white; +} + +.nav-button.primary:hover { + background: #2563eb; +} + +.nav-link { + color: #64748b; + text-decoration: none; + font-weight: 500; + font-size: 0.875rem; + transition: color 0.2s ease; +} + +.nav-link:hover { + color: #334155; +} + +.page-title-section { + padding: 2rem; + text-align: center; +} + .app-header h1 { margin: 0; color: #1f2937; @@ -80,7 +144,18 @@ gap: 0.75rem; } - .app-header { + .app-nav { + padding: 1rem; + flex-direction: column; + gap: 1rem; + align-items: stretch; + } + + .nav-actions { + justify-content: center; + } + + .page-title-section { padding: 1.5rem 1rem; } diff --git a/src/App.tsx b/src/App.tsx index 5ac715b..2c68de4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' import { ThemeBrowser, ThemeDetailPage, LayoutDetailPage, LayoutPreviewPage } from './components/themes' +import { NewPresentationPage, PresentationViewer, PresentationEditor, SlideEditor, PresentationsList } from './components/presentations' import { AppHeader } from './components/AppHeader' import { Welcome } from './components/Welcome' import './App.css' @@ -13,6 +14,11 @@ function App() {
} /> + } /> + } /> + } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index 47d4b42..67620a0 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -8,6 +8,34 @@ export const AppHeader: React.FC = () => { if (location.pathname === '/') { return 'Welcome to Slideshare'; } + if (location.pathname === '/presentations') { + return 'My Presentations'; + } + if (location.pathname === '/presentations/new') { + return 'Create New Presentation'; + } + if (location.pathname.includes('/slide/') && location.pathname.includes('/edit')) { + const segments = location.pathname.split('/'); + const slideId = segments[4]; + if (slideId === 'new') { + return 'Add New Slide'; + } else { + return 'Edit Slide'; + } + } + if (location.pathname.includes('/presentations/') && location.pathname.includes('/slides/')) { + const segments = location.pathname.split('/'); + if (segments.length === 6 && segments[1] === 'presentations') { + const mode = segments[3]; // 'edit' or 'view' + const slideNumber = segments[5]; + if (mode === 'edit') { + return `Editing Slide ${slideNumber}`; + } else if (mode === 'view') { + return `Viewing Slide ${slideNumber}`; + } + return `Presentation Slide ${slideNumber}`; + } + } if (location.pathname === '/themes') { return 'Theme Browser'; } @@ -27,6 +55,33 @@ export const AppHeader: React.FC = () => { if (location.pathname === '/') { return 'Create beautiful presentations with customizable themes'; } + if (location.pathname === '/presentations') { + return 'View and manage all your presentations'; + } + if (location.pathname === '/presentations/new') { + return 'Select a theme and enter details for your new presentation'; + } + if (location.pathname.includes('/slide/') && location.pathname.includes('/edit')) { + const segments = location.pathname.split('/'); + const slideId = segments[4]; + if (slideId === 'new') { + return 'Choose a layout and add content for your new slide'; + } else { + return 'Edit slide content and layout'; + } + } + if (location.pathname.includes('/presentations/') && location.pathname.includes('/slides/')) { + const segments = location.pathname.split('/'); + if (segments.length === 6 && segments[1] === 'presentations') { + const mode = segments[3]; + if (mode === 'edit') { + return 'Edit slide content, add notes, and manage your presentation'; + } else if (mode === 'view') { + return 'View your presentation slides in read-only mode'; + } + } + return 'View and manage your presentation slides'; + } if (location.pathname === '/themes') { return 'Browse and select themes for your presentations'; } @@ -46,11 +101,19 @@ export const AppHeader: React.FC = () => {

{getPageTitle()}

diff --git a/src/components/Welcome.tsx b/src/components/Welcome.tsx index 6f41c35..ec6b3bc 100644 --- a/src/components/Welcome.tsx +++ b/src/components/Welcome.tsx @@ -10,7 +10,10 @@ export const Welcome: React.FC = () => { Create beautiful presentations with customizable themes and layouts

- + + Create Presentation + + Browse Themes
@@ -114,8 +117,8 @@ export const Welcome: React.FC = () => {

Start building your presentation with our theme collection

- - Explore Themes + + Create Your First Presentation
diff --git a/src/components/presentations/NewPresentationPage.css b/src/components/presentations/NewPresentationPage.css new file mode 100644 index 0000000..b659458 --- /dev/null +++ b/src/components/presentations/NewPresentationPage.css @@ -0,0 +1,382 @@ +.new-presentation-page { + min-height: 100vh; + background: #f8fafc; +} + +.page-header { + background: white; + border-bottom: 1px solid #e2e8f0; + padding: 1.5rem 2rem; + display: flex; + align-items: center; + gap: 1.5rem; +} + +.back-button { + background: none; + border: none; + color: #64748b; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + padding: 0.5rem 0.75rem; + border-radius: 0.375rem; + transition: all 0.2s ease; +} + +.back-button:hover { + background: #f1f5f9; + color: #334155; +} + +.header-content h1 { + margin: 0; + font-size: 1.875rem; + font-weight: 700; + color: #1e293b; +} + +.header-content p { + margin: 0.25rem 0 0 0; + color: #64748b; + font-size: 0.875rem; +} + +.page-content { + padding: 2rem; + max-width: 1200px; + margin: 0 auto; +} + +.creation-form { + display: flex; + flex-direction: column; + gap: 3rem; +} + +/* Presentation Details Section */ +.presentation-details { + background: white; + border-radius: 0.75rem; + padding: 2rem; + border: 1px solid #e2e8f0; +} + +.presentation-details h2 { + margin: 0 0 1.5rem 0; + font-size: 1.25rem; + font-weight: 600; + color: #1e293b; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-group:last-child { + margin-bottom: 0; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: #374151; + font-size: 0.875rem; +} + +.form-input, +.form-textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.5rem; + font-size: 0.875rem; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + background: white; + color: #374151; + box-sizing: border-box; +} + +.form-input:focus, +.form-textarea:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.form-textarea { + resize: vertical; + min-height: 80px; + font-family: inherit; +} + +/* Theme Selection Section */ +.theme-selection { + background: white; + border-radius: 0.75rem; + padding: 2rem; + border: 1px solid #e2e8f0; +} + +.theme-selection h2 { + margin: 0 0 0.5rem 0; + font-size: 1.25rem; + font-weight: 600; + color: #1e293b; +} + +.section-description { + margin: 0 0 2rem 0; + color: #64748b; + font-size: 0.875rem; +} + +.no-themes { + text-align: center; + padding: 3rem; + color: #64748b; +} + +/* Creation Actions Section */ +.creation-actions { + background: white; + border-radius: 0.75rem; + padding: 2rem; + border: 1px solid #e2e8f0; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; + flex-wrap: wrap; +} + +.selected-theme-info { + flex: 1; + min-width: 0; +} + +.theme-preview-info h3 { + margin: 0 0 0.5rem 0; + font-size: 1.125rem; + font-weight: 600; + color: #1e293b; +} + +.theme-preview-info p { + margin: 0 0 0.75rem 0; + color: #64748b; + font-size: 0.875rem; +} + +.theme-stats { + display: flex; + gap: 1rem; + font-size: 0.75rem; + color: #6b7280; +} + +.theme-stats span { + background: #f1f5f9; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; +} + +.creation-error { + margin-top: 1rem; + padding: 0.75rem; + background: #fef2f2; + border: 1px solid #fecaca; + border-radius: 0.375rem; + color: #dc2626; +} + +.creation-error p { + margin: 0; + font-size: 0.875rem; + font-weight: 500; +} + +/* Aspect Ratio Selection */ +.aspect-ratio-selection { + margin-bottom: 2rem; +} + +.aspect-ratio-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; +} + +.aspect-ratio-card { + border: 2px solid #e2e8f0; + border-radius: 0.75rem; + padding: 1.5rem; + cursor: pointer; + transition: all 0.2s ease; + background: white; +} + +.aspect-ratio-card:hover { + border-color: #cbd5e1; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +.aspect-ratio-card.selected { + border-color: #3b82f6; + box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.2); + background: #eff6ff; +} + +.aspect-ratio-preview { + display: flex; + align-items: center; + justify-content: center; + height: 80px; + margin-bottom: 1rem; + background: #f8fafc; + border-radius: 0.5rem; + border: 1px dashed #cbd5e1; +} + +.preview-box { + background: #3b82f6; + border-radius: 0.25rem; +} + +.preview-box.aspect-16-9 { + width: 64px; + height: 36px; +} + +.preview-box.aspect-4-3 { + width: 60px; + height: 45px; +} + +.preview-box.aspect-16-10 { + width: 64px; + height: 40px; +} + +.aspect-ratio-info h3 { + margin: 0 0 0.5rem 0; + font-size: 1rem; + font-weight: 600; + color: #1e293b; +} + +.ratio-description { + margin: 0 0 0.75rem 0; + font-size: 0.875rem; + color: #64748b; + line-height: 1.4; +} + +.ratio-dimensions { + font-size: 0.75rem; + color: #6b7280; + background: #f1f5f9; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + display: inline-block; +} + +.action-buttons { + display: flex; + gap: 1rem; + flex-shrink: 0; +} + +.button { + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + font-weight: 500; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s ease; + border: none; + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.button.primary { + background: #3b82f6; + color: white; +} + +.button.primary:hover:not(:disabled) { + background: #2563eb; +} + +.button.primary:disabled { + background: #9ca3af; + cursor: not-allowed; +} + +.button.secondary { + background: #f8fafc; + color: #64748b; + border: 1px solid #e2e8f0; +} + +.button.secondary:hover { + background: #f1f5f9; + color: #475569; +} + +/* Loading and Error States */ +.loading-content, +.error-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 50vh; + text-align: center; + gap: 1rem; +} + +.loading-spinner { + color: #64748b; + font-size: 1.125rem; +} + +.error-content h2 { + color: #dc2626; + margin: 0; +} + +.error-content p { + color: #64748b; + margin: 0.5rem 0 1.5rem 0; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .page-header { + padding: 1rem; + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .page-content { + padding: 1rem; + } + + .creation-actions { + flex-direction: column; + align-items: stretch; + } + + .action-buttons { + justify-content: stretch; + } + + .button { + flex: 1; + } +} \ No newline at end of file diff --git a/src/components/presentations/NewPresentationPage.tsx b/src/components/presentations/NewPresentationPage.tsx new file mode 100644 index 0000000..7879275 --- /dev/null +++ b/src/components/presentations/NewPresentationPage.tsx @@ -0,0 +1,237 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import type { Theme } from '../../types/theme'; +import type { AspectRatio } from '../../types/presentation'; +import { ASPECT_RATIOS } from '../../types/presentation'; +import { getThemes } from '../../themes'; +import { createPresentation } from '../../utils/presentationStorage'; +import { ThemeSelector } from './ThemeSelector'; +import './NewPresentationPage.css'; + +export const NewPresentationPage: React.FC = () => { + const navigate = useNavigate(); + const [themes, setThemes] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedTheme, setSelectedTheme] = useState(null); + const [selectedAspectRatio, setSelectedAspectRatio] = useState('16:9'); + const [presentationTitle, setPresentationTitle] = useState(''); + const [presentationDescription, setPresentationDescription] = useState(''); + const [creating, setCreating] = useState(false); + + useEffect(() => { + const loadThemes = async () => { + try { + setLoading(true); + const discoveredThemes = await getThemes(); + setThemes(discoveredThemes); + + // Auto-select first theme if available + if (discoveredThemes.length > 0) { + setSelectedTheme(discoveredThemes[0]); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load themes'); + } finally { + setLoading(false); + } + }; + + loadThemes(); + }, []); + + const handleCreatePresentation = async () => { + if (!selectedTheme) { + alert('Please select a theme for your presentation'); + return; + } + + if (!presentationTitle.trim()) { + alert('Please enter a title for your presentation'); + return; + } + + try { + setCreating(true); + setError(null); + + const presentation = await createPresentation({ + name: presentationTitle.trim(), + description: presentationDescription.trim(), + theme: selectedTheme.id, + aspectRatio: selectedAspectRatio + }); + + console.log('Presentation created successfully:', presentation); + + // Navigate to the new presentation editor (slide 1) + navigate(`/presentations/${presentation.metadata.id}/edit/slides/1`); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to create presentation'); + console.error('Error creating presentation:', err); + } finally { + setCreating(false); + } + }; + + if (loading) { + return ( +
+
+
Loading themes...
+
+
+ ); + } + + if (error) { + return ( +
+
+

Error Loading Themes

+

{error}

+ +
+
+ ); + } + + return ( +
+
+ +
+

Create New Presentation

+

Choose a theme and enter details for your new presentation

+
+
+ +
+
+
+

Presentation Details

+
+ + setPresentationTitle(e.target.value)} + placeholder="Enter presentation title" + className="form-input" + required + /> +
+ +
+ +