@umituz/react-native-gamification 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/src/store/gamificationStore.ts +127 -132
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-gamification",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "Generic gamification system for React Native apps - achievements, points, levels, streaks with customizable UI components",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -35,13 +35,15 @@
|
|
|
35
35
|
"react-native": ">=0.74.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
39
|
+
"@types/react": "~19.1.10",
|
|
38
40
|
"@umituz/react-native-design-system": "latest",
|
|
39
41
|
"@umituz/react-native-localization": "latest",
|
|
40
42
|
"@umituz/react-native-storage": "latest",
|
|
41
|
-
"@types/react": "~19.1.10",
|
|
42
43
|
"react": "19.1.0",
|
|
43
44
|
"react-native": "0.81.5",
|
|
44
|
-
"typescript": "~5.9.2"
|
|
45
|
+
"typescript": "~5.9.2",
|
|
46
|
+
"zustand": "^5.0.9"
|
|
45
47
|
},
|
|
46
48
|
"publishConfig": {
|
|
47
49
|
"access": "public"
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* Zustand store with persist middleware
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { persist, createJSONStorage } from "zustand/middleware";
|
|
8
|
-
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
6
|
+
import { createStore } from "@umituz/react-native-storage";
|
|
9
7
|
import type {
|
|
10
8
|
GamificationState,
|
|
11
9
|
GamificationActions,
|
|
@@ -34,134 +32,131 @@ const DEFAULT_STATE: GamificationState = {
|
|
|
34
32
|
|
|
35
33
|
let currentConfig: GamificationConfig | null = null;
|
|
36
34
|
|
|
37
|
-
export const useGamificationStore =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
persist
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
addPoints: (amount: number) => {
|
|
68
|
-
set((state) => ({ points: state.points + amount }));
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
completeTask: () => {
|
|
72
|
-
const state = get();
|
|
73
|
-
const pointsToAdd = currentConfig?.pointsPerAction ?? 15;
|
|
74
|
-
|
|
75
|
-
set({
|
|
76
|
-
totalTasksCompleted: state.totalTasksCompleted + 1,
|
|
77
|
-
points: state.points + pointsToAdd,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Update streak
|
|
81
|
-
get().updateStreak();
|
|
82
|
-
|
|
83
|
-
// Check achievements
|
|
84
|
-
get().checkAchievements();
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
updateStreak: () => {
|
|
88
|
-
const state = get();
|
|
89
|
-
const now = new Date();
|
|
90
|
-
const lastDate = state.streak.lastActivityDate
|
|
91
|
-
? new Date(state.streak.lastActivityDate)
|
|
92
|
-
: null;
|
|
93
|
-
|
|
94
|
-
let newStreak = state.streak.current;
|
|
95
|
-
|
|
96
|
-
if (!lastDate || !isSameDay(lastDate, now)) {
|
|
97
|
-
if (isStreakActive(state.streak.lastActivityDate)) {
|
|
98
|
-
newStreak = state.streak.current + 1;
|
|
99
|
-
} else {
|
|
100
|
-
newStreak = 1;
|
|
101
|
-
}
|
|
35
|
+
export const useGamificationStore = createStore<GamificationState, GamificationActions>({
|
|
36
|
+
name: "gamification-storage",
|
|
37
|
+
initialState: DEFAULT_STATE,
|
|
38
|
+
persist: true,
|
|
39
|
+
version: 1,
|
|
40
|
+
partialize: (state) => ({
|
|
41
|
+
points: state.points,
|
|
42
|
+
totalTasksCompleted: state.totalTasksCompleted,
|
|
43
|
+
achievements: state.achievements,
|
|
44
|
+
streak: state.streak,
|
|
45
|
+
isLoading: false,
|
|
46
|
+
isInitialized: false,
|
|
47
|
+
}),
|
|
48
|
+
actions: (set, get) => ({
|
|
49
|
+
initialize: async (config: GamificationConfig) => {
|
|
50
|
+
currentConfig = config;
|
|
51
|
+
const state = get();
|
|
52
|
+
|
|
53
|
+
// Initialize achievements from config
|
|
54
|
+
const achievements: Achievement[] = config.achievements.map((def) => ({
|
|
55
|
+
...def,
|
|
56
|
+
isUnlocked: false,
|
|
57
|
+
progress: 0,
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Merge with existing unlocked achievements
|
|
61
|
+
const mergedAchievements = achievements.map((ach: Achievement) => {
|
|
62
|
+
const existing = state.achievements.find((a: Achievement) => a.id === ach.id);
|
|
63
|
+
if (existing) {
|
|
64
|
+
return { ...ach, isUnlocked: existing.isUnlocked, progress: existing.progress };
|
|
102
65
|
}
|
|
66
|
+
return ach;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
set({ achievements: mergedAchievements, isInitialized: true });
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
addPoints: (amount: number) => {
|
|
73
|
+
const state = get();
|
|
74
|
+
set({ points: state.points + amount });
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
completeTask: () => {
|
|
78
|
+
const state = get();
|
|
79
|
+
const pointsToAdd = currentConfig?.pointsPerAction ?? 15;
|
|
80
|
+
|
|
81
|
+
set({
|
|
82
|
+
totalTasksCompleted: state.totalTasksCompleted + 1,
|
|
83
|
+
points: state.points + pointsToAdd,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Update streak
|
|
87
|
+
get().updateStreak();
|
|
88
|
+
|
|
89
|
+
// Check achievements
|
|
90
|
+
get().checkAchievements();
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
updateStreak: () => {
|
|
94
|
+
const state = get();
|
|
95
|
+
const now = new Date();
|
|
96
|
+
const lastDate = state.streak.lastActivityDate
|
|
97
|
+
? new Date(state.streak.lastActivityDate)
|
|
98
|
+
: null;
|
|
99
|
+
|
|
100
|
+
let newStreak = state.streak.current;
|
|
101
|
+
|
|
102
|
+
if (!lastDate || !isSameDay(lastDate, now)) {
|
|
103
|
+
if (isStreakActive(state.streak.lastActivityDate)) {
|
|
104
|
+
newStreak = state.streak.current + 1;
|
|
105
|
+
} else {
|
|
106
|
+
newStreak = 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
set({
|
|
111
|
+
streak: {
|
|
112
|
+
current: newStreak,
|
|
113
|
+
longest: Math.max(state.streak.longest, newStreak),
|
|
114
|
+
lastActivityDate: now.toISOString(),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
checkAchievements: () => {
|
|
120
|
+
if (!currentConfig) return [];
|
|
121
|
+
|
|
122
|
+
const state = get();
|
|
123
|
+
const newlyUnlocked: Achievement[] = [];
|
|
124
|
+
|
|
125
|
+
const updatedAchievements = state.achievements.map((ach: Achievement) => {
|
|
126
|
+
if (ach.isUnlocked) return ach;
|
|
127
|
+
|
|
128
|
+
const progress = updateAchievementProgress(
|
|
129
|
+
ach,
|
|
130
|
+
state.totalTasksCompleted,
|
|
131
|
+
state.streak.current
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const shouldUnlock = checkAchievementUnlock(
|
|
135
|
+
ach,
|
|
136
|
+
state.totalTasksCompleted,
|
|
137
|
+
state.streak.current
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (shouldUnlock && !ach.isUnlocked) {
|
|
141
|
+
const unlocked = {
|
|
142
|
+
...ach,
|
|
143
|
+
isUnlocked: true,
|
|
144
|
+
unlockedAt: new Date().toISOString(),
|
|
145
|
+
progress: 100,
|
|
146
|
+
};
|
|
147
|
+
newlyUnlocked.push(unlocked);
|
|
148
|
+
return unlocked;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { ...ach, progress };
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
set({ achievements: updatedAchievements });
|
|
155
|
+
return newlyUnlocked;
|
|
156
|
+
},
|
|
103
157
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
checkAchievements: () => {
|
|
114
|
-
if (!currentConfig) return [];
|
|
115
|
-
|
|
116
|
-
const state = get();
|
|
117
|
-
const newlyUnlocked: Achievement[] = [];
|
|
118
|
-
|
|
119
|
-
const updatedAchievements = state.achievements.map((ach) => {
|
|
120
|
-
if (ach.isUnlocked) return ach;
|
|
121
|
-
|
|
122
|
-
const progress = updateAchievementProgress(
|
|
123
|
-
ach,
|
|
124
|
-
state.totalTasksCompleted,
|
|
125
|
-
state.streak.current
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const shouldUnlock = checkAchievementUnlock(
|
|
129
|
-
ach,
|
|
130
|
-
state.totalTasksCompleted,
|
|
131
|
-
state.streak.current
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
if (shouldUnlock && !ach.isUnlocked) {
|
|
135
|
-
const unlocked = {
|
|
136
|
-
...ach,
|
|
137
|
-
isUnlocked: true,
|
|
138
|
-
unlockedAt: new Date().toISOString(),
|
|
139
|
-
progress: 100,
|
|
140
|
-
};
|
|
141
|
-
newlyUnlocked.push(unlocked);
|
|
142
|
-
return unlocked;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return { ...ach, progress };
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
set({ achievements: updatedAchievements });
|
|
149
|
-
return newlyUnlocked;
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
reset: async () => {
|
|
153
|
-
set(DEFAULT_STATE);
|
|
154
|
-
},
|
|
155
|
-
}),
|
|
156
|
-
{
|
|
157
|
-
name: "gamification-storage",
|
|
158
|
-
storage: createJSONStorage(() => AsyncStorage),
|
|
159
|
-
partialize: (state) => ({
|
|
160
|
-
points: state.points,
|
|
161
|
-
totalTasksCompleted: state.totalTasksCompleted,
|
|
162
|
-
achievements: state.achievements,
|
|
163
|
-
streak: state.streak,
|
|
164
|
-
}),
|
|
165
|
-
}
|
|
166
|
-
)
|
|
167
|
-
);
|
|
158
|
+
reset: async () => {
|
|
159
|
+
set(DEFAULT_STATE);
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
});
|