Commit ec99fd3a authored by Johannes Winter's avatar Johannes Winter

v0.4.1rc2

Merge branch 'develop'
parents 3610c955 b0cceead
Pipeline #146 passed with stages
in 7 minutes and 39 seconds
......@@ -2,11 +2,40 @@ stages:
- build
- deploy
build:
variables:
DEV_DOMAIN: "next.app.solidbase.info"
PROD_DOMAIN: "app.solidbase.info"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
build-dev:
image: node
stage: build
only:
- develop
variables:
GIT_SUBMODULE_STRATEGY: recursive
VUE_APP_DOMAIN: https://$DEV_DOMAIN
before_script:
- npm install --progress=false
script:
- npm run build
artifacts:
paths:
- dist
build-prod:
image: node
stage: build
only:
- master
variables:
GIT_SUBMODULE_STRATEGY: recursive
VUE_APP_DOMAIN: https://$PROD_DOMAIN
script:
- npm install --progress=false
- npm run build
......@@ -24,7 +53,7 @@ deploy-dev:
EMAIL: "status@ecobytes.net"
environment:
name: development
url: $DEV_DOMAIN
script:
- apk add --no-cache git openssh
- mkdir -p ~/.ssh
......@@ -36,7 +65,7 @@ deploy-dev:
- git init
- git add -A .
- git commit -m runner
- git remote add dokku dokku@dokku.ecobytes.net:next.app.solidbase.info
- git remote add dokku dokku@dokku.ecobytes.net:$DEV_DOMAIN
- git push -vv dokku +master
deploy-prod:
......@@ -49,6 +78,7 @@ deploy-prod:
EMAIL: "status@ecobytes.net"
environment:
name: production
url: $PROD_DOMAIN
script:
- apk add --no-cache git openssh
- mkdir -p ~/.ssh
......@@ -60,5 +90,5 @@ deploy-prod:
- git init
- git add -A .
- git commit -m runner
- git remote add dokku dokku@dokku.ecobytes.net:app.solidbase.info
- git remote add dokku dokku@dokku.ecobytes.net:$PROD_DOMAIN
- git push -vv dokku +master
<template>
<section>
<b-select v-model="selectedClass" @input="$emit('input',$event)">
<section v-if="classes">
<b-select v-model="selectedClass" @input="$emit('input',$event)" ref="dropdown">
<option v-for="businessClass in classes" :value="businessClass" :key="businessClass.value">
{{ localize ? $store.getters.classLabel({ressource: businessClass}) : decodeURIComponent(businessClass.value.split('#').pop())}}
......@@ -20,6 +20,9 @@ export default {
selectedClass: this.currentClass || this.classes[0]
}
},
mounted() {
this.$refs.dropdown.focus()
},
watch: {
currentClass: function() {
this.selectedClass = this.currentClass || this.classes[0]
......
......@@ -48,7 +48,7 @@
<button class="button is-warning" @click="isLoadModalActive = true" title="ctrl+shift+l">
{{$t('load')}}
</button>
<editBudgetModalButton @valueChanged="$emit('repaintChart',undefined)"
<editBudgetModalButton @valueChanged="$emit('repaintChart',undefined)" title="ctrl+alt+p"
ref="editBudgetButtonComponent" />
</div>
</div>
......
......@@ -39,26 +39,26 @@
</span>
</td>
<td>
<div v-if="$store.getters.hasIncludes(item.ID) || !$store.getters.isAnnual(item.ID)">
{{ $n(annualValue(item.ID),'currency', $store.getters.locale()) }}
<div v-if="type == 'expense' && !$store.state.ro && ($store.getters.hasIncludes(item.ID) || !$store.getters.isAnnual(item.ID))">
<money :locale="locale()" :amount="annualValue(item.ID)"/>
</div>
<div v-if="(carriedValue(item.ID) != undefined) && !$store.getters.months(item.ID) && !$store.state.ro">
<b-input @input="valueChanged(item)" v-model.number="item.value" type="number" min="0" :step="$store.getters.stepValue()"
max="10000000" style="display: inline-block;max-width:110px;min-width:75px;" />
</div>
<div
v-else-if="((carriedValue(item.ID) != undefined) && $store.getters.months(item.ID) && $store.getters.hasIncludes(item.ID)) || $store.state.ro">
v-else-if="((carriedValue(item.ID) != undefined) && $store.getters.months(item.ID) && $store.getters.hasIncludes(item.ID))
|| (type =='income' && $store.getters.months(item.ID))
|| $store.state.ro
">
<money :locale="locale()" :amount="$store.getters.hasValue(item.ID)"/>
</div>
</td>
<td v-if='displayNotes'>
<span v-html='htmlNote(item.ID)' />
<td>
<p class="control">
<editPintModalButton v-if="!$store.state.ro" :type="type" :item="item"
@valueChanged="$emit('valueChanged',$event)" />
</p>
</td>
</tr>
</tbody>
......@@ -112,7 +112,7 @@ export default {
return note == undefined ? "" : note
},
valueChanged(item) {
if (item.value != this.$store.getters.hasValue(item.ID)) {
if (typeof(item.value) == 'number') {
this.$store.commit('changeIntentValue', {
ID: item.ID,
value: item.value
......@@ -121,11 +121,14 @@ export default {
}
},
monthlyValueChanged(intent, month) {
this.$store.commit('changeMonthlyValue', {
ID: month.ID,
value: month.value
})
this.$emit('valueChanged', this.$store.getters.processOfIntent(intent))
// console.log(JSON.stringify(month, null, 4));
if (typeof(month.value) == 'number') {
this.$store.commit('changeMonthlyValue', {
ID: month.ID,
value: month.value
})
this.$emit('valueChanged', this.$store.getters.processOfIntent(intent))
}
},
displayValue(item) { // display value
return (this.type == 'process') ||
......
......@@ -9,37 +9,48 @@
/* eslint-disable no-console */
import auth from 'solid-auth-client'
const DOMAIN = process.env.VUE_APP_DOMAIN || 'https://app.solidbase.info'
const popupUri = 'https://ld.solidbase.info/common/popup.html'
var callbackURL = new URL('popup.html', DOMAIN)
callbackURL.protocol='http'
export default {
name: 'LoginButton',
mounted () {
if (!this.$store.getters.isLoggedIn) this.trackSession()
},
methods: {
login () {
async login () {
if (this.$store.getters.isLoggedIn) {
auth.logout().then(()=>{
this.trackSession()
})
} else {
auth.popupLogin({ popupUri }).then(() => {
this.trackSession()
})
}//TODO: Beautify, still apparently multiple calls to tracksession
// console.log(`logging in using options:\n${JSON.stringify({popupUri, callbackUri})}`);
this.session = await auth.popupLogin({ popupUri})
this.trackSession()
}
//TODO: Still apparently multiple calls to tracksession
// https://github.com/solid/solid-auth-client/issues/63
// https://github.com/solid/solid-auth-client/issues/130
},
trackSession() {
if (!this.$store.getters.isLoggedIn) this.$store.dispatch('trackSession').then(
session => {
this.$store.commit('setSession',session)
},
() => {
console.log('You are currently logged off')
this.$toast.open({
message: this.$t('please-log-in'),
type: 'is-danger',
duration: 1000
})
(err) => {
if (err=='app not authenticated') {
this.$dialog.alert({ //TODO: i18n
message: `<p><code>${DOMAIN}</code> is not a registered trusted app for <a href="${this.$store.state.webId}">${this.$store.state.webId}</a></p><p>You won't be able to use the app unless you follow <a href="https://github.com/solid/userguide#using-third-party-apps">this guide</a> to specify the Solid Base app as trusted origin.</p><p>By clicking <emph>OK</emph> you are redirected to solid POD configuration.</p>`,
type: 'is-danger',
onConfirm: () => {
window.location=this.$store.state.webId
}
})
}
console.log('There had been login problems')
}
)
}
......
......@@ -6,7 +6,7 @@
<section class="modal-card-body">
<b-field :label="$t('sel-cat')">
<ClassSelector :classes="expenseClasses" :currentClass="cat" :localize='true'
@input="expenseClass=$event; setParentIntents()" ref="classSelector" />
@input="expenseClass=$event; setParentIntents()"/>
</b-field>
<b-field :label="$t('name')">
<b-autocomplete v-model="intentName" :data="filteredParentIntents"
......@@ -16,13 +16,13 @@
<b-field v-for="month in months" :key="month.name"
:label="$t(month.name.toLowerCase())" horizontal
style="display:-webkit-box;text-align:right;margin-bottom:0rem;">
<b-numberinput v-model="month.value" :step="$store.getters.stepValue()"
<b-numberinput v-model="month.value" :step="$store.getters.stepValue()" @keyup.native.enter="saveIntent()"
controls-position="compact" controls-rounded style="display:inline-flex;"/>
</b-field>
</div>
<div v-else>
<b-field :label="$tc('amount', 1)" style="display:block;">
<b-numberinput v-model.number="intentValue" :step="$store.getters.stepValue()"
<b-numberinput v-model.number="intentValue" :step="$store.getters.stepValue()" @keyup.native.enter="saveIntent()"
controls-position="compact" controls-rounded min="0" max="10000000"
style="display:inline-flex;" />
</b-field>
......
......@@ -5,19 +5,19 @@
</header>
<section class="modal-card-body">
<b-field :label="$t('name')">
<b-input v-model="income.name" :placeholder="$t('enter-inc-name')" />
<b-input v-model="income.name" :placeholder="$t('enter-inc-name')" ref="nameInput"/>
</b-field>
<div v-if="showMonths">
<b-field v-for="month in months" :key="month.name"
:label="$t(month.name.toLowerCase())" horizontal
style="display:-webkit-box;text-align:right;margin-bottom:0rem;">
<b-numberinput v-model="month.value" :step="$store.getters.stepValue()"
<b-numberinput v-model="month.value" :step="$store.getters.stepValue()" @keyup.native.enter="addIncome()"
controls-position="compact" controls-rounded style="display:inline-flex;"/>
</b-field>
</div>
<div v-else>
<b-field :label="$t('annual')" style="display:block;">
<b-numberinput v-model.number="income.value" :step="$store.getters.stepValue()"
<b-field :label="$t('annually')" style="display:block;">
<b-numberinput v-model.number="income.value" :step="$store.getters.stepValue()" @keyup.native.enter="addIncome()"
controls-position="compact" controls-rounded min="0" max="10000000"
style="display:inline-flex;" />
</b-field>
......@@ -133,7 +133,9 @@ export default {
}) : ''
},
},
mounted() {},
mounted() {
this.$refs.nameInput.focus()
},
methods: {
newParentProcess(proc) {
this.returnProcess = proc
......@@ -152,7 +154,7 @@ export default {
console.log('name test failed');
return false
}
if (this.income.value == 0) {
if (this.income.value == 0 && this.aSum() == 0) {
this.$toast.open({
message: this.$t('no-null'),
type: 'is-danger',
......@@ -191,6 +193,9 @@ export default {
this.$emit('repaintChart', this.returnProcess)
this.$parent.close()
},
aSum() {
return this.months.map(a => parseFloat(a.value)).reduce((a, b) => a + b, 0)
},
togglePeriod() {
this.showMonths = !this.showMonths
if (this.showMonths && this.income.value > 0) {
......@@ -199,8 +204,7 @@ export default {
else this.months[i].value = Math.round(this.income.value / 12)
}
} else {
this.income.value = this.months.map(a => parseFloat(a.value))
.reduce((a, b) => a + b, 0)
this.income.value = this.aSum()
}
},
update(e) {
......
......@@ -5,11 +5,11 @@
<b-field grouped>
<b-field :message="$t('sel-cat')">
<ClassSelector :classes="$store.getters.expenseClasses({admin})"
:currentClass="expenseClass || $store.getters.expenseClasses({admin})[0]"
:currentClass="expenseClass"
@input="loadText({name: $event})" />
</b-field>
<b-field :message="$t('sel-loc')">
<b-select v-model="selectedLocale" @input="loadText({lang: $event})">
<b-select v-model="selectedLocale" @input="loadText({name: expenseClass, lang: $event})">
<option v-for="loc in $store.state.locales" :value="loc" :key="loc">
{{ loc }}
</option>
......@@ -109,30 +109,39 @@ export default {
this.$dialog.confirm({
message: this.$t('confirm-delete-class') + ' <i>' +
decodeURIComponent(this.expenseClass.value.split('#').pop()) + '</i> ?',
onConfirm: () => {
this.$store.dispatch('destroyExpenseClass', {
ressource: this.expenseClass,
onConfirm: async () => {
let expenseClasses = this.$store.getters.expenseClasses({
admin: this.admin
}).then(
() => {
this.$toast.open({
message: this.$t('delete-class-success') + '<br>' +
decodeURIComponent(this.expenseClass.value.split('#').pop()),
type: 'is-success',
duration: 3000
})
this.expenseClass = this.$store.getters.expenseClasses({
admin: this.admin
})[0] || null
this.loadText({})
}, error => {
this.$toast.open({
message: this.$t('delete-class-error') + '.<br>Error message: ' + error,
type: 'is-danger',
duration: 3000
})
}
)
})
if (expenseClasses.length>1) {
this.$store.dispatch('destroyExpenseClass', {
ressource: this.expenseClass,
admin: this.admin
}).then(
() => {
this.$toast.open({
message: this.$t('delete-class-success') + '<br>'+
decodeURIComponent(this.expenseClass.value.split('#').pop()),
type: 'is-success',
duration: 3000
})
this.expenseClass = this.$store.getters.expenseClasses({
admin: this.admin
})[0]
console.log(`new current expense class: ${this.expenseClass}`);
this.loadText({name: this.expenseClass})
}, error => {
this.$toast.open({
message: this.$t('delete-class-error') + '.<br>Error message: ' + error,
type: 'is-danger',
duration: 3000
})
}
)
} else {
await this.$store.dispatch('copyAdminRessource')
this.$store.dispatch('loadAdminRessource',this.admin)
}
}
})
},
......@@ -140,8 +149,10 @@ export default {
name,
lang
}) {
this.expenseClass = name || this.expenseClass
if (name) this.expenseClass = name
lang = lang || this.selectedLocale
console.log(`loading text for ${name}`);
if ( name == null ) return false
let text = this.$store.getters.eClassText({
ressource: this.expenseClass,
......
......@@ -48,13 +48,13 @@
:label="$t(month.name.toLowerCase())" horizontal
style="display:-webkit-box;text-align:right;margin-bottom:0rem;">
<b-numberinput @input="monthlyValueChanged(ID,month)" v-model="month.value" :step="$store.getters.stepValue()"
controls-position="compact" controls-rounded style="display:inline-flex;"/>
controls-position="compact" controls-rounded style="display:inline-flex;" min="0" max="10000000"/>
</b-field>
</div>
<div v-else-if="intentValue != undefined">
<b-field :label="$t('annual')" horizontal
<b-field :label="$t('annually')" horizontal
style="display:-webkit-box;text-align:right;">
<b-numberinput v-model.number="intentValue" :step="$store.getters.stepValue()" @input="valueChanged()"
<b-numberinput v-model="intentValue" :step="$store.getters.stepValue()" @input="valueChanged()"
controls-position="compact" controls-rounded min="0" max="10000000"
style="display:inline-flex;" />
</b-field>
......@@ -254,19 +254,21 @@ export default {
return allprocs
},
monthlyValueChanged(intent, month) {
let value = typeof(month.value) == 'number' ? month.value : 0
// console.log(JSON.stringify(month,null,4));
this.$store.commit('changeMonthlyValue', {
ID: month.ID,
value: month.value
value
})
},
valueChanged() {
if (this.intentValue != this.$store.getters.hasValue(this.ID)) {
this.$store.commit('changeIntentValue', {
ID: this.ID,
value: this.intentValue
})
this.$emit('valueChanged', this.$store.getters.processOfIntent(this.ID))
}
let value = typeof(this.intentValue) == 'number' ? this.intentValue : 0
this.$store.commit('changeIntentValue', {
ID: this.ID,
value
})
},
newParentProcess(newProcess) {
console.log('newParentProcess');
......@@ -303,6 +305,16 @@ export default {
console.log(this.nextLabel);
console.log(this.ID);
if (this.type == 'process' &&
this.$store.getters.allProcesses().map(a => a.name).includes(this.nextLabel)) {
console.log('Process Name already chosen');
this.$toast.open({
message: this.$t('no-double-cat'),
type: 'is-danger',
duration: 3000
})
return false
}
if (expenses.includes(this.nextLabel)) {
this.$toast.open({
message: this.$t('rename-exp-err'),
......
......@@ -33,7 +33,7 @@ export default {
},
methods: {
KeyListener: function(evt) {
if (evt.keyCode === 68 && evt.ctrlKey && evt.shiftKey) { // 66==`d`
if (evt.keyCode === 80 && evt.ctrlKey && evt.altKey) { // 80==`p`
this.isEditModalActive = true
}
}
......
<template>
<div class="modal-card" style="width: auto">
<header class="modal-card-head">
<p class="modal-card-title">{{$t('join-expenses')}}</p>
<p class="modal-card-title">{{$t('join-exp')}}</p>
</header>
<section class="modal-card-body">
<b-field :label="$t('joint-exp-name')">
......
Subproject commit 7a97dfaa35289279581d36b9818f1b9681e4c296
Subproject commit 95f57ca021fefac1828e0d328b011d4df8a25ec7
......@@ -96,6 +96,10 @@ export default new Vuex.Store({
decimals: false
},
getters: {
// returns true if budget contains a intent categorized as ID
hasCategory: (state) => (ID) => {
return state.graph.any(undefined,VFBT('classifiedAs'),ID) == undefined ? false : true
},
budgetName: () => (budget) => {
return decodeURIComponent((new URL(budget.uri.value)).pathname.split('/').pop())
},
......@@ -218,7 +222,7 @@ export default new Vuex.Store({
// console.log(`budgetCategoriesUrl: ${state.graph}`)
// console.log(JSON.stringify(res))
if (!res) res = getters.adminIri(false)
if (!res.length) res = getters.adminIri(false)
else res = res[0].object.value
// console.log(JSON.stringify(res));
return res.split('#')[0]
......@@ -540,6 +544,8 @@ export default new Vuex.Store({
}
}
},
// returns a human readable list of expense category names saved on POD or on admin POD
classList: (state, getters) => ({list, admin}) => {
if (admin == undefined)
admin = false
......@@ -548,6 +554,18 @@ export default new Vuex.Store({
return raw.map(a => decodeURIComponent(a.value.split('#').pop()))
},
// returns all categories used in the loaded budget
budgetClasses: (state, getters) => () => {
// https://ilikekillnerds.com/2016/05/removing-duplicate-objects-array-property-name-javascript/
function removeDuplicates(myArr, prop) {
return myArr.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos;
});
}
let cats = state.graph.statementsMatching(undefined, VFBT('estimatedDemand')).map(st => getters.classification(st.object))
return removeDuplicates(cats,'value').map(obj=>obj.value)
},
classLabel: (state) => ({ressource,admin}) => {
if (!ressource) return false
let graph = admin ? state.eClassGraphAdmin : state.eClassGraph
......@@ -950,9 +968,9 @@ export default new Vuex.Store({
for (let j = 0; j < intents.length; j++) {
if (getters.processName(intents[j]) == newName) {
console.log("Intent with same name " + newName + " and category " + Class);
let oClass = getters.classification(intents[j]).value || undefined
if (oClass != Class) {
console.log(getters.classification(intents[j]))
let oClass = getters.classification(intents[j]) || undefined
if (oClass.value != Class.value) {
console.log(oClass)
console.log(Class);
console.log('Intents have not the same category');
reject('not same class')
......@@ -1161,12 +1179,12 @@ export default new Vuex.Store({
return new Promise(async (resolve, reject) => {
let graph = admin ? state.eClassGraphAdmin : state.eClassGraph
graph.removeMany(ressource)
await graph.removeMany(ressource)
if (!graph.statements.length) {
fileClient.deleteFile(getters.adminIri(admin)).then((suc) => resolve(suc), (err) => reject(err))
} else {
dispatch('saveAdminRessource', {
await dispatch('saveAdminRessource', {
admin,
graph
}).then((suc) => resolve(suc), (err) => reject(err))
......@@ -1278,8 +1296,9 @@ export default new Vuex.Store({
() => resolve('working'),
(err) => {
if (err=='app not authenticated') {
resolve () //TODO: this is a hack
// reject ('app not authenticated')
console.log(err);
reject (err)
return
}
console.log(`Private subfolder not loadable. Errorcode: ${err}`);
......@@ -1324,11 +1343,9 @@ export default new Vuex.Store({
state.ro=false
await dispatch('testSession').then(
() => resolve(session),
() => {
console.log('auth error')
// auth.logout()
// this.$router.push('config')
// reject('login failure')
(err) => {
console.log(err)
reject(err)
}
)
}, err => {
......@@ -1378,7 +1395,7 @@ export default new Vuex.Store({
)
})
},
async loadBudget({commit, state, dispatch} , url) {
async loadBudget({commit, state, getters,dispatch} , url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
var graph = $rdf.graph()
......@@ -1401,13 +1418,14 @@ export default new Vuex.Store({
}
// Examine the text in the response
response.text().then(function(text) {
response.text().then(async function(text) {
try {
$rdf.parse(text, graph, url, mimeType)
commit('setGraph', graph)
commit('setLdRessource', url)
console.log('graph:\n' + graph)
dispatch ('loadExpenseCategories')
// console.log('graph:\n' + graph)
await dispatch ('loadExpenseCategories')
state.decimals = getters.stepValue() == '0.01' ? true : false
resolve(url)
} catch (err) {
console.log(err) //TODO: show as toast
......@@ -1653,7 +1671,13 @@ export default new Vuex.Store({
$rdf.parse(text, graph, url, mimeType)
state.eClassGraph = graph
console.log(`Loaded expenseCategories: ${graph}`);
let cats = graph.each(undefined,RDF('type')).map(st => st.value)
let bcats = getters.budgetClasses()
console.log(`Available expense categories: ${JSON.stringify(cats,null,4)}`);
console.log(`Expense categories used in the budget: ${JSON.stringify(bcats,null,4)}`);
bcats.map(bcat => {
if (!cats.includes(bcat)) console.log(`Warning! Undefined expense category ${bcat}`);
})
resolve()
} catch (err) {
// console.log(err)
......@@ -1666,6 +1690,16 @@ export default new Vuex.Store({
})
})
},
async copyAdminRessource({getters,state, dispatch}) {
let from = getters.adminIri(true)
let to = getters.adminIri(false)
console.log('copy ' + from + ' to ' + to);
await $rdf.fetcher(state.graph).webCopy(from, to, 'text/turtle')
.then(res => {
console.log('copy done ' + JSON.stringify(res));
dispatch('loadAdminRessource', false)
}, e => console.log < ("Error copying : " + e))
},
async loadAdminRessource({getters, state, dispatch} ,admin) {
let url = getters.adminIri(admin)
fileClient.fetchAndParse(url, 'text/turtle').then(graph => {
......@@ -1675,14 +1709,7 @@ export default new Vuex.Store({
console.log(`failed fetching ${url}`)
console.log(`error code: ${err}`);
if (!admin && (err.substr(0, 3) == '404')) {
let from = getters.adminIri(true)
let to = getters.adminIri(false)
console.log('copy ' + from + ' to ' + to);
await $rdf.fetcher(state.graph).webCopy(from, to, 'text/turtle')
.then(res => {
console.log('copy done ' + JSON.stringify(res));
dispatch('loadAdminRessource', false)
}, e => console.log < ("Error copying : " + e))
dispatch('copyAdminRessource')
} else {
console.log('init graph');
admin ? state.eClassGraphAdmin = $rdf.graph() : state.eClassGraph = $rdf.graph()
......
This diff is collapsed.
......@@ -8,7 +8,7 @@
You can login with user <code>solidbase</code>with password <code>!+`9B!>;/u</code> on
<strong>Solid Community</strong> for demo purposes.<br><br>
<LoginButton />
<div v-if="loggedIn && $store.state.webId && $store.state.eClassGraph.statements.length">
<div v-if="loggedIn && $store.state.webId">
<PersonalInfo />
<div :title="$t('change-locale')">
{{$t('preferred-locale')}} <strong>{{locale}}</strong>.
......@@ -23,18 +23,20 @@
</div>
</div>
<hr>
<h2>{{$t('exp-cat-conf')}}</h2>
<div style="display: inline-block;">
<classEditor :admin='false' />
<div v-if="$store.state.eClassGraph.statements.length">
<h2>{{$t('exp-cat-conf')}}</h2>
<div style="display: inline-block;">
<classEditor :admin='false' />
</div>
<hr>
</div>
<hr>
</div>
<div v-else>
<!-- <div> TODO: make work
<b-loading :is-full-page="true" :active="loggedIn">
<b-icon pack="fas" icon="sync-alt" size="is-large" custom-class="fa-spin">
</b-icon>
</b-loading>
</div>
</div> -->
</div>
</template>
......