promo! react

This commit is contained in:
Zakhar
2026-08-24 13:07:16 +03:00
parent dec37e92c8
commit 8c5d86c9ab
41 changed files with 4174 additions and 2 deletions
+7
View File
@@ -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**';
+31
View File
@@ -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>;
}
+119
View File
@@ -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,
}
+30
View File
@@ -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();
}