react-achievements 1.0.3 → 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.
@@ -1,7 +1,8 @@
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
8
  declare const _default: React.NamedExoticComponent<AchievementModalProps>;
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { BadgeConfig } from '../badges';
2
+ import { AchievementData } from '../types';
3
3
  interface BadgesModalProps {
4
4
  show: boolean;
5
- badges: BadgeConfig[];
5
+ achievements: AchievementData[];
6
6
  onClose: () => void;
7
7
  }
8
8
  declare const _default: React.NamedExoticComponent<BadgesModalProps>;
@@ -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;
@@ -1,20 +1,16 @@
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;
11
- showAchievementModal: (message: string) => void;
4
+ metrics: Metrics;
5
+ setMetrics: (metrics: Metrics | ((prevMetrics: Metrics) => Metrics)) => void;
6
+ achievedAchievements: string[];
7
+ checkAchievements: () => void;
12
8
  showBadgesModal: () => void;
13
9
  }
14
10
  interface AchievementProviderProps {
15
11
  children: ReactNode;
16
- initialBadges?: BadgeConfig[];
17
- initialLevels?: LevelConfig[];
12
+ config: AchievementConfig;
13
+ initialState?: Record<string, any>;
18
14
  storageKey?: string;
19
15
  badgesButtonPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
20
16
  }
package/dist/index.cjs.js CHANGED
@@ -2,30 +2,8 @@
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 defaultLevels = [
22
- { level: 1, threshold: 10, badgeId: 'beginner' },
23
- { level: 2, threshold: 50, badgeId: 'intermediate' },
24
- // Add more levels as needed
25
- ];
26
-
27
- const AchievementModal = ({ show, message, onClose }) => {
28
- if (!show)
5
+ const AchievementModal = ({ show, achievement, onClose }) => {
6
+ if (!show || !achievement)
29
7
  return null;
30
8
  const modalStyle = {
31
9
  position: 'fixed',
@@ -51,12 +29,14 @@ const AchievementModal = ({ show, message, onClose }) => {
51
29
  React.createElement("div", { style: overlayStyle, onClick: onClose }),
52
30
  React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "achievement-title" },
53
31
  React.createElement("h2", { id: "achievement-title" }, "Achievement Unlocked!"),
54
- React.createElement("p", null, message),
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),
55
35
  React.createElement("button", { onClick: onClose }, "Okay"))));
56
36
  };
57
37
  var AchievementModal$1 = React.memo(AchievementModal);
58
38
 
59
- const BadgesModal = ({ show, badges, onClose }) => {
39
+ const BadgesModal = ({ show, achievements, onClose }) => {
60
40
  if (!show)
61
41
  return null;
62
42
  const modalStyle = {
@@ -86,10 +66,10 @@ const BadgesModal = ({ show, badges, onClose }) => {
86
66
  React.createElement("div", { style: overlayStyle, onClick: onClose }),
87
67
  React.createElement("div", { style: modalStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": "badges-title" },
88
68
  React.createElement("h2", { id: "badges-title" }, "Your Achievements"),
89
- React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'center' } }, badges.map(badge => (React.createElement("div", { key: badge.id, style: { margin: '10px', textAlign: 'center' } },
90
- React.createElement("img", { src: badge.icon, alt: badge.title, style: { width: '50px', height: '50px' } }),
91
- React.createElement("h4", null, badge.title),
92
- React.createElement("p", null, badge.description))))),
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))))),
93
73
  React.createElement("button", { onClick: onClose, style: { marginTop: '20px' } }, "Close"))));
94
74
  };
95
75
  var BadgesModal$1 = React.memo(BadgesModal);
@@ -111,52 +91,171 @@ const BadgesButton = ({ onClick, position }) => {
111
91
  };
112
92
  var BadgesButton$1 = React.memo(BadgesButton);
113
93
 
