react-achievements 1.2.0 → 1.3.1

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/dist/index.esm.js CHANGED
@@ -1,90 +1,148 @@
1
1
  import React, { useEffect, useRef, useState, useCallback, createContext, useContext } from 'react';
2
2
 
3
- const AchievementModal = ({ show, achievement, onClose }) => {
4
- if (!show || !achievement)
5
- return null;
6
- const modalStyle = {
3
+ const defaultStyles = {
4
+ achievementModal: {
5
+ overlay: {
6
+ position: 'fixed',
7
+ top: 0,
8
+ left: 0,
9
+ right: 0,
10
+ bottom: 0,
11
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ },
16
+ content: {
17
+ backgroundColor: '#ffffff',
18
+ borderRadius: '8px',
19
+ padding: '20px',
20
+ maxWidth: '400px',
21
+ width: '100%',
22
+ },
23
+ title: {
24
+ fontSize: '24px',
25
+ fontWeight: 'bold',
26
+ marginBottom: '10px',
27
+ },
28
+ icon: {
29
+ width: '50px',
30
+ height: '50px',
31
+ marginBottom: '10px',
32
+ },
33
+ description: {
34
+ fontSize: '16px',
35
+ marginBottom: '20px',
36
+ },
37
+ button: {
38
+ backgroundColor: '#007bff',
39
+ color: '#ffffff',
40
+ padding: '10px 20px',
41
+ borderRadius: '4px',
42
+ border: 'none',
43
+ cursor: 'pointer',
44
+ },
45
+ },
46
+ badgesModal: {
47
+ overlay: {
48
+ position: 'fixed',
49
+ top: 0,
50
+ left: 0,
51
+ right: 0,
52
+ bottom: 0,
53
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
54
+ display: 'flex',
55
+ alignItems: 'center',
56
+ justifyContent: 'center',
57
+ },
58
+ content: {
59
+ backgroundColor: '#ffffff',
60
+ borderRadius: '8px',
61
+ padding: '20px',
62
+ maxWidth: '600px',
63
+ width: '100%',
64
+ maxHeight: '80vh',
65
+ overflowY: 'auto',
66
+ },
67
+ title: {
68
+ fontSize: '24px',
69
+ fontWeight: 'bold',
70
+ marginBottom: '20px',
71
+ },
72
+ badgeContainer: {
73
+ display: 'flex',
74
+ flexWrap: 'wrap',
75
+ justifyContent: 'center',
76
+ },
77
+ badge: {
78
+ display: 'flex',
79
+ flexDirection: 'column',
80
+ alignItems: 'center',
81
+ margin: '10px',
82
+ },
83
+ badgeIcon: {
84
+ width: '50px',
85
+ height: '50px',
86
+ marginBottom: '5px',
87
+ },
88
+ badgeTitle: {
89
+ fontSize: '14px',
90
+ textAlign: 'center',
91
+ },
92
+ button: {
93
+ backgroundColor: '#007bff',
94
+ color: '#ffffff',
95
+ padding: '10px 20px',
96
+ borderRadius: '4px',
97
+ border: 'none',
98
+ cursor: 'pointer',
99
+ marginTop: '20px',
100
+ },
101
+ },
102
+ badgesButton: {
7
103
  position: 'fixed',
8
- top: '50%',
9
- left: '50%',
10
- transform: 'translate(-50%, -50%)',
11
- backgroundColor: '#fff',
12
- padding: '20px',
13
- borderRadius: '8px',
14
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
104
+ padding: '10px 20px',
105
+ backgroundColor: '#007bff',
106
+ color: '#ffffff',
107
+ border: 'none',
108
+ borderRadius: '4px',
109
+ cursor: 'pointer',
15
110
  zIndex: 1000,
16
- };
17
- const overlayStyle = {
18
- position: 'fixed',
19
- top: 0,
20
- left: 0,
21
- right: 0,
22
- bottom: 0,
23
- backgroundColor: 'rgba(0,0,0,0.5)',
24
- zIndex: 999,
25
- };
26
- return (React.createElement(React.Fragment, null,
27
- React.createElement("div", { style: overlayStyle, onClick: onClose }),
28
- React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "achievement-title" },
29
- React.createElement("h2", { id: "achievement-title" }, "Achievement Unlocked!"),
30
- React.createElement("img", { src: achievement.icon, alt: achievement.title, style: { width: '50px', height: '50px' } }),
31
- React.createElement("h3", null, achievement.title),
32
- React.createElement("p", null, achievement.description),
33
- React.createElement("button", { onClick: onClose }, "Okay"))));
111
+ },
112
+ };
113
+
114
+ const AchievementModal = ({ isOpen, achievement, onClose, styles }) => {
115
+ if (!isOpen || !achievement)
116
+ return null;
117
+ return (React.createElement("div", { style: styles.overlay },
118
+ React.createElement("div", { style: styles.content },
119
+ React.createElement("h2", { style: styles.title }, "Achievement Unlocked!"),
120
+ React.createElement("img", { src: achievement.icon, alt: achievement.title, style: styles.icon }),
121
+ React.createElement("h3", { style: styles.title }, achievement.title),
122
+ React.createElement("p", { style: styles.description }, achievement.description),
123
+ React.createElement("button", { onClick: onClose, style: styles.button }, "Okay"))));
34
124
  };
