mirror of
https://github.com/requarks/wiki.git
synced 2026-08-26 13:17:10 -05:00
feat: update profile + user theme
This commit is contained in:
@@ -580,7 +580,7 @@ exports.up = async knex => {
|
||||
timezone: 'America/New_York',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: '12h',
|
||||
darkMode: false
|
||||
appearance: 'site'
|
||||
},
|
||||
localeCode: 'en'
|
||||
},
|
||||
@@ -597,7 +597,7 @@ exports.up = async knex => {
|
||||
timezone: 'America/New_York',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: '12h',
|
||||
darkMode: false
|
||||
appearance: 'site'
|
||||
},
|
||||
localeCode: 'en'
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ module.exports = {
|
||||
async userById (obj, args, context, info) {
|
||||
const usr = await WIKI.models.users.query().findById(args.id)
|
||||
|
||||
if (!usr) {
|
||||
throw new Error('Invalid User')
|
||||
}
|
||||
|
||||
// const str = _.get(WIKI.auth.strategies, usr.providerKey)
|
||||
// str.strategy = _.find(WIKI.data.authentication, ['key', str.strategyKey])
|
||||
// usr.providerName = str.displayName
|
||||
@@ -56,25 +60,25 @@ module.exports = {
|
||||
|
||||
return usr
|
||||
},
|
||||
async profile (obj, args, context, info) {
|
||||
if (!context.req.user || context.req.user.id < 1 || context.req.user.id === 2) {
|
||||
throw new WIKI.Error.AuthRequired()
|
||||
}
|
||||
const usr = await WIKI.models.users.query().findById(context.req.user.id)
|
||||
if (!usr.isActive) {
|
||||
throw new WIKI.Error.AuthAccountBanned()
|
||||
}
|
||||
// async profile (obj, args, context, info) {
|
||||
// if (!context.req.user || context.req.user.id < 1 || context.req.user.id === 2) {
|
||||
// throw new WIKI.Error.AuthRequired()
|
||||
// }
|
||||
// const usr = await WIKI.models.users.query().findById(context.req.user.id)
|
||||
// if (!usr.isActive) {
|
||||
// throw new WIKI.Error.AuthAccountBanned()
|
||||
// }
|
||||
|
||||
const providerInfo = _.get(WIKI.auth.strategies, usr.providerKey, {})
|
||||
// const providerInfo = _.get(WIKI.auth.strategies, usr.providerKey, {})
|
||||
|
||||
usr.providerName = providerInfo.displayName || 'Unknown'
|
||||
usr.lastLoginAt = usr.lastLoginAt || usr.updatedAt
|
||||
usr.password = ''
|
||||
usr.providerId = ''
|
||||
usr.tfaSecret = ''
|
||||
// usr.providerName = providerInfo.displayName || 'Unknown'
|
||||
// usr.lastLoginAt = usr.lastLoginAt || usr.updatedAt
|
||||
// usr.password = ''
|
||||
// usr.providerId = ''
|
||||
// usr.tfaSecret = ''
|
||||
|
||||
return usr
|
||||
},
|
||||
// return usr
|
||||
// },
|
||||
async lastLogins (obj, args, context, info) {
|
||||
return WIKI.models.users.query()
|
||||
.select('id', 'name', 'lastLoginAt')
|
||||
@@ -193,7 +197,7 @@ module.exports = {
|
||||
},
|
||||
async updateProfile (obj, args, context) {
|
||||
try {
|
||||
if (!context.req.user || context.req.user.id < 1 || context.req.user.id === 2) {
|
||||
if (!context.req.user || context.req.user.id === WIKI.auth.guest.id) {
|
||||
throw new WIKI.Error.AuthRequired()
|
||||
}
|
||||
const usr = await WIKI.models.users.query().findById(context.req.user.id)
|
||||
@@ -204,29 +208,33 @@ module.exports = {
|
||||
throw new WIKI.Error.AuthAccountNotVerified()
|
||||
}
|
||||
|
||||
if (!['', 'DD/MM/YYYY', 'DD.MM.YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD', 'YYYY/MM/DD'].includes(args.dateFormat)) {
|
||||
if (args.dateFormat && !['', 'DD/MM/YYYY', 'DD.MM.YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD', 'YYYY/MM/DD'].includes(args.dateFormat)) {
|
||||
throw new WIKI.Error.InputInvalid()
|
||||
}
|
||||
|
||||
if (!['', 'light', 'dark'].includes(args.appearance)) {
|
||||
if (args.appearance && !['site', 'light', 'dark'].includes(args.appearance)) {
|
||||
throw new WIKI.Error.InputInvalid()
|
||||
}
|
||||
|
||||
await WIKI.models.users.updateUser({
|
||||
id: usr.id,
|
||||
name: _.trim(args.name),
|
||||
jobTitle: _.trim(args.jobTitle),
|
||||
location: _.trim(args.location),
|
||||
timezone: args.timezone,
|
||||
dateFormat: args.dateFormat,
|
||||
appearance: args.appearance
|
||||
await WIKI.models.users.query().findById(usr.id).patch({
|
||||
name: args.name?.trim() ?? usr.name,
|
||||
meta: {
|
||||
...usr.meta,
|
||||
location: args.location?.trim() ?? usr.meta.location,
|
||||
jobTitle: args.jobTitle?.trim() ?? usr.meta.jobTitle,
|
||||
pronouns: args.pronouns?.trim() ?? usr.meta.pronouns
|
||||
},
|
||||
prefs: {
|
||||
...usr.prefs,
|
||||
timezone: args.timezone || usr.prefs.timezone,
|
||||
dateFormat: args.dateFormat ?? usr.prefs.dateFormat,
|
||||
timeFormat: args.timeFormat ?? usr.prefs.timeFormat,
|
||||
appearance: args.appearance || usr.prefs.appearance
|
||||
}
|
||||
})
|
||||
|
||||
const newToken = await WIKI.models.users.refreshToken(usr.id)
|
||||
|
||||
return {
|
||||
operation: graphHelper.generateSuccess('User profile updated successfully'),
|
||||
jwt: newToken.token
|
||||
operation: graphHelper.generateSuccess('User profile updated successfully')
|
||||
}
|
||||
} catch (err) {
|
||||
return graphHelper.generateError(err)
|
||||
@@ -273,15 +281,15 @@ module.exports = {
|
||||
groups (usr) {
|
||||
return usr.$relatedQuery('groups')
|
||||
}
|
||||
},
|
||||
UserProfile: {
|
||||
async groups (usr) {
|
||||
const usrGroups = await usr.$relatedQuery('groups')
|
||||
return usrGroups.map(g => g.name)
|
||||
},
|
||||
async pagesTotal (usr) {
|
||||
const result = await WIKI.models.pages.query().count('* as total').where('creatorId', usr.id).first()
|
||||
return _.toSafeInteger(result.total)
|
||||
}
|
||||
}
|
||||
// UserProfile: {
|
||||
// async groups (usr) {
|
||||
// const usrGroups = await usr.$relatedQuery('groups')
|
||||
// return usrGroups.map(g => g.name)
|
||||
// },
|
||||
// async pagesTotal (usr) {
|
||||
// const result = await WIKI.models.pages.query().count('* as total').where('creatorId', usr.id).first()
|
||||
// return _.toSafeInteger(result.total)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ extend type Query {
|
||||
id: UUID!
|
||||
): User
|
||||
|
||||
profile: UserProfile
|
||||
|
||||
lastLogins: [UserLastLogin]
|
||||
}
|
||||
|
||||
@@ -66,13 +64,15 @@ extend type Mutation {
|
||||
): DefaultResponse
|
||||
|
||||
updateProfile(
|
||||
name: String!
|
||||
location: String!
|
||||
jobTitle: String!
|
||||
timezone: String!
|
||||
dateFormat: String!
|
||||
appearance: String!
|
||||
): UserTokenResponse
|
||||
name: String
|
||||
location: String
|
||||
jobTitle: String
|
||||
pronouns: String
|
||||
timezone: String
|
||||
dateFormat: String
|
||||
timeFormat: String
|
||||
appearance: UserSiteAppearance
|
||||
): DefaultResponse
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
@@ -110,32 +110,13 @@ type User {
|
||||
isVerified: Boolean
|
||||
meta: JSON
|
||||
prefs: JSON
|
||||
pictureUrl: String
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
lastLoginAt: Date
|
||||
groups: [Group]
|
||||
}
|
||||
|
||||
type UserProfile {
|
||||
id: Int
|
||||
name: String
|
||||
email: String
|
||||
providerKey: String
|
||||
providerName: String
|
||||
isSystem: Boolean
|
||||
isVerified: Boolean
|
||||
location: String
|
||||
jobTitle: String
|
||||
timezone: String
|
||||
dateFormat: String
|
||||
appearance: String
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
lastLoginAt: Date
|
||||
groups: [String]
|
||||
pagesTotal: Int
|
||||
}
|
||||
|
||||
type UserTokenResponse {
|
||||
operation: Operation
|
||||
jwt: String
|
||||
@@ -150,6 +131,12 @@ enum UserOrderBy {
|
||||
lastLoginAt
|
||||
}
|
||||
|
||||
enum UserSiteAppearance {
|
||||
site
|
||||
light
|
||||
dark
|
||||
}
|
||||
|
||||
input UserUpdateInput {
|
||||
email: String
|
||||
name: String
|
||||
|
||||
@@ -22,7 +22,7 @@ module.exports = class User extends Model {
|
||||
|
||||
properties: {
|
||||
id: {type: 'string'},
|
||||
email: {type: 'string', format: 'email'},
|
||||
email: {type: 'string'},
|
||||
name: {type: 'string', minLength: 1, maxLength: 255},
|
||||
pictureUrl: {type: 'string'},
|
||||
isSystem: {type: 'boolean'},
|
||||
|
||||
+6
-6
@@ -77,12 +77,12 @@ module.exports = configure(function (/* ctx */) {
|
||||
|
||||
extendViteConf (viteConf) {
|
||||
viteConf.build.assetsDir = '_assets'
|
||||
viteConf.build.rollupOptions = {
|
||||
...viteConf.build.rollupOptions ?? {},
|
||||
external: [
|
||||
/^\/_site\//
|
||||
]
|
||||
}
|
||||
// viteConf.build.rollupOptions = {
|
||||
// ...viteConf.build.rollupOptions ?? {},
|
||||
// external: [
|
||||
// /^\/_site\//
|
||||
// ]
|
||||
// }
|
||||
},
|
||||
// viteVuePluginOptions: {},
|
||||
|
||||
|
||||
+28
-3
@@ -3,9 +3,10 @@ router-view
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, onMounted, reactive } from 'vue'
|
||||
import { nextTick, onMounted, reactive, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useSiteStore } from 'src/stores/site'
|
||||
import { useUserStore } from 'src/stores/user'
|
||||
import { setCssVar, useQuasar } from 'quasar'
|
||||
|
||||
/* global siteConfig */
|
||||
@@ -17,6 +18,7 @@ const $q = useQuasar()
|
||||
// STORES
|
||||
|
||||
const siteStore = useSiteStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// ROUTER
|
||||
|
||||
@@ -28,10 +30,24 @@ const state = reactive({
|
||||
isInitialized: false
|
||||
})
|
||||
|
||||
// WATCHERS
|
||||
|
||||
watch(() => userStore.appearance, (newValue) => {
|
||||
if (newValue === 'site') {
|
||||
$q.dark.set(siteStore.theme.dark)
|
||||
} else {
|
||||
$q.dark.set(newValue === 'dark')
|
||||
}
|
||||
})
|
||||
|
||||
// THEME
|
||||
|
||||
function applyTheme () {
|
||||
$q.dark.set(siteStore.theme.dark)
|
||||
if (userStore.appearance === 'site') {
|
||||
$q.dark.set(siteStore.theme.dark)
|
||||
} else {
|
||||
$q.dark.set(userStore.appearance === 'dark')
|
||||
}
|
||||
setCssVar('primary', siteStore.theme.colorPrimary)
|
||||
setCssVar('secondary', siteStore.theme.colorSecondary)
|
||||
setCssVar('accent', siteStore.theme.colorAccent)
|
||||
@@ -51,12 +67,21 @@ if (typeof siteConfig !== 'undefined') {
|
||||
|
||||
router.beforeEach(async (to, from) => {
|
||||
siteStore.routerLoading = true
|
||||
// Site Info
|
||||
if (!siteStore.id) {
|
||||
console.info('No pre-cached site config. Loading site info...')
|
||||
await siteStore.loadSite(window.location.hostname)
|
||||
console.info(`Using Site ID ${siteStore.id}`)
|
||||
applyTheme()
|
||||
}
|
||||
// User Auth
|
||||
await userStore.refreshAuth()
|
||||
// User Profile
|
||||
if (userStore.authenticated && !userStore.profileLoaded) {
|
||||
console.info(`Refreshing user ${userStore.id} profile...`)
|
||||
await userStore.refreshProfile()
|
||||
}
|
||||
// Apply Theme
|
||||
applyTheme()
|
||||
})
|
||||
router.afterEach(() => {
|
||||
if (!state.isInitialized) {
|
||||
|
||||
@@ -3,10 +3,10 @@ import { ApolloClient, InMemoryCache } from '@apollo/client/core'
|
||||
import { setContext } from '@apollo/client/link/context'
|
||||
import { createUploadLink } from 'apollo-upload-client'
|
||||
|
||||
export default boot(({ app }) => {
|
||||
export default boot(({ app, store }) => {
|
||||
// Authentication Link
|
||||
const authLink = setContext(async (req, { headers }) => {
|
||||
const token = 'test' // await window.auth0Client.getTokenSilently()
|
||||
const token = store.state.value.user.token
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template lang='pug'>
|
||||
q-btn.q-ml-md(flat, round, dense, color='grey')
|
||||
q-icon(v-if='!state.user.picture', name='las la-user-circle')
|
||||
q-icon(v-if='!userStore.authenticated || !userStore.pictureUrl', name='las la-user-circle')
|
||||
q-avatar(v-else)
|
||||
img(:src='state.user.picture')
|
||||
img(:src='userStore.pictureUrl')
|
||||
q-menu(auto-close)
|
||||
q-card(flat, style='width: 300px;', :dark='false')
|
||||
q-card-section(align='center')
|
||||
.text-subtitle1.text-grey-7 {{state.user.name}}
|
||||
.text-caption.text-grey-8 {{state.user.email}}
|
||||
.text-subtitle1.text-grey-7 {{userStore.name}}
|
||||
.text-caption.text-grey-8 {{userStore.email}}
|
||||
q-separator(:dark='false')
|
||||
q-card-actions(align='center')
|
||||
q-btn(
|
||||
@@ -15,7 +15,7 @@ q-btn.q-ml-md(flat, round, dense, color='grey')
|
||||
label='Profile'
|
||||
icon='las la-user-alt'
|
||||
color='primary'
|
||||
href='/_profile'
|
||||
to='/_profile'
|
||||
no-caps
|
||||
)
|
||||
q-btn(flat
|
||||
@@ -29,13 +29,7 @@ q-btn.q-ml-md(flat, round, dense, color='grey')
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import { useUserStore } from 'src/stores/user'
|
||||
|
||||
const state = reactive({
|
||||
user: {
|
||||
name: 'John Doe',
|
||||
email: 'test@example.com',
|
||||
picture: null
|
||||
}
|
||||
})
|
||||
const userStore = useUserStore()
|
||||
</script>
|
||||
|
||||
@@ -17,10 +17,10 @@ q-header.bg-header.text-white.site-header(
|
||||
size='34px'
|
||||
square
|
||||
)
|
||||
img(src='/_site/logo')
|
||||
img(:src='`/_site/logo`')
|
||||
img(
|
||||
v-else
|
||||
src='/_site/logo'
|
||||
:src='`/_site/logo`'
|
||||
style='height: 34px'
|
||||
)
|
||||
q-toolbar-title.text-h6(v-if='siteStore.logoText') {{siteStore.title}}
|
||||
|
||||
@@ -117,6 +117,19 @@ q-layout(view='hHh lpR fFf', container)
|
||||
dense
|
||||
:aria-label='t(`admin.users.jobTitle`)'
|
||||
)
|
||||
q-separator.q-my-sm(inset)
|
||||
q-item
|
||||
blueprint-icon(icon='gender')
|
||||
q-item-section
|
||||
q-item-label {{t(`admin.users.pronouns`)}}
|
||||
q-item-label(caption) {{t(`admin.users.pronounsHint`)}}
|
||||
q-item-section
|
||||
q-input(
|
||||
outlined
|
||||
v-model='state.user.meta.pronouns'
|
||||
dense
|
||||
:aria-label='t(`admin.users.pronouns`)'
|
||||
)
|
||||
|
||||
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.user.meta')
|
||||
q-card-section
|
||||
@@ -181,18 +194,23 @@ q-layout(view='hHh lpR fFf', container)
|
||||
]`
|
||||
)
|
||||
q-separator.q-my-sm(inset)
|
||||
q-item(tag='label', v-ripple)
|
||||
q-item
|
||||
blueprint-icon(icon='light-on')
|
||||
q-item-section
|
||||
q-item-label {{t(`admin.users.darkMode`)}}
|
||||
q-item-label {{t(`admin.users.appearance`)}}
|
||||
q-item-label(caption) {{t(`admin.users.darkModeHint`)}}
|
||||
q-item-section(avatar)
|
||||
q-toggle(
|
||||
v-model='state.user.prefs.darkMode'
|
||||
color='primary'
|
||||
checked-icon='las la-check'
|
||||
unchecked-icon='las la-times'
|
||||
:aria-label='t(`admin.users.darkMode`)'
|
||||
q-item-section.col-auto
|
||||
q-btn-toggle(
|
||||
v-model='state.user.prefs.appearance'
|
||||
push
|
||||
glossy
|
||||
no-caps
|
||||
toggle-color='primary'
|
||||
:options=`[
|
||||
{ label: t('profile.appearanceDefault'), value: 'site' },
|
||||
{ label: t('profile.appearanceLight'), value: 'light' },
|
||||
{ label: t('profile.appearanceDark'), value: 'dark' }
|
||||
]`
|
||||
)
|
||||
|
||||
.col-12.col-lg-4
|
||||
|
||||
@@ -1359,7 +1359,7 @@
|
||||
"profile.activity.lastUpdatedOn": "Profile last updated on",
|
||||
"profile.activity.pagesCreated": "Pages created",
|
||||
"profile.activity.title": "Activity",
|
||||
"profile.appearance": "Appearance",
|
||||
"profile.appearance": "Site Appearance",
|
||||
"profile.appearanceDark": "Dark",
|
||||
"profile.appearanceDefault": "Site Default",
|
||||
"profile.appearanceLight": "Light",
|
||||
@@ -1498,5 +1498,12 @@
|
||||
"admin.utilities.disconnectWSHint": "Force all active websocket connections to be closed.",
|
||||
"admin.utilities.disconnectWSSuccess": "All active websocket connections have been terminated.",
|
||||
"admin.login.bgUploadSuccess": "Login background image uploaded successfully.",
|
||||
"admin.login.saveSuccess": "Login configuration saved successfully."
|
||||
"admin.login.saveSuccess": "Login configuration saved successfully.",
|
||||
"profile.appearanceHint": "Use the light or dark theme.",
|
||||
"profile.saving": "Saving profile...",
|
||||
"profile.saveSuccess": "Profile saved successfully.",
|
||||
"profile.saveFailed": "Failed to save profile changes.",
|
||||
"admin.users.pronouns": "Pronouns",
|
||||
"admin.users.pronounsHint": "The pronouns used to address this user.",
|
||||
"admin.users.appearance": "Site Appearance"
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ q-layout(view='hHh Lpr lff')
|
||||
q-item(
|
||||
clickable
|
||||
v-ripple
|
||||
href='/logout'
|
||||
)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-sign-out-alt', color='negative')
|
||||
@@ -80,7 +81,7 @@ const sidenav = [
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
label: 'Authentication',
|
||||
icon: 'las la-key'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
.auth
|
||||
.auth-content
|
||||
.auth-logo
|
||||
img(src='/_site/logo' :alt='siteStore.title')
|
||||
img(:src='`/_site/logo`' :alt='siteStore.title')
|
||||
h2.auth-site-title(v-if='siteStore.logoText') {{ siteStore.title }}
|
||||
p.text-grey-7 Login to continue
|
||||
auth-login-panel
|
||||
.auth-bg(aria-hidden="true")
|
||||
img(src='/_site/loginbg' alt='')
|
||||
img(:src='`/_site/loginbg`' alt='')
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
+93
-14
@@ -121,25 +121,27 @@ q-page.q-py-md(:style-fn='pageStyle')
|
||||
:options='timeFormats'
|
||||
)
|
||||
q-separator.q-my-sm(inset)
|
||||
q-item(tag='label', v-ripple)
|
||||
q-item
|
||||
blueprint-icon(icon='light-on')
|
||||
q-item-section
|
||||
q-item-label {{t(`profile.darkMode`)}}
|
||||
q-item-label(caption) {{t(`profile.darkModeHint`)}}
|
||||
q-item-section(avatar)
|
||||
q-toggle(
|
||||
v-model='state.config.darkMode'
|
||||
color='primary'
|
||||
checked-icon='las la-check'
|
||||
unchecked-icon='las la-times'
|
||||
:aria-label='t(`profile.darkMode`)'
|
||||
q-item-label {{t(`profile.appearance`)}}
|
||||
q-item-label(caption) {{t(`profile.appearanceHint`)}}
|
||||
q-item-section.col-auto
|
||||
q-btn-toggle(
|
||||
v-model='state.config.appearance'
|
||||
push
|
||||
glossy
|
||||
no-caps
|
||||
toggle-color='primary'
|
||||
:options='appearances'
|
||||
)
|
||||
.actions-bar.q-mt-lg
|
||||
q-btn(
|
||||
icon='las la-check'
|
||||
unelevated
|
||||
label='Save Changes'
|
||||
:label='t(`common.actions.saveChanges`)'
|
||||
color='secondary'
|
||||
@click='save'
|
||||
)
|
||||
</template>
|
||||
|
||||
@@ -152,6 +154,7 @@ import { onMounted, reactive, watch } from 'vue'
|
||||
|
||||
import { useSiteStore } from 'src/stores/site'
|
||||
import { useDataStore } from 'src/stores/data'
|
||||
import { useUserStore } from 'src/stores/user'
|
||||
|
||||
// QUASAR
|
||||
|
||||
@@ -161,6 +164,7 @@ const $q = useQuasar()
|
||||
|
||||
const siteStore = useSiteStore()
|
||||
const dataStore = useDataStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// I18N
|
||||
|
||||
@@ -176,14 +180,15 @@ useMeta({
|
||||
|
||||
const state = reactive({
|
||||
config: {
|
||||
name: 'John Doe',
|
||||
email: 'john.doe@company.com',
|
||||
name: '',
|
||||
email: '',
|
||||
location: '',
|
||||
jobTitle: '',
|
||||
pronouns: '',
|
||||
timezone: '',
|
||||
dateFormat: '',
|
||||
timeFormat: '12h',
|
||||
darkMode: false
|
||||
appearance: 'site'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -199,6 +204,11 @@ const timeFormats = [
|
||||
{ value: '12h', label: t('admin.general.defaultTimeFormat12h') },
|
||||
{ value: '24h', label: t('admin.general.defaultTimeFormat24h') }
|
||||
]
|
||||
const appearances = [
|
||||
{ value: 'site', label: t('profile.appearanceDefault') },
|
||||
{ value: 'light', label: t('profile.appearanceLight') },
|
||||
{ value: 'dark', label: t('profile.appearanceDark') }
|
||||
]
|
||||
|
||||
// METHODS
|
||||
|
||||
@@ -207,4 +217,73 @@ function pageStyle (offset, height) {
|
||||
'min-height': `${height - 100 - offset}px`
|
||||
}
|
||||
}
|
||||
|
||||
async function save () {
|
||||
$q.loading.show({
|
||||
message: t('profile.saving')
|
||||
})
|
||||
try {
|
||||
const respRaw = await APOLLO_CLIENT.mutate({
|
||||
mutation: gql`
|
||||
mutation saveProfile (
|
||||
$name: String
|
||||
$location: String
|
||||
$jobTitle: String
|
||||
$pronouns: String
|
||||
$timezone: String
|
||||
$dateFormat: String
|
||||
$timeFormat: String
|
||||
$appearance: UserSiteAppearance
|
||||
) {
|
||||
updateProfile (
|
||||
name: $name
|
||||
location: $location
|
||||
jobTitle: $jobTitle
|
||||
pronouns: $pronouns
|
||||
timezone: $timezone
|
||||
dateFormat: $dateFormat
|
||||
timeFormat: $timeFormat
|
||||
appearance: $appearance
|
||||
) {
|
||||
operation {
|
||||
succeeded
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: state.config
|
||||
})
|
||||
if (respRaw.data?.updateProfile?.operation?.succeeded) {
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: t('profile.saveSuccess')
|
||||
})
|
||||
userStore.$patch(state.config)
|
||||
} else {
|
||||
throw new Error(respRaw.data?.updateProfile?.operation?.message || 'An unexpected error occured')
|
||||
}
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: t('profile.saveFailed'),
|
||||
caption: err.message
|
||||
})
|
||||
}
|
||||
$q.loading.hide()
|
||||
}
|
||||
|
||||
// MOUNTED
|
||||
|
||||
onMounted(() => {
|
||||
state.config.name = userStore.name || ''
|
||||
state.config.email = userStore.email
|
||||
state.config.location = userStore.location || ''
|
||||
state.config.jobTitle = userStore.jobTitle || ''
|
||||
state.config.pronouns = userStore.pronouns || ''
|
||||
state.config.timezone = userStore.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || ''
|
||||
state.config.dateFormat = userStore.dateFormat || ''
|
||||
state.config.timeFormat = userStore.timeFormat || '12h'
|
||||
state.config.appearance = userStore.appearance || 'site'
|
||||
})
|
||||
</script>
|
||||
|
||||
+65
-17
@@ -1,47 +1,95 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import jwtDecode from 'jwt-decode'
|
||||
import Cookies from 'js-cookie'
|
||||
import gql from 'graphql-tag'
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
id: 0,
|
||||
id: '10000000-0000-4000-8000-000000000001',
|
||||
email: '',
|
||||
name: '',
|
||||
pictureUrl: '',
|
||||
localeCode: '',
|
||||
defaultEditor: '',
|
||||
timezone: '',
|
||||
dateFormat: '',
|
||||
appearance: '',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: '12h',
|
||||
appearance: 'site',
|
||||
permissions: [],
|
||||
iat: 0,
|
||||
exp: 0,
|
||||
authenticated: false
|
||||
exp: null,
|
||||
authenticated: false,
|
||||
token: '',
|
||||
profileLoaded: false
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
refreshAuth () {
|
||||
async refreshAuth () {
|
||||
const jwtCookie = Cookies.get('jwt')
|
||||
if (jwtCookie) {
|
||||
try {
|
||||
const jwtData = jwtDecode(jwtCookie)
|
||||
this.id = jwtData.id
|
||||
this.email = jwtData.email
|
||||
this.name = jwtData.name
|
||||
this.pictureUrl = jwtData.av
|
||||
this.localeCode = jwtData.lc
|
||||
this.timezone = jwtData.tz || Intl.DateTimeFormat().resolvedOptions().timeZone || ''
|
||||
this.dateFormat = jwtData.df || ''
|
||||
this.appearance = jwtData.ap || ''
|
||||
// this.defaultEditor = jwtData.defaultEditor
|
||||
this.permissions = jwtData.permissions
|
||||
this.iat = jwtData.iat
|
||||
this.exp = jwtData.exp
|
||||
this.authenticated = true
|
||||
this.exp = DateTime.fromSeconds(jwtData.exp, { zone: 'utc' })
|
||||
this.token = jwtCookie
|
||||
if (this.exp <= DateTime.utc()) {
|
||||
console.info('Token has expired. Attempting renew...')
|
||||
} else {
|
||||
this.authenticated = true
|
||||
}
|
||||
} catch (err) {
|
||||
console.debug('Invalid JWT. Silent authentication skipped.')
|
||||
}
|
||||
}
|
||||
},
|
||||
async refreshProfile () {
|
||||
if (!this.authenticated || !this.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const respRaw = await APOLLO_CLIENT.query({
|
||||
query: gql`
|
||||
query refreshProfile (
|
||||
$id: UUID!
|
||||
) {
|
||||
userById(id: $id) {
|
||||
id
|
||||
name
|
||||
email
|
||||
meta
|
||||
prefs
|
||||
lastLoginAt
|
||||
groups {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: this.id
|
||||
}
|
||||
})
|
||||
const resp = respRaw?.data?.userById
|
||||
if (!resp || resp.id !== this.id) {
|
||||
throw new Error('Failed to fetch user profile!')
|
||||
}
|
||||
this.name = resp.name || 'Unknown User'
|
||||
this.email = resp.email
|
||||
this.pictureUrl = (resp.pictureUrl === 'local') ? `/_user/${this.id}/avatar` : resp.pictureUrl
|
||||
this.location = resp.meta.location || ''
|
||||
this.jobTitle = resp.meta.jobTitle || ''
|
||||
this.pronouns = resp.meta.pronouns || ''
|
||||
this.timezone = resp.prefs.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || ''
|
||||
this.dateFormat = resp.prefs.dateFormat || ''
|
||||
this.timeFormat = resp.prefs.timeFormat || '12h'
|
||||
this.appearance = resp.prefs.appearance || 'site'
|
||||
this.profileLoaded = true
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user