react-achievements 1.0.4 → 1.0.6
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 +156 -0
- package/dist/context/AchievementContext.d.ts +2 -1
- package/dist/index.cjs.js +28 -6
- package/dist/index.esm.js +28 -6
- package/package.json +1 -1
- package/src/context/AchievementContext.tsx +37 -8
- package/public/badges/icon1.svg +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
### Update the location of the badges button
|
|
80
|
+
You can specify the position of the badges button by passing
|
|
81
|
+
badgesButtonPosition to the AchievementProvider:
|
|
82
|
+
```javascript
|
|
83
|
+
<AchievementProvider config={achievementConfig} badgesButtonPosition="bottom-right">
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The possible values for badgesButtonPosition are
|
|
87
|
+
- `'top-left'`
|
|
88
|
+
- `'top-right'`
|
|
89
|
+
- `'bottom-left'`
|
|
90
|
+
- `'bottom-right'`
|
|
91
|
+
|
|
92
|
+
### Use the useAchievement hook
|
|
93
|
+
In your components, use the useAchievement hook to update metrics and trigger achievement checks:
|
|
94
|
+
```javascript
|
|
95
|
+
import { useAchievement } from 'react-achievements';
|
|
96
|
+
|
|
97
|
+
function TransactionComponent() {
|
|
98
|
+
const { setMetrics } = useAchievement();
|
|
99
|
+
|
|
100
|
+
const handleNewTransaction = () => {
|
|
101
|
+
// Your transaction logic here
|
|
102
|
+
setMetrics(prevMetrics => ({
|
|
103
|
+
...prevMetrics,
|
|
104
|
+
transactions: (prevMetrics.transactions || 0) + 1
|
|
105
|
+
}));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<button onClick={handleNewTransaction}>New Transaction</button>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Features
|
|
115
|
+
|
|
116
|
+
### Flexible Achievement System:
|
|
117
|
+
Define custom metrics and achievement conditions.
|
|
118
|
+
|
|
119
|
+
### Automatic Achievement Tracking:
|
|
120
|
+
Achievements are automatically checked and unlocked when metrics change.
|
|
121
|
+
|
|
122
|
+
### Achievement Notifications:
|
|
123
|
+
A modal pops up when an achievement is unlocked.
|
|
124
|
+
|
|
125
|
+
### Persistent Achievements:
|
|
126
|
+
Achieved achievements are stored in local storage.
|
|
127
|
+
|
|
128
|
+
### Achievement Gallery:
|
|
129
|
+
Users can view all their unlocked achievements.
|
|
130
|
+
|
|
131
|
+
### Customizable UI:
|
|
132
|
+
The achievement modal and badges button can be styled to fit your app's design.
|
|
133
|
+
Confetti Effect: A celebratory confetti effect is displayed when an achievement is unlocked.
|
|
134
|
+
|
|
135
|
+
## API
|
|
136
|
+
### AchievementProvider
|
|
137
|
+
#### Props:
|
|
138
|
+
|
|
139
|
+
1. config: An object defining your metrics and achievements.
|
|
140
|
+
storageKey (optional): A string to use as the key for localStorage. Default: 'react-achievements'
|
|
141
|
+
badgesButtonPosition (optional): Position of the badges button. Options: 'top-left', 'top-right', 'bottom-left', 'bottom-right'. Default: 'top-right'
|
|
142
|
+
|
|
143
|
+
2. useAchievement Hook Returns an object with:
|
|
144
|
+
|
|
145
|
+
3. setMetrics: Function to update the metrics.
|
|
146
|
+
|
|
147
|
+
4. metrics: Current metrics object.
|
|
148
|
+
5. achievedAchievements: Array of achieved achievement IDs.
|
|
149
|
+
6. showBadgesModal: Function to manually show the badges modal.
|
|
150
|
+
|
|
151
|
+
Customization
|
|
152
|
+
You can customize the appearance of the achievement modal, badges modal, and badges button by modifying their respective components in the package.
|
|
153
|
+
License
|
|
154
|
+
MIT
|
|
155
|
+
Copy
|
|
156
|
+
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
|
|
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
|
-
|
|
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(
|
|
222
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
220
|
-
|
|
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,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
|
|
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
|
-
|
|
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
|
|
68
|
-
setMetrics(
|
|
69
|
-
|
|
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,
|
package/public/badges/icon1.svg
DELETED
|
@@ -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>
|