94
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
95
+
96
+ function getDefaultExportFromCjs (x) {
97
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
98
+ }
99
+
100
+ var reactConfetti_min = {exports: {}};
101
+
102
+ (function (module, exports) {
103
+ !function(t,e){module.exports=e(React);}("undefined"!=typeof self?self:commonjsGlobal,(function(t){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r});},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0});},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=2)}([function(e,n){e.exports=t;},function(t,e,n){var r={linear:function(t,e,n,r){return (n-e)*t/r+e},easeInQuad:function(t,e,n,r){return (n-e)*(t/=r)*t+e},easeOutQuad:function(t,e,n,r){return -(n-e)*(t/=r)*(t-2)+e},easeInOutQuad:function(t,e,n,r){var i=n-e;return (t/=r/2)<1?i/2*t*t+e:-i/2*(--t*(t-2)-1)+e},easeInCubic:function(t,e,n,r){return (n-e)*(t/=r)*t*t+e},easeOutCubic:function(t,e,n,r){return (n-e)*((t=t/r-1)*t*t+1)+e},easeInOutCubic:function(t,e,n,r){var i=n-e;return (t/=r/2)<1?i/2*t*t*t+e:i/2*((t-=2)*t*t+2)+e},easeInQuart:function(t,e,n,r){return (n-e)*(t/=r)*t*t*t+e},easeOutQuart:function(t,e,n,r){return -(n-e)*((t=t/r-1)*t*t*t-1)+e},easeInOutQuart:function(t,e,n,r){var i=n-e;return (t/=r/2)<1?i/2*t*t*t*t+e:-i/2*((t-=2)*t*t*t-2)+e},easeInQuint:function(t,e,n,r){return (n-e)*(t/=r)*t*t*t*t+e},easeOutQuint:function(t,e,n,r){return (n-e)*((t=t/r-1)*t*t*t*t+1)+e},easeInOutQuint:function(t,e,n,r){var i=n-e;return (t/=r/2)<1?i/2*t*t*t*t*t+e:i/2*((t-=2)*t*t*t*t+2)+e},easeInSine:function(t,e,n,r){var i=n-e;return -i*Math.cos(t/r*(Math.PI/2))+i+e},easeOutSine:function(t,e,n,r){return (n-e)*Math.sin(t/r*(Math.PI/2))+e},easeInOutSine:function(t,e,n,r){return -(n-e)/2*(Math.cos(Math.PI*t/r)-1)+e},easeInExpo:function(t,e,n,r){return 0==t?e:(n-e)*Math.pow(2,10*(t/r-1))+e},easeOutExpo:function(t,e,n,r){var i=n-e;return t==r?e+i:i*(1-Math.pow(2,-10*t/r))+e},easeInOutExpo:function(t,e,n,r){var i=n-e;return 0===t?e:t===r?e+i:(t/=r/2)<1?i/2*Math.pow(2,10*(t-1))+e:i/2*(2-Math.pow(2,-10*--t))+e},easeInCirc:function(t,e,n,r){return -(n-e)*(Math.sqrt(1-(t/=r)*t)-1)+e},easeOutCirc:function(t,e,n,r){return (n-e)*Math.sqrt(1-(t=t/r-1)*t)+e},easeInOutCirc:function(t,e,n,r){var i=n-e;return (t/=r/2)<1?-i/2*(Math.sqrt(1-t*t)-1)+e:i/2*(Math.sqrt(1-(t-=2)*t)+1)+e},easeInElastic:function(t,e,n,r){var i,o,a,c=n-e;return a=1.70158,0===t?e:1==(t/=r)?e+c:((o=0)||(o=.3*r),(i=c)<Math.abs(c)?(i=c,a=o/4):a=o/(2*Math.PI)*Math.asin(c/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t*r-a)*(2*Math.PI)/o)+e)},easeOutElastic:function(t,e,n,r){var i,o,a,c=n-e;return a=1.70158,0===t?e:1==(t/=r)?e+c:((o=0)||(o=.3*r),(i=c)<Math.abs(c)?(i=c,a=o/4):a=o/(2*Math.PI)*Math.asin(c/i),i*Math.pow(2,-10*t)*Math.sin((t*r-a)*(2*Math.PI)/o)+c+e)},easeInOutElastic:function(t,e,n,r){var i,o,a,c=n-e;return a=1.70158,0===t?e:2==(t/=r/2)?e+c:((o=0)||(o=r*(.3*1.5)),(i=c)<Math.abs(c)?(i=c,a=o/4):a=o/(2*Math.PI)*Math.asin(c/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t*r-a)*(2*Math.PI)/o)*-.5+e:i*Math.pow(2,-10*(t-=1))*Math.sin((t*r-a)*(2*Math.PI)/o)*.5+c+e)},easeInBack:function(t,e,n,r,i){return void 0===i&&(i=1.70158),(n-e)*(t/=r)*t*((i+1)*t-i)+e},easeOutBack:function(t,e,n,r,i){return void 0===i&&(i=1.70158),(n-e)*((t=t/r-1)*t*((i+1)*t+i)+1)+e},easeInOutBack:function(t,e,n,r,i){var o=n-e;return void 0===i&&(i=1.70158),(t/=r/2)<1?o/2*(t*t*((1+(i*=1.525))*t-i))+e:o/2*((t-=2)*t*((1+(i*=1.525))*t+i)+2)+e},easeInBounce:function(t,e,n,i){var o=n-e;return o-r.easeOutBounce(i-t,0,o,i)+e},easeOutBounce:function(t,e,n,r){var i=n-e;return (t/=r)<1/2.75?i*(7.5625*t*t)+e:t<2/2.75?i*(7.5625*(t-=1.5/2.75)*t+.75)+e:t<2.5/2.75?i*(7.5625*(t-=2.25/2.75)*t+.9375)+e:i*(7.5625*(t-=2.625/2.75)*t+.984375)+e},easeInOutBounce:function(t,e,n,i){var o=n-e;return t<i/2?.5*r.easeInBounce(2*t,0,o,i)+e:.5*r.easeOutBounce(2*t-i,0,o,i)+.5*o+e}};t.exports=r;},function(t,e,n){t.exports=n(3);},function(t,e,n){n.r(e),n.d(e,"ReactConfetti",(function(){return Q}));var r,i,o=n(0),a=n.n(o),c=n(1),s=n.n(c);function u(t,e){return t+Math.random()*(e-t)}function f(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r);}}function h(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}!function(t){t[t.Circle=0]="Circle",t[t.Square=1]="Square",t[t.Strip=2]="Strip";}(r||(r={})),function(t){t[t.Positive=1]="Positive",t[t.Negative=-1]="Negative";}(i||(i={}));var l=function(){function t(e,n,r,o){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),h(this,"context",void 0),h(this,"radius",void 0),h(this,"x",void 0),h(this,"y",void 0),h(this,"w",void 0),h(this,"h",void 0),h(this,"vx",void 0),h(this,"vy",void 0),h(this,"shape",void 0),h(this,"angle",void 0),h(this,"angularSpin",void 0),h(this,"color",void 0),h(this,"rotateY",void 0),h(this,"rotationDirection",void 0),h(this,"getOptions",void 0),this.getOptions=n;var a,c,s=this.getOptions(),f=s.colors,l=s.initialVelocityX,p=s.initialVelocityY;this.context=e,this.x=r,this.y=o,this.w=u(5,20),this.h=u(5,20),this.radius=u(5,10),this.vx="number"==typeof l?u(-l,l):u(l.min,l.max),this.vy="number"==typeof p?u(-p,0):u(p.min,p.max),this.shape=(a=0,c=2,Math.floor(a+Math.random()*(c-a+1))),this.angle=u(0,360)*Math.PI/180,this.angularSpin=u(-.2,.2),this.color=f[Math.floor(Math.random()*f.length)],this.rotateY=u(0,1),this.rotationDirection=u(0,1)?i.Positive:i.Negative;}var e,n;return e=t,(n=[{key:"update",value:function(){var t=this.getOptions(),e=t.gravity,n=t.wind,o=t.friction,a=t.opacity,c=t.drawShape;this.x+=this.vx,this.y+=this.vy,this.vy+=e,this.vx+=n,this.vx*=o,this.vy*=o,this.rotateY>=1&&this.rotationDirection===i.Positive?this.rotationDirection=i.Negative:this.rotateY<=-1&&this.rotationDirection===i.Negative&&(this.rotationDirection=i.Positive);var s=.1*this.rotationDirection;if(this.rotateY+=s,this.angle+=this.angularSpin,this.context.save(),this.context.translate(this.x,this.y),this.context.rotate(this.angle),this.context.scale(1,this.rotateY),this.context.rotate(this.angle),this.context.beginPath(),this.context.fillStyle=this.color,this.context.strokeStyle=this.color,this.context.globalAlpha=a,this.context.lineCap="round",this.context.lineWidth=2,c&&"function"==typeof c)c.call(this,this.context);else switch(this.shape){case r.Circle:this.context.beginPath(),this.context.arc(0,0,this.radius,0,2*Math.PI),this.context.fill();break;case r.Square:this.context.fillRect(-this.w/2,-this.h/2,this.w,this.h);break;case r.Strip:this.context.fillRect(-this.w/6,-this.h/2,this.w/3,this.h);}this.context.closePath(),this.context.restore();}}])&&f(e.prototype,n),t}();function p(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var v=function t(e,n){var r=this;!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),p(this,"canvas",void 0),p(this,"context",void 0),p(this,"getOptions",void 0),p(this,"x",0),p(this,"y",0),p(this,"w",0),p(this,"h",0),p(this,"lastNumberOfPieces",0),p(this,"tweenInitTime",Date.now()),p(this,"particles",[]),p(this,"particlesGenerated",0),p(this,"removeParticleAt",(function(t){r.particles.splice(t,1);})),p(this,"getParticle",(function(){var t=u(r.x,r.w+r.x),e=u(r.y,r.h+r.y);return new l(r.context,r.getOptions,t,e)})),p(this,"animate",(function(){var t=r.canvas,e=r.context,n=r.particlesGenerated,i=r.lastNumberOfPieces,o=r.getOptions(),a=o.run,c=o.recycle,s=o.numberOfPieces,u=o.debug,f=o.tweenFunction,h=o.tweenDuration;if(!a)return !1;var l=r.particles.length,p=c?l:n,v=Date.now();if(p<s){i!==s&&(r.tweenInitTime=v,r.lastNumberOfPieces=s);for(var y=r.tweenInitTime,d=f(v-y>h?h:Math.max(0,v-y),p,s,h),b=Math.round(d-p),g=0;g<b;g++)r.particles.push(r.getParticle());r.particlesGenerated+=b;}return u&&(e.font="12px sans-serif",e.fillStyle="#333",e.textAlign="right",e.fillText("Particles: ".concat(l),t.width-10,t.height-20)),r.particles.forEach((function(e,n){e.update(),(e.y>t.height||e.y<-100||e.x>t.width+100||e.x<-100)&&(c&&p<=s?r.particles[n]=r.getParticle():r.removeParticleAt(n));})),l>0||p<s})),this.canvas=e;var i=this.canvas.getContext("2d");if(!i)throw new Error("Could not get canvas context");this.context=i,this.getOptions=n;};function y(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r);}return n}function d(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{};e%2?y(Object(n),!0).forEach((function(e){g(t,e,n[e]);})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):y(Object(n)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e));}));}return t}function b(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r);}}function g(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var O={width:"undefined"!=typeof window?window.innerWidth:300,height:"undefined"!=typeof window?window.innerHeight:200,numberOfPieces:200,friction:.99,wind:0,gravity:.1,initialVelocityX:4,initialVelocityY:10,colors:["#f44336","#e91e63","#9c27b0","#673ab7","#3f51b5","#2196f3","#03a9f4","#00bcd4","#009688","#4CAF50","#8BC34A","#CDDC39","#FFEB3B","#FFC107","#FF9800","#FF5722","#795548"],opacity:1,debug:!1,tweenFunction:s.a.easeInOutQuad,tweenDuration:5e3,recycle:!0,run:!0},w=function(){function t(e,n){var r=this;!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),g(this,"canvas",void 0),g(this,"context",void 0),g(this,"_options",void 0),g(this,"generator",void 0),g(this,"rafId",void 0),g(this,"setOptionsWithDefaults",(function(t){var e={confettiSource:{x:0,y:0,w:r.canvas.width,h:0}};r._options=d(d(d({},e),O),t),Object.assign(r,t.confettiSource);})),g(this,"update",(function(){var t=r.options,e=t.run,n=t.onConfettiComplete,i=r.canvas,o=r.context;e&&(o.fillStyle="white",o.clearRect(0,0,i.width,i.height)),r.generator.animate()?r.rafId=requestAnimationFrame(r.update):(n&&"function"==typeof n&&r.generator.particlesGenerated>0&&n.call(r,r),r._options.run=!1);})),g(this,"reset",(function(){r.generator&&r.generator.particlesGenerated>0&&(r.generator.particlesGenerated=0,r.generator.particles=[],r.generator.lastNumberOfPieces=0);})),g(this,"stop",(function(){r.options={run:!1},r.rafId&&(cancelAnimationFrame(r.rafId),r.rafId=void 0);})),this.canvas=e;var i=this.canvas.getContext("2d");if(!i)throw new Error("Could not get canvas context");this.context=i,this.generator=new v(this.canvas,(function(){return r.options})),this.options=n,this.update();}var e,n;return e=t,(n=[{key:"options",get:function(){return this._options},set:function(t){var e=this._options&&this._options.run,n=this._options&&this._options.recycle;this.setOptionsWithDefaults(t),this.generator&&(Object.assign(this.generator,this.options.confettiSource),"boolean"==typeof t.recycle&&t.recycle&&!1===n&&(this.generator.lastNumberOfPieces=this.generator.particles.length)),"boolean"==typeof t.run&&t.run&&!1===e&&this.update();}}])&&b(e.prototype,n),t}();function m(t){return function(t){if(Array.isArray(t))return C(t)}(t)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(t)||S(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function x(t){return (x="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function P(){return (P=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r]);}return t}).apply(this,arguments)}function j(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r);}return n}function M(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{};e%2?j(Object(n),!0).forEach((function(e){T(t,e,n[e]);})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):j(Object(n)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e));}));}return t}function I(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(t)))return;var n=[],r=!0,i=!1,o=void 0;try{for(var a,c=t[Symbol.iterator]();!(r=(a=c.next()).done)&&(n.push(a.value),!e||n.length!==e);r=!0);}catch(t){i=!0,o=t;}finally{try{r||null==c.return||c.return();}finally{if(i)throw o}}return n}(t,e)||S(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function S(t,e){if(t){if("string"==typeof t)return C(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return "Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?C(t,e):void 0}}function C(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n<e;n++)r[n]=t[n];return r}function D(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function E(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r);}}function _(t,e){return (_=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function R(t){var e=function(){if("undefined"==typeof Reflect||!Reflect.construct)return !1;if(Reflect.construct.sham)return !1;if("function"==typeof Proxy)return !0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(t){return !1}}();return function(){var n,r=F(t);if(e){var i=F(this).constructor;n=Reflect.construct(r,arguments,i);}else n=r.apply(this,arguments);return k(this,n)}}function k(t,e){return !e||"object"!==x(e)&&"function"!=typeof e?A(t):e}function A(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function F(t){return (F=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function T(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var B=a.a.createRef(),N=function(t){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&_(t,e);}(o,t);var e,n,i=R(o);function o(t){var e;D(this,o);for(var n=arguments.length,r=new Array(n>1?n-1:0),c=1;c<n;c++)r[c-1]=arguments[c];return T(A(e=i.call.apply(i,[this,t].concat(r))),"canvas",a.a.createRef()),T(A(e),"confetti",void 0),e.canvas=t.canvasRef||B,e}return e=o,(n=[{key:"componentDidMount",value:function(){if(this.canvas.current){var t=q(this.props)[0];this.confetti=new w(this.canvas.current,t);}}},{key:"componentDidUpdate",value:function(){var t=q(this.props)[0];this.confetti&&(this.confetti.options=t);}},{key:"componentWillUnmount",value:function(){this.confetti&&this.confetti.stop(),this.confetti=void 0;}},{key:"render",value:function(){var t=I(q(this.props),2),e=t[0],n=t[1],r=M({zIndex:2,position:"absolute",pointerEvents:"none",top:0,left:0,bottom:0,right:0},n.style);return a.a.createElement("canvas",P({width:e.width,height:e.height,ref:this.canvas},n,{style:r}))}}])&&E(e.prototype,n),o}(o.Component);function q(t){var e={},n={},r=[].concat(m(Object.keys(O)),["confettiSource","drawShape","onConfettiComplete"]),i=["canvasRef"];for(var o in t){var a=t[o];r.includes(o)?e[o]=a:i.includes(o)?i[o]=a:n[o]=a;}return [e,n,{}]}T(N,"defaultProps",M({},O)),T(N,"displayName","ReactConfetti");var Q=a.a.forwardRef((function(t,e){return a.a.createElement(N,P({canvasRef:e},t))}));e.default=Q;}]).default}));
104
+
105
+ } (reactConfetti_min));
106
+
107
+ var reactConfetti_minExports = reactConfetti_min.exports;
108
+ var Confetti = /*@__PURE__*/getDefaultExportFromCjs(reactConfetti_minExports);
109
+
110
+ function on(obj) {
111
+ var args = [];
112
+ for (var _i = 1; _i < arguments.length; _i++) {
113
+ args[_i - 1] = arguments[_i];
114
+ }
115
+ if (obj && obj.addEventListener) {
116
+ obj.addEventListener.apply(obj, args);
117
+ }
118
+ }
119
+ function off(obj) {
120
+ var args = [];
121
+ for (var _i = 1; _i < arguments.length; _i++) {
122
+ args[_i - 1] = arguments[_i];
123
+ }
124
+ if (obj && obj.removeEventListener) {
125
+ obj.removeEventListener.apply(obj, args);
126
+ }
127
+ }
128
+ var isBrowser = typeof window !== 'undefined';
129
+
130
+ var useEffectOnce = function (effect) {
131
+ React.useEffect(effect, []);
132
+ };
133
+
134
+ var useUnmount = function (fn) {
135
+ var fnRef = React.useRef(fn);
136
+ // update the ref each render so if it change the newest callback will be invoked
137
+ fnRef.current = fn;
138
+ useEffectOnce(function () { return function () { return fnRef.current(); }; });
139
+ };
140
+
141
+ var useRafState = function (initialState) {
142
+ var frame = React.useRef(0);
143
+ var _a = React.useState(initialState), state = _a[0], setState = _a[1];
144
+ var setRafState = React.useCallback(function (value) {
145
+ cancelAnimationFrame(frame.current);
146
+ frame.current = requestAnimationFrame(function () {
147
+ setState(value);
148
+ });
149
+ }, []);
150
+ useUnmount(function () {
151
+ cancelAnimationFrame(frame.current);
152
+ });
153
+ return [state, setRafState];
154
+ };
155
+
156
+ var useWindowSize = function (initialWidth, initialHeight) {
157
+ if (initialWidth === void 0) { initialWidth = Infinity; }
158
+ if (initialHeight === void 0) { initialHeight = Infinity; }
159
+ var _a = useRafState({
160
+ width: isBrowser ? window.innerWidth : initialWidth,
161
+ height: isBrowser ? window.innerHeight : initialHeight,
162
+ }), state = _a[0], setState = _a[1];
163
+ React.useEffect(function () {
164
+ if (isBrowser) {
165
+ var handler_1 = function () {
166
+ setState({
167
+ width: window.innerWidth,
168
+ height: window.innerHeight,
169
+ });
170
+ };
171
+ on(window, 'resize', handler_1);
172
+ return function () {
173
+ off(window, 'resize', handler_1);
174
+ };
175
+ }
176
+ }, []);
177
+ return state;
178
+ };
179
+
180
+ const ConfettiWrapper = ({ show, confettiProps }) => {
181
+ const { width, height } = useWindowSize();
182
+ if (!show)
183
+ return null;
184
+ return (React.createElement(Confetti, Object.assign({ width: width, height: height }, confettiProps)));
185
+ };
186
+
114
187
  const AchievementContext = React.createContext(undefined);
