react-achievements 1.0.2 → 1.0.4

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/badges.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- interface BadgeConfig {
1
+ export interface BadgeConfig {
2
2
  id: string;
3
3
  icon: string;
4
4
  title: string;
5
5
  description: string;
6
6
  }
7
7
  declare const defaultBadges: BadgeConfig[];
8
- export { defaultBadges, BadgeConfig };
8
+ export { defaultBadges };
@@ -6,5 +6,5 @@ interface AchievementProps {
6
6
  message: string;
7
7
  children: React.ReactNode;
8
8
  }
9
- declare const Achievement: React.FC<AchievementProps>;
10
- export default Achievement;
9
+ declare const _default: React.NamedExoticComponent<AchievementProps>;
10
+ export default _default;
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
+ import { AchievementData } from '../types';
2
3
  interface AchievementModalProps {
3
4
  show: boolean;
4
- message: string;
5
+ achievement: AchievementData | null;
5
6
  onClose: () => void;
6
7
  }
7
- declare const AchievementModal: React.FC<AchievementModalProps>;
8
- export default AchievementModal;
8
+ declare const _default: React.NamedExoticComponent<AchievementModalProps>;
9
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface BadgesButtonProps {
3
+ onClick: () => void;
4
+ position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
5
+ }
6
+ declare const _default: React.NamedExoticComponent<BadgesButtonProps>;
7
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { AchievementData } from '../types';
3
+ interface BadgesModalProps {
4
+ show: boolean;
5
+ achievements: AchievementData[];
6
+ onClose: () => void;
7
+ }
8
+ declare const _default: React.NamedExoticComponent<BadgesModalProps>;
9
+ export default _default;
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
+ import { ConfettiProps } from 'react-confetti';
2
3
  interface ConfettiWrapperProps {
3
4
  show: boolean;
5
+ confettiProps?: Partial<ConfettiProps>;
4
6
  }
5
7
  declare const ConfettiWrapper: React.FC<ConfettiWrapperProps>;
6
8
  export default ConfettiWrapper;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface ProgressProps {
3
+ style?: React.CSSProperties;
4
+ }
5
+ declare const _default: React.NamedExoticComponent<ProgressProps>;
6
+ export default _default;
@@ -1,16 +1,18 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { BadgeConfig } from '../badges';
3
- import { LevelConfig } from '../levels';
2
+ import { Metrics, AchievementConfig } from '../types';
4
3
  interface AchievementContextProps {
5
- metric: number;
6
- setMetric: (value: number) => void;
7
- badges: BadgeConfig[];
8
- levels: LevelConfig[];
9
- achievedLevels: number[];
10
- handleAchieve: (level: number, message: string) => void;
4
+ metrics: Metrics;
5
+ setMetrics: (metrics: Metrics) => void;
6
+ achievedAchievements: string[];
7
+ checkAchievements: () => void;
8
+ showBadgesModal: () => void;
11
9
  }
12
- declare const AchievementProvider: React.FC<{
10
+ interface AchievementProviderProps {
13
11
  children: ReactNode;
14
- }>;
15
- declare const useAchievement: () => AchievementContextProps;
16
- export { AchievementProvider, useAchievement };
12
+ config: AchievementConfig;
13
+ storageKey?: string;
14
+ badgesButtonPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
15
+ }
16
+ export declare const AchievementProvider: React.FC<AchievementProviderProps>;
17
+ export declare const useAchievement: () => AchievementContextProps;
18
+ export {};
package/dist/index.cjs.js CHANGED
@@ -2,27 +2,94 @@
2
2
 
3
3
  var React = require('react');
4
4
 
5
- const defaultBadges = [
6
- {
7
- id: 'beginner',
8
- icon: '/path/to/beginner-icon.png',
9
- title: 'Beginner',
10
- description: 'Achieved beginner level',
11
- },
12
- {
13
- id: 'intermediate',
14
- icon: '/path/to/intermediate-icon.png',
15
- title: 'Intermediate',
16
- description: 'Achieved intermediate level',
17
- },
18
- // Add more badges as needed
19
- ];
20
-
21
- const levels = [
22
- { level: 1, threshold: 10, badgeId: 'beginner' },
23
- { level: 2, threshold: 50, badgeId: 'intermediate' },
24
- // Add more levels as needed
25
- ];
5
+ const AchievementModal = ({ show, achievement, onClose }) => {
6
+ if (!show || !achievement)
7
+ return null;
8
+ const modalStyle = {
9
+ position: 'fixed',
10
+ top: '50%',
11
+ left: '50%',
12
+ transform: 'translate(-50%, -50%)',
13
+ backgroundColor: '#fff',
14
+ padding: '20px',
15
+ borderRadius: '8px',
16
+ boxShadow: '0 0 10px rgba(0,0,0,0.1)',
17
+ zIndex: 1000,
18
+ };
19
+ const overlayStyle = {
20
+ position: 'fixed',
21
+ top: 0,
22
+ left: 0,
23
+ right: 0,
24
+ bottom: 0,
25
+ backgroundColor: 'rgba(0,0,0,0.5)',
26
+ zIndex: 999,
27
+ };
28
+ return (React.createElement(React.Fragment, null,
29
+ React.createElement("div", { style: overlayStyle, onClick: onClose }),
30
+ React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "achievement-title" },
31
+ React.createElement("h2", { id: "achievement-title" }, "Achievement Unlocked!"),
32
+ React.createElement("img", { src: achievement.icon, alt: achievement.title, style: { width: '50px', height: '50px' } }),
33
+ React.createElement("h3", null, achievement.title),
34
+ React.createElement("p", null, achievement.description),
35
+ React.createElement("button", { onClick: onClose }, "Okay"))));
36
+ };
37
+ var AchievementModal$1 = React.memo(AchievementModal);
38
+
39
+ const BadgesModal = ({ show, achievements, onClose }) => {
40
+ if (!show)
41
+ return null;
42
+ const modalStyle = {
43
+ position: 'fixed',
44
+ top: '50%',
45
+ left: '50%',
46
+ transform: 'translate(-50%, -50%)',
47
+ backgroundColor: '#fff',
48
+ padding: '20px',
49
+ borderRadius: '8px',
50
+ boxShadow: '0 0 10px rgba(0,0,0,0.1)',
51
+ zIndex: 1000,
52
+ maxWidth: '80%',
53
+ maxHeight: '80%',
54
+ overflow: 'auto',
55
+ };
56
+ const overlayStyle = {
57
+ position: 'fixed',
58
+ top: 0,
59
+ left: 0,
60
+ right: 0,
61
+ bottom: 0,
62
+ backgroundColor: 'rgba(0,0,0,0.5)',
63
+ zIndex: 999,
64
+ };
65
+ return (React.createElement(React.Fragment, null,
66
+ React.createElement("div", { style: overlayStyle, onClick: onClose }),
67
+ React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "badges-title" },
68
+ React.createElement("h2", { id: "badges-title" }, "Your Achievements"),
69
+ React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'center' } }, achievements.map(achievement => (React.createElement("div", { key: achievement.id, style: { margin: '10px', textAlign: 'center' } },
70
+ React.createElement("img", { src: achievement.icon, alt: achievement.title, style: { width: '50px', height: '50px' } }),
71
+ React.createElement("h4", null, achievement.title),
72
+ React.createElement("p", null, achievement.description))))),
73
+ React.createElement("button", { onClick: onClose, style: { marginTop: '20px' } }, "Close"))));
74
+ };
75
+ var BadgesModal$1 = React.memo(BadgesModal);
76
+
77
+ const BadgesButton = ({ onClick, position }) => {
78
+ const buttonStyle = {
79
+ position: 'fixed',
80
+ [position.split('-')[0]]: '20px',
81
+ [position.split('-')[1]]: '20px',
82
+ padding: '10px 20px',
83
+ backgroundColor: '#007bff',
84
+ color: '#fff',
85
+ border: 'none',
86
+ borderRadius: '5px',
87
+ cursor: 'pointer',
88
+ zIndex: 998,
89
+ };
90
+ return (React.createElement("button", { style: buttonStyle, onClick: onClick }, "View Achievements"));
91
+ };
92
+ var BadgesButton$1 = React.memo(BadgesButton);
26
93
 
