import React, { useState, useEffect } from 'react';
import { X, Plus, Edit2, Trash2, ExternalLink } from 'lucide-react';
export default function LiteraryJournal() {
const [authors, setAuthors] = useState([]);
const [isAdmin, setIsAdmin] = useState(false);
const [selectedAuthor, setSelectedAuthor] = useState(null);
const [isEditing, setIsEditing] = useState(false);
const [editingAuthor, setEditingAuthor] = useState(null);
const [showPasswordModal, setShowPasswordModal] = useState(false);
const [passwordInput, setPasswordInput] = useState('');
const [passwordError, setPasswordError] = useState('');
// Set your admin password here
const ADMIN_PASSWORD = 'your-secure-password-123';
// Load authors from storage on mount
useEffect(() => {
loadAuthors();
}, []);
const loadAuthors = async () => {
try {
const result = await window.storage.get('journal-authors');
if (result) {
setAuthors(JSON.parse(result.value));
}
} catch (error) {
console.log('No saved authors yet');
}
};
const saveAuthors = async (updatedAuthors) => {
try {
await window.storage.set('journal-authors', JSON.stringify(updatedAuthors));
setAuthors(updatedAuthors);
} catch (error) {
console.error('Failed to save authors:', error);
}
};
const handleAddAuthor = () => {
setEditingAuthor({
id: Date.now(),
name: '',
photo: '',
bio: '',
articleType: 'link',
articleTitle: '',
articleContent: ''
});
setIsEditing(true);
};
const handleAdminToggle = () => {
if (isAdmin) {
// Logging out
setIsAdmin(false);
} else {
// Trying to log in
setShowPasswordModal(true);
setPasswordInput('');
setPasswordError('');
}
};
const handlePasswordSubmit = () => {
if (passwordInput === ADMIN_PASSWORD) {
setIsAdmin(true);
setShowPasswordModal(false);
setPasswordInput('');
setPasswordError('');
} else {
setPasswordError('Incorrect password');
}
};
const handleEditAuthor = (author) => {
setEditingAuthor({ ...author });
setIsEditing(true);
};
const handleDeleteAuthor = (id) => {
if (window.confirm('Are you sure you want to delete this author?')) {
const updated = authors.filter(a => a.id !== id);
saveAuthors(updated);
}
};
const handleSaveAuthor = () => {
if (!editingAuthor.name || !editingAuthor.articleTitle) {
alert('Please fill in at least the name and article title');
return;
}
const updated = authors.find(a => a.id === editingAuthor.id)
? authors.map(a => a.id === editingAuthor.id ? editingAuthor : a)
: [...authors, editingAuthor];
saveAuthors(updated);
setIsEditing(false);
setEditingAuthor(null);
};
const handlePhotoUpload = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
setEditingAuthor({ ...editingAuthor, photo: event.target.result });
};
reader.readAsDataURL(file);
}
};
const handlePDFUpload = (e) => {
const file = e.target.files[0];
if (file && file.type === 'application/pdf') {
const reader = new FileReader();
reader.onload = (event) => {
setEditingAuthor({
...editingAuthor,
articleContent: event.target.result,
articleType: 'pdf'
});
};
reader.readAsDataURL(file);
}
};
return (
<div className="min-h-screen bg-stone-50">
{/* Header */}
<header className="bg-white border-b border-stone-200">
<div className="max-w-7xl mx-auto px-6 py-8">
<div className="flex justify-between items-center">
<div>
<h1 className="text-4xl font-serif text-stone-900 mb-2">The Quarterly</h1>
<p className="text-stone-600 italic">A collection of contemporary voices</p>
</div>
<button
onClick={handleAdminToggle}
className="px-4 py-2 text-sm bg-stone-800 text-white rounded hover:bg-stone-700 transition"
>
{isAdmin ? 'Exit Admin' : 'Admin Mode'}
</button>
</div>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto px-6 py-12">
{isAdmin && (
<div className="mb-8">
<button
onClick={handleAddAuthor}
className="flex items-center gap-2 px-6 py-3 bg-stone-800 text-white rounded hover:bg-stone-700 transition"
>
<Plus size={20} />
Add New Author
</button>
</div>
)}
{/* Authors Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{authors.map((author) => (
<div
key={author.id}
className="group relative bg-white border border-stone-200 overflow-hidden hover:shadow-lg transition-shadow cursor-pointer"
onClick={() => !isAdmin && setSelectedAuthor(author)}
>
{author.photo ? (
<img
src={author.photo}
alt={author.name}
className="w-full h-64 object-cover"
/>
) : (
<div className="w-full h-64 bg-stone-200 flex items-center justify-center">
<span className="text-stone-400 text-sm">No photo</span>
</div>
)}
<div className="p-6">
<h3 className="text-xl font-serif text-stone-900 mb-1">{author.name}</h3>
<p className="text-sm text-stone-600 italic">{author.articleTitle}</p>
</div>
{isAdmin && (
<div className="absolute top-4 right-4 flex gap-2">
<button
onClick={(e) => {
e.stopPropagation();
handleEditAuthor(author);
}}
className="p-2 bg-white rounded-full shadow-lg hover:bg-stone-100"
>
<Edit2 size={16} />
</button>
<button
onClick={(e) => {
e.stopPropagation();
handleDeleteAuthor(author.id);
}}
className="p-2 bg-white rounded-full shadow-lg hover:bg-red-100 text-red-600"
>
<Trash2 size={16} />
</button>
</div>
)}
</div>
))}
</div>
{authors.length === 0 && (
<div className="text-center py-20">
<p className="text-stone-500 text-lg">No authors yet. {isAdmin ? 'Add your first author to get started.' : ''}</p>
</div>
)}
</main>
{/* Author Detail Modal */}
{selectedAuthor && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white max-w-3xl w-full max-h-[90vh] overflow-y-auto">
<div className="sticky top-0 bg-white border-b border-stone-200 p-6 flex justify-between items-start">
<div className="flex gap-6">
{selectedAuthor.photo && (
<img
src={selectedAuthor.photo}
alt={selectedAuthor.name}
className="w-24 h-24 object-cover"
/>
)}
<div>
<h2 className="text-3xl font-serif text-stone-900 mb-2">{selectedAuthor.name}</h2>
<p className="text-stone-600 italic">{selectedAuthor.articleTitle}</p>
</div>
</div>
<button
onClick={() => setSelectedAuthor(null)}
className="p-2 hover:bg-stone-100 rounded"
>
<X size={24} />
</button>
</div>
<div className="p-6">
<div className="mb-8">
<h3 className="text-sm uppercase tracking-wider text-stone-500 mb-3">About the Author</h3>
<p className="text-stone-700 leading-relaxed">{selectedAuthor.bio}</p>
</div>
<div className="border-t border-stone-200 pt-8">
<h3 className="text-sm uppercase tracking-wider text-stone-500 mb-4">Read the Work</h3>
{selectedAuthor.articleType === 'link' && (
<a
href={selectedAuthor.articleContent}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 bg-stone-800 text-white hover:bg-stone-700 transition"
>
Read "{selectedAuthor.articleTitle}"
<ExternalLink size={18} />
</a>
)}
{selectedAuthor.articleType === 'text' && (
<div className="prose max-w-none">
<p className="whitespace-pre-wrap text-stone-700 leading-relaxed">
{selectedAuthor.articleContent}
</p>
</div>
)}
{selectedAuthor.articleType === 'pdf' && selectedAuthor.articleContent && (
<a
href={selectedAuthor.articleContent}
download={`${selectedAuthor.articleTitle}.pdf`}
className="inline-flex items-center gap-2 px-6 py-3 bg-stone-800 text-white hover:bg-stone-700 transition"
>
Download PDF
</a>
)}
</div>
</div>
</div>
</div>
)}
{/* Password Modal */}
{showPasswordModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white max-w-md w-full p-6 rounded">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-serif">Admin Login</h2>
<button
onClick={() => {
setShowPasswordModal(false);
setPasswordInput('');
setPasswordError('');
}}
className="p-2 hover:bg-stone-100 rounded"
>
<X size={24} />
</button>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">
Enter Admin Password
</label>
<input
type="password"
value={passwordInput}
onChange={(e) => {
setPasswordInput(e.target.value);
setPasswordError('');
}}
onKeyPress={(e) => {
if (e.key === 'Enter') {
handlePasswordSubmit();
}
}}
className="w-full px-4 py-2 border border-stone-300 rounded focus:outline-none focus:ring-2 focus:ring-stone-500"
autoFocus
/>
{passwordError && (
<p className="mt-2 text-sm text-red-600">{passwordError}</p>
)}
</div>
<button
onClick={handlePasswordSubmit}
className="w-full px-6 py-3 bg-stone-800 text-white rounded hover:bg-stone-700 transition"
>
Login
</button>
</div>
</div>
</div>
)}
{/* Edit Modal */}
{isEditing && editingAuthor && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div className="sticky top-0 bg-white border-b border-stone-200 p-6 flex justify-between items-center">
<h2 className="text-2xl font-serif">
{authors.find(a => a.id === editingAuthor.id) ? 'Edit Author' : 'New Author'}
</h2>
<button
onClick={() => {
setIsEditing(false);
setEditingAuthor(null);
}}
className="p-2 hover:bg-stone-100 rounded"
>
<X size={24} />
</button>
</div>
<div className="p-6 space-y-6">
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Name *</label>
<input
type="text"
value={editingAuthor.name}
onChange={(e) => setEditingAuthor({ ...editingAuthor, name: e.target.value })}
className="w-full px-4 py-2 border border-stone-300 rounded focus:outline-none focus:ring-2 focus:ring-stone-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Photo</label>
<input
type="file"
accept="image/*"
onChange={handlePhotoUpload}
className="w-full"
/>
{editingAuthor.photo && (
<img src={editingAuthor.photo} alt="Preview" className="mt-2 w-32 h-32 object-cover" />
)}
</div>
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Bio</label>
<textarea
value={editingAuthor.bio}
onChange={(e) => setEditingAuthor({ ...editingAuthor, bio: e.target.value })}
rows={4}
className="w-full px-4 py-2 border border-stone-300 rounded focus:outline-none focus:ring-2 focus:ring-stone-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Article Title *</label>
<input
type="text"
value={editingAuthor.articleTitle}
onChange={(e) => setEditingAuthor({ ...editingAuthor, articleTitle: e.target.value })}
className="w-full px-4 py-2 border border-stone-300 rounded focus:outline-none focus:ring-2 focus:ring-stone-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Article Type</label>
<select
value={editingAuthor.articleType}
onChange={(e) => setEditingAuthor({ ...editingAuthor, articleType: e.target.value })}
className="w-full px-4 py-2 border border-stone-300 rounded focus:outline-none focus:ring-2 focus:ring-stone-500"
>
<option value="link">External Link</option>
<option value="text">Text Content</option>
<option value="pdf">PDF Upload</option>
</select>
</div>
{editingAuthor.articleType === 'link' && (
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Article URL</label>
<input
type="url"
value={editingAuthor.articleContent}
onChange={(e) => setEditingAuthor({ ...editingAuthor, articleContent: e.target.value })}
placeholder="https://..."
className="w-full px-4 py-2 border border-stone-300 rounded focus:outline-none focus:ring-2 focus:ring-stone-500"
/>
</div>
)}
{editingAuthor.articleType === 'text' && (
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Article Text</label>
<textarea
value={editingAuthor.articleContent}
onChange={(e) => setEditingAuthor({ ...editingAuthor, articleContent: e.target.value })}
rows={8}
className="w-full px-4 py-2 border border-stone-300 rounded focus:outline-none focus:ring-2 focus:ring-stone-500"
/>
</div>
)}
{editingAuthor.articleType === 'pdf' && (
<div>
<label className="block text-sm font-medium text-stone-700 mb-2">Upload PDF</label>
<input
type="file"
accept="application/pdf"
onChange={handlePDFUpload}
className="w-full"
/>
{editingAuthor.articleContent && (
<p className="mt-2 text-sm text-green-600">PDF uploaded successfully</p>
)}
</div>
)}
<div className="flex gap-3 pt-4">
<button
onClick={handleSaveAuthor}
className="flex-1 px-6 py-3 bg-stone-800 text-white rounded hover:bg-stone-700 transition"
>
Save Author
</button>
<button
onClick={() => {
setIsEditing(false);
setEditingAuthor(null);
}}
className="px-6 py-3 border border-stone-300 rounded hover:bg-stone-50 transition"
>
Cancel
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
}