CustomExpiryModal.tsx164 lines · main
1import { zodResolver } from '@hookform/resolvers/zod'
2import dayjs from 'dayjs'
3import { SubmitHandler, useForm, useWatch } from 'react-hook-form'
4import {
5 Button,
6 Form,
7 FormControl,
8 FormField,
9 Input,
10 Modal,
11 Select,
12 SelectContent,
13 SelectItem,
14 SelectTrigger,
15 SelectValue,
16} from 'ui'
17import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
18import * as z from 'zod'
19
20import { useCopyUrl } from './useCopyUrl'
21import { DATETIME_FORMAT } from '@/lib/constants'
22import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer'
23
24const unitMap = {
25 days: 3600 * 24,
26 weeks: 3600 * 24 * 7,
27 months: 3600 * 24 * 30,
28 years: 3600 * 24 * 365,
29} as const
30
31const formSchema = z.object({
32 expiresIn: z.preprocess(
33 (val) => (val ? val : undefined),
34 z.coerce
35 .number({ required_error: 'Required', invalid_type_error: 'Required' })
36 .positive('Expiry duration must be greater than 0')
37 ),
38 units: z.enum(['days', 'weeks', 'months', 'years']),
39})
40
41type FormSchema = z.infer<typeof formSchema>
42
43const formId = 'storage-custom-expiry-form'
44
45export const CustomExpiryModal = () => {
46 const { onCopyUrl } = useCopyUrl()
47 const snap = useStorageExplorerStateSnapshot()
48 const { selectedFileCustomExpiry, setSelectedFileCustomExpiry } = snap
49
50 const visible = selectedFileCustomExpiry !== undefined
51 const onClose = () => setSelectedFileCustomExpiry(undefined)
52
53 const form = useForm<FormSchema>({
54 resolver: zodResolver(formSchema as any),
55 defaultValues: { expiresIn: 0, units: 'days' },
56 })
57
58 const handleClose = () => {
59 onClose()
60 form.reset()
61 }
62
63 const { isDirty, isSubmitting, isValid } = form.formState
64 const handleSubmit: SubmitHandler<FormSchema> = async (values) => {
65 await onCopyUrl(selectedFileCustomExpiry!.path!, values.expiresIn * unitMap[values.units])
66 handleClose()
67 }
68
69 const [expiresIn, units] = useWatch({
70 name: ['expiresIn', 'units'],
71 control: form.control,
72 })
73
74 return (
75 <Modal
76 hideFooter
77 size="small"
78 header="Custom expiry for signed URL"
79 visible={visible}
80 alignFooter="right"
81 confirmText="Get URL"
82 onCancel={handleClose}
83 >
84 <Form {...form}>
85 <Modal.Content>
86 <p className="text-sm text-foreground-light mb-4">
87 Enter the duration for which the URL will be valid for:
88 </p>
89 <form
90 id={formId}
91 onSubmit={form.handleSubmit(handleSubmit)}
92 noValidate
93 className="flex items-start space-x-2"
94 >
95 <div className="grow">
96 <FormField
97 control={form.control}
98 name="expiresIn"
99 render={({ field }) => (
100 <FormItemLayout layout="vertical" label="Duration">
101 <FormControl>
102 <Input
103 {...field}
104 type="number"
105 onChange={(e) => {
106 field.onChange(
107 isNaN(e.target.valueAsNumber) ? '' : e.target.valueAsNumber
108 )
109 }}
110 />
111 </FormControl>
112 </FormItemLayout>
113 )}
114 />
115 </div>
116 <div>
117 <FormField
118 control={form.control}
119 name="units"
120 render={({ field }) => (
121 <FormItemLayout layout="vertical" label="Units">
122 <FormControl>
123 <Select value={field.value} onValueChange={field.onChange}>
124 <SelectTrigger>
125 <SelectValue aria-label="Units" placeholder="Select an option" />
126 </SelectTrigger>
127 <SelectContent>
128 <SelectItem value="days">days</SelectItem>
129 <SelectItem value="weeks">weeks</SelectItem>
130 <SelectItem value="months">months</SelectItem>
131 <SelectItem value="years">years</SelectItem>
132 </SelectContent>
133 </Select>
134 </FormControl>
135 </FormItemLayout>
136 )}
137 />
138 </div>
139 </form>
140 {isDirty && isValid && (
141 <p className="text-sm text-foreground-light mt-2">
142 URL will expire on {dayjs().add(expiresIn, units).format(DATETIME_FORMAT)}
143 </p>
144 )}
145 </Modal.Content>
146 <Modal.Separator />
147 <Modal.Content className="flex items-center justify-end space-x-2">
148 <Button type="default" onClick={handleClose}>
149 Cancel
150 </Button>
151 <Button
152 form={formId}
153 disabled={!isDirty || isSubmitting}
154 loading={isSubmitting}
155 htmlType="submit"
156 type="primary"
157 >
158 Get signed URL
159 </Button>
160 </Modal.Content>
161 </Form>
162 </Modal>
163 )
164}