35
125
  var AchievementModal$1 = React.memo(AchievementModal);
36
126
 
37
- const BadgesModal = ({ show, achievements, onClose }) => {
38
- if (!show)
127
+ const BadgesModal = ({ isOpen, achievements, onClose, styles }) => {
128
+ if (!isOpen)
39
129
  return null;
40
- const modalStyle = {
41
- position: 'fixed',
42
- top: '50%',
43
- left: '50%',
44
- transform: 'translate(-50%, -50%)',
45
- backgroundColor: '#fff',
46
- padding: '20px',
47
- borderRadius: '8px',
48
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
49
- zIndex: 1000,
50
- maxWidth: '80%',
51
- maxHeight: '80%',
52
- overflow: 'auto',
53
- };
54
- const overlayStyle = {
55
- position: 'fixed',
56
- top: 0,
57
- left: 0,
58
- right: 0,
59
- bottom: 0,
60
- backgroundColor: 'rgba(0,0,0,0.5)',
61
- zIndex: 999,
62
- };
63
- return (React.createElement(React.Fragment, null,
64
- React.createElement("div", { style: overlayStyle, onClick: onClose }),
65
- React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "badges-title" },
66
- React.createElement("h2", { id: "badges-title" }, "Your Achievements"),
67
- React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'center' } }, achievements.map(achievement => (React.createElement("div", { key: achievement.id, style: { margin: '10px', textAlign: 'center' } },
68
- React.createElement("img", { src: achievement.icon, alt: achievement.title, style: { width: '50px', height: '50px' } }),
69
- React.createElement("h4", null, achievement.title))))),
70
- React.createElement("button", { onClick: onClose, style: { marginTop: '20px' } }, "Close"))));
130
+ return (React.createElement("div", { style: styles.overlay },
131
+ React.createElement("div", { style: styles.content },
132
+ React.createElement("h2", { style: styles.title }, "Your Achievements"),
133
+ React.createElement("div", { style: styles.badgeContainer }, achievements.map((achievement) => (React.createElement("div", { key: achievement.id, style: styles.badge },
134
+ React.createElement("img", { src: achievement.icon, alt: achievement.title, style: styles.badgeIcon }),
135
+ React.createElement("span", { style: styles.badgeTitle }, achievement.title))))),
136
+ React.createElement("button", { onClick: onClose, style: styles.button }, "Close"))));
71
137
  };
72
138
  var BadgesModal$1 = React.memo(BadgesModal);
73
139
 
