Browse Source

refactor: admin general to vue3 comp api

NGPixel 3 years ago
parent
commit
464f55cf46
2 changed files with 401 additions and 371 deletions
  1. 400 370
      ux/src/pages/AdminGeneral.vue
  2. 1 1
      ux/src/router/routes.js

+ 400 - 370
ux/src/pages/AdminGeneral.vue

@@ -4,8 +4,8 @@ q-page.admin-general
     .col-auto
       img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-web.svg')
     .col.q-pl-md
-      .text-h5.text-primary.animated.fadeInLeft {{ $t('admin.general.title') }}
-      .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.general.subtitle') }}
+      .text-h5.text-primary.animated.fadeInLeft {{ t('admin.general.title') }}
+      .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.general.subtitle') }}
     .col-auto
       q-btn.q-mr-sm.acrylic-btn(
         icon='las la-question-circle'
@@ -16,19 +16,19 @@ q-page.admin-general
         type='a'
         )
       q-btn.q-mr-sm.acrylic-btn(
-        icon='las la-redo-alt'
+        icon='fa-solid fa-rotate'
         flat
         color='secondary'
-        :loading='loading > 0'
+        :loading='state.loading > 0'
         @click='load'
         )
       q-btn(
         unelevated
-        icon='mdi-check'
-        :label='$t(`common.actions.apply`)'
+        icon='fa-solid fa-check'
+        :label='t(`common.actions.apply`)'
         color='secondary'
         @click='save'
-        :disabled='loading > 0'
+        :disabled='state.loading > 0'
       )
   q-separator(inset)
   .row.q-pa-md.q-col-gutter-md
@@ -38,52 +38,48 @@ q-page.admin-general
       //- -----------------------
       q-card.shadow-1.q-pb-sm
         q-card-section
-          .text-subtitle1 {{$t('admin.general.siteInfo')}}
+          .text-subtitle1 {{t('admin.general.siteInfo')}}
         q-item
           blueprint-icon(icon='home')
           q-item-section
-            q-item-label {{$t(`admin.general.siteTitle`)}}
-            q-item-label(caption) {{$t(`admin.general.siteTitleHint`)}}
+            q-item-label {{t(`admin.general.siteTitle`)}}
+            q-item-label(caption) {{t(`admin.general.siteTitleHint`)}}
           q-item-section
             q-input(
               outlined
-              v-model='config.title'
+              v-model='state.config.title'
               dense
-              :rules=`[
-                val => /^[^<>"]+$/.test(val) || $t('admin.general.siteTitleInvalidChars')
-              ]`
+              :rules='rulesTitle'
               hide-bottom-space
-              :aria-label='$t(`admin.general.siteTitle`)'
+              :aria-label='t(`admin.general.siteTitle`)'
               )
         q-separator.q-my-sm(inset)
         q-item
           blueprint-icon(icon='select-all')
           q-item-section
-            q-item-label {{$t(`admin.general.siteDescription`)}}
-            q-item-label(caption) {{$t(`admin.general.siteDescriptionHint`)}}
+            q-item-label {{t(`admin.general.siteDescription`)}}
+            q-item-label(caption) {{t(`admin.general.siteDescriptionHint`)}}
           q-item-section
             q-input(
               outlined
-              v-model='config.description'
+              v-model='state.config.description'
               dense
-              :aria-label='$t(`admin.general.siteDescription`)'
+              :aria-label='t(`admin.general.siteDescription`)'
               )
         q-separator.q-my-sm(inset)
         q-item
           blueprint-icon(icon='dns')
           q-item-section
-            q-item-label {{$t(`admin.general.siteHostname`)}}
-            q-item-label(caption) {{$t(`admin.general.siteHostnameHint`)}}
+            q-item-label {{t(`admin.general.siteHostname`)}}
+            q-item-label(caption) {{t(`admin.general.siteHostnameHint`)}}
           q-item-section
             q-input(
               outlined
-              v-model='config.hostname'
+              v-model='state.config.hostname'
               dense
-              :rules=`[
-                val => /^(([a-z0-9.-]+)|([*]{1}))$/.test(val) || $t('admin.general.siteHostnameInvalid')
-              ]`
+              :rules='rulesHostname'
               hide-bottom-space
-              :aria-label='$t(`admin.general.siteHostname`)'
+              :aria-label='t(`admin.general.siteHostname`)'
               )
 
       //- -----------------------
