refactor: admin sites + create site dialog to vue3 comp api

pull/5698/head
NGPixel 2 years ago
parent 70a2e3ca63
commit dfde2e10aa
No known key found for this signature in database
GPG Key ID: 8FDA2F1757F60D63

@ -13,6 +13,7 @@
"i18n-ally.localesPaths": [
"src/i18n",
"src/i18n/locales"
]
],
"i18n-ally.keystyle": "nested"
}
}

@ -1,24 +1,24 @@
<template lang="pug">
q-dialog(ref='dialog', @hide='onDialogHide')
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-plus-plus.svg', left, size='sm')
span {{$t(`admin.sites.new`)}}
span {{t(`admin.sites.new`)}}
q-form.q-py-sm(ref='createSiteForm')
q-item
blueprint-icon(icon='home')
q-item-section
q-input(
outlined
v-model='siteName'
v-model='state.siteName'
dense
:rules=`[
val => val.length > 0 || $t('admin.sites.nameMissing'),
val => /^[^<>"]+$/.test(val) || $t('admin.sites.nameInvalidChars')
val => val.length > 0 || t('admin.sites.nameMissing'),
val => /^[^<>"]+$/.test(val) || t('admin.sites.nameInvalidChars')
]`
hide-bottom-space
:label='$t(`common.field.name`)'
:aria-label='$t(`common.field.name`)'
:label='t(`common.field.name`)'
:aria-label='t(`common.field.name`)'
lazy-rules='ondemand'
autofocus
)
@ -27,107 +27,121 @@ q-dialog(ref='dialog', @hide='onDialogHide')
q-item-section
q-input(
outlined
v-model='siteHostname'
v-model='state.siteHostname'
dense
:rules=`[
val => val.length > 0 || $t('admin.sites.hostnameMissing'),
val => /^(\\*)|([a-z0-9\-.:]+)$/.test(val) || $t('admin.sites.hostnameInvalidChars')
val => val.length > 0 || t('admin.sites.hostnameMissing'),
val => /^(\\*)|([a-z0-9\-.:]+)$/.test(val) || t('admin.sites.hostnameInvalidChars')
]`
:hint='$t(`admin.sites.hostnameHint`)'
:hint='t(`admin.sites.hostnameHint`)'
hide-bottom-space
:label='$t(`admin.sites.hostname`)'
:aria-label='$t(`admin.sites.hostname`)'
:label='t(`admin.sites.hostname`)'
:aria-label='t(`admin.sites.hostname`)'
lazy-rules='ondemand'
)
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='$t(`common.actions.cancel`)'
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='hide'
@click='onDialogCancel'
)
q-btn(
unelevated
:label='$t(`common.actions.create`)'
:label='t(`common.actions.create`)'
color='primary'
padding='xs md'
@click='create'
:loading='isLoading'
:loading='state.isLoading'
)
</template>
<script>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { reactive, ref } from 'vue'
export default {
emits: ['ok', 'hide'],
data () {
return {
siteName: '',
siteHostname: 'wiki.example.com',
isLoading: false
import { useAdminStore } from '../stores/admin'
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
siteName: '',
siteHostname: 'wiki.example.com',
isLoading: false
})
// REFS
const createSiteForm = ref(null)
// METHODS
async function create () {
state.isLoading = true
try {
const isFormValid = await createSiteForm.value.validate(true)
if (!isFormValid) {
throw new Error(t('admin.sites.createInvalidData'))
}
},
methods: {
show () {
this.$refs.dialog.show()
},
hide () {
this.$refs.dialog.hide()
},
onDialogHide () {
this.$emit('hide')
},
async create () {
this.isLoading = true
try {
const isFormValid = await this.$refs.createSiteForm.validate(true)
if (!isFormValid) {
throw new Error(this.$t('admin.sites.createInvalidData'))
}
const resp = await this.$apollo.mutate({
mutation: gql`
mutation createSite (
$hostname: String!
$title: String!
) {
createSite(
hostname: $hostname
title: $title
) {
status {
succeeded
message
}
}
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation createSite (
$hostname: String!
$title: String!
) {
createSite(
hostname: $hostname
title: $title
) {
status {
succeeded
message
}
`,
variables: {
hostname: this.siteHostname,
title: this.siteName
}
})
if (resp?.data?.createSite?.status?.succeeded) {
this.$q.notify({
type: 'positive',
message: this.$t('admin.sites.createSuccess')
})
await this.$store.dispatch('admin/fetchSites')
this.$emit('ok')
this.hide()
} else {
throw new Error(resp?.data?.createSite?.status?.message || 'An unexpected error occured.')
}
} catch (err) {
this.$q.notify({
type: 'negative',
message: err.message
})
`,
variables: {
hostname: state.siteHostname,
title: state.siteName
}
this.isLoading = false
})
if (resp?.data?.createSite?.status?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.sites.createSuccess')
})
await adminStore.fetchSites()
onDialogOK()
} else {
throw new Error(resp?.data?.createSite?.status?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
}
state.isLoading = false
}
</script>

@ -138,6 +138,45 @@ body.desktop .acrylic-btn {
}
}
// ------------------------------------------------------------------
// DIALOGS
// ------------------------------------------------------------------
.card-header {
display: flex;
align-items: center;
font-weight: 500;
font-size: .9rem;
background-color: $dark-3;
background-image: radial-gradient(at bottom right, $dark-3, $dark-5);
color: #FFF;
@at-root .body--light & {
border-bottom: 1px solid $dark-3;
box-shadow: 0 1px 0 0 $dark-6;
}
@at-root .body--dark & {
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 lighten($dark-3, 2%);
}
}
.card-actions {
@at-root .body--light & {
background-color: #FAFAFA;
background-image: linear-gradient(to bottom, #FCFCFC, #F0F0F0);
color: $dark-3;
border-top: 1px solid #EEE;
box-shadow: inset 0 1px 0 0 #FFF;
}
@at-root .body--dark & {
background-color: $dark-3;
background-image: radial-gradient(at top left, $dark-3, $dark-5);
border-top: 1px solid #000;
box-shadow: 0 -1px 0 0 lighten($dark-3, 2%);
}
}
// ------------------------------------------------------------------
// IMPORTS
// ------------------------------------------------------------------

@ -167,8 +167,7 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section {{ t('admin.dev.flags.title') }}
q-page-container.admin-container
router-view(v-slot='{ Component }')
transition(name='fade')
component(:is='Component')
component(:is='Component')
q-dialog.admin-overlay(
v-model='overlayIsShown'
persistent

@ -4,8 +4,8 @@ q-page.admin-locale
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-change-theme.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.sites.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.sites.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.sites.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.sites.subtitle') }}
.col-auto
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
@ -16,7 +16,7 @@ q-page.admin-locale
target='_blank'
)
q-btn.q-mr-sm.acrylic-btn(
icon='las la-redo-alt'
icon='fa-solid fa-rotate'
flat
color='secondary'
@click='refresh'
@ -24,7 +24,7 @@ q-page.admin-locale
q-btn(
unelevated
icon='las la-plus'
:label='$t(`admin.sites.new`)'
:label='t(`admin.sites.new`)'
color='primary'
@click='createSite'
)
@ -34,7 +34,7 @@ q-page.admin-locale
q-card.shadow-1
q-list(separator)
q-item(
v-for='site of sites'
v-for='site of adminStore.sites'
:key='site.id'
)
q-item-section(side)
@ -75,8 +75,8 @@ q-page.admin-locale
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:label='$t(`admin.sites.isActive`)'
:aria-label='$t(`admin.sites.isActive`)'
:label='t(`admin.sites.isActive`)'
:aria-label='t(`admin.sites.isActive`)'
@update:model-value ='(val) => { toggleSiteState(site, val) }'
)
q-separator.q-ml-md(vertical)
@ -86,7 +86,7 @@ q-page.admin-locale
@click='editSite(site)'
icon='las la-pen'
color='indigo'
:label='$t(`common.actions.edit`)'
:label='t(`common.actions.edit`)'
no-caps
)
q-btn.acrylic-btn(
@ -97,82 +97,89 @@ q-page.admin-locale
)
</template>
<script>
import { get } from 'vuex-pathify'
import { copyToClipboard, createMetaMixin } from 'quasar'
<script setup>
import { useMeta, useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { defineAsyncComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useAdminStore } from '../stores/admin'
// COMPONENTS
import SiteActivateDialog from '../components/SiteActivateDialog.vue'
import SiteCreateDialog from '../components/SiteCreateDialog.vue'
import SiteDeleteDialog from '../components/SiteDeleteDialog.vue'
export default {
mixins: [
createMetaMixin(function () {
return {
title: this.$t('admin.sites.title')
}
})
],
data () {
return {
loading: false
// QUASAR
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
// ROUTER
const router = useRouter()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('admin.sites.title')
})
// DATA
const loading = ref(false)
// METHODS
async function refresh () {
await adminStore.fetchSites()
$q.notify({
type: 'positive',
message: t('admin.sites.refreshSuccess')
})
}
function createSite () {
$q.dialog({
component: SiteCreateDialog
})
}
function editSite (st) {
adminStore.$patch({
currentSiteId: st.id
})
nextTick(() => {
router.push(`/_admin/${st.id}/general`)
})
}
function toggleSiteState (st, newState) {
$q.dialog({
component: SiteActivateDialog,
componentProps: {
site: st,
value: newState
}
},
computed: {
sites: get('admin/sites', false)
},
methods: {
copyID (uid) {
copyToClipboard(uid).then(() => {
this.$q.notify({
type: 'positive',
message: this.$t('common.clipboard.uuidSuccess')
})
}).catch(() => {
this.$q.notify({
type: 'negative',
message: this.$t('common.clipboard.uuidFailure')
})
})
},
async refresh () {
await this.$store.dispatch('admin/fetchSites')
this.$q.notify({
type: 'positive',
message: this.$t('admin.sites.refreshSuccess')
})
},
createSite () {
this.$q.dialog({
component: SiteCreateDialog
})
},
editSite (st) {
this.$store.set('admin/currentSiteId', st.id)
this.$nextTick(() => {
this.$router.push(`/_admin/${st.id}/general`)
})
},
toggleSiteState (st, newState) {
this.$q.dialog({
component: SiteActivateDialog,
componentProps: {
site: st,
value: newState
}
})
},
deleteSite (st) {
this.$q.dialog({
component: SiteDeleteDialog,
componentProps: {
site: st
}
})
})
}
function deleteSite (st) {
$q.dialog({
component: SiteDeleteDialog,
componentProps: {
site: st
}
},
mounted () {
this.$store.dispatch('admin/fetchSites')
}
})
}
// MOUNTED
onMounted(async () => {
await adminStore.fetchSites()
})
</script>

Loading…
Cancel
Save