import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from 'react' import dayjs from 'dayjs' import { NUM_INFINITE } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' import { IS_CLOUD_EDITION } from '@/config' import type { ModalState } from '../modal-context' export type TriggerEventsLimitModalPayload = { usage: number total: number resetInDays?: number planType: Plan storageKey?: string persistDismiss?: boolean } type TriggerPlanInfo = { type: Plan usage: { triggerEvents: number } total: { triggerEvents: number } reset: { triggerEvents?: number | null } } type UseTriggerEventsLimitModalOptions = { plan: TriggerPlanInfo isFetchedPlan: boolean currentWorkspaceId?: string } type UseTriggerEventsLimitModalResult = { showTriggerEventsLimitModal: ModalState | null setShowTriggerEventsLimitModal: Dispatch | null>> persistTriggerEventsLimitModalDismiss: () => void } const TRIGGER_EVENTS_LOCALSTORAGE_PREFIX = 'trigger-events-limit-dismissed' export const useTriggerEventsLimitModal = ({ plan, isFetchedPlan, currentWorkspaceId, }: UseTriggerEventsLimitModalOptions): UseTriggerEventsLimitModalResult => { const [showTriggerEventsLimitModal, setShowTriggerEventsLimitModal] = useState | null>(null) const dismissedTriggerEventsLimitStorageKeysRef = useRef>({}) useEffect(() => { if (!IS_CLOUD_EDITION) return if (typeof window === 'undefined') return if (!currentWorkspaceId) return if (!isFetchedPlan) { setShowTriggerEventsLimitModal(null) return } const { type, usage, total, reset } = plan const isUnlimited = total.triggerEvents === NUM_INFINITE const reachedLimit = total.triggerEvents > 0 && usage.triggerEvents >= total.triggerEvents if (type === Plan.team || isUnlimited || !reachedLimit) { if (showTriggerEventsLimitModal) setShowTriggerEventsLimitModal(null) return } const triggerResetInDays = type === Plan.professional && total.triggerEvents !== NUM_INFINITE ? reset.triggerEvents ?? undefined : undefined const cycleTag = (() => { if (typeof reset.triggerEvents === 'number') return dayjs().startOf('day').add(reset.triggerEvents, 'day').format('YYYY-MM-DD') if (type === Plan.sandbox) return dayjs().endOf('month').format('YYYY-MM-DD') return 'none' })() const storageKey = `${TRIGGER_EVENTS_LOCALSTORAGE_PREFIX}-${currentWorkspaceId}-${type}-${total.triggerEvents}-${cycleTag}` if (dismissedTriggerEventsLimitStorageKeysRef.current[storageKey]) return let persistDismiss = true let hasDismissed = false try { if (localStorage.getItem(storageKey) === '1') hasDismissed = true } catch { persistDismiss = false } if (hasDismissed) return if (showTriggerEventsLimitModal?.payload.storageKey === storageKey) return setShowTriggerEventsLimitModal({ payload: { usage: usage.triggerEvents, total: total.triggerEvents, planType: type, resetInDays: triggerResetInDays, storageKey, persistDismiss, }, }) }, [plan, isFetchedPlan, showTriggerEventsLimitModal, currentWorkspaceId]) const persistTriggerEventsLimitModalDismiss = useCallback(() => { const storageKey = showTriggerEventsLimitModal?.payload.storageKey if (!storageKey) return if (showTriggerEventsLimitModal?.payload.persistDismiss) { try { localStorage.setItem(storageKey, '1') return } catch { // ignore error and fall back to in-memory guard } } dismissedTriggerEventsLimitStorageKeysRef.current[storageKey] = true }, [showTriggerEventsLimitModal]) return { showTriggerEventsLimitModal, setShowTriggerEventsLimitModal, persistTriggerEventsLimitModalDismiss, } }