@@ -4,36 +4,31 @@ import {
44 endOfDay ,
55 endOfMonth ,
66 endOfWeek ,
7- isSaturday ,
8- isSunday ,
9- previousSaturday ,
107 startOfDay ,
118 startOfMonth ,
129 startOfWeek ,
13- startOfYear ,
1410 subDays ,
15- subMonths ,
16- subWeeks ,
11+ subWeeks
1712} from "date-fns" ;
1813import parse from "parse-duration" ;
1914import { startTransition , useCallback , useEffect , useState , type ReactNode } from "react" ;
15+ import simplur from "simplur" ;
2016import { AppliedFilter } from "~/components/primitives/AppliedFilter" ;
17+ import { Callout } from "~/components/primitives/Callout" ;
2118import { DateTime } from "~/components/primitives/DateTime" ;
2219import { DateTimePicker } from "~/components/primitives/DateTimePicker" ;
2320import { Label } from "~/components/primitives/Label" ;
2421import { Paragraph } from "~/components/primitives/Paragraph" ;
2522import { RadioButtonCircle } from "~/components/primitives/RadioButton" ;
2623import { ComboboxProvider , SelectPopover , SelectProvider } from "~/components/primitives/Select" ;
24+ import { useOptionalOrganization } from "~/hooks/useOrganizations" ;
2725import { useSearchParams } from "~/hooks/useSearchParam" ;
2826import { type ShortcutDefinition } from "~/hooks/useShortcutKeys" ;
2927import { cn } from "~/utils/cn" ;
30- import { Button } from "../../primitives/Buttons" ;
28+ import { organizationBillingPath } from "~/utils/pathBuilder" ;
29+ import { Button , LinkButton } from "../../primitives/Buttons" ;
3130import { filterIcon } from "./RunFilters" ;
3231
33- export type DisplayableEnvironment = Pick < RuntimeEnvironment , "type" | "id" > & {
34- userName ?: string ;
35- } ;
36-
3732export function FilterMenuProvider ( {
3833 children,
3934 onClose,
@@ -128,6 +123,22 @@ function parsePeriodString(period: string): { value: number; unit: string } | nu
128123 return null ;
129124}
130125
126+ const MS_PER_DAY = 1000 * 60 * 60 * 24 ;
127+
128+ // Convert a period string to days using parse-duration
129+ function periodToDays ( period : string ) : number {
130+ const ms = parse ( period ) ;
131+ if ( ! ms ) return 0 ;
132+ return ms / MS_PER_DAY ;
133+ }
134+
135+ // Calculate the number of days a date range spans from now
136+ function dateRangeToDays ( from ?: Date ) : number {
137+ if ( ! from ) return 0 ;
138+ const now = new Date ( ) ;
139+ return Math . ceil ( ( now . getTime ( ) - from . getTime ( ) ) / MS_PER_DAY ) ;
140+ }
141+
131142const DEFAULT_PERIOD = "7d" ;
132143const defaultPeriodMs = parse ( DEFAULT_PERIOD ) ;
133144if ( ! defaultPeriodMs ) {
@@ -292,6 +303,8 @@ export interface TimeFilterProps {
292303 applyShortcut ?: ShortcutDefinition | undefined ;
293304 /** Callback when the user applies a time filter selection, receives the applied values */
294305 onValueChange ?: ( values : TimeFilterApplyValues ) => void ;
306+ /** When set an upgrade message will be shown if you select a period further back than this number of days */
307+ maxPeriodDays ?: number ;
295308}
296309
297310export function TimeFilter ( {
@@ -303,6 +316,7 @@ export function TimeFilter({
303316 hideLabel = false ,
304317 applyShortcut,
305318 onValueChange,
319+ maxPeriodDays,
306320} : TimeFilterProps = { } ) {
307321 const { value } = useSearchParams ( ) ;
308322 const periodValue = period ?? value ( "period" ) ;
@@ -339,6 +353,7 @@ export function TimeFilter({
339353 labelName = { labelName }
340354 applyShortcut = { applyShortcut }
341355 onValueChange = { onValueChange }
356+ maxPeriodDays = { maxPeriodDays }
342357 />
343358 ) }
344359 </ FilterMenuProvider >
@@ -368,7 +383,7 @@ export function TimeDropdown({
368383 applyShortcut,
369384 onApply,
370385 onValueChange,
371- maxPeriodDays
386+ maxPeriodDays,
372387} : {
373388 trigger : ReactNode ;
374389 period ?: string ;
@@ -383,6 +398,7 @@ export function TimeDropdown({
383398 /** When set an upgrade message will be shown if you select a period further back than this number of days */
384399 maxPeriodDays ?: number ;
385400} ) {
401+ const organization = useOptionalOrganization ( ) ;
386402 const [ open , setOpen ] = useState < boolean | undefined > ( ) ;
387403 const { replace } = useSearchParams ( ) ;
388404 const [ fromValue , setFromValue ] = useState ( from ) ;
@@ -422,9 +438,28 @@ export function TimeDropdown({
422438 return ! isNaN ( value ) && value > 0 ;
423439 } ) ( ) ;
424440
441+ // Calculate if the current selection exceeds maxPeriodDays
442+ const exceedsMaxPeriod = ( ( ) => {
443+ if ( ! maxPeriodDays ) return false ;
444+
445+ if ( activeSection === "duration" ) {
446+ const periodToCheck = selectedPeriod === "custom" ? `${ customValue } ${ customUnit } ` : selectedPeriod ;
447+ if ( ! periodToCheck ) return false ;
448+ return periodToDays ( periodToCheck ) > maxPeriodDays ;
449+ } else {
450+ // For date range, check if fromValue is further back than maxPeriodDays
451+ return dateRangeToDays ( fromValue ) > maxPeriodDays ;
452+ }
453+ } ) ( ) ;
454+
425455 const applySelection = useCallback ( ( ) => {
426456 setValidationError ( null ) ;
427457
458+ if ( exceedsMaxPeriod ) {
459+ setValidationError ( `Your plan allows a maximum of ${ maxPeriodDays } days. Upgrade for longer retention.` ) ;
460+ return ;
461+ }
462+
428463 if ( activeSection === "duration" ) {
429464 // Validate custom duration
430465 if ( selectedPeriod === "custom" && ! isCustomDurationValid ) {
@@ -759,6 +794,17 @@ export function TimeDropdown({
759794 </ div >
760795 </ div >
761796
797+ { /* Upgrade callout when exceeding maxPeriodDays */ }
798+ { exceedsMaxPeriod && organization && (
799+ < Callout
800+ variant = "pricing"
801+ cta = { < LinkButton variant = "primary/small" to = { organizationBillingPath ( { slug : organization . slug } ) } > Upgrade</ LinkButton > }
802+ className = "items-center"
803+ >
804+ { simplur `Your plan allows a maximum of ${ maxPeriodDays } day[|s].` }
805+ </ Callout >
806+ ) }
807+
762808 { /* Action buttons */ }
763809 < div className = "flex justify-between gap-1 border-t border-grid-bright px-0 pt-3" >
764810 < Button
@@ -786,6 +832,7 @@ export function TimeDropdown({
786832 applySelection ( ) ;
787833 } }
788834 type = "button"
835+ disabled = { exceedsMaxPeriod }
789836 >
790837 Apply
791838 </ Button >
0 commit comments