fix: persist saved client delivery choice
This commit is contained in:
parent
de0cf49490
commit
5142ff30db
|
|
@ -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 = () => {
|
|||
</p>
|
||||
</Panel>
|
||||
|
||||
{isChoiceSaved && savedChoiceLabel ? (
|
||||
<Panel className="space-y-2 p-5 sm:p-6">
|
||||
<p className="text-sm uppercase tracking-[0.24em] text-[var(--color-text-muted)]">Ваш выбор</p>
|
||||
<h2 className="text-xl font-semibold leading-tight">Сохранено: {savedChoiceLabel}</h2>
|
||||
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
|
||||
При повторном открытии этой ссылки будет показан тот же выбор.
|
||||
</p>
|
||||
</Panel>
|
||||
) : null}
|
||||
|
||||
{isActiveState && !isChoiceSaved && slots.length ? (
|
||||
<DeliverySlotsPicker
|
||||
slots={slots}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { supabase, hasSupabaseConfig } from "../supabaseClient";
|
|||
const SHOWCASE_TOKEN = "showcase";
|
||||
const LOCAL_CLIENT_FLOW_TOKEN_PREFIX = "client-flow-";
|
||||
const LOCAL_CLIENT_FLOW_TOKEN = "client-flow-1001";
|
||||
const LOCAL_INVITATION_STORAGE_PREFIX = "delivery-invitation:";
|
||||
|
||||
const localDeliveryInvitationCache = new Map();
|
||||
|
||||
|
|
@ -16,6 +17,43 @@ const addDays = (date, days) => {
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue