From b60d18d365a25531f2f3006b1a69b0e5bdb83d11 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Sun, 28 Dec 2025 22:14:10 +0000 Subject: [PATCH] feat(drive-web): item share expiry config ui --- apps/drive-web/package.json | 4 + apps/drive-web/src/components/date-input.tsx | 204 ++++++++++ apps/drive-web/src/components/ui/calendar.tsx | 232 +++++++++++ apps/drive-web/src/components/ui/kbd.tsx | 28 ++ apps/drive-web/src/components/ui/popover.tsx | 40 ++ apps/drive-web/src/components/ui/select.tsx | 188 +++++++++ apps/drive-web/src/lib/date.ts | 19 + apps/drive-web/src/sharing/api.ts | 25 +- .../src/sharing/item-share-dialog.tsx | 372 ++++++++++++++++-- bun.lock | 22 ++ 10 files changed, 1099 insertions(+), 35 deletions(-) create mode 100644 apps/drive-web/src/components/date-input.tsx create mode 100644 apps/drive-web/src/components/ui/calendar.tsx create mode 100644 apps/drive-web/src/components/ui/kbd.tsx create mode 100644 apps/drive-web/src/components/ui/popover.tsx create mode 100644 apps/drive-web/src/components/ui/select.tsx create mode 100644 apps/drive-web/src/lib/date.ts diff --git a/apps/drive-web/package.json b/apps/drive-web/package.json index 9461630..dc0e971 100644 --- a/apps/drive-web/package.json +++ b/apps/drive-web/package.json @@ -19,7 +19,9 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tooltip": "^1.2.8", @@ -34,6 +36,7 @@ "clsx": "^2.1.1", "convex": "^1.27.0", "convex-helpers": "^0.1.104", + "date-fns": "^4.1.0", "jotai": "^2.14.0", "jotai-effect": "^2.1.3", "jotai-scope": "^0.9.5", @@ -43,6 +46,7 @@ "nanoid": "^5.1.6", "next-themes": "^0.4.6", "react": "^19", + "react-day-picker": "^9.13.0", "react-dom": "^19", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", diff --git a/apps/drive-web/src/components/date-input.tsx b/apps/drive-web/src/components/date-input.tsx new file mode 100644 index 0000000..df7393f --- /dev/null +++ b/apps/drive-web/src/components/date-input.tsx @@ -0,0 +1,204 @@ +import { CalendarIcon } from "lucide-react" +import * as React from "react" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Input } from "@/components/ui/input" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { formatDate, isValidDate } from "@/lib/date" +import { cn } from "@/lib/utils" + +export type DateInputHandle = { + readonly date: Date | undefined +} + +type DateInputProps = { + ref?: React.Ref + value?: Date + defaultValue?: Date + onDateChange?: (date: Date | undefined) => void + placeholder?: string + inputId?: string + disabled?: boolean + startMonth?: Date + endMonth?: Date + className?: string + inputClassName?: string + buttonClassName?: string +} + +export function DateInput(props: DateInputProps) { + const { + ref: imperativeRef, + value, + defaultValue, + onDateChange, + placeholder = "June 01, 2025", + inputId, + disabled = false, + startMonth: startMonthProp, + endMonth: endMonthProp, + className, + inputClassName, + buttonClassName, + } = props + const isControlled = Object.hasOwn(props, "value") + const [open, setOpen] = React.useState(false) + const [internalDate, setInternalDate] = React.useState( + isControlled ? value : defaultValue, + ) + const selectedDate = isControlled ? value : internalDate + const [month, setMonth] = React.useState(selectedDate) + const [inputValue, setInputValue] = React.useState(formatDate(selectedDate)) + const baseYear = (selectedDate ?? new Date()).getFullYear() + const startMonth = + startMonthProp ?? new Date(baseYear - 100, 0, 1) + const endMonth = + endMonthProp ?? new Date(baseYear + 100, 11, 1) + + React.useEffect(() => { + setInputValue(formatDate(selectedDate)) + setMonth(selectedDate) + }, [selectedDate]) + + React.useImperativeHandle( + imperativeRef, + () => ({ + get date() { + return selectedDate + }, + }), + [selectedDate], + ) + + const commitDate = React.useCallback( + (nextDate: Date | undefined) => { + if (!isControlled) { + setInternalDate(nextDate) + } + + onDateChange?.(nextDate) + }, + [isControlled, onDateChange], + ) + + return ( +
+ { + const nextValue = event.target.value + setInputValue(nextValue) + + if (!nextValue.trim()) { + commitDate(undefined) + setMonth(undefined) + } + }} + onBlur={() => { + const nextDate = new Date(inputValue) + + if (isValidDate(nextDate)) { + commitDate(nextDate) + setMonth(nextDate) + setInputValue(formatDate(nextDate)) + } + }} + onKeyDown={(event) => { + if (event.key === "ArrowDown") { + event.preventDefault() + if (!disabled) { + setOpen(true) + } + } + + if (event.key === "Enter") { + const nextDate = new Date(inputValue) + + if (isValidDate(nextDate)) { + commitDate(nextDate) + setMonth(nextDate) + setInputValue(formatDate(nextDate)) + } + } + }} + /> + { + if (disabled) { + return + } + + setOpen(nextOpen) + }} + > + + + + + { + commitDate(nextDate) + setInputValue(formatDate(nextDate)) + if (nextDate) { + setMonth(nextDate) + } + setOpen(false) + }} + /> +
+ +
+
+
+
+ ) +} diff --git a/apps/drive-web/src/components/ui/calendar.tsx b/apps/drive-web/src/components/ui/calendar.tsx new file mode 100644 index 0000000..d259813 --- /dev/null +++ b/apps/drive-web/src/components/ui/calendar.tsx @@ -0,0 +1,232 @@ +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "lucide-react" +import * as React from "react" +import { + type DayButton, + DayPicker, + getDefaultClassNames, +} from "react-day-picker" +import { Button, buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "ghost", + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"] +}) { + const defaultClassNames = getDefaultClassNames() + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className, + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit", defaultClassNames.root), + months: cn( + "flex gap-4 flex-col md:flex-row relative", + defaultClassNames.months, + ), + month: cn( + "flex flex-col w-full gap-4", + defaultClassNames.month, + ), + nav: cn( + "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + defaultClassNames.nav, + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_previous, + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_next, + ), + month_caption: cn( + "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + defaultClassNames.month_caption, + ), + dropdowns: cn( + "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + defaultClassNames.dropdowns, + ), + dropdown_root: cn( + "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", + defaultClassNames.dropdown_root, + ), + dropdown: cn( + "absolute bg-popover inset-0 opacity-0", + defaultClassNames.dropdown, + ), + caption_label: cn( + "select-none font-medium", + captionLayout === "label" + ? "text-sm" + : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", + defaultClassNames.caption_label, + ), + table: "w-full border-collapse", + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + defaultClassNames.weekday, + ), + week: cn("flex w-full mt-2", defaultClassNames.week), + week_number_header: cn( + "select-none w-(--cell-size)", + defaultClassNames.week_number_header, + ), + week_number: cn( + "text-[0.8rem] select-none text-muted-foreground", + defaultClassNames.week_number, + ), + day: cn( + "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", + props.showWeekNumber + ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md" + : "[&:first-child[data-selected=true]_button]:rounded-l-md", + defaultClassNames.day, + ), + range_start: cn( + "rounded-l-md bg-accent", + defaultClassNames.range_start, + ), + range_middle: cn( + "rounded-none", + defaultClassNames.range_middle, + ), + range_end: cn( + "rounded-r-md bg-accent", + defaultClassNames.range_end, + ), + today: cn( + "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today, + ), + outside: cn( + "text-muted-foreground aria-selected:text-muted-foreground", + defaultClassNames.outside, + ), + disabled: cn( + "text-muted-foreground opacity-50", + defaultClassNames.disabled, + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ) + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ) + } + + if (orientation === "right") { + return ( + + ) + } + + return ( + + ) + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ) + }, + ...components, + }} + {...props} + /> + ) +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames() + + const ref = React.useRef(null) + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus() + }, [modifiers.focused]) + + return ( +
@@ -225,27 +259,284 @@ function ShareLinkListItem({ share }: { share: Share }) { Copy share link - - - - - - + ) } -function ShareLinkOptionsMenu({ share }: { share: Share }) { +const ACTIVE_POPOVER_KIND = { + rename: "rename", + setExpiration: "setExpiration", +} as const +type ActivePopoverKind = + (typeof ACTIVE_POPOVER_KIND)[keyof typeof ACTIVE_POPOVER_KIND] + +function ShareLinkOptionsMenuButton({ share }: { share: Share }) { + const activePopoverAtom = useMemo( + () => atom(null), + [], + ) + + const activePopoverKind = useAtomValue(activePopoverAtom) + + const button = ( + + ) + + switch (activePopoverKind) { + case "rename": + return ( + + {button} + + ) + + case "setExpiration": + return ( + + {button} + + ) + + default: + return ( + + {button} + + ) + } +} + +function RenameShareLinkPopover({ + share, + children, + activePopoverAtom, +}: React.PropsWithChildren<{ + share: Share + activePopoverAtom: PrimitiveAtom +}>) { + const setActivePopover = useSetAtom(activePopoverAtom) + const inputId = `rename-share-link-${share.id}` + + return ( + { + if (!nextOpen) { + setActivePopover(null) + } + }} + > + {children} + +
+

+ Set a label for this link +

+

+ Only you can see the label. +

+
+ +
+ + +
+
+
+ ) +} + +function ConfigureShareLinkExpirationPopover({ + share, + children, + activePopoverAtom, +}: React.PropsWithChildren<{ + share: Share + activePopoverAtom: PrimitiveAtom +}>) { + const EXPIRATION_TYPE = { + date: "date", + never: "never", + } as const + type ExpirationType = (typeof EXPIRATION_TYPE)[keyof typeof EXPIRATION_TYPE] + + const [expirationType, setExpirationType] = useState( + share.expiresAt === null ? EXPIRATION_TYPE.never : EXPIRATION_TYPE.date, + ) const { item } = useContext(ItemShareDialogContext) const store = useStore() + const setActivePopover = useSetAtom(activePopoverAtom) + const dateInputRef = useRef(null) + + const { mutate: updateShare, isPending: isUpdatingShare } = useMutation({ + ...useAtomValue(updateShareMutationAtom), + onSuccess: (_updatedShare, _vars, _, { client }) => { + let queryKey: readonly unknown[] | null + switch (item.kind) { + case "file": + queryKey = store.get(fileSharesQueryAtom(item.id)).queryKey + break + case "directory": + queryKey = store.get( + directorySharesQueryAtom(item.id), + ).queryKey + break + default: + queryKey = null + break + } + if (queryKey) { + client.invalidateQueries({ + queryKey, + }) + } + setActivePopover(null) + }, + }) + + const updateExpirationDate = () => { + switch (expirationType) { + case EXPIRATION_TYPE.date: + if (dateInputRef.current?.date) { + updateShare({ + shareId: share.id, + expiresAt: dateInputRef.current.date, + }) + } + break + case EXPIRATION_TYPE.never: + updateShare({ + shareId: share.id, + expiresAt: null, + }) + break + } + } + + return ( + { + if (!nextOpen) { + setActivePopover(null) + } + }} + > + {children} + +
+

+ Configure expiration +

+

+ The share link will be accessible until the selected + date. +

+
+ + {expirationType === EXPIRATION_TYPE.date ? ( + + ) : null} +
+ + +
+
+
+ ) +} + +function ShareLinkOptionsMenu({ + share, + children, + activePopoverAtom, +}: React.PropsWithChildren<{ + share: Share + activePopoverAtom: PrimitiveAtom +}>) { + const { item } = useContext(ItemShareDialogContext) + const setActivePopover = useSetAtom(activePopoverAtom) + const store = useStore() const { mutate: deleteShare } = useMutation({ ...useAtomValue(deleteShareMutationAtom), @@ -293,20 +584,35 @@ function ShareLinkOptionsMenu({ share }: { share: Share }) { }) return ( - - - Rename link - Set expiration - { - deleteShare({ shareId: share.id }) - }} - > - Delete link - - - + + {children} + + + { + setActivePopover(ACTIVE_POPOVER_KIND.rename) + }} + > + Rename link + + { + setActivePopover(ACTIVE_POPOVER_KIND.setExpiration) + }} + > + Configure expiration + + { + deleteShare({ shareId: share.id }) + }} + > + Delete link + + + + ) } diff --git a/bun.lock b/bun.lock index 72b272e..4a03e5f 100644 --- a/bun.lock +++ b/bun.lock @@ -42,7 +42,9 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tooltip": "^1.2.8", @@ -57,6 +59,7 @@ "clsx": "^2.1.1", "convex": "^1.27.0", "convex-helpers": "^0.1.104", + "date-fns": "^4.1.0", "jotai": "^2.14.0", "jotai-effect": "^2.1.3", "jotai-scope": "^0.9.5", @@ -66,6 +69,7 @@ "nanoid": "^5.1.6", "next-themes": "^0.4.6", "react": "^19", + "react-day-picker": "^9.13.0", "react-dom": "^19", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", @@ -222,6 +226,8 @@ "@convex-dev/better-auth": ["@convex-dev/better-auth@0.8.9", "", { "dependencies": { "@better-fetch/fetch": "^1.1.18", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "is-network-error": "^1.1.0", "type-fest": "^4.39.1", "zod": "^3.24.4" }, "peerDependencies": { "better-auth": "1.3.8", "convex": "^1.26.2", "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-t6x2lYsgv0sGL14xmIsTqxVltkS//mWtIjb+Wm39rWUCgeqmCTqsyhQnmTDQNwaqZS0sH0WfTObNHu3xbCSx1w=="], + "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], + "@drexa/auth": ["@drexa/auth@workspace:packages/auth"], "@drexa/cli": ["@drexa/cli@workspace:apps/cli"], @@ -336,6 +342,8 @@ "@peculiar/x509": ["@peculiar/x509@1.14.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.5.0", "@peculiar/asn1-csr": "^2.5.0", "@peculiar/asn1-ecc": "^2.5.0", "@peculiar/asn1-pkcs9": "^2.5.0", "@peculiar/asn1-rsa": "^2.5.0", "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], @@ -368,6 +376,8 @@ "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], @@ -380,6 +390,8 @@ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], @@ -612,6 +624,10 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], + + "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], @@ -760,6 +776,8 @@ "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + "react-day-picker": ["react-day-picker@9.13.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ=="], + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], @@ -874,8 +892,12 @@ "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],