27
94
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
28
95
 
@@ -110,87 +177,63 @@ var useWindowSize = function (initialWidth, initialHeight) {
110
177
  return state;
111
178
  };
112
179
 
113
- const ConfettiWrapper = ({ show }) => {
180
+ const ConfettiWrapper = ({ show, confettiProps }) => {
114
181
  const { width, height } = useWindowSize();
115
- return show ? React.createElement(Confetti, { width: width, height: height }) : null;
116
- };
117
-
118
- const AchievementModal = ({ show, message, onClose }) => {
119
182
  if (!show)
120
183
  return null;
121
- const modalStyle = {
122
- position: 'fixed',
123
- top: '50%',
124
- left: '50%',
125
- transform: 'translate(-50%, -50%)',
126
- backgroundColor: '#fff',
127
- padding: '20px',
128
- borderRadius: '8px',
129
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
130
- zIndex: 1000,
131
- };
132
- const overlayStyle = {
133
- position: 'fixed',
134
- top: 0,
135
- left: 0,
136
- right: 0,
137
- bottom: 0,
138
- backgroundColor: 'rgba(0,0,0,0.5)',
139
- zIndex: 999,
140
- };
141
- return (React.createElement(React.Fragment, null,
142
- React.createElement("div", { style: overlayStyle, onClick: onClose }),
143
- React.createElement("div", { style: modalStyle },
144
- React.createElement("h2", null, "Achievement Unlocked!"),
145
- React.createElement("p", null, message),
146
- React.createElement("button", { onClick: onClose }, "Okay"))));
147
- };
148
-
149
- const Badge = ({ icon, title, description, position = 'top-right' }) => {
150
- const badgeStyle = {
151
- position: 'fixed',
152
- [position.split('-')[0]]: '10px',
153
- [position.split('-')[1]]: '10px',
154
- display: 'flex',
155
- flexDirection: 'column',
156
- alignItems: 'center',
157
- backgroundColor: '#fff',
158
- border: '1px solid #ccc',
159
- borderRadius: '8px',
160
- padding: '10px',
161
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
162
- };
163
- return (React.createElement("div", { style: badgeStyle },
164
- React.createElement("img", { src: icon, alt: title, style: { width: '50px', height: '50px' } }),
165
- React.createElement("h4", null, title),
166
- React.createElement("p", null, description)));
184
+ return (React.createElement(Confetti, Object.assign({ width: width, height: height }, confettiProps)));
167
185
  };
168
186
 
169
187
  const AchievementContext = React.createContext(undefined);
170
- const AchievementProvider = ({ children }) => {
171
- const [metric, setMetric] = React.useState(0);
172
- const [achievedLevels, setAchievedLevels] = React.useState([]);
188
+ const AchievementProvider = ({ children, config, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
189
+ const [metrics, setMetrics] = React.useState({});
190
+ const [achievedAchievements, setAchievedAchievements] = React.useState(() => {
191
+ const saved = localStorage.getItem(`${storageKey}-achievements`);
192
+ return saved ? JSON.parse(saved) : [];
193
+ });
194
+ const [newAchievement, setNewAchievement] = React.useState(null);
195
+ const [showBadges, setShowBadges] = React.useState(false);
173
196
  const [showConfetti, setShowConfetti] = React.useState(false);
174
- const [modalMessage, setModalMessage] = React.useState('');
175
- const handleAchieve = (level, message) => {
176
- if (!achievedLevels.includes(level)) {
177
- setAchievedLevels([...achievedLevels, level]);
197
+ const checkAchievements = () => {
198
+ const newAchievements = [];
199
+ Object.entries(config).forEach(([metricKey, conditions]) => {
200
+ const metricValue = metrics[metricKey];
201
+ conditions.forEach(condition => {
202
+ if (condition.check(metricValue) && !achievedAchievements.includes(condition.data.id)) {
203
+ newAchievements.push(condition.data);
204
+ }
205
+ });
206
+ });
207
+ if (newAchievements.length > 0) {
208
+ const updatedAchievements = [...achievedAchievements, ...newAchievements.map(a => a.id)];
209
+ setAchievedAchievements(updatedAchievements);
210
+ localStorage.setItem(`${storageKey}-achievements`, JSON.stringify(updatedAchievements));
211
+ setNewAchievement(newAchievements[0]);
178
212
  setShowConfetti(true);
179
- setModalMessage(message);
180
213
  }
181
214
  };
182
- const handleCloseModal = () => {
183
- setShowConfetti(false);
184
- setModalMessage('');
215
+ const showBadgesModal = () => {
216
+ setShowBadges(true);
217
+ };
218
+ const contextValue = {
219
+ metrics,
220
+ setMetrics: (newMetrics) => {
221
+ setMetrics(newMetrics);
222
+ checkAchievements();
223
+ },
224
+ achievedAchievements,
225
+ checkAchievements,
226
+ showBadgesModal
185
227
  };
186
- return (React.createElement(AchievementContext.Provider, { value: { metric, setMetric, badges: defaultBadges, levels, achievedLevels, handleAchieve } },
228
+ return (React.createElement(AchievementContext.Provider, { value: contextValue },
187
229
  children,
188
- levels.map(levelConfig => {
189
- var _a, _b, _c;
190
- return achievedLevels.includes(levelConfig.level) ? (React.createElement(Badge, { key: levelConfig.level, icon: ((_a = defaultBadges.find(badge => badge.id === levelConfig.badgeId)) === null || _a === void 0 ? void 0 : _a.icon) || '', title: ((_b = defaultBadges.find(badge => badge.id === levelConfig.badgeId)) === null || _b === void 0 ? void 0 : _b.title) || '', description: ((_c = defaultBadges.find(badge => badge.id === levelConfig.badgeId)) === null || _c === void 0 ? void 0 : _c.description) || '' })) : null;
191
- }),
192
- React.createElement(ConfettiWrapper, { show: showConfetti }),
193
- React.createElement(AchievementModal, { show: !!modalMessage, message: modalMessage, onClose: handleCloseModal })));
230
+ React.createElement(AchievementModal$1, { show: !!newAchievement, achievement: newAchievement, onClose: () => {
231
+ setNewAchievement(null);
232
+ setShowConfetti(false);
233
+ } }),
234
+ React.createElement(BadgesModal$1, { show: showBadges, achievements: Object.values(config).flatMap(conditions => conditions.filter(c => achievedAchievements.includes(c.data.id)).map(c => c.data)), onClose: () => setShowBadges(false) }),
235
+ React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition }),
236
+ React.createElement(ConfettiWrapper, { show: showConfetti })));
194
237
  };
195
238
  const useAchievement = () => {
196
239
  const context = React.useContext(AchievementContext);
@@ -200,21 +243,6 @@ const useAchievement = () => {
200
243
  return context;
201
244
  };
202
245
 
203
- const Achievement = ({ metric, threshold, onAchieve, message, children }) => {
204
- const { setMetric, achievedLevels, levels, handleAchieve } = useAchievement();
205
- React.useEffect(() => {
206
- if (metric >= threshold && !achievedLevels.includes(threshold)) {
207
- onAchieve();
208
- const levelConfig = levels.find(level => level.threshold === threshold);
209
- if (levelConfig) {
210
- setMetric(metric);
211
- handleAchieve(levelConfig.level, message);
212
- }
213
- }
214
- }, [metric, threshold, onAchieve, setMetric, achievedLevels, levels, message, handleAchieve]);
215
- return React.createElement("div", null, children);
216
- };
217
-
218
- exports.Achievement = Achievement;
219
246
  exports.AchievementProvider = AchievementProvider;
247
+ exports.ConfettiWrapper = ConfettiWrapper;
220
248
  exports.useAchievement = useAchievement;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import { AchievementProvider, useAchievement } from './context/AchievementContext';
2
- import Achievement from './components/Achievement';
3
- export { AchievementProvider, useAchievement, Achievement };
2
+ import { Metrics, AchievementConfig, AchievementData, AchievementCondition } from './types';
3
+ import ConfettiWrapper from './components/ConfettiWrapper';
4
+ export { AchievementProvider, useAchievement, Metrics, AchievementConfig, AchievementData, AchievementCondition, ConfettiWrapper };
package/dist/index.esm.js CHANGED
@@ -1,26 +1,93 @@
1
1
  import React, { useEffect, useRef, useState, useCallback, createContext, useContext } from 'react';
2
2
 
3
- const defaultBadges = [
4
- {
5
- id: 'beginner',
6
- icon: '/path/to/beginner-icon.png',
7
- title: 'Beginner',
8
- description: 'Achieved beginner level',
9
- },
10
- {
11
- id: 'intermediate',
12
- icon: '/path/to/intermediate-icon.png',
13
- title: 'Intermediate',
14
- description: 'Achieved intermediate level',
15
- },
16
- // Add more badges as needed
17
- ];
18
-
19
- const levels = [
20
- { level: 1, threshold: 10, badgeId: 'beginner' },
21
- { level: 2, threshold: 50, badgeId: 'intermediate' },
22
- // Add more levels as needed
23
- ];
3
+ const AchievementModal = ({ show, achievement, onClose }) => {
4
+ if (!show || !achievement)
5
+ return null;
6
+ const modalStyle = {
7
+ 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)',
15
+ 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"))));
34
+ };
35
+ var AchievementModal$1 = React.memo(AchievementModal);
36
+
37
+ const BadgesModal = ({ show, achievements, onClose }) => {
38
+ if (!show)
39
+ 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("p", null, achievement.description))))),
71
+ React.createElement("button", { onClick: onClose, style: { marginTop: '20px' } }, "Close"))));
72
+ };
73
+ var BadgesModal$1 = React.memo(BadgesModal);
74
+
75
+ const BadgesButton = ({ onClick, position }) => {
76
+ const buttonStyle = {
77
+ position: 'fixed',
78
+ [position.split('-')[0]]: '20px',
79
+ [position.split('-')[1]]: '20px',
80
+ padding: '10px 20px',
81
+ backgroundColor: '#007bff',
82
+ color: '#fff',
83
+ border: 'none',
84
+ borderRadius: '5px',
85
+ cursor: 'pointer',
86
+ zIndex: 998,
87
+ };
88
+ return (React.createElement("button", { style: buttonStyle, onClick: onClick }, "View Achievements"));
89
+ };
90
+ var BadgesButton$1 = React.memo(BadgesButton);
24
91
 
