import { useState } from 'react';
import { useScrapbook } from '../context/ScrapbookContext';
import { PaintBucket, X } from 'lucide-react';
interface BackgroundSelectorProps {
pageId: string;
}
// Mock background data
const PAPER_BACKGROUNDS = [
{ id: 'vintage', url: 'https://img.freepik.com/free-photo/brown-concrete-wall-with-scratches_1339-4193.jpg' },
{ id: 'craft', url: 'https://img.freepik.com/free-photo/brown-paper-texture_95678-75.jpg' },
{ id: 'watercolor', url: 'https://img.freepik.com/free-photo/abstract-watercolor-paper-background_23-2149060409.jpg' },
{ id: 'newspaper', url: 'https://img.freepik.com/free-photo/old-newspaper-background_1194-6309.jpg' },
{ id: 'music', url: 'https://img.freepik.com/free-photo/sheet-music-notes_144627-16624.jpg' }
];
const PATTERN_BACKGROUNDS = [
{ id: 'polkadot', url: 'https://img.freepik.com/free-vector/polka-dots-pattern-design_53876-90531.jpg' },
{ id: 'gingham-red', url: 'https://img.freepik.com/free-vector/red-white-gingham-pattern-background_1409-1599.jpg' },
{ id: 'floral', url: 'https://img.freepik.com/free-vector/vintage-floral-background_1284-38654.jpg' },
{ id: 'stripes', url: 'https://img.freepik.com/free-vector/vintage-striped-seamless-pattern_1409-2938.jpg' },
{ id: 'hearts', url: 'https://img.freepik.com/free-vector/hand-drawn-heart-pattern_1189-1510.jpg' }
];
const COLOR_BACKGROUNDS = [
{ id: 'cream', color: '#FFF8E1' },
{ id: 'pink', color: '#FCE4EC' },
{ id: 'light-blue', color: '#E3F2FD' },
{ id: 'mint', color: '#E8F5E9' },
{ id: 'lavender', color: '#F3E5F5' },
{ id: 'peach', color: '#FFF3E0' }
];
export default function BackgroundSelector({ pageId }: BackgroundSelectorProps) {
const { updatePageBackground } = useScrapbook();
const [isOpen, setIsOpen] = useState(false);
const [activeTab, setActiveTab] = useState<'paper' | 'pattern' | 'color'>('paper');
const handleSelectBackground = (type: 'paper' | 'pattern' | 'color', value: string) => {
updatePageBackground(pageId, type, value);
setIsOpen(false);
};
return (
{isOpen && (
Select Background
{activeTab === 'paper' && (
{PAPER_BACKGROUNDS.map(bg => (
))}
)}
{activeTab === 'pattern' && (
{PATTERN_BACKGROUNDS.map(bg => (
))}
)}
{activeTab === 'color' && (
{COLOR_BACKGROUNDS.map(bg => (
)}
)}
);
}
import { useState } from 'react';
import { useScrapbook } from '../context/ScrapbookContext';
import { BookOpen, X } from 'lucide-react';
interface AddJournalEntryProps {
pageId: string;
}
export default function AddJournalEntry({ pageId }: AddJournalEntryProps) {
const { addJournalEntry } = useScrapbook();
const [isAdding, setIsAdding] = useState(false);
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const handleAdd = () => {
if (!content.trim()) return;
const today = new Date();
const formattedDate = today.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric'
});
addJournalEntry(pageId, {
title: title || undefined,
content,
date: formattedDate
});
setTitle('');
setContent('');
setIsAdding(false);
};
if (!isAdding) {
return (
);
}
return (
New Journal Entry
setTitle(e.target.value)}
className="w-full px-3 py-2 border border-amber-300 rounded-lg"
placeholder="Add a title..."
/>
);
}
import { useState, useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { GuestbookEntry } from '../types/GuestbookEntry';
import { Calendar, MessageSquare, PenLine, Send, User } from 'lucide-react';
export default function GuestbookPage() {
const [entries, setEntries] = useState([]);
const [name, setName] = useState('');
const [message, setMessage] = useState('');
// Load entries from localStorage on initial render
useEffect(() => {
const savedEntries = localStorage.getItem('guestbook-entries');
if (savedEntries) {
setEntries(JSON.parse(savedEntries));
}
}, []);
// Save entries to localStorage whenever they change
useEffect(() => {
localStorage.setItem('guestbook-entries', JSON.stringify(entries));
}, [entries]);
const addEntry = () => {
if (!name.trim() || !message.trim()) return;
const newEntry: GuestbookEntry = {
id: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
name: name.trim(),
message: message.trim(),
timestamp: new Date().toLocaleString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
};
setEntries([newEntry, ...entries]);
setName('');
setMessage('');
};
return (
Guestbook
Leave a message to sign our scrapbook!
Messages ({entries.length})
{entries.length === 0 ? (
No messages yet. Be the first to sign!
) : (
{entries.map((entry) => (
{entry.name}
{entry.timestamp}
{entry.message}
))}
)}
);
}
import { Book, House, MessageSquare, Settings, User } from 'lucide-react';
import { useState } from 'react';
export default function Header() {
const [isProfileOpen, setIsProfileOpen] = useState(false);
return
Avina's Diary
{isProfileOpen &&
}
;
}import { useState, useRef } from 'react';
import { JournalEntry as JournalEntryType } from '../types';
import { useScrapbook } from '../context/ScrapbookContext';
import { Squircle, Move, Trash2 } from 'lucide-react';
interface JournalEntryProps {
entry: JournalEntryType;
pageId: string;
}
export default function JournalEntry({ entry, pageId }: JournalEntryProps) {
const { updateJournalEntry, deleteJournalEntry } = useScrapbook();
const [isEditing, setIsEditing] = useState(false);
const [title, setTitle] = useState(entry.title || '');
const [content, setContent] = useState(entry.content);
const [showControls, setShowControls] = useState(false);
const entryRef = useRef(null);
const handleSave = () => {
updateJournalEntry(pageId, {
...entry,
title,
content
});
setIsEditing(false);
};
const handleDelete = () => {
deleteJournalEntry(pageId, entry.id);
};
return (
setShowControls(true)}
onMouseLeave={() => setShowControls(false)}
>
{isEditing ? (
) : (
{entry.title && (
{entry.title}
)}
{entry.content}
{entry.date}
)}
{showControls && !isEditing && (
)}
);
}
import { useState } from 'react';
import { useScrapbook } from '../context/ScrapbookContext';
import { ArrowLeft, ArrowRight, Book, Plus } from 'lucide-react';
export default function PageNavigation() {
const { pages, currentPageId, setCurrentPageId, addPage } = useScrapbook();
const [newPageTitle, setNewPageTitle] = useState('');
const [isAddingPage, setIsAddingPage] = useState(false);
const currentPageIndex = pages.findIndex(page => page.id === currentPageId);
const handlePrevPage = () => {
if (currentPageIndex > 0) {
setCurrentPageId(pages[currentPageIndex - 1].id);
}
};
const handleNextPage = () => {
if (currentPageIndex < pages.length - 1) {
setCurrentPageId(pages[currentPageIndex + 1].id);
}
};
const handleAddPage = () => {
if (newPageTitle.trim()) {
addPage(newPageTitle);
setNewPageTitle('');
setIsAddingPage(false);
}
};
return (
Page {currentPageIndex + 1} of {pages.length}
{isAddingPage ? (
<>
setNewPageTitle(e.target.value)}
placeholder="Page title..."
className="px-3 py-1.5 text-sm border border-amber-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
autoFocus
/>
>
) : (
)}
);
}
import { useState, useRef } from 'react';
import { Photo as PhotoType } from '../types';
import { useScrapbook } from '../context/ScrapbookContext';
import { Squircle, Move, Trash2 } from 'lucide-react';
interface PhotoProps {
photo: PhotoType;
pageId: string;
}
export default function Photo({ photo, pageId }: PhotoProps) {
const { updatePhoto, deletePhoto } = useScrapbook();
const [isEditing, setIsEditing] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [caption, setCaption] = useState(photo.caption || '');
const [showControls, setShowControls] = useState(false);
const photoRef = useRef(null);
const initialPosition = useRef({ x: 0, y: 0 });
const photoPosition = useRef({ x: photo.x || 0, y: photo.y || 0 });
const handleMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
setIsDragging(true);
initialPosition.current = { x: e.clientX, y: e.clientY };
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDragging) return;
const dx = e.clientX - initialPosition.current.x;
const dy = e.clientY - initialPosition.current.y;
initialPosition.current = { x: e.clientX, y: e.clientY };
photoPosition.current = {
x: photoPosition.current.x + dx,
y: photoPosition.current.y + dy
};
if (photoRef.current) {
photoRef.current.style.transform = `
translate(${photoPosition.current.x}px, ${photoPosition.current.y}px)
rotate(${photo.rotation || 0}deg)
`;
}
};
const handleMouseUp = () => {
if (isDragging) {
setIsDragging(false);
updatePhoto(pageId, {
...photo,
x: photoPosition.current.x,
y: photoPosition.current.y
});
}
};
const handleRotate = (direction: 'left' | 'right') => {
const currentRotation = photo.rotation || 0;
const newRotation = direction === 'left'
? currentRotation - 5
: currentRotation + 5;
updatePhoto(pageId, { ...photo, rotation: newRotation });
};
const handleSaveCaption = () => {
updatePhoto(pageId, { ...photo, caption });
setIsEditing(false);
};
const handleDelete = () => {
deletePhoto(pageId, photo.id);
};
return (
setShowControls(true)}
onMouseLeave={() => setShowControls(false)}
>

{isEditing ? (
) : (
photo.caption && (
{photo.caption}
)
)}
{showControls && (
)}
{isDragging && (
)}
);
}