AddZoneDialog.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. <script setup lang="ts">
  2. import {
  3. Dialog,
  4. DialogContent,
  5. DialogDescription,
  6. DialogFooter,
  7. DialogHeader,
  8. DialogTitle,
  9. } from '@/components/ui/dialog'
  10. import { Button } from '@/components/ui/button'
  11. import { Input } from '@/components/ui/input'
  12. import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
  13. import { useForm } from 'vee-validate'
  14. import { toTypedSchema } from '@vee-validate/zod'
  15. import * as z from 'zod'
  16. import { toast } from 'vue-sonner'
  17. import { api } from '@/api/client'
  18. import type { Zone } from '@/api/types'
  19. import { ref, watch } from 'vue'
  20. import SpinnerLoader from '@/components/SpinnerLoader.vue'
  21. const props = defineProps<{
  22. open: boolean
  23. zone?: Zone | null
  24. }>()
  25. const emit = defineEmits<{
  26. (e: 'update:open', value: boolean): void
  27. (e: 'saved'): void
  28. }>()
  29. const submitting = ref(false)
  30. const isEdit = () => !!props.zone
  31. const schema = toTypedSchema(
  32. z.object({
  33. zone_id: z.string().min(1, 'Zone ID is required'),
  34. name: z.string().min(1, 'Name is required'),
  35. api_key: z.string().min(1, 'API key is required'),
  36. }),
  37. )
  38. const { handleSubmit, resetForm, setValues } = useForm({
  39. validationSchema: schema,
  40. })
  41. watch(
  42. () => props.open,
  43. (open) => {
  44. if (open && props.zone) {
  45. setValues({
  46. zone_id: props.zone.zone_id,
  47. name: props.zone.name,
  48. api_key: props.zone.api_key,
  49. })
  50. } else if (open) {
  51. resetForm()
  52. }
  53. },
  54. )
  55. const onSubmit = handleSubmit(async (values) => {
  56. submitting.value = true
  57. try {
  58. if (isEdit() && props.zone) {
  59. await api.put(`/api/zones/${props.zone.id}`, values)
  60. toast.success('Zone updated')
  61. } else {
  62. await api.post('/api/zones', values)
  63. toast.success('Zone created')
  64. }
  65. emit('update:open', false)
  66. emit('saved')
  67. } catch (err) {
  68. toast.error(err instanceof Error ? err.message : 'Failed to save zone')
  69. } finally {
  70. submitting.value = false
  71. }
  72. })
  73. </script>
  74. <template>
  75. <Dialog :open="open" @update:open="emit('update:open', $event)">
  76. <DialogContent class="sm:max-w-md">
  77. <DialogHeader>
  78. <DialogTitle>{{ isEdit() ? 'Edit Zone' : 'Add Zone' }}</DialogTitle>
  79. <DialogDescription>
  80. {{ isEdit() ? 'Update the Cloudflare zone details.' : 'Add a new Cloudflare zone to manage.' }}
  81. </DialogDescription>
  82. </DialogHeader>
  83. <form @submit="onSubmit" class="space-y-4">
  84. <FormField name="zone_id" v-slot="{ field }">
  85. <FormItem>
  86. <FormLabel>Zone ID</FormLabel>
  87. <FormControl>
  88. <Input v-bind="field" placeholder="Cloudflare Zone ID" :disabled="isEdit()" />
  89. </FormControl>
  90. <FormMessage />
  91. </FormItem>
  92. </FormField>
  93. <FormField name="name" v-slot="{ field }">
  94. <FormItem>
  95. <FormLabel>Name</FormLabel>
  96. <FormControl>
  97. <Input v-bind="field" placeholder="example.com" />
  98. </FormControl>
  99. <FormMessage />
  100. </FormItem>
  101. </FormField>
  102. <FormField name="api_key" v-slot="{ field }">
  103. <FormItem>
  104. <FormLabel>API Key</FormLabel>
  105. <FormControl>
  106. <Input v-bind="field" type="password" placeholder="Cloudflare API token" />
  107. </FormControl>
  108. <FormMessage />
  109. </FormItem>
  110. </FormField>
  111. <DialogFooter>
  112. <Button type="button" variant="outline" @click="emit('update:open', false)">
  113. Cancel
  114. </Button>
  115. <Button type="submit" class="bg-orange-500 hover:bg-orange-500/90 text-white">
  116. <SpinnerLoader v-if="submitting" customClass="w-4 h-4 fill-gray-200 text-orange-500" />
  117. <template v-else>{{ isEdit() ? 'Update' : 'Create' }}</template>
  118. </Button>
  119. </DialogFooter>
  120. </form>
  121. </DialogContent>
  122. </Dialog>
  123. </template>