From 1952a412edcd04b332655199737c340a4389d174 Mon Sep 17 00:00:00 2001
From: Thomas Hallock
Date: Mon, 22 Dec 2025 13:31:04 -0600
Subject: [PATCH] feat(classroom): implement enrollment system (Phase 4)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add EnrollChildFlow component for parents to enroll children
- Add enrollment hooks (useEnrolledStudents, useCreateEnrollmentRequest, etc.)
- Update StudentManagerTab with enrolled students and pending requests
- Add "Enroll in another classroom" option for teachers who are parents
- Fix critical auth bug: convert guestId to user.id in all classroom API routes
The API routes were incorrectly using viewerId (guestId from cookie) when
they should have been using users.id. This caused 403 errors for all
classroom operations. Fixed by adding getOrCreateUser() to convert
guestId to proper user.id before calling business logic functions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5
---
.claude/settings.local.json | 8 +-
.../[requestId]/approve/route.ts | 23 +-
.../[requestId]/deny/route.ts | 23 +-
.../enrollment-requests/route.ts | 28 +-
.../enrollments/[playerId]/route.ts | 23 +-
.../[classroomId]/enrollments/route.ts | 21 +-
.../presence/[playerId]/route.ts | 23 +-
.../[classroomId]/presence/route.ts | 28 +-
.../app/api/classrooms/[classroomId]/route.ts | 26 +-
apps/web/src/app/practice/PracticeClient.tsx | 113 +++-
.../classroom/ClassroomDashboard.tsx | 71 ++-
.../components/classroom/EnrollChildFlow.tsx | 441 ++++++++++++++
.../classroom/StudentManagerTab.tsx | 552 +++++++++++++++---
apps/web/src/components/classroom/index.ts | 1 +
apps/web/src/hooks/useClassroom.ts | 207 ++++++-
15 files changed, 1465 insertions(+), 123 deletions(-)
create mode 100644 apps/web/src/components/classroom/EnrollChildFlow.tsx
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index c4005ce8..4a8b1b0c 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -254,7 +254,13 @@
"Bash(mcp__sqlite__describe_table:*)",
"Bash(git diff:*)",
"Bash(git show:*)",
- "Bash(npx tsx:*)"
+ "Bash(npx tsx:*)",
+ "Bash(xargs ls:*)",
+ "Bash(mcp__sqlite__list_tables)",
+ "WebFetch(domain:developer.chrome.com)",
+ "Bash(claude mcp add:*)",
+ "Bash(claude mcp:*)",
+ "Bash(git rev-parse:*)"
],
"deny": [],
"ask": []
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/approve/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/approve/route.ts
index 32f4dc11..6e6ed946 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/approve/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/approve/route.ts
@@ -1,7 +1,25 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import { approveEnrollmentRequest, getTeacherClassroom } from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string; requestId: string }>
}
@@ -16,14 +34,15 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId, requestId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
// Verify user is the teacher of this classroom
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
if (!classroom || classroom.id !== classroomId) {
return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
}
- const result = await approveEnrollmentRequest(requestId, viewerId, 'teacher')
+ const result = await approveEnrollmentRequest(requestId, user.id, 'teacher')
return NextResponse.json({
request: result.request,
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/deny/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/deny/route.ts
index 7d1b1d88..cd7c5ab5 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/deny/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/[requestId]/deny/route.ts
@@ -1,7 +1,25 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import { denyEnrollmentRequest, getTeacherClassroom } from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string; requestId: string }>
}
@@ -16,14 +34,15 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId, requestId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
// Verify user is the teacher of this classroom
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
if (!classroom || classroom.id !== classroomId) {
return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
}
- const request = await denyEnrollmentRequest(requestId, viewerId, 'teacher')
+ const request = await denyEnrollmentRequest(requestId, user.id, 'teacher')
return NextResponse.json({ request })
} catch (error) {
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/route.ts
index 89bdd139..c2a15bfa 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/enrollment-requests/route.ts
@@ -1,4 +1,6 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import {
createEnrollmentRequest,
getPendingRequestsForClassroom,
@@ -7,6 +9,22 @@ import {
} from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string }>
}
@@ -21,9 +39,10 @@ export async function GET(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
// Verify user is the teacher of this classroom
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
if (!classroom || classroom.id !== classroomId) {
return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
}
@@ -48,6 +67,7 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
const body = await req.json()
if (!body.playerId) {
@@ -55,9 +75,9 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
}
// Determine role: is user the teacher or a parent?
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
const isTeacher = classroom?.id === classroomId
- const parentCheck = await isParent(viewerId, body.playerId)
+ const parentCheck = await isParent(user.id, body.playerId)
if (!isTeacher && !parentCheck) {
return NextResponse.json(
@@ -71,7 +91,7 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
const request = await createEnrollmentRequest({
classroomId,
playerId: body.playerId,
- requestedBy: viewerId,
+ requestedBy: user.id,
requestedByRole,
})
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/enrollments/[playerId]/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/enrollments/[playerId]/route.ts
index fd77774e..c34bf220 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/enrollments/[playerId]/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/enrollments/[playerId]/route.ts
@@ -1,7 +1,25 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import { unenrollStudent, getTeacherClassroom, isParent } from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string; playerId: string }>
}
@@ -16,11 +34,12 @@ export async function DELETE(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId, playerId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
// Check authorization: must be teacher of classroom OR parent of student
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
const isTeacher = classroom?.id === classroomId
- const parentCheck = await isParent(viewerId, playerId)
+ const parentCheck = await isParent(user.id, playerId)
if (!isTeacher && !parentCheck) {
return NextResponse.json(
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/enrollments/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/enrollments/route.ts
index ec5dcc7e..c532cb76 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/enrollments/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/enrollments/route.ts
@@ -1,7 +1,25 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import { getEnrolledStudents, getTeacherClassroom } from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string }>
}
@@ -16,9 +34,10 @@ export async function GET(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
// Verify user is the teacher of this classroom
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
if (!classroom || classroom.id !== classroomId) {
return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
}
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/presence/[playerId]/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/presence/[playerId]/route.ts
index 41a53f5f..62eed5e4 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/presence/[playerId]/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/presence/[playerId]/route.ts
@@ -1,7 +1,25 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import { leaveSpecificClassroom, getTeacherClassroom, isParent } from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string; playerId: string }>
}
@@ -16,11 +34,12 @@ export async function DELETE(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId, playerId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
// Check authorization: must be teacher of classroom OR parent of student
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
const isTeacher = classroom?.id === classroomId
- const parentCheck = await isParent(viewerId, playerId)
+ const parentCheck = await isParent(user.id, playerId)
if (!isTeacher && !parentCheck) {
return NextResponse.json(
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/presence/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/presence/route.ts
index e56bc141..8d28cf59 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/presence/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/presence/route.ts
@@ -1,4 +1,6 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import {
enterClassroom,
getClassroomPresence,
@@ -7,6 +9,22 @@ import {
} from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string }>
}
@@ -21,9 +39,10 @@ export async function GET(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
// Verify user is the teacher of this classroom
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
if (!classroom || classroom.id !== classroomId) {
return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
}
@@ -55,6 +74,7 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
const body = await req.json()
if (!body.playerId) {
@@ -62,9 +82,9 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
}
// Check authorization: must be teacher of classroom OR parent of student
- const classroom = await getTeacherClassroom(viewerId)
+ const classroom = await getTeacherClassroom(user.id)
const isTeacher = classroom?.id === classroomId
- const parentCheck = await isParent(viewerId, body.playerId)
+ const parentCheck = await isParent(user.id, body.playerId)
if (!isTeacher && !parentCheck) {
return NextResponse.json(
@@ -76,7 +96,7 @@ export async function POST(req: NextRequest, { params }: RouteParams) {
const result = await enterClassroom({
playerId: body.playerId,
classroomId,
- enteredBy: viewerId,
+ enteredBy: user.id,
})
if (!result.success) {
diff --git a/apps/web/src/app/api/classrooms/[classroomId]/route.ts b/apps/web/src/app/api/classrooms/[classroomId]/route.ts
index ebe277ec..48b28647 100644
--- a/apps/web/src/app/api/classrooms/[classroomId]/route.ts
+++ b/apps/web/src/app/api/classrooms/[classroomId]/route.ts
@@ -1,4 +1,6 @@
+import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
+import { db, schema } from '@/db'
import {
deleteClassroom,
getClassroom,
@@ -7,6 +9,22 @@ import {
} from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
+/**
+ * Get or create user record for a viewerId (guestId)
+ */
+async function getOrCreateUser(viewerId: string) {
+ let user = await db.query.users.findFirst({
+ where: eq(schema.users.guestId, viewerId),
+ })
+
+ if (!user) {
+ const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
+ user = newUser
+ }
+
+ return user
+}
+
interface RouteParams {
params: Promise<{ classroomId: string }>
}
@@ -45,11 +63,12 @@ export async function PATCH(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
const body = await req.json()
// Handle code regeneration separately
if (body.regenerateCode) {
- const newCode = await regenerateClassroomCode(classroomId, viewerId)
+ const newCode = await regenerateClassroomCode(classroomId, user.id)
if (!newCode) {
return NextResponse.json(
{ error: 'Not authorized or classroom not found' },
@@ -69,7 +88,7 @@ export async function PATCH(req: NextRequest, { params }: RouteParams) {
return NextResponse.json({ error: 'No valid updates provided' }, { status: 400 })
}
- const classroom = await updateClassroom(classroomId, viewerId, updates)
+ const classroom = await updateClassroom(classroomId, user.id, updates)
if (!classroom) {
return NextResponse.json({ error: 'Not authorized or classroom not found' }, { status: 403 })
@@ -92,8 +111,9 @@ export async function DELETE(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
+ const user = await getOrCreateUser(viewerId)
- const success = await deleteClassroom(classroomId, viewerId)
+ const success = await deleteClassroom(classroomId, user.id)
if (!success) {
return NextResponse.json({ error: 'Not authorized or classroom not found' }, { status: 403 })
diff --git a/apps/web/src/app/practice/PracticeClient.tsx b/apps/web/src/app/practice/PracticeClient.tsx
index 1a9e7c5e..65151efb 100644
--- a/apps/web/src/app/practice/PracticeClient.tsx
+++ b/apps/web/src/app/practice/PracticeClient.tsx
@@ -3,7 +3,7 @@
import { useRouter } from 'next/navigation'
import { useCallback, useMemo, useState } from 'react'
import { Z_INDEX } from '@/constants/zIndex'
-import { ClassroomDashboard, CreateClassroomForm } from '@/components/classroom'
+import { ClassroomDashboard, CreateClassroomForm, EnrollChildFlow } from '@/components/classroom'
import { PageWithNav } from '@/components/PageWithNav'
import { StudentFilterBar } from '@/components/practice/StudentFilterBar'
import { StudentSelector, type StudentWithProgress } from '@/components/practice'
@@ -34,6 +34,7 @@ export function PracticeClient({ initialPlayers }: PracticeClientProps) {
// Classroom state - check if user is a teacher
const { data: classroom, isLoading: isLoadingClassroom } = useMyClassroom()
const [showCreateClassroom, setShowCreateClassroom] = useState(false)
+ const [showEnrollChild, setShowEnrollChild] = useState(false)
// Filter state
const [searchQuery, setSearchQuery] = useState('')
@@ -190,6 +191,15 @@ export function PracticeClient({ initialPlayers }: PracticeClientProps) {
setShowCreateClassroom(false)
}, [])
+ // Handle enrollment flow
+ const handleEnrollChild = useCallback(() => {
+ setShowEnrollChild(true)
+ }, [])
+
+ const handleCloseEnrollChild = useCallback(() => {
+ setShowEnrollChild(false)
+ }, [])
+
// If user is a teacher, show the classroom dashboard
if (classroom) {
return (
@@ -235,6 +245,31 @@ export function PracticeClient({ initialPlayers }: PracticeClientProps) {
)
}
+ // Show enroll child flow if requested
+ if (showEnrollChild) {
+ return (
+
+
+
+
+
+ )
+ }
+
// Parent view - show student list with filter bar
return (
@@ -295,31 +330,67 @@ export function PracticeClient({ initialPlayers }: PracticeClientProps) {
Build your soroban skills one step at a time
- {/* Become a Teacher option */}
+ {/* Parent/Teacher options */}
{!isLoadingClassroom && !classroom && (
-
+ {/* Enroll in Classroom option - for parents with a teacher */}
+ {players.length > 0 && (
+
+ )}
+
+ {/* Become a Teacher option */}
+
+
)}
diff --git a/apps/web/src/components/classroom/ClassroomDashboard.tsx b/apps/web/src/components/classroom/ClassroomDashboard.tsx
index 4ceb6b83..6238c565 100644
--- a/apps/web/src/components/classroom/ClassroomDashboard.tsx
+++ b/apps/web/src/components/classroom/ClassroomDashboard.tsx
@@ -6,6 +6,7 @@ import { useTheme } from '@/contexts/ThemeContext'
import { css } from '../../../styled-system/css'
import { ClassroomCodeShare } from './ClassroomCodeShare'
import { ClassroomTab } from './ClassroomTab'
+import { EnrollChildFlow } from './EnrollChildFlow'
import { StudentManagerTab } from './StudentManagerTab'
type TabId = 'classroom' | 'students'
@@ -29,6 +30,7 @@ export function ClassroomDashboard({ classroom, ownChildren = [] }: ClassroomDas
const { resolvedTheme } = useTheme()
const isDark = resolvedTheme === 'dark'
const [activeTab, setActiveTab] = useState('classroom')
+ const [showEnrollChild, setShowEnrollChild] = useState(false)
const tabs: { id: TabId; label: string; icon: string }[] = [
{ id: 'classroom', label: 'Classroom', icon: '🏫' },
@@ -95,16 +97,45 @@ export function ClassroomDashboard({ classroom, ownChildren = [] }: ClassroomDas
borderColor: isDark ? 'green.800' : 'green.200',
})}
>
-
- Your Children
-
+
+ Your Children
+
+
+
)}
+ {/* Enroll child modal */}
+ {showEnrollChild && (
+
{
+ if (e.target === e.currentTarget) {
+ setShowEnrollChild(false)
+ }
+ }}
+ >
+ setShowEnrollChild(false)}
+ onCancel={() => setShowEnrollChild(false)}
+ />
+
+ )}
+
{/* Tab navigation */}