Move UI configs to profile (#2201)

* feat: add tabs

* feat: move buttons to user profile

* feat: i18n

* refactor: move ui methods to `account` component
This commit is contained in:
Vlad Stan
2024-01-15 11:51:34 +02:00
committed by GitHub
parent 031ce14857
commit 4cea06c5a5
7 changed files with 423 additions and 377 deletions

View File

@ -5,230 +5,406 @@
{% block scripts %} {{ window_vars(user) }} {% block scripts %} {{ window_vars(user) }}
<script src="{{ static_url_for('static', 'js/account.js') }}"></script> <script src="{{ static_url_for('static', 'js/account.js') }}"></script>
{% endblock %} {% block page %} {% endblock %} {% block page %}
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<div v-if="user" class="col-12 col-md-6 q-gutter-y-md"> <div v-if="user" class="col-12 col-md-6 q-gutter-y-md">
<q-card v-if="passwordData.show"> <q-card>
<q-card-section> <q-card-section>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h4 class="q-my-none"> <q-tabs v-model="tab" align="justify">
<span v-text="$t('password_config')"></span> <q-tab
</h4> name="user"
</div> :label="$t('account_settings')"
<div class="col"> @update="val => tab = val.name"
<q-img ></q-tab>
v-if="user.config.picture" <q-tab
style="max-width: 100px" name="theme"
:src="user.config.picture" :label="$t('look_and_feel')"
class="float-right" @update="val => tab = val.name"
></q-img> ></q-tab>
</div> </q-tabs>
</div> <q-tab-panels v-model="tab">
</q-card-section> <q-tab-panel name="user">
<q-separator></q-separator> <div v-if="passwordData.show">
<q-card-section> <q-card-section>
<q-input <div class="row">
v-if="user.has_password" <div class="col">
v-model="passwordData.oldPassword" <h4 class="q-my-none">
type="password" <span v-text="$t('password_config')"></span>
autocomplete="off" </h4>
label="Old Password" </div>
filled <div class="col">
dense <q-img
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]" v-if="user.config.picture"
></q-input> style="max-width: 100px"
<q-input :src="user.config.picture"
v-model="passwordData.newPassword" class="float-right"
type="password" ></q-img>
autocomplete="off" </div>
:label="$t('password')" </div>
filled </q-card-section>
dense <q-separator></q-separator>
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]" <q-card-section>
></q-input> <q-input
<q-input v-if="user.has_password"
v-model="passwordData.newPasswordRepeat" v-model="passwordData.oldPassword"
type="password" type="password"
autocomplete="off" autocomplete="off"
:label="$t('password_repeat')" label="Old Password"
filled filled
dense dense
class="q-mb-md" :rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]"
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]" ></q-input>
></q-input> <q-input
</q-card-section> v-model="passwordData.newPassword"
<q-separator></q-separator> type="password"
<q-card-section class="q-pb-lg"> autocomplete="off"
<q-btn :label="$t('password')"
@click="updatePassword" filled
:disable="(!passwordData.newPassword || !passwordData.newPasswordRepeat) || passwordData.newPassword !== passwordData.newPasswordRepeat" dense
unelevated :rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]"
color="primary" ></q-input>
:label="$t('change_password')" <q-input
> v-model="passwordData.newPasswordRepeat"
</q-btn> type="password"
<q-btn autocomplete="off"
@click="passwordData.show = false" :label="$t('password_repeat')"
:label="$t('back')" filled
outline dense
unelevated class="q-mb-md"
color="grey" :rules="[(val) => !val || val.length >= 8 || $t('invalid_password')]"
class="float-right" ></q-input>
></q-btn> </q-card-section>
</q-card-section> <q-separator></q-separator>
</q-card> <q-card-section class="q-pb-lg">
<q-card v-else> <q-btn
<q-card-section> @click="updatePassword"
<div class="row"> :disable="(!passwordData.newPassword || !passwordData.newPasswordRepeat) || passwordData.newPassword !== passwordData.newPasswordRepeat"
<div class="col"> unelevated
<h4 class="q-my-none"> color="primary"
<span v-text="$t('account_settings')"></span> :label="$t('change_password')"
</h4> >
</div> </q-btn>
<div class="col"> <q-btn
<q-img @click="passwordData.show = false"
v-if="user.config.picture" :label="$t('back')"
style="max-width: 100px" outline
:src="user.config.picture" unelevated
class="float-right" color="grey"
></q-img> class="float-right"
</div> ></q-btn>
</div> </q-card-section>
</q-card-section> </div>
<q-separator></q-separator> <div v-else>
<q-card-section>
<div class="row">
<div class="col">
<q-img
v-if="user.config.picture"
style="max-width: 100px"
:src="user.config.picture"
class="float-right"
></q-img>
</div>
</div>
</q-card-section>
<q-separator></q-separator>
<q-card-section> <q-card-section>
<q-input <q-input
v-model="user.id" v-model="user.id"
:label="$t('user_id')" :label="$t('user_id')"
filled filled
dense dense
readonly readonly
:type="showUserId ? 'text': 'password'" :type="showUserId ? 'text': 'password'"
class="q-mb-md" class="q-mb-md"
><q-btn ><q-btn
@click="showUserId = !showUserId" @click="showUserId = !showUserId"
dense dense
flat flat
:icon="showUserId ? 'visibility_off' : 'visibility'" :icon="showUserId ? 'visibility_off' : 'visibility'"
color="grey" color="grey"
></q-btn> ></q-btn>
</q-input> </q-input>
<q-input <q-input
v-model="user.username" v-model="user.username"
:label="$t('username')" :label="$t('username')"
filled filled
dense dense
:readonly="hasUsername" :readonly="hasUsername"
class="q-mb-md" class="q-mb-md"
> >
</q-input> </q-input>
<q-input <q-input
v-model="user.email" v-model="user.email"
:label="$t('email')" :label="$t('email')"
filled filled
dense dense
readonly readonly
class="q-mb-md" class="q-mb-md"
> >
</q-input> </q-input>
<div v-if="!user.email" class="row"></div> <div v-if="!user.email" class="row"></div>
<div v-if="!user.email" class="row"> <div v-if="!user.email" class="row">
{% if "google-auth" in LNBITS_AUTH_METHODS or "github-auth" in {% if "google-auth" in LNBITS_AUTH_METHODS or
LNBITS_AUTH_METHODS %} "github-auth" in LNBITS_AUTH_METHODS %}
<div class="col q-pa-sm text-h6"> <div class="col q-pa-sm text-h6">
<span v-text="$t('verify_email')"></span>: <span v-text="$t('verify_email')"></span>:
</div> </div>
{%endif%} {% if "google-auth" in LNBITS_AUTH_METHODS %} {%endif%} {% if "google-auth" in LNBITS_AUTH_METHODS %}
<div class="col q-pa-sm"> <div class="col q-pa-sm">
<q-btn <q-btn
:href="`/api/v1/auth/google?user_id=${user.id}`" :href="`/api/v1/auth/google?user_id=${user.id}`"
type="a" type="a"
outline outline
no-caps no-caps
rounded rounded
color="grey" color="grey"
class="full-width" class="full-width"
> >
<q-avatar size="32px" class="q-mr-md"> <q-avatar size="32px" class="q-mr-md">
<q-img <q-img
:src="'{{ static_url_for('static', 'images/google-logo.png') }}'" :src="'{{ static_url_for('static', 'images/google-logo.png') }}'"
></q-img> ></q-img>
</q-avatar> </q-avatar>
<div>Google</div> <div>Google</div>
</q-btn> </q-btn>
</div> </div>
{%endif%} {% if "github-auth" in LNBITS_AUTH_METHODS %} {%endif%} {% if "github-auth" in LNBITS_AUTH_METHODS %}
<div class="col q-pa-sm"> <div class="col q-pa-sm">
<q-btn <q-btn
:href="`/api/v1/auth/github?user_id=${user.id}`" :href="`/api/v1/auth/github?user_id=${user.id}`"
type="a" type="a"
outline outline
no-caps no-caps
color="grey" color="grey"
rounded rounded
class="full-width" class="full-width"
> >
<q-avatar size="32px" class="q-mr-md"> <q-avatar size="32px" class="q-mr-md">
<q-img <q-img
:src="'{{ static_url_for('static', 'images/github-logo.png') }}'" :src="'{{ static_url_for('static', 'images/github-logo.png') }}'"
></q-img> ></q-img>
</q-avatar> </q-avatar>
<div>GitHub</div> <div>GitHub</div>
</q-btn> </q-btn>
</div> </div>
{%endif%} {%endif%}
</div> </div>
</q-card-section> </q-card-section>
<q-card-section v-if="user.config"> <q-card-section v-if="user.config">
<q-input <q-input
v-model="user.config.first_name" v-model="user.config.first_name"
:label="$t('first_name')" :label="$t('first_name')"
filled filled
dense dense
class="q-mb-md" class="q-mb-md"
> >
</q-input> </q-input>
<q-input <q-input
v-model="user.config.last_name" v-model="user.config.last_name"
:label="$t('last_name')" :label="$t('last_name')"
filled filled
dense dense
class="q-mb-md" class="q-mb-md"
> >
</q-input> </q-input>
<q-input <q-input
v-model="user.config.provider" v-model="user.config.provider"
:label="$t('auth_provider')" :label="$t('auth_provider')"
filled filled
dense dense
readonly readonly
class="q-mb-md" class="q-mb-md"
> >
</q-input> </q-input>
<q-input <q-input
v-model="user.config.picture" v-model="user.config.picture"
:label="$t('picture')" :label="$t('picture')"
filled filled
dense dense
class="q-mb-md" class="q-mb-md"
> >
</q-input> </q-input>
</q-card-section> </q-card-section>
<q-separator></q-separator> <q-separator></q-separator>
<q-card-section> <q-card-section>
<q-btn @click="updateAccount" unelevated color="primary"> <q-btn @click="updateAccount" unelevated color="primary">
<span v-text="$t('update_account')"></span> <span v-text="$t('update_account')"></span>
</q-btn> </q-btn>
<q-btn <q-btn
@click="showChangePassword()" @click="showChangePassword()"
:label="user.has_password ? $t('change_password'): $t('set_password')" :label="user.has_password ? $t('change_password'): $t('set_password')"
outline outline
unelevated unelevated
color="grey" color="grey"
class="float-right" class="float-right"
></q-btn> ></q-btn>
</q-card-section>
</div>
</q-tab-panel>
<q-tab-panel name="theme">
<div class="row q-mb-md">
<div class="col-4"><span v-text="$t('language')"></span></div>
<div class="col-8">
<q-btn-dropdown
dense
flat
round
size="sm"
icon="language"
class="q-pl-md"
>
<q-list v-for="(lang, index) in g.langs" :key="index">
<q-item
clickable
v-close-popup
:active="activeLanguage(lang.value)"
@click="changeLanguage(lang.value)"
><q-item-section>
<q-item-label
v-text="lang.display ?? lang.value.toUpperCase()"
></q-item-label>
<q-tooltip
><span v-text="lang.label"></span
></q-tooltip>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('color_scheme')"></span>
</div>
<div class="col-8">
<q-btn
v-if="g.allowedThemes.includes('classic')"
dense
flat
@click="changeColor('classic')"
icon="circle"
color="deep-purple"
size="md"
><q-tooltip>classic</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('bitcoin')"
dense
flat
@click="changeColor('bitcoin')"
icon="circle"
color="orange"
size="md"
><q-tooltip>bitcoin</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('mint')"
dense
flat
@click="changeColor('mint')"
icon="circle"
color="green"
size="md"
><q-tooltip>mint</q-tooltip> </q-btn
><q-btn
v-if="g.allowedThemes.includes('autumn')"
dense
flat
@click="changeColor('autumn')"
icon="circle"
color="brown"
size="md"
><q-tooltip>autumn</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('monochrome')"
dense
flat
@click="changeColor('monochrome')"
icon="circle"
color="grey"
size="md"
><q-tooltip>monochrome</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('salvador')"
dense
flat
@click="changeColor('salvador')"
icon="circle"
color="blue-10"
size="md"
><q-tooltip>elSalvador</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('freedom')"
dense
flat
@click="changeColor('freedom')"
icon="circle"
color="pink-13"
size="md"
><q-tooltip>freedom</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('cyber')"
dense
flat
@click="changeColor('cyber')"
icon="circle"
color="light-green-9"
size="md"
><q-tooltip>cyber</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('flamingo')"
dense
flat
@click="changeColor('flamingo')"
icon="circle"
color="pink-3"
size="md"
><q-tooltip>flamingo</q-tooltip>
</q-btn>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('toggle_darkmode')"></span>
</div>
<div class="col-8">
<q-btn
dense
flat
round
@click="toggleDarkMode"
:icon="($q.dark.isActive) ? 'brightness_3' : 'wb_sunny'"
size="sm"
>
<q-tooltip
><span v-text="$t('toggle_darkmode')"></span
></q-tooltip>
</q-btn>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">Notifications</div>
<div class="col-8">
<lnbits-notifications-btn
v-if="g.user"
pubkey="{{ WEBPUSH_PUBKEY }}"
></lnbits-notifications-btn>
</div>
</div>
</q-tab-panel>
</q-tab-panels>
</div>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</div> </div>

