react-achievements 2.1.0 → 2.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 +183 -338
- package/dist/defaultStyles.d.ts +0 -9
- package/dist/hooks/useAchievement.d.ts +1 -1
- package/dist/index.cjs.js +290 -262
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.js +293 -265
- package/dist/providers/AchievementProvider.d.ts +5 -4
- package/dist/redux/achievementSlice.d.ts +5 -6
- package/dist/types.d.ts +8 -0
- package/package.json +18 -9
- package/rollup.config.mjs +11 -0
- package/src/defaultStyles.ts +0 -52
- package/src/hooks/useAchievement.ts +8 -11
- package/src/index.ts +14 -8
- package/src/providers/AchievementProvider.tsx +147 -142
- package/src/redux/achievementSlice.ts +68 -45
- package/src/redux/notificationSlice.ts +5 -5
- package/src/redux/store.ts +1 -5
- package/src/types.ts +12 -7
- package/tsconfig.json +3 -1
- package/demo/README.md +0 -8
- package/demo/eslint.config.js +0 -38
- package/demo/index.html +0 -13
- package/demo/package-lock.json +0 -12053
- package/demo/package.json +0 -47
- package/demo/public/vite.svg +0 -1
- package/demo/src/AchievementConfig.ts +0 -37
- package/demo/src/App.css +0 -42
- package/demo/src/App.jsx +0 -89
- package/demo/src/assets/achievements/explorer.webp +0 -0
- package/demo/src/assets/achievements/seaoned_warrior.webp +0 -0
- package/demo/src/assets/achievements/warrior.webp +0 -0
- package/demo/src/assets/react.svg +0 -1
- package/demo/src/index.css +0 -68
- package/demo/src/main.jsx +0 -10
- package/demo/vite.config.js +0 -7
- package/src/components/AchievementModal.tsx +0 -57
- package/src/hooks/useAchievementState.ts +0 -12
package/README.md
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
<h1 align="center">🏆 React-Achievements 🏆</h1>
|
|
2
2
|
|
|
3
|
-
A flexible and customizable achievement system for React applications, perfect for adding gamification elements to your projects
|
|
3
|
+
<p align="center">A flexible and customizable achievement system for React applications, perfect for adding gamification elements to your projects.</p>
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
If you want to test the package, you can try it out here
|
|
7
|
+
<p align="center">If you want to test the package, you can try it out here:</p>
|
|
8
8
|
|
|
9
|
-
https://stackblitz.com/edit/vitejs-vite-sccdux
|
|
9
|
+
<p align="center">https://stackblitz.com/edit/vitejs-vite-sccdux</p>
|
|
10
10
|
|
|
11
11
|
<h2 align="center">🚀 Installation</h2>
|
|
12
12
|
|
|
13
|
-
Install `react-achievements` using npm or yarn:
|
|
13
|
+
Install `react-achievements` and its peer dependencies using npm or yarn:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install react
|
|
16
|
+
npm install react-achievements @reduxjs/toolkit react-redux react-toastify react-confetti react-use
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
or
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
yarn add react
|
|
22
|
+
yarn add react-achievements @reduxjs/toolkit react-redux react-toastify react-confetti react-use
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
<h2 align="center">🎮 Usage</h2>
|
|
26
26
|
|
|
27
|
-
Let's walk through setting up a simple RPG-style game with achievements
|
|
27
|
+
Let's walk through setting up a simple RPG-style game with achievements.
|
|
28
28
|
|
|
29
29
|
<h3 align="center">🛠 Set up the AchievementProvider</h3>
|
|
30
30
|
|
|
@@ -33,10 +33,10 @@ First, wrap your app or a part of your app with the AchievementProvider:
|
|
|
33
33
|
```jsx
|
|
34
34
|
import React from 'react';
|
|
35
35
|
import { Provider } from 'react-redux';
|
|
36
|
-
import store from './store';
|
|
36
|
+
import store from './store';
|
|
37
37
|
import { AchievementProvider } from 'react-achievements';
|
|
38
|
-
import Game from './Game';
|
|
39
|
-
import achievementConfig from './achievementConfig';
|
|
38
|
+
import Game from './Game';
|
|
39
|
+
import achievementConfig from './achievementConfig';
|
|
40
40
|
|
|
41
41
|
const initialState = {
|
|
42
42
|
level: 1,
|
|
@@ -44,7 +44,6 @@ const initialState = {
|
|
|
44
44
|
monstersDefeated: 0,
|
|
45
45
|
questsCompleted: 0,
|
|
46
46
|
previouslyAwardedAchievements: ['first_step'], // Optional: Load previously awarded achievements
|
|
47
|
-
// Add any other initial metrics here
|
|
48
47
|
};
|
|
49
48
|
|
|
50
49
|
function App() {
|
|
@@ -150,12 +149,6 @@ const achievementConfig = {
|
|
|
150
149
|
export default achievementConfig;
|
|
151
150
|
```
|
|
152
151
|
|
|
153
|
-
Key points:
|
|
154
|
-
|
|
155
|
-
- `isConditionMet`: A function that determines if an achievement should be unlocked.
|
|
156
|
-
- `achievementDetails`: An object containing the details of the achievement.
|
|
157
|
-
- `achievementIconKey`: A string used to reference the icon in the `AchievementProvider`'s icons prop. A list of icons are already provided by the library.
|
|
158
|
-
|
|
159
152
|
<h3 align="center">🎣 Use the useAchievement hook</h3>
|
|
160
153
|
|
|
161
154
|
In your game components, use the useAchievement hook to update metrics and trigger achievement checks:
|
|
@@ -164,25 +157,23 @@ import React, { useState } from 'react';
|
|
|
164
157
|
import { useAchievement } from 'react-achievements';
|
|
165
158
|
|
|
166
159
|
function Game() {
|
|
167
|
-
const {
|
|
160
|
+
const { updateMetrics, metrics } = useAchievement();
|
|
168
161
|
const [currentQuest, setCurrentQuest] = useState(null);
|
|
169
162
|
|
|
170
163
|
const defeatMonster = () => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}));
|
|
164
|
+
updateMetrics({
|
|
165
|
+
monstersDefeated: [(metrics.monstersDefeated?.[0] || 0) + 1],
|
|
166
|
+
experience: [(metrics.experience?.[0] || 0) + 10],
|
|
167
|
+
level: [Math.floor(((metrics.experience?.[0] || 0) + 10) / 100) + 1], // Calculate new level
|
|
168
|
+
});
|
|
177
169
|
};
|
|
178
170
|
|
|
179
171
|
const completeQuest = () => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}));
|
|
172
|
+
updateMetrics({
|
|
173
|
+
questsCompleted: [(metrics.questsCompleted?.[0] || 0) + 1],
|
|
174
|
+
experience: [(metrics.experience?.[0] || 0) + 50],
|
|
175
|
+
level: [Math.floor(((metrics.experience?.[0] || 0) + 50) / 100) + 1], // Calculate new level
|
|
176
|
+
});
|
|
186
177
|
setCurrentQuest(null);
|
|
187
178
|
};
|
|
188
179
|
|
|
@@ -193,10 +184,10 @@ function Game() {
|
|
|
193
184
|
return (
|
|
194
185
|
<div>
|
|
195
186
|
<h1>My RPG Game</h1>
|
|
196
|
-
<p>Level: {metrics.level}</p>
|
|
197
|
-
<p>Experience: {metrics.experience}</p>
|
|
198
|
-
<p>Monsters Defeated: {metrics.monstersDefeated}</p>
|
|
199
|
-
<p>Quests Completed: {metrics.questsCompleted}</p>
|
|
187
|
+
<p>Level: {metrics.level?.[0] || 1}</p>
|
|
188
|
+
<p>Experience: {metrics.experience?.[0] || 0}</p>
|
|
189
|
+
<p>Monsters Defeated: {metrics.monstersDefeated?.[0] || 0}</p>
|
|
190
|
+
<p>Quests Completed: {metrics.questsCompleted?.[0] || 0}</p>
|
|
200
191
|
|
|
201
192
|
<div>
|
|
202
193
|
<h2>Battle Arena</h2>
|
|
@@ -227,13 +218,13 @@ export default Game;
|
|
|
227
218
|
- Built with TypeScript: Provides strong typing and improved developer experience.
|
|
228
219
|
- Redux-Powered State Management: Leverages Redux for predictable and scalable state management of achievements and metrics.
|
|
229
220
|
- Automatic Achievement Tracking: Achievements are automatically checked and unlocked when metrics change.
|
|
230
|
-
- Achievement Notifications:
|
|
231
|
-
- Persistent Achievements: Unlocked achievements and metrics are stored in local storage, allowing players to keep their progress
|
|
232
|
-
- Achievement Gallery: Players can view all their unlocked achievements, encouraging completionism
|
|
233
|
-
- Confetti Effect: A celebratory confetti effect is displayed when an achievement is unlocked, adding to the excitement
|
|
234
|
-
- Local Storage: Achievements are stored locally on the device
|
|
235
|
-
- **Loading Previous Awards:** The AchievementProvider accepts an optional previouslyAwardedAchievements array in its initialState prop, allowing you to load achievements that the user has already earned
|
|
236
|
-
- **Programmatic Reset:** Includes a `resetStorage` function accessible via the `useAchievementContext` hook to easily reset all achievement data
|
|
221
|
+
- Achievement Notifications: Uses react-toastify to display notifications when an achievement is unlocked
|
|
222
|
+
- Persistent Achievements: Unlocked achievements and metrics are stored in local storage, allowing players to keep their progress
|
|
223
|
+
- Achievement Gallery: Players can view all their unlocked achievements, encouraging completionism
|
|
224
|
+
- Confetti Effect: A celebratory confetti effect is displayed when an achievement is unlocked, adding to the excitement
|
|
225
|
+
- Local Storage: Achievements are stored locally on the device
|
|
226
|
+
- **Loading Previous Awards:** The AchievementProvider accepts an optional previouslyAwardedAchievements array in its initialState prop, allowing you to load achievements that the user has already earned
|
|
227
|
+
- **Programmatic Reset:** Includes a `resetStorage` function accessible via the `useAchievementContext` hook to easily reset all achievement data
|
|
237
228
|
|
|
238
229
|
<h2 align="center">🔧 API</h2>
|
|
239
230
|
|
|
@@ -241,303 +232,17 @@ export default Game;
|
|
|
241
232
|
|
|
242
233
|
#### Props:
|
|
243
234
|
|
|
244
|
-
- `config
|
|
245
|
-
- `initialState
|
|
235
|
+
- `config` (required): An object defining your metrics and achievements
|
|
236
|
+
- `initialState` (optional): The initial state of your metrics. Can also include an optional previouslyAwardedAchievements array of achievement IDs
|
|
246
237
|
- `storageKey` (optional): A string to use as the key for localStorage. Default: 'react-achievements'
|
|
247
|
-
- `badgesButtonPosition` (optional): Position of the badges button. Default: 'top-right'
|
|
248
|
-
- `styles` (optional): Custom styles for the
|
|
249
|
-
|
|
250
|
-
<h3 align="center">🪝 useAchievement Hook</h3>
|
|
251
|
-
|
|
252
|
-
#### Returns an object with:
|
|
253
|
-
|
|
254
|
-
- `updateMetrics`: Function to update the metrics. Accepts either a new metrics object or a function that receives the previous metrics and returns the new metrics.
|
|
255
|
-
- `unlockedAchievements`: Array of unlocked achievement IDs. (Note: Access the actual Redux state using `useSelector`).
|
|
256
|
-
- `resetStorage`: Function to clear all achievement data from local storage and reset the Redux state.
|
|
257
|
-
|
|
258
|
-
<h3 align="center">🪝 useAchievementState Hook</h3>
|
|
259
|
-
<h4 align="center">Returns an object containing the current achievement state, useful for saving to a server or other persistent storage.
|
|
260
|
-
</h4>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
#### Returns an object with:
|
|
264
|
-
|
|
265
|
-
- `metrics`: The current achievement metrics object.
|
|
266
|
-
- `previouslyAwardedAchievements`: An array of achievement IDs that have been previously awarded to the user.
|
|
267
|
-
|
|
268
|
-
**Example Usage:**
|
|
269
|
-
|
|
270
|
-
```jsx
|
|
271
|
-
import React from 'react';
|
|
272
|
-
import { useAchievementState } from 'react-achievements';
|
|
273
|
-
|
|
274
|
-
const SyncAchievementsButton = () => {
|
|
275
|
-
const { metrics, previouslyAwardedAchievements } = useAchievementState();
|
|
276
|
-
|
|
277
|
-
const handleSaveToServer = async () => {
|
|
278
|
-
const achievementData = {
|
|
279
|
-
metrics,
|
|
280
|
-
previouslyAwardedAchievements,
|
|
281
|
-
};
|
|
282
|
-
try {
|
|
283
|
-
const response = await fetch('/api/save-achievements', {
|
|
284
|
-
method: 'POST',
|
|
285
|
-
headers: {
|
|
286
|
-
'Content-Type': 'application/json',
|
|
287
|
-
},
|
|
288
|
-
body: JSON.stringify(achievementData),
|
|
289
|
-
});
|
|
290
|
-
if (response.ok) {
|
|
291
|
-
console.log('Achievement data saved successfully!');
|
|
292
|
-
} else {
|
|
293
|
-
console.error('Failed to save achievement data.');
|
|
294
|
-
}
|
|
295
|
-
} catch (error) {
|
|
296
|
-
console.error('Error saving achievement data:', error);
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
return (
|
|
301
|
-
<button onClick={handleSaveToServer}>Save Achievements to Server</button>
|
|
302
|
-
);
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
export default SyncAchievementsButton;
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
<h2 align="center">🎨 Customization</h2>
|
|
309
|
-
|
|
310
|
-
React-Achievements allows for extensive customization of its appearance. You can override the default styles by passing a `styles` prop to the `AchievementProvider`:
|
|
311
|
-
|
|
312
|
-
```jsx
|
|
313
|
-
const customStyles = {
|
|
314
|
-
achievementModal: {
|
|
315
|
-
// Custom styles for the achievement modal below
|
|
316
|
-
},
|
|
317
|
-
badgesModal: {
|
|
318
|
-
// Custom styles for the badges modal below
|
|
319
|
-
},
|
|
320
|
-
badgesButton: {
|
|
321
|
-
// Custom styles for the badges button below
|
|
322
|
-
},
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
function App() {
|
|
326
|
-
return (
|
|
327
|
-
<AchievementProvider
|
|
328
|
-
config={achievementConfig}
|
|
329
|
-
initialState={initialState}
|
|
330
|
-
styles={customStyles}
|
|
331
|
-
>
|
|
332
|
-
<Game />
|
|
333
|
-
</AchievementProvider>
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### achievementModal (to be passed in as a customStyle above)
|
|
339
|
-
|
|
340
|
-
Customizes the modal that appears when an achievement is unlocked.
|
|
238
|
+
- `badgesButtonPosition` (optional): Position of the badges button. One of: 'top-left', 'top-right', 'bottom-left', 'bottom-right'. Default: 'top-right'
|
|
239
|
+
- `styles` (optional): Custom styles for the badges components (see Customization section below)
|
|
240
|
+
- `icons` (optional): Custom icons to use for achievements. You can use the default icons provided by the library (see Available Icons section) or provide your own. Icons should be a Record<string, string> where the key is the iconKey referenced in your achievement config and the value is the icon string/element.
|
|
341
241
|
|
|
342
|
-
|
|
343
|
-
achievementModal: {
|
|
344
|
-
overlay: {
|
|
345
|
-
// Styles for the modal overlay (background)
|
|
346
|
-
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
347
|
-
// You can also customize other overlay properties like zIndex, transition, etc.
|
|
348
|
-
},
|
|
349
|
-
content: {
|
|
350
|
-
// Styles for the modal content container
|
|
351
|
-
backgroundColor: '#2a2a2a',
|
|
352
|
-
color: '#ffffff',
|
|
353
|
-
borderRadius: '10px',
|
|
354
|
-
padding: '20px',
|
|
355
|
-
// Add any other CSS properties for the content container
|
|
356
|
-
},
|
|
357
|
-
title: {
|
|
358
|
-
// Styles for the achievement title
|
|
359
|
-
fontSize: '24px',
|
|
360
|
-
fontWeight: 'bold',
|
|
361
|
-
color: '#ffd700',
|
|
362
|
-
},
|
|
363
|
-
icon: {
|
|
364
|
-
// Styles for the achievement icon
|
|
365
|
-
width: '64px',
|
|
366
|
-
height: '64px',
|
|
367
|
-
marginBottom: '10px',
|
|
368
|
-
},
|
|
369
|
-
description: {
|
|
370
|
-
// Styles for the achievement description
|
|
371
|
-
fontSize: '16px',
|
|
372
|
-
marginTop: '10px',
|
|
373
|
-
},
|
|
374
|
-
button: {
|
|
375
|
-
// Styles for the close button
|
|
376
|
-
backgroundColor: '#4CAF50',
|
|
377
|
-
color: 'white',
|
|
378
|
-
padding: '10px 20px',
|
|
379
|
-
border: 'none',
|
|
380
|
-
borderRadius: '5px',
|
|
381
|
-
cursor: 'pointer',
|
|
382
|
-
},
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### badgesModal (to be passed in as a customStyle above)
|
|
387
|
-
|
|
388
|
-
```
|
|
389
|
-
badgesModal: {
|
|
390
|
-
overlay: {
|
|
391
|
-
// Similar to achievementModal overlay
|
|
392
|
-
},
|
|
393
|
-
content: {
|
|
394
|
-
// Similar to achievementModal content
|
|
395
|
-
},
|
|
396
|
-
title: {
|
|
397
|
-
// Styles for the modal title
|
|
398
|
-
},
|
|
399
|
-
badgeContainer: {
|
|
400
|
-
// Styles for the container holding all badges
|
|
401
|
-
display: 'flex',
|
|
402
|
-
flexWrap: 'wrap',
|
|
403
|
-
justifyContent: 'center',
|
|
404
|
-
},
|
|
405
|
-
badge: {
|
|
406
|
-
// Styles for individual badge containers
|
|
407
|
-
margin: '10px',
|
|
408
|
-
textAlign: 'center',
|
|
409
|
-
},
|
|
410
|
-
badgeIcon: {
|
|
411
|
-
// Styles for badge icons
|
|
412
|
-
width: '50px',
|
|
413
|
-
height: '50px',
|
|
414
|
-
},
|
|
415
|
-
badgeTitle: {
|
|
416
|
-
// Styles for badge titles
|
|
417
|
-
fontSize: '14px',
|
|
418
|
-
marginTop: '5px',
|
|
419
|
-
},
|
|
420
|
-
button: {
|
|
421
|
-
// Styles for the close button (similar to achievementModal button)
|
|
422
|
-
},
|
|
423
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
### badgesButton (to be passed in as a customStyle above)
|
|
428
|
-
|
|
429
|
-
```
|
|
430
|
-
badgesButton: {
|
|
431
|
-
// Styles for the floating badges button
|
|
432
|
-
position: 'fixed',
|
|
433
|
-
padding: '10px 20px',
|
|
434
|
-
backgroundColor: '#007bff',
|
|
435
|
-
color: '#ffffff',
|
|
436
|
-
border: 'none',
|
|
437
|
-
borderRadius: '5px',
|
|
438
|
-
cursor: 'pointer',
|
|
439
|
-
zIndex: 1000,
|
|
440
|
-
// You can add more CSS properties as needed. These are just regular CSS
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
<h2 align="center">Resetting React Achievements</h2>
|
|
446
|
-
|
|
447
|
-
The achievements and metrics are managed by Redux and persisted in local storage. You have two primary ways to reset the achievement system:
|
|
448
|
-
|
|
449
|
-
1. **Programmatic Reset:** Use the `resetStorage` function provided by the `useAchievementContext` hook within your components:
|
|
450
|
-
|
|
451
|
-
```jsx
|
|
452
|
-
import React from 'react';
|
|
453
|
-
import { useAchievementContext } from 'react-achievements';
|
|
454
|
-
|
|
455
|
-
function ResetButton() {
|
|
456
|
-
const { resetStorage } = useAchievementContext();
|
|
457
|
-
|
|
458
|
-
const handleReset = () => {
|
|
459
|
-
resetStorage();
|
|
460
|
-
console.log('Achievements and progress reset!');
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
return <button onClick={handleReset}>Reset Achievements</button>;
|
|
464
|
-
}
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
<h2 align="center">💾 Saving and Loading Progress</h2>
|
|
468
|
-
|
|
469
|
-
<h4 align="center">To persist user achievement progress across sessions or devices, you'll typically want to save the `metrics` and `previouslyAwardedAchievements` from your Redux store to your server. You can use the `useAchievementState` hook to access this data and trigger the save operation, for example, when the user logs out:
|
|
470
|
-
</h4>
|
|
471
|
-
|
|
472
|
-
```jsx
|
|
473
|
-
import React from 'react';
|
|
474
|
-
import { useAchievementState } from 'react-achievements/hooks/useAchievementState';
|
|
475
|
-
|
|
476
|
-
const LogoutButtonWithSave = ({ onLogout }) => {
|
|
477
|
-
const { metrics, previouslyAwardedAchievements } = useAchievementState();
|
|
478
|
-
|
|
479
|
-
const handleLogoutAndSave = async () => {
|
|
480
|
-
const achievementData = {
|
|
481
|
-
metrics,
|
|
482
|
-
previouslyAwardedAchievements,
|
|
483
|
-
};
|
|
484
|
-
try {
|
|
485
|
-
const response = await fetch('/api/save-achievements', {
|
|
486
|
-
method: 'POST',
|
|
487
|
-
headers: {
|
|
488
|
-
'Content-Type': 'application/json',
|
|
489
|
-
// Include any necessary authentication headers
|
|
490
|
-
},
|
|
491
|
-
body: JSON.stringify(achievementData),
|
|
492
|
-
});
|
|
493
|
-
if (response.ok) {
|
|
494
|
-
console.log('Achievement data saved successfully before logout!');
|
|
495
|
-
} else {
|
|
496
|
-
console.error('Failed to save achievement data before logout.');
|
|
497
|
-
}
|
|
498
|
-
} catch (error) {
|
|
499
|
-
console.error('Error saving achievement data:', error);
|
|
500
|
-
} finally {
|
|
501
|
-
// Proceed with the logout action regardless of save success
|
|
502
|
-
onLogout();
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
return (
|
|
507
|
-
<button onClick={handleLogoutAndSave}>Logout</button>
|
|
508
|
-
);
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
export default LogoutButtonWithSave;
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
<h2 align="center">🏆Available Icons🏆</h2>
|
|
515
|
-
|
|
516
|
-
```
|
|
517
|
-
// General Progress & Milestones
|
|
518
|
-
levelUp: '🏆',
|
|
519
|
-
questComplete: '📜',
|
|
520
|
-
monsterDefeated: '⚔️',
|
|
521
|
-
itemCollected: '📦',
|
|
522
|
-
challengeCompleted: '🏁',
|
|
523
|
-
milestoneReached: '🏅',
|
|
524
|
-
firstStep: '👣',
|
|
525
|
-
newBeginnings: '🌱',
|
|
526
|
-
breakthrough: '💡',
|
|
527
|
-
growth: '📈',
|
|
528
|
-
|
|
529
|
-
// Social & Engagement
|
|
530
|
-
shared: '🔗',
|
|
531
|
-
liked: '❤️',
|
|
532
|
-
commented: '💬',
|
|
533
|
-
followed: '👥',
|
|
534
|
-
invited: '🤝',
|
|
535
|
-
communityMember: '🏘️',
|
|
536
|
-
supporter: '🌟',
|
|
537
|
-
connected: '🌐',
|
|
538
|
-
participant: '🙋',
|
|
539
|
-
influencer: '📣',
|
|
242
|
+
### Available Default Icons
|
|
540
243
|
|
|
244
|
+
```javascript
|
|
245
|
+
{
|
|
541
246
|
// Time & Activity
|
|
542
247
|
activeDay: '☀️',
|
|
543
248
|
activeWeek: '📅',
|
|
@@ -610,9 +315,149 @@ export default LogoutButtonWithSave;
|
|
|
610
315
|
ribbon: '🎗️',
|
|
611
316
|
badge: '🎖️',
|
|
612
317
|
shield: '🛡️',
|
|
318
|
+
}
|
|
613
319
|
```
|
|
614
320
|
|
|
615
|
-
<h2 align="center"
|
|
616
|
-
|
|
321
|
+
<h2 align="center">🎨 Customization</h2>
|
|
322
|
+
|
|
323
|
+
You can customize the look of the achievement badges by overriding the default styles. Pass a `styles` prop to the `AchievementProvider`:
|
|
617
324
|
|
|
618
|
-
|
|
325
|
+
```javascript
|
|
326
|
+
const customStyles = {
|
|
327
|
+
badge: {
|
|
328
|
+
// Your custom styles here
|
|
329
|
+
},
|
|
330
|
+
// ...other styles
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
<AchievementProvider
|
|
334
|
+
config={achievementConfig}
|
|
335
|
+
initialState={initialState}
|
|
336
|
+
styles={customStyles}
|
|
337
|
+
>
|
|
338
|
+
<Game />
|
|
339
|
+
</AchievementProvider>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
<h2 align="center">🔄 Complex Achievement Conditions</h2>
|
|
343
|
+
|
|
344
|
+
<h3 align="center">Achievement Dependencies</h3>
|
|
345
|
+
|
|
346
|
+
You can create achievements that depend on other achievements being unlocked first:
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
const achievementConfig = {
|
|
350
|
+
prerequisite: [
|
|
351
|
+
{
|
|
352
|
+
isConditionMet: (value) => value === true,
|
|
353
|
+
achievementDetails: {
|
|
354
|
+
achievementId: 'prerequisite',
|
|
355
|
+
achievementTitle: 'Prerequisites Met',
|
|
356
|
+
achievementDescription: 'Unlocked advanced achievements',
|
|
357
|
+
achievementIconKey: 'unlock'
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
],
|
|
361
|
+
dependent: [
|
|
362
|
+
{
|
|
363
|
+
isConditionMet: (value, state) => {
|
|
364
|
+
const prereqMet = state.unlockedAchievements.includes('prerequisite');
|
|
365
|
+
return prereqMet && typeof value === 'number' && value >= 100;
|
|
366
|
+
},
|
|
367
|
+
achievementDetails: {
|
|
368
|
+
achievementId: 'dependent',
|
|
369
|
+
achievementTitle: 'Advanced Achievement',
|
|
370
|
+
achievementDescription: 'Completed an advanced challenge',
|
|
371
|
+
achievementIconKey: 'star'
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
]
|
|
375
|
+
};
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
<h3 align="center">Time-Based Achievements</h3>
|
|
379
|
+
|
|
380
|
+
You can create achievements based on specific times or dates:
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
const achievementConfig = {
|
|
384
|
+
loginTime: [
|
|
385
|
+
{
|
|
386
|
+
isConditionMet: (value) => {
|
|
387
|
+
if (!(value instanceof Date)) return false;
|
|
388
|
+
const hour = value.getHours();
|
|
389
|
+
return hour >= 22 || hour < 6;
|
|
390
|
+
},
|
|
391
|
+
achievementDetails: {
|
|
392
|
+
achievementId: 'night_owl',
|
|
393
|
+
achievementTitle: 'Night Owl',
|
|
394
|
+
achievementDescription: 'Logged in during night hours',
|
|
395
|
+
achievementIconKey: 'moon'
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
};
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
<h3 align="center">Progressive Achievements</h3>
|
|
403
|
+
|
|
404
|
+
You can create achievement chains that unlock in sequence:
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
const achievementConfig = {
|
|
408
|
+
skillLevel: [
|
|
409
|
+
{
|
|
410
|
+
isConditionMet: (value) => typeof value === 'number' && value >= 1,
|
|
411
|
+
achievementDetails: {
|
|
412
|
+
achievementId: 'skill_novice',
|
|
413
|
+
achievementTitle: 'Novice',
|
|
414
|
+
achievementDescription: 'Reached skill level 1',
|
|
415
|
+
achievementIconKey: 'bronze'
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
isConditionMet: (value) => typeof value === 'number' && value >= 5,
|
|
420
|
+
achievementDetails: {
|
|
421
|
+
achievementId: 'skill_master',
|
|
422
|
+
achievementTitle: 'Master',
|
|
423
|
+
achievementDescription: 'Reached skill level 5',
|
|
424
|
+
achievementIconKey: 'gold'
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
};
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
<h2 align="center">💾 Saving and Loading Progress</h2>
|
|
432
|
+
|
|
433
|
+
To persist user achievement progress across sessions or devices, you can save the metrics and previouslyAwardedAchievements from your Redux store:
|
|
434
|
+
|
|
435
|
+
```jsx
|
|
436
|
+
import React from 'react';
|
|
437
|
+
import { useAchievementState } from 'react-achievements/hooks/useAchievementState';
|
|
438
|
+
|
|
439
|
+
const LogoutButtonWithSave = ({ onLogout }) => {
|
|
440
|
+
const { metrics, previouslyAwardedAchievements } = useAchievementState();
|
|
441
|
+
|
|
442
|
+
const handleLogoutAndSave = async () => {
|
|
443
|
+
const achievementData = {
|
|
444
|
+
metrics,
|
|
445
|
+
previouslyAwardedAchievements,
|
|
446
|
+
};
|
|
447
|
+
try {
|
|
448
|
+
await fetch('/api/save-achievements', {
|
|
449
|
+
method: 'POST',
|
|
450
|
+
headers: {
|
|
451
|
+
'Content-Type': 'application/json',
|
|
452
|
+
},
|
|
453
|
+
body: JSON.stringify(achievementData),
|
|
454
|
+
});
|
|
455
|
+
onLogout();
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error('Failed to save achievements:', error);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
return <button onClick={handleLogoutAndSave}>Logout</button>;
|
|
462
|
+
};
|
|
463
|
+
```
|
package/dist/defaultStyles.d.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
type StyleObject = {
|
|
2
2
|
[key: string]: string | number;
|
|
3
3
|
};
|
|
4
|
-
interface ModalStyles {
|
|
5
|
-
overlay: StyleObject;
|
|
6
|
-
content: StyleObject;
|
|
7
|
-
title: StyleObject;
|
|
8
|
-
icon: StyleObject;
|
|
9
|
-
description: StyleObject;
|
|
10
|
-
button: StyleObject;
|
|
11
|
-
}
|
|
12
4
|
interface BadgesModalStyles {
|
|
13
5
|
overlay: StyleObject;
|
|
14
6
|
content: StyleObject;
|
|
@@ -20,7 +12,6 @@ interface BadgesModalStyles {
|
|
|
20
12
|
button: StyleObject;
|
|
21
13
|
}
|
|
22
14
|
export interface Styles {
|
|
23
|
-
achievementModal: ModalStyles;
|
|
24
15
|
badgesModal: BadgesModalStyles;
|
|
25
16
|
badgesButton: StyleObject;
|
|
26
17
|
}
|
|
@@ -2,7 +2,7 @@ export declare const useAchievement: () => {
|
|
|
2
2
|
metrics: import("..").AchievementMetrics;
|
|
3
3
|
unlockedAchievements: string[];
|
|
4
4
|
notifications: import("..").AchievementDetails[];
|
|
5
|
-
config: import("
|
|
5
|
+
config: import("../types").SerializedAchievementConfiguration;
|
|
6
6
|
updateMetrics: (newMetrics: import("..").AchievementMetrics | ((prevMetrics: import("..").AchievementMetrics) => import("..").AchievementMetrics)) => void;
|
|
7
7
|
resetStorage: () => void;
|
|
8
8
|
};
|