This commit is contained in:
nmemmert
2026-06-08 16:47:21 -04:00
parent 3e1b1e0fae
commit 9ad24df626
6 changed files with 192 additions and 27 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "siteforge",
"runtimeExecutable": "node",
"runtimeArgs": ["--env-file=.env", "server.js"],
"port": 4173
}
]
}
@@ -2,7 +2,8 @@
"byStudy": {
"colossians": {
"completedSectionIds": [
"1-1-2"
"1-1-2",
"1-3-8"
],
"quizAnswers": {
"1-1-2": [
@@ -12,5 +13,5 @@
}
}
},
"updatedAt": "2026-06-03T18:15:19.898Z"
"updatedAt": "2026-06-08T20:30:19.689Z"
}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"users": {},
"updatedAt": "2026-06-08T18:21:54.358Z"
"updatedAt": "2026-06-08T20:46:20.016Z"
}
+25 -5
View File
@@ -9,8 +9,15 @@
"studyRemindersEnabled": false,
"enrolledStudySlugs": [],
"avatarUrl": "",
"createdAt": null,
"updatedAt": null,
"lastLoginAt": "2026-05-21T15:41:16.259Z",
"pendingEmailChange": null
"pendingEmailChange": null,
"twoFaMethod": null,
"totpSecret": null,
"totpVerified": false,
"totpEnabledAt": null,
"totpRecoveryCodes": []
},
{
"id": "3307bdd7-a770-4fad-a28e-cf2d0c2a6e34",
@@ -23,9 +30,15 @@
"colossians"
],
"avatarUrl": "data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2288%22%20height%3D%2288%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20rx%3D%2218%22%20ry%3D%2218%22%20fill%3D%22%2311100d%22%2F%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20dominant-baseline%3D%22middle%22%20text-anchor%3D%22middle%22%20font-size%3D%2256%22%3E%F0%9F%A6%81%3C%2Ftext%3E%3C%2Fsvg%3E",
"lastLoginAt": "2026-06-03T17:47:53.877Z",
"createdAt": null,
"updatedAt": "2026-06-08T20:46:30.804Z",
"lastLoginAt": "2026-06-08T20:46:30.804Z",
"pendingEmailChange": null,
"updatedAt": "2026-06-03T17:47:59.104Z",
"twoFaMethod": null,
"totpSecret": null,
"totpVerified": false,
"totpEnabledAt": null,
"totpRecoveryCodes": [],
"totpSecretPending": "FGR7UI5TWA4HB4DURCXEUXATTVVVJDJ5"
},
{
@@ -37,9 +50,16 @@
"studyRemindersEnabled": false,
"enrolledStudySlugs": [],
"avatarUrl": "data:image/svg+xml;utf8,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"88\" height=\"88\"%3E%3Crect width=\"100%\" height=\"100%\" rx=\"18\" ry=\"18\" fill=\"%2311100d\"/%3E%3Ctext x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" font-size=\"56\"%3E🐱%3C/text%3E%3C/svg%3E",
"createdAt": null,
"updatedAt": null,
"lastLoginAt": "2026-06-02T17:33:13.669Z",
"pendingEmailChange": null
"pendingEmailChange": null,
"twoFaMethod": null,
"totpSecret": null,
"totpVerified": false,
"totpEnabledAt": null,
"totpRecoveryCodes": []
}
],
"updatedAt": "2026-06-03T17:47:59.104Z"
"updatedAt": "2026-06-08T20:46:30.806Z"
}
+98 -2
View File
@@ -1650,10 +1650,14 @@
padding: 3.75rem 0 1.75rem;
}
.section-study-classroom .section-inner {
max-width: 1320px;
}
.study-classroom-shell {
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(260px, 0.85fr);
gap: 1rem;
grid-template-columns: minmax(0, 1.55fr) minmax(300px, 0.9fr);
gap: 1.75rem;
align-items: start;
}
@@ -1732,6 +1736,98 @@
margin-top: 0.5rem;
}
.study-track-switcher {
margin: 1rem 0;
}
.study-track-switcher-tabs {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}
.study-track-tab {
display: inline-block;
padding: 0.45rem 1rem;
border-radius: 999px;
border: 1px solid rgba(201, 168, 76, 0.3);
color: var(--brand-muted);
font-size: 0.85rem;
font-family: 'Barlow', sans-serif;
text-decoration: none;
transition: background 0.18s, color 0.18s, border-color 0.18s;
}
.study-track-tab:hover {
background: rgba(201, 168, 76, 0.1);
border-color: rgba(201, 168, 76, 0.6);
color: #e8d8b0;
}
.study-track-tab--active {
background: rgba(201, 168, 76, 0.18);
border-color: var(--brand-gold);
color: var(--brand-gold);
font-weight: 600;
pointer-events: none;
}
.study-notebook {
margin-top: 1rem;
}
.study-notebook-tabs {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
padding-left: 0.5rem;
}
.study-notebook-tab {
background: rgba(18, 18, 16, 0.6);
border: 1px solid rgba(201, 168, 76, 0.18);
border-bottom: none;
color: var(--brand-muted);
padding: 0.55rem 1.1rem;
border-radius: 10px 10px 0 0;
cursor: pointer;
font-size: 0.82rem;
font-family: 'Barlow', sans-serif;
transition: background 0.18s, color 0.18s;
position: relative;
top: 1px;
}
.study-notebook-tab:hover {
background: rgba(201, 168, 76, 0.1);
color: #e8d8b0;
}
.study-notebook-tab--active {
background: rgba(18, 18, 16, 0.86);
border-color: rgba(201, 168, 76, 0.35);
color: var(--brand-gold);
font-weight: 600;
}
.study-notebook-page {
padding: 1.25rem;
border-radius: 0 12px 12px 12px;
border: 1px solid rgba(201, 168, 76, 0.35);
background: rgba(18, 18, 16, 0.86);
}
.study-notebook-page h3 {
margin: 0.2rem 0 0;
font-family: var(--brand-font-heading);
color: var(--brand-warm-white);
}
.study-notebook-page .study-module-focus {
margin-top: 1rem;
}
.study-classroom-sidebar {
position: sticky;
top: 5.75rem;
+53 -16
View File
@@ -1965,6 +1965,7 @@ export function ColossiansStudyNotesPage({ content }: Props) {
const [notes, setNotes] = useState<StudyNotesMap>({})
const [loading, setLoading] = useState(true)
const [message, setMessage] = useState('')
const [activeNoteTab, setActiveNoteTab] = useState('')
useEffect(() => {
document.title = study ? `My Notes | ${study.title}` : 'My Study Notes'
@@ -2039,6 +2040,10 @@ export function ColossiansStudyNotesPage({ content }: Props) {
})
.filter(item => item.note && item.note.trim())
const enrolledStudies = !loading && auth.authenticated
? getStudies(content).filter(s => (auth.enrolledStudySlugs ?? []).includes(s.slug.toLowerCase()))
: []
return (
<main className="study-section-page" aria-label="My study notes">
<section className="section-study-classroom">
@@ -2047,6 +2052,22 @@ export function ColossiansStudyNotesPage({ content }: Props) {
<p className="study-lesson-label">Student Workspace</p>
<h1>My Lesson Notes</h1>
{!loading && auth.authenticated && <p className="study-detail-summary">Signed in as {auth.username}</p>}
{enrolledStudies.length > 1 && (
<div className="study-track-switcher">
<p className="study-lesson-label">Switch Track</p>
<div className="study-track-switcher-tabs">
{enrolledStudies.map(s => (
<Link
key={s.slug}
to={`/study/${s.slug}/notes`}
className={`study-track-tab${s.slug === study.slug ? ' study-track-tab--active' : ''}`}
>
{s.title}
</Link>
))}
</div>
</div>
)}
{!loading && auth.authenticated && (
<div className="study-signup-actions" style={{ marginBottom: '1rem' }}>
<Link to="/study/account" className="btn-secondary">My Account</Link>
@@ -2076,24 +2097,40 @@ export function ColossiansStudyNotesPage({ content }: Props) {
{study.sections[0] && <Link to={`/study/${study.slug}/${study.sections[0].id}`} className="btn-primary">Open First Lesson</Link>}
</article>
)}
{!loading && auth.authenticated && enrolled && entries.length > 0 && (
<div className="study-module-list">
{entries.map(({ sectionId, section, note }) => (
<article key={sectionId} className="study-module-row">
<div className="study-module-row-left">
<p className="study-module-lesson">{section ? `Lesson ${getLessonNumber(study.sections, section.id)}` : 'Saved Note'}</p>
<h3>{section?.title ?? sectionId}</h3>
<p>{section ? section.reference : 'Lesson reference unavailable'}</p>
</div>
<div className="study-module-row-right">
<p className="study-module-focus">Your Note</p>
<p className="study-detail-copy study-detail-copy--scripture">{note}</p>
{section && <Link to={`/study/${study.slug}/${section.id}`} className="btn-secondary">Open Lesson</Link>}
</div>
</article>
{!loading && auth.authenticated && enrolled && entries.length > 0 && (() => {
const sorted = [...entries].sort((a, b) => {
const aNum = a.section ? getLessonNumber(study.sections, a.section.id) : 0
const bNum = b.section ? getLessonNumber(study.sections, b.section.id) : 0
return aNum - bNum
})
const active = sorted.find(item => item.sectionId === activeNoteTab) ?? sorted[0]
return (
<div className="study-notebook">
<div className="study-notebook-tabs" role="tablist" aria-label="Lesson notes">
{sorted.map(({ sectionId, section }) => (
<button
key={sectionId}
type="button"
role="tab"
aria-selected={active.sectionId === sectionId}
className={`study-notebook-tab${active.sectionId === sectionId ? ' study-notebook-tab--active' : ''}`}
onClick={() => setActiveNoteTab(sectionId)}
>
{section ? `Lesson ${getLessonNumber(study.sections, section.id)}` : 'Saved Note'}
</button>
))}
</div>
)}
<article className="study-notebook-page">
<p className="study-module-lesson">{active.section ? `Lesson ${getLessonNumber(study.sections, active.section.id)}` : 'Saved Note'}</p>
<h3>{active.section?.title ?? active.sectionId}</h3>
<p className="study-detail-reference">{active.section ? active.section.reference : 'Lesson reference unavailable'}</p>
<p className="study-module-focus">Your Note</p>
<p className="study-detail-copy study-detail-copy--scripture">{active.note}</p>
{active.section && <Link to={`/study/${study.slug}/${active.section.id}`} className="btn-secondary">Open Lesson</Link>}
</article>
</div>
)
})()}
{message && <p className="study-note-status">{message}</p>}
</div>
</section>