74
- const BadgesButton = ({ onClick, position }) => {
75
- const buttonStyle = {
76
- position: 'fixed',
140
+ const BadgesButton = ({ onClick, position, styles }) => {
141
+ const positionStyle = {
77
142
  [position.split('-')[0]]: '20px',
78
143
  [position.split('-')[1]]: '20px',
79
- padding: '10px 20px',
80
- backgroundColor: '#007bff',
81
- color: '#fff',
82
- border: 'none',
83
- borderRadius: '5px',
84
- cursor: 'pointer',
85
- zIndex: 998,
86
144
  };
87
- return (React.createElement("button", { style: buttonStyle, onClick: onClick }, "View Achievements"));
145
+ return (React.createElement("button", { onClick: onClick, style: Object.assign(Object.assign({}, styles), positionStyle) }, "View Achievements"));
88
146
  };
89
147
  var BadgesButton$1 = React.memo(BadgesButton);
90
148
 
@@ -174,35 +232,26 @@ var useWindowSize = function (initialWidth, initialHeight) {
174
232
  return state;
175
233
  };
176
234
 
177
- const ConfettiWrapper = ({ show, confettiProps }) => {
235
+ const ConfettiWrapper = ({ show }) => {
178
236
  const { width, height } = useWindowSize();
179
237
  if (!show)
180
238
  return null;
181
- return (React.createElement(Confetti, Object.assign({ width: width, height: height }, confettiProps)));
239
+ return React.createElement(Confetti, { width: width, height: height, recycle: false });
182
240
  };
183
241
 
184
242
  const AchievementContext = createContext(undefined);
185
- const AchievementProvider = ({ children, config, initialState = {}, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
186
- const extractMetrics = (state) => {
187
- return Object.keys(config).reduce((acc, key) => {
188
- if (key in state) {
189
- acc[key] = state[key];
190
- }
191
- else {
192
- acc[key] = [];
193
- }
194
- return acc;
195
- }, {});
196
- };
243
+ const AchievementProvider = ({ children, config, initialState = {}, storageKey = 'react-achievements', badgesButtonPosition = 'top-right', styles = {}, }) => {
244
+ const mergedStyles = React.useMemo(() => mergeDeep(defaultStyles, styles), [styles]);
197
245
  const [metrics, setMetrics] = useState(() => {
198
246
  const savedMetrics = localStorage.getItem(`${storageKey}-metrics`);
199
- if (savedMetrics) {
200
- return JSON.parse(savedMetrics);
201
- }
202
- return extractMetrics(initialState);
247
+ return savedMetrics ? JSON.parse(savedMetrics) : initialState;
203
248
  });
204
- const [achievedAchievements, setAchievedAchievements] = useState(() => {
205
- const saved = localStorage.getItem(`${storageKey}-achievements`);
249
+ const [unlockedAchievements, setUnlockedAchievements] = useState(() => {
250
+ const saved = localStorage.getItem(`${storageKey}-unlocked-achievements`);
251
+ return saved ? JSON.parse(saved) : [];
252
+ });
253
+ const [newlyUnlockedAchievements, setNewlyUnlockedAchievements] = useState(() => {
254
+ const saved = localStorage.getItem(`${storageKey}-newly-unlocked-achievements`);
206
255
  return saved ? JSON.parse(saved) : [];
207
256
  });
208
257
  const [achievementQueue, setAchievementQueue] = useState([]);
@@ -214,31 +263,43 @@ const AchievementProvider = ({ children, config, initialState = {}, storageKey =
214
263
  Object.entries(config).forEach(([metricKey, conditions]) => {
215
264
  const metricValue = metrics[metricKey];
216
265
  conditions.forEach(condition => {
217
- if (condition.check(metricValue) && !achievedAchievements.includes(condition.data.id)) {
266
+ if (condition.check(metricValue) && !unlockedAchievements.includes(condition.data.id)) {
218
267
  newAchievements.push(condition.data);
219
268
  }
220
269
  });
221
270
  });
222
271
  if (newAchievements.length > 0) {
223
- const updatedAchievements = [...achievedAchievements, ...newAchievements.map(a => a.id)];
224
- setAchievedAchievements(updatedAchievements);
225
- localStorage.setItem(`${storageKey}-achievements`, JSON.stringify(updatedAchievements));
272
+ const newlyUnlockedIds = newAchievements.map(a => a.id);
273
+ setUnlockedAchievements(prev => {
274
+ const updated = [...prev, ...newlyUnlockedIds];
275
+ localStorage.setItem(`${storageKey}-unlocked-achievements`, JSON.stringify(updated));
276
+ return updated;
277
+ });
278
+ setNewlyUnlockedAchievements(prev => {
279
+ const updated = [...prev, ...newlyUnlockedIds];
280
+ localStorage.setItem(`${storageKey}-newly-unlocked-achievements`, JSON.stringify(updated));
281
+ return updated;
282
+ });
226
283
  setAchievementQueue(prevQueue => [...prevQueue, ...newAchievements]);
227
284
  setShowConfetti(true);
228
285
  }
229
- }, [config, metrics, achievedAchievements, storageKey]);
286
+ }, [config, metrics, unlockedAchievements, storageKey]);
230
287
  useEffect(() => {
231
288
  checkAchievements();
232
289
  }, [metrics, checkAchievements]);
233
290
  useEffect(() => {
234
291
  if (achievementQueue.length > 0 && !currentAchievement) {
235
- setCurrentAchievement(achievementQueue[0]);
292
+ const nextAchievement = achievementQueue[0];
293
+ setCurrentAchievement(nextAchievement);
236
294
  setAchievementQueue(prevQueue => prevQueue.slice(1));
295
+ setNewlyUnlockedAchievements(prev => {
296
+ const updated = prev.filter(id => id !== nextAchievement.id);
297
+ localStorage.setItem(`${storageKey}-newly-unlocked-achievements`, JSON.stringify(updated));
298
+ return updated;
299
+ });
237
300
  }
238
- }, [achievementQueue, currentAchievement]);
239
- const showBadgesModal = () => {
240
- setShowBadges(true);
241
- };
301
+ }, [achievementQueue, currentAchievement, storageKey]);
302
+ const showBadgesModal = () => setShowBadges(true);
242
303
  const getAchievements = (achievedIds) => {
243
304
  return Object.values(config).flatMap(conditions => conditions.filter(c => achievedIds.includes(c.data.id)).map(c => c.data));
244
305
  };
@@ -251,20 +312,20 @@ const AchievementProvider = ({ children, config, initialState = {}, storageKey =
251
312
  return updatedMetrics;
252
313
  });
253
314
  },
254
- achievedAchievements,
315
+ unlockedAchievements,
255
316
  checkAchievements,
256
317
  showBadgesModal
257
318
  };
258
319
  return (React.createElement(AchievementContext.Provider, { value: contextValue },
259
320
  children,
260
- React.createElement(AchievementModal$1, { show: !!currentAchievement, achievement: currentAchievement, onClose: () => {
321
+ React.createElement(AchievementModal$1, { isOpen: !!currentAchievement, achievement: currentAchievement, onClose: () => {
261
322
  setCurrentAchievement(null);
262
- if (achievementQueue.length === 0) {
323
+ if (achievementQueue.length === 0 && newlyUnlockedAchievements.length === 0) {
263
324
  setShowConfetti(false);
264
325
  }
265
- } }),
266
- React.createElement(BadgesModal$1, { show: showBadges, achievements: getAchievements(achievedAchievements), onClose: () => setShowBadges(false) }),
267
- React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition }),
326
+ }, styles: mergedStyles.achievementModal }),
327
+ React.createElement(BadgesModal$1, { isOpen: showBadges, achievements: getAchievements(unlockedAchievements), onClose: () => setShowBadges(false), styles: mergedStyles.badgesModal }),
328
+ React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition, styles: mergedStyles.badgesButton }),
268
329
  React.createElement(ConfettiWrapper, { show: showConfetti || achievementQueue.length > 0 })));
