mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-28 04:01:10 -04:00
v6 update
This commit is contained in:
110
resources/scripts/admin/views/installation/Installation.vue
Normal file
110
resources/scripts/admin/views/installation/Installation.vue
Normal file
@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-between w-full pt-10">
|
||||
<img
|
||||
id="logo-crater"
|
||||
src="/img/crater-logo.png"
|
||||
alt="Crater Logo"
|
||||
class="h-12 mb-5 md:mb-10"
|
||||
/>
|
||||
|
||||
<BaseWizard
|
||||
:steps="7"
|
||||
:current-step="currentStepNumber"
|
||||
@click="onNavClick"
|
||||
>
|
||||
<component :is="stepComponent" @next="onStepChange" />
|
||||
</BaseWizard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Step1RequirementsCheck from './Step1RequirementsCheck.vue'
|
||||
import Step2PermissionCheck from './Step2PermissionCheck.vue'
|
||||
import Step3DatabaseConfig from './Step3DatabaseConfig.vue'
|
||||
import Step4VerifyDomain from './Step4VerifyDomain.vue'
|
||||
import Step5EmailConfig from './Step5EmailConfig.vue'
|
||||
import Step6AccountSettings from './Step6AccountSettings.vue'
|
||||
import Step7CompanyInfo from './Step7CompanyInfo.vue'
|
||||
import Step8CompanyPreferences from './Step8CompanyPreferences.vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
step_1: Step1RequirementsCheck,
|
||||
step_2: Step2PermissionCheck,
|
||||
step_3: Step3DatabaseConfig,
|
||||
step_4: Step4VerifyDomain,
|
||||
step_5: Step5EmailConfig,
|
||||
step_6: Step6AccountSettings,
|
||||
step_7: Step7CompanyInfo,
|
||||
step_8: Step8CompanyPreferences,
|
||||
},
|
||||
|
||||
setup() {
|
||||
let stepComponent = ref('step_1')
|
||||
let currentStepNumber = ref(1)
|
||||
|
||||
const router = useRouter()
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
checkCurrentProgress()
|
||||
|
||||
async function checkCurrentProgress() {
|
||||
let res = await installationStore.fetchInstallationStep()
|
||||
|
||||
if (res.data.profile_complete === 'COMPLETED') {
|
||||
router.push('/admin/dashboard')
|
||||
return
|
||||
}
|
||||
|
||||
let dbstep = parseInt(res.data.profile_complete)
|
||||
|
||||
if (dbstep) {
|
||||
currentStepNumber.value = dbstep + 1
|
||||
stepComponent.value = `step_${dbstep + 1}`
|
||||
}
|
||||
}
|
||||
|
||||
async function saveStepProgress(data) {
|
||||
let status = {
|
||||
profile_complete: data,
|
||||
}
|
||||
|
||||
try {
|
||||
await installationStore.addInstallationStep(status)
|
||||
return true
|
||||
} catch (e) {
|
||||
if (e?.response?.data?.message === 'The MAC is invalid.') {
|
||||
window.location.reload()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function onStepChange(data) {
|
||||
if (data) {
|
||||
let res = await saveStepProgress(data)
|
||||
if (!res) return false
|
||||
}
|
||||
|
||||
currentStepNumber.value++
|
||||
|
||||
if (currentStepNumber.value <= 8) {
|
||||
stepComponent.value = 'step_' + currentStepNumber.value
|
||||
}
|
||||
}
|
||||
|
||||
function onNavClick(e) {}
|
||||
|
||||
return {
|
||||
stepComponent,
|
||||
currentStepNumber,
|
||||
onStepChange,
|
||||
saveStepProgress,
|
||||
onNavClick,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.req.system_req')"
|
||||
:description="$t('wizard.req.system_req_desc')"
|
||||
>
|
||||
<div class="w-full md:w-2/3">
|
||||
<div class="mb-6">
|
||||
<div
|
||||
v-if="phpSupportInfo"
|
||||
class="grid grid-flow-row grid-cols-3 p-3 border border-gray-200 lg:gap-24 sm:gap-4"
|
||||
>
|
||||
<div class="col-span-2 text-sm">
|
||||
{{
|
||||
$t('wizard.req.php_req_version', {
|
||||
version: phpSupportInfo.minimum,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{{ phpSupportInfo.current }}
|
||||
<span
|
||||
v-if="phpSupportInfo.supported"
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 bg-green-500 rounded-full"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 bg-red-500 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="requirements">
|
||||
<div
|
||||
v-for="(requirement, index) in requirements"
|
||||
:key="index"
|
||||
class="grid grid-flow-row grid-cols-3 p-3 border border-gray-200 lg:gap-24 sm:gap-4"
|
||||
>
|
||||
<div class="col-span-2 text-sm">
|
||||
{{ index }}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span
|
||||
v-if="requirement"
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 bg-green-500 rounded-full"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 bg-red-500 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseButton v-if="hasNext" @click="next">
|
||||
{{ $t('wizard.continue') }}
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="ArrowRightIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-if="!requirements"
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
@click="getRequirements"
|
||||
>
|
||||
{{ $t('wizard.req.check_req') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation.js'
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
const requirements = ref('')
|
||||
const phpSupportInfo = ref('')
|
||||
const isSaving = ref(false)
|
||||
const isShow = ref(true)
|
||||
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
const hasNext = computed(() => {
|
||||
if (requirements.value) {
|
||||
let isRequired = true
|
||||
for (const key in requirements.value) {
|
||||
if (!requirements.value[key]) {
|
||||
isRequired = false
|
||||
}
|
||||
return requirements.value && phpSupportInfo.value.supported && isRequired
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
async function getRequirements() {
|
||||
isSaving.value = true
|
||||
const response = await installationStore.fetchInstallationRequirements()
|
||||
|
||||
if (response.data) {
|
||||
requirements.value = response?.data?.requirements?.requirements?.php
|
||||
phpSupportInfo.value = response?.data?.phpSupportInfo
|
||||
}
|
||||
}
|
||||
|
||||
function next() {
|
||||
isSaving.value = true
|
||||
emit('next')
|
||||
isSaving.value = false
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.permissions.permissions')"
|
||||
:description="$t('wizard.permissions.permission_desc')"
|
||||
>
|
||||
<!-- Content Placeholders -->
|
||||
<BaseContentPlaceholders v-if="isFetchingInitialData">
|
||||
<div
|
||||
v-for="(permission, index) in 3"
|
||||
:key="index"
|
||||
class="
|
||||
grid grid-flow-row grid-cols-3
|
||||
lg:gap-24
|
||||
sm:gap-4
|
||||
border border-gray-200
|
||||
"
|
||||
>
|
||||
<BaseContentPlaceholdersText :lines="1" class="col-span-4 p-3" />
|
||||
</div>
|
||||
<BaseContentPlaceholdersBox
|
||||
:rounded="true"
|
||||
class="mt-10"
|
||||
style="width: 96px; height: 42px"
|
||||
/>
|
||||
</BaseContentPlaceholders>
|
||||
<!-- End of Content Placeholder -->
|
||||
|
||||
<div v-else class="relative">
|
||||
<div
|
||||
v-for="(permission, index) in permissions"
|
||||
:key="index"
|
||||
class="border border-gray-200"
|
||||
>
|
||||
<div class="grid grid-flow-row grid-cols-3 lg:gap-24 sm:gap-4">
|
||||
<div class="col-span-2 p-3">
|
||||
{{ permission.folder }}
|
||||
</div>
|
||||
<div class="p-3 text-right">
|
||||
<span
|
||||
v-if="permission.isSet"
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-green-500"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-red-500"
|
||||
/>
|
||||
<span>{{ permission.permission }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
v-show="!isFetchingInitialData"
|
||||
class="mt-10"
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
@click="next"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="ArrowRightIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('wizard.continue') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation'
|
||||
import { useDialogStore } from '@/scripts/stores/dialog'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
let isFetchingInitialData = ref(false)
|
||||
let isSaving = ref(false)
|
||||
let permissions = ref([])
|
||||
const { tm, t } = useI18n()
|
||||
|
||||
const installationStore = useInstallationStore()
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
onMounted(() => {
|
||||
getPermissions()
|
||||
})
|
||||
|
||||
async function getPermissions() {
|
||||
isFetchingInitialData.value = true
|
||||
|
||||
const res = await installationStore.fetchInstallationPermissions()
|
||||
|
||||
permissions.value = res.data.permissions.permissions
|
||||
|
||||
if (res.data && res.data.permissions.errors) {
|
||||
setTimeout(() => {
|
||||
dialogStore
|
||||
.openDialog({
|
||||
title: tm('wizard.permissions.permission_confirm_title'),
|
||||
message: t('wizard.permissions.permission_confirm_desc'),
|
||||
yesLabel: 'OK',
|
||||
noLabel: 'Cancel',
|
||||
variant: 'danger',
|
||||
hideNoButton: false,
|
||||
size: 'lg',
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
|
||||
function next() {
|
||||
isSaving.value = true
|
||||
emit('next')
|
||||
isSaving.value = false
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.database.database')"
|
||||
:description="$t('wizard.database.desc')"
|
||||
step-container="w-full p-8 mb-8 bg-white border border-gray-200 border-solid rounded md:w-full"
|
||||
>
|
||||
<component
|
||||
:is="databaseData.database_connection"
|
||||
:config-data="databaseData"
|
||||
:is-saving="isSaving"
|
||||
@on-change-driver="getDatabaseConfig"
|
||||
@submit-data="next"
|
||||
/>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed } from 'vue'
|
||||
import Mysql from './database/MysqlDatabase.vue'
|
||||
import Pgsql from './database/PgsqlDatabase.vue'
|
||||
import Sqlite from './database/SqliteDatabase.vue'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Mysql,
|
||||
Pgsql,
|
||||
Sqlite,
|
||||
},
|
||||
|
||||
emits: ['next'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const database_connection = ref('mysql')
|
||||
const isSaving = ref(false)
|
||||
const { t } = useI18n()
|
||||
|
||||
const notificationStore = useNotificationStore()
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
const databaseData = computed(() => {
|
||||
return installationStore.currentDataBaseData
|
||||
})
|
||||
|
||||
async function getDatabaseConfig(connection) {
|
||||
let params = {
|
||||
connection,
|
||||
}
|
||||
|
||||
const res = await installationStore.fetchInstallationDatabase(params)
|
||||
|
||||
if (res.data.success) {
|
||||
databaseData.value.database_connection =
|
||||
res.data.config.database_connection
|
||||
}
|
||||
|
||||
if (connection === 'sqlite') {
|
||||
databaseData.value.database_name = res.data.config.database_name
|
||||
} else {
|
||||
databaseData.value.database_name = null
|
||||
}
|
||||
}
|
||||
|
||||
async function next(databaseData) {
|
||||
isSaving.value = true
|
||||
|
||||
try {
|
||||
let res = await installationStore.addInstallationDatabase(databaseData)
|
||||
isSaving.value = false
|
||||
|
||||
if (res.data.success) {
|
||||
await installationStore.addInstallationFinish()
|
||||
|
||||
emit('next', 3)
|
||||
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: t('wizard.success.' + res.data.success),
|
||||
})
|
||||
|
||||
return
|
||||
} else if (res.data.error) {
|
||||
if (res.data.requirement) {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: t('wizard.errors.' + res.data.error, {
|
||||
version: res.data.requirement.minimum,
|
||||
name: databaseData.value.database_connection,
|
||||
}),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: t('wizard.errors.' + res.data.error),
|
||||
})
|
||||
} else if (res.data.errors) {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: res.data.errors[0],
|
||||
})
|
||||
} else if (res.data.error_message) {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: res.data.error_message,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: t('validation.something_went_wrong'),
|
||||
})
|
||||
isSaving.value = false
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
databaseData,
|
||||
database_connection,
|
||||
isSaving,
|
||||
getDatabaseConfig,
|
||||
next,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
108
resources/scripts/admin/views/installation/Step4VerifyDomain.vue
Normal file
108
resources/scripts/admin/views/installation/Step4VerifyDomain.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.verify_domain.title')"
|
||||
:description="$t('wizard.verify_domain.desc')"
|
||||
>
|
||||
<div class="w-full md:w-2/3">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.verify_domain.app_domain')"
|
||||
:error="v$.app_domain.$error && v$.app_domain.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="formData.app_domain"
|
||||
:invalid="v$.app_domain.$error"
|
||||
type="text"
|
||||
@input="v$.app_domain.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 mb-0 text-sm text-gray-600">Notes:</p>
|
||||
<ul class="w-full text-gray-600 list-disc list-inside">
|
||||
<li class="text-sm leading-8">
|
||||
App domain should not contain
|
||||
<b class="inline-block px-1 bg-gray-100 rounded-sm">https://</b> or
|
||||
<b class="inline-block px-1 bg-gray-100 rounded-sm">http</b> in front of
|
||||
the domain.
|
||||
</li>
|
||||
<li class="text-sm leading-8">
|
||||
If you're accessing the website on a different port, please mention the
|
||||
port. For example:
|
||||
<b class="inline-block px-1 bg-gray-100">localhost:8080</b>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
class="mt-8"
|
||||
@click="verifyDomain"
|
||||
>
|
||||
{{ $t('wizard.verify_domain.verify_now') }}
|
||||
</BaseButton>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { required, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { ref, inject, computed, reactive } from 'vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
const formData = reactive({
|
||||
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
|
||||
})
|
||||
const isSaving = ref(false)
|
||||
const { t } = useI18n()
|
||||
const utils = inject('utils')
|
||||
const isUrl = (value) => utils.checkValidDomainUrl(value)
|
||||
|
||||
const installationStore = useInstallationStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
const rules = {
|
||||
app_domain: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
isUrl: helpers.withMessage(t('validation.invalid_domain_url'), isUrl),
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => formData)
|
||||
)
|
||||
|
||||
async function verifyDomain() {
|
||||
v$.value.$touch()
|
||||
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
try {
|
||||
await installationStore.setInstallationDomain(formData)
|
||||
await installationStore.installationLogin()
|
||||
let driverRes = await installationStore.checkAutheticated()
|
||||
|
||||
if (driverRes.data) {
|
||||
emit('next', 4)
|
||||
}
|
||||
|
||||
isSaving.value = false
|
||||
} catch (e) {
|
||||
notificationStore.showNotification({
|
||||
type: 'error',
|
||||
message: t('wizard.verify_domain.failed'),
|
||||
})
|
||||
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.mail.mail_config')"
|
||||
:description="$t('wizard.mail.mail_config_desc')"
|
||||
>
|
||||
<form action="" @submit.prevent="next">
|
||||
<component
|
||||
:is="mailDriverStore.mail_driver"
|
||||
:config-data="mailDriverStore.mailConfigData"
|
||||
:is-saving="isSaving"
|
||||
:is-fetching-initial-data="isFetchingInitialData"
|
||||
@on-change-driver="(val) => changeDriver(val)"
|
||||
@submit-data="next"
|
||||
/>
|
||||
</form>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Smtp from './mail-driver/SmtpMailDriver.vue'
|
||||
import Mailgun from './mail-driver/MailgunMailDriver.vue'
|
||||
import Ses from './mail-driver/SesMailDriver.vue'
|
||||
import Basic from './mail-driver/BasicMailDriver.vue'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Smtp,
|
||||
Mailgun,
|
||||
Ses,
|
||||
sendmail: Basic,
|
||||
Mail: Basic,
|
||||
},
|
||||
|
||||
emits: ['next'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const isSaving = ref(false)
|
||||
const isFetchingInitialData = ref(false)
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
|
||||
mailDriverStore.mail_driver = 'mail'
|
||||
|
||||
loadData()
|
||||
|
||||
function changeDriver(value) {
|
||||
mailDriverStore.mail_driver = value
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
isFetchingInitialData.value = true
|
||||
await mailDriverStore.fetchMailDrivers()
|
||||
isFetchingInitialData.value = false
|
||||
}
|
||||
|
||||
async function next(mailConfigData) {
|
||||
isSaving.value = true
|
||||
let res = await mailDriverStore.updateMailConfig(mailConfigData)
|
||||
isSaving.value = false
|
||||
|
||||
if (res.data.success) {
|
||||
await emit('next', 5)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mailDriverStore,
|
||||
isSaving,
|
||||
isFetchingInitialData,
|
||||
changeDriver,
|
||||
next,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.account_info')"
|
||||
:description="$t('wizard.account_info_desc')"
|
||||
>
|
||||
<form action="" @submit.prevent="next">
|
||||
<div class="grid grid-cols-1 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$tc('settings.account_settings.profile_picture')"
|
||||
>
|
||||
<BaseFileUploader
|
||||
:avatar="true"
|
||||
:preview-image="avatarUrl"
|
||||
@change="onFileInputChange"
|
||||
@remove="onFileInputRemove"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.name')"
|
||||
:error="
|
||||
v$.userForm.name.$error && v$.userForm.name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="userForm.name"
|
||||
:invalid="v$.userForm.name.$error"
|
||||
type="text"
|
||||
name="name"
|
||||
@input="v$.userForm.name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.email')"
|
||||
:error="
|
||||
v$.userForm.email.$error && v$.userForm.email.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="userForm.email"
|
||||
:invalid="v$.userForm.email.$error"
|
||||
type="text"
|
||||
name="email"
|
||||
@input="v$.userForm.email.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.password')"
|
||||
:error="
|
||||
v$.userForm.password.$error &&
|
||||
v$.userForm.password.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="userForm.password"
|
||||
:invalid="v$.userForm.password.$error"
|
||||
:type="isShowPassword ? 'text' : 'password'"
|
||||
name="password"
|
||||
@input="v$.userForm.password.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<EyeOffIcon
|
||||
v-if="isShowPassword"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<EyeIcon
|
||||
v-else
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.confirm_password')"
|
||||
:error="
|
||||
v$.userForm.confirm_password.$error &&
|
||||
v$.userForm.confirm_password.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="userForm.confirm_password"
|
||||
:invalid="v$.userForm.confirm_password.$error"
|
||||
:type="isShowConfirmPassword ? 'text' : 'password'"
|
||||
name="confirm_password"
|
||||
@input="v$.userForm.confirm_password.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowConfirmPassword"
|
||||
name="EyeOffIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowConfirmPassword = !isShowConfirmPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
name="EyeIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowConfirmPassword = !isShowConfirmPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton :loading="isSaving" :disabled="isSaving" class="mt-4">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('wizard.save_cont') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
helpers,
|
||||
required,
|
||||
requiredIf,
|
||||
sameAs,
|
||||
minLength,
|
||||
email,
|
||||
} from '@vuelidate/validators'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useVuelidate } from '@vuelidate/core'
|
||||
import { useUserStore } from '@/scripts/admin/stores/user'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
let isSaving = ref(false)
|
||||
const isShowPassword = ref(false)
|
||||
const isShowConfirmPassword = ref(false)
|
||||
let avatarUrl = ref('')
|
||||
let avatarFileBlob = ref(null)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const companyStore = useCompanyStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const userForm = computed(() => {
|
||||
return userStore.userForm
|
||||
})
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
userForm: {
|
||||
name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
email: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
password: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
minLength: helpers.withMessage(
|
||||
t('validation.password_min_length', { count: 8 }),
|
||||
minLength(8)
|
||||
),
|
||||
},
|
||||
confirm_password: {
|
||||
required: helpers.withMessage(
|
||||
t('validation.required'),
|
||||
requiredIf(userStore.userForm.password)
|
||||
),
|
||||
sameAsPassword: helpers.withMessage(
|
||||
t('validation.password_incorrect'),
|
||||
sameAs(userStore.userForm.password)
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => userStore)
|
||||
)
|
||||
|
||||
function onFileInputChange(fileName, file) {
|
||||
avatarFileBlob.value = file
|
||||
}
|
||||
|
||||
function onFileInputRemove() {
|
||||
avatarFileBlob.value = null
|
||||
}
|
||||
|
||||
async function next() {
|
||||
v$.value.userForm.$touch()
|
||||
if (v$.value.userForm.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
let res = await userStore.updateCurrentUser(userForm.value)
|
||||
isSaving.value = false
|
||||
|
||||
if (res.data.data) {
|
||||
if (avatarFileBlob.value) {
|
||||
let avatarData = new FormData()
|
||||
|
||||
avatarData.append('admin_avatar', avatarFileBlob.value)
|
||||
|
||||
await userStore.uploadAvatar(avatarData)
|
||||
}
|
||||
|
||||
const company = res.data.data.companies[0]
|
||||
|
||||
await companyStore.setSelectedCompany(company)
|
||||
emit('next', 6)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
247
resources/scripts/admin/views/installation/Step7CompanyInfo.vue
Normal file
247
resources/scripts/admin/views/installation/Step7CompanyInfo.vue
Normal file
@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.company_info')"
|
||||
:description="$t('wizard.company_info_desc')"
|
||||
step-container="bg-white border border-gray-200 border-solid mb-8 md:w-full p-8 rounded w-full"
|
||||
>
|
||||
<form action="" @submit.prevent="next">
|
||||
<div class="grid grid-cols-1 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup :label="$tc('settings.company_info.company_logo')">
|
||||
<BaseFileUploader
|
||||
base64
|
||||
:preview-image="previewLogo"
|
||||
@change="onFileInputChange"
|
||||
@remove="onFileInputRemove"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.company_name')"
|
||||
:error="
|
||||
v$.companyForm.name.$error &&
|
||||
v$.companyForm.name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="companyForm.name"
|
||||
:invalid="v$.companyForm.name.$error"
|
||||
type="text"
|
||||
name="name"
|
||||
@input="v$.companyForm.name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.country')"
|
||||
:error="
|
||||
v$.companyForm.address.country_id.$error &&
|
||||
v$.companyForm.address.country_id.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="companyForm.address.country_id"
|
||||
label="name"
|
||||
:invalid="v$.companyForm.address.country_id.$error"
|
||||
:options="globalStore.countries"
|
||||
value-prop="id"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:placeholder="$t('general.select_country')"
|
||||
searchable
|
||||
track-by="name"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup :label="$t('wizard.state')">
|
||||
<BaseInput
|
||||
v-model="companyForm.address.state"
|
||||
name="state"
|
||||
type="text"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup :label="$t('wizard.city')">
|
||||
<BaseInput
|
||||
v-model="companyForm.address.city"
|
||||
name="city"
|
||||
type="text"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
|
||||
<div>
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.address')"
|
||||
:error="
|
||||
v$.companyForm.address.address_street_1.$error &&
|
||||
v$.companyForm.address.address_street_1.$errors[0].$message
|
||||
"
|
||||
>
|
||||
<BaseTextarea
|
||||
v-model.trim="companyForm.address.address_street_1"
|
||||
:invalid="v$.companyForm.address.address_street_1.$error"
|
||||
:placeholder="$t('general.street_1')"
|
||||
name="billing_street1"
|
||||
rows="2"
|
||||
@input="v$.companyForm.address.address_street_1.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:error="
|
||||
v$.companyForm.address.address_street_2.$error &&
|
||||
v$.companyForm.address.address_street_2.$errors[0].$message
|
||||
"
|
||||
class="mt-1 lg:mt-2 md:mt-2"
|
||||
>
|
||||
<BaseTextarea
|
||||
v-model="companyForm.address.address_street_2"
|
||||
:invalid="v$.companyForm.address.address_street_2.$error"
|
||||
:placeholder="$t('general.street_2')"
|
||||
name="billing_street2"
|
||||
rows="2"
|
||||
@input="v$.companyForm.address.address_street_2.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<BaseInputGroup :label="$t('wizard.zip_code')">
|
||||
<BaseInput
|
||||
v-model.trim="companyForm.address.zip"
|
||||
type="text"
|
||||
name="zip"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup :label="$t('wizard.phone')" class="mt-4">
|
||||
<BaseInput
|
||||
v-model.trim="companyForm.address.phone"
|
||||
type="text"
|
||||
name="phone"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseButton :loading="isSaving" :disabled="isSaving" class="mt-4">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('wizard.save_cont') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { required, maxLength, helpers } from '@vuelidate/validators'
|
||||
import { useVuelidate } from '@vuelidate/core'
|
||||
import { useGlobalStore } from '@/scripts/admin/stores/global'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
let isFetchingInitialData = ref(false)
|
||||
let isSaving = ref(false)
|
||||
const { t } = useI18n()
|
||||
let previewLogo = ref(null)
|
||||
let logoFileBlob = ref(null)
|
||||
let logoFileName = ref(null)
|
||||
|
||||
const companyForm = reactive({
|
||||
name: null,
|
||||
address: {
|
||||
address_street_1: '',
|
||||
address_street_2: '',
|
||||
website: '',
|
||||
country_id: null,
|
||||
state: '',
|
||||
city: '',
|
||||
phone: '',
|
||||
zip: '',
|
||||
},
|
||||
})
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
onMounted(async () => {
|
||||
isFetchingInitialData.value = true
|
||||
await globalStore.fetchCountries()
|
||||
isFetchingInitialData.value = false
|
||||
})
|
||||
|
||||
const rules = {
|
||||
companyForm: {
|
||||
name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
address: {
|
||||
country_id: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
address_street_1: {
|
||||
maxLength: helpers.withMessage(
|
||||
t('validation.address_maxlength', { count: 255 }),
|
||||
maxLength(255)
|
||||
),
|
||||
},
|
||||
address_street_2: {
|
||||
maxLength: helpers.withMessage(
|
||||
t('validation.address_maxlength', { count: 255 }),
|
||||
maxLength(255)
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(rules, { companyForm })
|
||||
|
||||
function onFileInputChange(fileName, file, fileCount, fileList) {
|
||||
logoFileName.value = fileList.name
|
||||
logoFileBlob.value = file
|
||||
}
|
||||
|
||||
function onFileInputRemove() {
|
||||
logoFileBlob.value = null
|
||||
}
|
||||
|
||||
async function next() {
|
||||
v$.value.companyForm.$touch()
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
let res = companyStore.updateCompany(companyForm)
|
||||
if (res) {
|
||||
if (logoFileBlob.value) {
|
||||
let logoData = new FormData()
|
||||
|
||||
logoData.append(
|
||||
'company_logo',
|
||||
JSON.stringify({
|
||||
name: logoFileName.value,
|
||||
data: logoFileBlob.value,
|
||||
})
|
||||
)
|
||||
await companyStore.updateCompanyLogo(logoData)
|
||||
}
|
||||
isSaving.value = false
|
||||
emit('next', 7)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<BaseWizardStep
|
||||
:title="$t('wizard.preferences')"
|
||||
:description="$t('wizard.preferences_desc')"
|
||||
step-container="bg-white border border-gray-200 border-solid mb-8 md:w-full p-8 rounded w-full"
|
||||
>
|
||||
<form action="" @submit.prevent="next">
|
||||
<div>
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.currency')"
|
||||
:error="
|
||||
v$.currentPreferences.currency.$error &&
|
||||
v$.currentPreferences.currency.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="currentPreferences.currency"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.currencies"
|
||||
label="name"
|
||||
value-prop="id"
|
||||
:searchable="true"
|
||||
track-by="name"
|
||||
:placeholder="$tc('settings.currencies.select_currency')"
|
||||
:invalid="v$.currentPreferences.currency.$error"
|
||||
class="w-full"
|
||||
>
|
||||
</BaseMultiselect>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.preferences.default_language')"
|
||||
:error="
|
||||
v$.currentPreferences.language.$error &&
|
||||
v$.currentPreferences.language.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="currentPreferences.language"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.languages"
|
||||
label="name"
|
||||
value-prop="code"
|
||||
:placeholder="$tc('settings.preferences.select_language')"
|
||||
class="w-full"
|
||||
track-by="code"
|
||||
:searchable="true"
|
||||
:invalid="v$.currentPreferences.language.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.date_format')"
|
||||
:error="
|
||||
v$.currentPreferences.carbon_date_format.$error &&
|
||||
v$.currentPreferences.carbon_date_format.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="currentPreferences.carbon_date_format"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.dateFormats"
|
||||
label="display_date"
|
||||
value-prop="carbon_format_value"
|
||||
:placeholder="$tc('settings.preferences.select_date_format')"
|
||||
track-by="display_date"
|
||||
searchable
|
||||
:invalid="v$.currentPreferences.carbon_date_format.$error"
|
||||
class="w-full"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.time_zone')"
|
||||
:error="
|
||||
v$.currentPreferences.time_zone.$error &&
|
||||
v$.currentPreferences.time_zone.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="currentPreferences.time_zone"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.timeZones"
|
||||
label="key"
|
||||
value-prop="value"
|
||||
:placeholder="$tc('settings.preferences.select_time_zone')"
|
||||
track-by="value"
|
||||
:searchable="true"
|
||||
:invalid="v$.currentPreferences.time_zone.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.fiscal_year')"
|
||||
:error="
|
||||
v$.currentPreferences.fiscal_year.$error &&
|
||||
v$.currentPreferences.fiscal_year.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="currentPreferences.fiscal_year"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:options="globalStore.fiscalYears"
|
||||
label="key"
|
||||
value-prop="value"
|
||||
:placeholder="$tc('settings.preferences.select_financial_year')"
|
||||
:invalid="v$.currentPreferences.fiscal_year.$error"
|
||||
track-by="key"
|
||||
:searchable="true"
|
||||
class="w-full"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('wizard.save_cont') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseWizardStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
import { required, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import Ls from '@/scripts/services/ls.js'
|
||||
import { useGlobalStore } from '@/scripts/admin/stores/global'
|
||||
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
import { useUserStore } from '@/scripts/admin/stores/user'
|
||||
import { useDialogStore } from '@/scripts/stores/dialog'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const emit = defineEmits(['next'])
|
||||
|
||||
const isSaving = ref(false)
|
||||
let isFetchingInitialData = ref(false)
|
||||
|
||||
let currentPreferences = reactive({
|
||||
currency: 1,
|
||||
language: 'en',
|
||||
carbon_date_format: 'd M Y',
|
||||
time_zone: 'UTC',
|
||||
fiscal_year: '1-12',
|
||||
})
|
||||
|
||||
const { tm, t } = useI18n()
|
||||
const router = useRouter()
|
||||
|
||||
isFetchingInitialData.value = true
|
||||
|
||||
const options = reactive([
|
||||
{
|
||||
title: tm('settings.customization.invoices.allow'),
|
||||
value: 'allow',
|
||||
},
|
||||
{
|
||||
title: tm(
|
||||
'settings.customization.invoices.disable_on_invoice_partial_paid'
|
||||
),
|
||||
value: 'disable_on_invoice_partial_paid',
|
||||
},
|
||||
{
|
||||
title: tm('settings.customization.invoices.disable_on_invoice_paid'),
|
||||
value: 'disable_on_invoice_paid',
|
||||
},
|
||||
{
|
||||
title: tm('settings.customization.invoices.disable_on_invoice_sent'),
|
||||
value: 'disable_on_invoice_sent',
|
||||
},
|
||||
])
|
||||
|
||||
const dialogStore = useDialogStore()
|
||||
const globalStore = useGlobalStore()
|
||||
const companyStore = useCompanyStore()
|
||||
const userStore = useUserStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
let fiscalYears = {
|
||||
key: 'fiscal_years',
|
||||
}
|
||||
let data = {
|
||||
key: 'languages',
|
||||
}
|
||||
|
||||
isFetchingInitialData.value = true
|
||||
Promise.all([
|
||||
globalStore.fetchCurrencies(),
|
||||
globalStore.fetchDateFormats(),
|
||||
globalStore.fetchTimeZones(),
|
||||
globalStore.fetchCountries(),
|
||||
globalStore.fetchConfig(fiscalYears),
|
||||
globalStore.fetchConfig(data),
|
||||
]).then(([res1]) => {
|
||||
isFetchingInitialData.value = false
|
||||
})
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
currentPreferences: {
|
||||
currency: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
language: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
carbon_date_format: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
time_zone: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
fiscal_year: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(rules, { currentPreferences })
|
||||
|
||||
async function next() {
|
||||
v$.value.currentPreferences.$touch()
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
dialogStore
|
||||
.openDialog({
|
||||
title: t('general.do_you_wish_to_continue'),
|
||||
message: t('wizard.currency_set_alert'),
|
||||
yesLabel: t('general.ok'),
|
||||
noLabel: t('general.cancel'),
|
||||
variant: 'danger',
|
||||
size: 'lg',
|
||||
hideNoButton: false,
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (res) {
|
||||
let data = {
|
||||
settings: {
|
||||
...currentPreferences,
|
||||
},
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
delete data.settings.discount_per_item
|
||||
|
||||
let res = await companyStore.updateCompanySettings({
|
||||
data,
|
||||
})
|
||||
|
||||
if (res.data) {
|
||||
isSaving.value = false
|
||||
|
||||
let data = {
|
||||
settings: {
|
||||
language: currentPreferences.language,
|
||||
},
|
||||
}
|
||||
|
||||
let res1 = await userStore.updateUserSettings(data)
|
||||
|
||||
if (res1.data) {
|
||||
emit('next', 'COMPLETED')
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: 'Login Successful',
|
||||
})
|
||||
router.push('/admin/dashboard')
|
||||
}
|
||||
|
||||
Ls.set('auth.token', res.data.token)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
isSaving.value = false
|
||||
return true
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<form action="" @submit.prevent="next">
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:mb-6 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.app_url')"
|
||||
:error="v$.app_url.$error && v$.app_url.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.app_url"
|
||||
:invalid="v$.app_url.$error"
|
||||
type="text"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.connection')"
|
||||
:error="
|
||||
v$.database_connection.$error &&
|
||||
v$.database_connection.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="databaseData.database_connection"
|
||||
:invalid="v$.database_connection.$error"
|
||||
:options="connections"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
@update:modelValue="onChangeConnection"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.port')"
|
||||
:error="v$.database_port.$error && v$.database_port.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_port"
|
||||
:invalid="v$.database_port.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.db_name')"
|
||||
:error="v$.database_name.$error && v$.database_name.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_name"
|
||||
:invalid="v$.database_name.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.username')"
|
||||
:error="
|
||||
v$.database_username.$error &&
|
||||
v$.database_username.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_username"
|
||||
:invalid="v$.database_username.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup :label="$t('wizard.database.password')">
|
||||
<BaseInput v-model="databaseData.database_password" type="password" />
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.host')"
|
||||
:error="
|
||||
v$.database_hostname.$error &&
|
||||
v$.database_hostname.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_hostname"
|
||||
:invalid="v$.database_hostname.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('wizard.save_cont') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, inject } from 'vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation'
|
||||
import { helpers, required, numeric } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const connections = reactive(['sqlite', 'mysql', 'pgsql'])
|
||||
const { t } = useI18n()
|
||||
const utils = inject('utils')
|
||||
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
onMounted(() => {
|
||||
for (const key in databaseData.value) {
|
||||
if (props.configData.hasOwnProperty(key)) {
|
||||
databaseData.value[key] = props.configData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const databaseData = computed(() => {
|
||||
return installationStore.currentDataBaseData
|
||||
})
|
||||
|
||||
const isUrl = (value) => utils.checkValidUrl(value)
|
||||
|
||||
const rules = {
|
||||
database_connection: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
database_hostname: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
database_port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric: numeric,
|
||||
},
|
||||
database_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
database_username: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
app_url: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
isUrl: helpers.withMessage(t('validation.invalid_url'), isUrl),
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(rules, databaseData.value)
|
||||
|
||||
function next() {
|
||||
v$.value.$touch()
|
||||
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
emit('submit-data', databaseData.value)
|
||||
}
|
||||
|
||||
function onChangeConnection() {
|
||||
v$.value.database_connection.$touch()
|
||||
|
||||
emit('on-change-driver', databaseData.value.database_connection)
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<form action="" @submit.prevent="next">
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:mb-6 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.app_url')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="v$.app_url.$error && v$.app_url.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.app_url"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.app_url.$error"
|
||||
type="text"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.connection')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.database_connection.$error &&
|
||||
v$.database_connection.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="databaseData.database_connection"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.database_connection.$error"
|
||||
:options="connections"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
@update:modelValue="onChangeConnection"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.port')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="v$.database_port.$error && v$.database_port.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_port"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.database_port.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.db_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="v$.database_name.$error && v$.database_name.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.database_name.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.username')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.database_username.$error &&
|
||||
v$.database_username.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_username"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.database_username.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:content-loading="isFetchingInitialData"
|
||||
:label="$t('wizard.database.password')"
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_password"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="password"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.host')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.database_hostname.$error &&
|
||||
v$.database_hostname.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_hostname"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.database_hostname.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
v-show="!isFetchingInitialData"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('wizard.save_cont') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, inject } from 'vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation'
|
||||
import { helpers, required, numeric } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const connections = reactive(['sqlite', 'mysql', 'pgsql'])
|
||||
const { t } = useI18n()
|
||||
const utils = inject('utils')
|
||||
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
const databaseData = computed(() => {
|
||||
return installationStore.currentDataBaseData
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
for (const key in databaseData.value) {
|
||||
if (props.configData.hasOwnProperty(key)) {
|
||||
databaseData.value[key] = props.configData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isUrl = (value) => utils.checkValidUrl(value)
|
||||
|
||||
const rules = {
|
||||
database_connection: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
database_hostname: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
database_port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric: numeric,
|
||||
},
|
||||
database_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
database_username: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
app_url: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
isUrl: helpers.withMessage(t('validation.invalid_url'), isUrl),
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(rules, databaseData.value)
|
||||
|
||||
function next() {
|
||||
v$.value.$touch()
|
||||
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
emit('submit-data', databaseData.value)
|
||||
}
|
||||
|
||||
function onChangeConnection() {
|
||||
v$.value.database_connection.$touch()
|
||||
emit('on-change-driver', databaseData.value.database_connection)
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<form action="" @submit.prevent="next">
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:mb-6 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.app_url')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="v$.app_url.$error && v$.app_url.$errors[0].$message"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.app_url"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.app_url.$error"
|
||||
type="text"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.connection')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.database_connection.$error &&
|
||||
v$.database_connection.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="databaseData.database_connection"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.database_connection.$error"
|
||||
:options="connections"
|
||||
:can-deselect="false"
|
||||
:can-clear="false"
|
||||
@update:modelValue="onChangeConnection"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.database.db_path')"
|
||||
:error="v$.database_name.$error && v$.database_name.$errors[0].$message"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="databaseData.database_name"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.database_name.$error"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
v-show="!isFetchingInitialData"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('wizard.save_cont') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, inject } from 'vue'
|
||||
import { useInstallationStore } from '@/scripts/admin/stores/installation'
|
||||
import { helpers, required } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
configData: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: Object,
|
||||
},
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const connections = reactive(['sqlite', 'mysql', 'pgsql'])
|
||||
const { t } = useI18n()
|
||||
const utils = inject('utils')
|
||||
|
||||
const installationStore = useInstallationStore()
|
||||
|
||||
const databaseData = computed(() => {
|
||||
return installationStore.currentDataBaseData
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
for (const key in databaseData.value) {
|
||||
if (props.configData.hasOwnProperty(key)) {
|
||||
databaseData.value[key] = props.configData[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isUrl = (value) => utils.checkValidUrl(value)
|
||||
|
||||
const rules = {
|
||||
database_connection: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
|
||||
database_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
|
||||
app_url: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
isUrl: helpers.withMessage(t('validation.invalid_url'), isUrl),
|
||||
},
|
||||
}
|
||||
|
||||
const v$ = useVuelidate(rules, databaseData.value)
|
||||
|
||||
function next() {
|
||||
v$.value.$touch()
|
||||
|
||||
if (v$.value.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
emit('submit-data', databaseData.value)
|
||||
}
|
||||
|
||||
function onChangeConnection() {
|
||||
v$.value.database_connection.$touch()
|
||||
emit('on-change-driver', databaseData.value.database_connection)
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.basicMailConfig.mail_driver.$error &&
|
||||
v$.basicMailConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="basicMailConfig.mail_driver"
|
||||
:invalid="v$.basicMailConfig.mail_driver.$error"
|
||||
:options="mailDriverStore.mail_drivers"
|
||||
:can-deselect="false"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.basicMailConfig.from_name.$error &&
|
||||
v$.basicMailConfig.from_name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="basicMailConfig.from_name"
|
||||
:invalid="v$.basicMailConfig.from_name.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="name"
|
||||
@input="v$.basicMailConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_mail')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.basicMailConfig.from_mail.$error &&
|
||||
v$.basicMailConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="basicMailConfig.from_mail"
|
||||
:invalid="v$.basicMailConfig.from_mail.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
@input="v$.basicMailConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { required, email, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
|
||||
const basicMailConfig = computed(() => {
|
||||
return mailDriverStore.basicMailConfig
|
||||
})
|
||||
|
||||
const mailDrivers = computed(() => {
|
||||
return mailDriverStore.mail_drivers
|
||||
})
|
||||
|
||||
basicMailConfig.value.mail_driver = 'mail'
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
basicMailConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
function saveEmailConfig() {
|
||||
v$.value.$touch()
|
||||
if (!v$.value.$invalid) {
|
||||
emit('submit-data', mailDriverStore.basicMailConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.basicMailConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore?.basicMailConfig?.mail_driver)
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_driver.$error &&
|
||||
v$.mailgunConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="mailgunConfig.mail_driver"
|
||||
:options="mailDriverStore.mail_drivers"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.mailgunConfig.mail_driver.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.mailgun_domain')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_mailgun_domain.$error &&
|
||||
v$.mailgunConfig.mail_mailgun_domain.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailgunConfig.mail_mailgun_domain"
|
||||
:invalid="v$.mailgunConfig.mail_mailgun_domain.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mailgun_domain"
|
||||
@input="v$.mailgunConfig.mail_mailgun_domain.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.mailgun_secret')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_mailgun_secret.$error &&
|
||||
v$.mailgunConfig.mail_mailgun_secret.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailgunConfig.mail_mailgun_secret"
|
||||
:invalid="v$.mailgunConfig.mail_mailgun_secret.$error"
|
||||
:type="getInputType"
|
||||
:content-loading="isFetchingInitialData"
|
||||
name="mailgun_secret"
|
||||
autocomplete="off"
|
||||
data-lpignore="true"
|
||||
@input="v$.mailgunConfig.mail_mailgun_secret.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
name="EyeOffIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
name="EyeIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.mailgun_endpoint')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.mail_mailgun_endpoint.$error &&
|
||||
v$.mailgunConfig.mail_mailgun_endpoint.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailgunConfig.mail_mailgun_endpoint"
|
||||
:invalid="v$.mailgunConfig.mail_mailgun_endpoint.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mailgun_endpoint"
|
||||
@input="v$.mailgunConfig.mail_mailgun_endpoint.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_mail')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.from_mail.$error &&
|
||||
v$.mailgunConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailgunConfig.from_mail"
|
||||
name="from_mail"
|
||||
type="text"
|
||||
:invalid="v$.mailgunConfig.from_mail.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@input="v$.mailgunConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.mailgunConfig.from_name.$error &&
|
||||
v$.mailgunConfig.from_name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="mailgunConfig.from_name"
|
||||
:invalid="v$.mailgunConfig.from_name.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_name"
|
||||
@input="v$.mailgunConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
:loading="loading"
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { required, email, helpers } from '@vuelidate/validators'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
let isShowPassword = ref(false)
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const mailgunConfig = computed(() => {
|
||||
return mailDriverStore.mailgunConfig
|
||||
})
|
||||
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
})
|
||||
|
||||
mailgunConfig.value.mail_driver = 'mailgun'
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
mailgunConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_mailgun_domain: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_mailgun_endpoint: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_mailgun_secret: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email,
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
function saveEmailConfig() {
|
||||
v$.value.$touch()
|
||||
if (!v$.value.$invalid) {
|
||||
emit('submit-data', mailDriverStore.mailgunConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.mailgunConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore.mailgunConfig.mail_driver)
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_driver.$error &&
|
||||
v$.sesConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="sesConfig.mail_driver"
|
||||
:options="mailDriverStore.mail_drivers"
|
||||
:can-deselect="false"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.sesConfig.mail_driver.$error"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.host')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_host.$error &&
|
||||
v$.sesConfig.mail_host.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="sesConfig.mail_host"
|
||||
:invalid="v$.sesConfig.mail_host.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
@input="v$.sesConfig.mail_host.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.port')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_port.$error &&
|
||||
v$.sesConfig.mail_port.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="sesConfig.mail_port"
|
||||
:invalid="v$.sesConfig.mail_port.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
@input="v$.sesConfig.mail_port.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.encryption')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_encryption.$error &&
|
||||
v$.sesConfig.mail_encryption.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model.trim="sesConfig.mail_encryption"
|
||||
:invalid="v$.sesConfig.mail_encryption.$error"
|
||||
:options="encryptions"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@input="v$.sesConfig.mail_encryption.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_mail')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.from_mail.$error &&
|
||||
v$.sesConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="sesConfig.from_mail"
|
||||
:invalid="v$.sesConfig.from_mail.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
@input="v$.sesConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_name')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.from_name.$error &&
|
||||
v$.sesConfig.from_name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="sesConfig.from_name"
|
||||
:invalid="v$.sesConfig.from_name.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="name"
|
||||
@input="v$.sesConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.ses_key')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_ses_key.$error &&
|
||||
v$.sesConfig.mail_ses_key.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="sesConfig.mail_ses_key"
|
||||
:invalid="v$.sesConfig.mail_ses_key.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_ses_key"
|
||||
@input="v$.sesConfig.mail_ses_key.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.ses_secret')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.sesConfig.mail_ses_secret.$error &&
|
||||
v$.sesConfig.mail_ses_secret.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="sesConfig.mail_ses_secret"
|
||||
:invalid="v$.sesConfig.mail_ses_secret.$error"
|
||||
:type="getInputType"
|
||||
:content-loading="isFetchingInitialData"
|
||||
name="mail_ses_secret"
|
||||
autocomplete="off"
|
||||
data-lpignore="true"
|
||||
@input="v$.sesConfig.mail_ses_secret.$touch()"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
name="EyeOffIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
name="EyeIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { required, email, numeric, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
|
||||
const props = defineProps({
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
const { t } = useI18n()
|
||||
const encryptions = reactive(['tls', 'ssl', 'starttls'])
|
||||
let isShowPassword = ref(false)
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
|
||||
const sesConfig = computed(() => {
|
||||
return mailDriverStore.sesConfig
|
||||
})
|
||||
|
||||
sesConfig.value.mail_driver = 'ses'
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
sesConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_host: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric,
|
||||
},
|
||||
mail_ses_key: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_ses_secret: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_encryption: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
async function saveEmailConfig() {
|
||||
v$.value.$touch()
|
||||
if (!v$.value.$invalid) {
|
||||
emit('submit-data', mailDriverStore.sesConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.sesConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore.sesConfig.mail_driver)
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<form @submit.prevent="saveEmailConfig">
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.driver')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.mail_driver.$error &&
|
||||
v$.smtpConfig.mail_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="smtpConfig.mail_driver"
|
||||
:options="mailDriverStore.mail_drivers"
|
||||
:can-deselect="false"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:invalid="v$.smtpConfig.mail_driver.$error"
|
||||
@update:modelValue="onChangeDriver"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.host')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
:error="
|
||||
v$.smtpConfig.mail_host.$error &&
|
||||
v$.smtpConfig.mail_host.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="smtpConfig.mail_host"
|
||||
:invalid="v$.smtpConfig.mail_host.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_host"
|
||||
@input="v$.smtpConfig.mail_host.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.username')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="smtpConfig.mail_username"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="db_name"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.password')"
|
||||
:content-loading="isFetchingInitialData"
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="smtpConfig.mail_password"
|
||||
:type="getInputType"
|
||||
:content-loading="isFetchingInitialData"
|
||||
autocomplete="off"
|
||||
data-lpignore="true"
|
||||
name="password"
|
||||
>
|
||||
<template #right>
|
||||
<BaseIcon
|
||||
v-if="isShowPassword"
|
||||
name="EyeOffIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
name="EyeIcon"
|
||||
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
/>
|
||||
</template>
|
||||
</BaseInput>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.port')"
|
||||
:error="
|
||||
v$.smtpConfig.mail_port.$error &&
|
||||
v$.smtpConfig.mail_port.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="smtpConfig.mail_port"
|
||||
:invalid="v$.smtpConfig.mail_port.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="mail_port"
|
||||
@input="v$.smtpConfig.mail_port.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.encryption')"
|
||||
:error="
|
||||
v$.smtpConfig.mail_encryption.$error &&
|
||||
v$.smtpConfig.mail_encryption.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model.trim="smtpConfig.mail_encryption"
|
||||
:options="encryptions"
|
||||
:can-deselect="false"
|
||||
:invalid="v$.smtpConfig.mail_encryption.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
@input="v$.smtpConfig.mail_encryption.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_mail')"
|
||||
:error="
|
||||
v$.smtpConfig.from_mail.$error &&
|
||||
v$.smtpConfig.from_mail.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="smtpConfig.from_mail"
|
||||
:invalid="v$.smtpConfig.from_mail.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_mail"
|
||||
@input="v$.smtpConfig.from_mail.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('wizard.mail.from_name')"
|
||||
:error="
|
||||
v$.smtpConfig.from_name.$error &&
|
||||
v$.smtpConfig.from_name.$errors[0].$message
|
||||
"
|
||||
:content-loading="isFetchingInitialData"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="smtpConfig.from_name"
|
||||
:invalid="v$.smtpConfig.from_name.$error"
|
||||
:content-loading="isFetchingInitialData"
|
||||
type="text"
|
||||
name="from_name"
|
||||
@input="v$.smtpConfig.from_name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
:loading="isSaving"
|
||||
:disabled="isSaving"
|
||||
:content-loading="isFetchingInitialData"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon v-if="!isSaving" name="SaveIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.save') }}
|
||||
</BaseButton>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { required, email, numeric, helpers } from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMailDriverStore } from '@/scripts/admin/stores/mail-driver'
|
||||
|
||||
const props = defineProps({
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInitialData: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit-data', 'on-change-driver'])
|
||||
|
||||
let isShowPassword = ref(false)
|
||||
const encryptions = reactive(['tls', 'ssl', 'starttls'])
|
||||
const { t } = useI18n()
|
||||
|
||||
const mailDriverStore = useMailDriverStore()
|
||||
|
||||
const smtpConfig = computed(() => {
|
||||
return mailDriverStore.smtpConfig
|
||||
})
|
||||
|
||||
const getInputType = computed(() => {
|
||||
if (isShowPassword.value) {
|
||||
return 'text'
|
||||
}
|
||||
return 'password'
|
||||
})
|
||||
|
||||
smtpConfig.value.mail_driver = 'smtp'
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
smtpConfig: {
|
||||
mail_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_host: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
mail_port: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
numeric: helpers.withMessage(t('validation.numbers_only'), numeric),
|
||||
},
|
||||
mail_encryption: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
from_mail: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
||||
},
|
||||
from_name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => mailDriverStore)
|
||||
)
|
||||
|
||||
async function saveEmailConfig() {
|
||||
v$.value.$touch()
|
||||
if (!v$.value.$invalid) {
|
||||
emit('submit-data', mailDriverStore.smtpConfig)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
v$.value.smtpConfig.mail_driver.$touch()
|
||||
emit('on-change-driver', mailDriverStore.smtpConfig.mail_driver)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user