diff --git a/apps/web/modules/onboarding/store/onboarding-store.ts b/apps/web/modules/onboarding/store/onboarding-store.ts index c6e045cca4..a45ff0f618 100644 --- a/apps/web/modules/onboarding/store/onboarding-store.ts +++ b/apps/web/modules/onboarding/store/onboarding-store.ts @@ -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()( { 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()( teamId: state.teamId, personalDetails: state.personalDetails, }), - } + } as PersistOptions> ) );