react-achievements 1.3.7 → 2.0.3
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 +181 -146
- package/coverage/clover.xml +1 -1
- package/coverage/lcov-report/src/context/AchievementContext.tsx.html +6 -6
- package/coverage/lcov-report/src/context/index.html +1 -1
- package/coverage/lcov-report/src/index.ts.html +1 -1
- package/coverage/lcov.info +1 -1
- package/dist/assets/defaultIcons.d.ts +81 -0
- package/dist/components/AchievementModal.d.ts +3 -2
- package/dist/components/BadgesButton.d.ts +6 -1
- package/dist/components/BadgesModal.d.ts +3 -2
- package/dist/hooks/useAchievement.d.ts +8 -0
- package/dist/index.cjs.js +2100 -103
- package/dist/index.d.ts +5 -3
- package/dist/index.esm.js +2097 -120
- package/dist/providers/AchievementProvider.d.ts +11 -0
- package/dist/redux/achievementSlice.d.ts +25 -0
- package/dist/redux/notificationSlice.d.ts +7 -0
- package/dist/redux/store.d.ts +15 -0
- package/dist/types.d.ts +22 -13
- package/package.json +26 -18
- package/src/assets/defaultIcons.ts +100 -0
- package/src/components/AchievementModal.tsx +37 -8
- package/src/components/BadgesButton.tsx +33 -7
- package/src/components/BadgesModal.tsx +23 -9
- package/src/hooks/useAchievement.ts +20 -0
- package/src/index.ts +11 -7
- package/src/providers/AchievementProvider.tsx +185 -0
- package/src/redux/achievementSlice.ts +71 -0
- package/src/redux/notificationSlice.ts +26 -0
- package/src/redux/store.ts +20 -0
- package/src/types.ts +24 -13
- package/demo/src/stories/Button.jsx +0 -50
- package/demo/src/stories/Button.stories.js +0 -48
- package/demo/src/stories/Configure.mdx +0 -364
- package/demo/src/stories/Header.jsx +0 -59
- package/demo/src/stories/Header.stories.js +0 -28
- package/demo/src/stories/Page.jsx +0 -69
- package/demo/src/stories/Page.stories.js +0 -28
- package/demo/src/stories/assets/accessibility.png +0 -0
- package/demo/src/stories/assets/accessibility.svg +0 -1
- package/demo/src/stories/assets/addon-library.png +0 -0
- package/demo/src/stories/assets/assets.png +0 -0
- package/demo/src/stories/assets/avif-test-image.avif +0 -0
- package/demo/src/stories/assets/context.png +0 -0
- package/demo/src/stories/assets/discord.svg +0 -1
- package/demo/src/stories/assets/docs.png +0 -0
- package/demo/src/stories/assets/figma-plugin.png +0 -0
- package/demo/src/stories/assets/github.svg +0 -1
- package/demo/src/stories/assets/share.png +0 -0
- package/demo/src/stories/assets/styling.png +0 -0
- package/demo/src/stories/assets/testing.png +0 -0
- package/demo/src/stories/assets/theming.png +0 -0
- package/demo/src/stories/assets/tutorials.svg +0 -1
- package/demo/src/stories/assets/youtube.svg +0 -1
- package/demo/src/stories/button.css +0 -30
- package/demo/src/stories/header.css +0 -32
- package/demo/src/stories/page.css +0 -69
- package/dist/stories/Button.d.ts +0 -28
- package/dist/stories/Button.stories.d.ts +0 -23
- package/dist/stories/Header.d.ts +0 -13
- package/dist/stories/Header.stories.d.ts +0 -18
- package/dist/stories/Page.d.ts +0 -3
- package/dist/stories/Page.stories.d.ts +0 -12
- package/src/context/AchievementContext.tsx +0 -185
- package/src/stories/Button.stories.ts +0 -52
- package/src/stories/Button.tsx +0 -48
- package/src/stories/Configure.mdx +0 -364
- package/src/stories/Header.stories.ts +0 -33
- package/src/stories/Header.tsx +0 -56
- package/src/stories/Page.stories.ts +0 -32
- package/src/stories/Page.tsx +0 -73
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +0 -1
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +0 -1
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +0 -1
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +0 -1
- package/src/stories/assets/youtube.svg +0 -1
- package/src/stories/button.css +0 -30
- package/src/stories/header.css +0 -32
- package/src/stories/page.css +0 -69
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AchievementProviderProps, AchievementMetrics as AchievementMetricsType } from '../types';
|
|
3
|
+
export interface AchievementContextType {
|
|
4
|
+
updateMetrics: (newMetrics: AchievementMetricsType | ((prevMetrics: AchievementMetricsType) => AchievementMetricsType)) => void;
|
|
5
|
+
unlockedAchievements: string[];
|
|
6
|
+
resetStorage: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const AchievementContext: React.Context<AchievementContextType | undefined>;
|
|
9
|
+
export declare const useAchievementContext: () => AchievementContextType;
|
|
10
|
+
export declare const AchievementProvider: React.FC<AchievementProviderProps>;
|
|
11
|
+
export declare function mergeDeep(target: any, source: any): any;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
import { AchievementConfiguration, InitialAchievementMetrics, AchievementMetrics } from '../types';
|
|
3
|
+
export interface AchievementState {
|
|
4
|
+
config: AchievementConfiguration;
|
|
5
|
+
metrics: AchievementMetrics;
|
|
6
|
+
unlockedAchievements: string[];
|
|
7
|
+
storageKey: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare const achievementSlice: import("@reduxjs/toolkit").Slice<AchievementState, {
|
|
10
|
+
initialize: (state: import("immer/dist/internal").WritableDraft<AchievementState>, action: PayloadAction<{
|
|
11
|
+
config: AchievementConfiguration;
|
|
12
|
+
initialState?: InitialAchievementMetrics;
|
|
13
|
+
storageKey: string;
|
|
14
|
+
}>) => void;
|
|
15
|
+
setMetrics: (state: import("immer/dist/internal").WritableDraft<AchievementState>, action: PayloadAction<AchievementMetrics>) => void;
|
|
16
|
+
unlockAchievement: (state: import("immer/dist/internal").WritableDraft<AchievementState>, action: PayloadAction<string>) => void;
|
|
17
|
+
resetAchievements: (state: import("immer/dist/internal").WritableDraft<AchievementState>) => void;
|
|
18
|
+
}, "achievements">;
|
|
19
|
+
export declare const initialize: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
20
|
+
config: AchievementConfiguration;
|
|
21
|
+
initialState?: InitialAchievementMetrics;
|
|
22
|
+
storageKey: string;
|
|
23
|
+
}, "achievements/initialize">, setMetrics: import("@reduxjs/toolkit").ActionCreatorWithPayload<AchievementMetrics, "achievements/setMetrics">, unlockAchievement: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "achievements/unlockAchievement">, resetAchievements: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"achievements/resetAchievements">;
|
|
24
|
+
declare const _default: import("@reduxjs/toolkit").Reducer<AchievementState>;
|
|
25
|
+
export default _default;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AchievementDetails } from '../types';
|
|
2
|
+
export interface NotificationState {
|
|
3
|
+
notifications: AchievementDetails[];
|
|
4
|
+
}
|
|
5
|
+
export declare const addNotification: import("@reduxjs/toolkit").ActionCreatorWithPayload<AchievementDetails, "notifications/addNotification">, clearNotifications: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notifications/clearNotifications">;
|
|
6
|
+
declare const _default: import("@reduxjs/toolkit").Reducer<NotificationState>;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AchievementState } from './achievementSlice';
|
|
2
|
+
import { NotificationState } from './notificationSlice';
|
|
3
|
+
export interface RootState {
|
|
4
|
+
achievements: AchievementState;
|
|
5
|
+
notifications: NotificationState;
|
|
6
|
+
}
|
|
7
|
+
declare const store: import("@reduxjs/toolkit/dist/configureStore").ToolkitStore<{
|
|
8
|
+
achievements: AchievementState;
|
|
9
|
+
notifications: NotificationState;
|
|
10
|
+
}, import("@reduxjs/toolkit").AnyAction, [import("@reduxjs/toolkit").ThunkMiddleware<{
|
|
11
|
+
achievements: AchievementState;
|
|
12
|
+
notifications: NotificationState;
|
|
13
|
+
}, import("@reduxjs/toolkit").AnyAction>]>;
|
|
14
|
+
export type AppDispatch = typeof store.dispatch;
|
|
15
|
+
export default store;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export type AchievementMetricValue = number | string | boolean | Date;
|
|
2
|
+
export interface AchievementDetails {
|
|
3
|
+
achievementId: string;
|
|
4
|
+
achievementTitle: string;
|
|
5
|
+
achievementDescription: string;
|
|
6
|
+
achievementIconKey?: string;
|
|
6
7
|
}
|
|
7
|
-
export type
|
|
8
|
-
export interface
|
|
9
|
-
[
|
|
8
|
+
export type AchievementIconRecord = Record<string, string>;
|
|
9
|
+
export interface AchievementConfiguration {
|
|
10
|
+
[metricName: string]: Array<AchievementUnlockCondition<AchievementMetricValue>>;
|
|
10
11
|
}
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
export type InitialAchievementMetrics = Record<string, AchievementMetricValue | AchievementMetricValue[] | undefined>;
|
|
13
|
+
export type AchievementMetrics = Record<string, AchievementMetricValue[]>;
|
|
14
|
+
export interface AchievementProviderProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
config: AchievementConfiguration;
|
|
17
|
+
initialState?: InitialAchievementMetrics;
|
|
18
|
+
storageKey?: string;
|
|
19
|
+
badgesButtonPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
20
|
+
styles?: Partial<import('./defaultStyles').Styles>;
|
|
21
|
+
icons?: Record<string, string>;
|
|
14
22
|
}
|
|
15
|
-
export interface
|
|
16
|
-
|
|
23
|
+
export interface AchievementUnlockCondition<T extends AchievementMetricValue> {
|
|
24
|
+
isConditionMet: (value: T) => boolean;
|
|
25
|
+
achievementDetails: AchievementDetails;
|
|
17
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-achievements",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "This package allows users to transpose a React achievements engine over their React apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -21,29 +21,37 @@
|
|
|
21
21
|
"license": "ISC",
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@chromatic-com/storybook": "^1.6.1",
|
|
24
|
+
"@emotion/react": "^11.14.0",
|
|
25
|
+
"@emotion/styled": "^11.14.0",
|
|
26
|
+
"@mui/icons-material": "^6.4.8",
|
|
24
27
|
"@rollup/plugin-commonjs": "^26.0.1",
|
|
25
28
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
26
|
-
"@storybook/addon-essentials": "^8.
|
|
27
|
-
"@storybook/addon-interactions": "^8.
|
|
28
|
-
"@storybook/addon-links": "^8.
|
|
29
|
-
"@storybook/addon-onboarding": "^8.
|
|
29
|
+
"@storybook/addon-essentials": "^8.6.8",
|
|
30
|
+
"@storybook/addon-interactions": "^8.6.8",
|
|
31
|
+
"@storybook/addon-links": "^8.6.8",
|
|
32
|
+
"@storybook/addon-onboarding": "^8.6.8",
|
|
30
33
|
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
|
|
31
|
-
"@storybook/blocks": "^8.
|
|
32
|
-
"@storybook/react": "^8.
|
|
33
|
-
"@storybook/react-webpack5": "^8.
|
|
34
|
-
"@storybook/test": "^8.
|
|
34
|
+
"@storybook/blocks": "^8.6.8",
|
|
35
|
+
"@storybook/react": "^8.6.8",
|
|
36
|
+
"@storybook/react-webpack5": "^8.6.8",
|
|
37
|
+
"@storybook/test": "^8.6.8",
|
|
35
38
|
"rollup": "^4.19.0",
|
|
36
39
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
37
|
-
"storybook": "^8.
|
|
38
|
-
"typescript": "^5.5.4"
|
|
39
|
-
},
|
|
40
|
-
"dependencies": {
|
|
40
|
+
"storybook": "^8.6.8",
|
|
41
|
+
"typescript": "^5.5.4",
|
|
41
42
|
"@types/jest": "^29.5.12",
|
|
42
43
|
"@types/node": "^20.14.12",
|
|
43
44
|
"@types/react": "^18.3.3",
|
|
44
|
-
"@types/react-dom": "^18.3.0"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"react
|
|
45
|
+
"@types/react-dom": "^18.3.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": "^18.0.0",
|
|
49
|
+
"react-dom": "^18.0.0",
|
|
50
|
+
"react-redux": "^8.0.0 || ^9.0.0",
|
|
51
|
+
"@reduxjs/toolkit": "^1.0.0",
|
|
52
|
+
"react-confetti": "^6.0.0",
|
|
53
|
+
"react-use": "^17.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
48
56
|
}
|
|
49
|
-
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/defaultIcons.ts
|
|
2
|
+
|
|
3
|
+
export const defaultAchievementIcons = {
|
|
4
|
+
// General Progress & Milestones
|
|
5
|
+
levelUp: '🏆',
|
|
6
|
+
questComplete: '📜',
|
|
7
|
+
monsterDefeated: '⚔️',
|
|
8
|
+
itemCollected: '📦',
|
|
9
|
+
challengeCompleted: '🏁',
|
|
10
|
+
milestoneReached: '🏅',
|
|
11
|
+
firstStep: '👣',
|
|
12
|
+
newBeginnings: '🌱',
|
|
13
|
+
breakthrough: '💡',
|
|
14
|
+
growth: '📈',
|
|
15
|
+
|
|
16
|
+
// Social & Engagement
|
|
17
|
+
shared: '🔗',
|
|
18
|
+
liked: '❤️',
|
|
19
|
+
commented: '💬',
|
|
20
|
+
followed: '👥',
|
|
21
|
+
invited: '🤝',
|
|
22
|
+
communityMember: '🏘️',
|
|
23
|
+
supporter: '🌟',
|
|
24
|
+
connected: '🌐',
|
|
25
|
+
participant: '🙋',
|
|
26
|
+
influencer: '📣',
|
|
27
|
+
|
|
28
|
+
// Time & Activity
|
|
29
|
+
activeDay: '☀️',
|
|
30
|
+
activeWeek: '📅',
|
|
31
|
+
activeMonth: '🗓️',
|
|
32
|
+
earlyBird: '⏰',
|
|
33
|
+
nightOwl: '🌙',
|
|
34
|
+
streak: '🔥',
|
|
35
|
+
dedicated: '⏳',
|
|
36
|
+
punctual: '⏱️',
|
|
37
|
+
consistent: '🔄',
|
|
38
|
+
marathon: '🏃',
|
|
39
|
+
|
|
40
|
+
// Creativity & Skill
|
|
41
|
+
artist: '🎨',
|
|
42
|
+
writer: '✍️',
|
|
43
|
+
innovator: '🔬',
|
|
44
|
+
creator: '🛠️',
|
|
45
|
+
expert: '🎓',
|
|
46
|
+
master: '👑',
|
|
47
|
+
pioneer: '🚀',
|
|
48
|
+
performer: '🎭',
|
|
49
|
+
thinker: '🧠',
|
|
50
|
+
explorer: '🗺️',
|
|
51
|
+
|
|
52
|
+
// Achievement Types
|
|
53
|
+
bronze: '🥉',
|
|
54
|
+
silver: '🥈',
|
|
55
|
+
gold: '🥇',
|
|
56
|
+
diamond: '💎',
|
|
57
|
+
legendary: '✨',
|
|
58
|
+
epic: '💥',
|
|
59
|
+
rare: '🔮',
|
|
60
|
+
common: '🔘',
|
|
61
|
+
special: '🎁',
|
|
62
|
+
hidden: '❓',
|
|
63
|
+
|
|
64
|
+
// Numbers & Counters
|
|
65
|
+
one: '1️⃣',
|
|
66
|
+
ten: '🔟',
|
|
67
|
+
hundred: '💯',
|
|
68
|
+
thousand: '🔢',
|
|
69
|
+
|
|
70
|
+
// Actions & Interactions
|
|
71
|
+
clicked: '🖱️',
|
|
72
|
+
used: '🔑',
|
|
73
|
+
found: '🔍',
|
|
74
|
+
built: '🧱',
|
|
75
|
+
solved: '🧩',
|
|
76
|
+
discovered: '🔭',
|
|
77
|
+
unlocked: '🔓',
|
|
78
|
+
upgraded: '⬆️',
|
|
79
|
+
repaired: '🔧',
|
|
80
|
+
defended: '🛡️',
|
|
81
|
+
|
|
82
|
+
// Placeholders
|
|
83
|
+
default: '⭐', // A fallback icon
|
|
84
|
+
loading: '⏳',
|
|
85
|
+
error: '⚠️',
|
|
86
|
+
success: '✅',
|
|
87
|
+
failure: '❌',
|
|
88
|
+
|
|
89
|
+
// Miscellaneous
|
|
90
|
+
trophy: '🏆',
|
|
91
|
+
star: '⭐',
|
|
92
|
+
flag: '🚩',
|
|
93
|
+
puzzle: '🧩',
|
|
94
|
+
gem: '💎',
|
|
95
|
+
crown: '👑',
|
|
96
|
+
medal: '🏅',
|
|
97
|
+
ribbon: '🎗️',
|
|
98
|
+
badge: '🎖️',
|
|
99
|
+
shield: '🛡️',
|
|
100
|
+
};
|
|
@@ -1,24 +1,53 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { AchievementDetails, AchievementIconRecord } from '../types';
|
|
3
3
|
import { Styles } from '../defaultStyles';
|
|
4
|
+
import { defaultAchievementIcons } from '../assets/defaultIcons';
|
|
4
5
|
|
|
5
6
|
interface AchievementModalProps {
|
|
6
7
|
isOpen: boolean;
|
|
7
|
-
achievement:
|
|
8
|
+
achievement: AchievementDetails | null;
|
|
8
9
|
onClose: () => void;
|
|
9
10
|
styles: Styles['achievementModal'];
|
|
11
|
+
icons?: Record<string, string>;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const AchievementModal: React.FC<AchievementModalProps> = ({ isOpen, achievement, onClose, styles }) => {
|
|
14
|
+
const AchievementModal: React.FC<AchievementModalProps> = ({ isOpen, achievement, onClose, styles, icons = {} }) => {
|
|
13
15
|
if (!isOpen || !achievement) return null;
|
|
14
16
|
|
|
17
|
+
const mergedIcons: AchievementIconRecord = { ...defaultAchievementIcons, ...icons };
|
|
18
|
+
const iconToDisplay = achievement?.achievementIconKey ? (mergedIcons[achievement.achievementIconKey] || mergedIcons.default) : mergedIcons.default;
|
|
19
|
+
|
|
15
20
|
return (
|
|
16
|
-
<div style={
|
|
17
|
-
|
|
21
|
+
<div style={{
|
|
22
|
+
...styles.overlay,
|
|
23
|
+
display: 'flex',
|
|
24
|
+
justifyContent: 'center',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
position: 'fixed', // Ensure it covers the screen
|
|
27
|
+
top: 0,
|
|
28
|
+
left: 0,
|
|
29
|
+
width: '100%',
|
|
30
|
+
height: '100%',
|
|
31
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)', // Optional semi-transparent background
|
|
32
|
+
}}>
|
|
33
|
+
<div style={{
|
|
34
|
+
...styles.content,
|
|
35
|
+
display: 'flex',
|
|
36
|
+
flexDirection: 'column',
|
|
37
|
+
alignItems: 'center', // Center content horizontally
|
|
38
|
+
justifyContent: 'center', // Center content vertically
|
|
39
|
+
padding: '20px',
|
|
40
|
+
borderRadius: '8px',
|
|
41
|
+
backgroundColor: 'white', // Or your modal background color
|
|
42
|
+
}}>
|
|
18
43
|
<h2 style={styles.title}>Achievement Unlocked!</h2>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
44
|
+
{iconToDisplay.startsWith('http') || iconToDisplay.startsWith('data:image') ? (
|
|
45
|
+
<img src={iconToDisplay} alt={achievement.achievementTitle} style={styles.icon} />
|
|
46
|
+
) : (
|
|
47
|
+
<p style={{ fontSize: '3em' }}>{iconToDisplay}</p>
|
|
48
|
+
)}
|
|
49
|
+
<h3 style={styles.title}>{achievement.achievementTitle}</h3>
|
|
50
|
+
<p style={styles.description}>{achievement.achievementDescription}</p>
|
|
22
51
|
<button onClick={onClose} style={styles.button}>Okay</button>
|
|
23
52
|
</div>
|
|
24
53
|
</div>
|
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Styles } from '../defaultStyles';
|
|
3
|
+
import { AchievementDetails } from '../types';
|
|
3
4
|
|
|
4
5
|
interface BadgesButtonProps {
|
|
5
6
|
onClick: () => void;
|
|
6
|
-
position
|
|
7
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
7
8
|
styles: Styles['badgesButton'];
|
|
9
|
+
unlockedAchievements: AchievementDetails[];
|
|
10
|
+
icon?: React.ReactNode; // Allow custom icons
|
|
11
|
+
drawer?: boolean; // Indicate if it triggers a drawer
|
|
12
|
+
customStyles?: React.CSSProperties; // Allow custom styles
|
|
8
13
|
}
|
|
9
14
|
|
|
10
|
-
const BadgesButton: React.FC<BadgesButtonProps> = ({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
const BadgesButton: React.FC<BadgesButtonProps> = ({
|
|
16
|
+
onClick,
|
|
17
|
+
position,
|
|
18
|
+
styles,
|
|
19
|
+
unlockedAchievements,
|
|
20
|
+
icon,
|
|
21
|
+
drawer = false,
|
|
22
|
+
customStyles,
|
|
23
|
+
}) => {
|
|
24
|
+
const positionStyle = position
|
|
25
|
+
? {
|
|
26
|
+
[position.split('-')[0]]: '20px',
|
|
27
|
+
[position.split('-')[1]]: '20px',
|
|
28
|
+
}
|
|
29
|
+
: {};
|
|
30
|
+
|
|
31
|
+
const handleButtonClick = () => {
|
|
32
|
+
onClick();
|
|
14
33
|
};
|
|
15
34
|
|
|
35
|
+
const achievementsText = 'View Achievements';
|
|
36
|
+
|
|
37
|
+
const buttonContent = icon ? icon : achievementsText;
|
|
38
|
+
|
|
16
39
|
return (
|
|
17
|
-
<button
|
|
18
|
-
|
|
40
|
+
<button
|
|
41
|
+
onClick={handleButtonClick}
|
|
42
|
+
style={{ ...styles, ...positionStyle, ...customStyles }}
|
|
43
|
+
>
|
|
44
|
+
{buttonContent}
|
|
19
45
|
</button>
|
|
20
46
|
);
|
|
21
47
|
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {AchievementDetails, AchievementIconRecord} from '../types';
|
|
3
3
|
import { Styles } from '../defaultStyles';
|
|
4
|
+
import { defaultAchievementIcons } from '../assets/defaultIcons'; // Import default icons
|
|
4
5
|
|
|
5
6
|
interface BadgesModalProps {
|
|
6
7
|
isOpen: boolean;
|
|
7
|
-
achievements:
|
|
8
|
+
achievements: AchievementDetails[];
|
|
8
9
|
onClose: () => void;
|
|
9
10
|
styles: Styles['badgesModal'];
|
|
11
|
+
icons?: Record<string, string>; // Optional user-specified icons
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const BadgesModal: React.FC<BadgesModalProps> = ({ isOpen, achievements, onClose, styles }) => {
|
|
14
|
+
const BadgesModal: React.FC<BadgesModalProps> = ({ isOpen, achievements, onClose, styles, icons = {} }) => {
|
|
13
15
|
if (!isOpen) return null;
|
|
14
16
|
|
|
15
17
|
return (
|
|
@@ -17,12 +19,24 @@ const BadgesModal: React.FC<BadgesModalProps> = ({ isOpen, achievements, onClose
|
|
|
17
19
|
<div style={styles.content}>
|
|
18
20
|
<h2 style={styles.title}>Your Achievements</h2>
|
|
19
21
|
<div style={styles.badgeContainer}>
|
|
20
|
-
{achievements.map((achievement) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
{achievements.map((achievement) => {
|
|
23
|
+
const mergedIcons: AchievementIconRecord = { ...defaultAchievementIcons, ...icons };
|
|
24
|
+
let iconToDisplay: string | undefined = mergedIcons.default;
|
|
25
|
+
if (achievement.achievementIconKey && mergedIcons[achievement.achievementIconKey]) {
|
|
26
|
+
iconToDisplay = mergedIcons[achievement.achievementIconKey];
|
|
27
|
+
}
|
|
28
|
+
return (
|
|
29
|
+
<div key={achievement.achievementId} style={styles.badge}>
|
|
30
|
+
{/* Render icon based on type (Unicode or image path) */}
|
|
31
|
+
{iconToDisplay.startsWith('http') || iconToDisplay.startsWith('data:image') ? (
|
|
32
|
+
<img src={iconToDisplay} alt={achievement.achievementTitle} style={styles.badgeIcon} />
|
|
33
|
+
) : (
|
|
34
|
+
<p style={{ fontSize: '2em' }}>{iconToDisplay}</p> // Render Unicode as large text
|
|
35
|
+
)}
|
|
36
|
+
<span style={styles.badgeTitle}>{achievement.achievementTitle}</span>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
})}
|
|
26
40
|
</div>
|
|
27
41
|
<button onClick={onClose} style={styles.button}>Close</button>
|
|
28
42
|
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useSelector, useDispatch } from 'react-redux';
|
|
2
|
+
import { RootState, AppDispatch } from '../redux/store';
|
|
3
|
+
import { useAchievementContext } from '../providers/AchievementProvider';
|
|
4
|
+
|
|
5
|
+
export const useAchievement = () => {
|
|
6
|
+
const dispatch: AppDispatch = useDispatch();
|
|
7
|
+
const { updateMetrics, unlockedAchievements, resetStorage } = useAchievementContext() || {};
|
|
8
|
+
const metrics = useSelector((state: RootState) => state.achievements.metrics);
|
|
9
|
+
const notifications = useSelector((state: RootState) => state.notifications.notifications);
|
|
10
|
+
const config = useSelector((state: RootState) => state.achievements.config);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
metrics: metrics,
|
|
14
|
+
unlockedAchievements: unlockedAchievements || [],
|
|
15
|
+
notifications: notifications,
|
|
16
|
+
config: config,
|
|
17
|
+
updateMetrics: updateMetrics || (() => {}),
|
|
18
|
+
resetStorage: resetStorage || (() => {}),
|
|
19
|
+
};
|
|
20
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { AchievementProvider, useAchievement } from './
|
|
2
|
-
import {
|
|
1
|
+
import { AchievementProvider, useAchievementContext as useAchievement } from './providers/AchievementProvider';
|
|
2
|
+
import { AchievementMetrics, AchievementConfiguration, AchievementDetails, AchievementUnlockCondition } from './types';
|
|
3
3
|
import ConfettiWrapper from './components/ConfettiWrapper';
|
|
4
|
+
import achievementReducer from './redux/achievementSlice';
|
|
5
|
+
import notificationReducer from './redux/notificationSlice'
|
|
4
6
|
|
|
5
7
|
export {
|
|
6
8
|
AchievementProvider,
|
|
7
9
|
useAchievement,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ConfettiWrapper
|
|
10
|
+
AchievementMetrics,
|
|
11
|
+
AchievementConfiguration,
|
|
12
|
+
AchievementDetails,
|
|
13
|
+
AchievementUnlockCondition,
|
|
14
|
+
ConfettiWrapper,
|
|
15
|
+
achievementReducer,
|
|
16
|
+
notificationReducer,
|
|
13
17
|
};
|