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:
Ryderjj89
2025-09-28 12:13:37 -04:00
parent 5f832aecd4
commit ceeb465c8d
1199 changed files with 187 additions and 86 deletions

View File

@@ -1,5 +1,5 @@
{
"name": "esv-bible-frontend",
"name": "the-bible-frontend",
"version": "1.0.0",
"private": true,
"dependencies": {

View File

@@ -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 */}

View File

@@ -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);

View File

@@ -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