@@ -91,36 +87,36 @@ q-page.admin-general
       //- -----------------------
       q-card.shadow-1.q-pb-sm.q-mt-md
         q-card-section
-          .text-subtitle1 {{$t('admin.general.footerCopyright')}}
+          .text-subtitle1 {{t('admin.general.footerCopyright')}}
         q-item
           blueprint-icon(icon='building')
           q-item-section
-            q-item-label {{$t(`admin.general.companyName`)}}
-            q-item-label(caption) {{$t(`admin.general.companyNameHint`)}}
+            q-item-label {{t(`admin.general.companyName`)}}
+            q-item-label(caption) {{t(`admin.general.companyNameHint`)}}
           q-item-section
             q-input(
               outlined
-              v-model='config.company'
+              v-model='state.config.company'
               dense
-              :aria-label='$t(`admin.general.companyName`)'
+              :aria-label='t(`admin.general.companyName`)'
               )
         q-separator.q-my-sm(inset)
         q-item
           blueprint-icon(icon='copyright')
           q-item-section
-            q-item-label {{$t(`admin.general.contentLicense`)}}
-            q-item-label(caption) {{$t(`admin.general.contentLicenseHint`)}}
+            q-item-label {{t(`admin.general.contentLicense`)}}
+            q-item-label(caption) {{t(`admin.general.contentLicenseHint`)}}
           q-item-section
             q-select(
               outlined
-              v-model='config.contentLicense'
+              v-model='state.config.contentLicense'
               :options='contentLicenses'
               option-value='value'
               option-label='text'
               emit-value
               map-options
               dense
-              :aria-label='$t(`admin.general.contentLicense`)'
+              :aria-label='t(`admin.general.contentLicense`)'
               )
 
       //- -----------------------
@@ -128,80 +124,76 @@ q-page.admin-general
       //- -----------------------
       q-card.shadow-1.q-pb-sm.q-mt-md
         q-card-section
-          .text-subtitle1 {{$t('admin.general.features')}}
+          .text-subtitle1 {{t('admin.general.features')}}
         q-item(tag='label')
           blueprint-icon(icon='discussion-forum')
           q-item-section
-            q-item-label {{$t(`admin.general.allowComments`)}}
-            q-item-label(caption) {{$t(`admin.general.allowCommentsHint`)}}
+            q-item-label {{t(`admin.general.allowComments`)}}
+            q-item-label(caption) {{t(`admin.general.allowCommentsHint`)}}
           q-item-section(avatar)
             q-toggle(
-              v-model='config.features.comments'
+              v-model='state.config.features.comments'
               color='primary'
               checked-icon='las la-check'
               unchecked-icon='las la-times'
-              :aria-label='$t(`admin.general.allowComments`)'
+              :aria-label='t(`admin.general.allowComments`)'
               )
         q-separator.q-my-sm(inset)
         q-item(tag='label')
           blueprint-icon(icon='pen')
           q-item-section
-            q-item-label {{$t(`admin.general.allowContributions`)}}
-            q-item-label(caption) {{$t(`admin.general.allowContributionsHint`)}}
+            q-item-label {{t(`admin.general.allowContributions`)}}
+            q-item-label(caption) {{t(`admin.general.allowContributionsHint`)}}
           q-item-section(avatar)
             q-toggle(
-              v-model='config.features.contributions'
+              v-model='state.config.features.contributions'
               color='primary'
               checked-icon='las la-check'
               unchecked-icon='las la-times'
-              :aria-label='$t(`admin.general.allowContributions`)'
+              :aria-label='t(`admin.general.allowContributions`)'
               )
         q-separator.q-my-sm(inset)
         q-item(tag='label')
           blueprint-icon(icon='administrator-male')
           q-item-section
-            q-item-label {{$t(`admin.general.allowProfile`)}}
-            q-item-label(caption) {{$t(`admin.general.allowProfileHint`)}}
+            q-item-label {{t(`admin.general.allowProfile`)}}
+            q-item-label(caption) {{t(`admin.general.allowProfileHint`)}}
           q-item-section(avatar)
             q-toggle(
-              v-model='config.features.profile'
+              v-model='state.config.features.profile'
               color='primary'
               checked-icon='las la-check'
               unchecked-icon='las la-times'
-              :aria-label='$t(`admin.general.allowProfile`)'
+              :aria-label='t(`admin.general.allowProfile`)'
               )
         q-separator.q-my-sm(inset)
         q-item
           blueprint-icon(icon='star-half-empty')
           q-item-section
