feat(admin): migrate mail to vue 3 composable

parent 36ba1eb1
...@@ -11,6 +11,6 @@ ...@@ -11,6 +11,6 @@
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"server/locales" "ux/src/i18n/locales"
] ]
} }
...@@ -4,8 +4,8 @@ q-page.admin-mail ...@@ -4,8 +4,8 @@ q-page.admin-mail
.col-auto .col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-message-settings.svg') img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-message-settings.svg')
.col.q-pl-md .col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.mail.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.mail.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.mail.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.mail.subtitle') }}
.col-auto .col-auto
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle' icon='las la-question-circle'
...@@ -19,16 +19,16 @@ q-page.admin-mail ...@@ -19,16 +19,16 @@ q-page.admin-mail
icon='las la-redo-alt' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
:loading='loading > 0' :loading='state.loading > 0'
@click='load' @click='load'
) )
q-btn( q-btn(
unelevated unelevated
icon='mdi-check' icon='fa-solid fa-check'
:label='$t(`common.actions.apply`)' :label='t(`common.actions.apply`)'
color='secondary' color='secondary'
@click='save' @click='save'
:disabled='loading > 0' :disabled='state.loading > 0'
) )
q-separator(inset) q-separator(inset)
.row.q-pa-md.q-col-gutter-md .row.q-pa-md.q-col-gutter-md
...@@ -38,184 +38,184 @@ q-page.admin-mail ...@@ -38,184 +38,184 @@ q-page.admin-mail
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.configuration')}} .text-subtitle1 {{t('admin.mail.configuration')}}
q-item q-item
blueprint-icon(icon='contact') blueprint-icon(icon='contact')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.senderName`)}} q-item-label {{t(`admin.mail.senderName`)}}
q-item-label(caption) {{$t(`admin.general.senderNameHint`)}} q-item-label(caption) {{t(`admin.general.senderNameHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.senderName' v-model='state.config.senderName'
dense dense
hide-bottom-space hide-bottom-space
:aria-label='$t(`admin.mail.senderName`)' :aria-label='t(`admin.mail.senderName`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='envelope') blueprint-icon(icon='envelope')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.senderEmail`)}} q-item-label {{t(`admin.mail.senderEmail`)}}
q-item-label(caption) {{$t(`admin.general.senderEmailHint`)}} q-item-label(caption) {{t(`admin.general.senderEmailHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.senderEmail' v-model='state.config.senderEmail'
dense dense
:aria-label='$t(`admin.mail.senderEmail`)' :aria-label='t(`admin.mail.senderEmail`)'
) )
//- ----------------------- //- -----------------------
//- SMTP //- SMTP
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.smtp')}} .text-subtitle1 {{t('admin.mail.smtp')}}
q-item q-item
blueprint-icon(icon='dns') blueprint-icon(icon='dns')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpHost`)}} q-item-label {{t(`admin.mail.smtpHost`)}}
q-item-label(caption) {{$t(`admin.mail.smtpHostHint`)}} q-item-label(caption) {{t(`admin.mail.smtpHostHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.host' v-model='state.config.host'
dense dense
hide-bottom-space hide-bottom-space
:aria-label='$t(`admin.mail.smtpHost`)' :aria-label='t(`admin.mail.smtpHost`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='ethernet-off') blueprint-icon(icon='ethernet-off')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpPort`)}} q-item-label {{t(`admin.mail.smtpPort`)}}
q-item-label(caption) {{$t(`admin.mail.smtpPortHint`)}} q-item-label(caption) {{t(`admin.mail.smtpPortHint`)}}
q-item-section(style='flex: 0 0 120px;') q-item-section(style='flex: 0 0 120px;')
q-input( q-input(
outlined outlined
v-model='config.port' v-model='state.config.port'
dense dense
:aria-label='$t(`admin.mail.smtpPort`)' :aria-label='t(`admin.mail.smtpPort`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='secure') blueprint-icon(icon='secure')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpTLS`)}} q-item-label {{t(`admin.mail.smtpTLS`)}}
q-item-label(caption) {{$t(`admin.mail.smtpTLSHint`)}} q-item-label(caption) {{t(`admin.mail.smtpTLSHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.secure' v-model='state.config.secure'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.mail.smtpTLS`)' :aria-label='t(`admin.mail.smtpTLS`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='security-ssl') blueprint-icon(icon='security-ssl')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpVerifySSL`)}} q-item-label {{t(`admin.mail.smtpVerifySSL`)}}
q-item-label(caption) {{$t(`admin.mail.smtpVerifySSLHint`)}} q-item-label(caption) {{t(`admin.mail.smtpVerifySSLHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.verifySSL' v-model='state.config.verifySSL'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.mail.smtpVerifySSL`)' :aria-label='t(`admin.mail.smtpVerifySSL`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='test-account') blueprint-icon(icon='test-account')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpUser`)}} q-item-label {{t(`admin.mail.smtpUser`)}}
q-item-label(caption) {{$t(`admin.mail.smtpUserHint`)}} q-item-label(caption) {{t(`admin.mail.smtpUserHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.user' v-model='state.config.user'
dense dense
:aria-label='$t(`admin.mail.smtpUser`)' :aria-label='t(`admin.mail.smtpUser`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='password') blueprint-icon(icon='password')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.smtpPwd`)}} q-item-label {{t(`admin.mail.smtpPwd`)}}
q-item-label(caption) {{$t(`admin.mail.smtpPwdHint`)}} q-item-label(caption) {{t(`admin.mail.smtpPwdHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.pass' v-model='state.config.pass'
dense dense
:aria-label='$t(`admin.mail.smtpPwd`)' :aria-label='t(`admin.mail.smtpPwd`)'
) )
//- ----------------------- //- -----------------------
//- DKIM //- DKIM
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.dkim')}} .text-subtitle1 {{t('admin.mail.dkim')}}
q-item.q-pt-none q-item.q-pt-none
q-item-section q-item-section
q-card.bg-info.text-white.rounded-borders(flat) q-card.bg-info.text-white.rounded-borders(flat)
q-card-section.items-center(horizontal) q-card-section.items-center(horizontal)
q-card-section.col-auto.q-pr-none q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm') q-icon(name='las la-info-circle', size='sm')
q-card-section.text-caption {{ $t('admin.mail.dkimHint') }} q-card-section.text-caption {{ t('admin.mail.dkimHint') }}
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='received') blueprint-icon(icon='received')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimUse`)}} q-item-label {{t(`admin.mail.dkimUse`)}}
q-item-label(caption) {{$t(`admin.mail.dkimUseHint`)}} q-item-label(caption) {{t(`admin.mail.dkimUseHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.useDKIM' v-model='state.config.useDKIM'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.mail.dkimUse`)' :aria-label='t(`admin.mail.dkimUse`)'
) )
template(v-if='config.useDKIM') template(v-if='state.config.useDKIM')
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='dns') blueprint-icon(icon='dns')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimDomainName`)}} q-item-label {{t(`admin.mail.dkimDomainName`)}}
q-item-label(caption) {{$t(`admin.mail.dkimDomainNameHint`)}} q-item-label(caption) {{t(`admin.mail.dkimDomainNameHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.dkimDomainName' v-model='state.config.dkimDomainName'
dense dense
:aria-label='$t(`admin.mail.dkimDomainName`)' :aria-label='t(`admin.mail.dkimDomainName`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='access') blueprint-icon(icon='access')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimKeySelector`)}} q-item-label {{t(`admin.mail.dkimKeySelector`)}}
q-item-label(caption) {{$t(`admin.mail.dkimKeySelectorHint`)}} q-item-label(caption) {{t(`admin.mail.dkimKeySelectorHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.dkimKeySelector' v-model='state.config.dkimKeySelector'
dense dense
:aria-label='$t(`admin.mail.dkimKeySelector`)' :aria-label='t(`admin.mail.dkimKeySelector`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='grand-master-key') blueprint-icon(icon='grand-master-key')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.dkimPrivateKey`)}} q-item-label {{t(`admin.mail.dkimPrivateKey`)}}
q-item-label(caption) {{$t(`admin.mail.dkimPrivateKeyHint`)}} q-item-label(caption) {{t(`admin.mail.dkimPrivateKeyHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.dkimPrivateKey' v-model='state.config.dkimPrivateKey'
dense dense
:aria-label='$t(`admin.mail.dkimPrivateKey`)' :aria-label='t(`admin.mail.dkimPrivateKey`)'
type='textarea' type='textarea'
) )
...@@ -225,12 +225,12 @@ q-page.admin-mail ...@@ -225,12 +225,12 @@ q-page.admin-mail
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.templates')}} .text-subtitle1 {{t('admin.mail.templates')}}
q-list q-list
q-item q-item
blueprint-icon(icon='resume-template') blueprint-icon(icon='resume-template')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.templateWelcome`)}} q-item-label {{t(`admin.mail.templateWelcome`)}}
q-item-section(side) q-item-section(side)
q-btn( q-btn(
outline outline
...@@ -238,13 +238,13 @@ q-page.admin-mail ...@@ -238,13 +238,13 @@ q-page.admin-mail
icon='las la-edit' icon='las la-edit'
color='primary' color='primary'
@click='' @click=''
:label='$t(`common.actions.edit`)' :label='t(`common.actions.edit`)'
) )
q-separator(inset) q-separator(inset)
q-item q-item
blueprint-icon(icon='resume-template') blueprint-icon(icon='resume-template')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.templateResetPwd`)}} q-item-label {{t(`admin.mail.templateResetPwd`)}}
q-item-section(side) q-item-section(side)
q-btn( q-btn(
outline outline
...@@ -252,54 +252,74 @@ q-page.admin-mail ...@@ -252,54 +252,74 @@ q-page.admin-mail
icon='las la-edit' icon='las la-edit'
color='primary' color='primary'
@click='' @click=''
:label='$t(`common.actions.edit`)' :label='t(`common.actions.edit`)'
) )
//- ----------------------- //- -----------------------
//- SMTP TEST //- SMTP TEST
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.mail.test')}} .text-subtitle1 {{t('admin.mail.test')}}
q-item q-item
blueprint-icon.self-start(icon='email') blueprint-icon.self-start(icon='email')
q-item-section q-item-section
q-item-label {{$t(`admin.mail.testRecipient`)}} q-item-label {{t(`admin.mail.testRecipient`)}}
q-item-label(caption) {{$t(`admin.mail.testRecipientHint`)}} q-item-label(caption) {{t(`admin.mail.testRecipientHint`)}}
q-input.q-mt-md( q-input.q-mt-md(
outlined outlined
v-model='testEmail' v-model='state.testEmail'
dense dense
:aria-label='$t(`admin.mail.testRecipient`)' :aria-label='t(`admin.mail.testRecipient`)'
) )
.flex.justify-end.q-pr-md.q-py-sm .flex.justify-end.q-pr-md.q-py-sm
q-btn( q-btn(
unelevated unelevated
color='primary' color='primary'
icon='las la-paper-plane' icon='las la-paper-plane'
:label='$t(`admin.mail.testSend`)' :label='t(`admin.mail.testSend`)'
@click='sendTest' @click='sendTest'
:loading='testLoading' :loading='state.testLoading'
) )
</template> </template>
<script> <script setup>
import toSafeInteger from 'lodash/toSafeInteger' import toSafeInteger from 'lodash/toSafeInteger'
import _get from 'lodash/get' import _get from 'lodash/get'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { createMetaMixin } from 'quasar'
export default { import { useI18n } from 'vue-i18n'
mixins: [ import { useMeta, useQuasar } from 'quasar'
createMetaMixin(function () { import { computed, onMounted, reactive, watch } from 'vue'
return {
title: this.$t('admin.mail.title') import { useAdminStore } from 'src/stores/admin'
} import { useSiteStore } from 'src/stores/site'
}) import { useDataStore } from 'src/stores/data'
],
data () { // QUASAR
return {
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const siteStore = useSiteStore()
const dataStore = useDataStore()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('admin.mail.title')
})
// DATA
const state = reactive({
config: { config: {
senderName: '', senderName: '',
senderEmail: '', senderEmail: '',
...@@ -317,16 +337,13 @@ export default { ...@@ -317,16 +337,13 @@ export default {
testEmail: '', testEmail: '',
testLoading: false, testLoading: false,
loading: 0 loading: 0
} })
},
mounted () { // METHODS
this.load() async function load () {
}, state.loading++
methods: {
async load () {
this.loading++
try { try {
const resp = await this.$apollo.query({ const resp = await APOLLO_CLIENT.query({
query: gql` query: gql`
query getMailConfig { query getMailConfig {
mailConfig { mailConfig {
...@@ -345,27 +362,28 @@ export default { ...@@ -345,27 +362,28 @@ export default {
} }
} }
`, `,
fetchPolicy: 'no-cache' fetchPolicy: 'network-only'
}) })
if (!resp?.data?.mailConfig) { if (!resp?.data?.mailConfig) {
throw new Error('Failed to fetch mail config.') throw new Error('Failed to fetch mail config.')
} }
this.config = cloneDeep(resp.data.mailConfig) state.config = cloneDeep(resp.data.mailConfig)
} catch (err) { } catch (err) {
this.$q.notify({ $q.notify({
type: 'negative', type: 'negative',
message: 'Failed to fetch mail config', message: 'Failed to fetch mail config',
caption: err.message caption: err.message
}) })
} }
this.loading-- state.loading--
}, }
async save () {
if (this.loading > 0) { return } async function save () {
if (state.loading > 0) { return }
this.loading++ state.loading++
try { try {
await this.$apollo.mutate({ await APOLLO_CLIENT.mutate({
mutation: gql` mutation: gql`
mutation saveMailConfig ( mutation saveMailConfig (
$senderName: String! $senderName: String!
...@@ -404,33 +422,37 @@ export default { ...@@ -404,33 +422,37 @@ export default {
} }
`, `,
variables: { variables: {
senderName: this.config.senderName || '', senderName: state.config.senderName || '',
senderEmail: this.config.senderEmail || '', senderEmail: state.config.senderEmail || '',
host: this.config.host || '', host: state.config.host || '',
port: toSafeInteger(this.config.port) || 0, port: toSafeInteger(state.config.port) || 0,
secure: this.config.secure ?? false, secure: state.config.secure ?? false,
verifySSL: this.config.verifySSL ?? false, verifySSL: state.config.verifySSL ?? false,
user: this.config.user || '', user: state.config.user || '',
pass: this.config.pass || '', pass: state.config.pass || '',
useDKIM: this.config.useDKIM ?? false, useDKIM: state.config.useDKIM ?? false,
dkimDomainName: this.config.dkimDomainName || '', dkimDomainName: state.config.dkimDomainName || '',
dkimKeySelector: this.config.dkimKeySelector || '', dkimKeySelector: state.config.dkimKeySelector || '',
dkimPrivateKey: this.config.dkimPrivateKey || '' dkimPrivateKey: state.config.dkimPrivateKey || ''
} }
}) })
this.$q.notify({ $q.notify({
type: 'positive', type: 'positive',
message: this.$t('admin.mail.saveSuccess') message: t('admin.mail.saveSuccess')
}) })
} catch (err) { } catch (err) {
this.$store.commit('pushGraphError', err) $q.notify({
type: 'negative',
message: err.message
})
} }
this.loading-- state.loading--
}, }
async sendTest () {
this.loading++ async function sendTest () {
state.loading++
try { try {
const resp = await this.$apollo.mutate({ const resp = await APOLLO_CLIENT.mutate({
mutation: gql` mutation: gql`
mutation sentMailTest ( mutation sentMailTest (
$recipientEmail: String! $recipientEmail: String!
...@@ -447,25 +469,32 @@ export default { ...@@ -447,25 +469,32 @@ export default {
} }
`, `,
variables: { variables: {
recipientEmail: this.testEmail recipientEmail: state.testEmail
} }
}) })
if (!_get(resp, 'data.sendMailTest.status.succeeded', false)) { if (!_get(resp, 'data.sendMailTest.status.succeeded', false)) {
throw new Error(_get(resp, 'data.sendMailTest.status.message', 'An unexpected error occurred.')) throw new Error(_get(resp, 'data.sendMailTest.status.message', 'An unexpected error occurred.'))
} }
this.testEmail = '' state.testEmail = ''
this.$q.notify({ $q.notify({
type: 'positive', type: 'positive',
message: this.$t('admin.mail.sendTestSuccess') message: t('admin.mail.sendTestSuccess')
}) })
} catch (err) { } catch (err) {
this.$store.commit('pushGraphError', err) $q.notify({
} type: 'negative',
this.loading-- message: err.message
} })
} }
state.loading--
} }
// MOUNTED
onMounted(() => {
load()
})
</script> </script>
<style lang='scss'> <style lang='scss'>
......
...@@ -46,7 +46,7 @@ const routes = [ ...@@ -46,7 +46,7 @@ const routes = [
// -> System // -> System
// { path: 'api', component: () => import('../pages/AdminApi.vue') }, // { path: 'api', component: () => import('../pages/AdminApi.vue') },
// { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') }, // { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') },
// { path: 'mail', component: () => import('../pages/AdminMail.vue') }, { path: 'mail', component: () => import('../pages/AdminMail.vue') },
// { path: 'security', component: () => import('../pages/AdminSecurity.vue') }, // { path: 'security', component: () => import('../pages/AdminSecurity.vue') },
{ path: 'system', component: () => import('../pages/AdminSystem.vue') }, { path: 'system', component: () => import('../pages/AdminSystem.vue') },
// { path: 'utilities', component: () => import('../pages/AdminUtilities.vue') }, // { path: 'utilities', component: () => import('../pages/AdminUtilities.vue') },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment