@phygitallabs/tapquest-core 2.0.0
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 +210 -0
- package/index.ts +1 -0
- package/package.json +43 -0
- package/src/constants/firebase.ts +36 -0
- package/src/constants/service.ts +30 -0
- package/src/helper/helpers.ts +3 -0
- package/src/helper/index.ts +1 -0
- package/src/index.ts +25 -0
- package/src/modules/achievement/helpers/index.ts +98 -0
- package/src/modules/achievement/hooks/index.ts +171 -0
- package/src/modules/achievement/index.ts +5 -0
- package/src/modules/achievement/types/index.ts +45 -0
- package/src/modules/achivementWithReward/hooks/achivementPlusRewardModel.ts +83 -0
- package/src/modules/achivementWithReward/hooks/index.ts +5 -0
- package/src/modules/achivementWithReward/index.ts +5 -0
- package/src/modules/auth/README.md +527 -0
- package/src/modules/auth/constants/index.ts +9 -0
- package/src/modules/auth/helpers/index.ts +161 -0
- package/src/modules/auth/helpers/refreshToken.ts +63 -0
- package/src/modules/auth/index.ts +20 -0
- package/src/modules/auth/providers/AuthProvider.tsx +207 -0
- package/src/modules/auth/providers/index.ts +1 -0
- package/src/modules/auth/services/FirebaseAuthService.ts +290 -0
- package/src/modules/auth/services/authServiceFactory.ts +22 -0
- package/src/modules/auth/services/index.ts +3 -0
- package/src/modules/auth/store/authSlice.ts +137 -0
- package/src/modules/auth/types/index.ts +109 -0
- package/src/modules/campaign/hooks/index.ts +6 -0
- package/src/modules/campaign/hooks/useCampaignService.ts +7 -0
- package/src/modules/campaign/index.tsx +7 -0
- package/src/modules/campaign/types/campaign.ts +51 -0
- package/src/modules/campaign/types/enums.ts +4 -0
- package/src/modules/campaign/types/index.ts +4 -0
- package/src/modules/campaign/types/requests.ts +46 -0
- package/src/modules/data-tracking/hooks/index.ts +67 -0
- package/src/modules/data-tracking/index.ts +1 -0
- package/src/modules/generate-certificate/hooks/index.ts +8 -0
- package/src/modules/generate-certificate/index.ts +3 -0
- package/src/modules/generate-certificate/types/generateCertificate.ts +7 -0
- package/src/modules/generate-certificate/types/index.ts +7 -0
- package/src/modules/location/hooks/index.ts +8 -0
- package/src/modules/location/hooks/useLocationService.ts +8 -0
- package/src/modules/location/index.tsx +11 -0
- package/src/modules/location/types/index.ts +18 -0
- package/src/modules/location/types/locationModel.ts +21 -0
- package/src/modules/location/utils/index.ts +5 -0
- package/src/modules/location/utils/locationHelpers.ts +13 -0
- package/src/modules/memory/hooks/index.ts +3 -0
- package/src/modules/memory/index.ts +3 -0
- package/src/modules/memory/types/index.ts +3 -0
- package/src/modules/notification/index.ts +2 -0
- package/src/modules/notification/providers/index.tsx +50 -0
- package/src/modules/notification/types/index.ts +3 -0
- package/src/modules/reward/hooks/index.ts +14 -0
- package/src/modules/reward/hooks/useRewardService.ts +14 -0
- package/src/modules/reward/index.tsx +16 -0
- package/src/modules/reward/types/enums.ts +13 -0
- package/src/modules/reward/types/index.ts +4 -0
- package/src/modules/reward/types/requests.ts +281 -0
- package/src/modules/reward/types/reward.ts +90 -0
- package/src/modules/scan-chip/hooks/index.tsx +67 -0
- package/src/modules/scan-chip/index.ts +2 -0
- package/src/modules/scan-chip/types/index.ts +25 -0
- package/src/modules/send-email/hooks/index.ts +2 -0
- package/src/modules/send-email/index.ts +1 -0
- package/src/modules/user-profile/hooks/index.ts +3 -0
- package/src/modules/user-profile/index.ts +3 -0
- package/src/modules/user-profile/types/index.ts +3 -0
- package/src/providers/ServicesProvider.tsx +173 -0
- package/src/providers/TapquestCoreProvider.tsx +64 -0
- package/src/providers/index.ts +1 -0
- package/src/store/hooks.ts +6 -0
- package/src/store/index.ts +45 -0
- package/src/types/common.d.ts +8 -0
- package/src/types/media.ts +26 -0
- package/src/types/service.d.ts +34 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# @phygitallabs/tapquest-core
|
|
2
|
+
|
|
3
|
+
A comprehensive React SDK that provides unified access to all Tapquest platform services including authentication, achievements, rewards, notifications, and certificate generation.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Tapquest Core is the central SDK for building applications on the Phygital Labs Tapquest platform. It consolidates multiple specialized services into a single, easy-to-use React provider that handles authentication, service configuration, and API communication across different environments.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Unified Provider Architecture**: Single `TapquestCoreProvider` that wraps all platform services
|
|
12
|
+
- **Multi-Environment Support**: Seamless switching between DEV, STAGING, and PRODUCTION environments
|
|
13
|
+
- **Service Modules**:
|
|
14
|
+
- Authentication & Firebase integration (v1.0)
|
|
15
|
+
- Scan chip
|
|
16
|
+
- Location
|
|
17
|
+
- Campaign
|
|
18
|
+
- Achievement
|
|
19
|
+
- Reward
|
|
20
|
+
- Notifications
|
|
21
|
+
- Generate Certificate
|
|
22
|
+
- Ads
|
|
23
|
+
- Data tracking
|
|
24
|
+
- **Built-in State Management**: Uses Zustand for efficient state management
|
|
25
|
+
- **React Query Integration**: Optimized API caching and synchronization
|
|
26
|
+
- **TypeScript Support**: Full type safety with generated type definitions
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Using pnpm (recommended for monorepo)
|
|
32
|
+
pnpm add @phygitallabs/tapquest-core
|
|
33
|
+
|
|
34
|
+
# Using npm
|
|
35
|
+
npm install @phygitallabs/tapquest-core
|
|
36
|
+
|
|
37
|
+
# Using yarn
|
|
38
|
+
yarn add @phygitallabs/tapquest-core
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### 1. Basic Setup
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import React from 'react';
|
|
47
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
48
|
+
import { TapquestCoreProvider } from '@phygitallabs/tapquest-core';
|
|
49
|
+
|
|
50
|
+
// Create a query client instance
|
|
51
|
+
const queryClient = new QueryClient();
|
|
52
|
+
|
|
53
|
+
// Define your API configuration
|
|
54
|
+
const apiConfig = {
|
|
55
|
+
environment: 'DEV', // 'DEV' | 'STAGING' | 'PRODUCTION'
|
|
56
|
+
version: 'v1' // 'v1' | 'v2'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function App() {
|
|
60
|
+
return (
|
|
61
|
+
<QueryClientProvider client={queryClient}>
|
|
62
|
+
<TapquestCoreProvider
|
|
63
|
+
queryClient={queryClient}
|
|
64
|
+
apiConfig={apiConfig}
|
|
65
|
+
>
|
|
66
|
+
{/* Your app components */}
|
|
67
|
+
<YourAppContent />
|
|
68
|
+
</TapquestCoreProvider>
|
|
69
|
+
</QueryClientProvider>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Using Services
|
|
75
|
+
|
|
76
|
+
Once wrapped with `TapquestCoreProvider`, all Tapquest services are available throughout your application:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import {
|
|
80
|
+
// Achievement services
|
|
81
|
+
useAchievements,
|
|
82
|
+
|
|
83
|
+
// Reward services
|
|
84
|
+
useRewards,
|
|
85
|
+
|
|
86
|
+
// Notification services
|
|
87
|
+
useNotifications,
|
|
88
|
+
|
|
89
|
+
// Certificate services
|
|
90
|
+
useCertificateGeneration,
|
|
91
|
+
|
|
92
|
+
// Auth services (when implemented)
|
|
93
|
+
// useAuth
|
|
94
|
+
} from '@phygitallabs/tapquest-core';
|
|
95
|
+
|
|
96
|
+
function YourComponent() {
|
|
97
|
+
const { achievements, loading } = useAchievements();
|
|
98
|
+
const { rewards } = useRewards();
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div>
|
|
102
|
+
{/* Render your content using the services */}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Configuration
|
|
109
|
+
|
|
110
|
+
### API Configuration
|
|
111
|
+
|
|
112
|
+
The `apiConfig` object configures which environment and API version to use:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
interface APIConfig {
|
|
116
|
+
environment: 'DEV' | 'STAGING' | 'PRODUCTION';
|
|
117
|
+
version: 'v1' | 'v2';
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Environment URLs
|
|
122
|
+
|
|
123
|
+
The SDK automatically routes requests to the appropriate endpoints based on your environment:
|
|
124
|
+
|
|
125
|
+
- **Development**: `https://api.dev.phygital.vn`
|
|
126
|
+
- **Staging**: `https://api.staging.phygital.vn`
|
|
127
|
+
- **Production**: `https://api.phygital.vn`
|
|
128
|
+
|
|
129
|
+
## Architecture
|
|
130
|
+
|
|
131
|
+
### Provider Hierarchy
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
TapquestCoreProvider
|
|
135
|
+
├── FirebaseProvider (Authentication & Real-time features)
|
|
136
|
+
├── AuthProvider (User authentication state)
|
|
137
|
+
└── ServicesProvider (API services & configuration)
|
|
138
|
+
├── Achievement Service
|
|
139
|
+
├── Reward Service
|
|
140
|
+
├── Notification Service
|
|
141
|
+
└── Certificate Service
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Module Structure
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
src/
|
|
148
|
+
├── constants/ # Environment and service configurations
|
|
149
|
+
├── modules/ # Service-specific modules
|
|
150
|
+
│ ├── achievement/ # Achievement system integration
|
|
151
|
+
│ ├── auth/ # Authentication utilities (in development)
|
|
152
|
+
│ ├── notification/ # Push notification services
|
|
153
|
+
│ ├── reward/ # Reward management
|
|
154
|
+
│ └── generate-certificate/ # Certificate generation
|
|
155
|
+
├── providers/ # React providers and context
|
|
156
|
+
└── types/ # TypeScript type definitions
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
### Building
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Build the package
|
|
165
|
+
pnpm build
|
|
166
|
+
|
|
167
|
+
# Build in watch mode for development
|
|
168
|
+
pnpm dev
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Dependencies
|
|
172
|
+
|
|
173
|
+
**Core Dependencies:**
|
|
174
|
+
- React 18.2+
|
|
175
|
+
- @tanstack/react-query ^5.66.8
|
|
176
|
+
- Zustand ^5.0.8
|
|
177
|
+
- Axios ^1.8.4
|
|
178
|
+
|
|
179
|
+
**Internal Workspace Dependencies:**
|
|
180
|
+
- @phygitallabs/achievement
|
|
181
|
+
- @phygitallabs/api-core
|
|
182
|
+
- @phygitallabs/generate-certificate
|
|
183
|
+
- @phygitallabs/notification-api
|
|
184
|
+
- @phygitallabs/reward
|
|
185
|
+
- pgl-platform
|
|
186
|
+
|
|
187
|
+
## TypeScript Support
|
|
188
|
+
|
|
189
|
+
This package is built with TypeScript and includes full type definitions. All exported functions, hooks, and components are fully typed for the best development experience.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import type { APIConfig, ServiceConfig } from '@phygitallabs/tapquest-core';
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
This package is part of the Phygital Labs monorepo. When contributing:
|
|
198
|
+
|
|
199
|
+
1. Ensure all new features include TypeScript types
|
|
200
|
+
2. Follow the existing module structure for new services
|
|
201
|
+
3. Update this README when adding new functionality
|
|
202
|
+
4. Run tests and build before submitting changes
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
Private package - All rights reserved by Phygital Labs.
|
|
207
|
+
|
|
208
|
+
## Support
|
|
209
|
+
|
|
210
|
+
For questions or issues, please contact the Phygital Labs development team or create an issue in the project repository.
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src";
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phygitallabs/tapquest-core",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"preversion": "npm run build",
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"dev": "tsup --watch"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@phygitallabs/achievement": "^2.2.0",
|
|
16
|
+
"@phygitallabs/api-core": "^2.0.0",
|
|
17
|
+
"@phygitallabs/generate-certificate": "^2.0.0",
|
|
18
|
+
"@phygitallabs/notification-api": "^2.0.0",
|
|
19
|
+
"@phygitallabs/reward": "^2.0.0",
|
|
20
|
+
"@phygitallabs/helpers": "^2.0.0",
|
|
21
|
+
"@reduxjs/toolkit": "^1.9.5",
|
|
22
|
+
"@tanstack/react-query": "^5.66.8",
|
|
23
|
+
"axios": "^1.8.4",
|
|
24
|
+
"mem": "^10.0.0",
|
|
25
|
+
"posthog-js": "^1.246.0",
|
|
26
|
+
"react": ">=18.2",
|
|
27
|
+
"react-redux": "^8.0.7",
|
|
28
|
+
"redux-persist": "^6.0.0",
|
|
29
|
+
"typescript": "latest",
|
|
30
|
+
"uuid": "^13.0.0",
|
|
31
|
+
"zustand": "^5.0.8"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"firebase": "^10.0.0",
|
|
35
|
+
"react": ">=18.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/react": "^18.2.79",
|
|
39
|
+
"@types/react-redux": "^7.1.33",
|
|
40
|
+
"tsup": "latest",
|
|
41
|
+
"typescript": "latest"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { EnvironmentType } from "../types/common";
|
|
2
|
+
|
|
3
|
+
const FIREBASE_ENV = {
|
|
4
|
+
dev:{
|
|
5
|
+
"apiKey": "AIzaSyBFReJURBrU3hyiVvHHhMKcruiyy3mkEks",
|
|
6
|
+
"authDomain": "phy-nomion-staging.firebaseapp.com",
|
|
7
|
+
"projectId": "phy-nomion-staging",
|
|
8
|
+
"storageBucket": "phy-nomion-staging.appspot.com",
|
|
9
|
+
"messagingSenderId": "445937266499",
|
|
10
|
+
"appId": "1:445937266499:web:9ce9b49c13dcecd9b889f6",
|
|
11
|
+
"measurementId": "G-GWYSS45K15"
|
|
12
|
+
},
|
|
13
|
+
staging:{
|
|
14
|
+
"apiKey": "AIzaSyAXrJB8n4eZyq43kb9pUSelz9vfkJLHRK8",
|
|
15
|
+
"authDomain": "phygital-388705.firebaseapp.com",
|
|
16
|
+
"projectId": "phygital-388705",
|
|
17
|
+
"storageBucket": "assets-fygito",
|
|
18
|
+
"messagingSenderId": "174350081236",
|
|
19
|
+
"appId": "1:174350081236:web:da29144bf47cf775a5af41",
|
|
20
|
+
"measurementId": "G-JVHVWXMDC9"
|
|
21
|
+
},
|
|
22
|
+
production:{
|
|
23
|
+
"apiKey": "AIzaSyAXrJB8n4eZyq43kb9pUSelz9vfkJLHRK8",
|
|
24
|
+
"authDomain": "phygital-388705.firebaseapp.com",
|
|
25
|
+
"projectId": "phygital-388705",
|
|
26
|
+
"storageBucket": "assets-fygito",
|
|
27
|
+
"messagingSenderId": "174350081236",
|
|
28
|
+
"appId": "1:174350081236:web:da29144bf47cf775a5af41",
|
|
29
|
+
"measurementId": "G-JVHVWXMDC9"
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const firebaseConfig = (env: EnvironmentType) => {
|
|
34
|
+
return FIREBASE_ENV[env];
|
|
35
|
+
};
|
|
36
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const serviceApiUrl = {
|
|
2
|
+
dev: {
|
|
3
|
+
API_BASE_URL: "https://backend-dev.nomion.io",
|
|
4
|
+
API_BASE_CORE_URL: "https://backend-dev.nomion.io/core",
|
|
5
|
+
API_ACHIEVEMENT_URL: "https://backend-dev.nomion.io/achievement",
|
|
6
|
+
API_REWARD_URL: "https://backend-dev.nomion.io/reward",
|
|
7
|
+
API_GENERATE_CERTIFICATE_URL: "https://media-prc-dev.nomion.io/api",
|
|
8
|
+
API_NOTIFICATION_SOCKET_URL: "https://backend-dev.nomion.io/notification-ws/ws",
|
|
9
|
+
},
|
|
10
|
+
staging: {
|
|
11
|
+
API_BASE_URL: "https://backend-staging.nomion.io",
|
|
12
|
+
API_BASE_CORE_URL: "https://backend-staging.nomion.io/core",
|
|
13
|
+
API_ACHIEVEMENT_URL: "https://backend-staging.nomion.io/achievement",
|
|
14
|
+
API_REWARD_URL: "https://backend-staging.nomion.io/reward",
|
|
15
|
+
API_GENERATE_CERTIFICATE_URL: "https://media-prc-staging.nomion.io/api",
|
|
16
|
+
API_NOTIFICATION_SOCKET_URL: "https://backend-staging.nomion.io/notification-ws/ws",
|
|
17
|
+
|
|
18
|
+
},
|
|
19
|
+
production: {
|
|
20
|
+
API_BASE_URL: "https://backend.nomion.io",
|
|
21
|
+
API_BASE_CORE_URL: "https://backend.nomion.io/core",
|
|
22
|
+
API_ACHIEVEMENT_URL: "https://backend.nomion.io/achievement",
|
|
23
|
+
API_REWARD_URL: "https://backend.nomion.io/reward",
|
|
24
|
+
API_GENERATE_CERTIFICATE_URL: "https://media-prc.nomion.io/api",
|
|
25
|
+
API_NOTIFICATION_SOCKET_URL: "https://backend.nomion.io/notification-ws/ws",
|
|
26
|
+
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default serviceApiUrl;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./helpers";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Export all modules
|
|
2
|
+
export * from "./modules/achievement";
|
|
3
|
+
export * from "./modules/reward";
|
|
4
|
+
export * from "./modules/notification";
|
|
5
|
+
export * from "./modules/memory";
|
|
6
|
+
|
|
7
|
+
export * from "./modules/auth";
|
|
8
|
+
export * from "./modules/user-profile";
|
|
9
|
+
|
|
10
|
+
export * from "./modules/scan-chip";
|
|
11
|
+
export * from "./modules/campaign";
|
|
12
|
+
|
|
13
|
+
export * from "./modules/location";
|
|
14
|
+
|
|
15
|
+
export * from "./modules/generate-certificate";
|
|
16
|
+
|
|
17
|
+
export * from "./modules/data-tracking";
|
|
18
|
+
|
|
19
|
+
export * from "./providers";
|
|
20
|
+
|
|
21
|
+
export * from "./modules/achivementWithReward";
|
|
22
|
+
|
|
23
|
+
export * from "./modules/send-email";
|
|
24
|
+
|
|
25
|
+
export * from "./helper";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Achievement, UserAchievementProgress } from "../types";
|
|
2
|
+
|
|
3
|
+
const getLocationIdsFromAchievementRule = (achievement: Achievement) => {
|
|
4
|
+
if (!achievement.rule) return [];
|
|
5
|
+
const locationIds: string[] = [];
|
|
6
|
+
Object.values(achievement.rule).forEach((ruleList) => {
|
|
7
|
+
if (!ruleList.rules) return;
|
|
8
|
+
ruleList.rules.forEach((rule) => {
|
|
9
|
+
if (!rule.filter) return;
|
|
10
|
+
Object.values(rule.filter).forEach((filterList) => {
|
|
11
|
+
if (!filterList.filters) return;
|
|
12
|
+
filterList.filters.forEach((filter) => {
|
|
13
|
+
if (filter.label === "location_id" && filter.value) {
|
|
14
|
+
if (Array.isArray(filter.value)) {
|
|
15
|
+
locationIds.push(...filter.value);
|
|
16
|
+
} else {
|
|
17
|
+
locationIds.push(filter.value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
return Array.from(new Set(locationIds)) as string[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getActionsFromAchievementRule = (achievement: Achievement) => {
|
|
28
|
+
if (!achievement.rule) return [];
|
|
29
|
+
const actions: string[] = [];
|
|
30
|
+
Object.values(achievement.rule).forEach((ruleList) => {
|
|
31
|
+
if (!ruleList.rules) return;
|
|
32
|
+
ruleList.rules.forEach((rule) => {
|
|
33
|
+
if (rule.action) {
|
|
34
|
+
actions.push(rule.action)
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return Array.from(new Set(actions)) as string[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isAchievementCompleted = (achievement: UserAchievementProgress) => {
|
|
42
|
+
return achievement.isCompleted || achievement.overallPercentage === 100;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
|
|
46
|
+
? `${T}${Capitalize<SnakeToCamelCase<U>>}`
|
|
47
|
+
: S;
|
|
48
|
+
|
|
49
|
+
type ConvertSnakeToCamel<T> = T extends (infer U)[]
|
|
50
|
+
? ConvertSnakeToCamel<U>[]
|
|
51
|
+
: T extends Record<string, unknown>
|
|
52
|
+
? {
|
|
53
|
+
[K in keyof T as K extends string
|
|
54
|
+
? SnakeToCamelCase<K>
|
|
55
|
+
: K]: ConvertSnakeToCamel<T[K]>;
|
|
56
|
+
}
|
|
57
|
+
: T;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Converts snake_case keys to camelCase keys in an object or array of objects
|
|
61
|
+
*/
|
|
62
|
+
export function convertSnakeToCamel<T>(obj: T): ConvertSnakeToCamel<T> {
|
|
63
|
+
if (obj === null || obj === undefined) {
|
|
64
|
+
return obj as ConvertSnakeToCamel<T>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Array.isArray(obj)) {
|
|
68
|
+
return obj.map((item) =>
|
|
69
|
+
convertSnakeToCamel(item)
|
|
70
|
+
) as ConvertSnakeToCamel<T>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof obj === "object" && obj.constructor === Object) {
|
|
74
|
+
const converted: Record<string, unknown> = {};
|
|
75
|
+
|
|
76
|
+
for (const key in obj) {
|
|
77
|
+
if (obj.hasOwnProperty(key)) {
|
|
78
|
+
const camelKey = key.replace(/_([a-z])/g, (_, letter) =>
|
|
79
|
+
letter.toUpperCase()
|
|
80
|
+
);
|
|
81
|
+
converted[camelKey] = convertSnakeToCamel(
|
|
82
|
+
(obj as Record<string, unknown>)[key]
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return converted as ConvertSnakeToCamel<T>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return obj as ConvertSnakeToCamel<T>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
getLocationIdsFromAchievementRule,
|
|
96
|
+
getActionsFromAchievementRule,
|
|
97
|
+
isAchievementCompleted
|
|
98
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { GetManyAchievementProgressParams, GetManyDeviceUidAchievementProgressParams, GetManyUserOrDeviceAchievementProgressParams } from "@phygitallabs/achievement";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
GetManyAchievementProgressByDeviceParams,
|
|
5
|
+
} from "@phygitallabs/api-core";
|
|
6
|
+
|
|
7
|
+
import { useManyAchievementProgress as useManyAchievementProgressCore } from "@phygitallabs/achievement";
|
|
8
|
+
import { useManyAchievementProgressByDevice as useManyAchievementProgressByDeviceCore } from "@phygitallabs/api-core";
|
|
9
|
+
|
|
10
|
+
// Core react query hooks
|
|
11
|
+
import {
|
|
12
|
+
useAchievementProgress,
|
|
13
|
+
useManyAchievements,
|
|
14
|
+
useUserAchievementAction,
|
|
15
|
+
useManyChildrenAchievements,
|
|
16
|
+
useOneAchievement,
|
|
17
|
+
useManyAchievementsRewardModels,
|
|
18
|
+
} from "@phygitallabs/achievement";
|
|
19
|
+
|
|
20
|
+
import { isAchievementCompleted, convertSnakeToCamel } from "../helpers";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
useAchievementProgress,
|
|
24
|
+
useManyAchievements,
|
|
25
|
+
useUserAchievementAction,
|
|
26
|
+
useManyChildrenAchievements,
|
|
27
|
+
useOneAchievement,
|
|
28
|
+
useManyAchievementsRewardModels,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Customize react query hooks
|
|
32
|
+
|
|
33
|
+
// Get achievement progress list by userid or deviceuid base on login status
|
|
34
|
+
export const useManyAchievementProgress = (
|
|
35
|
+
params: GetManyUserOrDeviceAchievementProgressParams,
|
|
36
|
+
options?: any
|
|
37
|
+
) => {
|
|
38
|
+
|
|
39
|
+
const isLoggedIn = !!params.userId;
|
|
40
|
+
|
|
41
|
+
const queryParams = isLoggedIn
|
|
42
|
+
? {
|
|
43
|
+
achievementIds: params.achievementIds,
|
|
44
|
+
userId: params.userId!,
|
|
45
|
+
applicationId: params.applicationId,
|
|
46
|
+
} as GetManyAchievementProgressParams
|
|
47
|
+
: {
|
|
48
|
+
achievement_ids: params.achievementIds,
|
|
49
|
+
device_uid: params.deviceUid!,
|
|
50
|
+
applicationId: params.applicationId,
|
|
51
|
+
} as GetManyDeviceUidAchievementProgressParams;
|
|
52
|
+
|
|
53
|
+
if (isLoggedIn) {
|
|
54
|
+
return useManyAchievementProgressCore(queryParams as GetManyAchievementProgressParams,
|
|
55
|
+
{
|
|
56
|
+
...options,
|
|
57
|
+
select: (data) => data.map((item) => ({
|
|
58
|
+
...item,
|
|
59
|
+
isCompleted: isAchievementCompleted(item),
|
|
60
|
+
})),
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return useManyAchievementProgressByDeviceCore(
|
|
66
|
+
queryParams as GetManyAchievementProgressByDeviceParams, {
|
|
67
|
+
...options,
|
|
68
|
+
select: (data) => {
|
|
69
|
+
const camelCaseData = convertSnakeToCamel(data);
|
|
70
|
+
return camelCaseData.map((item) => ({
|
|
71
|
+
...item,
|
|
72
|
+
isCompleted: isAchievementCompleted(item),
|
|
73
|
+
}));
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// export const useManyAchievementWithProgress = (
|
|
79
|
+
// params: UseManyAchievementWithProgressParams,
|
|
80
|
+
// options?: {
|
|
81
|
+
// achievementOptions?: Partial<UseQueryOptions>;
|
|
82
|
+
// progressOptions?: Partial<UseQueryOptions>;
|
|
83
|
+
// }
|
|
84
|
+
// ): UseManyAchievementWithProgressReturn => {
|
|
85
|
+
// // Fetch achievements with retry logic
|
|
86
|
+
// const achievementsQuery = useManyAchievements(
|
|
87
|
+
// {
|
|
88
|
+
// ...(params.achievementIds?.length && { ids: params.achievementIds }),
|
|
89
|
+
// },
|
|
90
|
+
// {
|
|
91
|
+
// retry: (failureCount: number, error: Error) => {
|
|
92
|
+
// // Retry up to 3 times with exponential backoff
|
|
93
|
+
// if (failureCount < 3) {
|
|
94
|
+
// console.warn(`Achievement fetch failed, retrying... (${failureCount + 1}/3)`, error);
|
|
95
|
+
// return true;
|
|
96
|
+
// }
|
|
97
|
+
// return false;
|
|
98
|
+
// },
|
|
99
|
+
// retryDelay: (attemptIndex: number) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
100
|
+
// staleTime: 5 * 60 * 1000, // 5 minutes
|
|
101
|
+
// gcTime: 10 * 60 * 1000, // 10 minutes
|
|
102
|
+
// ...options?.achievementOptions,
|
|
103
|
+
// }
|
|
104
|
+
// );
|
|
105
|
+
|
|
106
|
+
// // Prepare achievement IDs for progress query
|
|
107
|
+
// const achievementIds = useMemo(() => {
|
|
108
|
+
// if (params.achievementIds?.length) {
|
|
109
|
+
// return params.achievementIds;
|
|
110
|
+
// }
|
|
111
|
+
// return achievementsQuery.data?.data?.map((a: any) => a.id) || [];
|
|
112
|
+
// }, [params.achievementIds, achievementsQuery.data]);
|
|
113
|
+
|
|
114
|
+
// // Fetch progress with retry logic (dependent on achievements)
|
|
115
|
+
// const progressQuery = useManyAchievementProgress(
|
|
116
|
+
// {
|
|
117
|
+
// achievementIds,
|
|
118
|
+
// userId: params.userId,
|
|
119
|
+
// deviceUid: params.deviceUid,
|
|
120
|
+
// applicationId: params.applicationId,
|
|
121
|
+
// },
|
|
122
|
+
// {
|
|
123
|
+
// enabled: achievementIds.length > 0
|
|
124
|
+
// }
|
|
125
|
+
// );
|
|
126
|
+
|
|
127
|
+
// // Combine data with error flags
|
|
128
|
+
// const combinedData = useMemo((): AchievementWithProgress[] => {
|
|
129
|
+
// const achievements = achievementsQuery.data?.data || [];
|
|
130
|
+
// const progressData = progressQuery.data || [];
|
|
131
|
+
|
|
132
|
+
// return achievements.map((achievement: any) => {
|
|
133
|
+
// const progress = progressData.find((p: any) => p.achievementId === achievement.id) || null;
|
|
134
|
+
|
|
135
|
+
// return {
|
|
136
|
+
// achievement,
|
|
137
|
+
// progress,
|
|
138
|
+
// hasAchievementError: !!achievementsQuery.error,
|
|
139
|
+
// hasProgressError: !!progressQuery.error,
|
|
140
|
+
// };
|
|
141
|
+
// });
|
|
142
|
+
// }, [achievementsQuery.data, progressQuery.data, achievementsQuery.error, progressQuery.error]);
|
|
143
|
+
|
|
144
|
+
// // Refetch functions
|
|
145
|
+
// const refetchAchievements = () => {
|
|
146
|
+
// console.log("Manually refetching achievements...");
|
|
147
|
+
// achievementsQuery.refetch();
|
|
148
|
+
// };
|
|
149
|
+
|
|
150
|
+
// const refetchProgress = () => {
|
|
151
|
+
// console.log("Manually refetching progress...");
|
|
152
|
+
// progressQuery.refetch();
|
|
153
|
+
// };
|
|
154
|
+
|
|
155
|
+
// const refetchAll = () => {
|
|
156
|
+
// console.log("Manually refetching all data...");
|
|
157
|
+
// Promise.all([achievementsQuery.refetch(), progressQuery.refetch()]);
|
|
158
|
+
// };
|
|
159
|
+
|
|
160
|
+
// return {
|
|
161
|
+
// data: combinedData,
|
|
162
|
+
// isLoading: achievementsQuery.isLoading || (achievementIds.length > 0 && progressQuery.isLoading),
|
|
163
|
+
// isSuccess: achievementsQuery.isSuccess && (achievementIds.length === 0 || progressQuery.isSuccess),
|
|
164
|
+
// achievementsError: achievementsQuery.error as Error | null,
|
|
165
|
+
// progressError: progressQuery.error as Error | null,
|
|
166
|
+
// refetchAchievements,
|
|
167
|
+
// refetchProgress,
|
|
168
|
+
// refetchAll,
|
|
169
|
+
// };
|
|
170
|
+
// };
|
|
171
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AchievementRuleActionType,
|
|
3
|
+
AchievementServiceProvider,
|
|
4
|
+
} from "@phygitallabs/achievement";
|
|
5
|
+
|
|
6
|
+
import type { Achievement, GetAchievementProgressResponse, UserAchievementProgress } from "@phygitallabs/achievement";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
Achievement,
|
|
10
|
+
GetAchievementProgressResponse,
|
|
11
|
+
UserAchievementProgress,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Combined types for hooks
|
|
15
|
+
export interface AchievementWithProgress {
|
|
16
|
+
achievement: Achievement;
|
|
17
|
+
progress: UserAchievementProgress | null;
|
|
18
|
+
hasProgressError?: boolean;
|
|
19
|
+
hasAchievementError?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UseManyAchievementWithProgressParams {
|
|
23
|
+
userId?: string;
|
|
24
|
+
deviceUid?: string;
|
|
25
|
+
applicationId?: string;
|
|
26
|
+
achievementIds?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface UseManyAchievementWithProgressReturn {
|
|
30
|
+
data: AchievementWithProgress[];
|
|
31
|
+
isLoading: boolean;
|
|
32
|
+
isSuccess: boolean;
|
|
33
|
+
achievementsError: Error | null;
|
|
34
|
+
progressError: Error | null;
|
|
35
|
+
refetchAchievements: () => void;
|
|
36
|
+
refetchProgress: () => void;
|
|
37
|
+
refetchAll: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export enum AchievementType {
|
|
41
|
+
DEFAULT = "default",
|
|
42
|
+
MULTIPLE_CHECK_INS = "multiple_check_ins",
|
|
43
|
+
RANDOM_CHECK_INS = "random_check_ins",
|
|
44
|
+
GROUP_MISSION = "group_mission",
|
|
45
|
+
}
|