-            q-item-label {{$t(`admin.general.allowRatings`)}}
-            q-item-label(caption) {{$t(`admin.general.allowRatingsHint`)}}
+            q-item-label {{t(`admin.general.allowRatings`)}}
+            q-item-label(caption) {{t(`admin.general.allowRatingsHint`)}}
           q-item-section.col-auto
             q-btn-toggle(
-              v-model='config.features.ratingsMode'
+              v-model='state.config.features.ratingsMode'
               push
               glossy
               no-caps
               toggle-color='primary'
-              :options=`[
-                { label: $t('admin.general.ratingsOff'), value: 'off' },
-                { label: $t('admin.general.ratingsThumbs'), value: 'thumbs' },
-                { label: $t('admin.general.ratingsStars'), value: 'stars' }
-              ]`
+              :options='ratingsModes'
             )
         q-separator.q-my-sm(inset)
         q-item(tag='label')
           blueprint-icon(icon='search')
           q-item-section
-            q-item-label {{$t(`admin.general.allowSearch`)}}
-            q-item-label(caption) {{$t(`admin.general.allowSearchHint`)}}
+            q-item-label {{t(`admin.general.allowSearch`)}}
+            q-item-label(caption) {{t(`admin.general.allowSearchHint`)}}
           q-item-section(avatar)
             q-toggle(
-              v-model='config.features.search'
+              v-model='state.config.features.search'
               color='primary'
               checked-icon='las la-check'
               unchecked-icon='las la-times'
-              :aria-label='$t(`admin.general.allowSearch`)'
+              :aria-label='t(`admin.general.allowSearch`)'
               )
 
     .col-12.col-lg-5
@@ -210,14 +202,14 @@ q-page.admin-general
       //- -----------------------
       q-card.shadow-1.q-pb-sm
         q-card-section
-          .text-subtitle1 {{$t('admin.general.logo')}}
+          .text-subtitle1 {{t('admin.general.logo')}}
         q-item
-          blueprint-icon.self-start(icon='butterfly', indicator, :indicator-text='$t(`admin.extensions.requiresSharp`)')
+          blueprint-icon.self-start(icon='butterfly', indicator, :indicator-text='t(`admin.extensions.requiresSharp`)')
           q-item-section
             .flex
               q-item-section
-                q-item-label {{$t(`admin.general.logoUpl`)}}
-                q-item-label(caption) {{$t(`admin.general.logoUplHint`)}}
+                q-item-label {{t(`admin.general.logoUpl`)}}
+                q-item-label(caption) {{t(`admin.general.logoUplHint`)}}
               q-item-section.col-auto
                 q-btn(
                   label='Upload'
@@ -233,7 +225,7 @@ q-page.admin-general
               )
               q-btn(dense, flat, to='/')
                 q-avatar(
-                  v-if='config.logoText'
+                  v-if='state.config.logoText'
                   size='34px'
                   square
                   )
@@ -243,29 +235,29 @@ q-page.admin-general
                   src='https://m.media-amazon.com/images/G/01/audibleweb/arya/navigation/audible_logo._V517446980_.svg'
                   style='height: 34px;'
                   )
-              q-toolbar-title.text-h6.font-poppins(v-if='config.logoText') {{config.title}}
+              q-toolbar-title.text-h6.font-poppins(v-if='state.config.logoText') {{state.config.title}}
         q-separator.q-my-sm(inset)
         q-item(tag='label')
           blueprint-icon(icon='information')
           q-item-section
-            q-item-label {{$t(`admin.general.displaySiteTitle`)}}
-            q-item-label(caption) {{$t(`admin.general.displaySiteTitleHint`)}}
+            q-item-label {{t(`admin.general.displaySiteTitle`)}}
+            q-item-label(caption) {{t(`admin.general.displaySiteTitleHint`)}}
           q-item-section(avatar)
             q-toggle(
-              v-model='config.logoText'
+              v-model='state.config.logoText'
               color='primary'
               checked-icon='las la-check'
               unchecked-icon='las la-times'
-              :aria-label='$t(`admin.general.displaySiteTitle`)'
+              :aria-label='t(`admin.general.displaySiteTitle`)'
               )
         q-separator.q-my-sm(inset)
         q-item
-          blueprint-icon.self-start(icon='starfish', indicator, :indicator-text='$t(`admin.extensions.requiresSharp`)')
+          blueprint-icon.self-start(icon='starfish', indicator, :indicator-text='t(`admin.extensions.requiresSharp`)')
           q-item-section
             .flex
               q-item-section