File diff suppressed because one or more lines are too long

View File

@ -224,5 +224,8 @@ window.localisation.en = {
auth_provider: 'Auth Provider', auth_provider: 'Auth Provider',
my_account: 'My Account', my_account: 'My Account',
back: 'Back', back: 'Back',
logout: 'Logout' logout: 'Logout',
look_and_feel: 'Look and Feel',
language: 'Language',
color_scheme: 'Color Scheme'
} }

View File

@ -6,6 +6,7 @@ new Vue({
user: null, user: null,
hasUsername: false, hasUsername: false,
showUserId: false, showUserId: false,
tab: 'user',
passwordData: { passwordData: {
show: false, show: false,
oldPassword: null, oldPassword: null,
@ -15,6 +16,21 @@ new Vue({
} }
}, },
methods: { methods: {
activeLanguage: function (lang) {
return window.i18n.locale === lang
},
changeLanguage: function (newValue) {
window.i18n.locale = newValue
this.$q.localStorage.set('lnbits.lang', newValue)
},
toggleDarkMode: function () {
this.$q.dark.toggle()
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
},
changeColor: function (newValue) {
document.body.setAttribute('data-theme', newValue)
this.$q.localStorage.set('lnbits.theme', newValue)
},
updateAccount: async function () { updateAccount: async function () {
try { try {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(

View File

@ -422,21 +422,10 @@ window.windowMixin = {
}, },
methods: { methods: {
activeLanguage: function (lang) {
return window.i18n.locale === lang
},
changeLanguage: function (newValue) {
window.i18n.locale = newValue
this.$q.localStorage.set('lnbits.lang', newValue)
},
changeColor: function (newValue) { changeColor: function (newValue) {
document.body.setAttribute('data-theme', newValue) document.body.setAttribute('data-theme', newValue)
this.$q.localStorage.set('lnbits.theme', newValue) this.$q.localStorage.set('lnbits.theme', newValue)
}, },
toggleDarkMode: function () {
this.$q.dark.toggle()
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
},
copyText: function (text, message, position) { copyText: function (text, message, position) {
var notify = this.$q.notify var notify = this.$q.notify
Quasar.utils.copyToClipboard(text).then(function () { Quasar.utils.copyToClipboard(text).then(function () {

View File

@ -1,6 +1,6 @@
// update cache version every time there is a new deployment // update cache version every time there is a new deployment
// so the service worker reinitializes the cache // so the service worker reinitializes the cache
const CACHE_VERSION = 99 const CACHE_VERSION = 102
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-` const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
const getApiKey = request => { const getApiKey = request => {

View File

@ -109,145 +109,7 @@
> >
<span>OFFLINE</span> <span>OFFLINE</span>
</q-badge> </q-badge>
<lnbits-notifications-btn
v-if="g.user"
pubkey="{{ WEBPUSH_PUBKEY }}"
></lnbits-notifications-btn>
<q-btn-dropdown
dense
flat
round
size="sm"
icon="language"
class="q-pl-md"
>
<q-list v-for="(lang, index) in g.langs" :key="index">
<q-item
clickable
v-close-popup
:active="activeLanguage(lang.value)"
@click="changeLanguage(lang.value)"
><q-item-section>
<q-item-label
v-text="lang.display ?? lang.value.toUpperCase()"
></q-item-label>
<q-tooltip><span v-text="lang.label"></span></q-tooltip>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn-dropdown
v-if="g.allowedThemes && g.allowedThemes.length > 1"
dense
flat
round
size="sm"
icon="format_color_fill"
class="q-pl-md"
>
<div class="row no-wrap q-pa-md">
<q-btn
v-if="g.allowedThemes.includes('classic')"
dense
flat
@click="changeColor('classic')"
icon="circle"
color="deep-purple"
size="md"
><q-tooltip>classic</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('bitcoin')"
dense
flat
@click="changeColor('bitcoin')"
icon="circle"
color="orange"
size="md"
><q-tooltip>bitcoin</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('mint')"
dense
flat
@click="changeColor('mint')"
icon="circle"
color="green"
size="md"
><q-tooltip>mint</q-tooltip> </q-btn
><q-btn
v-if="g.allowedThemes.includes('autumn')"
dense
flat
@click="changeColor('autumn')"
icon="circle"
color="brown"
size="md"
><q-tooltip>autumn</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('monochrome')"
dense
flat
@click="changeColor('monochrome')"
icon="circle"
color="grey"
size="md"
><q-tooltip>monochrome</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('salvador')"
dense
flat
@click="changeColor('salvador')"
icon="circle"
color="blue-10"
size="md"
><q-tooltip>elSalvador</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('freedom')"
dense
flat
@click="changeColor('freedom')"
icon="circle"
color="pink-13"
size="md"
><q-tooltip>freedom</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('cyber')"
dense
flat
@click="changeColor('cyber')"
icon="circle"
color="light-green-9"
size="md"
><q-tooltip>cyber</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('flamingo')"
dense
flat
@click="changeColor('flamingo')"
icon="circle"
color="pink-3"
size="md"
><q-tooltip>flamingo</q-tooltip>
</q-btn>
</div>
</q-btn-dropdown>
<q-btn
dense
flat
round
@click="toggleDarkMode"
:icon="($q.dark.isActive) ? 'brightness_3' : 'wb_sunny'"
size="sm"
>
<q-tooltip><span v-text="$t('toggle_darkmode')"></span></q-tooltip>
</q-btn>
<q-btn-dropdown <q-btn-dropdown
v-if="isUserAuthorized" v-if="isUserAuthorized"
dense dense