forked from Nova-reward/Nova-Rewards
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.js
More file actions
124 lines (109 loc) · 3.91 KB
/
Copy pathapi.js
File metadata and controls
124 lines (109 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import axios from 'axios';
import { saveToOfflineCache, getFromOfflineCache } from './offlineStorage';
import { syncInBackground } from './pwa';
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
headers: { 'Content-Type': 'application/json' },
timeout: 15000,
});
// Request interceptor - add auth token
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Ensure a correlation id is sent with every request for tracing
try {
const existing = config.headers && (config.headers['x-correlation-id'] || config.headers['X-Correlation-Id']);
if (!existing) {
const cid = (typeof crypto !== 'undefined' && crypto.randomUUID) ? crypto.randomUUID() : `cid-${Math.random().toString(36).slice(2,10)}`;
config.headers['x-correlation-id'] = cid;
}
} catch (e) {
// ignore
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor - handle token refresh and offline scenarios
api.interceptors.response.use(
async (response) => {
// Cache successful GET requests for offline access
if (response.config.method === 'get') {
const cacheKey = response.config.url;
await saveToOfflineCache(cacheKey, response.data);
}
return response;
},
async (error) => {
const originalRequest = error.config;
// Handle 401 - try to refresh token
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
const refreshResponse = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/auth/refresh`,
{ refreshToken }
);
const { token } = refreshResponse.data;
localStorage.setItem('token', token);
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// Retry the original request
return api(originalRequest);
}
} catch (refreshError) {
// Refresh failed, redirect to login
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
// Handle offline errors
if (!navigator.onLine || error.message === 'Network Error') {
const cacheKey = error.config?.url;
// Try to get cached data for GET requests
if (error.config?.method === 'get' && cacheKey) {
const cachedData = await getFromOfflineCache(cacheKey);
if (cachedData) {
return { data: cachedData, fromCache: true };
}
}
// Queue POST/PUT/DELETE requests for background sync
if (['post', 'put', 'delete'].includes(error.config?.method)) {
await syncInBackground('sync-transactions');
}
}
return Promise.reject(error);
}
);
// Rewards API
/**
* Fetch a single page of rewards.
* @param {number} page 1-based page number
* @param {number} limit Items per page
* @returns {Promise<{ rewards: any[], userPoints: number, hasMore: boolean, total: number }>}
*/
export async function getRewards(page = 1, limit = 12) {
const response = await api.get('/rewards', { params: { page, limit } });
// Support both paginated and legacy (array) responses
const data = response.data;
if (Array.isArray(data)) {
return { rewards: data, userPoints: 0, hasMore: false, total: data.length };
}
return {
rewards: data.rewards ?? [],
userPoints: data.userPoints ?? 0,
hasMore: data.hasMore ?? false,
total: data.total ?? 0,
};
}
export async function redeemReward(rewardId) {
const response = await api.post('/redemptions', { rewardId });
return response.data;
}
export default api;