-                q-item-label {{$t(`admin.general.favicon`)}}
-                q-item-label(caption) {{$t(`admin.general.faviconHint`)}}
+                q-item-label {{t(`admin.general.favicon`)}}
+                q-item-label(caption) {{t(`admin.general.faviconHint`)}}
               q-item-section.col-auto
                 q-btn(
                   label='Upload'
@@ -282,7 +274,7 @@ q-page.admin-general
                   square
                   )
                   img(src='/_assets/logo-wikijs.svg')
-                .text-caption.q-ml-sm {{config.title}}
+                .text-caption.q-ml-sm {{state.config.title}}
               div
                 q-icon(name='las la-otter', size='24px', color='grey')
                 .text-caption.q-ml-sm Lorem ipsum
@@ -293,19 +285,19 @@ q-page.admin-general
       //- -----------------------
       //- Defaults
       //- -----------------------
-      q-card.shadow-1.q-pb-sm.q-mt-md(v-if='config.defaults')
+      q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.config.defaults')
         q-card-section
-          .text-subtitle1 {{$t('admin.general.defaults')}}
+          .text-subtitle1 {{t('admin.general.defaults')}}
         q-item
           blueprint-icon(icon='timezone')
           q-item-section
-            q-item-label {{$t(`admin.general.defaultTimezone`)}}
-            q-item-label(caption) {{$t(`admin.general.defaultTimezoneHint`)}}
+            q-item-label {{t(`admin.general.defaultTimezone`)}}
+            q-item-label(caption) {{t(`admin.general.defaultTimezoneHint`)}}
           q-item-section
             q-select(
               outlined
-              v-model='config.defaults.timezone'
-              :options='timezones'
+              v-model='state.config.defaults.timezone'
+              :options='dataStore.timezones'
               option-value='value'
               option-label='text'
               emit-value
@@ -313,372 +305,410 @@ q-page.admin-general
               dense
               options-dense
               :virtual-scroll-slice-size='1000'
-              :aria-label='$t(`admin.general.defaultTimezone`)'
+              :aria-label='t(`admin.general.defaultTimezone`)'
               )
         q-separator.q-my-sm(inset)
         q-item
           blueprint-icon(icon='calendar')
           q-item-section
-            q-item-label {{$t(`admin.general.defaultDateFormat`)}}
-            q-item-label(caption) {{$t(`admin.general.defaultDateFormatHint`)}}
+            q-item-label {{t(`admin.general.defaultDateFormat`)}}
+            q-item-label(caption) {{t(`admin.general.defaultDateFormatHint`)}}
           q-item-section
             q-select(
               outlined
-              v-model='config.defaults.dateFormat'
+              v-model='state.config.defaults.dateFormat'
               emit-value
               map-options
               dense
-              :aria-label='$t(`admin.general.defaultDateFormat`)'
-              :options=`[
-                { label: $t('profile.localeDefault'), value: '' },
-                { label: 'DD/MM/YYYY', value: 'DD/MM/YYYY' },
-                { label: 'DD.MM.YYYY', value: 'DD.MM.YYYY' },
-                { label: 'MM/DD/YYYY', value: 'MM/DD/YYYY' },
-                { label: 'YYYY-MM-DD', value: 'YYYY-MM-DD' },
-                { label: 'YYYY/MM/DD', value: 'YYYY/MM/DD' }
-              ]`
+              :aria-label='t(`admin.general.defaultDateFormat`)'
+              :options='dateFormats'
               )
         q-separator.q-my-sm(inset)
         q-item
           blueprint-icon(icon='clock')
           q-item-section
-            q-item-label {{$t(`admin.general.defaultTimeFormat`)}}
-            q-item-label(caption) {{$t(`admin.general.defaultTimeFormatHint`)}}
+            q-item-label {{t(`admin.general.defaultTimeFormat`)}}
+            q-item-label(caption) {{t(`admin.general.defaultTimeFormatHint`)}}
           q-item-section.col-auto
             q-btn-toggle(
-              v-model='config.defaults.timeFormat'
+              v-model='state.config.defaults.timeFormat'
               push
               glossy
               no-caps
               toggle-color='primary'
-              :options=`[
-                { label: $t('admin.general.defaultTimeFormat12h'), value: '12h' },
-                { label: $t('admin.general.defaultTimeFormat24h'), value: '24h' }
-              ]`
+              :options='timeFormats'
             )
 
       //- -----------------------
       //- SEO
       //- -----------------------
