Skip to content

Commit 134b874

Browse files
authored
Merge pull request #5 from jvcByte/preview
update ProgressBar bg color
2 parents a730a74 + 44934a4 commit 134b874

4 files changed

Lines changed: 53 additions & 60 deletions

File tree

app/api/exercises/[id]/session/route.ts

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,34 @@ export async function GET(
99
{ params }: { params: { id: string } }
1010
) {
1111
const session = await getServerSession(authOptions);
12-
1312
if (!session?.user?.id) {
1413
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
1514
}
1615

1716
const userId = session.user.id;
1817
const exerciseId = params.id;
1918

20-
// Look up or create the session for this exercise + user
19+
// Check exercise-level start_time BEFORE creating a session
20+
// This way participants who haven't started yet are blocked at the exercise level
21+
const exerciseRows = await sql`
22+
SELECT start_time, end_time, duration_limit, question_count
23+
FROM exercises WHERE id = ${exerciseId} LIMIT 1
24+
`;
25+
if (exerciseRows.length === 0) {
26+
return NextResponse.json({ error: 'Exercise not found' }, { status: 404 });
27+
}
28+
const exercise = exerciseRows[0];
29+
30+
if (exercise.start_time && new Date() < new Date(exercise.start_time as string)) {
31+
return NextResponse.json(
32+
{ error: 'Session not yet open', opens_at: exercise.start_time },
33+
{ status: 423 }
34+
);
35+
}
36+
37+
// Look up or create the session
2138
let rows = await sql`
22-
SELECT s.id, s.start_time, s.end_time, s.duration_limit,
39+
SELECT s.id, s.end_time, s.duration_limit,
2340
s.started_at, s.closed_at, s.current_question_index,
2441
e.question_count
2542
FROM sessions s
@@ -30,69 +47,50 @@ export async function GET(
3047
`;
3148

3249
if (rows.length === 0) {
33-
// Create a new session, inheriting timing from the exercise
50+
// Create session — inherit end_time and duration_limit but NOT start_time
51+
// (start_time is checked at the exercise level above)
3452
const created = await sql`
35-
INSERT INTO sessions (exercise_id, user_id, started_at, current_question_index, start_time, end_time, duration_limit)
53+
INSERT INTO sessions (exercise_id, user_id, started_at, current_question_index, end_time, duration_limit)
3654
SELECT
3755
${exerciseId},
3856
${userId},
3957
now(),
4058
0,
41-
e.start_time,
4259
e.end_time,
4360
e.duration_limit
4461
FROM exercises e
4562
WHERE e.id = ${exerciseId}
46-
RETURNING id, start_time, end_time, duration_limit,
47-
started_at, closed_at, current_question_index
48-
`;
49-
50-
const exerciseRows = await sql`
51-
SELECT question_count FROM exercises WHERE id = ${exerciseId}
63+
RETURNING id, end_time, duration_limit, started_at, closed_at, current_question_index
5264
`;
5365

5466
rows = created.map((r) => ({
5567
...r,
56-
question_count: exerciseRows[0]?.question_count ?? 0,
68+
question_count: exercise.question_count ?? 0,
5769
}));
5870
}
5971

6072
const row = rows[0];
6173

62-
// 423 if start_time is set and now() < start_time
63-
if (row.start_time && new Date() < new Date(row.start_time as string)) {
64-
return NextResponse.json(
65-
{ error: 'Session not yet open', opens_at: row.start_time },
66-
{ status: 423 }
67-
);
68-
}
69-
7074
// 410 if session is closed
7175
if (row.closed_at) {
7276
return NextResponse.json({ error: 'Session closed' }, { status: 410 });
7377
}
7478

7579
const now = new Date();
7680

77-
// Auto-close if end_time has passed
78-
if (row.end_time && now > new Date(row.end_time as string)) {
79-
await sql`
80-
UPDATE sessions SET closed_at = now()
81-
WHERE id = ${row.id} AND closed_at IS NULL
82-
`;
81+
// Auto-close if end_time has passed (check both session and exercise end_time)
82+
const effectiveEndTime = (row.end_time || exercise.end_time) as string | null;
83+
if (effectiveEndTime && now > new Date(effectiveEndTime)) {
84+
await sql`UPDATE sessions SET closed_at = now() WHERE id = ${row.id} AND closed_at IS NULL`;
8385
return NextResponse.json({ error: 'Session closed' }, { status: 410 });
8486
}
8587

86-
// Auto-close if duration_limit has been exceeded
88+
// Auto-close if duration_limit exceeded
8789
if (row.duration_limit && row.started_at) {
8890
const durationSeconds = parseIntervalToSeconds(row.duration_limit);
8991
const startedAt = new Date(row.started_at as string).getTime();
90-
const expiresAt = startedAt + durationSeconds * 1000;
91-
if (now.getTime() > expiresAt) {
92-
await sql`
93-
UPDATE sessions SET closed_at = now()
94-
WHERE id = ${row.id} AND closed_at IS NULL
95-
`;
92+
if (now.getTime() > startedAt + durationSeconds * 1000) {
93+
await sql`UPDATE sessions SET closed_at = now() WHERE id = ${row.id} AND closed_at IS NULL`;
9694
return NextResponse.json({ error: 'Session closed' }, { status: 410 });
9795
}
9896
}
@@ -103,41 +101,30 @@ export async function GET(
103101
if (row.duration_limit && row.started_at) {
104102
const durationSeconds = parseIntervalToSeconds(row.duration_limit);
105103
const startedAt = new Date(row.started_at as string).getTime();
106-
const expiresAt = startedAt + durationSeconds * 1000;
107-
remainingSeconds = Math.floor((expiresAt - Date.now()) / 1000);
104+
remainingSeconds = Math.floor((startedAt + durationSeconds * 1000 - Date.now()) / 1000);
108105
}
109106

110-
if (row.end_time) {
111-
const fromEndTime = Math.floor(
112-
(new Date(row.end_time as string).getTime() - Date.now()) / 1000
113-
);
107+
if (effectiveEndTime) {
108+
const fromEndTime = Math.floor((new Date(effectiveEndTime).getTime() - Date.now()) / 1000);
114109
remainingSeconds = remainingSeconds === null ? fromEndTime : Math.min(remainingSeconds, fromEndTime);
115110
}
116111

117-
const warningLowTime =
118-
remainingSeconds !== null && remainingSeconds < 300;
119-
120-
// Query per-question submission statuses
121112
const submissions = await sql`
122113
SELECT question_index, is_final,
123114
(response_text IS NOT NULL AND response_text <> '') AS has_draft
124-
FROM submissions
125-
WHERE session_id = ${row.id}
115+
FROM submissions WHERE session_id = ${row.id}
126116
`;
127117

128-
const questionStatuses = submissions.map((s) => ({
129-
question_index: s.question_index,
130-
has_draft: Boolean(s.has_draft),
131-
is_final: Boolean(s.is_final),
132-
}));
133-
134118
return NextResponse.json({
135119
session_id: row.id,
136-
current_question_index: row.current_question_index,
137-
question_count: row.question_count,
120+
current_question_index: row.current_question_index as number,
121+
question_count: row.question_count as number,
138122
remaining_seconds: remainingSeconds,
139-
warning_low_time: warningLowTime,
140-
question_statuses: questionStatuses,
123+
warning_low_time: remainingSeconds !== null && remainingSeconds < 300,
124+
question_statuses: submissions.map((s) => ({
125+
question_index: s.question_index,
126+
has_draft: Boolean(s.has_draft),
127+
is_final: Boolean(s.is_final),
128+
})),
141129
});
142130
}
143-

app/globals.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
--bg2: #0c0f14;
66
--bg3: #111520;
77
--bg4: #161b28;
8+
--bg5: #535353;
89
--border: rgba(255,255,255,0.06);
910
--border2: rgba(255,255,255,0.1);
1011
--accent: #6366f1;

app/instructor/exercises/[id]/ExerciseManager.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ export default function ExerciseManager({ exercise: initial, sessions, assignedU
124124
</div>
125125
</div>
126126
<button
127-
onClick={() => patch({ start_time: startTime || null, end_time: endTime || null, duration_limit: durationLimit || null })}
127+
onClick={() => {
128+
const toUTC = (local: string) => local ? new Date(local).toISOString() : null;
129+
patch({ start_time: toUTC(startTime), end_time: toUTC(endTime), duration_limit: durationLimit || null });
130+
}}
128131
disabled={saving}
129132
className="btn btn-primary"
130133
>

app/participant/session/[id]/SessionView.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,17 @@ export default function SessionView({ exerciseId }: { exerciseId: string }) {
196196
)}
197197

198198
{/* Header */}
199-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '1rem' }}>
199+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: '1rem' }}>
200200
<ProgressBar
201201
currentIndex={sessionState.current_question_index}
202202
viewingIndex={activeIndex}
203203
questionCount={sessionState.question_count}
204204
questionStatuses={sessionState.question_statuses}
205205
onNavigate={(i) => setViewingIndex(i === sessionState.current_question_index ? null : i)}
206206
/>
207-
<TimerDisplay remainingSeconds={displayRemainingSeconds} warningLowTime={displayRemainingSeconds !== null && displayRemainingSeconds < 300} />
207+
<div style={{ flexShrink: 0 }}>
208+
<TimerDisplay remainingSeconds={displayRemainingSeconds} warningLowTime={displayRemainingSeconds !== null && displayRemainingSeconds < 300} />
209+
</div>
208210
</div>
209211

210212
{/* Question */}

0 commit comments

Comments
 (0)