react-achievements 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Welcome to React-Achievements!
2
+
3
+ A flexible and customizable achievement system for React applications.
4
+
5
+ ## Installation
6
+
7
+ The first thing you need to do is install react-achievements:
8
+
9
+ `npm install react-achievements`
10
+
11
+ or if you're using yarn:
12
+ `yarn add react-achievements`
13
+
14
+ ## Usage
15
+
16
+ ### Set up the AchievementProvider
17
+
18
+ Wrap your app or a part of your app with the AchievementProvider:
19
+
20
+ ```
21
+ import { AchievementProvider } from 'react-achievements';
22
+ import achievementConfig from './achievementConfig';
23
+
24
+ function App() {
25
+ return (
26
+ <AchievementProvider config={achievementConfig} initialState={<your_object>}>
27
+ {/* Your app components */}
28
+ </AchievementProvider>
29
+ );
30
+ }
31
+ ```
32
+
33
+ The object that you pass to the initialState prop should have keys that match the metrics defined in your achievement configuration. For example, if your achievement configuration defines a 'transactions' metric, your initialState object should look like this:
34
+ ```typescript
35
+ const user = {
36
+ transactions: [
37
+ {
38
+ id: 1,
39
+ amount: 100
40
+ },
41
+ {
42
+ id: 2,
43
+ amount: 200
44
+ }
45
+ ]
46
+ }
47
+ ```
48
+
49
+ ### Create an achievement configuration
50
+ Create a file (e.g., achievementConfig.js) to define your achievements:
51
+ ```javascript
52
+ const achievementConfig = {
53
+ transactions: [
54
+ {
55
+ check: (value) => value.length >= 1,
56
+ data: {
57
+ id: 'first_transaction',
58
+ title: 'First Transaction',
59
+ description: 'Completed your first transaction',
60
+ icon: '/path/to/icon.png'
61
+ }
62
+ },
63
+ {
64
+ check: (value) => value.length >= 10,
65
+ data: {
66
+ id: 'ten_transactions',
67
+ title: 'Ten Transactions',
68
+ description: 'Completed ten transactions',
69
+ icon: '/path/to/icon.png'
70
+ }
71
+ },
72
+ ],
73
+ // Add more metrics and achievements as needed
74
+ };
75
+
76
+ export default achievementConfig;
77
+ ```
78
+
79
+ ### Use the useAchievement hook
80
+ In your components, use the useAchievement hook to update metrics and trigger achievement checks:
81
+ ```javascript
82
+ import { useAchievement } from 'react-achievements';
83
+
84
+ function TransactionComponent() {
85
+ const { setMetrics } = useAchievement();
86
+
87
+ const handleNewTransaction = () => {
88
+ // Your transaction logic here
89
+ setMetrics(prevMetrics => ({
90
+ ...prevMetrics,
91
+ transactions: (prevMetrics.transactions || 0) + 1
92
+ }));
93
+ };
94
+
95
+ return (
96
+ <button onClick={handleNewTransaction}>New Transaction</button>
97
+ );
98
+ }
99
+ ```
100
+
101
+ ## Features
102
+
103
+ ### Flexible Achievement System:
104
+ Define custom metrics and achievement conditions.
105
+
106
+ ### Automatic Achievement Tracking:
107
+ Achievements are automatically checked and unlocked when metrics change.
108
+
109
+ ### Achievement Notifications:
110
+ A modal pops up when an achievement is unlocked.
111
+
112
+ ### Persistent Achievements:
113
+ Achieved achievements are stored in local storage.
114
+
115
+ ### Achievement Gallery:
116
+ Users can view all their unlocked achievements.
117
+
118
+ ### Customizable UI:
119
+ The achievement modal and badges button can be styled to fit your app's design.
120
+ Confetti Effect: A celebratory confetti effect is displayed when an achievement is unlocked.
121
+
122
+ ## API
123
+ ### AchievementProvider
124
+ #### Props:
125
+
126
+ 1. config: An object defining your metrics and achievements.
127
+ storageKey (optional): A string to use as the key for localStorage. Default: 'react-achievements'
128
+ badgesButtonPosition (optional): Position of the badges button. Options: 'top-left', 'top-right', 'bottom-left', 'bottom-right'. Default: 'top-right'
129
+
130
+ 2. useAchievement Hook Returns an object with:
131
+
132
+ 3. setMetrics: Function to update the metrics.
133
+
134
+ 4. metrics: Current metrics object.
135
+ 5. achievedAchievements: Array of achieved achievement IDs.
136
+ 6. showBadgesModal: Function to manually show the badges modal.
137
+
138
+ Customization
139
+ You can customize the appearance of the achievement modal, badges modal, and badges button by modifying their respective components in the package.
140
+ License
141
+ MIT
142
+ Copy
143
+ This README provides a comprehensive overview of how to use the react-achievements package, including setup, usage examples, features, and API details. You may want to adjust or expand certain sections based on the specific implementation details or additional features of your package.
@@ -2,7 +2,7 @@ import React, { ReactNode } from 'react';
2
2
  import { Metrics, AchievementConfig } from '../types';
