@contractspec/example.learning-journey-ui-gamified 0.0.0-canary-20260113170453

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.
Files changed (71) hide show
  1. package/.turbo/turbo-build$colon$bundle.log +57 -0
  2. package/.turbo/turbo-build.log +58 -0
  3. package/CHANGELOG.md +466 -0
  4. package/LICENSE +21 -0
  5. package/README.md +32 -0
  6. package/dist/GamifiedMiniApp.d.ts +17 -0
  7. package/dist/GamifiedMiniApp.d.ts.map +1 -0
  8. package/dist/GamifiedMiniApp.js +63 -0
  9. package/dist/GamifiedMiniApp.js.map +1 -0
  10. package/dist/components/DayCalendar.d.ts +16 -0
  11. package/dist/components/DayCalendar.d.ts.map +1 -0
  12. package/dist/components/DayCalendar.js +33 -0
  13. package/dist/components/DayCalendar.js.map +1 -0
  14. package/dist/components/FlashCard.d.ts +19 -0
  15. package/dist/components/FlashCard.d.ts.map +1 -0
  16. package/dist/components/FlashCard.js +80 -0
  17. package/dist/components/FlashCard.js.map +1 -0
  18. package/dist/components/MasteryRing.d.ts +18 -0
  19. package/dist/components/MasteryRing.d.ts.map +1 -0
  20. package/dist/components/MasteryRing.js +82 -0
  21. package/dist/components/MasteryRing.js.map +1 -0
  22. package/dist/components/index.d.ts +4 -0
  23. package/dist/components/index.js +5 -0
  24. package/dist/docs/index.d.ts +1 -0
  25. package/dist/docs/index.js +1 -0
  26. package/dist/docs/learning-journey-ui-gamified.docblock.d.ts +1 -0
  27. package/dist/docs/learning-journey-ui-gamified.docblock.js +20 -0
  28. package/dist/docs/learning-journey-ui-gamified.docblock.js.map +1 -0
  29. package/dist/example.d.ts +7 -0
  30. package/dist/example.d.ts.map +1 -0
  31. package/dist/example.js +42 -0
  32. package/dist/example.js.map +1 -0
  33. package/dist/index.d.ts +12 -0
  34. package/dist/index.js +14 -0
  35. package/dist/views/Overview.d.ts +15 -0
  36. package/dist/views/Overview.d.ts.map +1 -0
  37. package/dist/views/Overview.js +161 -0
  38. package/dist/views/Overview.js.map +1 -0
  39. package/dist/views/Progress.d.ts +11 -0
  40. package/dist/views/Progress.d.ts.map +1 -0
  41. package/dist/views/Progress.js +143 -0
  42. package/dist/views/Progress.js.map +1 -0
  43. package/dist/views/Steps.d.ts +12 -0
  44. package/dist/views/Steps.d.ts.map +1 -0
  45. package/dist/views/Steps.js +56 -0
  46. package/dist/views/Steps.js.map +1 -0
  47. package/dist/views/Timeline.d.ts +11 -0
  48. package/dist/views/Timeline.d.ts.map +1 -0
  49. package/dist/views/Timeline.js +133 -0
  50. package/dist/views/Timeline.js.map +1 -0
  51. package/dist/views/index.d.ts +5 -0
  52. package/dist/views/index.js +6 -0
  53. package/example.ts +1 -0
  54. package/package.json +76 -0
  55. package/src/GamifiedMiniApp.tsx +93 -0
  56. package/src/components/DayCalendar.tsx +53 -0
  57. package/src/components/FlashCard.tsx +100 -0
  58. package/src/components/MasteryRing.tsx +81 -0
  59. package/src/components/index.ts +3 -0
  60. package/src/docs/index.ts +1 -0
  61. package/src/docs/learning-journey-ui-gamified.docblock.ts +18 -0
  62. package/src/example.ts +31 -0
  63. package/src/index.ts +10 -0
  64. package/src/views/Overview.tsx +164 -0
  65. package/src/views/Progress.tsx +183 -0
  66. package/src/views/Steps.tsx +50 -0
  67. package/src/views/Timeline.tsx +197 -0
  68. package/src/views/index.ts +4 -0
  69. package/tsconfig.json +10 -0
  70. package/tsconfig.tsbuildinfo +1 -0
  71. package/tsdown.config.js +17 -0
