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: {