promo! react
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
export const API_HOST = 'https://local.hvatilo.ru:8055';
|
||||
|
||||
export const jsonHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
export const cspNonce = '**MY_PRETTY_CSP_NONCE**';
|
||||
@@ -0,0 +1,31 @@
|
||||
import { API_HOST } from './config';
|
||||
import { refreshToken } from './tokenApi';
|
||||
|
||||
export function apiUrl(path: string): string {
|
||||
return `${API_HOST}${path}`;
|
||||
}
|
||||
|
||||
export async function requestWithRefresh<TResponse>(
|
||||
url: string,
|
||||
options: RequestInit = {},
|
||||
): Promise<TResponse> {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data?.error_message === 'need_refresh') {
|
||||
await refreshToken();
|
||||
|
||||
const retryResponse = await fetch(url, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
return await retryResponse.json() as Promise<TResponse>;
|
||||
}
|
||||
|
||||
return Promise.resolve(data) as Promise<TResponse>;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { apiUrl, requestWithRefresh } from './http';
|
||||
import { jsonHeaders } from './config';
|
||||
import { initToken, refreshToken } from './tokenApi';
|
||||
import type { CartUpdateResponse, InitialData } from '../types/api';
|
||||
|
||||
export async function loadInitialData(): Promise<InitialData> {
|
||||
try {
|
||||
const response = await fetch(apiUrl('/api/promo/get_initial_data'), {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status === 400) {
|
||||
await clearGuestCookie();
|
||||
document.location.reload();
|
||||
return fallbackInitialData;
|
||||
}
|
||||
|
||||
if (response.status === 401 && data.error_message !== 'need_refresh') {
|
||||
await initToken();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const retryResponse = await fetch(apiUrl('/api/promo/get_initial_data'), {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
return retryResponse.json();
|
||||
}
|
||||
|
||||
if (response.status === 403 || data.error_message === 'need_refresh') {
|
||||
await refreshToken();
|
||||
|
||||
const retryResponse = await fetch(apiUrl('/api/promo/get_initial_data'), {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
return retryResponse.json();
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch {
|
||||
return fallbackInitialData;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCartUpdates(): Promise<CartUpdateResponse> {
|
||||
return new Promise<CartUpdateResponse>((resolve, reject) => {
|
||||
// Use a simple mutex to ensure only one request is active at a time
|
||||
let isPending = false;
|
||||
let pendingRequest: Promise<CartUpdateResponse | null> | null = null;
|
||||
|
||||
if (isPending) {
|
||||
// If already pending, reject the current request and return a placeholder
|
||||
return reject(new Error('getCartUpdates is already in progress'));
|
||||
}
|
||||
|
||||
isPending = true;
|
||||
|
||||
// Create a new request that will resolve when the actual request completes
|
||||
pendingRequest = requestWithRefresh<CartUpdateResponse>(apiUrl('/api/promo/get_updates'), {
|
||||
headers: jsonHeaders,
|
||||
}).then(
|
||||
(response) => {
|
||||
isPending = false;
|
||||
resolve(response as CartUpdateResponse);
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
isPending = false;
|
||||
reject(error);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
// Return the promise so the caller can wait for it
|
||||
return pendingRequest;
|
||||
});
|
||||
}
|
||||
|
||||
export function replaceProduct(productName: string): Promise<unknown> {
|
||||
return requestWithRefresh(apiUrl('/api/promo/replace_item'), {
|
||||
method: 'POST',
|
||||
headers: jsonHeaders,
|
||||
body: JSON.stringify({ product_name: productName }),
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteProduct(productName: string): Promise<unknown> {
|
||||
return requestWithRefresh(apiUrl('/api/promo/delete_item'), {
|
||||
method: 'POST',
|
||||
headers: jsonHeaders,
|
||||
body: JSON.stringify({ product_name: productName }),
|
||||
});
|
||||
}
|
||||
|
||||
export function setOption<TValue>(key: string, value: TValue): Promise<unknown> {
|
||||
return requestWithRefresh(apiUrl('/api/promo/set_option'), {
|
||||
method: 'POST',
|
||||
headers: jsonHeaders,
|
||||
body: JSON.stringify({ [key]: value }),
|
||||
});
|
||||
}
|
||||
|
||||
export function clearGuestCookie(): Promise<unknown> {
|
||||
return requestWithRefresh(apiUrl('/api/guest/clear_cookie'), {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
const fallbackInitialData: InitialData = {
|
||||
cart: [],
|
||||
users: [],
|
||||
initial_groups: [],
|
||||
available_groups: [],
|
||||
days: 7,
|
||||
excess: 0,
|
||||
precision: 0,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { API_HOST } from './config';
|
||||
|
||||
export async function initToken(): Promise<unknown> {
|
||||
const response = await fetch(`${API_HOST}/api/guest/init_cookie?utm_source=hvatilo_promo`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function refreshToken(): Promise<unknown> {
|
||||
const response = await fetch(`${API_HOST}/api/guest/refresh_cookie`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (response.status === 400) {
|
||||
await fetch(`${API_HOST}/api/guest/clear_cookie`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
return initToken();
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
Reference in New Issue
Block a user