react-achievements 3.0.2 → 3.2.0
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/README.md +338 -225
- package/dist/index.d.ts +205 -75
- package/dist/index.js +458 -115
- package/dist/index.js.map +1 -1
- package/dist/types/core/icons/defaultIcons.d.ts +0 -73
- package/dist/types/core/types.d.ts +14 -0
- package/dist/types/core/utils/configNormalizer.d.ts +3 -0
- package/dist/types/hooks/useSimpleAchievements.d.ts +40 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/providers/AchievementProvider.d.ts +2 -2
- package/dist/types/utils/achievementHelpers.d.ts +135 -0
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -99,6 +99,29 @@ class LocalStorage {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
class MemoryStorage {
|
|
103
|
+
constructor() {
|
|
104
|
+
this.metrics = {};
|
|
105
|
+
this.unlockedAchievements = [];
|
|
106
|
+
}
|
|
107
|
+
getMetrics() {
|
|
108
|
+
return this.metrics;
|
|
109
|
+
}
|
|
110
|
+
setMetrics(metrics) {
|
|
111
|
+
this.metrics = metrics;
|
|
112
|
+
}
|
|
113
|
+
getUnlockedAchievements() {
|
|
114
|
+
return this.unlockedAchievements;
|
|
115
|
+
}
|
|
116
|
+
setUnlockedAchievements(achievements) {
|
|
117
|
+
this.unlockedAchievements = achievements;
|
|
118
|
+
}
|
|
119
|
+
clear() {
|
|
120
|
+
this.metrics = {};
|
|
121
|
+
this.unlockedAchievements = [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
102
125
|
const getPositionStyles = (position) => {
|
|
103
126
|
const base = {
|
|
104
127
|
position: 'fixed',
|
|
@@ -129,94 +152,14 @@ const BadgesButton = ({ onClick, position, styles = {}, unlockedAchievements })
|
|
|
129
152
|
};
|
|
130
153
|
|
|
131
154
|
const defaultAchievementIcons = {
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
milestoneReached: '🏅',
|
|
139
|
-
firstStep: '👣',
|
|
140
|
-
newBeginnings: '🌱',
|
|
141
|
-
breakthrough: '💡',
|
|
142
|
-
growth: '📈',
|
|
143
|
-
// Social & Engagement
|
|
144
|
-
shared: '🔗',
|
|
145
|
-
liked: '❤️',
|
|
146
|
-
commented: '💬',
|
|
147
|
-
followed: '👥',
|
|
148
|
-
invited: '🤝',
|
|
149
|
-
communityMember: '🏘️',
|
|
150
|
-
supporter: '🌟',
|
|
151
|
-
connected: '🌐',
|
|
152
|
-
participant: '🙋',
|
|
153
|
-
influencer: '📣',
|
|
154
|
-
// Time & Activity
|
|
155
|
-
activeDay: '☀️',
|
|
156
|
-
activeWeek: '📅',
|
|
157
|
-
activeMonth: '🗓️',
|
|
158
|
-
earlyBird: '⏰',
|
|
159
|
-
nightOwl: '🌙',
|
|
160
|
-
streak: '🔥',
|
|
161
|
-
dedicated: '⏳',
|
|
162
|
-
punctual: '⏱️',
|
|
163
|
-
consistent: '🔄',
|
|
164
|
-
marathon: '🏃',
|
|
165
|
-
// Creativity & Skill
|
|
166
|
-
artist: '🎨',
|
|
167
|
-
writer: '✍️',
|
|
168
|
-
innovator: '🔬',
|
|
169
|
-
creator: '🛠️',
|
|
170
|
-
expert: '🎓',
|
|
171
|
-
master: '👑',
|
|
172
|
-
pioneer: '🚀',
|
|
173
|
-
performer: '🎭',
|
|
174
|
-
thinker: '🧠',
|
|
175
|
-
explorer: '🗺️',
|
|
176
|
-
// Achievement Types
|
|
177
|
-
bronze: '🥉',
|
|
178
|
-
silver: '🥈',
|
|
179
|
-
gold: '🥇',
|
|
180
|
-
diamond: '💎',
|
|
181
|
-
legendary: '✨',
|
|
182
|
-
epic: '💥',
|
|
183
|
-
rare: '🔮',
|
|
184
|
-
common: '🔘',
|
|
185
|
-
special: '🎁',
|
|
186
|
-
hidden: '❓',
|
|
187
|
-
// Numbers & Counters
|
|
188
|
-
one: '1️⃣',
|
|
189
|
-
ten: '🔟',
|
|
190
|
-
hundred: '💯',
|
|
191
|
-
thousand: '🔢',
|
|
192
|
-
// Actions & Interactions
|
|
193
|
-
clicked: '🖱️',
|
|
194
|
-
used: '🔑',
|
|
195
|
-
found: '🔍',
|
|
196
|
-
built: '🧱',
|
|
197
|
-
solved: '🧩',
|
|
198
|
-
discovered: '🔭',
|
|
199
|
-
unlocked: '🔓',
|
|
200
|
-
upgraded: '⬆️',
|
|
201
|
-
repaired: '🔧',
|
|
202
|
-
defended: '🛡️',
|
|
203
|
-
// Placeholders
|
|
204
|
-
default: '⭐', // A fallback icon
|
|
205
|
-
loading: '⏳',
|
|
206
|
-
error: '⚠️',
|
|
207
|
-
success: '✅',
|
|
208
|
-
failure: '❌',
|
|
209
|
-
// Miscellaneous
|
|
155
|
+
// Essential fallback icons for system use
|
|
156
|
+
default: '⭐', // Fallback when no icon is provided
|
|
157
|
+
loading: '⏳', // For loading states
|
|
158
|
+
error: '⚠️', // For error states
|
|
159
|
+
success: '✅', // For success states
|
|
160
|
+
// A few common icons for backward compatibility
|
|
210
161
|
trophy: '🏆',
|
|
211
162
|
star: '⭐',
|
|
212
|
-
flag: '🚩',
|
|
213
|
-
puzzle: '🧩',
|
|
214
|
-
gem: '💎',
|
|
215
|
-
crown: '👑',
|
|
216
|
-
medal: '🏅',
|
|
217
|
-
ribbon: '🎗️',
|
|
218
|
-
badge: '🎖️',
|
|
219
|
-
shield: '🛡️',
|
|
220
163
|
};
|
|
221
164
|
|
|
222
165
|
const BadgesModal = ({ isOpen, onClose, achievements, styles = {}, icons = {} }) => {
|
|
@@ -316,31 +259,100 @@ const ConfettiWrapper = ({ show }) => {
|
|
|
316
259
|
} }));
|
|
317
260
|
};
|
|
318
261
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
262
|
+
// Type guard to check if config is simple format
|
|
263
|
+
function isSimpleConfig(config) {
|
|
264
|
+
if (!config || typeof config !== 'object')
|
|
265
|
+
return false;
|
|
266
|
+
const firstKey = Object.keys(config)[0];
|
|
267
|
+
if (!firstKey)
|
|
268
|
+
return true; // Empty config is considered simple
|
|
269
|
+
const firstValue = config[firstKey];
|
|
270
|
+
// Check if it's the current complex format (array of AchievementCondition)
|
|
271
|
+
if (Array.isArray(firstValue))
|
|
272
|
+
return false;
|
|
273
|
+
// Check if it's the simple format (object with string keys)
|
|
274
|
+
return typeof firstValue === 'object' && !Array.isArray(firstValue);
|
|
275
|
+
}
|
|
276
|
+
// Generate a unique ID for achievements
|
|
277
|
+
function generateId() {
|
|
278
|
+
return Math.random().toString(36).substr(2, 9);
|
|
279
|
+
}
|
|
280
|
+
// Check if achievement details has a custom condition
|
|
281
|
+
function hasCustomCondition(details) {
|
|
282
|
+
return 'condition' in details && typeof details.condition === 'function';
|
|
283
|
+
}
|
|
284
|
+
// Convert simple config to complex config format
|
|
285
|
+
function normalizeAchievements(config) {
|
|
286
|
+
if (!isSimpleConfig(config)) {
|
|
287
|
+
// Already in complex format, return as-is
|
|
288
|
+
return config;
|
|
339
289
|
}
|
|
290
|
+
const normalized = {};
|
|
291
|
+
Object.entries(config).forEach(([metric, achievements]) => {
|
|
292
|
+
normalized[metric] = Object.entries(achievements).map(([key, achievement]) => {
|
|
293
|
+
if (hasCustomCondition(achievement)) {
|
|
294
|
+
// Custom condition function
|
|
295
|
+
return {
|
|
296
|
+
isConditionMet: (value, state) => {
|
|
297
|
+
// Convert internal metrics format (arrays) to simple format for custom conditions
|
|
298
|
+
const simpleMetrics = {};
|
|
299
|
+
Object.entries(state.metrics).forEach(([key, val]) => {
|
|
300
|
+
simpleMetrics[key] = Array.isArray(val) ? val[0] : val;
|
|
301
|
+
});
|
|
302
|
+
return achievement.condition(simpleMetrics);
|
|
303
|
+
},
|
|
304
|
+
achievementDetails: {
|
|
305
|
+
achievementId: `${metric}_custom_${generateId()}`,
|
|
306
|
+
achievementTitle: achievement.title,
|
|
307
|
+
achievementDescription: achievement.description || '',
|
|
308
|
+
achievementIconKey: achievement.icon || 'default'
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// Threshold-based achievement
|
|
314
|
+
const threshold = parseFloat(key);
|
|
315
|
+
const isValidThreshold = !isNaN(threshold);
|
|
316
|
+
let conditionMet;
|
|
317
|
+
if (isValidThreshold) {
|
|
318
|
+
// Numeric threshold
|
|
319
|
+
conditionMet = (value) => {
|
|
320
|
+
const numValue = Array.isArray(value) ? value[0] : value;
|
|
321
|
+
return typeof numValue === 'number' && numValue >= threshold;
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// String or boolean threshold
|
|
326
|
+
conditionMet = (value) => {
|
|
327
|
+
const actualValue = Array.isArray(value) ? value[0] : value;
|
|
328
|
+
// Handle boolean thresholds
|
|
329
|
+
if (key === 'true')
|
|
330
|
+
return actualValue === true;
|
|
331
|
+
if (key === 'false')
|
|
332
|
+
return actualValue === false;
|
|
333
|
+
// Handle string thresholds
|
|
334
|
+
return actualValue === key;
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
isConditionMet: conditionMet,
|
|
339
|
+
achievementDetails: {
|
|
340
|
+
achievementId: `${metric}_${key}`,
|
|
341
|
+
achievementTitle: achievement.title,
|
|
342
|
+
achievementDescription: achievement.description || (isValidThreshold ? `Reach ${threshold} ${metric}` : `Achieve ${key} for ${metric}`),
|
|
343
|
+
achievementIconKey: achievement.icon || 'default'
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
return normalized;
|
|
340
350
|
}
|
|
341
351
|
|
|
342
352
|
const AchievementContext = createContext(undefined);
|
|
343
|
-
const AchievementProvider = ({ achievements, storage = StorageType.Local, children, icons = {}, }) => {
|
|
353
|
+
const AchievementProvider = ({ achievements: achievementsConfig, storage = StorageType.Local, children, icons = {}, }) => {
|
|
354
|
+
// Normalize the configuration to the complex format
|
|
355
|
+
const achievements = normalizeAchievements(achievementsConfig);
|
|
344
356
|
const [achievementState, setAchievementState] = useState({
|
|
345
357
|
unlocked: [],
|
|
346
358
|
all: achievements,
|
|
@@ -432,12 +444,16 @@ const AchievementProvider = ({ achievements, storage = StorageType.Local, childr
|
|
|
432
444
|
let newlyUnlockedAchievements = [];
|
|
433
445
|
let achievementToShow = null;
|
|
434
446
|
Object.entries(achievements).forEach(([metricName, metricAchievements]) => {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
447
|
+
metricAchievements.forEach((achievement) => {
|
|
448
|
+
const state = { metrics, unlockedAchievements: achievementState.unlocked };
|
|
449
|
+
const achievementId = achievement.achievementDetails.achievementId;
|
|
450
|
+
// For custom conditions, we always check against all metrics
|
|
451
|
+
// For threshold-based conditions, we check against the specific metric
|
|
452
|
+
const currentValue = metrics[metricName];
|
|
453
|
+
const shouldCheckAchievement = currentValue !== undefined ||
|
|
454
|
+
achievement.achievementDetails.achievementId.includes('_custom_');
|
|
455
|
+
if (shouldCheckAchievement) {
|
|
439
456
|
const valueToCheck = currentValue;
|
|
440
|
-
const achievementId = achievement.achievementDetails.achievementId;
|
|
441
457
|
if (achievement.isConditionMet(valueToCheck, state)) {
|
|
442
458
|
if (!achievementState.unlocked.includes(achievementId) &&
|
|
443
459
|
!newlyUnlockedAchievements.includes(achievementId)) {
|
|
@@ -447,8 +463,8 @@ const AchievementProvider = ({ achievements, storage = StorageType.Local, childr
|
|
|
447
463
|
}
|
|
448
464
|
}
|
|
449
465
|
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
466
|
+
}
|
|
467
|
+
});
|
|
452
468
|
});
|
|
453
469
|
if (newlyUnlockedAchievements.length > 0) {
|
|
454
470
|
const allUnlocked = [...achievementState.unlocked, ...newlyUnlockedAchievements];
|
|
@@ -550,6 +566,47 @@ const useAchievements = () => {
|
|
|
550
566
|
return context;
|
|
551
567
|
};
|
|
552
568
|
|
|
569
|
+
/**
|
|
570
|
+
* A simplified hook for achievement tracking.
|
|
571
|
+
* Provides an easier API for common use cases while maintaining access to advanced features.
|
|
572
|
+
*/
|
|
573
|
+
const useSimpleAchievements = () => {
|
|
574
|
+
const { update, achievements, reset, getState } = useAchievements();
|
|
575
|
+
return {
|
|
576
|
+
/**
|
|
577
|
+
* Track a metric value for achievements
|
|
578
|
+
* @param metric - The metric name (e.g., 'score', 'level')
|
|
579
|
+
* @param value - The metric value
|
|
580
|
+
*/
|
|
581
|
+
track: (metric, value) => update({ [metric]: value }),
|
|
582
|
+
/**
|
|
583
|
+
* Track multiple metrics at once
|
|
584
|
+
* @param metrics - Object with metric names as keys and values
|
|
585
|
+
*/
|
|
586
|
+
trackMultiple: (metrics) => update(metrics),
|
|
587
|
+
/**
|
|
588
|
+
* Array of unlocked achievement IDs
|
|
589
|
+
*/
|
|
590
|
+
unlocked: achievements.unlocked,
|
|
591
|
+
/**
|
|
592
|
+
* All available achievements
|
|
593
|
+
*/
|
|
594
|
+
all: achievements.all,
|
|
595
|
+
/**
|
|
596
|
+
* Number of unlocked achievements
|
|
597
|
+
*/
|
|
598
|
+
unlockedCount: achievements.unlocked.length,
|
|
599
|
+
/**
|
|
600
|
+
* Reset all achievement progress
|
|
601
|
+
*/
|
|
602
|
+
reset,
|
|
603
|
+
/**
|
|
604
|
+
* Get current state (advanced usage)
|
|
605
|
+
*/
|
|
606
|
+
getState,
|
|
607
|
+
};
|
|
608
|
+
};
|
|
609
|
+
|
|
553
610
|
const defaultStyles = {
|
|
554
611
|
badgesButton: {
|
|
555
612
|
backgroundColor: '#4CAF50',
|
|
@@ -627,5 +684,291 @@ const defaultStyles = {
|
|
|
627
684
|
},
|
|
628
685
|
};
|
|
629
686
|
|
|
630
|
-
|
|
687
|
+
/**
|
|
688
|
+
* Base class for chainable achievement configuration (Tier 2)
|
|
689
|
+
*/
|
|
690
|
+
class Achievement {
|
|
691
|
+
constructor(metric, defaultAward) {
|
|
692
|
+
this.metric = metric;
|
|
693
|
+
this.award = defaultAward;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Customize the award details for this achievement
|
|
697
|
+
* @param award - Custom award details
|
|
698
|
+
* @returns This achievement for chaining
|
|
699
|
+
*/
|
|
700
|
+
withAward(award) {
|
|
701
|
+
this.award = Object.assign(Object.assign({}, this.award), award);
|
|
702
|
+
return this;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Threshold-based achievement (score, level, etc.)
|
|
707
|
+
*/
|
|
708
|
+
class ThresholdAchievement extends Achievement {
|
|
709
|
+
constructor(metric, threshold, defaultAward) {
|
|
710
|
+
super(metric, defaultAward);
|
|
711
|
+
this.threshold = threshold;
|
|
712
|
+
}
|
|
713
|
+
toConfig() {
|
|
714
|
+
return {
|
|
715
|
+
[this.metric]: {
|
|
716
|
+
[this.threshold]: {
|
|
717
|
+
title: this.award.title,
|
|
718
|
+
description: this.award.description,
|
|
719
|
+
icon: this.award.icon
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Boolean achievement (tutorial completion, first login, etc.)
|
|
727
|
+
*/
|
|
728
|
+
class BooleanAchievement extends Achievement {
|
|
729
|
+
toConfig() {
|
|
730
|
+
return {
|
|
731
|
+
[this.metric]: {
|
|
732
|
+
true: {
|
|
733
|
+
title: this.award.title,
|
|
734
|
+
description: this.award.description,
|
|
735
|
+
icon: this.award.icon
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Value-based achievement (character class, difficulty, etc.)
|
|
743
|
+
*/
|
|
744
|
+
class ValueAchievement extends Achievement {
|
|
745
|
+
constructor(metric, value, defaultAward) {
|
|
746
|
+
super(metric, defaultAward);
|
|
747
|
+
this.value = value;
|
|
748
|
+
}
|
|
749
|
+
toConfig() {
|
|
750
|
+
return {
|
|
751
|
+
[this.metric]: {
|
|
752
|
+
[this.value]: {
|
|
753
|
+
title: this.award.title,
|
|
754
|
+
description: this.award.description,
|
|
755
|
+
icon: this.award.icon
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Complex achievement builder for power users (Tier 3)
|
|
763
|
+
*/
|
|
764
|
+
class ComplexAchievementBuilder {
|
|
765
|
+
constructor() {
|
|
766
|
+
this.id = '';
|
|
767
|
+
this.metric = '';
|
|
768
|
+
this.condition = null;
|
|
769
|
+
this.award = {};
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Set the unique identifier for this achievement
|
|
773
|
+
*/
|
|
774
|
+
withId(id) {
|
|
775
|
+
this.id = id;
|
|
776
|
+
return this;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Set the metric this achievement tracks
|
|
780
|
+
*/
|
|
781
|
+
withMetric(metric) {
|
|
782
|
+
this.metric = metric;
|
|
783
|
+
return this;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Set the condition function that determines if achievement is unlocked
|
|
787
|
+
*/
|
|
788
|
+
withCondition(fn) {
|
|
789
|
+
this.condition = fn;
|
|
790
|
+
return this;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Set the award details for this achievement
|
|
794
|
+
*/
|
|
795
|
+
withAward(award) {
|
|
796
|
+
this.award = Object.assign(Object.assign({}, this.award), award);
|
|
797
|
+
return this;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Build the final achievement configuration
|
|
801
|
+
*/
|
|
802
|
+
build() {
|
|
803
|
+
if (!this.id || !this.metric || !this.condition) {
|
|
804
|
+
throw new Error('Complex achievement requires id, metric, and condition');
|
|
805
|
+
}
|
|
806
|
+
// Convert our two-parameter condition function to the single-parameter format
|
|
807
|
+
// expected by the existing CustomAchievementDetails type
|
|
808
|
+
const compatibleCondition = (metrics) => {
|
|
809
|
+
const state = {
|
|
810
|
+
metrics: {}, // We don't have access to the full metrics structure here
|
|
811
|
+
unlockedAchievements: []
|
|
812
|
+
};
|
|
813
|
+
return this.condition(metrics[this.metric], state);
|
|
814
|
+
};
|
|
815
|
+
return {
|
|
816
|
+
[this.id]: {
|
|
817
|
+
custom: {
|
|
818
|
+
title: this.award.title || this.id,
|
|
819
|
+
description: this.award.description || `Achieve ${this.award.title || this.id}`,
|
|
820
|
+
icon: this.award.icon || '💎',
|
|
821
|
+
condition: compatibleCondition
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Main AchievementBuilder with three-tier API
|
|
829
|
+
* Tier 1: Simple static methods with smart defaults
|
|
830
|
+
* Tier 2: Chainable customization
|
|
831
|
+
* Tier 3: Full builder for complex logic
|
|
832
|
+
*/
|
|
833
|
+
class AchievementBuilder {
|
|
834
|
+
// TIER 1: Simple Static Methods (90% of use cases)
|
|
835
|
+
/**
|
|
836
|
+
* Create a single score achievement with smart defaults
|
|
837
|
+
* @param threshold - Score threshold to achieve
|
|
838
|
+
* @returns Chainable ThresholdAchievement
|
|
839
|
+
*/
|
|
840
|
+
static createScoreAchievement(threshold) {
|
|
841
|
+
return new ThresholdAchievement('score', threshold, {
|
|
842
|
+
title: `Score ${threshold}!`,
|
|
843
|
+
description: `Score ${threshold} points`,
|
|
844
|
+
icon: '🏆'
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Create multiple score achievements
|
|
849
|
+
* @param thresholds - Array of thresholds or [threshold, award] tuples
|
|
850
|
+
* @returns Complete SimpleAchievementConfig
|
|
851
|
+
*/
|
|
852
|
+
static createScoreAchievements(thresholds) {
|
|
853
|
+
const config = { score: {} };
|
|
854
|
+
thresholds.forEach(item => {
|
|
855
|
+
if (typeof item === 'number') {
|
|
856
|
+
// Use default award
|
|
857
|
+
config.score[item] = {
|
|
858
|
+
title: `Score ${item}!`,
|
|
859
|
+
description: `Score ${item} points`,
|
|
860
|
+
icon: '🏆'
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
// Custom award
|
|
865
|
+
const [threshold, award] = item;
|
|
866
|
+
config.score[threshold] = {
|
|
867
|
+
title: award.title || `Score ${threshold}!`,
|
|
868
|
+
description: award.description || `Score ${threshold} points`,
|
|
869
|
+
icon: award.icon || '🏆'
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
return config;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Create a single level achievement with smart defaults
|
|
877
|
+
* @param level - Level threshold to achieve
|
|
878
|
+
* @returns Chainable ThresholdAchievement
|
|
879
|
+
*/
|
|
880
|
+
static createLevelAchievement(level) {
|
|
881
|
+
return new ThresholdAchievement('level', level, {
|
|
882
|
+
title: `Level ${level}!`,
|
|
883
|
+
description: `Reach level ${level}`,
|
|
884
|
+
icon: '📈'
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Create multiple level achievements
|
|
889
|
+
* @param levels - Array of levels or [level, award] tuples
|
|
890
|
+
* @returns Complete SimpleAchievementConfig
|
|
891
|
+
*/
|
|
892
|
+
static createLevelAchievements(levels) {
|
|
893
|
+
const config = { level: {} };
|
|
894
|
+
levels.forEach(item => {
|
|
895
|
+
if (typeof item === 'number') {
|
|
896
|
+
// Use default award
|
|
897
|
+
config.level[item] = {
|
|
898
|
+
title: `Level ${item}!`,
|
|
899
|
+
description: `Reach level ${item}`,
|
|
900
|
+
icon: '📈'
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
// Custom award
|
|
905
|
+
const [level, award] = item;
|
|
906
|
+
config.level[level] = {
|
|
907
|
+
title: award.title || `Level ${level}!`,
|
|
908
|
+
description: award.description || `Reach level ${level}`,
|
|
909
|
+
icon: award.icon || '📈'
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
return config;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Create a boolean achievement with smart defaults
|
|
917
|
+
* @param metric - The metric name (e.g., 'completedTutorial')
|
|
918
|
+
* @returns Chainable BooleanAchievement
|
|
919
|
+
*/
|
|
920
|
+
static createBooleanAchievement(metric) {
|
|
921
|
+
// Convert camelCase to Title Case
|
|
922
|
+
const formattedMetric = metric.replace(/([A-Z])/g, ' $1').toLowerCase();
|
|
923
|
+
const titleCase = formattedMetric.charAt(0).toUpperCase() + formattedMetric.slice(1);
|
|
924
|
+
return new BooleanAchievement(metric, {
|
|
925
|
+
title: `${titleCase}!`,
|
|
926
|
+
description: `Complete ${formattedMetric}`,
|
|
927
|
+
icon: '✅'
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Create a value-based achievement with smart defaults
|
|
932
|
+
* @param metric - The metric name (e.g., 'characterClass')
|
|
933
|
+
* @param value - The value to match (e.g., 'wizard')
|
|
934
|
+
* @returns Chainable ValueAchievement
|
|
935
|
+
*/
|
|
936
|
+
static createValueAchievement(metric, value) {
|
|
937
|
+
const formattedValue = value.charAt(0).toUpperCase() + value.slice(1);
|
|
938
|
+
return new ValueAchievement(metric, value, {
|
|
939
|
+
title: `${formattedValue}!`,
|
|
940
|
+
description: `Choose ${formattedValue.toLowerCase()} for ${metric}`,
|
|
941
|
+
icon: '🎯'
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
// TIER 3: Full Builder for Complex Logic
|
|
945
|
+
/**
|
|
946
|
+
* Create a complex achievement builder for power users
|
|
947
|
+
* @returns ComplexAchievementBuilder for full control
|
|
948
|
+
*/
|
|
949
|
+
static create() {
|
|
950
|
+
return new ComplexAchievementBuilder();
|
|
951
|
+
}
|
|
952
|
+
// UTILITY METHODS
|
|
953
|
+
/**
|
|
954
|
+
* Combine multiple achievement configurations
|
|
955
|
+
* @param achievements - Array of SimpleAchievementConfig objects or Achievement instances
|
|
956
|
+
* @returns Combined SimpleAchievementConfig
|
|
957
|
+
*/
|
|
958
|
+
static combine(achievements) {
|
|
959
|
+
const combined = {};
|
|
960
|
+
achievements.forEach(achievement => {
|
|
961
|
+
const config = achievement instanceof Achievement ? achievement.toConfig() : achievement;
|
|
962
|
+
Object.keys(config).forEach(key => {
|
|
963
|
+
if (!combined[key]) {
|
|
964
|
+
combined[key] = {};
|
|
965
|
+
}
|
|
966
|
+
Object.assign(combined[key], config[key]);
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
return combined;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
export { AchievementBuilder, AchievementContext, AchievementProvider, BadgesButton, BadgesModal, ConfettiWrapper, LocalStorage, MemoryStorage, StorageType, defaultAchievementIcons, defaultStyles, isSimpleConfig, normalizeAchievements, useAchievements, useSimpleAchievements };
|
|
631
974
|
//# sourceMappingURL=index.js.map
|