Skip to content

Commit 7411e1a

Browse files
committed
courses
1 parent 7eb3187 commit 7411e1a

File tree

6 files changed

+160
-109
lines changed

6 files changed

+160
-109
lines changed

app/models/bible.server.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { promises as fs } from 'fs';
22
import { join } from 'path';
3+
import { prisma } from "~/db.server";
34

45
interface BookChapter {
56
book: string;
@@ -21,40 +22,49 @@ interface BookData {
2122
chapters: ChapterData[];
2223
}
2324

24-
2525
export async function getBibleData() {
2626
try {
27-
// Paths to JSON files (adjust based on your project structure)
2827
const booksPath = join(process.cwd(), 'app', 'bible_data', 'book.chapters.json');
2928
const kjvPath = join(process.cwd(), 'app', 'bible_data', 'kjv.json');
3029

31-
// Read JSON files
3230
const booksContent = await fs.readFile(booksPath, 'utf8');
3331
const kjvContent = await fs.readFile(kjvPath, 'utf8');
3432

35-
// Parse JSON for books
3633
const booksArray: BookChapter[] = JSON.parse(booksContent);
3734
const books = booksArray.reduce((acc: { [book: string]: number }, bookInfo: BookChapter) => {
3835
acc[bookInfo.book] = bookInfo.chapters;
3936
return acc;
40-
}, {}); // Should be { "Genesis": 50, "Exodus": 40, ... }
37+
}, {});
4138

42-
// Parse JSON for KJV and transform to desired format
4339
const kjvArray: BookData[] = JSON.parse(kjvContent);
44-
const kjv: Record<string, Record<string, string>> = kjvArray.reduce((booksAcc, bookData) => { // Added type annotation here
45-
const chapters: Record<string, string> = bookData.chapters.reduce((chaptersAcc, chapterData) => { // Added type annotation here
40+
const kjv: Record<string, Record<string, string>> = kjvArray.reduce((booksAcc, bookData) => {
41+
const chapters: Record<string, string> = bookData.chapters.reduce((chaptersAcc, chapterData) => {
4642
const versesText = chapterData.verses.map(verse => `${verse.verse}. ${verse.text}`).join('\n');
4743
chaptersAcc[chapterData.chapter] = versesText;
4844
return chaptersAcc;
49-
}, {} as Record<string, string>); // Added initial value type hint here
45+
}, {} as Record<string, string>);
5046
booksAcc[bookData.book] = chapters;
5147
return booksAcc;
52-
}, {} as Record<string, Record<string, string>>); // Should be { "Genesis": { "1": "text...", ... }, ... }
53-
48+
}, {} as Record<string, Record<string, string>>);
5449

5550
return { books, kjv };
5651
} catch (error) {
5752
console.error('Error loading Bible data:', error);
5853
throw new Error('Failed to load Bible data');
5954
}
55+
}
56+
57+
export async function recordBibleReading({ userId, book, chapter }: { userId: string; book: string; chapter: string }) {
58+
try {
59+
await prisma.bibleReading.create({
60+
data: {
61+
userId,
62+
book,
63+
chapter,
64+
},
65+
});
66+
} catch (error) {
67+
console.error('Error recording Bible reading:', error);
68+
throw new Error('Failed to record Bible reading');
69+
}
6070
}

app/root.tsx

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from "@remix-run/react";
1515

1616
import { getUser } from "~/session.server";
17-
import { useUser } from "~/utils";
17+
import { useOptionalUser } from "~/utils";
1818
import stylesheet from "~/tailwind.css";
1919
import React, { useState } from 'react';
2020

