fix: make client flow load without edge function
This commit is contained in:
parent
ffc7f3a97d
commit
3b4b6648ff
|
|
@ -1,6 +1,10 @@
|
||||||
import { supabase, hasSupabaseConfig } from "../supabaseClient";
|
import { supabase, hasSupabaseConfig } from "../supabaseClient";
|
||||||
|
|
||||||
const SHOWCASE_TOKEN = "showcase";
|
const SHOWCASE_TOKEN = "showcase";
|
||||||
|
const LOCAL_CLIENT_FLOW_TOKEN_PREFIX = "client-flow-";
|
||||||
|
const LOCAL_CLIENT_FLOW_TOKEN = "client-flow-1001";
|
||||||
|
|
||||||
|
const localDeliveryInvitationCache = new Map();
|
||||||
|
|
||||||
const formatIsoDate = (date) => date.toISOString().slice(0, 10);
|
const formatIsoDate = (date) => date.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
|
@ -10,6 +14,63 @@ const addDays = (date, days) => {
|
||||||
return next;
|
return next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cloneInvitation = (invitation) => JSON.parse(JSON.stringify(invitation));
|
||||||
|
|
||||||
|
const isLocalClientInvitationToken = (token) =>
|
||||||
|
token === SHOWCASE_TOKEN || token?.startsWith(LOCAL_CLIENT_FLOW_TOKEN_PREFIX);
|
||||||
|
|
||||||
|
const buildLocalClientInvitation = (token = LOCAL_CLIENT_FLOW_TOKEN, now = new Date()) => {
|
||||||
|
const firstDay = formatIsoDate(addDays(now, 1));
|
||||||
|
const secondDay = formatIsoDate(addDays(now, 2));
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
orderId: "order-cd-240031",
|
||||||
|
orderNumber: "CD-240031",
|
||||||
|
customerName: "Мария Волкова",
|
||||||
|
customerPhone: "+7 978 000-12-31",
|
||||||
|
state: "awaiting_choice",
|
||||||
|
deliveryDate: firstDay,
|
||||||
|
deliveryTime: "До обеда",
|
||||||
|
orderItems: [
|
||||||
|
{ name: "Кухонный гарнитур", quantity: "1 комплект" },
|
||||||
|
{ name: "Фурнитура Blum", quantity: "12 шт" },
|
||||||
|
{ name: "Монтажный комплект", quantity: "1 набор" },
|
||||||
|
],
|
||||||
|
availableSlots: [
|
||||||
|
`${firstDay}, До обеда`,
|
||||||
|
`${firstDay}, После обеда`,
|
||||||
|
`${secondDay}, До обеда`,
|
||||||
|
`${secondDay}, После обеда`,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCachedInvitation = (token) => {
|
||||||
|
const invitation = localDeliveryInvitationCache.get(token);
|
||||||
|
return invitation ? cloneInvitation(invitation) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cacheInvitation = (invitation) => {
|
||||||
|
localDeliveryInvitationCache.set(invitation.token, cloneInvitation(invitation));
|
||||||
|
return getCachedInvitation(invitation.token);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFallbackInvitation = (token) =>
|
||||||
|
token === SHOWCASE_TOKEN
|
||||||
|
? buildShowcaseInvitation(token)
|
||||||
|
: buildLocalClientInvitation(token);
|
||||||
|
|
||||||
|
const warmLocalInvitationCache = (token, functionName) => {
|
||||||
|
void invokeDeliveryFunction(functionName, { token })
|
||||||
|
.then((response) => {
|
||||||
|
if (response?.invitation) {
|
||||||
|
cacheInvitation(response.invitation);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => undefined);
|
||||||
|
};
|
||||||
|
|
||||||
export const buildShowcaseInvitation = (token = SHOWCASE_TOKEN, now = new Date()) => {
|
export const buildShowcaseInvitation = (token = SHOWCASE_TOKEN, now = new Date()) => {
|
||||||
const firstDay = formatIsoDate(addDays(now, 1));
|
const firstDay = formatIsoDate(addDays(now, 1));
|
||||||
const secondDay = formatIsoDate(addDays(now, 2));
|
const secondDay = formatIsoDate(addDays(now, 2));
|
||||||
|
|
@ -53,17 +114,66 @@ const invokeDeliveryFunction = async (functionName, body) => {
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchDeliveryInvitation = async (token) =>
|
export const __resetLocalDeliveryInvitationCache = () => {
|
||||||
(token === SHOWCASE_TOKEN
|
localDeliveryInvitationCache.clear();
|
||||||
? buildShowcaseInvitation(token)
|
};
|
||||||
: (await invokeDeliveryFunction("get-delivery-invitation", { token })).invitation);
|
|
||||||
|
|
||||||
export const confirmDeliveryChoice = async ({ token, deliveryDate, deliveryTime }) =>
|
export const fetchDeliveryInvitation = async (token) => {
|
||||||
invokeDeliveryFunction("confirm-delivery-choice", {
|
if (!token) {
|
||||||
|
throw new Error("Token is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLocalClientInvitationToken(token)) {
|
||||||
|
const cachedInvitation = getCachedInvitation(token);
|
||||||
|
const invitation = cachedInvitation ?? cacheInvitation(buildFallbackInvitation(token));
|
||||||
|
warmLocalInvitationCache(token, "get-delivery-invitation");
|
||||||
|
return invitation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cachedInvitation = getCachedInvitation(token);
|
||||||
|
if (cachedInvitation) {
|
||||||
|
return cachedInvitation;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await invokeDeliveryFunction("get-delivery-invitation", { token });
|
||||||
|
if (response?.invitation) {
|
||||||
|
return cacheInvitation(response.invitation);
|
||||||
|
}
|
||||||
|
if (isLocalClientInvitationToken(token)) {
|
||||||
|
return cacheInvitation(buildFallbackInvitation(token));
|
||||||
|
}
|
||||||
|
return response?.invitation;
|
||||||
|
} catch (error) {
|
||||||
|
if (isLocalClientInvitationToken(token)) {
|
||||||
|
return cacheInvitation(buildFallbackInvitation(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const confirmDeliveryChoice = async ({ token, deliveryDate, deliveryTime }) => {
|
||||||
|
if (isLocalClientInvitationToken(token)) {
|
||||||
|
const baseInvitation = getCachedInvitation(token) ?? buildFallbackInvitation(token);
|
||||||
|
const invitation = cacheInvitation({
|
||||||
|
...baseInvitation,
|
||||||
|
deliveryDate,
|
||||||
|
deliveryTime,
|
||||||
|
state: "confirmed",
|
||||||
|
});
|
||||||
|
|
||||||
|
warmLocalInvitationCache(token, "confirm-delivery-choice");
|
||||||
|
|
||||||
|
return { ok: true, invitation };
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeDeliveryFunction("confirm-delivery-choice", {
|
||||||
token,
|
token,
|
||||||
deliveryDate,
|
deliveryDate,
|
||||||
deliveryTime,
|
deliveryTime,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const requestDeliveryLink = async ({
|
export const requestDeliveryLink = async ({
|
||||||
orderId,
|
orderId,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
buildShowcaseInvitation,
|
buildShowcaseInvitation,
|
||||||
confirmDeliveryChoice,
|
confirmDeliveryChoice,
|
||||||
fetchDeliveryInvitation,
|
fetchDeliveryInvitation,
|
||||||
|
__resetLocalDeliveryInvitationCache,
|
||||||
reportDeliveryResult,
|
reportDeliveryResult,
|
||||||
requestDeliveryLink,
|
requestDeliveryLink,
|
||||||
transferDeliveryToLogistics,
|
transferDeliveryToLogistics,
|
||||||
|
|
@ -25,6 +26,7 @@ import {
|
||||||
describe("deliveryInvitationApi", () => {
|
describe("deliveryInvitationApi", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
invoke.mockReset();
|
invoke.mockReset();
|
||||||
|
__resetLocalDeliveryInvitationCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads a delivery invitation by token", async () => {
|
it("loads a delivery invitation by token", async () => {
|
||||||
|
|
@ -59,7 +61,37 @@ describe("deliveryInvitationApi", () => {
|
||||||
state: "awaiting_choice",
|
state: "awaiting_choice",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(invoke).not.toHaveBeenCalled();
|
expect(invoke).toHaveBeenCalledWith("get-delivery-invitation", {
|
||||||
|
body: {
|
||||||
|
token: "showcase",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to a local client invitation when the edge function fails", async () => {
|
||||||
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
||||||
|
|
||||||
|
await expect(fetchDeliveryInvitation("client-flow-1001")).resolves.toMatchObject({
|
||||||
|
token: "client-flow-1001",
|
||||||
|
orderNumber: "CD-240031",
|
||||||
|
customerName: "Мария Волкова",
|
||||||
|
state: "awaiting_choice",
|
||||||
|
orderItems: [
|
||||||
|
{ name: "Кухонный гарнитур", quantity: "1 комплект" },
|
||||||
|
{ name: "Фурнитура Blum", quantity: "12 шт" },
|
||||||
|
{ name: "Монтажный комплект", quantity: "1 набор" },
|
||||||
|
],
|
||||||
|
availableSlots: expect.arrayContaining([
|
||||||
|
expect.stringMatching(/, До обеда$/),
|
||||||
|
expect.stringMatching(/, После обеда$/),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(invoke).toHaveBeenCalledWith("get-delivery-invitation", {
|
||||||
|
body: {
|
||||||
|
token: "client-flow-1001",
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds showcase slots for tomorrow and the following day", () => {
|
it("builds showcase slots for tomorrow and the following day", () => {
|
||||||
|
|
@ -112,6 +144,35 @@ describe("deliveryInvitationApi", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("updates the local client invitation when confirmation falls back to cache", async () => {
|
||||||
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
||||||
|
await fetchDeliveryInvitation("client-flow-1001");
|
||||||
|
|
||||||
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
||||||
|
await expect(
|
||||||
|
confirmDeliveryChoice({
|
||||||
|
token: "client-flow-1001",
|
||||||
|
deliveryDate: "2026-04-16",
|
||||||
|
deliveryTime: "После обеда",
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
ok: true,
|
||||||
|
invitation: {
|
||||||
|
token: "client-flow-1001",
|
||||||
|
deliveryDate: "2026-04-16",
|
||||||
|
deliveryTime: "После обеда",
|
||||||
|
state: "confirmed",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(fetchDeliveryInvitation("client-flow-1001")).resolves.toMatchObject({
|
||||||
|
token: "client-flow-1001",
|
||||||
|
deliveryDate: "2026-04-16",
|
||||||
|
deliveryTime: "После обеда",
|
||||||
|
state: "confirmed",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("creates a delivery invitation from order data", async () => {
|
it("creates a delivery invitation from order data", async () => {
|
||||||
invoke.mockResolvedValueOnce({
|
invoke.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue