build version 400

This commit is contained in:
Mohit Panjwani
2020-12-02 17:54:08 +05:30
parent 326508e567
commit 89ee58590c
963 changed files with 62887 additions and 48868 deletions

View File

@ -1,266 +0,0 @@
<template>
<div class="card-body">
<form action="" @submit.prevent="next()">
<p class="form-title">{{ $t('wizard.company_info') }}</p>
<p class="form-desc">{{ $t('wizard.company_info_desc') }}</p>
<div class="row mb-4">
<div class="col-md-6">
<label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label>
<div id="pick-avatar" class="image-upload-box">
<div class="overlay">
<font-awesome-icon class="white-icon" icon="camera"/>
</div>
<img v-if="previewLogo" :src="previewLogo" class="preview-logo">
<div v-else class="upload-content">
<font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/>
<p class="upload-text"> {{ $t('general.choose_file') }} </p>
</div>
</div>
</div>
<avatar-cropper
:labels="{ submit: 'Submit', cancel: 'Cancel'}"
:cropper-options="cropperOptions"
:output-options="cropperOutputOptions"
:output-quality="0.8"
:upload-handler="cropperHandler"
trigger="#pick-avatar"
@changed="setFileObject"
@error="handleUploadError"
/>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.company_name') }}</label><span class="text-danger"> *</span>
<base-input
:invalid="$v.companyData.name.$error"
v-model.trim="companyData.name"
type="text"
name="name"
@input="$v.companyData.name.$touch()"
/>
<div v-if="$v.companyData.name.$error">
<span v-if="!$v.companyData.name.required" class="text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.country') }}</label><span class="text-danger"> *</span>
<base-select
v-model="country"
:class="{'error': $v.companyData.country_id.$error }"
:options="countries"
:searchable="true"
:allow-empty="false"
:show-labels="false"
:placeholder="$t('general.select_country')"
track-by="id"
label="name"
/>
<div v-if="$v.companyData.country_id.$error">
<span v-if="!$v.companyData.country_id.required" class="text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.state') }}</label>
<base-input
v-model="companyData.state"
name="state"
type="text"
/>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.city') }}</label>
<base-input
v-model="companyData.city"
name="city"
type="text"
/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.address') }}</label>
<base-text-area
:invalid="$v.companyData.address_street_1.$error"
v-model.trim="companyData.address_street_1"
:placeholder="$t('general.street_1')"
name="billing_street1"
rows="2"
@input="$v.companyData.address_street_1.$touch()"
/>
<div v-if="$v.companyData.address_street_1.$error">
<span v-if="!$v.companyData.address_street_1.maxLength" class="text-danger">{{ $t('validation.description_maxlength') }}</span>
</div>
<base-text-area
:invalid="$v.companyData.address_street_2.$error"
v-model="companyData.address_street_2"
:placeholder="$t('general.street_2')"
name="billing_street2"
rows="2"
@input="$v.companyData.address_street_2.$touch()"
/>
<div v-if="$v.companyData.address_street_2.$error">
<span v-if="!$v.companyData.address_street_2.maxLength" class="text-danger">{{ $t('validation.description_maxlength') }}</span>
</div>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-12">
<label class="form-label">{{ $t('wizard.zip_code') }}</label>
<base-input
v-model.trim="companyData.zip"
type="text"
name="zip"
/>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label class="form-label">{{ $t('wizard.phone') }}</label>
<base-input
v-model.trim="companyData.phone"
type="text"
name="phone"
/>
</div>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right"
icon="save"
color="theme"
type="submit"
>
{{ $t('wizard.save_cont') }}
</base-button>
</form>
</div>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import AvatarCropper from 'vue-avatar-cropper'
import { validationMixin } from 'vuelidate'
const { required, maxLength } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect,
AvatarCropper
},
mixins: [validationMixin],
data () {
return {
cropperOutputOptions: {
width: 150,
height: 150
},
cropperOptions: {
autoCropArea: 1,
viewMode: 0,
movable: true,
zoomable: true
},
companyData: {
logo: '',
name: null,
address_street_1: '',
address_street_2: '',
city: '',
state: '',
country_id: '',
zip: '',
phone: ''
},
loading: false,
step: 1,
countries: [],
country: null,
previewLogo: null
}
},
validations: {
companyData: {
name: {
required
},
country_id: {
required
},
address_street_1: {
maxLength: maxLength(255)
},
address_street_2: {
maxLength: maxLength(255)
}
}
},
watch: {
country ({ id }) {
this.companyData.country_id = id
return true
}
},
mounted () {
this.fetchCountry()
},
methods: {
cropperHandler (cropper) {
this.previewLogo = cropper.getCroppedCanvas().toDataURL(this.cropperOutputMime)
},
setFileObject (file) {
this.fileObject = file
},
handleUploadError (message, type, xhr) {
window.toastr['error']('Oops! Something went wrong...')
},
async next () {
this.$v.companyData.$touch()
if (this.$v.companyData.$invalid) {
return true
}
this.loading = true
let response = await window.axios.post('/api/admin/onboarding/company', this.companyData)
if (response.data) {
if (this.fileObject && this.previewLogo) {
let logoData = new FormData()
logoData.append('company_logo', JSON.stringify({
name: this.fileObject.name,
data: this.previewLogo
}))
await axios.post('/api/admin/onboarding/company/upload-logo', logoData, {
headers: {
'Content-Type': 'multipart/form-data',
'company': response.data.user.company.id
}
})
}
this.$emit('next')
this.loading = false
}
},
onFileChange (e) {
var input = event.target
this.companyData.logo = input.files[0]
if (input.files && input.files[0]) {
var reader = new FileReader()
reader.onload = (e) => {
this.previewLogo = e.target.result
}
reader.readAsDataURL(input.files[0])
}
},
async fetchCountry () {
let res = await window.axios.get('/api/countries')
if (res) {
this.countries = res.data.countries
}
}
}
}
</script>

View File

@ -1,219 +0,0 @@
<template>
<div class="card-body">
<form action="" @submit.prevent="next()">
<p class="form-title">{{ $t('wizard.database.database') }}</p>
<p class="form-desc">{{ $t('wizard.database.desc') }}</p>
<div class="row mt-5">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.database.app_url') }}</label>
<span class="text-danger"> * </span>
<base-input
:invalid="$v.databaseData.app_url.$error"
v-model.trim="databaseData.app_url"
type="text"
name="name"
@input="$v.databaseData.app_url.$touch()"
/>
<div v-if="$v.databaseData.app_url.$error">
<span v-if="!$v.databaseData.app_url.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.databaseData.app_url.isUrl" class="text-danger">
{{ $tc('validation.invalid_url') }}
</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.database.connection') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model="databaseData.database_connection"
:invalid="$v.databaseData.database_connection.$error"
:options="connections"
:searchable="true"
:show-labels="false"
@change="$v.databaseData.database_connection.$touch()"
/>
<div v-if="$v.databaseData.database_connection.$error">
<span v-if="!$v.databaseData.database_connection.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.database.port') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.databaseData.database_port.$error"
v-model.trim="databaseData.database_port"
type="text"
name="database_port"
@input="$v.databaseData.database_port.$touch()"
/>
<div v-if="$v.databaseData.database_port.$error">
<span v-if="!$v.databaseData.database_port.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.databaseData.database_port.numeric" class="text-danger">
{{ $tc('validation.numbers_only') }}
</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.database.db_name') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.databaseData.database_name.$error"
v-model.trim="databaseData.database_name"
type="text"
name="database_name"
@input="$v.databaseData.database_name.$touch()"
/>
<div v-if="$v.databaseData.database_name.$error">
<span v-if="!$v.databaseData.database_name.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.database.username') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.databaseData.database_username.$error"
v-model.trim="databaseData.database_username"
type="text"
name="database_username"
@input="$v.databaseData.database_username.$touch()"
/>
<div v-if="$v.databaseData.database_username.$error">
<span v-if="!$v.databaseData.database_username.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.database.password') }}</label>
<base-input
v-model.trim="databaseData.database_password"
type="password"
name="name"
/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.database.host') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.databaseData.database_hostname.$error"
v-model.trim="databaseData.database_hostname"
type="text"
name="database_hostname"
@input="$v.databaseData.database_hostname.$touch()"
/>
<div v-if="$v.databaseData.database_hostname.$error">
<span v-if="!$v.databaseData.database_hostname.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-5"
icon="save"
color="theme"
type="submit"
>
{{ $t('wizard.save_cont') }}
</base-button>
</form>
</div>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
data () {
return {
databaseData: {
database_connection: 'mysql',
database_hostname: '127.0.0.1',
database_port: '3306',
database_name: null,
database_username: null,
database_password: null,
app_url: window.location.origin
},
loading: false,
connections: [
'sqlite',
'mysql',
'pgsql',
'sqlsrv'
]
}
},
validations: {
databaseData: {
database_connection: {
required
},
database_hostname: {
required
},
database_port: {
required,
numeric
},
database_name: {
required
},
database_username: {
required
},
app_url: {
required,
isUrl (val) {
return this.$utils.checkValidUrl(val)
}
}
}
},
methods: {
async next () {
this.$v.databaseData.$touch()
if (this.$v.databaseData.$invalid) {
return true
}
this.loading = true
try {
let response = await window.axios.post('/api/admin/onboarding/environment/database', this.databaseData)
if (response.data.success) {
this.$emit('next')
window.toastr['success'](this.$t('wizard.success.' + response.data.success))
return true
} else if (response.data.error) {
window.toastr['error'](this.$t('wizard.errors.' + response.data.error))
} else if (response.data.error_message) {
window.toastr['error'](response.data.error_message)
}
} catch (e) {
window.toastr['error'](e.response.data.message)
} finally {
this.loading = false
}
}
}
}
</script>

View File

@ -1,78 +0,0 @@
<template>
<div class="card-body">
<form action="" @submit.prevent="next()">
<p class="form-title">{{ $t('wizard.mail.mail_config') }}</p>
<p class="form-desc">{{ $t('wizard.mail.mail_config_desc') }}</p>
<component
:is="mail_driver"
:config-data="mailConfigData"
:loading="loading"
:mail-drivers="mail_drivers"
@on-change-driver="(val) => mail_driver = mailConfigData.mail_driver = val"
@submit-data="next"
/>
</form>
</div>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
import Smtp from './mailDriver/Smtp'
import Mailgun from './mailDriver/Mailgun'
import Ses from './mailDriver/Ses'
import Basic from './mailDriver/Basic'
export default {
components: {
MultiSelect,
Smtp,
Mailgun,
Ses,
sendmail: Basic,
mail: Basic
},
mixins: [validationMixin],
data () {
return {
mailConfigData: {
mail_driver: 'mail'
},
mail_driver: 'mail',
loading: false,
mail_drivers: []
}
},
created () {
this.getMailDrivers()
},
methods: {
async getMailDrivers () {
this.loading = true
let response = await window.axios.get('/api/admin/onboarding/environment/mail')
if (response.data) {
this.mail_drivers = response.data
this.loading = false
}
},
async next (mailConfigData) {
this.loading = true
try {
let response = await window.axios.post('/api/admin/onboarding/environment/mail', mailConfigData)
if (response.data.success) {
this.$emit('next')
window.toastr['success'](this.$t('wizard.success.' + response.data.success))
} else {
window.toastr['error'](this.$t('wizard.errors.' + response.data.error))
}
this.loading = false
return true
} catch (e) {
window.toastr['error']('Something went wrong')
}
}
}
}
</script>

View File