115
- const AchievementProvider = ({ children, initialBadges = defaultBadges, initialLevels = defaultLevels, storageKey = 'react-achievements', badgesButtonPosition = 'top-right' }) => {
116
- const [metric, setMetric] = React.useState(() => {
117
- const saved = localStorage.getItem(`${storageKey}-metric`);
118
- return saved ? parseInt(saved, 10) : 0;
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);
119
203
  });
120
- const [achievedLevels, setAchievedLevels] = React.useState(() => {
121
- const saved = localStorage.getItem(`${storageKey}-levels`);
204
+ const [achievedAchievements, setAchievedAchievements] = React.useState(() => {
205
+ const saved = localStorage.getItem(`${storageKey}-achievements`);
122
206
  return saved ? JSON.parse(saved) : [];
123
207
  });
124
- const [badges] = React.useState(initialBadges);
125
- const [levels] = React.useState(initialLevels);
126
- const [achievementModalMessage, setAchievementModalMessage] = React.useState('');
208
+ const [newAchievement, setNewAchievement] = React.useState(null);
127
209
  const [showBadges, setShowBadges] = React.useState(false);
128
- const handleAchieve = React.useCallback((level, message) => {
129
- if (!achievedLevels.includes(level)) {
130
- const newAchievedLevels = [...achievedLevels, level];
131
- setAchievedLevels(newAchievedLevels);
132
- localStorage.setItem(`${storageKey}-levels`, JSON.stringify(newAchievedLevels));
133
- setAchievementModalMessage(message);
210
+ const [showConfetti, setShowConfetti] = React.useState(false);
211
+ React.useEffect(() => {
212
+ localStorage.setItem(`${storageKey}-metrics`, JSON.stringify(metrics));
213
+ }, [metrics, storageKey]);
214
+ const checkAchievements = React.useCallback(() => {
215
+ const newAchievements = [];
216
+ Object.entries(config).forEach(([metricKey, conditions]) => {
217
+ const metricValue = metrics[metricKey];
218
+ conditions.forEach(condition => {
219
+ if (condition.check(metricValue) && !achievedAchievements.includes(condition.data.id)) {
220
+ newAchievements.push(condition.data);
221
+ }
222
+ });
223
+ });
224
+ if (newAchievements.length > 0) {
225
+ const updatedAchievements = [...achievedAchievements, ...newAchievements.map(a => a.id)];
226
+ setAchievedAchievements(updatedAchievements);
227
+ localStorage.setItem(`${storageKey}-achievements`, JSON.stringify(updatedAchievements));
228
+ setNewAchievement(newAchievements[0]);
229
+ setShowConfetti(true);
134
230
  }
135
- }, [achievedLevels, storageKey]);
136
- const showAchievementModal = React.useCallback((message) => {
137
- setAchievementModalMessage(message);
138
- }, []);
139
- const showBadgesModal = React.useCallback(() => {
231
+ }, [config, metrics, achievedAchievements, storageKey]);
232
+ React.useEffect(() => {
233
+ checkAchievements();
234
+ }, [checkAchievements]);
235
+ const showBadgesModal = () => {
140
236
  setShowBadges(true);
141
- }, []);
142
- const contextValue = React.useMemo(() => ({
143
- metric,
144
- setMetric: (value) => {
145
- setMetric(value);
146
- localStorage.setItem(`${storageKey}-metric`, value.toString());
237
+ };
238
+ const contextValue = {
239
+ metrics,
240
+ setMetrics: (newMetrics) => {
241
+ setMetrics(prevMetrics => {
242
+ const updatedMetrics = typeof newMetrics === 'function' ? newMetrics(prevMetrics) : newMetrics;
243
+ return updatedMetrics;
244
+ });
147
245
  },
148
- badges,
149
- levels,
150
- achievedLevels,
151
- handleAchieve,
152
- showAchievementModal,
246
+ achievedAchievements,
247
+ checkAchievements,
153
248
  showBadgesModal
154
- }), [metric, badges, levels, achievedLevels, handleAchieve, showAchievementModal, showBadgesModal, storageKey]);
249
+ };
155
250
  return (React.createElement(AchievementContext.Provider, { value: contextValue },
156
251
  children,
157
- React.createElement(AchievementModal$1, { show: !!achievementModalMessage, message: achievementModalMessage, onClose: () => setAchievementModalMessage('') }),
158
- React.createElement(BadgesModal$1, { show: showBadges, badges: badges.filter(badge => { var _a; return achievedLevels.includes(((_a = levels.find(level => level.badgeId === badge.id)) === null || _a === void 0 ? void 0 : _a.level) || -1); }), onClose: () => setShowBadges(false) }),
159
- React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition })));
252
+ React.createElement(AchievementModal$1, { show: !!newAchievement, achievement: newAchievement, onClose: () => {
253
+ setNewAchievement(null);
254
+ setShowConfetti(false);
255
+ } }),
256
+ 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) }),
257
+ React.createElement(BadgesButton$1, { onClick: showBadgesModal, position: badgesButtonPosition }),
258
+ React.createElement(ConfettiWrapper, { show: showConfetti })));
160
259
  };
