CustomExpiryModal.tsx 5.0 KB

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