131 lines
4.0 KiB
TypeScript
131 lines
4.0 KiB
TypeScript
|
|
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<TriggerEventsLimitModalPayload> | null
|
||
|
|
setShowTriggerEventsLimitModal: Dispatch<SetStateAction<ModalState<TriggerEventsLimitModalPayload> | null>>
|
||
|
|
persistTriggerEventsLimitModalDismiss: () => void
|
||
|
|
}
|
||
|
|
|
||
|
|
const TRIGGER_EVENTS_LOCALSTORAGE_PREFIX = 'trigger-events-limit-dismissed'
|
||
|
|
|
||
|
|
export const useTriggerEventsLimitModal = ({
|
||
|
|
plan,
|
||
|
|
isFetchedPlan,
|
||
|
|
currentWorkspaceId,
|
||
|
|
}: UseTriggerEventsLimitModalOptions): UseTriggerEventsLimitModalResult => {
|
||
|
|
const [showTriggerEventsLimitModal, setShowTriggerEventsLimitModal] = useState<ModalState<TriggerEventsLimitModalPayload> | null>(null)
|
||
|
|
const dismissedTriggerEventsLimitStorageKeysRef = useRef<Record<string, boolean>>({})
|
||
|
|
|
||
|
|
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,
|
||
|
|
}
|
||
|
|
}
|