161
260
  const useAchievement = () => {
162
261
  const context = React.useContext(AchievementContext);
@@ -166,22 +265,6 @@ const useAchievement = () => {
166
265
  return context;
167
266
  };
168
267
 
169
- const Achievement = ({ metric, threshold, onAchieve, message, children }) => {
170
- const { setMetric, achievedLevels, levels, handleAchieve } = useAchievement();
171
- React.useEffect(() => {
172
- if (metric >= threshold && !achievedLevels.includes(threshold)) {
173
- onAchieve();
174
- const levelConfig = levels.find(level => level.threshold === threshold);
175
- if (levelConfig) {
176
- setMetric(metric);
177
- handleAchieve(levelConfig.level, message);
178
- }
179
- }
180
- }, [metric, threshold, onAchieve, achievedLevels, levels, message, setMetric, handleAchieve]);
181
- return React.createElement(React.Fragment, null, children);
182
- };
183
- var Achievement$1 = React.memo(Achievement);
184
-
185
- exports.Achievement = Achievement$1;
186
268
  exports.AchievementProvider = AchievementProvider;
269
+ exports.ConfettiWrapper = ConfettiWrapper;
187
270
  exports.useAchievement = useAchievement;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { AchievementProvider, useAchievement } from './context/AchievementContext';
2
- import Achievement from './components/Achievement';
3
- import { BadgeConfig } from './badges';
4
- import { LevelConfig } from './levels';
5
- export { AchievementProvider, useAchievement, Achievement, BadgeConfig, LevelConfig };
2
+ import { Metrics, AchievementConfig, AchievementData, AchievementCondition } from './types';
3
+ import ConfettiWrapper from './components/ConfettiWrapper';
4
+ export { AchievementProvider, useAchievement, Metrics, AchievementConfig, AchievementData, AchievementCondition, ConfettiWrapper };