-      q-card.shadow-1.q-pb-sm.q-mt-md(v-if='config.robots')
+      q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.config.robots')
         q-card-section
           .text-subtitle1 SEO
         q-item(tag='label')
           blueprint-icon(icon='bot')
           q-item-section
-            q-item-label {{$t(`admin.general.searchAllowIndexing`)}}
-            q-item-label(caption) {{$t(`admin.general.searchAllowIndexingHint`)}}
+            q-item-label {{t(`admin.general.searchAllowIndexing`)}}
+            q-item-label(caption) {{t(`admin.general.searchAllowIndexingHint`)}}
           q-item-section(avatar)
             q-toggle(
-              v-model='config.robots.index'
+              v-model='state.config.robots.index'
               color='primary'
               checked-icon='las la-check'
               unchecked-icon='las la-times'
-              :aria-label='$t(`admin.general.searchAllowIndexing`)'
+              :aria-label='t(`admin.general.searchAllowIndexing`)'
               )
         q-separator.q-my-sm(inset)
         q-item(tag='label')
           blueprint-icon(icon='polyline')
           q-item-section
-            q-item-label {{$t(`admin.general.searchAllowFollow`)}}
-            q-item-label(caption) {{$t(`admin.general.searchAllowFollowHint`)}}
+            q-item-label {{t(`admin.general.searchAllowFollow`)}}
+            q-item-label(caption) {{t(`admin.general.searchAllowFollowHint`)}}
           q-item-section(avatar)
             q-toggle(
-              v-model='config.robots.follow'
+              v-model='state.config.robots.follow'
               color='primary'
               checked-icon='las la-check'
               unchecked-icon='las la-times'
-              :aria-label='$t(`admin.general.searchAllowFollow`)'
+              :aria-label='t(`admin.general.searchAllowFollow`)'
               )
 
 </template>
 
-<script>
-import { get } from 'vuex-pathify'
+<script setup>
 import gql from 'graphql-tag'
 import cloneDeep from 'lodash/cloneDeep'
-import { createMetaMixin } from 'quasar'
+import { useI18n } from 'vue-i18n'
+import { useMeta, useQuasar } from 'quasar'
+import { onMounted, reactive, watch } from 'vue'
 
