rogasper commited on
Commit
d3f3189
·
1 Parent(s): 382b9aa

feat: enhance tracking of user interactions in test components by integrating analytics for attempt start, finish, and abandon events. Update logic to handle undefined package properties gracefully and improve loading state handling in the TakeTestComponent.

Browse files
apps/web/src/routes/package.$id.attempt.$attemptId.tsx CHANGED
@@ -43,6 +43,31 @@ function ContinueAttemptComponent() {
43
  startQuestionTimer,
44
  } = useTestSession(packageId, attemptId);
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  if (packageQuery.isLoading) {
47
  return (
48
  <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-4xl mx-auto bg-[var(--warm-cream)]">
@@ -66,31 +91,6 @@ function ContinueAttemptComponent() {
66
  );
67
  }
68
 
69
- const totalQuestions = pkg.sections.reduce((sum: number, sec) => sum + sec.questions.length, 0);
70
- const answeredCount = Object.keys(answers).length;
71
-
72
- const handleFinishWithTracking = useCallback(async () => {
73
- const result = await handleFinish();
74
- if (result) {
75
- trackUmamiEvent(AnalyticsEvent.ATTEMPT_FINISH, {
76
- exam_type: pkg.examTypeName ?? "unknown",
77
- score: result.totalScore,
78
- max_score: result.maxScore,
79
- percentage: result.percentage,
80
- time_elapsed_sec: timeElapsed,
81
- });
82
- }
83
- }, [handleFinish, pkg.examTypeName, timeElapsed]);
84
-
85
- const handleAbandonWithTracking = useCallback(async () => {
86
- await handleAbandon();
87
- trackUmamiEvent(AnalyticsEvent.ATTEMPT_ABANDON, {
88
- exam_type: pkg.examTypeName ?? "unknown",
89
- questions_answered: answeredCount,
90
- total_questions: totalQuestions,
91
- });
92
- }, [handleAbandon, pkg.examTypeName, answeredCount, totalQuestions]);
93
-
94
  const currentSection = pkg.sections[currentSectionIdx];
95
  if (!currentSection) {
96
  return (
 
43
  startQuestionTimer,
44
  } = useTestSession(packageId, attemptId);
45
 
46
+ const totalQuestions = pkg?.sections.reduce((sum: number, sec) => sum + sec.questions.length, 0) ?? 0;
47
+ const answeredCount = Object.keys(answers).length;
48
+
49
+ const handleFinishWithTracking = useCallback(async () => {
50
+ const result = await handleFinish();
51
+ if (result) {
52
+ trackUmamiEvent(AnalyticsEvent.ATTEMPT_FINISH, {
53
+ exam_type: pkg?.examTypeName ?? "unknown",
54
+ score: result.totalScore,
55
+ max_score: result.maxScore,
56
+ percentage: result.percentage,
57
+ time_elapsed_sec: timeElapsed,
58
+ });
59
+ }
60
+ }, [handleFinish, pkg?.examTypeName, timeElapsed]);
61
+
62
+ const handleAbandonWithTracking = useCallback(async () => {
63
+ await handleAbandon();
64
+ trackUmamiEvent(AnalyticsEvent.ATTEMPT_ABANDON, {
65
+ exam_type: pkg?.examTypeName ?? "unknown",
66
+ questions_answered: answeredCount,
67
+ total_questions: totalQuestions,
68
+ });
69
+ }, [handleAbandon, pkg?.examTypeName, answeredCount, totalQuestions]);
70
+
71
  if (packageQuery.isLoading) {
72
  return (
73
  <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-4xl mx-auto bg-[var(--warm-cream)]">
 
91
  );
92
  }
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  const currentSection = pkg.sections[currentSectionIdx];
95
  if (!currentSection) {
96
  return (
apps/web/src/routes/package.$id.take.tsx CHANGED
@@ -67,61 +67,61 @@ function TakeTestComponent() {
67
  startQuestionTimer,
68
  } = useTestSession(packageId);
69
 
70
- if (packageQuery.isLoading || activeAttemptQuery.isLoading) {
71
- return (
72
- <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-4xl mx-auto bg-[var(--warm-cream)]">
73
- <div className="h-8 w-48 bg-[var(--oat-light)] animate-pulse rounded mb-4" />
74
- <div className="h-64 bg-[var(--oat-light)] animate-pulse rounded-[var(--radius-xl)]" />
75
- </div>
76
- );
77
- }
78
-
79
- if (!pkg) {
80
- return (
81
- <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-4xl mx-auto bg-[var(--warm-cream)]">
82
- <div className="text-center py-20">
83
- <MaterialIcon name="error_outline" className="text-6xl text-[var(--warm-silver)] mx-auto mb-4" />
84
- <p className="text-lg text-[var(--warm-charcoal)] font-semibold">Paket tidak ditemukan</p>
85
- <Link to="/packages" className="text-[var(--matcha-600)] font-semibold mt-4 inline-block">
86
- Kembali ke Paket
87
- </Link>
88
- </div>
89
- </div>
90
- );
91
- }
92
-
93
- const totalQuestions = pkg.sections.reduce((sum: number, sec) => sum + sec.questions.length, 0);
94
  const answeredCount = Object.keys(answers).length;
95
 
96
  const handleStartWithTracking = useCallback(async () => {
97
  await handleStart();
98
  trackUmamiEvent(AnalyticsEvent.ATTEMPT_START, {
99
- exam_type: pkg.examTypeName ?? "unknown",
100
  question_count: totalQuestions,
101
  });
102
- }, [handleStart, pkg.examTypeName, totalQuestions]);
103
 
104
  const handleFinishWithTracking = useCallback(async () => {
105
  const result = await handleFinish();
106
  if (result) {
107
  trackUmamiEvent(AnalyticsEvent.ATTEMPT_FINISH, {
108
- exam_type: pkg.examTypeName ?? "unknown",
109
  score: result.totalScore,
110
  max_score: result.maxScore,
111
  percentage: result.percentage,
112
  time_elapsed_sec: timeElapsed,
113
  });
114
  }
115
- }, [handleFinish, pkg.examTypeName, timeElapsed]);
116
 
117
  const handleAbandonWithTracking = useCallback(async () => {
118
  await handleAbandon();
119
  trackUmamiEvent(AnalyticsEvent.ATTEMPT_ABANDON, {
120
- exam_type: pkg.examTypeName ?? "unknown",
121
  questions_answered: answeredCount,
122
  total_questions: totalQuestions,
123
  });
124
- }, [handleAbandon, pkg.examTypeName, answeredCount, totalQuestions]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  // Start screen
127
  if (!isStarted) {
 
67
  startQuestionTimer,
68
  } = useTestSession(packageId);
69
 
70
+ const totalQuestions = pkg?.sections.reduce((sum: number, sec) => sum + sec.questions.length, 0) ?? 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  const answeredCount = Object.keys(answers).length;
72
 
73
  const handleStartWithTracking = useCallback(async () => {
74
  await handleStart();
75
  trackUmamiEvent(AnalyticsEvent.ATTEMPT_START, {
76
+ exam_type: pkg?.examTypeName ?? "unknown",
77
  question_count: totalQuestions,
78
  });
79
+ }, [handleStart, pkg?.examTypeName, totalQuestions]);
80
 
81
  const handleFinishWithTracking = useCallback(async () => {
82
  const result = await handleFinish();
83
  if (result) {
84
  trackUmamiEvent(AnalyticsEvent.ATTEMPT_FINISH, {
85
+ exam_type: pkg?.examTypeName ?? "unknown",
86
  score: result.totalScore,
87
  max_score: result.maxScore,
88
  percentage: result.percentage,
89
  time_elapsed_sec: timeElapsed,
90
  });
91
  }
92
+ }, [handleFinish, pkg?.examTypeName, timeElapsed]);
93
 
94
  const handleAbandonWithTracking = useCallback(async () => {
95
  await handleAbandon();
96
  trackUmamiEvent(AnalyticsEvent.ATTEMPT_ABANDON, {
97
+ exam_type: pkg?.examTypeName ?? "unknown",
98
  questions_answered: answeredCount,
99
  total_questions: totalQuestions,
100
  });
101
+ }, [handleAbandon, pkg?.examTypeName, answeredCount, totalQuestions]);
102
+
103
+ if (packageQuery.isLoading || activeAttemptQuery.isLoading) {
104
+ return (
105
+ <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-4xl mx-auto bg-[var(--warm-cream)]">
106
+ <div className="h-8 w-48 bg-[var(--oat-light)] animate-pulse rounded mb-4" />
107
+ <div className="h-64 bg-[var(--oat-light)] animate-pulse rounded-[var(--radius-xl)]" />
108
+ </div>
109
+ );
110
+ }
111
+
112
+ if (!pkg) {
113
+ return (
114
+ <div className="min-h-screen pt-8 pb-32 px-6 md:px-12 lg:px-16 max-w-4xl mx-auto bg-[var(--warm-cream)]">
115
+ <div className="text-center py-20">
116
+ <MaterialIcon name="error_outline" className="text-6xl text-[var(--warm-silver)] mx-auto mb-4" />
117
+ <p className="text-lg text-[var(--warm-charcoal)] font-semibold">Paket tidak ditemukan</p>
118
+ <Link to="/packages" className="text-[var(--matcha-600)] font-semibold mt-4 inline-block">
119
+ Kembali ke Paket
120
+ </Link>
121
+ </div>
122
+ </div>
123
+ );
124
+ }
125
 
126
  // Start screen
127
  if (!isStarted) {