@ -1,113 +0,0 @@
<template>
<div class="wizard">
<div class="step-indicator">
<img
id="logo-crater"
src="/assets/img/crater-logo.png"
alt="Crater Logo"
class="logo-main"
>
<div class="indicator-line">
<div class="center">
<div class="steps" :class="{'active': step === 1, 'completed': step > 1}">
<font-awesome-icon v-if="step > 1" icon="check" class="icon-check"/>
</div>
<div class="steps" :class="{'active': step === 2, 'completed': step > 2}">
<font-awesome-icon v-if="step > 2" icon="check" class="icon-check"/>
</div>
<div class="steps" :class="{'active': step === 3, 'completed': step > 3}">
<font-awesome-icon v-if="step > 3" icon="check" class="icon-check"/>
</div>
<div class="steps" :class="{'active': step === 4, 'completed': step > 4}">
<font-awesome-icon v-if="step > 4" icon="check" class="icon-check"/>
</div>
<div class="steps" :class="{'active': step === 5, 'completed': step > 5}">
<font-awesome-icon v-if="step > 5" icon="check" class="icon-check"/>
</div>
<div class="steps" :class="{'active': step === 6, 'completed': step > 6}">
<font-awesome-icon v-if="step > 6" icon="check" class="icon-check"/>
</div>
<div class="steps" :class="{'active': step === 7, 'completed': step > 7}">
<font-awesome-icon v-if="step > 7" icon="check" class="icon-check"/>
</div>
</div>
</div>
</div>
<div class="form-content">
<div class="card wizard-card">
<component
:is="tab"
@next="setTab"
/>
</div>
</div>
</div>
</template>
<script>
import SystemRequirement from './SystemRequirement'
import Permission from './Permission'
import Database from './Database'
import EmailConfiguration from './EmailConfiguration'
import UserProfile from './UserProfile'
import CompanyInfo from './CompanyInfo'
import Settings from './Settings'
export default {
components: {
step_1: SystemRequirement,
step_2: Permission,
step_3: Database,
step_4: EmailConfiguration,
step_5: UserProfile,
step_6: CompanyInfo,
step_7: Settings
},
data () {
return {
loading: false,
tab: 'step_1',
step: 1
}
},
created () {
this.getOnboardingData()
},
methods: {
async getOnboardingData () {
let response = await window.axios.get('/api/admin/onboarding')
if (response.data) {
if (response.data.profile_complete === 'COMPLETED') {
this.$router.push('/admin/dashboard')
return
}
let dbStep = parseInt(response.data.profile_complete)
if (dbStep) {
this.step = dbStep + 1
this.tab = `step_${dbStep + 1}`
}
this.languages = response.data.languages
this.currencies = response.data.currencies
this.dateFormats = response.data.date_formats
this.timeZones = response.data.time_zones
// this.settingData.currency = this.currencies.find(currency => currency.id === 1)
// this.settingData.language = this.languages.find(language => language.code === 'en')
// this.settingData.dateFormat = this.dateFormats.find(dateFormat => dateFormat.value === 'd M Y')
}
},
setTab (data) {
this.step++
if (this.step <= 7) {
this.tab = 'step_' + this.step
} else {
// window.location.reload()
}
}
}
}
</script>

View File

@ -1,86 +0,0 @@
<template>
<div class="card-body permissions">
<p class="form-title">{{ $t('wizard.permissions.permissions') }}</p>
<p class="form-desc">{{ $t('wizard.permissions.permission_desc') }}</p>
<div class="d-flex justify-content-start">
<div class="lists col-md-6">
<div
v-for="(permission, index) in permissions"
:key="index"
class="row list-items"
>
<div class="col-sm-9 left-item">
{{ permission.folder }}
</div>
<div class="col-sm-3 right-item">
<span v-if="permission.isSet" class="verified"/>
<span v-else class="not-verified"/>
<span>{{ permission.permission }}</span>
</div>
</div>
</div>
</div>
<base-button
v-if="isContinue"
class="pull-right mt-5"
icon="arrow-right"
right-icon
color="theme"
@click="next"
>
{{ $t('wizard.continue') }}
</base-button>
</div>
</template>
<script>
export default {
data () {
return {
loading: false,
permissions: [],
errors: false,
isContinue: false
}
},
created () {
this.getPermissions()
},
methods: {
async getPermissions () {
this.loading = true
let response = await window.axios.get('/api/admin/onboarding/permissions', this.profileData)
if (response.data) {
this.permissions = response.data.permissions.permissions
this.errors = response.data.permissions.errors
let self = this
if (this.errors) {
swal({
title: this.$t('wizard.permissions.permission_confirm_title'),
text: this.$t('wizard.permissions.permission_confirm_desc'),
icon: 'warning',
buttons: true,
dangerMode: true
}).then(async (willConfirm) => {
if (willConfirm) {
self.isContinue = true
}
})
} else {
this.isContinue = true
}
this.loading = false
}
},
async next () {
this.loading = true
await this.$emit('next')
this.loading = false
}
}
}
</script>

View File

@ -1,219 +0,0 @@
<template>
<div class="card-body">
<form action="" @submit.prevent="next()">
<p class="form-title">{{ $t('wizard.preferences') }}</p>
<p class="form-desc">{{ $t('wizard.preferences_desc') }}</p>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.currency') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model="settingData.currency"
:class="{'error': $v.settingData.currency.$error }"
:options="currencies"
:custom-label="currencyNameWithCode"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.currencies.select_currency')"
track-by="id"
label="name"
@input="$v.settingData.currency.$touch()"
/>
<div v-if="$v.settingData.currency.$error">
<span v-if="!$v.settingData.currency.required" class="text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.language') }}</label><span class="text-danger"> *</span>
<base-select
v-model="settingData.language"
:class="{'error': $v.settingData.language.$error }"
:options="languages"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_language')"
label="name"
@input="$v.settingData.language.$touch()"
/>
<div v-if="$v.settingData.language.$error">
<span v-if="!$v.settingData.language.required" class="text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.date_format') }}</label><span class="text-danger"> *</span>
<base-select
v-model="settingData.dateFormat"
:class="{'error': $v.settingData.dateFormat.$error }"
:options="dateFormats"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_date_formate')"
label="display_date"
@input="$v.settingData.dateFormat.$touch()"
/>
<div v-if="$v.settingData.dateFormat.$error">
<span v-if="!$v.settingData.dateFormat.required" class="text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.time_zone') }}</label><span class="text-danger"> *</span>
<base-select
v-model="settingData.timeZone"
:class="{'error': $v.settingData.timeZone.$error }"
:options="timeZones"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_date_formate')"
label="key"
@input="$v.settingData.timeZone.$touch()"
/>
<div v-if="$v.settingData.timeZone.$error">
<span v-if="!$v.settingData.timeZone.required" class="text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.fiscal_year') }}</label><span class="text-danger"> *</span>
<base-select
v-model="settingData.fiscalYear"
:class="{'error': $v.settingData.fiscalYear.$error }"
:options="fiscalYears"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_financial_year')"
label="key"
@input="$v.settingData.fiscalYear.$touch()"
/>
<div v-if="$v.settingData.fiscalYear.$error">
<span v-if="!$v.settingData.fiscalYear.required" class="text-danger">{{ $tc('customers.errors.required') }}</span>
</div>
</div>
</div>
<base-button :loading="loading" class="pull-right" icon="save" color="theme" type="submit">
{{ $t('wizard.save_cont') }}
</base-button>
</form>
</div>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
import Ls from '../../services/ls'
import { mapActions } from 'vuex'
const { required, minLength, email } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
data () {
return {
settingData: {
language: null,
currency: null,
timeZone: null,
dateFormat: null,
fiscalYear: null
},
loading: false,
step: 1,
languages: [],
currencies: [],
timeZones: [],
dateFormats: [],
fiscalYears: []
}
},
validations: {
settingData: {
currency: {
required
},
language: {
required
},
dateFormat: {
required
},
timeZone: {
required
},
fiscalYear: {
required
}
}
},
mounted () {
this.getOnboardingData()
},
methods: {
currencyNameWithCode ({name, code}) {
return `${code} - ${name}`
},
...mapActions('auth', [
'loginOnBoardingUser'
]),
async getOnboardingData () {
let response = await window.axios.get('/api/admin/onboarding')
if (response.data) {
if (response.data.profile_complete === 'COMPLETED') {
this.$router.push('/admin/dashboard')
return
}
let dbStep = parseInt(response.data.profile_complete)
if (dbStep) {
this.step = dbStep + 1
}
this.languages = response.data.languages
this.currencies = response.data.currencies
this.dateFormats = response.data.date_formats
this.timeZones = response.data.time_zones
this.fiscalYears = response.data.fiscal_years
this.settingData.currency = this.currencies.find(currency => currency.id === 1)
this.settingData.language = this.languages.find(language => language.code === 'en')
this.settingData.dateFormat = response.data.date_formats.find(dateFormat => dateFormat.carbon_format_value == 'd M Y')
this.settingData.timeZone = this.timeZones.find(timeZone => timeZone.value === 'UTC')
this.settingData.fiscalYear = this.fiscalYears.find(fiscalYear => fiscalYear.value === '1-12')
}
},
async next () {
this.$v.settingData.$touch()
if (this.$v.settingData.$invalid) {
return true
}
this.loading = true
let data = {
currency: this.settingData.currency.id,
time_zone: this.settingData.timeZone.value,
language: this.settingData.language.code,
fiscal_year: this.settingData.fiscalYear.value,
carbon_date_format: this.settingData.dateFormat.carbon_format_value,
moment_date_format: this.settingData.dateFormat.moment_format_value
}
let response = await window.axios.post('/api/admin/onboarding/settings', data)
if (response.data) {
// this.$emit('next')
this.loading = false
Ls.set('auth.token', response.data.token)
this.loginOnBoardingUser(response.data.token)
window.toastr['success']('Login Successful')
this.$router.push('/admin/dashboard')
}
}
}
}
</script>

View File

@ -1,112 +0,0 @@
<template>
<div class="card-body">
<p class="form-title">{{ $t('wizard.req.system_req') }}</p>
<p class="form-desc">{{ $t('wizard.req.system_req_desc') }}</p>
<div v-if="phpSupportInfo" class="d-flex justify-content-start">
<div class="col-md-6">
<div class="row list-items">
<div class="col-md-9 left-item">
{{ $t('wizard.req.php_req_version', { version: phpSupportInfo.minimum }) }}
</div>
<div class="col-md-3 right-item justify-content-end">
{{ phpSupportInfo.current }}
<span v-if="phpSupportInfo.supported" class="verified"/>
<span v-else class="not-verified"/>
</div>
</div>
</div>
</div>
<div v-if="requirements" class="d-flex justify-content-start">
<div class="col-md-6">
<div
v-for="(requirement, index) in requirements"
:key="index"
class="row list-items"
>
<div class="col-md-9 left-item">
{{ index }}
</div>
<div class="col-md-3 right-item justify-content-end">
<span v-if="requirement" class="verified"/>
<span v-else class="not-verified"/>
</div>
</div>
</div>
</div>
<base-button
v-if="hasNext"
:loading="loading"
class="pull-right mt-4"
icon="arrow-right"
color="theme"
right-icon
@click="next"
>
{{ $t('wizard.continue') }}
</base-button>
<base-button
v-if="!requirements"
:loading="loading"
class="pull-right mt-4"
color="theme"
@click="getRequirements"
>
{{ $t('wizard.req.check_req') }}
</base-button>
</div>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
data () {
return {
requirements: null,
phpSupportInfo: null,
loading: false,
isShow: true
}
},
computed: {
hasNext () {
if (this.requirements) {
let isRequired = true
for (const key in this.requirements) {
if (!this.requirements[key]) {
isRequired = false
}
}
return this.requirements && this.phpSupportInfo.supported && isRequired
}
return false
}
},
methods: {
listToggle () {
this.isShow = !this.isShow
},
async getRequirements () {
this.loading = true
let response = await window.axios.get('/api/admin/onboarding/requirements', this.profileData)
if (response.data) {
this.requirements = response.data.requirements.requirements.php
this.phpSupportInfo = response.data.phpSupportInfo
this.loading = false
}
},
async next () {
this.loading = true
await this.$emit('next')
this.loading = false
}
}
}
</script>

View File