-export default {
-  mixins: [
-    createMetaMixin(function () {
-      return {
-        title: this.$t('admin.general.title')
-      }
-    })
-  ],
-  data () {
-    return {
-      loading: 0,
-      config: {
-        hostname: '',
-        title: '',
-        description: '',
-        company: '',
-        contentLicense: '',
-        logoText: false,
-        ratings: {
-          index: false,
-          follow: false
-        },
-        features: {
-          ratings: false,
-          ratingsMode: 'off',
-          comments: false,
-          contributions: false,
-          profile: false
-        },
-        defaults: {
-          timezone: '',
-          dateFormat: '',
-          timeFormat: ''
+import { useAdminStore } from 'src/stores/admin'
+import { useSiteStore } from 'src/stores/site'
+import { useDataStore } from 'src/stores/data'
+import { useRoute, useRouter } from 'vue-router'
+
+// QUASAR
+
+const $q = useQuasar()
+
+// STORES
+
+const adminStore = useAdminStore()
+const siteStore = useSiteStore()
+const dataStore = useDataStore()
+
+// ROUTER
+
+const router = useRouter()
+const route = useRoute()
+
+// I18N
+
+const { t } = useI18n()
+
+// META
+
+useMeta({
+  title: t('admin.dashboard.title')
+})
+
+// DATA
+
+const state = reactive({
+  loading: 0,
+  config: {
+    hostname: '',
+    title: '',
+    description: '',
+    company: '',
+    contentLicense: '',
+    logoText: false,
+    ratings: {
+      index: false,
+      follow: false
+    },
+    features: {
+      ratings: false,
+      ratingsMode: 'off',
+      comments: false,
+      contributions: false,
+      profile: false
+    },
+    defaults: {
+      timezone: '',
+      dateFormat: '',
+      timeFormat: ''
+    }
+  }
+})
+
+const contentLicenses = [
+  { value: '', text: t('common.license.none') },
+  { value: 'alr', text: t('common.license.alr') },
+  { value: 'cc0', text: t('common.license.cc0') },
+  { value: 'ccby', text: t('common.license.ccby') },
+  { value: 'ccbysa', text: t('common.license.ccbysa') },
+  { value: 'ccbynd', text: t('common.license.ccbynd') },
+  { value: 'ccbync', text: t('common.license.ccbync') },
+  { value: 'ccbyncsa', text: t('common.license.ccbyncsa') },
+  { value: 'ccbyncnd', text: t('common.license.ccbyncnd') }
+]
+const ratingsModes = [
+  { value: 'off', label: t('admin.general.ratingsOff') },
+  { value: 'thumbs', label: t('admin.general.ratingsThumbs') },
+  { value: 'stars', label: t('admin.general.ratingsStars') }
+]
+const dateFormats = [
+  { value: '', label: t('profile.localeDefault') },
+  { value: 'DD/MM/YYYY', label: 'DD/MM/YYYY' },
+  { value: 'DD.MM.YYYY', label: 'DD.MM.YYYY' },
+  { value: 'MM/DD/YYYY', label: 'MM/DD/YYYY' },
+  { value: 'YYYY-MM-DD', label: 'YYYY-MM-DD' },
+  { value: 'YYYY/MM/DD', label: 'YYYY/MM/DD' }
+]
+const timeFormats = [
+  { value: '12h', label: t('admin.general.defaultTimeFormat12h') },
+  { value: '24h', label: t('admin.general.defaultTimeFormat24h') }
+]
+
+const rulesTitle = [
+  val => /^[^<>"]+$/.test(val) || t('admin.general.siteTitleInvalidChars')
+]
+const rulesHostname = [
+  val => /^(([a-z0-9.-]+)|([*]{1}))$/.test(val) || t('admin.general.siteHostnameInvalid')
+]
+
+// WATCHERS
+
+watch(() => adminStore.currentSiteId, (newValue) => {
+  load()
+})
+
+// METHODS
+
+async function load () {
+  state.loading++
+  $q.loading.show()
+  const resp = await APOLLO_CLIENT.query({
+    query: gql`
+      query getSite (
+        $id: UUID!
+      ) {
+        siteById(
+          id: $id
+        ) {
+          id
+          hostname
+          isEnabled
+          title
+          description
+          company
+          contentLicense
+          logoText
+          robots {
+            index
+            follow
+          }
+          features {
+            comments
+            ratings
+            ratingsMode
+            contributions
+            profile
+            search
+          }
+          defaults {
+            timezone
+            dateFormat
+            timeFormat
+          }
         }
       }
-    }
-  },
-  computed: {
-    currentSiteId: get('admin/currentSiteId', false),
-    contentLicenses () {
-      return [
-        { value: '', text: this.$t('common.license.none') },
-        { value: 'alr', text: this.$t('common.license.alr') },
-        { value: 'cc0', text: this.$t('common.license.cc0') },
-        { value: 'ccby', text: this.$t('common.license.ccby') },
-        { value: 'ccbysa', text: this.$t('common.license.ccbysa') },
-        { value: 'ccbynd', text: this.$t('common.license.ccbynd') },
-        { value: 'ccbync', text: this.$t('common.license.ccbync') },
-        { value: 'ccbyncsa', text: this.$t('common.license.ccbyncsa') },
-        { value: 'ccbyncnd', text: this.$t('common.license.ccbyncnd') }
-      ]
+    `,
+    variables: {
+      id: adminStore.currentSiteId
     },
-    timezones: get('data/timezones', false)
-  },
-  watch: {
-    currentSiteId (newValue) {
-      this.load()
-    }
-  },
-  mounted () {
-    if (this.currentSiteId) {
-      this.load()
+    fetchPolicy: 'network-only'
+  })
+  state.config = cloneDeep(resp?.data?.siteById)
+  $q.loading.hide()
+  state.loading--
+}
+
+async function save () {
+  state.loading++
+  try {
+    await APOLLO_CLIENT.mutate({
+      mutation: gql`
+        mutation saveSite (
+          $id: UUID!
+          $patch: SiteUpdateInput!
+        ) {
+          updateSite (
+            id: $id
+            patch: $patch
+          ) {
+            operation {
+              succeeded
+              slug
+              message
+            }
+          }
+        }
+      `,
+      variables: {
+        id: adminStore.currentSiteId,
+        patch: {
+          hostname: state.config.hostname ?? '',
+          title: state.config.title ?? '',
+          description: state.config.description ?? '',
+          company: state.config.company ?? '',
+          contentLicense: state.config.contentLicense ?? '',
+          logoText: state.config.logoText ?? false,
+          robots: {
+            index: state.config.robots?.index ?? false,
+            follow: state.config.robots?.follow ?? false
+          },
+          features: {
+            comments: state.config.features?.comments ?? false,
+            ratings: (state.config.features?.ratings || 'off') !== 'off',
+            ratingsMode: state.config.features?.ratingsMode ?? 'off',
+            contributions: state.config.features?.contributions ?? false,
+            profile: state.config.features?.profile ?? false,
+            search: state.config.features?.search ?? false
+          },
+          defaults: {
+            timezone: state.config.defaults?.timezone ?? 'America/New_York',
+            dateFormat: state.config.defaults?.dateFormat ?? 'YYYY-MM-DD',
+            timeFormat: state.config.defaults?.timeFormat ?? '12h'
+          }
+        }
+      }
+    })
+    $q.notify({
+      type: 'positive',
+      message: t('admin.general.saveSuccess')
+    })
+    if (adminStore.currentSiteId === siteStore.id) {
+      await adminStore.fetchSites()
+      siteStore.$patch({
+        title: state.config.title,
+        description: state.config.description,
+        company: state.config.company,
+        contentLicense: state.config.contentLicense
+      })
     }
-  },
-  methods: {
-    async load () {
-      this.loading++
-      this.$q.loading.show()
-      const resp = await this.$apollo.query({
-        query: gql`
-          query getSite (
+  } catch (err) {
+    $q.notify({
+      type: 'negative',
+      message: 'Failed to save site configuration.',
+      caption: err.message
+    })
+  }
+  state.loading--
+}
+
+async function uploadLogo () {
+  const input = document.createElement('input')
+  input.type = 'file'
+
+  input.onchange = async e => {
+    state.loading++
+    try {
+      await APOLLO_CLIENT.mutate({
+        mutation: gql`
+          mutation uploadLogo (
             $id: UUID!
+            $image: Upload!
           ) {
-            siteById(
+            uploadSiteLogo (
               id: $id
+              image: $image
             ) {
-              id
-              hostname
-              isEnabled
-              title
-              description
-              company
-              contentLicense
-              logoText
-              robots {
-                index
-                follow
-              }
-              features {
-                comments
-                ratings
-                ratingsMode
-                contributions
-                profile
-                search
-              }
-              defaults {
-                timezone
-                dateFormat
-                timeFormat
+              status {
+                succeeded
+                slug
+                message
               }
             }
           }
         `,
         variables: {
-          id: this.currentSiteId
-        },
-        fetchPolicy: 'network-only'
+          id: adminStore.currentSiteId,
+          image: e.target.files[0]
+        }
       })
-      this.config = cloneDeep(resp?.data?.siteById)
-      this.$q.loading.hide()
-      this.loading--
-    },
-    async save () {
-      this.loading++
-      try {
-        await this.$apollo.mutate({
-          mutation: gql`
-            mutation saveSite (
-              $id: UUID!
-              $patch: SiteUpdateInput!
+      $q.notify({
+        type: 'positive',
+        message: t('admin.general.logoUploadSuccess')
+      })
+    } catch (err) {
+      $q.notify({
+        type: 'negative',
+        message: 'Failed to upload site logo.',
+        caption: err.message
+      })
+    }
+    state.loading--
+  }
+
+  input.click()
+}
+
+async function uploadFavicon () {
+  const input = document.createElement('input')
+  input.type = 'file'
+
+  input.onchange = async e => {
+    state.loading++
+    try {
+      await APOLLO_CLIENT.mutate({
+        mutation: gql`
+          mutation uploadFavicon (
+            $id: UUID!
+            $image: Upload!
+          ) {
+            uploadSiteFavicon (
+              id: $id
+              image: $image
             ) {
-              updateSite (
-                id: $id
-                patch: $patch
-              ) {
-                status {
-                  succeeded
-                  slug
-                  message
-                }
-              }
-            }
-          `,
-          variables: {
-            id: this.currentSiteId,
-            patch: {
-              hostname: this.config.hostname ?? '',
-              title: this.config.title ?? '',
-              description: this.config.description ?? '',
-              company: this.config.company ?? '',
-              contentLicense: this.config.contentLicense ?? '',
-              logoText: this.config.logoText ?? false,
-              robots: {
-                index: this.config.robots?.index ?? false,
-                follow: this.config.robots?.follow ?? false
-              },
-              features: {
-                comments: this.config.features?.comments ?? false,
-                ratings: (this.config.features?.ratings || 'off') !== 'off',
-                ratingsMode: this.config.features?.ratingsMode ?? 'off',
-                contributions: this.config.features?.contributions ?? false,
-                profile: this.config.features?.profile ?? false,
-                search: this.config.features?.search ?? false
-              },
-              defaults: {
-                timezone: this.config.defaults?.timezone ?? 'America/New_York',
-                dateFormat: this.config.defaults?.dateFormat ?? 'YYYY-MM-DD',
-                timeFormat: this.config.defaults?.timeFormat ?? '12h'
+              status {
+                succeeded
+                slug
+                message
               }
             }
           }
-        })
-        this.$q.notify({
-          type: 'positive',
-          message: this.$t('admin.general.saveSuccess')
-        })
-        if (this.currentSiteId === this.$store.get('site/id')) {
-          await this.$store.dispatch('admin/fetchSites')
-          this.$store.set('site/title', this.config.title)
-          this.$store.set('site/description', this.config.description)
-          this.$store.set('site/company', this.config.company)
-          this.$store.set('site/contentLicense', this.config.contentLicense)
-        }
-      } catch (err) {
-        this.$q.notify({
-          type: 'negative',
-          message: 'Failed to save site configuration.',
-          caption: err.message
-        })
-      }
-      this.loading--
-    },
-    async uploadLogo () {
-      const input = document.createElement('input')
-      input.type = 'file'
-
-      input.onchange = async e => {
-        this.loading++
-        try {
-          await this.$apollo.mutate({
-            mutation: gql`
-              mutation uploadLogo (
-                $id: UUID!
-                $image: Upload!
-              ) {
-                uploadSiteLogo (
-                  id: $id
-                  image: $image
-                ) {
-                  status {
-                    succeeded
-                    slug
-                    message
-                  }
-                }
-              }
-            `,
-            variables: {
-              id: this.currentSiteId,
-              image: e.target.files[0]
-            }
-          })
-          this.$q.notify({
-            type: 'positive',
-            message: this.$t('admin.general.logoUploadSuccess')
-          })
-        } catch (err) {
-          this.$q.notify({
-            type: 'negative',
-            message: 'Failed to upload site logo.',
-            caption: err.message
-          })
-        }
-        this.loading--
-      }
-
-      input.click()
-    },
-    refreshLogo () {
-      this.$forceUpdate()
-    },
-    async uploadFavicon () {
-      const input = document.createElement('input')
-      input.type = 'file'
-
-      input.onchange = async e => {
-        this.loading++
-        try {
-          await this.$apollo.mutate({
-            mutation: gql`
-              mutation uploadFavicon (
-                $id: UUID!
-                $image: Upload!
-              ) {
-                uploadSiteFavicon (
-                  id: $id
-                  image: $image
-                ) {
-                  status {
-                    succeeded
-                    slug
-                    message
-                  }
-                }
-              }
-            `,
-            variables: {
-              id: this.currentSiteId,
-              image: e.target.files[0]
-            }
-          })
-          this.$q.notify({
-            type: 'positive',
-            message: this.$t('admin.general.faviconUploadSuccess')
-          })
-        } catch (err) {
-          this.$q.notify({
-            type: 'negative',
-            message: 'Failed to upload site favicon.',
-            caption: err.message
-          })
+        `,
+        variables: {
+          id: adminStore.currentSiteId,
+          image: e.target.files[0]
         }
-        this.loading--
-      }
-
-      input.click()
+      })
+      $q.notify({
+        type: 'positive',
+        message: t('admin.general.faviconUploadSuccess')
+      })
+    } catch (err) {
+      $q.notify({
+        type: 'negative',
+        message: 'Failed to upload site favicon.',
+        caption: err.message
+      })
     }
+    state.loading--
   }
+
+  input.click()
 }
+
+// MOUNTED
+
+onMounted(() => {
+  if (adminStore.currentSiteId) {
+    load()
+  }
+})
 </script>
 
 <style lang='scss'>

+ 1 - 1
ux/src/router/routes.js

@@ -31,7 +31,7 @@ const routes = [
       { path: 'dashboard', component: () => import('../pages/AdminDashboard.vue') },
       { path: 'sites', component: () => import('../pages/AdminSites.vue') },
       // // -> Site
-      // { path: ':siteid/general', component: () => import('../pages/AdminGeneral.vue') },
+      { path: ':siteid/general', component: () => import('../pages/AdminGeneral.vue') },
       { path: ':siteid/editors', component: () => import('../pages/AdminEditors.vue') },
       // { path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') },
       // { path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') },