BanUserModal.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { zodResolver } from '@hookform/resolvers/zod'
  2. import { useParams } from 'common'
  3. import dayjs from 'dayjs'
  4. import { useEffect } from 'react'
  5. import { useForm } from 'react-hook-form'
  6. import { toast } from 'sonner'
  7. import {
  8. Button,
  9. cn,
  10. Form,
  11. FormControl,
  12. FormField,
  13. Input,
  14. Modal,
  15. Select,
  16. SelectContent,
  17. SelectItem,
  18. SelectTrigger,
  19. Separator,
  20. } from 'ui'
  21. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  22. import * as z from 'zod'
  23. import { useUserUpdateMutation } from '@/data/auth/user-update-mutation'
  24. import { User } from '@/data/auth/users-infinite-query'
  25. interface BanUserModalProps {
  26. visible: boolean
  27. user: User
  28. onClose: () => void
  29. }
  30. export const BanUserModal = ({ visible, user, onClose }: BanUserModalProps) => {
  31. const { ref: projectRef } = useParams()
  32. const { mutate: updateUser, isPending: isBanningUser } = useUserUpdateMutation({
  33. onSuccess: (_, vars) => {
  34. const bannedUntil = dayjs()
  35. .add(Number(vars.banDuration), 'hours')
  36. .format('DD MMM YYYY HH:mm (ZZ)')
  37. toast.success(`User banned successfully until ${bannedUntil}`)
  38. onClose()
  39. },
  40. })
  41. const FormSchema = z.object({
  42. value: z.string().min(1, { message: 'Please provide a duration' }),
  43. unit: z.enum(['hours', 'days']),
  44. })
  45. type FormType = z.infer<typeof FormSchema>
  46. const defaultValues: FormType = { value: '24', unit: 'hours' }
  47. const form = useForm<FormType>({
  48. mode: 'onBlur',
  49. reValidateMode: 'onChange',
  50. resolver: zodResolver(FormSchema as any),
  51. defaultValues,
  52. })
  53. const { value, unit } = form.watch()
  54. const bannedUntil = dayjs().add(Number(value), unit).format('DD MMM YYYY HH:mm (ZZ)')
  55. const onSubmit = (data: FormType) => {
  56. if (projectRef === undefined) return console.error('Project ref is required')
  57. if (user.id === undefined) {
  58. return toast.error(`Failed to ban user: User ID not found`)
  59. }
  60. const durationHours = data.unit === 'hours' ? Number(data.value) : Number(data.value) * 24
  61. updateUser({
  62. projectRef,
  63. userId: user.id,
  64. banDuration: durationHours,
  65. })
  66. }
  67. useEffect(() => {
  68. if (visible) form.reset(defaultValues)
  69. // eslint-disable-next-line react-hooks/exhaustive-deps
  70. }, [visible])
  71. return (
  72. <Modal
  73. hideFooter
  74. visible={visible}
  75. size="small"
  76. header="Confirm to ban user"
  77. onCancel={() => onClose()}
  78. >
  79. <Form {...form}>
  80. <form onSubmit={form.handleSubmit(onSubmit)}>
  81. <Modal.Content className="flex flex-col gap-y-3">
  82. <p className="text-sm">
  83. This will revoke the user's access to your project and prevent them from logging in
  84. for a specified duration.
  85. </p>
  86. <div className="flex items-start gap-x-2 [&>div:first-child]:grow">
  87. <FormField
  88. control={form.control}
  89. name="value"
  90. render={({ field }) => (
  91. <FormItemLayout className="[&>div>div]:mt-0" label="Set a ban duration">
  92. <FormControl>
  93. <Input {...field} />
  94. </FormControl>
  95. </FormItemLayout>
  96. )}
  97. />
  98. <FormField
  99. control={form.control}
  100. name="unit"
  101. render={({ field }) => (
  102. <FormItemLayout className="[&>div>div]:mt-0 mt-[33px]">
  103. <FormControl>
  104. <Select
  105. {...field}
  106. value={field.value}
  107. onValueChange={(value) => form.setValue('unit', value as 'hours' | 'days')}
  108. >
  109. <SelectTrigger className="capitalize w-24">{field.value}</SelectTrigger>
  110. <SelectContent>
  111. <SelectItem value="hours">Hours</SelectItem>
  112. <SelectItem value="days">Days</SelectItem>
  113. </SelectContent>
  114. </Select>
  115. </FormControl>
  116. </FormItemLayout>
  117. )}
  118. />
  119. </div>
  120. <div>
  121. <p className="text-sm text-foreground-lighter">
  122. This user will not be able to log in until:
  123. </p>
  124. <p className={cn('text-sm', !value && 'text-foreground-light')}>
  125. {!!value ? bannedUntil : 'Invalid duration set'}
  126. </p>
  127. </div>
  128. </Modal.Content>
  129. <Separator />
  130. <Modal.Content className="flex justify-end gap-2">
  131. <Button type="default" disabled={isBanningUser} onClick={() => onClose()}>
  132. Cancel
  133. </Button>
  134. <Button type="warning" htmlType="submit" loading={isBanningUser}>
  135. Confirm ban
  136. </Button>
  137. </Modal.Content>
  138. </form>
  139. </Form>
  140. </Modal>
  141. )
  142. }