25
92
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
26
93
 
@@ -108,87 +175,63 @@ var useWindowSize = function (initialWidth, initialHeight) {
108
175
  return state;
109
176
  };
110
177
 
111
- const ConfettiWrapper = ({ show }) => {
178
+ const ConfettiWrapper = ({ show, confettiProps }) => {
112
179
  const { width, height } = useWindowSize();
113
- return show ? React.createElement(Confetti, { width: width, height: height }) : null;
114
- };
115
-
116
- const AchievementModal = ({ show, message, onClose }) => {
117
180
  if (!show)
118
181
  return null;
119
- const modalStyle = {
120
- position: 'fixed',
121
- top: '50%',
122
- left: '50%',
123
- transform: 'translate(-50%, -50%)',
124
- backgroundColor: '#fff',
125
- padding: '20px',
126
- borderRadius: '8px',
127
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
128
- zIndex: 1000,
129
- };
130
- const overlayStyle = {
131
- position: 'fixed',
132
- top: 0,
133
- left: 0,
134
- right: 0,
135
- bottom: 0,
136
- backgroundColor: 'rgba(0,0,0,0.5)',
137
- zIndex: 999,
138
- };
139
- return (React.createElement(React.Fragment, null,
140
- React.createElement("div", { style: overlayStyle, onClick: onClose }),
141
- React.createElement("div", { style: modalStyle },
142
- React.createElement("h2", null, "Achievement Unlocked!"),
143
- React.createElement("p", null, message),
144
- React.createElement("button", { onClick: onClose }, "Okay"))));
145
- };
146
-
147
- const Badge = ({ icon, title, description, position = 'top-right' }) => {
148
- const badgeStyle = {
149
- position: 'fixed',
150
- [position.split('-')[0]]: '10px',
151
- [position.split('-')[1]]: '10px',
152
- display: 'flex',
153
- flexDirection: 'column',
154
- alignItems: 'center',
155
- backgroundColor: '#fff',
156
- border: '1px solid #ccc',
157
- borderRadius: '8px',
158
- padding: '10px',
159
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
160
- };
161
- return (React.createElement("div", { style: badgeStyle },
162
- React.createElement("img", { src: icon, alt: title, style: { width: '50px', height: '50px' } }),
163
- React.createElement("h4", null, title),
164
- React.createElement("p", null, description)));
182
+ return (React.createElement(Confetti, Object.assign({ width: width, height: height }, confettiProps)));
165
183
  };
166
184
 
167
185
  const AchievementContext = createContext(undefined);
168
- const AchievementProvider = ({ children }) => {
169
- const [metric, setMetric] = useState(0);
170
- const [achievedLevels, setAchievedLevels] = useState([]);
186
+ const AchievementProvider = ({ children, config, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
187
+ const [metrics, setMetrics] = useState({});
188
+ const [achievedAchievements, setAchievedAchievements] = useState(() => {
189
+ const saved = localStorage.getItem(`${storageKey}-achievements`);
190
+ return saved ? JSON.parse(saved) : [];
191
+ });
192
+ const [newAchievement, setNewAchievement] = useState(null);
193
+ const [showBadges, setShowBadges] = useState(false);
171
194
  const [showConfetti, setShowConfetti] = useState(false);
172
- const [modalMessage, setModalMessage] = useState('');
173
- const handleAchieve = (level, message) => {
174
- if (!achievedLevels.includes(level)) {
175
- setAchievedLevels([...achievedLevels, level]);
195
+ const checkAchievements = () => {
196
+ const newAchievements = [];
197
+ Object.entries(config).forEach(([metricKey, conditions]) => {
198
+ const metricValue = metrics[metricKey];
199
+ conditions.forEach(condition => {
200
+ if (condition.check(metricValue) && !achievedAchievements.includes(condition.data.id)) {
201
+ newAchievements.push(condition.data);
202
+ }
203
+ });
204
+ });
205
+ if (newAchievements.length > 0) {
206
+ const updatedAchievements = [...achievedAchievements, ...newAchievements.map(a => a.id)];
207
+ setAchievedAchievements(updatedAchievements);
208
+ localStorage.setItem(`${storageKey}-achievements`, JSON.stringify(updatedAchievements));
209
+ setNewAchievement(newAchievements[0]);
176
210
  setShowConfetti(true);
177
- setModalMessage(message);
178
211
  }
179
212
  };
180
- const handleCloseModal = () => {
181
- setShowConfetti(false);
182
- setModalMessage('');
213
+ const showBadgesModal = () => {
214
+ setShowBadges(true);
215
+ };
216
+ const contextValue = {
217
+ metrics,
218
+ setMetrics: (newMetrics) => {
219
+ setMetrics(newMetrics);
220
+ checkAchievements();
221
+ },
222
+ achievedAchievements,
223
+ checkAchievements,
224
+ showBadgesModal
183
225
  };
184
- return (React.createElement(AchievementContext.Provider, { value: { metric, setMetric, badges: defaultBadges, levels, achievedLevels, handleAchieve } },
226
+ return (React.createElement(AchievementContext.Provider, { value: contextValue },
185
227
  children,
186
- levels.map(levelConfig => {
187
- var _a, _b, _c;
188
- return achievedLevels.includes(levelConfig.level) ? (React.createElement(Badge, { key: levelConfig.level, icon: ((_a = defaultBadges.find(badge => badge.id === levelConfig.badgeId)) === null || _a === void 0 ? void 0 : _a.icon) || '', title: ((_b = defaultBadges.find(badge => badge.id === levelConfig.badgeId)) === null || _b === void 0 ? void 0 : _b.title) || '', description: ((_c = defaultBadges.find(badge => badge.id === levelConfig.badgeId)) === null || _c === void 0 ? void 0 : _c.description) || '' })) : null;
189
- }),
190
- React.createElement(ConfettiWrapper, { show: showConfetti }),
191
- React.createElement(AchievementModal, { show: !!modalMessage, message: modalMessage, onClose: handleCloseModal })));
228
+ React.createElement(AchievementModal$1, { show: !!newAchievement, achievement: newAchievement, onClose: () => {
229
+ setNewAchievement(null);
230
+ setShowConfetti(false);
231
+ } }),
232
+ React.createElement(BadgesModal$1, { show: showBadges, achievements: Object.values(config).flatMap(conditions => conditions.filter(c => achievedAchievements.includes(c.data.id)).map(c => c.data)), onClose: () => setShowBadges(false) }),
233
+ React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition }),
234
+ React.createElement(ConfettiWrapper, { show: showConfetti })));
192
235
  };
193
236
  const useAchievement = () => {
194
237
  const context = useContext(AchievementContext);
@@ -198,19 +241,4 @@ const useAchievement = () => {
198
241
  return context;
199
242
  };
200
243
 
201
- const Achievement = ({ metric, threshold, onAchieve, message, children }) => {
202
- const { setMetric, achievedLevels, levels, handleAchieve } = useAchievement();
203
- React.useEffect(() => {
204
- if (metric >= threshold && !achievedLevels.includes(threshold)) {
205
- onAchieve();
206
- const levelConfig = levels.find(level => level.threshold === threshold);
207
- if (levelConfig) {
208
- setMetric(metric);
209
- handleAchieve(levelConfig.level, message);
210
- }
211
- }
212
- }, [metric, threshold, onAchieve, setMetric, achievedLevels, levels, message, handleAchieve]);
213
- return React.createElement("div", null, children);
214
- };
215
-
216
- export { Achievement, AchievementProvider, useAchievement };
244
+ export { AchievementProvider, ConfettiWrapper, useAchievement };
package/dist/levels.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- interface LevelConfig {
1
+ export interface LevelConfig {
2
2
  level: number;
3
3
  threshold: number;
4
4
  badgeId: string;
5
5
  }
6
- declare const levels: LevelConfig[];
7
- export { levels, LevelConfig };
6
+ declare const defaultLevels: LevelConfig[];
7
+ export { defaultLevels };
@@ -0,0 +1,17 @@
1
+ export type MetricValue = number | boolean | string | any;
2
+ export interface Metrics {
3
+ [key: string]: MetricValue;
4
+ }
5
+ export interface AchievementData {
6
+ id: string;
7
+ title: string;
8
+ description: string;
9
+ icon: string;
10
+ }
11
+ export interface AchievementCondition {
12
+ check: (metricValue: MetricValue) => boolean;
13
+ data: AchievementData;
14
+ }
15
+ export interface AchievementConfig {
16
+ [metricKey: string]: AchievementCondition[];
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-achievements",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "This package allows users to transpose a React achievements engine over their React apps",
5
5
  "keywords": [
6
6
  "react",
@@ -12,7 +12,8 @@
12
12
  "types": "dist/index.d.ts",
13
13
  "scripts": {
14
14
  "test": "echo \"Error: no test specified\" && exit 1",
15
- "build": "rollup -c"
15
+ "build": "rollup -c",
16
+ "deploy": "npm run build && npm publish"
16
17
  },
17
18
  "author": "David Brown",
18
19
  "license": "ISC",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M560-440q-50 0-85-35t-35-85q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35ZM280-320q-33 0-56.5-23.5T200-400v-320q0-33 23.5-56.5T280-800h560q33 0 56.5 23.5T920-720v320q0 33-23.5 56.5T840-320H280Zm80-80h400q0-33 23.5-56.5T840-480v-160q-33 0-56.5-23.5T760-720H360q0 33-23.5 56.5T280-640v160q33 0 56.5 23.5T360-400Zm440 240H120q-33 0-56.5-23.5T40-240v-440h80v440h680v80ZM280-400v-320 320Z"/></svg>
@@ -1,13 +1,14 @@
1
1
  import React from 'react';
2
+ import { AchievementData } from '../types';
2
3
 
3
4
  interface AchievementModalProps {
4
5
  show: boolean;
5
- message: string;
6
+ achievement: AchievementData | null;
6
7
  onClose: () => void;
7
8
  }
8
9
 
9
- const AchievementModal: React.FC<AchievementModalProps> = ({ show, message, onClose }) => {
10
- if (!show) return null;
10
+ const AchievementModal: React.FC<AchievementModalProps> = ({ show, achievement, onClose }) => {
11
+ if (!show || !achievement) return null;
11
12
 
12
13
  const modalStyle: React.CSSProperties = {
13
14
  position: 'fixed',
@@ -34,13 +35,15 @@ const AchievementModal: React.FC<AchievementModalProps> = ({ show, message, onCl
34
35
  return (
35
36
  <>
36
37
  <div style={overlayStyle} onClick={onClose} />
37
- <div style={modalStyle}>
38
- <h2>Achievement Unlocked!</h2>
39
- <p>{message}</p>
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>
40
43
  <button onClick={onClose}>Okay</button>
41
44
  </div>
42
45
  </>
43
46
  );
44
47
  };
45
48
 
46
- export default AchievementModal;
49
+ export default React.memo(AchievementModal);
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+
3
+ interface BadgesButtonProps {
4
+ onClick: () => void;
5
+ position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
6
+ }
7
+
8
+ const BadgesButton: React.FC<BadgesButtonProps> = ({ onClick, position }) => {
9
+ const buttonStyle: React.CSSProperties = {
10
+ position: 'fixed',
11
+ [position.split('-')[0]]: '20px',
12
+ [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
+ };
21
+
22
+ return (
23
+ <button style={buttonStyle} onClick={onClick}>
24
+ View Achievements
25
+ </button>
26
+ );
27
+ };
28
+
29
+ export default React.memo(BadgesButton);
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { AchievementData } from '../types';
3
+
4
+ interface BadgesModalProps {
5
+ show: boolean;
6
+ achievements: AchievementData[];
7
+ onClose: () => void;
8
+ }
9
+
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
+ };
37
+
38
+ 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>
48
+ <p>{achievement.description}</p>
49
+ </div>
50
+ ))}
51
+ </div>
52
+ <button onClick={onClose} style={{ marginTop: '20px' }}>Close</button>
53
+ </div>
54
+ </>
55
+ );
56
+ };
57
+
58
+ export default React.memo(BadgesModal);
@@ -1,15 +1,25 @@
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';
4
5
 
5
6
  interface ConfettiWrapperProps {
6
7
  show: boolean;
8
+ confettiProps?: Partial<ConfettiProps>;
7
9
  }
8
10
 
9
- const ConfettiWrapper: React.FC<ConfettiWrapperProps> = ({ show }) => {
11
+ const ConfettiWrapper: React.FC<ConfettiWrapperProps> = ({ show, confettiProps }) => {
10
12
  const { width, height } = useWindowSize();
11
13
 
12
- return show ? <Confetti width={width} height={height} /> : null;
14
+ if (!show) return null;
15
+
16
+ return (
17
+ <Confetti
18
+ width={width}
19
+ height={height}
20
+ {...confettiProps}
21
+ />
22
+ );
13
23
  };
14
24
 
15
- export default ConfettiWrapper;
25
+ export default ConfettiWrapper;
@@ -1,65 +1,106 @@
1
1
  import React, { createContext, useContext, useState, ReactNode } from 'react';
2
- import { defaultBadges, BadgeConfig } from '../badges';
3
- import { levels, LevelConfig } from '../levels';
4
- import ConfettiWrapper from '../components/ConfettiWrapper';
2
+ import { Metrics, AchievementConfig, AchievementData } from '../types';
5
3
  import AchievementModal from '../components/AchievementModal';
6
- import Badge from '../components/Badge';
4
+ import BadgesModal from '../components/BadgesModal';
5
+ import BadgesButton from '../components/BadgesButton';
6
+ import ConfettiWrapper from '../components/ConfettiWrapper';
7
7
 
8
8
  interface AchievementContextProps {
9
- metric: number;
10
- setMetric: (value: number) => void;
11
- badges: BadgeConfig[];
12
- levels: LevelConfig[];
13
- achievedLevels: number[];
14
- handleAchieve: (level: number, message: string) => void;
9
+ metrics: Metrics;
10
+ setMetrics: (metrics: Metrics) => void;
11
+ achievedAchievements: string[];
12
+ checkAchievements: () => void;
13
+ showBadgesModal: () => void;
14
+ }
15
+
16
+ interface AchievementProviderProps {
17
+ children: ReactNode;
18
+ config: AchievementConfig;
19
+ storageKey?: string;
20
+ badgesButtonPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
15
21
  }
16
22
 
17
23
  const AchievementContext = createContext<AchievementContextProps | undefined>(undefined);
18
24
 
19
- const AchievementProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
20
- const [metric, setMetric] = useState(0);
21
- const [achievedLevels, setAchievedLevels] = useState<number[]>([]);
25
+ export const AchievementProvider: React.FC<AchievementProviderProps> = ({
26
+ children,
27
+ config,
28
+ storageKey = 'react-achievements',
29
+ badgesButtonPosition = 'top-right'
30
+ }) => {
31
+ const [metrics, setMetrics] = useState<Metrics>({});
32
+ const [achievedAchievements, setAchievedAchievements] = useState<string[]>(() => {
33
+ const saved = localStorage.getItem(`${storageKey}-achievements`);
34
+ return saved ? JSON.parse(saved) : [];
35
+ });
36
+ const [newAchievement, setNewAchievement] = useState<AchievementData | null>(null);
37
+ const [showBadges, setShowBadges] = useState(false);
22
38
  const [showConfetti, setShowConfetti] = useState(false);
23
- const [modalMessage, setModalMessage] = useState('');
24
39
 
25
- const handleAchieve = (level: number, message: string) => {
26
- if (!achievedLevels.includes(level)) {
27
- setAchievedLevels([...achievedLevels, level]);
40
+ const checkAchievements = () => {
41
+ const newAchievements: AchievementData[] = [];
42
+
43
+ Object.entries(config).forEach(([metricKey, conditions]) => {
44
+ const metricValue = metrics[metricKey];
45
+ conditions.forEach(condition => {
46
+ if (condition.check(metricValue) && !achievedAchievements.includes(condition.data.id)) {
47
+ newAchievements.push(condition.data);
48
+ }
49
+ });
50
+ });
51
+
52
+ if (newAchievements.length > 0) {
53
+ const updatedAchievements = [...achievedAchievements, ...newAchievements.map(a => a.id)];
54
+ setAchievedAchievements(updatedAchievements);
55
+ localStorage.setItem(`${storageKey}-achievements`, JSON.stringify(updatedAchievements));
56
+ setNewAchievement(newAchievements[0]);
28
57
  setShowConfetti(true);
29
- setModalMessage(message);
30
58
  }
31
59
  };
32
60
 
33
- const handleCloseModal = () => {
34
- setShowConfetti(false);
35
- setModalMessage('');
61
+ const showBadgesModal = () => {
62
+ setShowBadges(true);
63
+ };
64
+
65
+ const contextValue: AchievementContextProps = {
66
+ metrics,
67
+ setMetrics: (newMetrics: Metrics) => {
68
+ setMetrics(newMetrics);
69
+ checkAchievements();
70
+ },
71
+ achievedAchievements,
72
+ checkAchievements,
73
+ showBadgesModal
36
74
  };
37
75
 
38
76
  return (
39
- <AchievementContext.Provider value={{ metric, setMetric, badges: defaultBadges, levels, achievedLevels, handleAchieve }}>
77
+ <AchievementContext.Provider value={contextValue}>
40
78
  {children}
41
- {levels.map(levelConfig =>
42
- achievedLevels.includes(levelConfig.level) ? (
43
- <Badge
44
- key={levelConfig.level}
45
- icon={defaultBadges.find(badge => badge.id === levelConfig.badgeId)?.icon || ''}
46
- title={defaultBadges.find(badge => badge.id === levelConfig.badgeId)?.title || ''}
47
- description={defaultBadges.find(badge => badge.id === levelConfig.badgeId)?.description || ''}
48
- />
49
- ) : null
50
- )}
79
+ <AchievementModal
80
+ show={!!newAchievement}
81
+ achievement={newAchievement}
82
+ onClose={() => {
83
+ setNewAchievement(null);
84
+ setShowConfetti(false);
85
+ }}
86
+ />
87
+ <BadgesModal
88
+ show={showBadges}
89
+ achievements={Object.values(config).flatMap(conditions =>
90
+ conditions.filter(c => achievedAchievements.includes(c.data.id)).map(c => c.data)
91
+ )}
92
+ onClose={() => setShowBadges(false)}
93
+ />
94
+ <BadgesButton onClick={showBadgesModal} position={badgesButtonPosition} />
51
95
  <ConfettiWrapper show={showConfetti} />
52
- <AchievementModal show={!!modalMessage} message={modalMessage} onClose={handleCloseModal} />
53
96
  </AchievementContext.Provider>
54
97
  );
55
98
  };
56
99
 
57
- const useAchievement = () => {
100
+ export const useAchievement = () => {
58
101
  const context = useContext(AchievementContext);
59
102
  if (context === undefined) {
60
103
  throw new Error('useAchievement must be used within an AchievementProvider');
61
104
  }
62
105
  return context;
63
- };
64
-
65
- export { AchievementProvider, useAchievement };
106
+ };
package/src/index.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  import { AchievementProvider, useAchievement } from './context/AchievementContext';
2
- import Achievement from './components/Achievement';
2
+ import { Metrics, AchievementConfig, AchievementData, AchievementCondition } from './types';
3
+ import ConfettiWrapper from './components/ConfettiWrapper';
3
4
 
4
- export { AchievementProvider, useAchievement, Achievement };
5
+ export {
6
+ AchievementProvider,
7
+ useAchievement,
8
+ Metrics,
9
+ AchievementConfig,
10
+ AchievementData,
11
+ AchievementCondition,
12
+ ConfettiWrapper
13
+ };
@@ -1,7 +1,7 @@
1
1
  declare module 'react-confetti' {
2
2
  import { ComponentType } from 'react';
3
3
 
4
- interface ConfettiProps {
4
+ export interface ConfettiProps {
5
5
  width?: number;
6
6
  height?: number;
7
7
  numberOfPieces?: number;
@@ -16,6 +16,4 @@ declare module 'react-confetti' {
16
16
 
17
17
  const Confetti: ComponentType<ConfettiProps>;
18
18
  export default Confetti;
19
- }
20
-
21
- declare module 'react-use'
19
+ }
@@ -0,0 +1,4 @@
1
+ declare module 'react-use' {
2
+ export function useWindowSize(): { width: number; height: number };
3
+ // Add other functions from react-use that you're using
4
+ }
package/src/types.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type MetricValue = number | boolean | string | any;
2
+
3
+ export interface Metrics {
4
+ [key: string]: MetricValue;
5
+ }
6
+
7
+ export interface AchievementData {
8
+ id: string;
9
+ title: string;
10
+ description: string;
11
+ icon: string;
12
+ }
13
+
14
+ export interface AchievementCondition {
15
+ check: (metricValue: MetricValue) => boolean;
16
+ data: AchievementData;
17
+ }
18
+
19
+ export interface AchievementConfig {
20
+ [metricKey: string]: AchievementCondition[];
21
+ }
package/tsconfig.json CHANGED
@@ -105,5 +105,7 @@
105
105
  // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
106
106
  "skipLibCheck": true /* Skip type checking all .d.ts files. */
107
107
  },
108
- "include": ["src"]
108
+ "include": [
109
+ "src",
110
+ "react-confetti.d.ts"]
109
111
  }
package/src/badges.ts DELETED
@@ -1,24 +0,0 @@
1
- interface BadgeConfig {
2
- id: string;
3
- icon: string;
4
- title: string;
5
- description: string;
6
- }
7
-
8
- const defaultBadges: BadgeConfig[] = [
9
- {
10
- id: 'beginner',
11
- icon: '/path/to/beginner-icon.png',
12
- title: 'Beginner',
13
- description: 'Achieved beginner level',
14
- },
15
- {
16
- id: 'intermediate',
17
- icon: '/path/to/intermediate-icon.png',
18
- title: 'Intermediate',
19
- description: 'Achieved intermediate level',
20
- },
21
- // Add more badges as needed
22
- ];
23
-
24
- export { defaultBadges, BadgeConfig };
@@ -1,29 +0,0 @@
1
- import React from 'react';
2
- import { useAchievement } from '../context/AchievementContext';
3
-
4
- interface AchievementProps {
5
- metric: number;
6
- threshold: number;
7
- onAchieve: () => void;
8
- message: string;
9
- children: React.ReactNode;
10
- }
11
-
12
- const Achievement: React.FC<AchievementProps> = ({ metric, threshold, onAchieve, message, children }) => {
13
- const { setMetric, achievedLevels, levels, handleAchieve } = useAchievement();
14
-
15
- React.useEffect(() => {
16
- if (metric >= threshold && !achievedLevels.includes(threshold)) {
17
- onAchieve();
18
- const levelConfig = levels.find(level => level.threshold === threshold);
19
- if (levelConfig) {
20
- setMetric(metric);
21
- handleAchieve(levelConfig.level, message);
22
- }
23
- }
24
- }, [metric, threshold, onAchieve, setMetric, achievedLevels, levels, message, handleAchieve]);
25
-
26
- return <div>{children}</div>;
27
- };
28
-
29
- export default Achievement;
@@ -1,34 +0,0 @@
1
- import React from 'react';
2
-
3
- interface BadgeProps {
4
- icon: string;
5
- title: string;
6
- description: string;
7
- position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
8
- }
9
-
10
- const Badge: React.FC<BadgeProps> = ({ icon, title, description, position = 'top-right' }) => {
11
- const badgeStyle: React.CSSProperties = {
12
- position: 'fixed',
13
- [position.split('-')[0]]: '10px',
14
- [position.split('-')[1]]: '10px',
15
- display: 'flex',
16
- flexDirection: 'column',
17
- alignItems: 'center',
18
- backgroundColor: '#fff',
19
- border: '1px solid #ccc',
20
- borderRadius: '8px',
21
- padding: '10px',
22
- boxShadow: '0 0 10px rgba(0,0,0,0.1)',
23
- };
24
-
25
- return (
26
- <div style={badgeStyle}>
27
- <img src={icon} alt={title} style={{ width: '50px', height: '50px' }} />
28
- <h4>{title}</h4>
29
- <p>{description}</p>
30
- </div>
31
- );
32
- };
33
-
34
- export default Badge;
package/src/levels.ts DELETED
@@ -1,13 +0,0 @@
1
- interface LevelConfig {
2
- level: number;
3
- threshold: number;
4
- badgeId: string;
5
- }
6
-
7
- const levels: LevelConfig[] = [
8
- { level: 1, threshold: 10, badgeId: 'beginner' },
9
- { level: 2, threshold: 50, badgeId: 'intermediate' },
10
- // Add more levels as needed
11
- ];
12
-
13
- export { levels, LevelConfig };