@@ -0,0 +1,183 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardHeader,
7
+ CardTitle,
8
+ } from '@contractspec/lib.ui-kit-web/ui/card';
9
+ import {
10
+ XpBar,
11
+ BadgeDisplay,
12
+ } from '@contractspec/example.learning-journey-ui-shared';
13
+ import { MasteryRing } from '../components/MasteryRing';
14
+ import type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';
15
+
16
+ export function Progress({ track, progress }: LearningViewProps) {
17
+ const totalXp =
18
+ track.totalXp ??
19
+ track.steps.reduce((sum, s) => sum + (s.xpReward ?? 0), 0) +
20
+ (track.completionRewards?.xpBonus ?? 0);
21
+
22
+ const completedSteps = progress.completedStepIds.length;
23
+ const totalSteps = track.steps.length;
24
+ const percentComplete =
25
+ totalSteps > 0 ? (completedSteps / totalSteps) * 100 : 0;
26
+
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
+
40
+ const surfaceColors: ('green' | 'blue' | 'violet' | 'orange')[] = [
41
+ 'green',
42
+ 'blue',
43
+ 'violet',
44
+ 'orange',
45
+ ];
46
+
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="text-4xl font-bold 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
+
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
+
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
+
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
+
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={`text-sm font-medium ${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
+ }
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+
3
+ import { FlashCard } from '../components/FlashCard';
4
+ import type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';
5
+
6
+ export function Steps({ track, progress, onStepComplete }: LearningViewProps) {
7
+ const currentStepIndex = track.steps.findIndex(
8
+ (s) => !progress.completedStepIds.includes(s.id)
9
+ );
10
+
11
+ return (
12
+ <div className="space-y-6">
13
+ {/* Header */}
14
+ <div className="text-center">
15
+ <h2 className="text-xl font-bold">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
+
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
+
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
+
39
+ {/* Progress Summary */}
40
+ <div className="text-muted-foreground text-center 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
+ }
@@ -0,0 +1,197 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardHeader,
7
+ CardTitle,
8
+ } from '@contractspec/lib.ui-kit-web/ui/card';
9
+ import { DayCalendar } from '../components/DayCalendar';
10
+ import type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';
11
+
12
+ export function Timeline({ track, progress }: LearningViewProps) {
13
+ // Check if this is a quest with day unlocks
14
+ const hasQuestDays = track.steps.some(
15
+ (s) => s.availability?.unlockOnDay !== undefined
16
+ );
17
+
18
+ if (hasQuestDays) {
19
+ // Quest-style calendar view
20
+ const totalDays = Math.max(
21
+ ...track.steps.map((s) => s.availability?.unlockOnDay ?? 1),
22
+ 7
23
+ );
24
+
25
+ const completedDays = track.steps
26
+ .filter((s) => progress.completedStepIds.includes(s.id))
27
+ .map((s) => s.availability?.unlockOnDay ?? 1);
28
+
29
+ // Current day is the first incomplete day
30
+ const currentDay =
31
+ track.steps.find((s) => !progress.completedStepIds.includes(s.id))
32
+ ?.availability?.unlockOnDay ?? 1;
33
+
34
+ return (
35
+ <div className="space-y-6">
36
+ {/* Header */}
37
+ <div className="text-center">
38
+ <h2 className="text-xl font-bold">{track.name}</h2>
39
+ <p className="text-muted-foreground">
40
+ Complete each day's challenge to progress
41
+ </p>
42
+ </div>
43
+
44
+ {/* Calendar Grid */}
45
+ <Card>
46
+ <CardHeader>
47
+ <CardTitle className="flex items-center gap-2">
48
+ <span>📅</span>
49
+ <span>Your Journey</span>
50
+ </CardTitle>
51
+ </CardHeader>
52
+ <CardContent className="flex justify-center">
53
+ <DayCalendar
54
+ totalDays={totalDays}
55
+ currentDay={currentDay}
56
+ completedDays={completedDays}
57
+ />
58
+ </CardContent>
59
+ </Card>
60
+
61
+ {/* Daily Steps */}
62
+ <Card>
63
+ <CardHeader>
64
+ <CardTitle className="flex items-center gap-2">
65
+ <span>📝</span>
66
+ <span>Daily Challenges</span>
67
+ </CardTitle>
68
+ </CardHeader>
69
+ <CardContent>
70
+ <div className="space-y-3">
71
+ {track.steps.map((step) => {
72
+ const day = step.availability?.unlockOnDay ?? 1;
73
+ const isCompleted = progress.completedStepIds.includes(step.id);
74
+ const isLocked = day > currentDay;
75
+
76
+ return (
77
+ <div
78
+ key={step.id}
79
+ className={`flex items-start gap-4 rounded-lg border p-4 ${
80
+ isLocked ? 'opacity-50' : ''
81
+ }`}
82
+ >
83
+ <div className="bg-muted flex h-10 w-10 shrink-0 items-center justify-center rounded-lg font-semibold">
84
+ {isCompleted ? '✓' : isLocked ? '🔒' : day}
85
+ </div>
86
+ <div className="flex-1">
87
+ <h4 className="font-semibold">{step.title}</h4>
88
+ <p className="text-muted-foreground text-sm">
89
+ {step.description}
90
+ </p>
91
+ </div>
92
+ {step.xpReward && (
93
+ <span
94
+ className={`text-sm font-medium ${
95
+ isCompleted
96
+ ? 'text-green-500'
97
+ : 'text-muted-foreground'
98
+ }`}
99
+ >
100
+ +{step.xpReward} XP
101
+ </span>
102
+ )}
103
+ </div>
104
+ );
105
+ })}
106
+ </div>
107
+ </CardContent>
108
+ </Card>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ // Drill-style timeline (step order)
114
+ return (
115
+ <div className="space-y-6">
116
+ {/* Header */}
117
+ <div className="text-center">
118
+ <h2 className="text-xl font-bold">Learning Path</h2>
119
+ <p className="text-muted-foreground">
120
+ Follow the steps to master this skill
121
+ </p>
122
+ </div>
123
+
124
+ {/* Timeline */}
125
+ <Card>
126
+ <CardContent className="p-6">
127
+ <div className="relative">
128
+ {/* Vertical line */}
129
+ <div className="bg-border absolute top-0 left-5 h-full w-0.5" />
130
+
131
+ {/* Steps */}
132
+ <div className="space-y-6">
133
+ {track.steps.map((step, index) => {
134
+ const isCompleted = progress.completedStepIds.includes(step.id);
135
+ const isCurrent =
136
+ !isCompleted &&
137
+ track.steps
138
+ .slice(0, index)
139
+ .every((s) => progress.completedStepIds.includes(s.id));
140
+
141
+ return (
142
+ <div key={step.id} className="relative flex gap-4 pl-2">
143
+ {/* Node */}
144
+ <div
145
+ className={`relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 ${
146
+ isCompleted
147
+ ? 'border-green-500 bg-green-500 text-white'
148
+ : isCurrent
149
+ ? 'border-violet-500 bg-violet-500 text-white'
150
+ : 'border-border bg-background'
151
+ }`}
152
+ >
153
+ {isCompleted ? '✓' : index + 1}
154
+ </div>
155
+
156
+ {/* Content */}
157
+ <div className="flex-1 pb-4">
158
+ <div className="flex items-start justify-between gap-2">
159
+ <div>
160
+ <h4
161
+ className={`font-semibold ${
162
+ isCompleted
163
+ ? 'text-foreground'
164
+ : isCurrent
165
+ ? 'text-violet-500'
166
+ : 'text-muted-foreground'
167
+ }`}
168
+ >
169
+ {step.title}
170
+ </h4>
171
+ <p className="text-muted-foreground mt-1 text-sm">
172
+ {step.description}
173
+ </p>
174
+ </div>
175
+ {step.xpReward && (
176
+ <span
177
+ className={`shrink-0 rounded-full px-2 py-1 text-xs font-semibold ${
178
+ isCompleted
179
+ ? 'bg-green-500/10 text-green-500'
180
+ : 'bg-muted text-muted-foreground'
181
+ }`}
182
+ >
183
+ +{step.xpReward} XP
184
+ </span>
185
+ )}
186
+ </div>
187
+ </div>
188
+ </div>
189
+ );
190
+ })}
191
+ </div>
192
+ </div>
193
+ </CardContent>
194
+ </Card>
195
+ </div>
196
+ );
197
+ }
@@ -0,0 +1,4 @@
1
+ export { Overview } from './Overview';
2
+ export { Steps } from './Steps';
3
+ export { Progress } from './Progress';
4
+ export { Timeline } from './Timeline';
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@contractspec/tool.typescript/react-library.json",
3
+ "include": ["src"],
4
+ "exclude": ["node_modules"],
5
+ "compilerOptions": {
6
+ "rootDir": "src",
7
+ "outDir": "dist"
8
+ }
9
+ }
10
+