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.
Files changed (38) hide show
  1. package/README.md +183 -338
  2. package/dist/defaultStyles.d.ts +0 -9
  3. package/dist/hooks/useAchievement.d.ts +1 -1
  4. package/dist/index.cjs.js +290 -262
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.esm.js +293 -265
  7. package/dist/providers/AchievementProvider.d.ts +5 -4
  8. package/dist/redux/achievementSlice.d.ts +5 -6
  9. package/dist/types.d.ts +8 -0
  10. package/package.json +18 -9
  11. package/rollup.config.mjs +11 -0
  12. package/src/defaultStyles.ts +0 -52
  13. package/src/hooks/useAchievement.ts +8 -11
  14. package/src/index.ts +14 -8
  15. package/src/providers/AchievementProvider.tsx +147 -142
  16. package/src/redux/achievementSlice.ts +68 -45
  17. package/src/redux/notificationSlice.ts +5 -5
  18. package/src/redux/store.ts +1 -5
  19. package/src/types.ts +12 -7
  20. package/tsconfig.json +3 -1
  21. package/demo/README.md +0 -8
  22. package/demo/eslint.config.js +0 -38
  23. package/demo/index.html +0 -13
  24. package/demo/package-lock.json +0 -12053
  25. package/demo/package.json +0 -47
  26. package/demo/public/vite.svg +0 -1
  27. package/demo/src/AchievementConfig.ts +0 -37
  28. package/demo/src/App.css +0 -42
  29. package/demo/src/App.jsx +0 -89
  30. package/demo/src/assets/achievements/explorer.webp +0 -0
  31. package/demo/src/assets/achievements/seaoned_warrior.webp +0 -0
  32. package/demo/src/assets/achievements/warrior.webp +0 -0
  33. package/demo/src/assets/react.svg +0 -1
  34. package/demo/src/index.css +0 -68
  35. package/demo/src/main.jsx +0 -10
  36. package/demo/vite.config.js +0 -7
  37. package/src/components/AchievementModal.tsx +0 -57
  38. 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
  ![React Achievements Demo](https://github.com/dave-b-b/react-achievements/blob/main/images/demo.gif?raw=true)
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 react-dom @reduxjs/toolkit react-achievements
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 react-dom @reduxjs/toolkit react-achievements
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 using React-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'; // Import the Redux store you will create
36
+ import store from './store';
37
37
  import { AchievementProvider } from 'react-achievements';
38
- import Game from './Game'; // Your main game component
39
- import achievementConfig from './achievementConfig'; // Your achievement configuration
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 { setMetrics, metrics } = useAchievement();
160
+ const { updateMetrics, metrics } = useAchievement();
168
161
  const [currentQuest, setCurrentQuest] = useState(null);
169
162
 
170
163
  const defeatMonster = () => {
171
- setMetrics((prevMetrics) => ({
172
- ...prevMetrics,
173
- monstersDefeated: prevMetrics.monstersDefeated + 1,
174
- experience: prevMetrics.experience + 10,
175
- level: Math.floor((prevMetrics.experience + 10) / 100) + 1, // Calculate new level
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
- setMetrics((prevMetrics) => ({
181
- ...prevMetrics,
182
- questsCompleted: prevMetrics.questsCompleted + 1,
183
- experience: prevMetrics.experience + 50,
184
- level: Math.floor((prevMetrics.experience + 50) / 100) + 1, // Calculate new level
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: A modal pops up when an achievement is unlocked, perfect for rewarding players.
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`: An object defining your metrics and achievements.
245
- - `initialState`: The initial state of your metrics. Can also include an optional previouslyAwardedAchievements array of achievement IDs.
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 achievement components.
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">📄 License</h2>
616
- MIT
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
- React-Achievements provides a comprehensive achievement system for React applications, perfect for adding gamification elements to your projects. Whether you're building a game, an educational app, or any interactive experience, this package offers an easy way to implement and manage achievements, enhancing user engagement and retention.
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
+ ```
@@ -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("..").AchievementConfiguration;
5
+ config: import("../types").SerializedAchievementConfiguration;
6
6
  updateMetrics: (newMetrics: import("..").AchievementMetrics | ((prevMetrics: import("..").AchievementMetrics) => import("..").AchievementMetrics)) => void;
7
7
  resetStorage: () => void;
8
8
  };