diff --git a/src/pages/ClientDeliveryPage.jsx b/src/pages/ClientDeliveryPage.jsx index 32fcb22..f7f42aa 100644 --- a/src/pages/ClientDeliveryPage.jsx +++ b/src/pages/ClientDeliveryPage.jsx @@ -143,6 +143,9 @@ export const ClientDeliveryPage = () => { const effectiveSelectedSlot = selectedSlot || invitationSelectedSlot; const isChoiceSaved = choiceSaved || (!isActiveState && Boolean(invitationSelectedSlot)); + const savedChoiceLabel = effectiveSelectedSlot + ? `${formatDeliveryDate(effectiveSelectedSlot.date)} / ${effectiveSelectedSlot.time}` + : ""; const handleSaveChoice = React.useCallback( async () => { @@ -236,6 +239,16 @@ export const ClientDeliveryPage = () => {

+ {isChoiceSaved && savedChoiceLabel ? ( + +

Ваш выбор

+

Сохранено: {savedChoiceLabel}

+

+ При повторном открытии этой ссылки будет показан тот же выбор. +

+
+ ) : null} + {isActiveState && !isChoiceSaved && slots.length ? ( { const cloneInvitation = (invitation) => JSON.parse(JSON.stringify(invitation)); +const getLocalStorage = () => { + try { + return globalThis.localStorage || null; + } catch (error) { + return null; + } +}; + +const getLocalStorageKey = (token) => `${LOCAL_INVITATION_STORAGE_PREFIX}${token}`; + +const readStoredInvitation = (token) => { + const storage = getLocalStorage(); + if (!storage) { + return null; + } + + try { + const raw = storage.getItem(getLocalStorageKey(token)); + return raw ? JSON.parse(raw) : null; + } catch (error) { + return null; + } +}; + +const writeStoredInvitation = (invitation) => { + const storage = getLocalStorage(); + if (!storage?.setItem) { + return; + } + + try { + storage.setItem(getLocalStorageKey(invitation.token), JSON.stringify(invitation)); + } catch (error) { + // Ignore storage quota and privacy mode failures. + } +}; + const isLocalClientInvitationToken = (token) => token === SHOWCASE_TOKEN || token?.startsWith(LOCAL_CLIENT_FLOW_TOKEN_PREFIX); @@ -52,8 +90,14 @@ const getCachedInvitation = (token) => { }; const cacheInvitation = (invitation) => { - localDeliveryInvitationCache.set(invitation.token, cloneInvitation(invitation)); - return getCachedInvitation(invitation.token); + const clonedInvitation = cloneInvitation(invitation); + localDeliveryInvitationCache.set(clonedInvitation.token, clonedInvitation); + + if (isLocalClientInvitationToken(clonedInvitation.token)) { + writeStoredInvitation(clonedInvitation); + } + + return getCachedInvitation(clonedInvitation.token); }; const buildFallbackInvitation = (token) => @@ -125,7 +169,10 @@ export const fetchDeliveryInvitation = async (token) => { if (isLocalClientInvitationToken(token)) { const cachedInvitation = getCachedInvitation(token); - const invitation = cachedInvitation ?? cacheInvitation(buildFallbackInvitation(token)); + const storedInvitation = cachedInvitation ?? readStoredInvitation(token); + const invitation = storedInvitation + ? cacheInvitation(storedInvitation) + : cacheInvitation(buildFallbackInvitation(token)); warmLocalInvitationCache(token, "get-delivery-invitation"); return invitation; } diff --git a/src/services/deliveryInvitationApi.test.js b/src/services/deliveryInvitationApi.test.js index 644057a..12f7fef 100644 --- a/src/services/deliveryInvitationApi.test.js +++ b/src/services/deliveryInvitationApi.test.js @@ -27,6 +27,19 @@ describe("deliveryInvitationApi", () => { beforeEach(() => { invoke.mockReset(); __resetLocalDeliveryInvitationCache(); + const storage = new Map(); + vi.stubGlobal("localStorage", { + getItem: (key) => storage.get(key) ?? null, + setItem: (key, value) => { + storage.set(key, String(value)); + }, + removeItem: (key) => { + storage.delete(key); + }, + clear: () => { + storage.clear(); + }, + }); }); it("loads a delivery invitation by token", async () => { @@ -173,6 +186,27 @@ describe("deliveryInvitationApi", () => { }); }); + it("restores the saved local client invitation after cache reset", async () => { + invoke.mockRejectedValueOnce(new Error("worker boot error")); + await fetchDeliveryInvitation("client-flow-1001"); + + invoke.mockRejectedValueOnce(new Error("worker boot error")); + await confirmDeliveryChoice({ + token: "client-flow-1001", + deliveryDate: "2026-04-16", + deliveryTime: "После обеда", + }); + + __resetLocalDeliveryInvitationCache(); + + 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 () => { invoke.mockResolvedValueOnce({ data: {