| 1 | <template> |
| 2 | <div class="customer-integration-form flex min-h-120 flex-col gap-6 overflow-hidden"> |
| 3 | <div> |
| 4 | <n-scrollbar x-scrollable trigger="none"> |
| 5 | <div class="p-1 pr-4"> |
| 6 | <n-steps :current size="small" :status="currentStatus"> |
| 7 | <n-step title="Choose Integration" /> |
| 8 | <n-step title="Set Auth Keys"> |
| 9 | <template #icon> |
| 10 | <Icon v-if="!isAuthKeysStepEnabled" :name="SkipIcon" /> |
| 11 | </template> |
| 12 | </n-step> |
| 13 | </n-steps> |
| 14 | </div> |
| 15 | </n-scrollbar> |
| 16 | </div> |
| 17 | |
| 18 | <div class="flex grow flex-col gap-2 overflow-hidden"> |
| 19 | <Transition :name="`slide-form-${slideFormDirection}`"> |
| 20 | <div v-if="current === 1" class="available-list grow overflow-hidden"> |
| 21 | <n-scrollbar class="max-h-100" trigger="none"> |
| 22 | <IntegrationsList |
| 23 | v-model:selected="selectedIntegration" |
| 24 | embedded |
| 25 | hide-totals |
| 26 | selectable |
| 27 | :disabled-ids-list |
| 28 | class="pr-4" |
| 29 | /> |
| 30 | </n-scrollbar> |
| 31 | </div> |
| 32 | <div v-else class="auth-key-form flex flex-wrap gap-3"> |
| 33 | <template v-for="ak of authKeysForm" :key="ak.key"> |
| 34 | <n-form-item v-if="ak.type === 'string'" :label="ak.key" required class="grow"> |
| 35 | <n-input v-model:value="ak.value" :placeholder="`Input ${ak.key}...`" clearable /> |
| 36 | </n-form-item> |
| 37 | <n-form-item v-if="ak.type === 'selectType'" :label="ak.key" required class="grow"> |
| 38 | <n-select |
| 39 | v-model:value="ak.value" |
| 40 | :options="apiTypeOptions" |
| 41 | :placeholder="`Input ${ak.key}...`" |
| 42 | class="min-w-36" |
| 43 | clearable |
| 44 | /> |
| 45 | </n-form-item> |
| 46 | </template> |
| 47 | </div> |
| 48 | </Transition> |
| 49 | </div> |
| 50 | |
| 51 | <div class="flex justify-between gap-4"> |
| 52 | <div class="flex gap-4"> |
| 53 | <n-button @click="close()">Close</n-button> |
| 54 | </div> |
| 55 | <div class="flex gap-4"> |
| 56 | <n-button v-if="isPrevStepEnabled" @click="prev()"> |
| 57 | <template #icon> |
| 58 | <Icon :name="ArrowLeftIcon" /> |
| 59 | </template> |
| 60 | Prev |
| 61 | </n-button> |
| 62 | <n-button v-if="isNextStepShown" :disabled="!isNextStepEnabled" icon-placement="right" @click="next()"> |
| 63 | <template #icon> |
| 64 | <Icon :name="ArrowRightIcon" /> |
| 65 | </template> |
| 66 | Next |
| 67 | </n-button> |
| 68 | <n-button v-if="isSubmitEnabled" type="primary" :disabled="!isSubmitValid" :loading @click="submit()"> |
| 69 | Submit |
| 70 | </n-button> |
| 71 | </div> |
| 72 | </div> |
| 73 | </div> |
| 74 | </template> |
| 75 | |
| 76 | <script setup lang="ts"> |
| 77 | // TODO-FE: refactor |
| 78 | import type { StepsProps } from "naive-ui" |
| 79 | import type { NewIntegration } from "@/api/endpoints/integrations" |
| 80 | import type { ServiceItemData } from "@/components/services/types" |
| 81 | import { NButton, NFormItem, NInput, NScrollbar, NSelect, NStep, NSteps, useMessage } from "naive-ui" |
| 82 | import { computed, ref, watch } from "vue" |
| 83 | import Api from "@/api" |
| 84 | import Icon from "@/components/common/Icon.vue" |
| 85 | import IntegrationsList from "@/components/integrations/IntegrationsList.vue" |
| 86 | |
| 87 | interface AuthKeysInput { |
| 88 | key: string |
| 89 | value: string |
| 90 | type: "selectType" | "string" |
| 91 | } |
| 92 | |
| 93 | const { customerCode, customerName, disabledIdsList } = defineProps<{ |
| 94 | customerCode: string |
| 95 | customerName: string |
| 96 | disabledIdsList?: (string | number)[] |
| 97 | }>() |
| 98 | |
| 99 | const emit = defineEmits<{ |
| 100 | (e: "update:loading", value: boolean): void |
| 101 | (e: "close"): void |
| 102 | (e: "submitted"): void |
| 103 | }>() |
| 104 | |
| 105 | const SkipIcon = "carbon:subtract" |
| 106 | const ArrowRightIcon = "carbon:arrow-right" |
| 107 | const ArrowLeftIcon = "carbon:arrow-left" |
| 108 | |
| 109 | const message = useMessage() |
| 110 | const current = ref<number>(1) |
| 111 | const currentStatus = ref<StepsProps["status"]>("process") |
| 112 | const slideFormDirection = ref<"right" | "left">("right") |
| 113 | |
| 114 | const selectedIntegration = ref<ServiceItemData | null>(null) |
| 115 | const authKeysForm = ref<AuthKeysInput[]>([]) |
| 116 | const apiTypeOptions = [ |
| 117 | { label: "Commercial", value: "commercial" }, |
| 118 | { label: "GCC", value: "gcc" }, |
| 119 | { label: "GCC-High", value: "gcc-high" } |
| 120 | ] |
| 121 | |
| 122 | watch(selectedIntegration, val => { |
| 123 | authKeysForm.value = [] |
| 124 | |
| 125 | if (val !== null) { |
| 126 | for (const ak of val.keys) { |
| 127 | authKeysForm.value.push({ |
| 128 | key: ak.auth_key_name, |
| 129 | value: ak.auth_key_name === "API_TYPE" ? (apiTypeOptions[0]?.value ?? "") : "", |
| 130 | type: ak.auth_key_name === "API_TYPE" ? "selectType" : "string" |
| 131 | }) |
| 132 | } |
| 133 | } |
| 134 | }) |
| 135 | |
| 136 | const isAuthKeysStepEnabled = computed(() => selectedIntegration.value !== null) |
| 137 | const isNextStepShown = computed(() => current.value === 1) |
| 138 | const isNextStepEnabled = computed(() => isNextStepShown.value && isAuthKeysStepEnabled.value) |
| 139 | const isPrevStepEnabled = computed(() => current.value > 1) |
| 140 | const isSubmitEnabled = computed(() => current.value === 2) |
| 141 | const isSubmitValid = computed(() => { |
| 142 | if (!isSubmitEnabled.value) { |
| 143 | return false |
| 144 | } |
| 145 | |
| 146 | const keys = authKeysForm.value.length |
| 147 | const valid = authKeysForm.value.filter(o => !!o.value).length |
| 148 | |
| 149 | return valid === keys |
| 150 | }) |
| 151 | |
| 152 | const loading = ref(false) |
| 153 | |
| 154 | function submit() { |
| 155 | if (selectedIntegration.value) { |
| 156 | currentStatus.value = "finish" |
| 157 | loading.value = true |
| 158 | |
| 159 | const payload: NewIntegration = { |
| 160 | customer_code: customerCode, |
| 161 | customer_name: customerName, |
| 162 | integration_name: selectedIntegration.value.name, |
| 163 | integration_auth_keys: authKeysForm.value.map(o => ({ |
| 164 | auth_key_name: o.key, |
| 165 | auth_value: o.value |
| 166 | })) |
| 167 | } |
| 168 | |
| 169 | Api.integrations |
| 170 | .createIntegration(payload) |
| 171 | .then(res => { |
| 172 | if (res.data.success) { |
| 173 | emit("submitted") |
| 174 | reset() |
| 175 | message.success(res.data?.message || "Customer integration successfully created.") |
| 176 | } else { |
| 177 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 178 | } |
| 179 | }) |
| 180 | .catch(err => { |
| 181 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 182 | }) |
| 183 | .finally(() => { |
| 184 | loading.value = false |
| 185 | }) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | function close() { |
| 190 | reset() |
| 191 | emit("close") |
| 192 | } |
| 193 | |
| 194 | function reset() { |
| 195 | currentStatus.value = "process" |
| 196 | slideFormDirection.value = "right" |
| 197 | current.value = 1 |
| 198 | |
| 199 | selectedIntegration.value = null |
| 200 | authKeysForm.value = [] |
| 201 | } |
| 202 | |
| 203 | function next() { |
| 204 | currentStatus.value = "process" |
| 205 | slideFormDirection.value = "right" |
| 206 | current.value++ |
| 207 | } |
| 208 | |
| 209 | function prev() { |
| 210 | currentStatus.value = "process" |
| 211 | slideFormDirection.value = "left" |
| 212 | current.value-- |
| 213 | } |
| 214 | </script> |
| 215 | |
| 216 | <style lang="scss" scoped> |
| 217 | .customer-integration-form { |
| 218 | .slide-form-right-enter-active, |
| 219 | .slide-form-right-leave-active, |
| 220 | .slide-form-left-enter-active, |
| 221 | .slide-form-left-leave-active { |
| 222 | transition: all 0.2s ease-out; |
| 223 | position: absolute; |
| 224 | width: 100%; |
| 225 | } |
| 226 | |
| 227 | .slide-form-left-enter-from { |
| 228 | transform: translateX(-100%); |
| 229 | } |
| 230 | |
| 231 | .slide-form-left-leave-to { |
| 232 | transform: translateX(100%); |
| 233 | } |
| 234 | |
| 235 | .slide-form-right-enter-from { |
| 236 | transform: translateX(100%); |
| 237 | } |
| 238 | |
| 239 | .slide-form-right-leave-to { |
| 240 | transform: translateX(-100%); |
| 241 | } |
| 242 | } |
| 243 | </style> |