@contractspec/example.learning-journey-ui-gamified 3.7.6 → 3.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/AGENTS.md +50 -25
- package/CHANGELOG.md +32 -0
- package/README.md +63 -20
- package/dist/GamifiedMiniApp.js +246 -246
- package/dist/browser/GamifiedMiniApp.js +246 -246
- package/dist/browser/components/DayCalendar.js +1 -1
- package/dist/browser/components/FlashCard.js +6 -6
- package/dist/browser/components/MasteryRing.js +1 -1
- package/dist/browser/components/index.js +100 -100
- package/dist/browser/index.js +247 -246
- package/dist/browser/views/Overview.js +16 -16
- package/dist/browser/views/Progress.js +7 -7
- package/dist/browser/views/Steps.js +8 -8
- package/dist/browser/views/Timeline.js +8 -8
- package/dist/browser/views/index.js +242 -242
- package/dist/components/DayCalendar.js +1 -1
- package/dist/components/FlashCard.js +6 -6
- package/dist/components/MasteryRing.js +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +100 -100
- package/dist/index.d.ts +3 -3
- package/dist/index.js +247 -246
- package/dist/node/GamifiedMiniApp.js +246 -246
- package/dist/node/components/DayCalendar.js +1 -1
- package/dist/node/components/FlashCard.js +6 -6
- package/dist/node/components/MasteryRing.js +1 -1
- package/dist/node/components/index.js +100 -100
- package/dist/node/index.js +247 -246
- package/dist/node/views/Overview.js +16 -16
- package/dist/node/views/Progress.js +7 -7
- package/dist/node/views/Steps.js +8 -8
- package/dist/node/views/Timeline.js +8 -8
- package/dist/node/views/index.js +242 -242
- package/dist/views/Overview.js +16 -16
- package/dist/views/Progress.js +7 -7
- package/dist/views/Steps.js +8 -8
- package/dist/views/Timeline.js +8 -8
- package/dist/views/index.d.ts +1 -1
- package/dist/views/index.js +242 -242
- package/package.json +12 -12
- package/src/GamifiedMiniApp.tsx +70 -70
- package/src/components/DayCalendar.tsx +41 -41
- package/src/components/FlashCard.tsx +83 -83
- package/src/components/MasteryRing.tsx +64 -64
- package/src/components/index.ts +1 -1
- package/src/docs/learning-journey-ui-gamified.docblock.ts +11 -11
- package/src/example.ts +25 -25
- package/src/index.ts +5 -6
- package/src/learning-journey-ui-gamified.feature.ts +12 -12
- package/src/views/Overview.tsx +145 -145
- package/src/views/Progress.tsx +167 -167
- package/src/views/Steps.tsx +40 -40
- package/src/views/Timeline.tsx +177 -177
- package/src/views/index.ts +1 -1
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -13
package/src/views/Progress.tsx
CHANGED
|
@@ -1,183 +1,183 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
CardHeader,
|
|
7
|
-
CardTitle,
|
|
8
|
-
} from '@contractspec/lib.ui-kit-web/ui/card';
|
|
9
|
-
import {
|
|
10
|
-
XpBar,
|
|
11
|
-
BadgeDisplay,
|
|
5
|
+
BadgeDisplay,
|
|
6
|
+
XpBar,
|
|
12
7
|
} from '@contractspec/example.learning-journey-ui-shared';
|
|
8
|
+
import {
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
} from '@contractspec/lib.ui-kit-web/ui/card';
|
|
13
14
|
import { MasteryRing } from '../components/MasteryRing';
|
|
14
|
-
import type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';
|
|
15
15
|
|
|
16
16
|
export function Progress({ track, progress }: LearningViewProps) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const totalXp =
|
|
18
|
+
track.totalXp ??
|
|
19
|
+
track.steps.reduce((sum, s) => sum + (s.xpReward ?? 0), 0) +
|
|
20
|
+
(track.completionRewards?.xpBonus ?? 0);
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const completedSteps = progress.completedStepIds.length;
|
|
23
|
+
const totalSteps = track.steps.length;
|
|
24
|
+
const percentComplete =
|
|
25
|
+
totalSteps > 0 ? (completedSteps / totalSteps) * 100 : 0;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
// Group steps by metadata surface for mastery rings
|
|
28
|
+
const surfaces = new Map<string, { total: number; completed: number }>();
|
|
29
|
+
track.steps.forEach((step) => {
|
|
30
|
+
const surface = (step.metadata?.surface as string) ?? 'general';
|
|
31
|
+
const current = surfaces.get(surface) ?? { total: 0, completed: 0 };
|
|
32
|
+
surfaces.set(surface, {
|
|
33
|
+
total: current.total + 1,
|
|
34
|
+
completed:
|
|
35
|
+
current.completed +
|
|
36
|
+
(progress.completedStepIds.includes(step.id) ? 1 : 0),
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const surfaceColors: ('green' | 'blue' | 'violet' | 'orange')[] = [
|
|
41
|
+
'green',
|
|
42
|
+
'blue',
|
|
43
|
+
'violet',
|
|
44
|
+
'orange',
|
|
45
|
+
];
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
47
|
+
return (
|
|
48
|
+
<div className="space-y-6">
|
|
49
|
+
{/* XP Progress */}
|
|
50
|
+
<Card>
|
|
51
|
+
<CardHeader>
|
|
52
|
+
<CardTitle className="flex items-center gap-2">
|
|
53
|
+
<span>⚡</span>
|
|
54
|
+
<span>Experience Points</span>
|
|
55
|
+
</CardTitle>
|
|
56
|
+
</CardHeader>
|
|
57
|
+
<CardContent className="space-y-4">
|
|
58
|
+
<div className="flex items-baseline gap-2">
|
|
59
|
+
<span className="font-bold text-4xl text-violet-500">
|
|
60
|
+
{progress.xpEarned.toLocaleString()}
|
|
61
|
+
</span>
|
|
62
|
+
<span className="text-muted-foreground">
|
|
63
|
+
/ {totalXp.toLocaleString()} XP
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
<XpBar
|
|
67
|
+
current={progress.xpEarned}
|
|
68
|
+
max={totalXp}
|
|
69
|
+
showLabel={false}
|
|
70
|
+
size="lg"
|
|
71
|
+
/>
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
{track.completionRewards?.xpBonus && percentComplete < 100 && (
|
|
74
|
+
<p className="text-muted-foreground text-sm">
|
|
75
|
+
🎁 Complete all steps for a{' '}
|
|
76
|
+
<span className="font-semibold text-green-500">
|
|
77
|
+
+{track.completionRewards.xpBonus} XP
|
|
78
|
+
</span>{' '}
|
|
79
|
+
bonus!
|
|
80
|
+
</p>
|
|
81
|
+
)}
|
|
82
|
+
</CardContent>
|
|
83
|
+
</Card>
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
85
|
+
{/* Mastery Rings */}
|
|
86
|
+
<Card>
|
|
87
|
+
<CardHeader>
|
|
88
|
+
<CardTitle className="flex items-center gap-2">
|
|
89
|
+
<span>🎯</span>
|
|
90
|
+
<span>Skill Mastery</span>
|
|
91
|
+
</CardTitle>
|
|
92
|
+
</CardHeader>
|
|
93
|
+
<CardContent>
|
|
94
|
+
<div className="flex flex-wrap justify-center gap-6">
|
|
95
|
+
{Array.from(surfaces.entries()).map(([surface, data], index) => (
|
|
96
|
+
<MasteryRing
|
|
97
|
+
key={surface}
|
|
98
|
+
label={surface.charAt(0).toUpperCase() + surface.slice(1)}
|
|
99
|
+
percentage={(data.completed / data.total) * 100}
|
|
100
|
+
color={surfaceColors[index % surfaceColors.length]}
|
|
101
|
+
size="lg"
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
<MasteryRing
|
|
105
|
+
label="Overall"
|
|
106
|
+
percentage={percentComplete}
|
|
107
|
+
color="violet"
|
|
108
|
+
size="lg"
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
</CardContent>
|
|
112
|
+
</Card>
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
114
|
+
{/* Badges */}
|
|
115
|
+
<Card>
|
|
116
|
+
<CardHeader>
|
|
117
|
+
<CardTitle className="flex items-center gap-2">
|
|
118
|
+
<span>🏅</span>
|
|
119
|
+
<span>Badges Earned</span>
|
|
120
|
+
</CardTitle>
|
|
121
|
+
</CardHeader>
|
|
122
|
+
<CardContent>
|
|
123
|
+
<BadgeDisplay badges={progress.badges} size="lg" maxVisible={10} />
|
|
124
|
+
{progress.badges.length === 0 && (
|
|
125
|
+
<p className="text-muted-foreground text-sm">
|
|
126
|
+
Complete the track to earn your first badge!
|
|
127
|
+
</p>
|
|
128
|
+
)}
|
|
129
|
+
</CardContent>
|
|
130
|
+
</Card>
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
132
|
+
{/* Step Breakdown */}
|
|
133
|
+
<Card>
|
|
134
|
+
<CardHeader>
|
|
135
|
+
<CardTitle className="flex items-center gap-2">
|
|
136
|
+
<span>📊</span>
|
|
137
|
+
<span>Step Breakdown</span>
|
|
138
|
+
</CardTitle>
|
|
139
|
+
</CardHeader>
|
|
140
|
+
<CardContent>
|
|
141
|
+
<div className="space-y-2">
|
|
142
|
+
{track.steps.map((step) => {
|
|
143
|
+
const isCompleted = progress.completedStepIds.includes(step.id);
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
key={step.id}
|
|
147
|
+
className="flex items-center justify-between rounded-lg border p-3"
|
|
148
|
+
>
|
|
149
|
+
<div className="flex items-center gap-3">
|
|
150
|
+
<span
|
|
151
|
+
className={
|
|
152
|
+
isCompleted ? 'text-green-500' : 'text-muted-foreground'
|
|
153
|
+
}
|
|
154
|
+
>
|
|
155
|
+
{isCompleted ? '✓' : '○'}
|
|
156
|
+
</span>
|
|
157
|
+
<span
|
|
158
|
+
className={
|
|
159
|
+
isCompleted
|
|
160
|
+
? 'text-foreground'
|
|
161
|
+
: 'text-muted-foreground'
|
|
162
|
+
}
|
|
163
|
+
>
|
|
164
|
+
{step.title}
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
{step.xpReward && (
|
|
168
|
+
<span
|
|
169
|
+
className={`font-medium text-sm ${isCompleted ? 'text-green-500' : 'text-muted-foreground'}`}
|
|
170
|
+
>
|
|
171
|
+
{isCompleted ? '+' : ''}
|
|
172
|
+
{step.xpReward} XP
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
})}
|
|
178
|
+
</div>
|
|
179
|
+
</CardContent>
|
|
180
|
+
</Card>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
183
|
}
|
package/src/views/Steps.tsx
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { FlashCard } from '../components/FlashCard';
|
|
4
3
|
import type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';
|
|
4
|
+
import { FlashCard } from '../components/FlashCard';
|
|
5
5
|
|
|
6
6
|
export function Steps({ track, progress, onStepComplete }: LearningViewProps) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const currentStepIndex = track.steps.findIndex(
|
|
8
|
+
(s) => !progress.completedStepIds.includes(s.id)
|
|
9
|
+
);
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
return (
|
|
12
|
+
<div className="space-y-6">
|
|
13
|
+
{/* Header */}
|
|
14
|
+
<div className="text-center">
|
|
15
|
+
<h2 className="font-bold text-xl">Complete Your Challenges</h2>
|
|
16
|
+
<p className="text-muted-foreground">
|
|
17
|
+
Tap each card to reveal the action, then mark as complete
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
{/* Card Stack */}
|
|
22
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
23
|
+
{track.steps.map((step, index) => {
|
|
24
|
+
const isCompleted = progress.completedStepIds.includes(step.id);
|
|
25
|
+
const isCurrent = index === currentStepIndex;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
return (
|
|
28
|
+
<FlashCard
|
|
29
|
+
key={step.id}
|
|
30
|
+
step={step}
|
|
31
|
+
isCompleted={isCompleted}
|
|
32
|
+
isCurrent={isCurrent}
|
|
33
|
+
onComplete={() => onStepComplete?.(step.id)}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
</div>
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
{/* Progress Summary */}
|
|
40
|
+
<div className="text-center text-muted-foreground text-sm">
|
|
41
|
+
{progress.completedStepIds.length} of {track.steps.length} completed
|
|
42
|
+
{track.completionRewards?.xpBonus && (
|
|
43
|
+
<span className="ml-2 text-green-500">
|
|
44
|
+
(+{track.completionRewards.xpBonus} XP bonus on completion)
|
|
45
|
+
</span>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
50
|
}
|