@ -1,204 +0,0 @@
<template>
<div class="card-body">
<form action="" @submit.prevent="next()">
<p class="form-title">{{ $t('wizard.account_info') }}</p>
<p class="form-desc">{{ $t('wizard.account_info_desc') }}</p>
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label">{{ $tc('settings.account_settings.profile_picture') }}</label>
<div id="pick-avatar" class="image-upload-box avatar-upload">
<div class="overlay">
<font-awesome-icon class="white-icon" icon="camera"/>
</div>
<img v-if="previewAvatar" :src="previewAvatar" class="preview-logo">
<div v-else class="upload-content">
<font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/>
<p class="upload-text"> {{ $tc('general.choose_file') }} </p>
</div>
</div>
</div>
<avatar-cropper
:labels="{ submit: 'submit', cancel: 'Cancel'}"
:cropper-options="cropperOptions"
:output-options="cropperOutputOptions"
:output-quality="0.8"
:upload-handler="cropperHandler"
trigger="#pick-avatar"
@changed="setFileObject"
@error="handleUploadError"
/>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.name') }}</label><span class="text-danger"> *</span>
<base-input
:invalid="$v.profileData.name.$error"
v-model.trim="profileData.name"
type="text"
name="name"
@input="$v.profileData.name.$touch()"
/>
<div v-if="$v.profileData.name.$error">
<span v-if="!$v.profileData.name.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.profileData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.profileData.name.$params.minLength.min, { count: $v.profileData.name.$params.minLength.min }) }} </span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.email') }}</label><span class="text-danger"> *</span>
<base-input
:invalid="$v.profileData.email.$error"
v-model.trim="profileData.email"
type="text"
name="email"
@input="$v.profileData.email.$touch()"
/>
<div v-if="$v.profileData.email.$error">
<span v-if="!$v.profileData.email.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.profileData.email.email" class="text-danger">{{ $tc('validation.email_incorrect') }}</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.password') }}</label><span class="text-danger"> *</span>
<base-input
:invalid="$v.profileData.password.$error"
v-model.trim="profileData.password"
type="password"
name="password"
@input="$v.profileData.password.$touch()"
/>
<div v-if="$v.profileData.password.$error">
<span v-if="!$v.profileData.password.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.profileData.password.minLength" class="text-danger"> {{ $tc('validation.password_min_length', $v.profileData.password.$params.minLength.min, {count: $v.profileData.password.$params.minLength.min}) }} </span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">{{ $t('wizard.confirm_password') }}</label><span class="text-danger"> *</span>
<base-input
:invalid="$v.profileData.confirm_password.$error"
v-model.trim="profileData.confirm_password"
type="password"
name="confirm_password"
@input="$v.profileData.confirm_password.$touch()"
/>
<div v-if="$v.profileData.confirm_password.$error">
<span v-if="!$v.profileData.confirm_password.sameAsPassword" class="text-danger">{{ $tc('validation.password_incorrect') }}</span>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('wizard.save_cont') }}
</base-button>
</form>
</div>
</template>
<script>
import AvatarCropper from 'vue-avatar-cropper'
import { validationMixin } from 'vuelidate'
import { mapActions } from 'vuex'
const { required, requiredIf, sameAs, minLength, email } = require('vuelidate/lib/validators')
export default {
components: {
AvatarCropper
},
mixins: [validationMixin],
data () {
return {
cropperOutputOptions: {
width: 150,
height: 150
},
cropperOptions: {
autoCropArea: 1,
viewMode: 0,
movable: true,
zoomable: true
},
profileData: {
name: null,
email: null,
password: null,
confirm_password: null
},
loading: false,
previewAvatar: '/images/default-avatar.jpg',
fileObject: null
}
},
validations: {
profileData: {
name: {
required,
minLength: minLength(3)
},
email: {
email,
required
},
password: {
required,
minLength: minLength(8)
},
confirm_password: {
required: requiredIf('isRequired'),
sameAsPassword: sameAs('password')
}
}
},
computed: {
isRequired () {
if (this.profileData.password === null || this.profileData.password === undefined || this.profileData.password === '') {
return false
}
return true
}
},
methods: {
...mapActions('userProfile', [
'uploadOnboardAvatar'
]),
cropperHandler (cropper) {
this.previewAvatar = cropper.getCroppedCanvas().toDataURL(this.cropperOutputMime)
},
setFileObject (file) {
this.fileObject = file
},
handleUploadError (message, type, xhr) {
window.toastr['error']('Oops! Something went wrong...')
},
async next () {
this.$v.profileData.$touch()
if (this.$v.profileData.$invalid) {
return true
}
this.loading = true
let response = await window.axios.post('/api/admin/onboarding/profile', this.profileData)
console.log('user_id', response.data.user.id)
if (response.data) {
if (this.fileObject && this.previewAvatar) {
let avatarData = new FormData()
avatarData.append('admin_avatar', JSON.stringify({
name: this.fileObject.name,
data: this.previewAvatar,
id: response.data.user.id
}))
this.uploadOnboardAvatar(avatarData)
}
this.$emit('next')
this.loading = false
}
return true
}
}
}
</script>

View File

@ -0,0 +1,89 @@
<template>
<div
class="flex flex-col items-center justify-between w-full h-32 pt-10 step-indicator"
>
<img
id="logo-crater"
src="/assets/img/crater-logo.png"
alt="Crater Logo"
class="h-12"
/>
<sw-wizard
:steps="7"
:currentStep.sync="step"
:allow-navigation-redirect="false"
>
<component :is="tab" @next="setTab" />
</sw-wizard>
</div>
</template>
<script>
import SystemRequirement from './WizardSystemRequirementStep'
import Permission from './WizardPermissionStep'
import Database from './WizardDatabaseStep'
import EmailConfiguration from './WizardEmailConfigStep'
import UserProfile from './WizardUserProfileStep'
import CompanyInfo from './WizardCompanyInfoStep'
import Settings from './WizardSettingsStep'
export default {
components: {
step_1: SystemRequirement,
step_2: Permission,
step_3: Database,
step_4: EmailConfiguration,
step_5: UserProfile,
step_6: CompanyInfo,
step_7: Settings,
},
data() {
return {
profile_complete: null,
loading: false,
tab: 'step_1',
step: 1,
}
},
created() {
this.getProfileComplete()
},
methods: {
async getProfileComplete() {
let response = await axios.get('/api/v1/onboarding/wizard-step')
if (response.data.profile_complete === 'COMPLETED') {
this.$router.push('/admin/dashboard')
return
}
let dbStep = parseInt(response.data.profile_complete)
if (dbStep) {
this.step = dbStep + 1
this.tab = `step_${dbStep + 1}`
}
},
async setProfileComplete(data) {
let status = {
profile_complete: data,
}
let response = await axios.post('/api/v1/onboarding/wizard-step', status)
},
async setTab(data) {
if (data) {
this.setProfileComplete(data)
}
this.step++
if (this.step <= 7) {
this.tab = 'step_' + this.step
} else {
// window.location.reload()
}
},
},
}
</script>

View File

@ -0,0 +1,324 @@
<template>
<sw-wizard-step
:title="$t('wizard.company_info')"
:description="$t('wizard.company_info_desc')"
>
<base-loader v-if="isFetching" :show-bg-overlay="true" />
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group :label="$tc('settings.company_info.company_logo')">
<div
id="logo-box"
class="relative flex items-center justify-center h-24 p-5 mt-2 bg-transparent border-2 border-gray-200 border-dashed rounded-md image-upload-box"
>
<img
v-if="previewLogo"
:src="previewLogo"
class="absolute opacity-100 preview-logo"
style="max-height: 80%; animation: fadeIn 2s ease"
/>
<div v-else class="flex flex-col items-center">
<cloud-upload-icon
class="h-5 mb-2 text-xl leading-6 text-gray-400"
/>
<p class="text-xs leading-4 text-center text-gray-400">
Drag a file here or
<span id="pick-avatar" class="cursor-pointer text-primary-500"
>browse</span
>
to choose a file
</p>
</div>
</div>
<sw-avatar
trigger="#logo-box"
:preview-avatar="previewLogo"
@changed="onChange"
@uploadHandler="onUploadHandler"
@handleUploadError="onHandleUploadError"
>
<template v-slot:icon>
<cloud-upload-icon
class="h-5 mb-2 text-xl leading-6 text-gray-400"
/>
</template>
</sw-avatar>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.company_name')"
:error="companyNameError"
required
>
<sw-input
:invalid="$v.companyData.name.$error"
v-model.trim="companyData.name"
type="text"
name="name"
@input="$v.companyData.name.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.country')"
:error="countryError"
required
>
<sw-select
v-model="country"
:class="{ error: $v.companyData.country_id.$error }"
:options="countries"
:searchable="true"
:allow-empty="false"
:show-labels="false"
:placeholder="$t('general.select_country')"
track-by="id"
label="name"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group :label="$t('wizard.state')">
<sw-input v-model="companyData.state" name="state" type="text" />
</sw-input-group>
<sw-input-group :label="$t('wizard.city')">
<sw-input v-model="companyData.city" name="city" type="text" />
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<div>
<sw-input-group
:label="$t('wizard.address')"
:error="address1Error"
>
<sw-textarea
:invalid="$v.companyData.address_street_1.$error"
v-model.trim="companyData.address_street_1"
:placeholder="$t('general.street_1')"
name="billing_street1"
rows="2"
@input="$v.companyData.address_street_1.$touch()"
/>
</sw-input-group>
<sw-input-group :error="address2Error" class="mt-1 lg:mt-2 md:mt-2">
<sw-textarea
:invalid="$v.companyData.address_street_2.$error"
v-model="companyData.address_street_2"
:placeholder="$t('general.street_2')"
name="billing_street2"
rows="2"
@input="$v.companyData.address_street_2.$touch()"
/>
</sw-input-group>
</div>
<div>
<sw-input-group :label="$t('wizard.zip_code')">
<sw-input v-model.trim="companyData.zip" type="text" name="zip" />
</sw-input-group>
<sw-input-group :label="$t('wizard.phone')" class="mt-4">
<sw-input
v-model.trim="companyData.phone"
type="text"
name="phone"
/>
</sw-input-group>
</div>
</div>
<sw-button
:loading="isLoading"
:disabled="isLoading"
class="mt-4"
variant="primary"
type="submit"
>
<save-icon v-if="!isLoading" class="mr-2" />
{{ $t('wizard.save_cont') }}
</sw-button>
</div>
</form>
</sw-wizard-step>
</template>
<script>
import { CloudUploadIcon } from '@vue-hero-icons/solid'
import { mapActions } from 'vuex'
const { required, maxLength } = require('vuelidate/lib/validators')
export default {
components: {
CloudUploadIcon
},
data() {
return {
companyData: {
logo: '',
name: null,
address_street_1: '',
address_street_2: '',
city: '',
state: '',
country_id: '',
zip: '',
phone: '',
},
isLoading: false,
isFetching: false,
step: 1,
countries: [],
country: null,
previewLogo: null,
fileObject: null,
cropperOutputMime: '',
}
},
validations: {
companyData: {
name: {
required,
},
country_id: {
required,
},
address_street_1: {
maxLength: maxLength(255),
},
address_street_2: {
maxLength: maxLength(255),
},
},
},
watch: {
country({ id }) {
this.companyData.country_id = id
return true
},
},
computed: {
companyNameError() {
if (!this.$v.companyData.name.$error) {
return ''
}
if (!this.$v.companyData.name.required) {
return this.$tc('validation.required')
}
},
countryError() {
if (!this.$v.companyData.country_id.$error) {
return ''
}
if (!this.$v.companyData.country_id.required) {
return this.$tc('validation.required')
}
},
address1Error() {
if (!this.$v.companyData.address_street_1.$error) {
return ''
}
if (!this.$v.companyData.address_street_1.maxLength) {
return this.$t('validation.description_maxlength')
}
},
address2Error() {
if (!this.$v.companyData.address_street_2.$error) {
return ''
}
if (!this.$v.companyData.address_street_2.maxLength) {
return this.$t('validation.description_maxlength')
}
},
},
mounted() {
this.fetchCountries()
},
methods: {
...mapActions('company', ['setSelectedCompany']),
onUploadHandler(cropper) {
this.previewLogo = cropper
.getCroppedCanvas()
.toDataURL(this.cropperOutputMime)
},
onHandleUploadError() {
window.toastr['error']('Oops! Something went wrong...')
},
onChange(file) {
this.cropperOutputMime = file.type
this.fileObject = file
},
async next() {
this.$v.companyData.$touch()
if (this.$v.companyData.$invalid) {
return true
}
this.isLoading = true
let response = await window.axios.put('/api/v1/company', this.companyData)
if (response.data) {
this.setSelectedCompany(response.data.company)
if (this.fileObject && this.previewLogo) {
let logoData = new FormData()
logoData.append(
'company_logo',
JSON.stringify({
name: this.fileObject.name,
data: this.previewLogo,
})
)
await axios.post('/api/v1/company/upload-logo', logoData, {
headers: {
'Content-Type': 'multipart/form-data',
company: response.data.company.id,
},
})
}
this.$emit('next', 6)
this.isLoading = false
}
},
onFileChange(e) {
var input = event.target
this.companyData.logo = input.files[0]
if (input.files && input.files[0]) {
var reader = new FileReader()
reader.onload = (e) => {
this.previewLogo = e.target.result
}
reader.readAsDataURL(input.files[0])
}
},
async fetchCountries() {
this.isFetching = true
let res = await window.axios.get('/api/v1/countries')
if (res) {
this.countries = res.data.countries
}
this.isFetching = false
},
},
}
</script>