269
330
  };
270
331
  const useAchievement = () => {
@@ -274,5 +335,26 @@ const useAchievement = () => {
274
335
  }
275
336
  return context;
276
337
  };
338
+ // Helper function to deep merge objects
339
+ function mergeDeep(target, source) {
340
+ const output = Object.assign({}, target);
341
+ if (isObject(target) && isObject(source)) {
342
+ Object.keys(source).forEach(key => {
343
+ if (isObject(source[key])) {
344
+ if (!(key in target))
345
+ Object.assign(output, { [key]: source[key] });
346
+ else
347
+ output[key] = mergeDeep(target[key], source[key]);
348
+ }
349
+ else {
350
+ Object.assign(output, { [key]: source[key] });
351
+ }
352
+ });
353
+ }
354
+ return output;
355
+ }
356
+ function isObject(item) {
357
+ return (item && typeof item === 'object' && !Array.isArray(item));
358
+ }
277
359
 
278
360
  export { AchievementProvider, ConfettiWrapper, useAchievement };
package/dist/types.d.ts CHANGED
@@ -1,18 +1,17 @@
1
- export type MetricValueItem = number | boolean | string | any;
2
- export type MetricValue = MetricValueItem[];
3
- export interface Metrics {
4
- [key: string]: MetricValue;
5
- }
6
1
  export interface AchievementData {
7
2
  id: string;
8
3
  title: string;
9
4
  description: string;
10
5
  icon: string;
11
6
  }
