Unverified Commit dfde2e10 authored by NGPixel's avatar NGPixel

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

parent 70a2e3ca
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"src/i18n", "src/i18n",
"src/i18n/locales" "src/i18n/locales"
] ],
"i18n-ally.keystyle": "nested"
} }
} }
<template lang="pug"> <template lang="pug">
q-dialog(ref='dialog', @hide='onDialogHide') q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 450px;') q-card(style='min-width: 450px;')
q-card-section.card-header q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-plus-plus.svg', left, size='sm') 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-form.q-py-sm(ref='createSiteForm')
q-item q-item
blueprint-icon(icon='home') blueprint-icon(icon='home')
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='siteName' v-model='state.siteName'
dense dense
:rules=`[ :rules=`[
val => val.length > 0 || $t('admin.sites.nameMissing'), val => val.length > 0 || t('admin.sites.nameMissing'),
val => /^[^<>"]+$/.test(val) || $t('admin.sites.nameInvalidChars') val => /^[^<>"]+$/.test(val) || t('admin.sites.nameInvalidChars')
]` ]`
hide-bottom-space hide-bottom-space
:label='$t(`common.field.name`)' :label='t(`common.field.name`)'
:aria-label='$t(`common.field.name`)' :aria-label='t(`common.field.name`)'
lazy-rules='ondemand' lazy-rules='ondemand'
autofocus autofocus
) )
...@@ -27,67 +27,84 @@ q-dialog(ref='dialog', @hide='onDialogHide') ...@@ -27,67 +27,84 @@ q-dialog(ref='dialog', @hide='onDialogHide')
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='siteHostname' v-model='state.siteHostname'
dense dense
:rules=`[ :rules=`[
val => val.length > 0 || $t('admin.sites.hostnameMissing'), val => val.length > 0 || t('admin.sites.hostnameMissing'),
val => /^(\\*)|([a-z0-9\-.:]+)$/.test(val) || $t('admin.sites.hostnameInvalidChars') val => /^(\\*)|([a-z0-9\-.:]+)$/.test(val) || t('admin.sites.hostnameInvalidChars')
]` ]`
:hint='$t(`admin.sites.hostnameHint`)' :hint='t(`admin.sites.hostnameHint`)'
hide-bottom-space hide-bottom-space
:label='$t(`admin.sites.hostname`)' :label='t(`admin.sites.hostname`)'
:aria-label='$t(`admin.sites.hostname`)' :aria-label='t(`admin.sites.hostname`)'
lazy-rules='ondemand' lazy-rules='ondemand'
) )
q-card-actions.card-actions q-card-actions.card-actions
q-space q-space
q-btn.acrylic-btn( q-btn.acrylic-btn(
flat flat
:label='$t(`common.actions.cancel`)' :label='t(`common.actions.cancel`)'
color='grey' color='grey'
padding='xs md' padding='xs md'
@click='hide' @click='onDialogCancel'
) )
q-btn( q-btn(
unelevated unelevated
:label='$t(`common.actions.create`)' :label='t(`common.actions.create`)'
color='primary' color='primary'
padding='xs md' padding='xs md'
@click='create' @click='create'
:loading='isLoading' :loading='state.isLoading'
) )
</template> </template>
<script> <script setup>
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { reactive, ref } from 'vue'
export default { import { useAdminStore } from '../stores/admin'
emits: ['ok', 'hide'],
data () { defineEmits([
return { ...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: '', siteName: '',
siteHostname: 'wiki.example.com', siteHostname: 'wiki.example.com',
isLoading: false isLoading: false
} })
},
methods: { // REFS
show () {
this.$refs.dialog.show() const createSiteForm = ref(null)
},
hide () { // METHODS
this.$refs.dialog.hide()
}, async function create () {
onDialogHide () { state.isLoading = true
this.$emit('hide')
},
async create () {
this.isLoading = true
try { try {
const isFormValid = await this.$refs.createSiteForm.validate(true) const isFormValid = await createSiteForm.value.validate(true)
if (!isFormValid) { if (!isFormValid) {
throw new Error(this.$t('admin.sites.createInvalidData')) throw new Error(t('admin.sites.createInvalidData'))
} }
const resp = await this.$apollo.mutate({ const resp = await APOLLO_CLIENT.mutate({
mutation: gql` mutation: gql`
mutation createSite ( mutation createSite (
$hostname: String! $hostname: String!
...@@ -105,29 +122,26 @@ export default { ...@@ -105,29 +122,26 @@ export default {
} }
`, `,
variables: { variables: {
hostname: this.siteHostname, hostname: state.siteHostname,
title: this.siteName title: state.siteName
} }
}) })
if (resp?.data?.createSite?.status?.succeeded) { if (resp?.data?.createSite?.status?.succeeded) {
this.$q.notify({ $q.notify({
type: 'positive', type: 'positive',
message: this.$t('admin.sites.createSuccess') message: t('admin.sites.createSuccess')
}) })
await this.$store.dispatch('admin/fetchSites') await adminStore.fetchSites()
this.$emit('ok') onDialogOK()
this.hide()
} else { } else {
throw new Error(resp?.data?.createSite?.status?.message || 'An unexpected error occured.') throw new Error(resp?.data?.createSite?.status?.message || 'An unexpected error occured.')
} }
} catch (err) { } catch (err) {
this.$q.notify({ $q.notify({
type: 'negative', type: 'negative',
message: err.message message: err.message
}) })
} }
this.isLoading = false state.isLoading = false
}
}
} }
</script> </script>
...@@ -139,6 +139,45 @@ body.desktop .acrylic-btn { ...@@ -139,6 +139,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 // IMPORTS
// ------------------------------------------------------------------ // ------------------------------------------------------------------
......
...@@ -167,7 +167,6 @@ q-layout.admin(view='hHh Lpr lff') ...@@ -167,7 +167,6 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section {{ t('admin.dev.flags.title') }} q-item-section {{ t('admin.dev.flags.title') }}
q-page-container.admin-container q-page-container.admin-container
router-view(v-slot='{ Component }') router-view(v-slot='{ Component }')
transition(name='fade')
component(:is='Component') component(:is='Component')
q-dialog.admin-overlay( q-dialog.admin-overlay(
v-model='overlayIsShown' v-model='overlayIsShown'
......
...@@ -4,8 +4,8 @@ q-page.admin-locale ...@@ -4,8 +4,8 @@ q-page.admin-locale
.col-auto .col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-change-theme.svg') img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-change-theme.svg')
.col.q-pl-md .col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.sites.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.sites.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.sites.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.sites.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'
...@@ -16,7 +16,7 @@ q-page.admin-locale ...@@ -16,7 +16,7 @@ q-page.admin-locale
target='_blank' target='_blank'
) )
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='las la-redo-alt' icon='fa-solid fa-rotate'
flat flat
color='secondary' color='secondary'
@click='refresh' @click='refresh'
...@@ -24,7 +24,7 @@ q-page.admin-locale ...@@ -24,7 +24,7 @@ q-page.admin-locale
q-btn( q-btn(
unelevated unelevated
icon='las la-plus' icon='las la-plus'
:label='$t(`admin.sites.new`)' :label='t(`admin.sites.new`)'
color='primary' color='primary'
@click='createSite' @click='createSite'
) )
...@@ -34,7 +34,7 @@ q-page.admin-locale ...@@ -34,7 +34,7 @@ q-page.admin-locale
q-card.shadow-1 q-card.shadow-1
q-list(separator) q-list(separator)
q-item( q-item(
v-for='site of sites' v-for='site of adminStore.sites'
:key='site.id' :key='site.id'
) )
q-item-section(side) q-item-section(side)
...@@ -75,8 +75,8 @@ q-page.admin-locale ...@@ -75,8 +75,8 @@ q-page.admin-locale
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:label='$t(`admin.sites.isActive`)' :label='t(`admin.sites.isActive`)'
:aria-label='$t(`admin.sites.isActive`)' :aria-label='t(`admin.sites.isActive`)'
@update:model-value ='(val) => { toggleSiteState(site, val) }' @update:model-value ='(val) => { toggleSiteState(site, val) }'
) )
q-separator.q-ml-md(vertical) q-separator.q-ml-md(vertical)
...@@ -86,7 +86,7 @@ q-page.admin-locale ...@@ -86,7 +86,7 @@ q-page.admin-locale
@click='editSite(site)' @click='editSite(site)'
icon='las la-pen' icon='las la-pen'
color='indigo' color='indigo'
:label='$t(`common.actions.edit`)' :label='t(`common.actions.edit`)'
no-caps no-caps
) )
q-btn.acrylic-btn( q-btn.acrylic-btn(
...@@ -97,82 +97,89 @@ q-page.admin-locale ...@@ -97,82 +97,89 @@ q-page.admin-locale
) )
</template> </template>
<script> <script setup>
import { get } from 'vuex-pathify' import { useMeta, useQuasar } from 'quasar'
import { copyToClipboard, createMetaMixin } 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 SiteActivateDialog from '../components/SiteActivateDialog.vue'
import SiteCreateDialog from '../components/SiteCreateDialog.vue' import SiteCreateDialog from '../components/SiteCreateDialog.vue'
import SiteDeleteDialog from '../components/SiteDeleteDialog.vue' import SiteDeleteDialog from '../components/SiteDeleteDialog.vue'
export default { // QUASAR
mixins: [
createMetaMixin(function () { const $q = useQuasar()
return {
title: this.$t('admin.sites.title') // STORES
}
}) const adminStore = useAdminStore()
],
data () { // ROUTER
return {
loading: false const router = useRouter()
}
}, // I18N
computed: {
sites: get('admin/sites', false) const { t } = useI18n()
},
methods: { // META
copyID (uid) {
copyToClipboard(uid).then(() => { useMeta({
this.$q.notify({ title: t('admin.sites.title')
type: 'positive', })
message: this.$t('common.clipboard.uuidSuccess')
}) // DATA
}).catch(() => {
this.$q.notify({ const loading = ref(false)
type: 'negative',
message: this.$t('common.clipboard.uuidFailure') // METHODS
})
}) async function refresh () {
}, await adminStore.fetchSites()
async refresh () { $q.notify({
await this.$store.dispatch('admin/fetchSites')
this.$q.notify({
type: 'positive', type: 'positive',
message: this.$t('admin.sites.refreshSuccess') message: t('admin.sites.refreshSuccess')
}) })
}, }
createSite () { function createSite () {
this.$q.dialog({ $q.dialog({
component: SiteCreateDialog component: SiteCreateDialog
}) })
}, }
editSite (st) { function editSite (st) {
this.$store.set('admin/currentSiteId', st.id) adminStore.$patch({
this.$nextTick(() => { currentSiteId: st.id
this.$router.push(`/_admin/${st.id}/general`)
}) })
}, nextTick(() => {
toggleSiteState (st, newState) { router.push(`/_admin/${st.id}/general`)
this.$q.dialog({ })
}
function toggleSiteState (st, newState) {
$q.dialog({
component: SiteActivateDialog, component: SiteActivateDialog,
componentProps: { componentProps: {
site: st, site: st,
value: newState value: newState
} }
}) })
}, }
deleteSite (st) { function deleteSite (st) {
this.$q.dialog({ $q.dialog({
component: SiteDeleteDialog, component: SiteDeleteDialog,
componentProps: { componentProps: {
site: st site: st
} }
}) })
}
},
mounted () {
this.$store.dispatch('admin/fetchSites')
}
} }
// MOUNTED
onMounted(async () => {
await adminStore.fetchSites()
})
</script> </script>
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