3
3
  interface AchievementContextProps {
4
4
  metrics: Metrics;
5
- setMetrics: (metrics: Metrics) => void;
5
+ setMetrics: (metrics: Metrics | ((prevMetrics: Metrics) => Metrics)) => void;
6
6
  achievedAchievements: string[];
7
7
  checkAchievements: () => void;
8
8
  showBadgesModal: () => void;
@@ -10,6 +10,7 @@ interface AchievementContextProps {
10
10
  interface AchievementProviderProps {
11
11
  children: ReactNode;
12
12
  config: AchievementConfig;
13
+ initialState?: Record<string, any>;
13
14
  storageKey?: string;
14
15
  badgesButtonPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
15
16
  }
package/dist/index.cjs.js CHANGED
@@ -185,8 +185,22 @@ const ConfettiWrapper = ({ show, confettiProps }) => {
185
185
  };
186
186
 
187
187
  const AchievementContext = React.createContext(undefined);
188
- const AchievementProvider = ({ children, config, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
189
- const [metrics, setMetrics] = React.useState({});
188
+ const AchievementProvider = ({ children, config, initialState = {}, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
189
+ const extractMetrics = (state) => {
190
+ return Object.keys(config).reduce((acc, key) => {
191
+ if (key in state) {
192
+ acc[key] = state[key];
193
+ }
194
+ return acc;
195
+ }, {});
196
+ };
197
+ const [metrics, setMetrics] = React.useState(() => {
198
+ const savedMetrics = localStorage.getItem(`${storageKey}-metrics`);
199
+ if (savedMetrics) {
200
+ return JSON.parse(savedMetrics);
201
+ }
202
+ return extractMetrics(initialState);
203
+ });
190
204
  const [achievedAchievements, setAchievedAchievements] = React.useState(() => {
191
205
  const saved = localStorage.getItem(`${storageKey}-achievements`);
192
206
  return saved ? JSON.parse(saved) : [];
@@ -194,7 +208,10 @@ const AchievementProvider = ({ children, config, storageKey = 'react-achievement
194
208
  const [newAchievement, setNewAchievement] = React.useState(null);
195
209
  const [showBadges, setShowBadges] = React.useState(false);
196
210
  const [showConfetti, setShowConfetti] = React.useState(false);
197
- const checkAchievements = () => {
211
+ React.useEffect(() => {
212
+ localStorage.setItem(`${storageKey}-metrics`, JSON.stringify(metrics));
213
+ }, [metrics, storageKey]);
214
+ const checkAchievements = React.useCallback(() => {
198
215
  const newAchievements = [];
199
216
  Object.entries(config).forEach(([metricKey, conditions]) => {
200
217
  const metricValue = metrics[metricKey];
@@ -211,15 +228,20 @@ const AchievementProvider = ({ children, config, storageKey = 'react-achievement
211
228
  setNewAchievement(newAchievements[0]);
212
229
  setShowConfetti(true);
213
230
  }
214
- };
231
+ }, [config, metrics, achievedAchievements, storageKey]);
232
+ React.useEffect(() => {
233
+ checkAchievements();
234
+ }, [checkAchievements]);
215
235
  const showBadgesModal = () => {
216
236
  setShowBadges(true);
217
237
  };
218
238
  const contextValue = {
219
239
  metrics,
220
240
  setMetrics: (newMetrics) => {
221
- setMetrics(newMetrics);
222
- checkAchievements();
241
+ setMetrics(prevMetrics => {
242
+ const updatedMetrics = typeof newMetrics === 'function' ? newMetrics(prevMetrics) : newMetrics;
243
+ return updatedMetrics;
244
+ });
223
245
  },
224
246
  achievedAchievements,
225
247
  checkAchievements,
package/dist/index.esm.js CHANGED
@@ -183,8 +183,22 @@ const ConfettiWrapper = ({ show, confettiProps }) => {
183
183
  };
184
184
 
185
185
  const AchievementContext = createContext(undefined);
186
- const AchievementProvider = ({ children, config, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
187
- const [metrics, setMetrics] = useState({});
186
+ const AchievementProvider = ({ children, config, initialState = {}, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
187
+ const extractMetrics = (state) => {
188
+ return Object.keys(config).reduce((acc, key) => {
189
+ if (key in state) {
190
+ acc[key] = state[key];
191
+ }
192
+ return acc;
193
+ }, {});
194
+ };
195
+ const [metrics, setMetrics] = useState(() => {
196
+ const savedMetrics = localStorage.getItem(`${storageKey}-metrics`);
197
+ if (savedMetrics) {
198
+ return JSON.parse(savedMetrics);
199
+ }
200
+ return extractMetrics(initialState);
201
+ });
188
202
  const [achievedAchievements, setAchievedAchievements] = useState(() => {
189
203
  const saved = localStorage.getItem(`${storageKey}-achievements`);
190
204
  return saved ? JSON.parse(saved) : [];
@@ -192,7 +206,10 @@ const AchievementProvider = ({ children, config, storageKey = 'react-achievement
192
206
  const [newAchievement, setNewAchievement] = useState(null);
193
207
  const [showBadges, setShowBadges] = useState(false);
194
208
  const [showConfetti, setShowConfetti] = useState(false);
195
- const checkAchievements = () => {
209
+ useEffect(() => {
210
+ localStorage.setItem(`${storageKey}-metrics`, JSON.stringify(metrics));
211
+ }, [metrics, storageKey]);
212
+ const checkAchievements = useCallback(() => {
196
213
  const newAchievements = [];
197
214
  Object.entries(config).forEach(([metricKey, conditions]) => {
198
215
  const metricValue = metrics[metricKey];
@@ -209,15 +226,20 @@ const AchievementProvider = ({ children, config, storageKey = 'react-achievement
209
226
  setNewAchievement(newAchievements[0]);
210
227
  setShowConfetti(true);
211
228
  }
212
- };
229
+ }, [config, metrics, achievedAchievements, storageKey]);
230
+ useEffect(() => {
231
+ checkAchievements();
232
+ }, [checkAchievements]);
213
233
  const showBadgesModal = () => {
214
234
  setShowBadges(true);
215
235
  };
216
236
  const contextValue = {
217
237
  metrics,
218
238
  setMetrics: (newMetrics) => {
219
- setMetrics(newMetrics);
220
- checkAchievements();
239
+ setMetrics(prevMetrics => {
240
+ const updatedMetrics = typeof newMetrics === 'function' ? newMetrics(prevMetrics) : newMetrics;
241
+ return updatedMetrics;
242
+ });
221
243
  },
222
244
  achievedAchievements,
223
245
  checkAchievements,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-achievements",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "This package allows users to transpose a React achievements engine over their React apps",
5
5
  "keywords": [
6
6
  "react",
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useState, ReactNode } from 'react';
1
+ import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
2
2
  import { Metrics, AchievementConfig, AchievementData } from '../types';
3
3
  import AchievementModal from '../components/AchievementModal';
4
4
  import BadgesModal from '../components/BadgesModal';
@@ -7,7 +7,7 @@ import ConfettiWrapper from '../components/ConfettiWrapper';
7
7
 
8
8
  interface AchievementContextProps {
9
9
  metrics: Metrics;
10
- setMetrics: (metrics: Metrics) => void;
10
+ setMetrics: (metrics: Metrics | ((prevMetrics: Metrics) => Metrics)) => void;
11
11
  achievedAchievements: string[];
12
12
  checkAchievements: () => void;
13
13
  showBadgesModal: () => void;
@@ -16,6 +16,7 @@ interface AchievementContextProps {
16
16
  interface AchievementProviderProps {
17
17
  children: ReactNode;
18
18
  config: AchievementConfig;
19
+ initialState?: Record<string, any>;
19
20
  storageKey?: string;
20
21
  badgesButtonPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
21
22
  }
@@ -25,19 +26,41 @@ const AchievementContext = createContext<AchievementContextProps | undefined>(un
25
26
  export const AchievementProvider: React.FC<AchievementProviderProps> = ({
26
27
  children,
27
28
  config,
29
+ initialState = {},
28
30
  storageKey = 'react-achievements',
29
31
  badgesButtonPosition = 'top-right'
30
32
  }) => {
31
- const [metrics, setMetrics] = useState<Metrics>({});
33
+ const extractMetrics = (state: Record<string, any>): Metrics => {
34
+ return Object.keys(config).reduce((acc, key) => {
35
+ if (key in state) {
36
+ acc[key] = state[key];
37
+ }
38
+ return acc;
39
+ }, {} as Metrics);
40
+ };
41
+
42
+ const [metrics, setMetrics] = useState<Metrics>(() => {
43
+ const savedMetrics = localStorage.getItem(`${storageKey}-metrics`);
44
+ if (savedMetrics) {
45
+ return JSON.parse(savedMetrics);
46
+ }
47
+ return extractMetrics(initialState);
48
+ });
49
+
32
50
  const [achievedAchievements, setAchievedAchievements] = useState<string[]>(() => {
33
51
  const saved = localStorage.getItem(`${storageKey}-achievements`);
34
52
  return saved ? JSON.parse(saved) : [];
35
53
  });
54
+
36
55
  const [newAchievement, setNewAchievement] = useState<AchievementData | null>(null);
37
56
  const [showBadges, setShowBadges] = useState(false);
38
57
  const [showConfetti, setShowConfetti] = useState(false);
39
58
 
40
- const checkAchievements = () => {
59
+ useEffect(() => {
60
+ localStorage.setItem(`${storageKey}-metrics`, JSON.stringify(metrics));
61
+ }, [metrics, storageKey]);
62
+
63
+ const checkAchievements = useCallback(() => {
41
64
  const newAchievements: AchievementData[] = [];
42
65
 
43
66
  Object.entries(config).forEach(([metricKey, conditions]) => {
@@ -56,7 +79,11 @@ export const AchievementProvider: React.FC<AchievementProviderProps> = ({
56
79
  setNewAchievement(newAchievements[0]);
57
80
  setShowConfetti(true);
58
81
  }
59
- };
82
+ }, [config, metrics, achievedAchievements, storageKey]);
83
+
84
+ useEffect(() => {
85
+ checkAchievements();
86
+ }, [checkAchievements]);
60
87
 
61
88
  const showBadgesModal = () => {
62
89
  setShowBadges(true);
@@ -64,9 +91,11 @@ export const AchievementProvider: React.FC<AchievementProviderProps> = ({
64
91
 
65
92
  const contextValue: AchievementContextProps = {
66
93
  metrics,
67
- setMetrics: (newMetrics: Metrics) => {
68
- setMetrics(newMetrics);
69
- checkAchievements();
94
+ setMetrics: (newMetrics) => {
95
+ setMetrics(prevMetrics => {
96
+ const updatedMetrics = typeof newMetrics === 'function' ? newMetrics(prevMetrics) : newMetrics;
97
+ return updatedMetrics;
98
+ });
70
99
  },
71
100
  achievedAchievements,
72
101
  checkAchievements,
@@ -1 +0,0 @@
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>