7
+ export type MetricValue = number | string | boolean | Date;
8
+ export interface Metrics {
9
+ [key: string]: MetricValue[];
10
+ }
12
11
  export interface AchievementCondition {
13
- check: (metricValue: MetricValue) => boolean;
12
+ check: (value: MetricValue[]) => boolean;
14
13
  data: AchievementData;
15
14
  }
16
15
  export interface AchievementConfig {
17
- [metricKey: string]: AchievementCondition[];
16
+ [key: string]: AchievementCondition[];
18
17
  }
@@ -0,0 +1,6 @@
1
+ import EventEmitter from 'eventemitter3';
2
+ interface EventTypes {
3
+ checkAchievements: (payload: Record<string, any>) => void;
4
+ }
5
+ declare const reactAchievementsEventEmitter: EventEmitter<EventTypes, any>;
6
+ export { reactAchievementsEventEmitter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-achievements",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "This package allows users to transpose a React achievements engine over their React apps",
5
5
  "keywords": [
6
6
  "react",
@@ -1,48 +1,27 @@
1
1
  import React from 'react';
2
2
  import { AchievementData } from '../types';
3
+ import { Styles } from '../defaultStyles';
3
4
 
4
5
  interface AchievementModalProps {
5
- show: boolean;
6
+ isOpen: boolean;
6
7
  achievement: AchievementData | null;
7
8
  onClose: () => void;
9
+ styles: Styles['achievementModal'];
8
10
  }
9
11
 
10
- const AchievementModal: React.FC<AchievementModalProps> = ({ show, achievement, onClose }) => {
11
- if (!show || !achievement) return null;
12
-
13
- const modalStyle: React.CSSProperties = {
14
- position: 'fixed',
15
- top: '50%',
16
- left: '50%',
17
- transform: 'translate(-50%, -50%)',
18
- backgroundColor: '#fff',
19
- padding: '20px',
20
- borderRadius: '8px',
21
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
22
- zIndex: 1000,
23
- };
24
-
25
- const overlayStyle: React.CSSProperties = {
26
- position: 'fixed',
27
- top: 0,
28
- left: 0,
29
- right: 0,
30
- bottom: 0,
31
- backgroundColor: 'rgba(0,0,0,0.5)',
32
- zIndex: 999,
33
- };
12
+ const AchievementModal: React.FC<AchievementModalProps> = ({ isOpen, achievement, onClose, styles }) => {
13
+ if (!isOpen || !achievement) return null;
34
14
 
35
15
  return (
36
- <>
37
- <div style={overlayStyle} onClick={onClose} />
38
- <div style={modalStyle} role="dialog" aria-modal="true" aria-labelledby="achievement-title">
39
- <h2 id="achievement-title">Achievement Unlocked!</h2>
40
- <img src={achievement.icon} alt={achievement.title} style={{ width: '50px', height: '50px' }} />
41
- <h3>{achievement.title}</h3>
42
- <p>{achievement.description}</p>
43
- <button onClick={onClose}>Okay</button>
16
+ <div style={styles.overlay as React.CSSProperties}>
17
+ <div style={styles.content}>
18
+ <h2 style={styles.title}>Achievement Unlocked!</h2>
19
+ <img src={achievement.icon} alt={achievement.title} style={styles.icon} />
20
+ <h3 style={styles.title}>{achievement.title}</h3>
21
+ <p style={styles.description}>{achievement.description}</p>
22
+ <button onClick={onClose} style={styles.button}>Okay</button>
44
23
  </div>
45
- </>
24
+ </div>
46
25
  );
47
26
  };
48
27
 
@@ -1,26 +1,20 @@
1
1
  import React from 'react';
2
+ import { Styles } from '../defaultStyles';
2
3
 
3
4
  interface BadgesButtonProps {
4
5
  onClick: () => void;
5
6
  position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ styles: Styles['badgesButton'];
6
8
  }
7
9
 
8
- const BadgesButton: React.FC<BadgesButtonProps> = ({ onClick, position }) => {
9
- const buttonStyle: React.CSSProperties = {
10
- position: 'fixed',
10
+ const BadgesButton: React.FC<BadgesButtonProps> = ({ onClick, position, styles }) => {
11
+ const positionStyle = {
11
12
  [position.split('-')[0]]: '20px',
12
13
  [position.split('-')[1]]: '20px',
13
- padding: '10px 20px',
14
- backgroundColor: '#007bff',
15
- color: '#fff',
16
- border: 'none',
17
- borderRadius: '5px',
18
- cursor: 'pointer',
19
- zIndex: 998,
20
14
  };
21
15
 
22
16
  return (
23
- <button style={buttonStyle} onClick={onClick}>
17
+ <button onClick={onClick} style={{ ...styles, ...positionStyle }}>
24
18
  View Achievements
25
19
  </button>
26
20
  );
@@ -1,56 +1,32 @@
1
1
  import React from 'react';
2
2
  import { AchievementData } from '../types';
3
+ import { Styles } from '../defaultStyles';
3
4
 
4
5
  interface BadgesModalProps {
5
- show: boolean;
6
+ isOpen: boolean;
6
7
  achievements: AchievementData[];
7
8
  onClose: () => void;
9
+ styles: Styles['badgesModal'];
8
10
  }
9
11
 
10
- const BadgesModal: React.FC<BadgesModalProps> = ({ show, achievements, onClose }) => {
11
- if (!show) return null;
12
-
13
- const modalStyle: React.CSSProperties = {
14
- position: 'fixed',
15
- top: '50%',
16
- left: '50%',
17
- transform: 'translate(-50%, -50%)',
18
- backgroundColor: '#fff',
19
- padding: '20px',
20
- borderRadius: '8px',
21
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
22
- zIndex: 1000,
23
- maxWidth: '80%',
24
- maxHeight: '80%',
25
- overflow: 'auto',
26
- };
27
-
28
- const overlayStyle: React.CSSProperties = {
29
- position: 'fixed',
30
- top: 0,
31
- left: 0,
32
- right: 0,
33
- bottom: 0,
34
- backgroundColor: 'rgba(0,0,0,0.5)',
35
- zIndex: 999,
36
- };
12
+ const BadgesModal: React.FC<BadgesModalProps> = ({ isOpen, achievements, onClose, styles }) => {
13
+ if (!isOpen) return null;
37
14
 
38
15
  return (
39
- <>
40
- <div style={overlayStyle} onClick={onClose} />
41
- <div style={modalStyle} role="dialog" aria-modal="true" aria-labelledby="badges-title">
42
- <h2 id="badges-title">Your Achievements</h2>
43
- <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
44
- {achievements.map(achievement => (
45
- <div key={achievement.id} style={{ margin: '10px', textAlign: 'center' }}>
46
- <img src={achievement.icon} alt={achievement.title} style={{ width: '50px', height: '50px' }} />
47
- <h4>{achievement.title}</h4>
16
+ <div style={styles.overlay as React.CSSProperties}>
17
+ <div style={styles.content}>
18
+ <h2 style={styles.title}>Your Achievements</h2>
19
+ <div style={styles.badgeContainer}>
20
+ {achievements.map((achievement) => (
21
+ <div key={achievement.id} style={styles.badge}>
22
+ <img src={achievement.icon} alt={achievement.title} style={styles.badgeIcon} />
23
+ <span style={styles.badgeTitle}>{achievement.title}</span>
48
24
  </div>
49
25
  ))}
50
26
  </div>
51
- <button onClick={onClose} style={{ marginTop: '20px' }}>Close</button>
27
+ <button onClick={onClose} style={styles.button}>Close</button>
52
28
  </div>
53
- </>
29
+ </div>
54
30
  );
55
31
  };
56
32
 
@@ -1,25 +1,17 @@
1
1
  import React from 'react';
2
2
  import Confetti from 'react-confetti';
3
3
  import { useWindowSize } from 'react-use';
4
- import { ConfettiProps } from 'react-confetti';
5
4
 
6
5
  interface ConfettiWrapperProps {
7
6
  show: boolean;
8
- confettiProps?: Partial<ConfettiProps>;
9
7
  }
10
8
 
11
- const ConfettiWrapper: React.FC<ConfettiWrapperProps> = ({ show, confettiProps }) => {
9
+ const ConfettiWrapper: React.FC<ConfettiWrapperProps> = ({ show }) => {
12
10
  const { width, height } = useWindowSize();
13
11
 
14
12
  if (!show) return null;
15
13
 
16
- return (
17
- <Confetti
18
- width={width}
19
- height={height}
20
- {...confettiProps}
21
- />
22
- );
14
+ return <Confetti width={width} height={height} recycle={false} />;
23
15
  };
24
16
 
25
17
  export default ConfettiWrapper;