@@ -31,7 +31,7 @@ export default function App() {
3131
const [drawerOpen, setDrawerOpen] = useState(false);
3232
const [profilePopupOpen, setProfilePopupOpen] = useState(false);
3333
const data = useLoaderData<typeof loader>();
34-
const user = useUser();
34+
const user = useOptionalUser();
3535

3636
return (
3737
<html lang="en" className="h-full">
@@ -54,43 +54,50 @@ export default function App() {
5454
</h1>
5555
</div>
5656
<div className="relative">
57-
<button
58-
onClick={() => setProfilePopupOpen(!profilePopupOpen)}
59-
className="text-white hover:text-gray-300 focus:outline-none"
60-
>
61-
<svg className="h-6 w-6 fill-current" viewBox="0 0 24 24">
62-
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/>
63-
</svg>
64-
</button>
65-
66-
{profilePopupOpen && (
67-
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-60">
68-
<div className="px-4 py-2 text-sm text-gray-700">
69-
Signed in as: {user?.email}
70-
</div>
71-
<Link
72-
to="/profile"
73-
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
74-
onClick={() => setProfilePopupOpen(false)}
75-
>
76-
Profile
77-
</Link>
78-
<Link
79-
to="/settings"
80-
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
81-
onClick={() => setProfilePopupOpen(false)}
57+
{user ? (
58+
<>
59+
<button
60+
onClick={() => setProfilePopupOpen(!profilePopupOpen)}
61+
className="text-white hover:text-gray-300 focus:outline-none"
8262
>
83-
Settings
84-
</Link>
85-
<Form action="/logout" method="post" className="mt-2">
86-
<button
87-
type="submit"
88-
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
89-
>
90-
Logout
91-
</button>
92-
</Form>
93-
</div>
63+
<svg className="h-6 w-6 fill-current" viewBox="0 0 24 24">
64+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/>
65+
</svg>
66+
</button>
67+
{profilePopupOpen && (
68+
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-60">
69+
<div className="px-4 py-2 text-sm text-gray-700">
70+
Signed in as: {user.email}
71+
</div>
72+
<Link
73+
to="/profile"
74+
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
75+
onClick={() => setProfilePopupOpen(false)}
76+
>
77+
Profile
78+
</Link>
79+
<Link
80+
to="/settings"
81+
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
82+
onClick={() => setProfilePopupOpen(false)}
83+
>
84+
Settings
85+
</Link>
86+
<Form action="/logout" method="post" className="mt-2">
87+
<button
88+
type="submit"
89+
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
90+
>
91+
Logout
92+
</button>
93+
</Form>
94+
</div>
95+
)}
96+
</>
97+
) : (
98+
<Link to="/login" className="text-white hover:text-gray-300">
99+
Login
100+
</Link>
94101
)}
95102
</div>
96103
</header>

app/routes/bible.tsx

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import type { LoaderFunctionArgs } from "@remix-run/node";
1+
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
22
import { json } from "@remix-run/node";
33
import { Form, Link, NavLink, Outlet, useLoaderData } from "@remix-run/react";
44
import { requireUserId } from "~/session.server";
55
import { useUser } from "~/utils";
6-
import { getBibleData } from "~/models/bible.server";
6+
import { getBibleData, recordBibleReading } from "~/models/bible.server";
77
import React, { useState } from 'react';
88

99
// Define types for Bible data
@@ -15,56 +15,73 @@ interface BibleData {
1515
export const loader = async ({ request }: LoaderFunctionArgs) => {
1616
const userId = await requireUserId(request);
1717
const bibleData = await getBibleData();
18-
return json({ userId, bibleData });
18+
const url = new URL(request.url);
19+
const selectedBook = url.searchParams.get("book") || Object.keys(bibleData.books)[0] || "Genesis";
20+
const selectedChapter = url.searchParams.get("chapter") || "1";
21+
return json({ userId, bibleData, selectedBook, selectedChapter });
22+
};
23+
24+
export const action = async ({ request }: ActionFunctionArgs) => {
25+
const userId = await requireUserId(request);
26+
const formData = await request.formData();
27+
const book = formData.get("book") as string;
28+
const chapter = formData.get("chapter") as string;
29+
30+
if (book && chapter) {
31+
await recordBibleReading({ userId, book, chapter });
32+
}
33+
34+
return null; // No redirect needed; we stay on the page
1935
};
2036

2137
export default function BiblePage() {
22-
const { bibleData } = useLoaderData<typeof loader>();
38+
const { userId, bibleData, selectedBook: initialBook, selectedChapter: initialChapter } = useLoaderData<typeof loader>();
2339
const user = useUser();
24-
const [selectedBook, setSelectedBook] = useState<string>(Object.keys(bibleData.books)[0] || ""); // Default to first book
25-
const [selectedChapter, setSelectedChapter] = useState<string>("1"); // Default to chapter 1
40+
const [selectedBook, setSelectedBook] = useState<string>(initialBook);
41+
const [selectedChapter, setSelectedChapter] = useState<string>(initialChapter);
2642

2743
const books = Object.keys(bibleData.books) as string[];
2844
const chapters = Array.from({ length: bibleData.books[selectedBook] || 0 }, (_, i) => (i + 1).toString());
2945

3046
const bibleText: string = bibleData.kjv[selectedBook]?.[selectedChapter] || "Select a book and chapter to view text.";
3147

32-
// Split the text into paragraphs (assuming paragraphs are separated by double newlines `\n\n`)
48+
// Split the text into paragraphs
3349
const paragraphs = bibleText.split('\n\n').map(paragraph => paragraph.trim()).filter(paragraph => paragraph.length > 0);
3450

3551
return (
3652
<div className="flex h-full min-h-screen flex-col">
37-
{/* Main container with padding-top to account for header */}
3853
<main className="flex-1 flex flex-col h-full bg-white" style={{ paddingTop: '64px' }}>
39-
{/* Picker container fixed below header, ensuring it doesn't overlap nav drawer */}
4054
<div className="fixed top-16 left-0 right-0 bg-white shadow-md z-30 p-4 border-b" style={{ zIndex: 30 }}>
41-
<div className="flex flex-col md:flex-row gap-4 items-center justify-center">
42-
{/* Book Selector */}
43-
<select
44-
value={selectedBook}
45-
onChange={(e) => setSelectedBook(e.target.value)}
55+
<Form method="post" className="flex flex-col md:flex-row gap-4 items-center justify-center">
56+
<select
57+
name="book"
58+
value={selectedBook}
59+
onChange={(e) => {
60+
setSelectedBook(e.target.value);
61+
e.target.form?.requestSubmit(); // Submit form on change
62+
}}
4663
className="w-full md:w-auto p-2 border rounded"
4764
>
4865
{books.map((book) => (
4966
<option key={book} value={book}>{book}</option>
5067
))}
5168
</select>
52-
53-
{/* Chapter Selector */}
54-
<select
55-
value={selectedChapter}
56-
onChange={(e) => setSelectedChapter(e.target.value)}
69+
<select
70+
name="chapter"
71+
value={selectedChapter}
72+
onChange={(e) => {
73+
setSelectedChapter(e.target.value);
74+
e.target.form?.requestSubmit(); // Submit form on change
75+
}}
5776
className="w-full md:w-auto p-2 border rounded"
5877
>
5978
{chapters.map((chapter) => (
6079
<option key={chapter} value={chapter}>Chapter {chapter}</option>
6180
))}
6281
</select>
63-
</div>
82+
</Form>
6483
</div>
65-
66-
{/* Main content area - adjusts for picker height, displays Bible text */}
67-
<div className="flex-1 p-6" style={{ paddingTop: '120px' }}> {/* Increased padding to account for picker */}
84+
<div className="flex-1 p-6" style={{ paddingTop: '120px' }}>
6885
<h1 className="text-2xl font-bold mb-4">{selectedBook} Chapter {selectedChapter}</h1>
6986
<div className="prose max-w-none">
7087
{paragraphs.length > 0 ? (

app/routes/portal.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ import { json } from "@remix-run/node";
33
import { Form, Link, Outlet, useLoaderData } from "@remix-run/react";
44
import { requireUserId } from "~/session.server";
55
import { useUser } from "~/utils";
6-
import { prisma } from "~/db.server"; // Import Prisma client
6+
import { prisma } from "~/db.server";
77
import React from 'react';
88

99
export const loader = async ({ request }: LoaderFunctionArgs) => {
1010
const userId = await requireUserId(request);
1111
const noteCount = await prisma.note.count({ where: { userId } });
1212
const courseCount = await prisma.course.count({ where: { userId } });
13-
return json({ userId, noteCount, courseCount });
13+
const recentReading = await prisma.bibleReading.findFirst({
14+
where: { userId },
15+
orderBy: { createdAt: 'desc' },
16+
select: { book: true, chapter: true },
17+
});
18+
return json({ userId, noteCount, courseCount, recentReading });
1419
};
1520

1621
export default function PortalPage() {
@@ -20,35 +25,33 @@ export default function PortalPage() {
2025
return (
2126
<div className="flex h-full min-h-screen flex-col">
2227
<main className="flex-1 flex flex-col bg-white" style={{ paddingTop: '64px' }}>
23-
{/* Dashboard Content */}
2428
<div className="flex-1 p-6 overflow-y-auto" style={{ marginTop: '64px' }}>
2529
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
26-
{/* Quick Access Cards */}
2730
<div className="bg-white p-4 rounded-lg shadow-md hover:shadow-lg transition-shadow">
2831
<h3 className="text-lg font-semibold mb-2">Recent Bible Readings</h3>
29-
<p className="text-gray-600">View your latest Bible chapters and verses.</p>
32+
<p className="text-gray-600">
33+
{data.recentReading
34+
? `Last read: ${data.recentReading.book} Chapter ${data.recentReading.chapter}`
35+
: "No recent readings yet."}
36+
</p>
3037
<Link to="/bible" className="text-blue-500 hover:text-blue-700 mt-2 block">
3138
Go to Bible →
3239
</Link>
3340
</div>
34-
3541
<div className="bg-white p-4 rounded-lg shadow-md hover:shadow-lg transition-shadow">
3642
<h3 className="text-lg font-semibold mb-2">Recent Notes</h3>
3743
<p className="text-gray-600">Check your most recent notes.</p>
3844
<Link to="/notes" className="text-blue-500 hover:text-blue-700 mt-2 block">
3945
Go to Notes →
4046
</Link>
4147
</div>
42-
4348
<div className="bg-white p-4 rounded-lg shadow-md hover:shadow-lg transition-shadow">
4449
<h3 className="text-lg font-semibold mb-2">Active Courses</h3>
4550
<p className="text-gray-600">Manage your current courses.</p>
4651
<Link to="/courses" className="text-blue-500 hover:text-blue-700 mt-2 block">
4752
Go to Courses →
4853
</Link>
4954
</div>
50-
51-
{/* Stats or Metrics Card */}
5255
<div className="bg-white p-4 rounded-lg shadow-md hover:shadow-lg transition-shadow col-span-1 md:col-span-2 lg:col-span-3">
5356
<h3 className="text-lg font-semibold mb-2">Dashboard Overview</h3>
5457
<p className="text-gray-600">Track your progress and activity across all tools.</p>
@@ -64,7 +67,7 @@ export default function PortalPage() {
6467
</div>
6568
</div>
6669
</div>
67-
<Outlet /> {/* Renders nested routes if any */}
70+
<Outlet />
6871
</div>
6972
</main>
7073
</div>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- CreateTable
2+
CREATE TABLE "BibleReading" (
3+
"id" TEXT NOT NULL PRIMARY KEY,
4+
"book" TEXT NOT NULL,
5+
"chapter" TEXT NOT NULL,
6+
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
7+
"userId" TEXT NOT NULL,
8+
CONSTRAINT "BibleReading_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
9+
);
10+
11+
-- CreateIndex
12+
CREATE INDEX "BibleReading_userId_createdAt_idx" ON "BibleReading"("userId", "createdAt");

0 commit comments

Comments
 (0)