Add multi-version Bible support with ESV and NKJV translations
- Rename project from 'ESV Bible' to 'The Bible' - Implement version selection dropdown in homepage header - Add support for multiple Bible versions: * ESV (English Standard Version) - from mdbible * NKJV (New King James Version) - from local NKJV/ directory - Update all API endpoints to accept version parameter (?version=esv|?version=nkjv) - Add version-aware favorites system that stores and displays Bible version (e.g., 'Genesis 1:1 (ESV)') - Update database schema to include version column in favorites table - Maintain backward compatibility with existing data - Update Docker configuration and documentation
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "esv-bible-frontend",
|
||||
"name": "the-bible-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -24,6 +24,8 @@ function App() {
|
||||
});
|
||||
const [error, setError] = useState<string>('');
|
||||
const [showSearch, setShowSearch] = useState(false);
|
||||
const [selectedVersion, setSelectedVersion] = useState('esv'); // Default to ESV
|
||||
const [availableVersions, setAvailableVersions] = useState<any[]>([]);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -32,10 +34,16 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
console.log('App useEffect triggered');
|
||||
loadVersions();
|
||||
loadBooks();
|
||||
checkAuthStatus();
|
||||
}, []);
|
||||
|
||||
// Load versions when version changes
|
||||
useEffect(() => {
|
||||
loadBooks();
|
||||
}, [selectedVersion]);
|
||||
|
||||
// Load user preferences when user changes
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
@@ -184,8 +192,9 @@ function App() {
|
||||
|
||||
const loadBooks = async () => {
|
||||
try {
|
||||
console.log('Loading books from API...');
|
||||
const data: BookData = await getBooks();
|
||||
console.log('Loading books from API for version:', selectedVersion);
|
||||
const response = await fetch(`/books?version=${selectedVersion}`);
|
||||
const data: BookData = await response.json();
|
||||
console.log('Books loaded:', data);
|
||||
setBooks(data.books);
|
||||
} catch (error) {
|
||||
@@ -196,6 +205,22 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const loadVersions = async () => {
|
||||
try {
|
||||
console.log('Loading available versions...');
|
||||
const response = await fetch('/versions');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setAvailableVersions(data.versions);
|
||||
console.log('Versions loaded:', data.versions);
|
||||
} else {
|
||||
console.error('Failed to load versions');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load versions:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Get current navigation info from URL
|
||||
const getCurrentNavInfo = () => {
|
||||
const pathParts = location.pathname.split('/').filter(Boolean);
|
||||
@@ -298,6 +323,7 @@ function App() {
|
||||
formatBookName={formatBookName}
|
||||
user={user}
|
||||
onFavoriteChange={handleFavoriteChange}
|
||||
version={selectedVersion}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -307,7 +333,7 @@ function App() {
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Book className="mx-auto h-12 w-12 text-blue-600 animate-pulse" />
|
||||
<p className="mt-4 text-lg">Loading ESV Bible...</p>
|
||||
<p className="mt-4 text-lg">Loading The Bible...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -321,12 +347,26 @@ function App() {
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className="flex items-center space-x-2 sm:space-x-4">
|
||||
<Book className="h-6 w-6 sm:h-8 sm:w-8 text-blue-600" />
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="text-lg sm:text-xl font-bold text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400"
|
||||
>
|
||||
ESV Bible
|
||||
</button>
|
||||
<div className="flex flex-col">
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="text-lg sm:text-xl font-bold text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400"
|
||||
>
|
||||
The Bible
|
||||
</button>
|
||||
{/* Version Selector */}
|
||||
<select
|
||||
value={selectedVersion}
|
||||
onChange={(e) => setSelectedVersion(e.target.value)}
|
||||
className="text-xs text-gray-600 dark:text-gray-400 bg-transparent border-none focus:outline-none cursor-pointer hover:text-blue-600 dark:hover:text-blue-400"
|
||||
>
|
||||
{availableVersions.map((version) => (
|
||||
<option key={version.id} value={version.id}>
|
||||
{version.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Breadcrumb - Hidden on small screens */}
|
||||
|
||||
@@ -9,9 +9,10 @@ interface BibleReaderProps {
|
||||
formatBookName: (bookName: string) => string;
|
||||
user?: any;
|
||||
onFavoriteChange?: () => void;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
const BibleReader: React.FC<BibleReaderProps> = ({ book, chapter, onBack, formatBookName, user, onFavoriteChange }) => {
|
||||
const BibleReader: React.FC<BibleReaderProps> = ({ book, chapter, onBack, formatBookName, user, onFavoriteChange, version = 'esv' }) => {
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [chapters, setChapters] = useState<string[]>([]);
|
||||
@@ -125,7 +126,8 @@ const BibleReader: React.FC<BibleReaderProps> = ({ book, chapter, onBack, format
|
||||
body: JSON.stringify({
|
||||
book: book,
|
||||
chapter: chapter,
|
||||
verse_start: parseInt(verseNumber)
|
||||
verse_start: parseInt(verseNumber),
|
||||
version: version
|
||||
})
|
||||
});
|
||||
|
||||
@@ -147,9 +149,10 @@ const BibleReader: React.FC<BibleReaderProps> = ({ book, chapter, onBack, format
|
||||
|
||||
const loadChapters = async () => {
|
||||
try {
|
||||
const response = await getBook(book);
|
||||
if (response.chapters) {
|
||||
setChapters(response.chapters);
|
||||
const response = await fetch(`/books/${book}?version=${version}`);
|
||||
const data = await response.json();
|
||||
if (data.chapters) {
|
||||
setChapters(data.chapters);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load chapters:', error);
|
||||
@@ -199,14 +202,13 @@ const BibleReader: React.FC<BibleReaderProps> = ({ book, chapter, onBack, format
|
||||
const loadChapter = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log(`Loading chapter: ${book}/${chapter}`);
|
||||
|
||||
// Format chapter as "Chapter_XX" with leading zero
|
||||
const paddedChapter = chapter.padStart(2, '0');
|
||||
const chapterFileName = `Chapter_${paddedChapter}`;
|
||||
console.log(`Chapter file name: ${chapterFileName}`);
|
||||
|
||||
const chapterContent = await getChapter(book, chapterFileName);
|
||||
console.log(`Loading chapter: ${book}/${chapter} (version: ${version})`);
|
||||
|
||||
// Use the chapter number directly as the API handles formatting
|
||||
const chapterFileName = chapter;
|
||||
const response = await fetch(`/books/${book}/${chapter}?version=${version}`);
|
||||
const chapterContent = await response.text();
|
||||
|
||||
setContent(chapterContent);
|
||||
} catch (error) {
|
||||
console.error('Failed to load chapter:', error);
|
||||
|
||||
@@ -8,6 +8,7 @@ interface Favorite {
|
||||
chapter?: string;
|
||||
verse_start?: number;
|
||||
verse_end?: number;
|
||||
version: string;
|
||||
note?: string;
|
||||
created_at: string;
|
||||
}
|
||||
@@ -87,16 +88,20 @@ const FavoritesMenu: React.FC<FavoritesMenuProps> = ({ user, formatBookName, get
|
||||
|
||||
const getFavoriteDisplayText = (favorite: Favorite) => {
|
||||
const bookName = formatBookName(favorite.book);
|
||||
|
||||
const versionAbbrev = favorite.version.toUpperCase(); // ESV, NKJV, etc.
|
||||
|
||||
let reference = '';
|
||||
if (favorite.verse_start && favorite.verse_end) {
|
||||
return `${bookName} ${favorite.chapter}:${favorite.verse_start}-${favorite.verse_end}`;
|
||||
reference = `${bookName} ${favorite.chapter}:${favorite.verse_start}-${favorite.verse_end}`;
|
||||
} else if (favorite.verse_start) {
|
||||
return `${bookName} ${favorite.chapter}:${favorite.verse_start}`;
|
||||
reference = `${bookName} ${favorite.chapter}:${favorite.verse_start}`;
|
||||
} else if (favorite.chapter) {
|
||||
return `${bookName} Chapter ${favorite.chapter}`;
|
||||
reference = `${bookName} Chapter ${favorite.chapter}`;
|
||||
} else {
|
||||
return bookName;
|
||||
reference = bookName;
|
||||
}
|
||||
|
||||
return `${reference} (${versionAbbrev})`;
|
||||
};
|
||||
|
||||
// Organize favorites by type
|
||||
|
||||
Reference in New Issue
Block a user