View File

@ -0,0 +1,103 @@
<template>
<sw-wizard-step
:title="$t('wizard.database.database')"
:description="$t('wizard.database.desc')"
>
<base-loader v-if="isFetching" :show-bg-overlay="true" />
<component
:is="database_connection"
:config-data="databaseData"
:is-loading="isLoading"
:is-fetching="isFetching"
@on-change-driver="getDatabaseConfig"
@submit-data="next"
/>
</sw-wizard-step>
</template>
<script>
import { validationMixin } from 'vuelidate'
import Mysql from './database/MysqlDatabase'
import Pgsql from './database/PgsqlDatabase'
import Sqlite from './database/SqliteDatabase'
import Sqlsrv from './database/SqlsrvDatabase'
export default {
components: {
Mysql,
Pgsql,
Sqlite,
Sqlsrv,
},
data() {
return {
databaseData: {
database_connection: 'mysql',
},
isLoading: false,
isFetching: false,
database_connection: 'mysql',
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
},
created() {
this.getDatabaseConfig(this.database_connection)
},
methods: {
async getDatabaseConfig(connection) {
this.isLoading = this.isFetching = true
let params = {
connection,
}
let response = await window.axios.get(
'/api/v1/onboarding/database/config',
{ params }
)
if (response.data.success) {
this.databaseData = response.data.config
this.database_connection = connection
this.databaseData.database_connection = connection
this.isLoading = this.isFetching = false
}
},
async next(databaseData) {
this.isLoading = this.isFetching = true
try {
await window.axios.get('/sanctum/csrf-cookie')
let response = await window.axios.post(
'/api/v1/onboarding/database/config',
databaseData
)
await window.axios.get('/sanctum/csrf-cookie')
if (response.data.success) {
await window.axios.post('/api/v1/onboarding/finish')
this.$emit('next', 3)
window.toastr['success'](
this.$t('wizard.success.' + response.data.success)
)
return true
} else if (response.data.error) {
window.toastr['error'](
this.$t('wizard.errors.' + response.data.error)
)
} else if (response.data.error_message) {
window.toastr['error'](response.data.error_message)
}
} catch (e) {
window.toastr['error'](e.response.data.message)
} finally {
this.isLoading = this.isFetching = false
}
},
},
}
</script>

View File

@ -0,0 +1,87 @@
<template>
<sw-wizard-step
:title="$t('wizard.mail.mail_config')"
:description="$t('wizard.mail.mail_config_desc')"
>
<base-loader v-if="isFetching" :show-bg-overlay="true" />
<form action="" @submit.prevent="next()">
<component
:is="mail_driver"
:config-data="mailConfigData"
:loading="isLoading"
:mail-drivers="mail_drivers"
@on-change-driver="
(val) => (mail_driver = mailConfigData.mail_driver = val)
"
@submit-data="next"
/>
</form>
</sw-wizard-step>
</template>
<script>
import Smtp from './mail-driver/SmtpMailDriver'
import Mailgun from './mail-driver/MailgunMailDriver'
import Ses from './mail-driver/SesMailDriver'
import Basic from './mail-driver/BasicMailDriver'
export default {
components: {
Smtp,
Mailgun,
Ses,
sendmail: Basic,
mail: Basic,
},
data() {
return {
mailConfigData: {
mail_driver: 'mail',
},
mail_driver: 'mail',
isLoading: false,
isFetching: false,
mail_drivers: [],
}
},
created() {
this.getMailDrivers()
},
methods: {
async getMailDrivers() {
this.isLoading = this.isFetching = true
let response = await window.axios.get('/api/v1/mail/drivers')
if (response.data) {
this.mail_drivers = response.data
this.isLoading = this.isFetching = false
}
},
async next(mailConfigData) {
this.isLoading = this.isFetching = true
try {
let response = await window.axios.post(
'/api/v1/mail/config',
mailConfigData
)
if (response.data.success) {
this.$emit('next', 4)
window.toastr['success'](
this.$t('wizard.success.' + response.data.success)
)
} else {
window.toastr['error'](
this.$t('wizard.errors.' + response.data.error)
)
}
this.isLoading = this.isFetching = false
return true
} catch (e) {
this.isLoading = this.isFetching = false
window.toastr['error']('Something went wrong')
}
},
},
}
</script>

View File

@ -0,0 +1,103 @@
<template>
<sw-wizard-step
:title="$t('wizard.permissions.permissions')"
:description="$t('wizard.permissions.permission_desc')"
>
<base-loader v-if="isFetching" :show-bg-overlay="true" />
<div 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-success"
/>
<span
v-else
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-danger"
/>
<span>{{ permission.permission }}</span>
</div>
</div>
</div>
<sw-button
v-show="!isFetching"
:loading="isLoading"
:disabled="isLoading"
class="mt-10"
variant="primary"
@click="next"
>
{{ $t('wizard.continue') }}
<arrow-right-icon class="h-5 ml-2 -mr-1" />
</sw-button>
</div>
</sw-wizard-step>
</template>
<script>
import { ArrowRightIcon } from '@vue-hero-icons/solid'
export default {
components: {
ArrowRightIcon,
},
data() {
return {
isFetching: false,
isLoading: false,
permissions: [],
errors: false,
}
},
created() {
this.getPermissions()
},
methods: {
async getPermissions() {
this.isLoading = this.isFetching = true
let response = await window.axios.get(
'/api/v1/onboarding/permissions',
this.profileData
)
if (response.data) {
this.permissions = response.data.permissions.permissions
this.errors = response.data.permissions.errors
let self = this
if (this.errors) {
swal({
title: this.$t('wizard.permissions.permission_confirm_title'),
text: this.$t('wizard.permissions.permission_confirm_desc'),
icon: 'warning',
buttons: true,
dangerMode: true,
}).then(async (willConfirm) => {
if (willConfirm) {
self.isLoading = this.isFetching = false
}
})
} else {
this.isLoading = this.isFetching = false
}
this.isLoading = this.isFetching = false
}
},
async next() {
this.isLoading = true
await this.$emit('next')
this.isLoading = false
},
},
}
</script>

View File

@ -0,0 +1,297 @@
<template>
<sw-wizard-step
:title="$t('wizard.preferences')"
:description="$t('wizard.preferences_desc')"
>
<base-loader v-if="isFetching" :show-bg-overlay="true" />
<form action="" @submit.prevent="next">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.currency')"
:error="currencyError"
required
>
<sw-select
v-model="settingData.currency"
:class="{ error: $v.settingData.currency.$error }"
:options="currencies"
:custom-label="currencyNameWithCode"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.currencies.select_currency')"
track-by="id"
label="name"
@input="$v.settingData.currency.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('settings.preferences.default_language')"
:error="languageError"
required
>
<sw-select
v-model="settingData.language"
:class="{ error: $v.settingData.language.$error }"
:options="languages"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_language')"
label="name"
@input="$v.settingData.language.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.date_format')"
:error="dateFormatError"
required
>
<sw-select
v-model="settingData.dateFormat"
:class="{ error: $v.settingData.dateFormat.$error }"
:options="dateFormats"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_date_format')"
label="display_date"
@input="$v.settingData.dateFormat.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.time_zone')"
:error="timeZoneError"
required
>
<sw-select
v-model="settingData.timeZone"
:class="{ error: $v.settingData.timeZone.$error }"
:options="timeZones"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_time_zone')"
label="key"
@input="$v.settingData.timeZone.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group
:label="$t('wizard.fiscal_year')"
:error="fiscalYearError"
required
>
<sw-select
v-model="settingData.fiscalYear"
:class="{ error: $v.settingData.fiscalYear.$error }"
:options="fiscalYears"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.preferences.select_financial_year')"
label="key"
@input="$v.settingData.fiscalYear.$touch()"
/>
</sw-input-group>
</div>
<sw-button
:loading="isLoading"
:disabled="isLoading"
variant="primary"
type="submit"
class="mt-4"
>
<save-icon v-if="!isLoading" class="mr-2" />
{{ $t('wizard.save_cont') }}
</sw-button>
</div>
</form>
</sw-wizard-step>
</template>
<script>
import Ls from '../../services/ls'
import { mapActions, mapGetters } from 'vuex'
const { required, minLength, email } = require('vuelidate/lib/validators')
export default {
data() {
return {
settingData: {
language: null,
currency: null,
timeZone: null,
dateFormat: null,
fiscalYear: null,
},
isLoading: false,
isFetching: false,
step: 1,
}
},
validations: {
settingData: {
currency: {
required,
},
language: {
required,
},
dateFormat: {
required,
},
timeZone: {
required,
},
fiscalYear: {
required,
},
},
},
computed: {
...mapGetters([
'languages',
'currencies',
'timeZones',
'dateFormats',
'fiscalYears',
]),
...mapGetters('company', ['defaultFiscalYear', 'defaultTimeZone']),
currencyError() {
if (!this.$v.settingData.currency.$error) {
return ''
}
if (!this.$v.settingData.currency.required) {
return this.$tc('validation.required')
}
},
languageError() {
if (!this.$v.settingData.language.$error) {
return ''
}
if (!this.$v.settingData.language.required) {
return this.$tc('validation.required')
}
},
dateFormatError() {
if (!this.$v.settingData.dateFormat.$error) {
return ''
}
if (!this.$v.settingData.dateFormat.required) {
return this.$tc('validation.required')
}
},
timeZoneError() {
if (!this.$v.settingData.timeZone.$error) {
return ''
}
if (!this.$v.settingData.timeZone.required) {
return this.$tc('validation.required')
}
},
fiscalYearError() {
if (!this.$v.settingData.fiscalYear.$error) {
return ''
}
if (!this.$v.settingData.fiscalYear.required) {
return this.$tc('validation.required')
}
},
},
mounted() {
// this.getOnboardingData()
this.setInitialData()
},
methods: {
...mapActions('company', ['updateCompanySettings', 'setSelectedCompany']),
...mapActions([
'fetchLanguages',
'fetchCurrencies',
'fetchFiscalYears',
'fetchDateFormats',
'fetchTimeZones',
]),
async setInitialData() {
this.isFetching = true
await this.fetchCurrencies()
await this.fetchDateFormats()
await this.fetchLanguages()
await this.fetchFiscalYears()
await this.fetchTimeZones()
await this.fetchLanguages()
this.settingData.currency = this.currencies.find(
(currency) => currency.id === 1
)
this.settingData.language = this.languages.find(
(language) => language.code === 'en'
)
this.settingData.dateFormat = this.dateFormats.find(
(dateFormat) => dateFormat.carbon_format_value == 'd M Y'
)
this.settingData.timeZone = this.timeZones.find(
(timeZone) => timeZone.value === 'UTC'
)
this.settingData.fiscalYear = this.fiscalYears.find(
(fiscalYear) => fiscalYear.value === '1-12'
)
this.isFetching = false
},
currencyNameWithCode({ name, code }) {
return `${code} - ${name}`
},
async next() {
this.$v.settingData.$touch()
if (this.$v.settingData.$invalid) {
return true
}
this.isLoading = true
let data = {
settings: {
currency: this.settingData.currency.id,
time_zone: this.settingData.timeZone.value,
language: this.settingData.language.code,
fiscal_year: this.settingData.fiscalYear.value,
carbon_date_format: this.settingData.dateFormat.carbon_format_value,
moment_date_format: this.settingData.dateFormat.moment_format_value,
},
}
let response = await this.updateCompanySettings(data)
if (response.data) {
this.isLoading = false
this.updateUserSettings()
Ls.set('auth.token', response.data.token)
}
},
async updateUserSettings() {
let data = {
settings: {
language: this.settingData.language.code,
},
}
let response = await axios.put('/api/v1/me/settings', data)
if (response.data) {
this.$emit('next', 'COMPLETED')
window.toastr['success']('Login Successful')
this.$router.push('/admin/dashboard')
}
},
},
}
</script>

View File

@ -0,0 +1,129 @@
<template>
<sw-wizard-step
: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 rounded-full bg-success"
/>
<span
v-else
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-danger"
/>
</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 rounded-full bg-success"
/>
<span
v-else
class="inline-block w-4 h-4 ml-3 mr-2 rounded-full bg-danger"
/>
</div>
</div>
</div>
</div>
<sw-button
v-if="hasNext"
class="mt-4 pull-right"
variant="primary"
@click="next"
>
{{ $t('wizard.continue') }}
<arrow-right-icon class="h-5 ml-2 -mr-1" />
</sw-button>
<sw-button
v-if="!requirements"
:loading="isLoading"
:disabled="isLoading"
class="mt-4"
variant="primary"
@click="getRequirements"
>
{{ $t('wizard.req.check_req') }}
</sw-button>
</div>
</sw-wizard-step>
</template>
<script>
import { ArrowRightIcon } from '@vue-hero-icons/solid'
export default {
components: {
ArrowRightIcon,
},
data() {
return {
requirements: null,
phpSupportInfo: null,
isLoading: false,
isShow: true,
}
},
computed: {
hasNext() {
if (this.requirements) {
let isRequired = true
for (const key in this.requirements) {
if (!this.requirements[key]) {
isRequired = false
}
}
return this.requirements && this.phpSupportInfo.supported && isRequired
}
return false
},
},
methods: {
listToggle() {
this.isShow = !this.isShow
},
async getRequirements() {
this.isLoading = true
let response = await window.axios.get(
'/api/v1/onboarding/requirements',
this.profileData
)
if (response.data) {
this.requirements = response.data.requirements.requirements.php
this.phpSupportInfo = response.data.phpSupportInfo
this.isLoading = false
}
},
async next() {
this.isLoading = true
await this.$emit('next')
this.isLoading = false
},
},
}
</script>

View File

@ -0,0 +1,265 @@
<template>
<sw-wizard-step
: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">
<sw-input-group
:label="$tc('settings.account_settings.profile_picture')"
>
<sw-avatar
:preview-avatar="previewAvatar"
:label="$tc('general.choose_file')"
@changed="onChange"
@uploadHandler="onUploadHandler"
@handleUploadError="onHandleUploadError"
>
<template v-slot:icon>
<cloud-upload-icon
class="h-5 mb-2 text-xl leading-6 text-gray-400"
/>
</template>
</sw-avatar>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group :label="$t('wizard.name')" :error="nameError" required>
<sw-input
:invalid="$v.profileData.name.$error"
v-model.trim="profileData.name"
type="text"
name="name"
@input="$v.profileData.name.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.email')"
:error="emailError"
required
>
<sw-input
:invalid="$v.profileData.email.$error"
v-model.trim="profileData.email"
type="text"
name="email"
@input="$v.profileData.email.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group
:label="$t('wizard.password')"
:error="passwordError"
required
>
<sw-input
:invalid="$v.profileData.password.$error"
v-model.trim="profileData.password"
type="password"
name="password"
@input="$v.profileData.password.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.confirm_password')"
:error="confirmPasswordError"
required
>
<sw-input
:invalid="$v.profileData.confirm_password.$error"
v-model.trim="profileData.confirm_password"
type="password"
name="confirm_password"
@input="$v.profileData.confirm_password.$touch()"
/>
</sw-input-group>
</div>
<sw-button
:loading="isLoading"
:disabled="isLoading"
variant="primary"
type="submit"
class="mt-4"
>
<save-icon v-if="!isLoading" class="mr-2" />
{{ $t('wizard.save_cont') }}
</sw-button>
</form>
</sw-wizard-step>
</template>
<script>
import { CloudUploadIcon } from '@vue-hero-icons/solid'
import { mapActions } from 'vuex'
const {
required,
requiredIf,
sameAs,
minLength,
email,
} = require('vuelidate/lib/validators')
export default {
components: {
CloudUploadIcon,
},
data() {
return {
profileData: {
name: null,
email: null,
password: null,
confirm_password: null,
},
isLoading: false,
previewAvatar: '/images/default-avatar.jpg',
fileObject: null,
cropperOutputMime: '',
}
},
validations: {
profileData: {
name: {
required,
minLength: minLength(3),
},
email: {
email,
required,
},
password: {
required,
minLength: minLength(8),
},
confirm_password: {
required: requiredIf('isRequired'),
sameAsPassword: sameAs('password'),
},
},
},
computed: {
emailError() {
if (!this.$v.profileData.email.$error) {
return ''
}
if (!this.$v.profileData.email.required) {
return this.$tc('validation.required')
}
if (!this.$v.profileData.email.email) {
return this.$tc('validation.email_incorrect')
}
},
nameError() {
if (!this.$v.profileData.name.$error) {
return ''
}
if (!this.$v.profileData.name.required) {
return this.$tc('validation.required')
}
if (!this.$v.profileData.name.minLength) {
return this.$tc(
'validation.name_min_length',
this.$v.profileData.name.$params.minLength.min,
{ count: this.$v.profileData.name.$params.minLength.min }
)
}
},
passwordError() {
if (!this.$v.profileData.password.$error) {
return ''
}
if (!this.$v.profileData.password.required) {
return this.$tc('validation.required')
}
if (!this.$v.profileData.password.minLength) {
return this.$tc(
'validation.password_min_length',
this.$v.profileData.password.$params.minLength.min,
{ count: this.$v.profileData.password.$params.minLength.min }
)
}
},
confirmPasswordError() {
if (!this.$v.profileData.confirm_password.$error) {
return ''
}
if (!this.$v.profileData.confirm_password.sameAsPassword) {
return this.$tc('validation.password_incorrect')
}
},
isRequired() {
if (
this.profileData.password === null ||
this.profileData.password === undefined ||
this.profileData.password === ''
) {
return false
}
return true
},
},
methods: {
...mapActions('user', ['uploadAvatar']),
onUploadHandler(cropper) {
this.previewAvatar = cropper
.getCroppedCanvas()
.toDataURL(this.cropperOutputMime)
},
onHandleUploadError() {
window.toastr['error']('Oops! Something went wrong...')
},
onChange(file) {
this.cropperOutputMime = file.type
this.fileObject = file
},
async next() {
this.$v.profileData.$touch()
if (this.$v.profileData.$invalid) {
return true
}
this.isLoading = true
let response = await window.axios.put('/api/v1/me', this.profileData)
if (response.data) {
if (this.fileObject && this.previewAvatar) {
let avatarData = new FormData()
avatarData.append(
'admin_avatar',
JSON.stringify({
name: this.fileObject.name,
data: this.previewAvatar,
id: response.data.user.id,
})
)
this.uploadAvatar(avatarData)
}
this.$emit('next', 5)
this.isLoading = false
}
return true
},
},
}
</script>
<style lang="scss">
.avatar-upload {
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,306 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
required
>
<sw-input
:invalid="$v.databaseData.app_url.$error"
v-model.trim="databaseData.app_url"
type="text"
name="name"
@input="$v.databaseData.app_url.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
required
>
<sw-select
v-model="databaseData.database_connection"
:invalid="$v.databaseData.database_connection.$error"
:options="connections"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeConnection"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.port')"
:error="portError"
required
>
<sw-input
:invalid="$v.databaseData.database_port.$error"
v-model.trim="databaseData.database_port"
type="text"
name="database_port"
@input="$v.databaseData.database_port.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.db_name')"
:error="nameError"
required
>
<sw-input
:invalid="$v.databaseData.database_name.$error"
v-model.trim="databaseData.database_name"
type="text"
name="database_name"
@input="$v.databaseData.database_name.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.username')"
:error="usernameError"
required
>
<sw-input
:invalid="$v.databaseData.database_username.$error"
v-model.trim="databaseData.database_username"
type="text"
name="database_username"
@input="$v.databaseData.database_username.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group :label="$t('wizard.database.password')">
<sw-input
v-model.trim="databaseData.database_password"
type="password"
name="name"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.host')"
:error="hostnameError"
required
>
<sw-input
:invalid="$v.databaseData.database_hostname.$error"
v-model.trim="databaseData.database_hostname"
type="text"
name="database_hostname"
@input="$v.databaseData.database_hostname.$touch()"
/>
</sw-input-group>
</div>
<sw-button
v-show="!isFetching"
:loading="isLoading"
:disabled="isLoading"
variant="primary"
class="mt-4"
type="submit"
>
<save-icon v-if="!isLoading" class="h-5 mr-2" />
{{ $t('wizard.save_cont') }}
</sw-button>
</div>
</form>
</template>
<script>
import { SaveIcon } from '@vue-hero-icons/outline'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
export default {
components: {
SaveIcon,
},
props: {
configData: {
type: Object,
require: true,
default: Object,
},
isLoading: {
type: Boolean,
require: true,
default: false,
},
isFetching: {
type: Boolean,
require: true,
default: false,
},
},
data() {
return {
databaseData: {
database_connection: 'mysql',
database_hostname: '127.0.0.1',
database_port: '3306',
database_name: null,
database_username: null,
database_password: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
},
validations: {
databaseData: {
database_connection: {
required,
},
database_hostname: {
required,
},
database_port: {
required,
numeric,
},
database_name: {
required,
},
database_username: {
required,
},
app_url: {
required,
isUrl(val) {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
computed: {
urlError() {
if (!this.$v.databaseData.app_url.$error) {
return ''
}
if (!this.$v.databaseData.app_url.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_url.isUrl) {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''
}
if (!this.$v.databaseData.database_connection.required) {
return this.$tc('validation.required')
}
},
portError() {
if (!this.$v.databaseData.database_port.$error) {
return ''
}
if (!this.$v.databaseData.database_port.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.database_port.numeric) {
return this.$tc('validation.numbers_only')
}
},
nameError() {
if (!this.$v.databaseData.database_name.$error) {
return ''
}
if (!this.$v.databaseData.database_name.required) {
return this.$tc('validation.required')
}
},
usernameError() {
if (!this.$v.databaseData.database_username.$error) {
return ''
}
if (!this.$v.databaseData.database_username.required) {
return this.$tc('validation.required')
}
},
hostnameError() {
if (!this.$v.databaseData.database_hostname.$error) {
return ''
}
if (!this.$v.databaseData.database_hostname.required) {
return this.$tc('validation.required')
}
},
},
mounted() {
for (const key in this.databaseData) {
if (this.configData.hasOwnProperty(key)) {
this.databaseData[key] = this.configData[key]
}
}
},
methods: {
async next() {
this.$v.databaseData.$touch()
if (!this.$v.databaseData.$invalid) {
this.$emit('submit-data', this.databaseData)
}
return false
},
onChangeConnection() {
this.$v.databaseData.database_connection.$touch()
this.$emit('on-change-driver', this.databaseData.database_connection)
},
},
}
</script>

View File

@ -0,0 +1,306 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
required
>
<sw-input
:invalid="$v.databaseData.app_url.$error"
v-model.trim="databaseData.app_url"
type="text"
name="name"
@input="$v.databaseData.app_url.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
required
>
<sw-select
v-model="databaseData.database_connection"
:invalid="$v.databaseData.database_connection.$error"
:options="connections"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeConnection"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.port')"
:error="portError"
required
>
<sw-input
:invalid="$v.databaseData.database_port.$error"
v-model.trim="databaseData.database_port"
type="text"
name="database_port"
@input="$v.databaseData.database_port.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.db_name')"
:error="nameError"
required
>
<sw-input
:invalid="$v.databaseData.database_name.$error"
v-model.trim="databaseData.database_name"
type="text"
name="database_name"
@input="$v.databaseData.database_name.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.username')"
:error="usernameError"
required
>
<sw-input
:invalid="$v.databaseData.database_username.$error"
v-model.trim="databaseData.database_username"
type="text"
name="database_username"
@input="$v.databaseData.database_username.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group :label="$t('wizard.database.password')">
<sw-input
v-model.trim="databaseData.database_password"
type="password"
name="name"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.host')"
:error="hostnameError"
required
>
<sw-input
:invalid="$v.databaseData.database_hostname.$error"
v-model.trim="databaseData.database_hostname"
type="text"
name="database_hostname"
@input="$v.databaseData.database_hostname.$touch()"
/>
</sw-input-group>
</div>
<sw-button
v-show="!isFetching"
:loading="isLoading"
:disabled="isLoading"
variant="primary"
class="mt-4"
type="submit"
>
<save-icon v-if="!isLoading" class="h-5 mr-2" />
{{ $t('wizard.save_cont') }}
</sw-button>
</div>
</form>
</template>
<script>
import { SaveIcon } from '@vue-hero-icons/outline'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
export default {
components: {
SaveIcon,
},
props: {
configData: {
type: Object,
require: true,
default: Object,
},
isLoading: {
type: Boolean,
require: true,
default: false,
},
isFetching: {
type: Boolean,
require: true,
default: false,
},
},
data() {
return {
databaseData: {
database_connection: 'mysql',
database_hostname: '127.0.0.1',
database_port: '3306',
database_name: null,
database_username: null,
database_password: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
},
computed: {
urlError() {
if (!this.$v.databaseData.app_url.$error) {
return ''
}
if (!this.$v.databaseData.app_url.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_url.isUrl) {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''
}
if (!this.$v.databaseData.database_connection.required) {
return this.$tc('validation.required')
}
},
portError() {
if (!this.$v.databaseData.database_port.$error) {
return ''
}
if (!this.$v.databaseData.database_port.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.database_port.numeric) {
return this.$tc('validation.numbers_only')
}
},
nameError() {
if (!this.$v.databaseData.database_name.$error) {
return ''
}
if (!this.$v.databaseData.database_name.required) {
return this.$tc('validation.required')
}
},
usernameError() {
if (!this.$v.databaseData.database_username.$error) {
return ''
}
if (!this.$v.databaseData.database_username.required) {
return this.$tc('validation.required')
}
},
hostnameError() {
if (!this.$v.databaseData.database_hostname.$error) {
return ''
}
if (!this.$v.databaseData.database_hostname.required) {
return this.$tc('validation.required')
}
},
},
validations: {
databaseData: {
database_connection: {
required,
},
database_hostname: {
required,
},
database_port: {
required,
numeric,
},
database_name: {
required,
},
database_username: {
required,
},
app_url: {
required,
isUrl(val) {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
mounted() {
for (const key in this.databaseData) {
if (this.configData.hasOwnProperty(key)) {
this.databaseData[key] = this.configData[key]
}
}
},
methods: {
async next() {
this.$v.databaseData.$touch()
if (!this.$v.databaseData.$invalid) {
this.$emit('submit-data', this.databaseData)
}
return false
},
onChangeConnection() {
this.$v.databaseData.database_connection.$touch()
this.$emit('on-change-driver', this.databaseData.database_connection)
},
},
}
</script>

View File

@ -0,0 +1,208 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
required
>
<sw-input
:invalid="$v.databaseData.app_url.$error"
v-model.trim="databaseData.app_url"
type="text"
name="name"
@input="$v.databaseData.app_url.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
required
>
<sw-select
v-model="databaseData.database_connection"
:invalid="$v.databaseData.database_connection.$error"
:options="connections"
:allow-empty="false"
:searchable="true"
:show-labels="false"
@input="onChangeConnection"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.db_path')"
:error="nameError"
required
>
<sw-input
:invalid="$v.databaseData.database_name.$error"
v-model.trim="databaseData.database_name"
type="text"
name="database_name"
@input="$v.databaseData.database_name.$touch()"
/>
</sw-input-group>
</div>
<sw-button
v-show="!isFetching"
:loading="isLoading"
:disabled="isLoading"
variant="primary"
class="mt-4"
type="submit"
>
<save-icon v-if="!isLoading" class="h-5 mr-2" />
{{ $t('wizard.save_cont') }}
</sw-button>
</div>
</form>
</template>
<script>
import { SaveIcon } from '@vue-hero-icons/outline'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
export default {
components: {
SaveIcon,
},
props: {
configData: {
type: Object,
require: true,
default: Object,
},
isLoading: {
type: Boolean,
require: true,
default: false,
},
isFetching: {
type: Boolean,
require: true,
default: false,
},
},
data() {
return {
databaseData: {
database_connection: 'mysql',
database_name: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
},
validations: {
databaseData: {
database_connection: {
required,
},
database_name: {
required,
},
app_url: {
required,
isUrl(val) {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
computed: {
urlError() {
if (!this.$v.databaseData.app_url.$error) {
return ''
}
if (!this.$v.databaseData.app_url.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_url.isUrl) {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''
}
if (!this.$v.databaseData.database_connection.required) {
return this.$tc('validation.required')
}
},
nameError() {
if (!this.$v.databaseData.database_name.$error) {
return ''
}
if (!this.$v.databaseData.database_name.required) {
return this.$tc('validation.required')
}
},
},
mounted() {
for (const key in this.databaseData) {
if (this.configData.hasOwnProperty(key)) {
this.databaseData[key] = this.configData[key]
}
}
},
methods: {
async next() {
this.$v.databaseData.$touch()
if (!this.$v.databaseData.$invalid) {
this.$emit('submit-data', this.databaseData)
}
return false
},
onChangeConnection() {
this.$v.databaseData.database_connection.$touch()
this.$emit('on-change-driver', this.databaseData.database_connection)
},
},
}
</script>

View File

@ -0,0 +1,306 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
required
>
<sw-input
:invalid="$v.databaseData.app_url.$error"
v-model.trim="databaseData.app_url"
type="text"
name="name"
@input="$v.databaseData.app_url.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
required
>
<sw-select
v-model="databaseData.database_connection"
:invalid="$v.databaseData.database_connection.$error"
:options="connections"
:searchable="true"
:show-labels="false"
:allow-empty="false"
@input="onChangeConnection"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.port')"
:error="portError"
required
>
<sw-input
:invalid="$v.databaseData.database_port.$error"
v-model.trim="databaseData.database_port"
type="text"
name="database_port"
@input="$v.databaseData.database_port.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.db_name')"
:error="nameError"
required
>
<sw-input
:invalid="$v.databaseData.database_name.$error"
v-model.trim="databaseData.database_name"
type="text"
name="database_name"
@input="$v.databaseData.database_name.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.username')"
:error="usernameError"
required
>
<sw-input
:invalid="$v.databaseData.database_username.$error"
v-model.trim="databaseData.database_username"
type="text"
name="database_username"
@input="$v.databaseData.database_username.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group :label="$t('wizard.database.password')">
<sw-input
v-model.trim="databaseData.database_password"
type="password"
name="name"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.host')"
:error="hostnameError"
required
>
<sw-input
:invalid="$v.databaseData.database_hostname.$error"
v-model.trim="databaseData.database_hostname"
type="text"
name="database_hostname"
@input="$v.databaseData.database_hostname.$touch()"
/>
</sw-input-group>
</div>
<sw-button
v-show="!isFetching"
:loading="isLoading"
:disabled="isLoading"
variant="primary"
class="mt-4"
type="submit"
>
<save-icon v-if="!isLoading" class="h-5 mr-2" />
{{ $t('wizard.save_cont') }}
</sw-button>
</div>
</form>
</template>
<script>
import { SaveIcon } from '@vue-hero-icons/outline'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
export default {
components: {
SaveIcon,
},
props: {
configData: {
type: Object,
require: true,
default: Object,
},
isLoading: {
type: Boolean,
require: true,
default: false,
},
isFetching: {
type: Boolean,
require: true,
default: false,
},
},
data() {
return {
databaseData: {
database_connection: 'mysql',
database_hostname: '127.0.0.1',
database_port: '3306',
database_name: null,
database_username: null,
database_password: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
},
validations: {
databaseData: {
database_connection: {
required,
},
database_hostname: {
required,
},
database_port: {
required,
numeric,
},
database_name: {
required,
},
database_username: {
required,
},
app_url: {
required,
isUrl(val) {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
computed: {
urlError() {
if (!this.$v.databaseData.app_url.$error) {
return ''
}
if (!this.$v.databaseData.app_url.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_url.isUrl) {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''
}
if (!this.$v.databaseData.database_connection.required) {
return this.$tc('validation.required')
}
},
portError() {
if (!this.$v.databaseData.database_port.$error) {
return ''
}
if (!this.$v.databaseData.database_port.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.database_port.numeric) {
return this.$tc('validation.numbers_only')
}
},
nameError() {
if (!this.$v.databaseData.database_name.$error) {
return ''
}
if (!this.$v.databaseData.database_name.required) {
return this.$tc('validation.required')
}
},
usernameError() {
if (!this.$v.databaseData.database_username.$error) {
return ''
}
if (!this.$v.databaseData.database_username.required) {
return this.$tc('validation.required')
}
},
hostnameError() {
if (!this.$v.databaseData.database_hostname.$error) {
return ''
}
if (!this.$v.databaseData.database_hostname.required) {
return this.$tc('validation.required')
}
},
},
mounted() {
for (const key in this.databaseData) {
if (this.configData.hasOwnProperty(key)) {
this.databaseData[key] = this.configData[key]
}
}
},
methods: {
async next() {
this.$v.databaseData.$touch()
if (!this.$v.databaseData.$invalid) {
this.$emit('submit-data', this.databaseData)
}
return false
},
onChangeConnection() {
this.$v.databaseData.database_connection.$touch()
this.$emit('on-change-driver', this.databaseData.database_connection)
},
},
}
</script>

View File

@ -0,0 +1,160 @@
<template>
<form @submit.prevent="saveEmailConfig">
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.driver')"
:error="driverError"
required
>
<sw-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:allow-empty="false"
:searchable="true"
:show-labels="false"
@input="onChangeDriver"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group
:label="$t('wizard.mail.from_name')"
:error="fromNameError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="name"
@input="$v.mailConfigData.from_name.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.from_mail')"
:error="fromMailError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
</sw-input-group>
</div>
<sw-button
:loading="loading"
:disabled="loading"
variant="primary"
type="submit"
class="mt-4"
>
<save-icon v-if="!loading" class="mr-2" />
{{ $t('general.save') }}
</sw-button>
</form>
</template>
<script>
const { required, email } = require('vuelidate/lib/validators')
export default {
props: {
configData: {
type: Object,
require: true,
default: Object,
},
loading: {
type: Boolean,
require: true,
default: false,
},
mailDrivers: {
type: Array,
require: true,
default: Array,
},
},
data() {
return {
mailConfigData: {
mail_driver: '',
mail_host: '',
from_mail: '',
from_name: '',
},
}
},
validations: {
mailConfigData: {
mail_driver: {
required,
},
from_mail: {
required,
email,
},
from_name: {
required,
},
},
},
computed: {
driverError() {
if (!this.$v.mailConfigData.mail_driver.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_driver.required) {
return this.$tc('validation.required')
}
},
fromMailError() {
if (!this.$v.mailConfigData.from_mail.$error) {
return ''
}
if (!this.$v.mailConfigData.from_mail.required) {
return this.$tc('validation.required')
}
if (!this.$v.mailConfigData.from_mail.email) {
return this.$tc('validation.email_incorrect')
}
},
fromNameError() {
if (!this.$v.mailConfigData.from_name.$error) {
return ''
}
if (!this.$v.mailConfigData.from_name.required) {
return this.$tc('validation.required')
}
},
},
mounted() {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig() {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver() {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
},
},
}
</script>

View File

@ -0,0 +1,260 @@
<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">
<sw-input-group
:label="$t('wizard.mail.driver')"
:error="driverError"
required
>
<sw-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeDriver"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.mailgun_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_mailgun_domain.$error"
v-model.trim="mailConfigData.mail_mailgun_domain"
type="text"
name="mailgun_domain"
@input="$v.mailConfigData.mail_mailgun_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.mailgun_secret')"
:error="secretError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_mailgun_secret.$error"
v-model.trim="mailConfigData.mail_mailgun_secret"
:type="getInputType"
name="mailgun_secret"
@input="$v.mailConfigData.mail_mailgun_secret.$touch()"
>
<template v-slot:rightIcon>
<eye-off-icon
v-if="isShowPassword"
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
<eye-icon
v-else
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
</template>
</sw-input>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.mailgun_endpoint')"
:error="endpointError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_mailgun_endpoint.$error"
v-model.trim="mailConfigData.mail_mailgun_endpoint"
type="text"
name="mailgun_endpoint"
@input="$v.mailConfigData.mail_mailgun_endpoint.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group
:label="$t('wizard.mail.from_mail')"
:error="fromMailError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.from_name')"
:error="fromNameError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="from_name"
@input="$v.mailConfigData.from_name.$touch()"
/>
</sw-input-group>
</div>
<sw-button
:loading="loading"
:disabled="loading"
variant="primary"
type="submit"
class="mt-4"
>
<save-icon v-if="!loading" class="mr-2" />
{{ $t('general.save') }}
</sw-button>
</form>
</template>
<script>
const { required, email } = require('vuelidate/lib/validators')
import { EyeIcon, EyeOffIcon } from '@vue-hero-icons/outline'
export default {
props: {
configData: {
type: Object,
require: true,
default: Object,
},
loading: {
type: Boolean,
require: true,
default: false,
},
mailDrivers: {
type: Array,
require: true,
default: Array,
},
},
components: {
EyeIcon,
EyeOffIcon,
},
data() {
return {
isShowPassword: false,
mailConfigData: {
mail_driver: '',
mail_mailgun_domain: '',
mail_mailgun_secret: '',
mail_mailgun_endpoint: '',
from_mail: '',
from_name: '',
},
}
},
validations: {
mailConfigData: {
mail_driver: {
required,
},
mail_mailgun_domain: {
required,
},
mail_mailgun_endpoint: {
required,
},
mail_mailgun_secret: {
required,
},
from_mail: {
required,
email,
},
from_name: {
required,
},
},
},
computed: {
driverError() {
if (!this.$v.mailConfigData.mail_driver.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_driver.required) {
return tthis.$tc('validation.required')
}
},
domainError() {
if (!this.$v.mailConfigData.mail_mailgun_domain.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_mailgun_domain.required) {
return this.$tc('validation.required')
}
},
secretError() {
if (!this.$v.mailConfigData.mail_mailgun_secret.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_mailgun_secret.required) {
return this.$tc('validation.required')
}
},
endpointError() {
if (!this.$v.mailConfigData.mail_mailgun_endpoint.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_mailgun_endpoint.required) {
return this.$tc('validation.required')
}
},
fromMailError() {
if (!this.$v.mailConfigData.from_mail.$error) {
return ''
}
if (!this.$v.mailConfigData.from_mail.required) {
return this.$tc('validation.required')
}
if (!this.$v.mailConfigData.from_mail.email) {
return this.$tc('validation.email_incorrect')
}
},
fromNameError() {
if (!this.$v.mailConfigData.from_name.$error) {
return ''
}
if (!this.$v.mailConfigData.from_name.required) {
return this.$tc('validation.required')
}
},
getInputType() {
if (this.isShowPassword) {
return 'text'
}
return 'password'
},
},
mounted() {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig() {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver() {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
},
},
}
</script>

View File

@ -0,0 +1,323 @@
<template>
<form @submit.prevent="saveEmailConfig">
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.driver')"
:error="driverError"
required
>
<sw-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeDriver"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.host')"
:error="hostError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_host.$error"
v-model.trim="mailConfigData.mail_host"
type="text"
name="mail_host"
@input="$v.mailConfigData.mail_host.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.port')"
:error="portError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_port.$error"
v-model.trim="mailConfigData.mail_port"
type="text"
name="mail_port"
@input="$v.mailConfigData.mail_port.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.encryption')"
:error="encryptionError"
required
>
<sw-select
v-model.trim="mailConfigData.mail_encryption"
:invalid="$v.mailConfigData.mail_encryption.$error"
:options="encryptions"
:searchable="true"
:show-labels="false"
@input="$v.mailConfigData.mail_encryption.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.from_mail')"
:error="fromEmailError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.from_name')"
:error="fromNameError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="name"
@input="$v.mailConfigData.from_name.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group
:label="$t('wizard.mail.ses_key')"
:error="keyError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_ses_key.$error"
v-model.trim="mailConfigData.mail_ses_key"
type="text"
name="mail_ses_key"
@input="$v.mailConfigData.mail_ses_key.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.ses_secret')"
:error="secretError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_ses_secret.$error"
v-model.trim="mailConfigData.mail_ses_secret"
:type="getInputType"
name="mail_ses_secret"
@input="$v.mailConfigData.mail_ses_secret.$touch()"
>
<template v-slot:rightIcon>
<eye-off-icon
v-if="isShowPassword"
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
<eye-icon
v-else
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
</template>
</sw-input>
</sw-input-group>
</div>
<sw-button
:loading="loading"
:disabled="loading"
variant="primary"
type="submit"
class="mt-4"
>
<save-icon v-if="!loading" class="mr-2" />
{{ $t('general.save') }}
</sw-button>
</form>
</template>
<script>
const { required, email, numeric } = require('vuelidate/lib/validators')
import { EyeIcon, EyeOffIcon } from '@vue-hero-icons/outline'
export default {
props: {
configData: {
type: Object,
require: true,
default: Object,
},
loading: {
type: Boolean,
require: true,
default: false,
},
mailDrivers: {
type: Array,
require: true,
default: Array,
},
},
components: {
EyeIcon,
EyeOffIcon,
},
data() {
return {
isShowPassword: false,
mailConfigData: {
mail_driver: '',
mail_host: '',
mail_port: null,
mail_ses_key: '',
mail_ses_secret: '',
mail_encryption: 'tls',
from_mail: '',
from_name: '',
},
encryptions: ['tls', 'ssl', 'starttls'],
}
},
validations: {
mailConfigData: {
mail_driver: {
required,
},
mail_host: {
required,
},
mail_port: {
required,
numeric,
},
mail_ses_key: {
required,
},
mail_ses_secret: {
required,
},
mail_encryption: {
required,
},
from_mail: {
required,
email,
},
from_name: {
required,
},
},
},
computed: {
driverError() {
if (!this.$v.mailConfigData.mail_driver.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_driver.required) {
return this.$tc('validation.required')
}
},
hostError() {
if (!this.$v.mailConfigData.mail_host.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_host.required) {
return this.$tc('validation.required')
}
},
portError() {
if (!this.$v.mailConfigData.mail_port.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_port.required) {
return this.$tc('validation.required')
}
if (!this.$v.mailConfigData.mail_port.numeric) {
return this.$tc('validation.numbers_only')
}
},
encryptionError() {
if (!this.$v.mailConfigData.mail_encryption.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_encryption.required) {
return this.$tc('validation.required')
}
},
fromEmailError() {
if (!this.$v.mailConfigData.from_mail.$error) {
return ''
}
if (!this.$v.mailConfigData.from_mail.required) {
return this.$tc('validation.required')
}
if (!this.$v.mailConfigData.from_mail.email) {
return this.$tc('validation.email_incorrect')
}
},
fromNameError() {
if (!this.$v.mailConfigData.from_name.$error) {
return ''
}
if (!this.$v.mailConfigData.from_name.required) {
return this.$tc('validation.required')
}
},
keyError() {
if (!this.$v.mailConfigData.mail_ses_key.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_ses_key.required) {
return this.$tc('validation.required')
}
},
secretError() {
if (!this.$v.mailConfigData.mail_ses_secret.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_ses_secret.required) {
return this.$tc('validation.required')
}
},
getInputType() {
if (this.isShowPassword) {
return 'text'
}
return 'password'
},
},
mounted() {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig() {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver() {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
},
},
}
</script>

View File

@ -0,0 +1,333 @@
<template>
<form @submit.prevent="saveEmailConfig()">
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.driver')"
:error="driverError"
required
>
<sw-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeDriver"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.host')"
:error="hostError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_host.$error"
v-model.trim="mailConfigData.mail_host"
type="text"
name="mail_host"
@input="$v.mailConfigData.mail_host.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.username')"
:error="usernameError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_username.$error"
v-model.trim="mailConfigData.mail_username"
type="text"
name="db_name"
@input="$v.mailConfigData.mail_username.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.password')"
:error="passwordError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_password.$error"
v-model.trim="mailConfigData.mail_password"
:type="getInputType"
name="password"
@input="$v.mailConfigData.mail_password.$touch()"
>
<template v-slot:rightIcon>
<eye-off-icon
v-if="isShowPassword"
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
<eye-icon
v-else
class="w-5 h-5 mr-1 text-gray-500 cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
</template>
</sw-input>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 md:mb-6">
<sw-input-group
:label="$t('wizard.mail.port')"
:error="portError"
required
>
<sw-input
:invalid="$v.mailConfigData.mail_port.$error"
v-model.trim="mailConfigData.mail_port"
type="text"
name="mail_port"
@input="$v.mailConfigData.mail_port.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.encryption')"
:error="encryptionError"
required
>
<sw-select
v-model.trim="mailConfigData.mail_encryption"
:invalid="$v.mailConfigData.mail_encryption.$error"
:options="encryptions"
:searchable="true"
:show-labels="false"
@input="$v.mailConfigData.mail_encryption.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group
:label="$t('wizard.mail.from_mail')"
:error="fromEmailError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.mail.from_name')"
:error="fromNameError"
required
>
<sw-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="from_name"
@input="$v.mailConfigData.from_name.$touch()"
/>
</sw-input-group>
</div>
<sw-button
:loading="loading"
:disabled="loading"
variant="primary"
type="submit"
class="mt-4"
>
<save-icon v-if="!loading" class="mr-2" />
{{ $t('general.save') }}
</sw-button>
</form>
</template>
<script>
const { required, email, numeric } = require('vuelidate/lib/validators')
import { EyeIcon, EyeOffIcon } from '@vue-hero-icons/outline'
export default {
props: {
configData: {
type: Object,
require: true,
default: Object,
},
loading: {
type: Boolean,
require: true,
default: false,
},
mailDrivers: {
type: Array,
require: true,
default: Array,
},
},
components: {
EyeIcon,
EyeOffIcon,
},
data() {
return {
isShowPassword: false,
mailConfigData: {
mail_driver: '',
mail_host: '',
mail_port: null,
mail_username: '',
mail_password: '',
mail_encryption: 'tls',
from_mail: '',
from_name: '',
},
encryptions: ['tls', 'ssl', 'starttls'],
}
},
validations: {
mailConfigData: {
mail_driver: {
required,
},
mail_host: {
required,
},
mail_port: {
required,
numeric,
},
mail_username: {
required,
},
mail_password: {
required,
},
mail_encryption: {
required,
},
from_mail: {
required,
email,
},
from_name: {
required,
},
},
},
computed: {
driverError() {
if (!this.$v.mailConfigData.mail_driver.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_driver.required) {
return this.$tc('validation.required')
}
},
hostError() {
if (!this.$v.mailConfigData.mail_host.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_host.required) {
return this.$tc('validation.required')
}
},
usernameError() {
if (!this.$v.mailConfigData.mail_username.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_username.required) {
return this.$tc('validation.required')
}
},
passwordError() {
if (!this.$v.mailConfigData.mail_password.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_password.required) {
return this.$tc('validation.required')
}
},
portError() {
if (!this.$v.mailConfigData.mail_port.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_port.required) {
return this.$tc('validation.required')
}
if (!this.$v.mailConfigData.mail_port.numeric) {
return this.$tc('validation.numbers_only')
}
},
encryptionError() {
if (!this.$v.mailConfigData.mail_encryption.$error) {
return ''
}
if (!this.$v.mailConfigData.mail_encryption.required) {
return this.$tc('validation.required')
}
},
fromEmailError() {
if (!this.$v.mailConfigData.from_mail.$error) {
return ''
}
if (!this.$v.mailConfigData.from_mail.required) {
return this.$tc('validation.required')
}
if (!this.$v.mailConfigData.from_mail.email) {
return this.$tc('validation.email_incorrect')
}
},
fromNameError() {
if (!this.$v.mailConfigData.from_name.$error) {
return ''
}
if (!this.$v.mailConfigData.from_name.required) {
return this.$tc('validation.required')
}
},
getInputType() {
if (this.isShowPassword) {
return 'text'
}
return 'password'
},
},
mounted() {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig() {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver() {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
},
},
}
</script>

View File

@ -1,160 +0,0 @@
<template>
<form @submit.prevent="saveEmailConfig()">
<div class="row">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.driver') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:allow-empty="false"
:searchable="true"
:show-labels="false"
@input="onChangeDriver"
/>
<div v-if="$v.mailConfigData.mail_driver.$error">
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<!-- <div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.host') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_host.$error"
v-model.trim="mailConfigData.mail_host"
type="text"
name="mail_host"
@input="$v.mailConfigData.mail_host.$touch()"
/>
<div v-if="$v.mailConfigData.mail_host.$error">
<span v-if="!$v.mailConfigData.mail_host.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div> -->
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_mail') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
<div v-if="$v.mailConfigData.from_mail.$error">
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
{{ $tc('validation.email_incorrect') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_name') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="name"
@input="$v.mailConfigData.from_name.$touch()"
/>
<div v-if="$v.mailConfigData.from_name.$error">
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
</form>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
const { required, email } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
props: {
configData: {
type: Object,
require: true,
default: Object
},
loading: {
type: Boolean,
require: true,
default: false
},
mailDrivers: {
type: Array,
require: true,
default: Array
}
},
data () {
return {
mailConfigData: {
mail_driver: '',
mail_host: '',
from_mail: '',
from_name: ''
}
}
},
validations: {
mailConfigData: {
mail_driver: {
required
},
from_mail: {
required,
email
},
from_name: {
required
}
}
},
mounted () {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig () {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver () {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
}
}
}
</script>

View File

@ -1,209 +0,0 @@
<template>
<form @submit.prevent="saveEmailConfig()">
<div class="row">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.driver') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeDriver"
/>
<div v-if="$v.mailConfigData.mail_driver.$error">
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.mailgun_domain') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_mailgun_domain.$error"
v-model.trim="mailConfigData.mail_mailgun_domain"
type="text"
name="mailgun_domain"
@input="$v.mailConfigData.mail_mailgun_domain.$touch()"
/>
<div v-if="$v.mailConfigData.mail_mailgun_domain.$error">
<span v-if="!$v.mailConfigData.mail_mailgun_domain.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.mailgun_secret') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_mailgun_secret.$error"
v-model.trim="mailConfigData.mail_mailgun_secret"
type="password"
name="mailgun_secret"
show-password
@input="$v.mailConfigData.mail_mailgun_secret.$touch()"
/>
<div v-if="$v.mailConfigData.mail_mailgun_secret.$error">
<span v-if="!$v.mailConfigData.mail_mailgun_secret.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.mailgun_endpoint') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_mailgun_endpoint.$error"
v-model.trim="mailConfigData.mail_mailgun_endpoint"
type="text"
name="mailgun_endpoint"
@input="$v.mailConfigData.mail_mailgun_endpoint.$touch()"
/>
<div v-if="$v.mailConfigData.mail_mailgun_endpoint.$error">
<span v-if="!$v.mailConfigData.mail_mailgun_endpoint.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.mail_mailgun_endpoint.numeric" class="text-danger">
{{ $tc('validation.numbers_only') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_mail') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
<div v-if="$v.mailConfigData.from_mail.$error">
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
{{ $tc('validation.email_incorrect') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_name') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="from_name"
@input="$v.mailConfigData.from_name.$touch()"
/>
<div v-if="$v.mailConfigData.from_name.$error">
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
</form>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
const { required, email } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
props: {
configData: {
type: Object,
require: true,
default: Object
},
loading: {
type: Boolean,
require: true,
default: false
},
mailDrivers: {
type: Array,
require: true,
default: Array
}
},
data () {
return {
mailConfigData: {
mail_driver: '',
mail_mailgun_domain: '',
mail_mailgun_secret: '',
mail_mailgun_endpoint: '',
from_mail: '',
from_name: ''
}
}
},
validations: {
mailConfigData: {
mail_driver: {
required
},
mail_mailgun_domain: {
required
},
mail_mailgun_endpoint: {
required
},
mail_mailgun_secret: {
required
},
from_mail: {
required,
email
},
from_name: {
required
}
}
},
mounted () {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig () {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver () {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
}
}
}
</script>

View File

@ -1,254 +0,0 @@
<template>
<form @submit.prevent="saveEmailConfig()">
<div class="row">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.driver') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeDriver"
/>
<div v-if="$v.mailConfigData.mail_driver.$error">
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.host') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_host.$error"
v-model.trim="mailConfigData.mail_host"
type="text"
name="mail_host"
@input="$v.mailConfigData.mail_host.$touch()"
/>
<div v-if="$v.mailConfigData.mail_host.$error">
<span v-if="!$v.mailConfigData.mail_host.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.port') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_port.$error"
v-model.trim="mailConfigData.mail_port"
type="text"
name="mail_port"
@input="$v.mailConfigData.mail_port.$touch()"
/>
<div v-if="$v.mailConfigData.mail_port.$error">
<span v-if="!$v.mailConfigData.mail_port.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.mail_port.numeric" class="text-danger">
{{ $tc('validation.numbers_only') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.encryption') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model.trim="mailConfigData.mail_encryption"
:invalid="$v.mailConfigData.mail_encryption.$error"
:options="encryptions"
:searchable="true"
:show-labels="false"
@input="$v.mailConfigData.mail_encryption.$touch()"
/>
<div v-if="$v.mailConfigData.mail_encryption.$error">
<span v-if="!$v.mailConfigData.mail_encryption.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_mail') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
<div v-if="$v.mailConfigData.from_mail.$error">
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
{{ $tc('validation.email_incorrect') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_name') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="name"
@input="$v.mailConfigData.from_name.$touch()"
/>
<div v-if="$v.mailConfigData.from_name.$error">
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.ses_key') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_ses_key.$error"
v-model.trim="mailConfigData.mail_ses_key"
type="text"
name="mail_ses_key"
@input="$v.mailConfigData.mail_ses_key.$touch()"
/>
<div v-if="$v.mailConfigData.mail_ses_key.$error">
<span v-if="!$v.mailConfigData.mail_ses_key.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.ses_secret') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_ses_secret.$error"
v-model.trim="mailConfigData.mail_ses_secret"
type="password"
name="mail_ses_secret"
show-password
@input="$v.mailConfigData.mail_ses_secret.$touch()"
/>
<div v-if="$v.mailConfigData.mail_ses_secret.$error">
<span v-if="!$v.mailConfigData.mail_ses_secret.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
</form>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
const { required, email, numeric } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
props: {
configData: {
type: Object,
require: true,
default: Object
},
loading: {
type: Boolean,
require: true,
default: false
},
mailDrivers: {
type: Array,
require: true,
default: Array
}
},
data () {
return {
mailConfigData: {
mail_driver: '',
mail_host: '',
mail_port: null,
mail_ses_key: '',
mail_ses_secret: '',
mail_encryption: 'tls',
from_mail: '',
from_name: ''
},
encryptions: ['tls', 'ssl', 'starttls']
}
},
validations: {
mailConfigData: {
mail_driver: {
required
},
mail_host: {
required
},
mail_port: {
required,
numeric
},
mail_ses_key: {
required
},
mail_ses_secret: {
required
},
mail_encryption: {
required
},
from_mail: {
required,
email
},
from_name: {
required
}
}
},
mounted () {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig () {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver () {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
}
}
}
</script>

View File

@ -1,254 +0,0 @@
<template>
<form @submit.prevent="saveEmailConfig()">
<div class="row">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.driver') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mailDrivers"
:searchable="true"
:allow-empty="false"
:show-labels="false"
@input="onChangeDriver"
/>
<div v-if="$v.mailConfigData.mail_driver.$error">
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.host') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_host.$error"
v-model.trim="mailConfigData.mail_host"
type="text"
name="mail_host"
@input="$v.mailConfigData.mail_host.$touch()"
/>
<div v-if="$v.mailConfigData.mail_host.$error">
<span v-if="!$v.mailConfigData.mail_host.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.username') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_username.$error"
v-model.trim="mailConfigData.mail_username"
type="text"
name="db_name"
@input="$v.mailConfigData.mail_username.$touch()"
/>
<div v-if="$v.mailConfigData.mail_username.$error">
<span v-if="!$v.mailConfigData.mail_username.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.password') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_password.$error"
v-model.trim="mailConfigData.mail_password"
type="password"
name="name"
show-password
@input="$v.mailConfigData.mail_password.$touch()"
/>
<div v-if="$v.mailConfigData.mail_password.$error">
<span v-if="!$v.mailConfigData.mail_password.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.port') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_port.$error"
v-model.trim="mailConfigData.mail_port"
type="text"
name="mail_port"
@input="$v.mailConfigData.mail_port.$touch()"
/>
<div v-if="$v.mailConfigData.mail_port.$error">
<span v-if="!$v.mailConfigData.mail_port.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.mail_port.numeric" class="text-danger">
{{ $tc('validation.numbers_only') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.encryption') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model.trim="mailConfigData.mail_encryption"
:invalid="$v.mailConfigData.mail_encryption.$error"
:options="encryptions"
:searchable="true"
:show-labels="false"
@input="$v.mailConfigData.mail_encryption.$touch()"
/>
<div v-if="$v.mailConfigData.mail_encryption.$error">
<span v-if="!$v.mailConfigData.mail_encryption.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_mail') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_mail.$error"
v-model.trim="mailConfigData.from_mail"
type="text"
name="from_mail"
@input="$v.mailConfigData.from_mail.$touch()"
/>
<div v-if="$v.mailConfigData.from_mail.$error">
<span v-if="!$v.mailConfigData.from_mail.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.from_mail.email" class="text-danger">
{{ $tc('validation.email_incorrect') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('wizard.mail.from_name') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.from_name.$error"
v-model.trim="mailConfigData.from_name"
type="text"
name="from_name"
@input="$v.mailConfigData.from_name.$touch()"
/>
<div v-if="$v.mailConfigData.from_name.$error">
<span v-if="!$v.mailConfigData.from_name.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
</form>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
const { required, email, numeric } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
props: {
configData: {
type: Object,
require: true,
default: Object
},
loading: {
type: Boolean,
require: true,
default: false
},
mailDrivers: {
type: Array,
require: true,
default: Array
}
},
data () {
return {
mailConfigData: {
mail_driver: '',
mail_host: '',
mail_port: null,
mail_username: '',
mail_password: '',
mail_encryption: 'tls',
from_mail: '',
from_name: ''
},
encryptions: ['tls', 'ssl', 'starttls']
}
},
validations: {
mailConfigData: {
mail_driver: {
required
},
mail_host: {
required
},
mail_port: {
required,
numeric
},
mail_username: {
required
},
mail_password: {
required
},
mail_encryption: {
required
},
from_mail: {
required,
email
},
from_name: {
required
}
}
},
mounted () {
for (const key in this.mailConfigData) {
if (this.configData.hasOwnProperty(key)) {
this.mailConfigData[key] = this.configData[key]
}
}
},
methods: {
async saveEmailConfig () {
this.$v.mailConfigData.$touch()
if (!this.$v.mailConfigData.$invalid) {
this.$emit('submit-data', this.mailConfigData)
}
return false
},
onChangeDriver () {
this.$v.mailConfigData.mail_driver.$touch()
this.$emit('on-change-driver', this.mailConfigData.mail_driver)
}
}
}
</script>