Fix TypeScript build errors and improve navigation consistency

- Replace hardcoded navigation routes with history-based navigation (navigate(-1))
- Convert presentation deletion to use shared ConfirmDialog/AlertDialog components
- Consolidate button components throughout the application
- Fix TypeScript unused import errors from button component consolidation
- Convert enums to const assertions to fix erasableSyntaxOnly compatibility
- Hide theme browsing features with "coming soon" comments
- Improve AppHeader routing with regex patterns instead of manual parsing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-08-21 12:34:11 -05:00
parent 5739bdf4a9
commit 3d0f6dd25e
11 changed files with 206 additions and 159 deletions

View File

@ -12,5 +12,5 @@
"hasMasterSlide": true
}
},
"generated": "2025-08-21T16:40:45.572Z"
"generated": "2025-08-21T17:31:29.998Z"
}

0
public/themes/CLAUDE.md Normal file
View File

View File

@ -1,99 +1,63 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Link, useLocation, useParams } from 'react-router-dom';
export const AppHeader: React.FC = () => {
const location = useLocation();
const params = useParams();
const getPageTitle = () => {
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';
}
if (location.pathname.includes('/themes/')) {
const segments = location.pathname.split('/');
if (segments.length === 3) {
return `Theme: ${segments[2]}`;
}
if (segments.length === 5 && segments[3] === 'layouts') {
return `Layout: ${segments[4]}`;
const { pathname } = location;
// Route patterns with titles (cleaner than manual parsing)
const routes = [
{ pattern: /^\/$/, title: 'Welcome to Slideshare' },
{ pattern: /^\/presentations$/, title: 'My Presentations' },
{ pattern: /^\/presentations\/new$/, title: 'Create New Presentation' },
{ pattern: /^\/presentations\/[^/]+\/edit\/slides\/(\d+)$/, title: () => `Editing Slide ${params.slideNumber}` },
{ pattern: /^\/presentations\/[^/]+\/view\/slides\/(\d+)$/, title: () => `Viewing Slide ${params.slideNumber}` },
{ pattern: /^\/presentations\/[^/]+\/present\/(\d+)$/, title: () => `Presenting Slide ${params.slideNumber}` },
{ pattern: /^\/presentations\/[^/]+\/slide\/new\/edit$/, title: 'Add New Slide' },
{ pattern: /^\/presentations\/[^/]+\/slide\/[^/]+\/edit$/, title: 'Edit Slide' },
{ pattern: /^\/themes$/, title: 'Theme Browser' },
{ pattern: /^\/themes\/([^/]+)$/, title: () => `Theme: ${params.themeId}` },
{ pattern: /^\/themes\/[^/]+\/layouts\/([^/]+)$/, title: () => `Layout: ${params.layoutId}` },
{ pattern: /^\/themes\/[^/]+\/layouts\/[^/]+\/preview$/, title: () => `Preview Layout` },
];
for (const route of routes) {
if (route.pattern.test(pathname)) {
return typeof route.title === 'function' ? route.title() : route.title;
}
}
return 'Slideshare';
};
const getPageDescription = () => {
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';
}
if (location.pathname.includes('/themes/')) {
const segments = location.pathname.split('/');
if (segments.length === 3) {
return 'View theme details, layouts, and color palette';
}
if (segments.length === 5 && segments[3] === 'layouts') {
return 'View layout template, slots, and configuration';
const { pathname } = location;
// Route patterns with descriptions
const routes = [
{ pattern: /^\/$/, description: 'Create beautiful presentations with customizable themes' },
{ pattern: /^\/presentations$/, description: 'View and manage all your presentations' },
{ pattern: /^\/presentations\/new$/, description: 'Enter details for your new presentation' },
{ pattern: /^\/presentations\/[^/]+\/edit\/slides\/(\d+)$/, description: 'Edit slide content, add notes, and manage your presentation' },
{ pattern: /^\/presentations\/[^/]+\/view\/slides\/(\d+)$/, description: 'View your presentation slides in read-only mode' },
{ pattern: /^\/presentations\/[^/]+\/present\/(\d+)$/, description: 'Present your slides in full screen mode' },
{ pattern: /^\/presentations\/[^/]+\/slide\/new\/edit$/, description: 'Choose a layout and add content for your new slide' },
{ pattern: /^\/presentations\/[^/]+\/slide\/[^/]+\/edit$/, description: 'Edit slide content and layout' },
{ pattern: /^\/themes$/, description: 'Browse and select themes for your presentations' },
{ pattern: /^\/themes\/([^/]+)$/, description: 'View theme details, layouts, and color palette' },
{ pattern: /^\/themes\/[^/]+\/layouts\/([^/]+)$/, description: 'View layout template, slots, and configuration' },
{ pattern: /^\/themes\/[^/]+\/layouts\/[^/]+\/preview$/, description: 'Preview layout with sample content' },
];
for (const route of routes) {
if (route.pattern.test(pathname)) {
return route.description;
}
}
return 'Slide authoring and presentation tool';
};
@ -110,9 +74,12 @@ export const AppHeader: React.FC = () => {
<Link to="/presentations/new" className="nav-button primary">
Create Presentation
</Link>
<Link to="/themes" className="nav-link">
Themes
</Link>
{/* Theme browsing - Coming soon */}
<div style={{ display: 'none' }}>
<Link to="/themes" className="nav-link">
Themes
</Link>
</div>
</div>
</nav>
<div className="page-title-section">

View File

@ -1,7 +1,10 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Button } from './ui/buttons/Button.tsx';
export const Welcome: React.FC = () => {
const navigate = useNavigate();
return (
<div className="welcome-page">
<section className="hero-section">
@ -10,9 +13,13 @@ export const Welcome: React.FC = () => {
Create beautiful presentations with customizable themes and layouts
</p>
<div className="hero-actions">
<Link to="/presentations/new" className="primary-button">
<Button
variant="primary"
size="large"
onClick={() => navigate('/presentations/new')}
>
Get started with a new presentation
</Link>
</Button>
</div>
</section>
@ -66,7 +73,8 @@ export const Welcome: React.FC = () => {
<section className="getting-started-section">
<h2 className="section-title">Getting Started</h2>
<div className="steps-list">
<div className="step-item">
{/* Theme browsing step - Coming soon */}
<div className="step-item" style={{ display: 'none' }}>
<div className="step-number">1</div>
<div className="step-content">
<h3 className="step-title">Browse Themes</h3>
@ -77,17 +85,17 @@ export const Welcome: React.FC = () => {
</div>
<div className="step-item">
<div className="step-number">2</div>
<div className="step-number">1</div>
<div className="step-content">
<h3 className="step-title">Create Presentation</h3>
<p className="step-description">
Start a new presentation using your chosen theme
Start a new presentation with the default theme
</p>
</div>
</div>
<div className="step-item">
<div className="step-number">3</div>
<div className="step-number">2</div>
<div className="step-content">
<h3 className="step-title">Add Content</h3>
<p className="step-description">
@ -97,7 +105,7 @@ export const Welcome: React.FC = () => {
</div>
<div className="step-item">
<div className="step-number">4</div>
<div className="step-number">3</div>
<div className="step-content">
<h3 className="step-title">Present</h3>
<p className="step-description">
@ -114,9 +122,13 @@ export const Welcome: React.FC = () => {
<p className="cta-description">
Start building your presentation with our theme collection
</p>
<Link to="/presentations/new" className="primary-button">
<Button
variant="primary"
size="large"
onClick={() => navigate('/presentations/new')}
>
Create Your First Presentation
</Link>
</Button>
</div>
</section>
</div>

View File

@ -107,8 +107,8 @@ export const NewPresentationPage: React.FC = () => {
<div className="error-content">
<h2>Error Loading Themes</h2>
<p>{error}</p>
<button onClick={() => navigate('/themes')} className="button secondary">
Back to Themes
<button onClick={() => navigate(-1)} className="button secondary">
Back
</button>
</div>
</div>
@ -119,11 +119,11 @@ export const NewPresentationPage: React.FC = () => {
<div className="new-presentation-page">
<header className="page-header">
<button
onClick={() => navigate('/themes')}
onClick={() => navigate(-1)}
className="back-button"
type="button"
>
Back to Themes
Back
</button>
<div className="header-content">
<h1>Create New Presentation</h1>
@ -160,7 +160,7 @@ export const NewPresentationPage: React.FC = () => {
error={error}
creating={creating}
presentationTitle={presentationTitle}
onCancel={() => navigate('/themes')}
onCancel={() => navigate(-1)}
onCreate={handleCreatePresentation}
/>
</div>

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import type { Presentation } from '../../types/presentation.ts';
import type { Theme } from '../../types/theme.ts';
import { getPresentationById, updatePresentation } from '../../utils/presentationStorage.ts';
import { getPresentationById } from '../../utils/presentationStorage.ts';
import { loadTheme } from '../../utils/themeLoader.ts';
import { useDialog } from '../../hooks/useDialog.ts';
import { AlertDialog } from '../ui/AlertDialog.tsx';

View File

@ -156,7 +156,8 @@ export const PresentationMode: React.FC = () => {
}, [presentationId]);
const exitPresentationMode = () => {
navigate(`/presentations/${presentationId}/view/slides/${currentSlideIndex + 1}`);
// Navigate back to the previous page in history
navigate(-1);
};
const renderSlideContent = (slide: SlideContent): string => {

View File

@ -1,8 +1,11 @@
import React, { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import type { Presentation } from '../../types/presentation.ts';
import { getAllPresentations, deletePresentation } from '../../utils/presentationStorage.ts';
import { loggers } from '../../utils/logger.ts';
import { ConfirmDialog } from '../ui/ConfirmDialog.tsx';
import { AlertDialog } from '../ui/AlertDialog.tsx';
import { Button } from '../ui/buttons/Button.tsx';
import './PresentationsList.css';
export const PresentationsList: React.FC = () => {
@ -11,6 +14,19 @@ export const PresentationsList: React.FC = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [deleting, setDeleting] = useState<string | null>(null);
// Dialog states
const [confirmDialog, setConfirmDialog] = useState<{
isOpen: boolean;
presentationId: string;
presentationName: string;
}>({ isOpen: false, presentationId: '', presentationName: '' });
const [alertDialog, setAlertDialog] = useState<{
isOpen: boolean;
message: string;
type?: 'error' | 'success';
}>({ isOpen: false, message: '' });
useEffect(() => {
loadPresentations();
@ -29,18 +45,34 @@ export const PresentationsList: React.FC = () => {
}
};
const handleDeletePresentation = async (id: string, name: string) => {
if (!confirm(`Are you sure you want to delete "${name}"? This action cannot be undone.`)) {
return;
}
const handleDeletePresentation = (id: string, name: string) => {
setConfirmDialog({
isOpen: true,
presentationId: id,
presentationName: name
});
};
const confirmDeletePresentation = async () => {
const { presentationId } = confirmDialog;
setConfirmDialog({ isOpen: false, presentationId: '', presentationName: '' });
try {
setDeleting(id);
await deletePresentation(id);
setPresentations(prev => prev.filter(p => p.metadata.id !== id));
setDeleting(presentationId);
await deletePresentation(presentationId);
setPresentations(prev => prev.filter(p => p.metadata.id !== presentationId));
setAlertDialog({
isOpen: true,
message: 'Presentation deleted successfully',
type: 'success'
});
} catch (err) {
loggers.presentation.error('Failed to delete presentation', err instanceof Error ? err : new Error(String(err)));
alert('Failed to delete presentation. Please try again.');
setAlertDialog({
isOpen: true,
message: 'Failed to delete presentation. Please try again.',
type: 'error'
});
} finally {
setDeleting(null);
}
@ -100,14 +132,6 @@ export const PresentationsList: React.FC = () => {
<h1>My Presentations</h1>
<p>Manage and organize your presentation library</p>
</div>
<div className="header-actions">
<Link
to="/presentations/new"
className="action-button primary"
>
Create New Presentation
</Link>
</div>
</header>
<main className="list-content">
@ -116,12 +140,13 @@ export const PresentationsList: React.FC = () => {
<div className="empty-content">
<h2>No presentations yet</h2>
<p>Create your first presentation to get started</p>
<Link
to="/presentations/new"
className="action-button primary large"
<Button
variant="primary"
size="large"
onClick={() => navigate('/presentations/new')}
>
Create Your First Presentation
</Link>
</Button>
</div>
</div>
) : (
@ -233,22 +258,36 @@ export const PresentationsList: React.FC = () => {
</p>
</div>
<div className="footer-actions">
<button
onClick={loadPresentations}
className="action-button secondary"
>
Refresh
</button>
<Link
to="/presentations/new"
className="action-button primary"
<Button
variant="primary"
onClick={() => navigate('/presentations/new')}
>
Create New
</Link>
</Button>
</div>
</div>
)}
</main>
{/* Confirmation Dialog for Deletion */}
<ConfirmDialog
isOpen={confirmDialog.isOpen}
onClose={() => setConfirmDialog({ isOpen: false, presentationId: '', presentationName: '' })}
onConfirm={confirmDeletePresentation}
title="Delete Presentation"
message={`Are you sure you want to delete "${confirmDialog.presentationName}"? This action cannot be undone.`}
type="danger"
confirmText="Delete"
isDestructive={true}
/>
{/* Alert Dialog for Success/Error Messages */}
<AlertDialog
isOpen={alertDialog.isOpen}
onClose={() => setAlertDialog({ isOpen: false, message: '' })}
message={alertDialog.message}
type={alertDialog.type}
/>
</div>
);
};

View File

@ -1,5 +1,5 @@
import React from 'react';
import { useParams, Link } from 'react-router-dom';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { SlideEditorErrorBoundary } from './SlideEditorErrorBoundary.tsx';
import { LoadingState } from './LoadingState.tsx';
import { ErrorState } from './ErrorState.tsx';
@ -11,6 +11,7 @@ import { useSlideEditor } from './useSlideEditor.ts';
import './SlideEditor.css';
export const SlideEditor: React.FC = () => {
const navigate = useNavigate();
const { presentationId, slideId } = useParams<{
presentationId: string;
slideId: string;
@ -43,6 +44,21 @@ export const SlideEditor: React.FC = () => {
cancelEditing,
} = useSlideEditor({ presentationId, slideId });
// Helper function to get current slide number for presentation
const getCurrentSlideNumber = (): number => {
if (!presentation || !slideId || slideId === 'new') {
return 1; // Default to first slide for new slides
}
const slideIndex = presentation.slides.findIndex(slide => slide.id === slideId);
return slideIndex !== -1 ? slideIndex + 1 : 1;
};
const handlePresentFromHere = () => {
const slideNumber = getCurrentSlideNumber();
navigate(`/presentations/${presentationId}/present/${slideNumber}`);
};
if (loading) {
return <LoadingState presentationId={presentationId} />;
}
@ -76,12 +92,20 @@ export const SlideEditor: React.FC = () => {
<div className="editor-actions">
{currentStep === 'content' && selectedLayout && (
<ActionButton
variant="secondary"
onClick={() => setShowPreview(true)}
>
Full Preview
</ActionButton>
<>
<ActionButton
variant="secondary"
onClick={() => setShowPreview(true)}
>
Full Preview
</ActionButton>
<ActionButton
variant="primary"
onClick={handlePresentFromHere}
>
Present from here
</ActionButton>
</>
)}
</div>
</header>

View File

@ -17,7 +17,7 @@ export function themeWatcherPlugin(): Plugin {
const watcher = fs.watch(
publicThemesPath,
{ recursive: true },
(eventType: string, filename: string | null) => {
(_eventType: string, filename: string | null) => {
if (filename && (filename.endsWith('.css') || filename.endsWith('.html'))) {
console.log(`Theme file changed: ${filename}`);

View File

@ -5,25 +5,29 @@ import log from 'loglevel';
*/
// Define log levels for different parts of the application
export enum LogLevel {
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
SILENT = 5,
}
export const LogLevel = {
TRACE: 0,
DEBUG: 1,
INFO: 2,
WARN: 3,
ERROR: 4,
SILENT: 5,
} as const;
export type LogLevel = typeof LogLevel[keyof typeof LogLevel];
// Log categories for better organization
export enum LogCategory {
PRESENTATION = 'presentation',
THEME = 'theme',
STORAGE = 'storage',
UI = 'ui',
SECURITY = 'security',
PERFORMANCE = 'performance',
GENERAL = 'general',
}
export const LogCategory = {
PRESENTATION: 'presentation',
THEME: 'theme',
STORAGE: 'storage',
UI: 'ui',
SECURITY: 'security',
PERFORMANCE: 'performance',
GENERAL: 'general',
} as const;
export type LogCategory = typeof LogCategory[keyof typeof LogCategory];
/**
* Configure logging based on environment