Polish Novya UI with brand colors, first-load skeletons, and session transition overlay.
Keep chat pinned to the latest message while AI thinks or streams, and align buttons/inputs with the logo teal/blue focus treatment. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { api, hasSessionToken } from '@/services/backend';
|
||||
import { getLatestByPhase } from '@/services/assessmentService';
|
||||
import { getProgramProgress } from '@/services/programService';
|
||||
import { PROGRAM_SESSION_COUNT } from '@/data/program/sessions';
|
||||
|
||||
/** Sessions 1..(N-1) done ⇒ learner is on final session; post-test may open manually. */
|
||||
const POST_TEST_UNLOCK_MIN_COMPLETED_SESSIONS = PROGRAM_SESSION_COUNT - 1;
|
||||
|
||||
export type LearningStep =
|
||||
| 'registered'
|
||||
@@ -65,6 +70,8 @@ export function useLearningJourney(userId?: string) {
|
||||
isCompleted: boolean;
|
||||
} | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
/** Skeleton / blocking load only on first fetch — silent on reloadProgress. */
|
||||
const hasLoadedOnceRef = useRef(false);
|
||||
|
||||
const mapBackendJourney = useCallback((journey: {
|
||||
currentStep: LearningStep;
|
||||
@@ -118,12 +125,16 @@ export function useLearningJourney(userId?: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const isInitialLoad = !hasLoadedOnceRef.current;
|
||||
if (isInitialLoad) {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
if (useLocalOnly) {
|
||||
const local = await mergeAssessmentCompletion(readLocalProgress(userId));
|
||||
setProgress(local);
|
||||
setBackendPermissions(null);
|
||||
hasLoadedOnceRef.current = true;
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -154,10 +165,15 @@ export function useLearningJourney(userId?: string) {
|
||||
setProgress(merged);
|
||||
setBackendPermissions(null);
|
||||
} finally {
|
||||
hasLoadedOnceRef.current = true;
|
||||
setLoading(false);
|
||||
}
|
||||
}, [mapBackendJourney, mergeAssessmentCompletion, userId, useLocalOnly]);
|
||||
|
||||
useEffect(() => {
|
||||
hasLoadedOnceRef.current = false;
|
||||
}, [userId]);
|
||||
|
||||
useEffect(() => {
|
||||
loadProgress();
|
||||
}, [loadProgress]);
|
||||
@@ -239,17 +255,24 @@ export function useLearningJourney(userId?: string) {
|
||||
}, [progress, saveProgress, userId]);
|
||||
|
||||
const derivedPermissions = useMemo(() => {
|
||||
const completedSessions = userId
|
||||
? getProgramProgress(userId).completedSessions.length
|
||||
: 0;
|
||||
const reachedFinalSession =
|
||||
Boolean(progress?.training_completed_at) ||
|
||||
completedSessions >= POST_TEST_UNLOCK_MIN_COMPLETED_SESSIONS;
|
||||
|
||||
return {
|
||||
canTakePreTest: !progress?.pre_test_completed_at,
|
||||
canOpenTraining: Boolean(progress?.pre_test_completed_at),
|
||||
canTakePostTest: Boolean(
|
||||
progress?.pre_test_completed_at &&
|
||||
progress?.training_completed_at &&
|
||||
reachedFinalSession &&
|
||||
!progress?.post_test_completed_at
|
||||
),
|
||||
isCompleted: Boolean(progress?.post_test_completed_at)
|
||||
};
|
||||
}, [progress]);
|
||||
}, [progress, userId]);
|
||||
|
||||
const permissions = backendPermissions ?? derivedPermissions;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user