fix: clear oversized images from onboarding store during migration (#27560)

* fix: clear oversized images from onboarding store during migration

This fixes the 429 errors caused by large images stored in IndexedDB
before PR #27285 added cropping functionality.

Changes:
- Add version 1 to zustand persist config with migration function
- Migration clears images exceeding thresholds:
  - Logos/avatars: 500KB (properly cropped are ~50-150KB)
  - Banners: 1.5MB (properly cropped are ~200-500KB)
- Add comprehensive tests for migration logic

When users refresh the page, the migration runs automatically and
clears any oversized images, requiring them to re-upload using the
new cropping flow.

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fixes

* chore: remove migration test file

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Hariom Balhara
2026-02-03 10:48:35 +00:00
committed by GitHub
co-authored by hariom@cal.com <hariombalhara@gmail.com> hariom@cal.com <hariombalhara@gmail.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 7840cc3a9d
commit 19e89dfcd5
@@ -1,8 +1,23 @@
import type { PersistOptions } from "zustand/middleware";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { onboardingIndexedDBStorage } from "./onboarding-storage";
// Thresholds for detecting oversized images (in characters of base64 string)
// Images larger than these were uploaded before the cropping fix and should be cleared
const LOGO_SIZE_THRESHOLD = 500_000; // ~500KB - properly cropped logos are ~50-150KB
const BANNER_SIZE_THRESHOLD = 1_500_000; // ~1.5MB - properly cropped banners are ~200-500KB
/**
* Checks if a base64 image string exceeds the given threshold.
* Returns true if the image should be cleared (is oversized).
*/
function isOversizedImage(imageData: string | null, threshold: number): boolean {
if (!imageData) return false;
return imageData.length > threshold;
}
export type PlanType = "personal" | "team" | "organization";
export type InviteRole = "MEMBER" | "ADMIN";
@@ -189,7 +204,42 @@ export const useOnboardingStore = create<OnboardingState>()(
{
name: "cal-onboarding-storage", // Storage key
storage: onboardingIndexedDBStorage, // Use IndexedDB instead of localStorage for larger capacity
// Optional: Only persist certain fields
// Version 0: Had issue with Image sizes being too large.
// Version 1: We fixed that and also migrated to version-1 to facilitate one-time cleanup of all oversized images.
version: 1,
migrate: (persistedState, version) => {
if (version >= 1) {
return persistedState as OnboardingState;
}
const state = persistedState as OnboardingState;
// Migration from version 0 (no version) to version 1:
// Clear oversized images that were uploaded before the cropping fix (PR #27285)
// These large images cause 429 errors when sent to the server
if (state.organizationBrand) {
if (isOversizedImage(state.organizationBrand.logo, LOGO_SIZE_THRESHOLD)) {
state.organizationBrand.logo = null;
}
if (isOversizedImage(state.organizationBrand.banner, BANNER_SIZE_THRESHOLD)) {
state.organizationBrand.banner = null;
}
}
if (state.teamBrand) {
if (isOversizedImage(state.teamBrand.logo, LOGO_SIZE_THRESHOLD)) {
state.teamBrand.logo = null;
}
}
if (state.personalDetails) {
if (isOversizedImage(state.personalDetails.avatar, LOGO_SIZE_THRESHOLD)) {
state.personalDetails.avatar = null;
}
}
return state;
},
// Only persist certain fields
partialize: (state) => ({
selectedPlan: state.selectedPlan,
organizationDetails: state.organizationDetails,
@@ -204,6 +254,6 @@ export const useOnboardingStore = create<OnboardingState>()(
teamId: state.teamId,
personalDetails: state.personalDetails,
}),
}
} as PersistOptions<OnboardingState, Partial<OnboardingState>>
)
);