cubash-archive/frontend/src/App.vue

2474 lines
91 KiB
Vue

<style>
h1 {
font-size: 32px ;
}
h2 {
font-size: 24px ;
}
mini-br {
border-width: 0.1em;
}
unselectable {
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
}
.vertical {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.emoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
blockquote {
border-left: 8px solid #bab7b7;
padding: 1rem;
}
.modal .animation-content .modal-card {
overflow-y: scroll;
scrollbar-width: none;
-ms-overflow-style: none;
}
.modal .animation-content .modal-card::-webkit-scrollbar {
width: 0;
height: 0;
}
.modal-card-body {
overflow: visible !important;
}
</style>
<template>
<div id='app'>
<div>
<confirm-modal
v-model='showConfirmModal'
@confirm='deleteAccount'
color='is-danger'
text='Send email'
style="z-index: 101"
>
<div class="card-content">
When submitting this, you will receive an email sent to your email address to proceed with account deletion.
</div>
</confirm-modal>
<modal-window v-model='settings.account.showLogoutModal' style='z-index: 100' width='25rem' :no-padding='true' :is-error="true">
<div slot="header">
You have been logged out.
</div>
<div slot='main'>
<p style='margin: 1rem;'>You have been logged out, please login with your new username, or email.</p>
</div>
<button
slot='footer'
class='button is-info'
style='z-index: 100; width: 100%;'
@click='showLoginModal = false'
ref='ajaxErrorsModalButton'
>
Okay
</button>
</modal-window>
<modal-window v-model='logoutModal' :can-cancel="true" :active="logoutModal" @update:active="value => logoutModal = value" style='z-index: 100'>
<template class="modal-card column box">
<div slot="header">
Logging out of {{$store.state.username}}
</div>
<div slot="main">
<p style='margin: 1rem;'>If you want to logout, press Logout, if you want to log out of all logged in accounts, press the Logout All button in the user dropdown menu. </p>
</div>
</template>
<button
slot='footer'
class='button is-info'
style='z-index: 100; width: 100%;'
@click='logout(false)'
ref='ajaxErrorsModalButton'
>
Logout
</button>
</modal-window>
<modal-window v-model='settings.account.emailCheckDelete' style='z-index: 100' width='25rem' :no-padding='true' :is-error="true">
<div slot="header">
You've got mail
</div>
<div slot='main'>
<p style='margin: 1rem;'>An email has been sent to delete your account, and to confirm that you're the true owner of the account.</p>
</div>
<button
slot='footer'
class='button is-info'
style='z-index: 100; width: 100%;'
@click='emailCheckDelete = false'
ref='ajaxErrorsModalButton'
>
Okay
</button>
</modal-window>
<modal-window v-model='showAjaxErrorsModal' style='z-index: 100' width='25rem' :no-padding='true' :is-error="true">
<div slot="header">
Oh uh!
</div>
<div slot='main'>
<p :key='error' v-for='error in this.$store.state.ajaxErrors' style='margin: 1rem;'>{{error}}</p>
</div>
<button
slot='footer'
class='button is-info'
style='z-index: 100; width: 100%;'
@click='showAjaxErrorsModal = false'
ref='ajaxErrorsModalButton'
>
Okay
</button>
</modal-window>
<b-modal style="z-index: 200" :can-cancel="false" :active="connModal" @update:active="value => connModal = value" v-model="connModal" full-screen>
<div class="modal-card" style="width: auto">
<section class="vertical">
<center>
<sync-loader color="#445159" :size="20"></sync-loader>
<h1>Connecting to Kaverti...</h1>
<p>Taking a while? Check the <a href="https://status.troplo.com">service status</a>, or alert us on <a href="https://twitter.com/Kaverti">Twitter</a>!</p>
</center>
</section>
</div>
</b-modal>
<b-modal :active="settingsModal" v-model="settingsModal">
<div class="modal-card" style="width: auto">
<template>
<div class='route_container section route_container--settings'>
<div class='column box'>
<tab-view
:tabs='["General", "Account", "Privacy", "Experiments", "About"]'
v-model="showSettingTab"
padding='true'
slot='main'
>
<template slot="General">
<div class='route_container'>
<h1>General settings</h1>
<div>
<h2>About me</h2>
<h4>
Description
</h4>
<b-input type="textarea"
:placeholder='"Hi, I am " + $store.state.username'
maxlength="256"
v-model='settings.general.description.value'
:error='settings.general.description.error'
></b-input>
<b-button
class='button is-info'
:loading='settings.general.description.loading'
@click='saveDescription'
>
Save description
</b-button>
</div>
<div>
<h2>Preferences</h2>
<b-switch class="is-info" v-model="settings.general.preferences.developerMode">Developer Mode and get beta features first</b-switch><br>
<b-button
class='button is-info'
:loading='settings.general.preferences.loading'
@click='savePreferences'
>
Save preferences
</b-button>
</div>
</div>
</template>
<template slot="Account">
<div class='route_container'>
<h1>Account settings</h1>
<br>
<div>
<h2>Change your password</h2>
<p class='p--condensed'>
For security, enter your current password
</p>
<div>
<fancy-input
placeholder='Current password'
autocomplete="current-password"
v-model='settings.account.password.current'
:error='settings.account.password.errors["current password"]'
type='password'
></fancy-input>
</div>
<div>
<fancy-input
placeholder='New password'
autocomplete="new-password"
v-model='settings.account.password.new'
:error='settings.account.password.errors["new password"]'
type='password'
></fancy-input>
</div>
<b-button
class='button is-success'
@click='savePassword'
:loading='settings.account.password.loading'
>Change password</b-button>
</div>
<div>
<h2>Change your username</h2>
<b>
Changing your username will cost 200 Koins!
</b>
<div>
<fancy-input
placeholder='Current password'
v-model='settings.account.username.password'
:error='settings.account.username.errors["password"]'
type='password'
></fancy-input>
</div>
<div>
<fancy-input
placeholder='New username'
v-model='settings.account.username.username'
:error='settings.account.username.errors["username"]'
></fancy-input>
</div>
<b-button
class='button is-success'
autocomplete="username"
@click='saveUsername'
:loading='settings.account.username.loading'
>Change username</b-button>
</div>
<div>
<br>
<h2>Change your email address</h2>
<p>Your email is currently: {{$store.state.email}}</p>
<p v-if="$store.state.emailVerified">Your email is verified!</p>
<p v-if="!$store.state.emailVerified">Your email is not verified, you may lose some functionality in the future with an unverified email address, if you entered your email wrong, you can change it here, or resend it.</p>
<b-button @click='verifyEmailResend' :loading='settings.account.email.loading' v-if="!$store.state.emailVerified">Resend Verification Email</b-button>
<p v-if="$store.state.emailVerified == null">Connecting...</p>
<p v-if="$store.state.emailVerified == undefined">Connecting...</p>
<div>
<fancy-input
placeholder='New email'
autocomplete="email"
v-model='settings.account.email.new'
:error='settings.account.email.errors["new email"]'
type='email'
></fancy-input>
</div>
<div>
<fancy-input
placeholder='Current password'
autocomplete="current-password"
v-model='settings.account.email.currentPassword'
:error='settings.account.email.errors["email current password"]'
type='password'
></fancy-input>
</div>
<b-button
class='button is-success'
@click='saveEmail'
:loading='settings.account.email.loadingChange'
>Change email address</b-button>
</div>
<div>
<br>
<h2>Enable 2 Factor Authentication <trpl-para>(Highly Recommended)</trpl-para></h2>
<p class='p--condensed'>
For security, enter your current password
</p>
<b-button
class='button is-success'
@click='save2FA'
:loading='settings.account.totp.loading'
>Enable 2FA</b-button>
</div>
<div>
<br>
<h2>Dangerous Options!</h2>
<h3>Kill all sessions</h3>
<p>
You'll be immediately logged out, along with all active sessions, this is useful if you think someone else is using your account, changing your password will also invalidate all sessions.
</p>
<b-button
class='button is-danger'
:loading='settings.account.invalidateSessionLoading'
@click='invalidateSession()'
>Invalidate sessions</b-button>
<h3>Delete your account</h3>
<p>
Once this is done, your account <strong>cannot</strong> be restored <br/>
Your current posts however will be retained, and will show up as the [deleted] user.
</p>
<b-button
class='button is-danger'
:loading='settings.account.deleteAccountLoading'
@click='showConfirmModal = true'
>Delete my account</b-button>
</div>
</div>
</template>
<template slot="Privacy">
<div>
<center>
<h1>Privacy</h1>
<br>
<div>
<h2>Opt out of user walls</h2>
<b-switch
type='is-info'
v-model="settings.privacy.wall"
>
Toggle
</b-switch>
</div>
<br>
<b-button
class='button is-info'
:loading='settings.privacy.loading'
@click='savePrivacy()'
>
Save privacy preferences
</b-button>
</center>
</div>
</template>
<template slot="Experiments">
<div v-if="$store.state.developerMode">
<center>
<h1>Experiments</h1>
<p>Experiments are features that aren't ready to be public, but if you want to try them early, you can (expect bugs), experiments may require a refresh or a route change to take effect.</p>
<div class="box">
<h2>Dark Theme (Remote, Default: Light)</h2>
<b-switch
passive-type='is-warning'
type='is-dark'
false-value="light"
true-value="dark"
v-model="settings.experiments.theme"
v-if="$store.state.developerMode">
Toggle
</b-switch>
</div>
<div class="box">
<h2>Relationships (Local, Default: Disabled)</h2>
<b-switch
v-model="settings.experiments.relationships"
v-if="$store.state.developerMode">
Toggle
</b-switch>
</div>
<div class="box">
<h2>Teams (Local, Default: Disabled)</h2>
<b-switch
v-model="settings.experiments.teams"
v-if="$store.state.developerMode">
Toggle
</b-switch>
</div>
<div class="box">
<h2>Marketplace (Local, Default: Disabled)</h2>
<b-switch
v-model="settings.experiments.marketplace"
v-if="$store.state.developerMode">
Toggle
</b-switch>
</div>
<div class="box">
<h2>New Settings Redesign (Local, Default: Enabled)</h2>
<b-switch
v-model="settings.experiments.newsettings"
v-if="$store.state.developerMode">
Toggle
</b-switch>
</div>
<b-button
class='button is-info'
:loading='settings.experiments.loading'
@click='doBoth()'
>
Save experiment preferences
</b-button>
</center>
</div>
<div v-else>
<center>
<h2>Please enable Integration Developer Mode to gain access to experiments.</h2>
<p>It's easy! Just go to Settings > General, flip the switch and save! </p>
</center>
</div>
</template>
<template slot="About">
<div>
<center>
<img src="http://localhost/icon.png" width="10%">
<h1>Kaverti v{{this.$store.state.clientVersion}}</h1>
<p>Latest client version: v{{this.$store.state.latestClientVersion}}</p>
<p>API version: v{{this.$store.state.latestAPIVersion}}</p>
</center>
</div>
</template>
</tab-view>
</div>
</div>
</template>
</div>
</b-modal>
<modal-window
full-screen
v-model='showAccountModal'
@input='closeAccountModal'
style="z-index: 99"
:no-padding='true'
:hide-footer='true'
:hide-header="true"
>
<tab-view
:tabs='["Register", "Login", "Recovery"]'
v-model="showAccountTab"
padding='true'
slot='main'
>
<template slot='Register'>
<p style='margin-top: 0;'>
Sign up to {{name}} today!
<br/>It's fast and easy.
</p>
<br/>
<form @submit.prevent='createAccount'>
<fancy-input
ref='input'
v-model='input'
autocomplete="username"
:label-position="kavelabelPosition"
:error='signup.errors.username'
placeholder='Username'
width='100%'
>
</fancy-input>
<fancy-input
v-model='signup.email'
autocomplete="email"
:label-position="kavelabelPosition"
:error='signup.errors.email'
placeholder='Email'
width='100%'
type="email"
>
</fancy-input>
<fancy-input
v-model='signup.password'
autocomplete="new-password"
:label-position="kavelabelPosition"
:error='signup.errors.hash'
placeholder='Password'
type='password'
width='100%'
>
</fancy-input>
<fancy-input
v-model='signup.confirmPassword'
autocomplete="new-password"
:label-position="kavelabelPosition"
:error='signup.errors.confirmPassword'
placeholder='Confirm Password'
type='password'
width='100%'
>
</fancy-input>
By pressing the "Register" button, you agree to the Kaverti <router-link to="/legal/tos" v-on:click.native="closeAccountModal">Terms of Service</router-link>, and have read the <router-link to="/legal/privacy" v-on:click.native="closeAccountModal">Privacy Policy</router-link>.
<div style='margin-top: 0.5rem;'>
<b-button
class='is-info'
style="width: 55%"
:loading='signup.loading'
@click='createAccount'
v-if='!$store.state.meta.RegistrationsDisabled'
>
Register
</b-button>
<b-button
class='is-info disabled'
disabled="disabled"
style="width: 55%"
:loading='signup.loading'
@click='closeAccountModal'
v-if='$store.state.meta.RegistrationsDisabled'
>
Disabled
</b-button>
&nbsp;
<b-button style="float: right" class="is-danger-passive" @click='closeAccountModal'>
Cancel
</b-button>
</div>
</form>
</template>
<template slot='TCreate'>
<p style='margin-top: 0;'>
Sign up to {{name}} today!
<br/>It's fast and easy.
</p>
<br/>
<form @submit.prevent='createAccount'>
<fancy-input
ref='input'
v-model='tcreate.username'
:label-position="kavelabelPosition"
:error='tcreate.errors.username'
placeholder='Username'
width='100%'
>
</fancy-input>
<fancy-input
v-model='tcreate.name'
:label-position="kavelabelPosition"
:error='tcreate.errors.name'
placeholder='Name'
width='100%'
type="email"
>
</fancy-input>
By pressing the "Register" button, you agree to the Kaverti <router-link to="/legal/tos" v-on:click.native="closeAccountModal">Terms of Service</router-link>, and have read the <router-link to="/legal/privacy" v-on:click.native="closeAccountModal">Privacy Policy</router-link>.
<div style='margin-top: 0.5rem;'>
<b-button
class='is-info'
style="width: 55%"
:loading='tcreate.loading'
@click='createTeam'
v-if='!$store.state.meta.RegistrationsDisabled'
>
Register
</b-button>
<b-button
class='is-info disabled'
disabled="disabled"
style="width: 55%"
:loading='signup.loading'
@click='closeAccountModal'
v-if='$store.state.meta.RegistrationsDisabled'
>
Disabled
</b-button>
&nbsp;
<b-button style="float: right" class="is-danger-passive" @click='closeAccountModal'>
Cancel
</b-button>
</div>
</form>
</template>
<template slot='Login'>
<p style='margin-top: 0;'>
Login to {{name}}
</p>
<form @submit.prevent='doLogin' v-if="loginAs !== 'limit'">
<fancy-input
v-model='login.username'
autocomplete="username"
:error='login.errors.username'
placeholder='Username or Email'
width='100%'
>
</fancy-input>
<fancy-input
v-model='login.password'
autocomplete="current-password"
:error='login.errors.hash'
placeholder='Password'
type='password'
width='100%'
>
</fancy-input>
<div style='margin-top: 0.5rem;'>
<b-button
class='is-info'
style="width: 55%"
:loading='login.loading'
@click='doLogin'
>
Login
</b-button>
&nbsp;
<b-button style="float: right" class="is-danger-passive" @click='closeAccountModal'>
Cancel
</b-button>
</div>
</form>
<div v-else>
<p>Oh uh you have reached the account limit, please log out of an account to continue.</p>
</div>
</template>
<template slot='Recovery'>
<p style='margin-top: 0;'>
{{name}} Account Recovery
</p>
<form @submit.prevent='doRecovery'>
<fancy-input
v-model='recovery.email'
:error='recovery.errors.email'
placeholder='Email'
width='100%'
>
</fancy-input>
<div style='margin-top: 0.5rem;'>
<b-button
class='is-info'
style="width: 55%"
:loading='recovery.loading'
@click='doRecovery'
>
Send email
</b-button>
&nbsp;
<b-button style="float: right" class="is-danger-passive" @click='closeAccountModal'>
Cancel
</b-button>
</div>
</form>
</template>
</tab-view>
</modal-window>
<template>
<section v-if='$store.state.clientVersion !== $store.state.latestClientVersion' class="hero is-info">
<div class="hero-body" style="padding: 1rem 1rem !important;">
<div class="mobile-container">
<div class="container">
<p style="text-align: center;">You are using an outdated client, refresh to update.</p>
</div>
</div>
</div>
</section>
<section v-if='!$store.state.emailVerified && $store.state.username' class="hero is-danger">
<div class="hero-body" style="padding: 1rem 1rem !important;">
<div class="mobile-container">
<div class="container">
<p style="text-align: center;"><b>Your email is not verified.</b> You will have limited access to Kaverti until you verify your email in <router-link style="color: #ffffff;" to="/settings/account">User Settings</router-link></p>
</div>
</div>
</div>
</section>
<section v-if="settings.experiments.corrupt" class="hero is-danger">
<div class="hero-body" style="padding: 1rem 1rem !important;">
<div class="mobile-container">
<div class="container">
<p style="text-align: center;">It has been detected that your Kaverti Experiments options have been corrupted, this may lead to issues when browsing Kaverti. <a @click="resetExperimentsStore()" style="color: #FFFFFF;">Reset experiment options</a></p>
</div>
</div>
</div>
</section>
<section v-if='$store.state.meta.bannerText && !$store.state.username' class="hero is-info">
<div class="hero-body" style="padding: 1rem 1rem !important;">
<div class="mobile-container">
<div class="container">
<p style="text-align: center;">{{$store.state.meta.bannerText}}</p>
</div>
</div>
</div>
</section>
<b-navbar>
<template slot="brand">
<b-navbar-item tag="router-link" :to="{ path: '/' }">
<img
:src = $store.state.meta.icon
>
<h2 style="padding-left: 3px;">{{name}}</h2>
</b-navbar-item>
</template>
<template slot="start">
<b-navbar-item tag="router-link" :to="{ path: '/forums' }"><b>
Forums
</b></b-navbar-item>
<b-navbar-item tag="router-link" :to="{ path: '/users' }"><b>
Users
</b></b-navbar-item>
<b-navbar-item tag="router-link" :to="{ path: '/marketplace' }"><b>
Marketplace
</b></b-navbar-item>
<b-navbar-item tag="router-link" :to="{ path: '/teams' }"><b>
Teams
</b></b-navbar-item>
<div class="navbar-item has-dropdown is-hoverable is-info" type="is-info">
<a class="navbar-link">
<b>More</b>
</a>
<div class="navbar-dropdown is-boxed">
<router-link class="navbar-item" to='/blog'>Blog</router-link>
<a class="navbar-item" href="https://twitter.com/Kaverti">
Twitter
</a>
<a class="navbar-item" href="https://discord.gg/3jN5RAX">
Discord
</a>
<router-link class="navbar-item" to='/developers'>Developers</router-link>
<router-link class="navbar-item is-active" to='/premium'>Upgrade</router-link>
<router-link class="navbar-item" to='/licenses'>Credits and Licenses</router-link>
<router-link class="navbar-item" to='/contributors'>Contributors</router-link>
<router-link class="navbar-item is-active" to='/jobs'>We're Hiring!</router-link>
</div>
</div>
</template>
<template slot="end" v-if='$store.state.username'>
<b-navbar-item @click="dailyReward()" v-if="$store.state.koins"><b>
{{$store.state.koins}} Koins
</b></b-navbar-item>
<b-navbar-item v-if="!$store.state.koins"><b>
<i class="fas fa-circle-notch fa-spin"></i> Loading...
</b></b-navbar-item>
<b-navbar-item style="background: transparent; " tag="div">
<NotificationButton class="is-hidden-mobile"></NotificationButton>
</b-navbar-item>
<b-navbar-item tag="div">
<div>
<div class="navbar-item has-dropdown is-hoverable is-info">
<a class="navbar-link">
<b>{{$store.state.username}}</b>
</a>
<div class="navbar-dropdown is-boxed">
<router-link class="navbar-item" :to='"/u/" + this.$store.state.username'>My Profile</router-link>
<a class="navbar-item is-hidden-mobile" v-if="$store.state.experimentsStore.newsettings" @click="settingsModal = true">Settings</a>
<router-link class="navbar-item is-hidden-desktop is-hidden-tablet" to='/settings' v-if="$store.state.experimentsStore.newsettings">Settings</router-link>
<router-link class="navbar-item " to='/settings' v-if="!$store.state.experimentsStore.newsettings">Settings</router-link>
<router-link to='/inventory' class="navbar-item">Inventory</router-link>
<router-link to='/transactions' class="navbar-item">Transactions</router-link>
<router-link to='/friends' class="navbar-item">Friends</router-link>
<router-link to='/character' class="navbar-item">Character</router-link>
<router-link class="navbar-item is-hidden-desktop" to='/notifications'>Notifications</router-link>
<router-link to='/admin' class="navbar-item" v-if='$store.state.admin'>Admin Panel</router-link>
<router-link class="navbar-item is-active" to='/premium'>Upgrade</router-link>
<a class="navbar-item" @click='logoutModal = true'>
Log out
</a>
</div>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable is-info">
<a class="navbar-link">
<b>Accounts</b>
</a>
<div class="navbar-dropdown is-boxed">
<b-navbar-item @click="switchUser('user1')" v-if="$store.state.availableUsers.user1.username">{{$store.state.availableUsers.user1.username}}</b-navbar-item>
<b-navbar-item @click="switchUser('user2')" v-if="$store.state.availableUsers.user2.username">{{$store.state.availableUsers.user2.username}}</b-navbar-item>
<b-navbar-item @click="switchUser('user3')" v-if="$store.state.availableUsers.user3.username">{{$store.state.availableUsers.user3.username}}</b-navbar-item>
<b-navbar-item @click="switchUser('user3')" v-if="$store.state.availableUsers.user3.username">{{$store.state.availableUsers.user3.username}}</b-navbar-item>
<b-navbar-item @click="showAccountModalTab(1)">Add another user</b-navbar-item>
<b-navbar-item @click="logoutAll">Logout all users</b-navbar-item>
<b-navbar-item @click="reload()">Not seeing any users?</b-navbar-item>
</div>
</div>
</b-navbar-item>
<b-navbar-item style="background: transparent; " tag="div">
<search-box class="kave-search" header-bar='true'></search-box>
</b-navbar-item>
</template>
<template slot="end" v-else>
<b-navbar-item tag="div">
<div class="buttons">
<a class="button is-info" @click="showAccountModalTab(0)">
<strong>Sign up</strong>
</a>
<a class="button is-light" @click="showAccountModalTab(1)">
Log in
</a>
</div>
</b-navbar-item>
</template>
</b-navbar>
</template>
<not-found v-show='$store.state.show404Page'></not-found>
<transition name='fade'>
<router-view id="main" v-show='!$store.state.show404Page'></router-view>
</transition>
<footer>
<div class="content has-text-centered">
<br>
<p>
<strong>Kaverti</strong> by <a href="https://troplo.com">Troplo</a> and <router-link to="/contributors">Contributors</router-link>
<br/>
Be sure to read the <router-link to="/legal/tos">Terms of Service</router-link> and <router-link to="/legal/privacy">Privacy Policy</router-link>
</p>
<br>
</div>
</footer>
</div>
</div>
</template>
<style>
@import 'https://kit-pro.fontawesome.com/releases/v5.15.1/css/pro.min.css';
</style>
<style lang="scss">
$primary: #007aff ;
$navbar-dropdown-arrow: #007aff ;
</style>
<script>
import ModalWindow from './components/ModalWindow'
import TabView from './components/TabView'
import FancyInput from './components/FancyInput'
import NotificationButton from './components/NotificationButton'
import SearchBox from './components/SearchBox'
import { SyncLoader } from '@saeris/vue-spinners'
import NotFound from './components/routes/NotFound'
import ConfirmModal from './components/ConfirmModal'
import AjaxErrorHandler from './assets/js/errorHandler'
import axios from "axios";
export default {
name: 'app',
components: {
ModalWindow,
TabView,
FancyInput,
NotificationButton,
SearchBox,
NotFound,
SyncLoader,
ConfirmModal
},
data() {
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
return {
kavelabelPosition: 'on-border',
logoutModal: false,
loginAs: 'user1',
signup: {
username: '',
password: '',
email: '',
confirmPassword: '',
passkey: '',
loading: false,
errors: {
username: '',
email: '',
hash: '',
confirmPassword: ''
}
},
tcreate: {
username: '',
name: '',
loading: false,
errors: {
username: '',
name: ''
}
},
login: {
username: '',
password: '',
loading: false,
errors: {
username: '',
hash: ''
}
},
recovery: {
email: '',
loading: false,
errors: {
email: ''
}
},
settings: {
menuItems: [
{ name: 'General', route: 'general', icon: 'cog' },
{ name: 'Account', route: 'account', icon: 'user'},
{ name: 'Privacy', route: 'privacy', icon: 'lock'},
{ name: 'Experiments', route: 'experiments', icon: 'flask'},
{ name: 'About', route: 'about', icon: 'info-circle'},
],
general: {
description: {
value: '',
loading: false,
error: ''
},
preferences: {
theme: '',
developerMode: '',
loading: false,
error: ''
},
},
privacy: {
wall: false,
loading: false
},
experiments: {
theme: 'light',
loading: false,
error: '',
relationships: false,
wall: false,
marketplace: false,
teams: false,
newsettings: true,
corrupt: false,
local: []
},
account: {
showLogoutModal: false,
password: {
loading: false,
current: '',
new: '',
errors: {
'new password': '',
'current password': ''
}
},
email: {
loading: false,
loadingChange: false,
currentPassword: '',
new: '',
errors: {
'new password': '',
'email current password': ''
}
},
username: {username: '',
password: '',
errors: {
'username': '',
'password': '',
},
loading: false,
},
deleteAcc: {
loading: false,
password: '',
errors: {
'current password': ''
}
},
totp: {loading: false
},
deleteAccountLoading: false,
invalidateSessionLoading: false,
emailCheckDelete: false,
showLoginModal: false
}
},
selected: 0,
input: '',
experimentsTemp: {"wall":false,"theme":"light","relationships":false,"teams":false,"marketplace":false, "newsettings": true},
loadingLogout: false,
showMenu: false,
connModal: true,
settingsModal: false,
showConfirmModal: false,
ajaxErrorHandler: AjaxErrorHandler(this.$store)
}
},
computed: {
name() {
return this.$store.state.meta.name
},
logo() {
if (this.$store.state.meta.logo) {
this.$store.state.meta.logo
}
return "http://localhost/logo.png";
},
theme() {
return this.$store.state.theme
},
showAccountModal: {
get() {
return this.$store.state.accountModal
},
set(val) {
this.$store.commit('setAccountModalState', val);
}
},
showAjaxErrorsModal: {
get() {
return this.$store.state.ajaxErrorsModal
},
set(val) {
this.$store.commit('setAjaxErrorsModalState', val)
}
},
showConnModal: {
get() {
return this.$store.state.connModal
},
set(val) {
this.$store.commit('setConnModal', val);
}
},
showOutLinkModal: {
get() {
return this.$store.state.showOutLinkModal
},
set(val) {
this.$store.commit('setOutLinkModalState', val)
}
},
showAccountTab: {
get() {
return this.$store.state.accountTabs
},
set(index) {
this.$store.commit('setAccountTabs', index)
}
},
showSettingTab: {
get() {
return this.$store.state.settingsTabs
},
set(index) {
this.$store.commit('setSettingsTabs', index)
}
},
categories() {
return this.$store.state.meta.categories
}
},
methods: {
reload() {
this.$router.go('/');
},
switchUser(user) {
if(JSON.parse(localStorage.getItem('currentUser'))) {
localStorage.removeItem('currentUser')
}
localStorage.setItem('currentUser', JSON.stringify(user));
this.$store.commit('setUser', user)
if(!JSON.parse(localStorage.getItem('currentUser'))) {
localStorage.setItem('currentUser', JSON.stringify("user1"));
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user1") {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken')))
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user2") {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken2')))
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user3") {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken3')))
} else {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken')))
}
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
if(res.data.theme === "dark") {
this.darkTheme()
}
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
},
experimentCheck() {
try {
JSON.parse(localStorage.getItem('experimentsStore'))
} catch (e) {
this.settings.experiments.corrupt = true
console.log("Corrupted experimentsStore, Please reset experimentsStore localStorage")
}
},
resetExperimentsStore() {
localStorage.removeItem('experimentsStore')
this.localExperiments()
this.setExperimentsStore()
},
doBoth() {
this.saveExperiments()
this.localExperiments()
this.setExperimentsStore()
},
localExperiments() {
localStorage.setItem('experimentsStore', JSON.stringify({wall: this.settings.experiments.wall, theme: this.settings.experiments.theme, newsettings: this.settings.experiments.newsettings, relationships: this.settings.experiments.relationships, teams: this.settings.experiments.teams, marketplace: this.settings.experiments.marketplace}));
this.setExperimentsStore()
},
saveExperiments() {
this.settings.experiments.error = ''
this.settings.experiments.loading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/experiments', {
theme: this.settings.experiments.theme
})
.then(() => {
this.settings.experiments.loading = false
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
})
})
.catch(e => {
this.settings.experiments.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
saveDescription() {
this.settings.general.description.error = ''
this.settings.general.description.loading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/preferences', {
description: this.settings.general.description.value
})
.then(() => {
this.settings.general.description.loading = false
})
.catch(e => {
this.settings.general.description.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
savePreferences() {
this.settings.general.preferences.error = ''
this.settings.general.preferences.loading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/preferences', {
developerMode: this.settings.general.preferences.developerMode
})
.then(() => {
this.settings.general.preferences.loading = false
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setDevMode', res.data.developerMode)
})
})
.catch(e => {
this.settings.general.preferences.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
capitalizeFirstLetter(value) {
return value.toUpperCase()
},
savePrivacy() {
this.settings.privacy.error = ''
this.settings.privacy.loading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/preferences', {
userWallOptOut: this.settings.privacy.wall
})
.then(() => {
this.settings.privacy.loading = false
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
})
})
.catch(e => {
this.settings.privacy.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
savePassword() {
this.settings.account.password.errors['current password'] = ''
this.settings.account.password.errors['new password'] = ''
if (!this.settings.account.password.current.length) {
this.settings.account.password.errors['current password'] = 'Cannot be blank'
return
}
if (!this.settings.account.password.new.length) {
this.settings.account.password.errors['new password'] = 'Cannot be blank'
return
}
this.settings.account.password.loading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'user/' + this.$store.state.username, {
currentPassword: this.settings.account.password.current,
newPassword: this.settings.account.password.new
})
.then(() => {
this.settings.account.password.loading = false
this.settings.account.email.loading = false
this.settings.account.password.current = ''
this.settings.account.password.new = ''
})
.catch(e => {
this.settings.account.password.loading = false
this.settings.account.email.loading = false
console.log(e)
AjaxErrorHandler(this.$store)(e, error => {
if (error.path === 'hash') {
this.settings.account.password.errors['new password'] = error.message
}
})
})
},
saveEmail() {
this.settings.account.email.errors['email current password'] = ''
this.settings.account.email.errors['new email'] = ''
if (!this.settings.account.email.currentPassword.length) {
this.settings.account.email.errors['email current password'] = 'Cannot be blank'
return
}
if (!this.settings.account.email.new.length) {
this.settings.account.email.errors['new email'] = 'Cannot be blank'
return
}
this.settings.account.email.loadingChange = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'user/' + this.$store.state.username, {
emailCurrentPassword: this.settings.account.email.currentPassword,
newEmail: this.settings.account.email.new
})
.then(() => {
this.settings.account.email.currentPassword = ''
this.settings.account.email.newEmail = ''
this.axios
.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/email-verify/send')
.then(() => {
this.settings.account.email.loadingChange = false
AjaxErrorHandler("Success, your email has been changed, and a verification email has been sent to your new email: " + this.$store.state.email)
})
.catch(e => {
this.settings.account.email.loadingChange = false
AjaxErrorHandler(this.$store)(e)
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
})
})
.catch(e => {
this.settings.account.email.loadingChange = false
console.log(e)
AjaxErrorHandler(this.$store)(e)
})
.catch(e => {
this.showConn(e)
})
},
save2FA() {
this.totp.loading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/preferences', {
twoFactorGetCode: true
})
.then(() => {
this.totp.loading = false
})
.catch(e => {
this.totp.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
invalidateSession() {
this.settings.account.invalidateSessionLoading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/preferences', {
invalidateSession: true
})
.then(() => {
this.settings.account.invalidateSessionLoading = false
})
.catch(e => {
this.settings.account.invalidateSessionLoading = false
AjaxErrorHandler(this.$store)(e)
})
},
saveUsername() {
this.username.errors['username'] = ''
if (!this.username.username.length) {
this.username.errors['username'] = 'Cannot be blank'
return
}
this.username.loading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/preferences', {
username: this.username.username,
password: this.username.password,
changeUsername: true
})
.then(() => {
this.username.username = ''
this.username.loading = false
localStorage.removeItem('accessToken');
this.$store.commit('setSessionToken', '')
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/state')
.then(res => {
this.$store.commit('setSettings', res.data)
this.$store.dispatch('setTitle', this.$store.state.meta.title)
this.$store.commit('setAPIVersion', res.data.latestAPIVersion)
this.$store.commit('setLatestVersion', res.data.latestStableVersion)
this.closeConn()
}).catch(err => {
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
} else {
this.showConn()
}
})
})
this.showLoginModal = true
})
.catch(e => {
this.username.loading = false
AjaxErrorHandler(this.$store)(e)
})
.catch(e => {
this.showConn(e)
})
},
deleteAccount() {
this.settings.account.deleteAccountLoading = true
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/delete')
.then(() => {
this.settings.account.deleteAccountLoading = false
this.settings.account.emailCheckDelete = true
})
.catch(e => {
this.settings.account.deleteAccountLoading = false
console.log(e)
AjaxErrorHandler(this.$store)(e)
})
},
verifyEmailResend() {
this.settings.account.email.loading = true
this.axios
.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/email-verify/send')
.then(() => {
this.settings.account.email.loading = false
})
.catch(e => {
this.settings.account.email.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
inputHandler(e) {
//tab, space or enter
if (
e.keyCode === 8 &&
!this.signup.username.length &&
this.selected.length
) {
this.signup.username = this.selected.pop().username;
//Down
} else if (e.keyCode === 40) {
this.setSelectionFocus(1);
//Up
} else if (e.keyCode === 38) {
this.setSelectionFocus(-1);
}
},
authHeader() {
let user = JSON.parse(localStorage.getItem('accessToken'));
if (user && user.accessToken) {
return {'Authorization': 'Bearer ' + user.accessToken};
} else {
return {};
}
},
darkTheme() {
let darkThemeLinkEl = document.createElement("link");
darkThemeLinkEl.setAttribute("rel", "stylesheet");
darkThemeLinkEl.setAttribute("href", "http://localhost/css/dark.css");
darkThemeLinkEl.setAttribute("id", "dark-theme");
let docHead = document.querySelector("head");
docHead.append(darkThemeLinkEl);
},
setExperimentsStore() {
if (localStorage.getItem('experimentsStore')) {
this.experimentsTemp = JSON.parse(localStorage.getItem('experimentsStore'))
this.settings.experiments = JSON.parse(localStorage.getItem('experimentsStore'))
this.$store.commit('setExperimentsStore', this.experimentsTemp)
} else {
console.log("No experimentsStore, forcing default experiments to avoid route failure.")
localStorage.setItem('experimentsStore', JSON.stringify({wall: this.settings.experiments.wall, theme: this.settings.experiments.theme, relationships: this.settings.experiments.relationships, teams: this.settings.experiments.teams, marketplace: this.settings.experiments.marketplace, newsettings: this.settings.experiments.newsettings}));
this.setExperimentsStore()
}
},
disableDarkTheme() {
let darkThemeLinkEl = document.removeElement("dark-theme");
let docHead = document.querySelector("head");
docHead.append(darkThemeLinkEl);
},
recaptcha() {
this.$recaptcha('login').then((token) => {
console.log(token) // Will print the token
})
},
showAccountModalTab(index) {
this.toggleMenu()
this.showAccountModal = true
this.showAccountTab = index
},
toggleMenu() {
this.showMenu = !this.showMenu
},
dailyReward() {
this.loading = true
this.axios.get(
process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/reward'
).then(res => {
this.$store.commit('setKoins', res.data.koins)
}).catch(err => {
this.loadingLogout = false
this.ajaxErrorHandler(err)
})
},
koinLimit() {
this.ajaxErrorHandler("Koin limit")
},
logoutAll() {
localStorage.removeItem('accessToken');
localStorage.removeItem('accessToken2');
localStorage.removeItem('accessToken3');
this.switchUser('user1')
this.$store.commit('setUsername', '')
},
logout(logoutAll) {
this.toggleMenu()
if(!JSON.parse(localStorage.getItem('currentUser'))) {
localStorage.setItem('currentUser', JSON.stringify("user1"));
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user1") {
localStorage.removeItem('accessToken');
if(JSON.parse(localStorage.getItem('currentUser')) === "user1" && localStorage.getItem('accessToken2')) {
this.switchUser('user2')
} else if(JSON.parse(localStorage.getItem('currentUser')) === "user1" && localStorage.getItem('accessToken3') && !localStorage.getItem('accessToken3')) {
this.switchUser('user3')
}
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user2") {
localStorage.removeItem('accessToken2');
if(JSON.parse(localStorage.getItem('currentUser')) === "user2" && localStorage.getItem('accessToken')) {
this.switchUser('user1')
} else if(JSON.parse(localStorage.getItem('currentUser')) === "user2" && localStorage.getItem('accessToken3') && !localStorage.getItem('accessToken')) {
this.switchUser('user3')
}
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user3") {
localStorage.removeItem('accessToken3');
if(JSON.parse(localStorage.getItem('currentUser')) === "user3" && localStorage.getItem('accessToken')) {
this.switchUser('user1')
} else if(JSON.parse(localStorage.getItem('currentUser')) === "user3" && localStorage.getItem('accessToken2') && !localStorage.getItem('accessToken')) {
this.switchUser('user2')
} else if(logoutAll){
localStorage.removeItem('accessToken');
localStorage.removeItem('accessToken2');
localStorage.removeItem('accessToken3');
this.switchUser('user1')
}
}
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/state')
.then(res => {
this.$store.commit('setSettings', res.data)
this.$store.dispatch('setTitle', this.$store.state.meta.title)
this.$store.commit('setAPIVersion', res.data.latestAPIVersion)
this.$store.commit('setLatestVersion', res.data.latestStableVersion)
this.closeConn()
}).catch(err => {
if (err.response.data.errors[0].name === 'noSettings') {
this.showConn()
} else {
this.showConn()
}
})
})
},
botLogout() {
if (this.$store.state.bot) {
this.$socket.emit('accountEvent')
this.$router.push('/')
this.toggleMenu()
localStorage.removeItem('accessToken');
this.$store.commit('setSessionToken', '')
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/state')
.then(res => {
this.$store.commit('setSettings', res.data)
this.$store.dispatch('setTitle', this.$store.state.meta.title)
this.$store.commit('setAPIVersion', res.data.latestAPIVersion)
this.$store.commit('setLatestVersion', res.data.latestStableVersion)
this.closeConn()
}).catch(err => {
if (err.response.data.errors[0].name === 'noSettings') {
this.showConn()
} else {
this.showConn()
}
})
})
}
},
clearSignup() {
this.signup.username = ''
this.signup.password = ''
this.signup.confirmPassword = ''
this.signup.email = ''
this.signup.passkey = ''
this.$store.commit('setToken', null)
this.$store.commit('setPassKey', null)
},
clearSignupErrors() {
this.signup.errors.username = ''
this.signup.errors.hash = ''
this.signup.errors.confirmPassword = ''
this.signup.errors.email = ''
this.signup.errors.passkey = ''
},
clearRecovery() {
this.recovery.email = ''
this.$store.commit('setToken', null)
this.$store.commit('setPassKey', null)
},
clearRecoveryErrors() {
this.recovery.errors.email = ''
},
pollConn() {
this.polling = setInterval(() => {
this.retryConnection()
}, 5000)
this.botLogout()
},
pollConnAlt() {
this.polling = setInterval(() => {
this.retryConnection()
}, 25000)
this.botLogout()
},
clearLogin() {
this.login.username = ''
this.login.password = ''
},
clearLoginErrors() {
this.login.errors.username = ''
this.login.errors.hash = ''
},
retryConnection() {
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/state')
.then(res => {
this.$store.commit('setSettings', res.data)
this.$store.dispatch('setTitle', this.$store.state.meta.title)
this.$store.commit('setAPIVersion', res.data.latestAPIVersion)
this.$store.commit('setLatestVersion', res.data.latestStableVersion)
this.closeConn()
}).catch(err => {
if (err.response.data.errors[0].name === 'noSettings') {
this.showConn()
} else {
this.showConn()
}
})
this.botLogout()
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/state')
.then(res => {
this.$store.commit('setSettings', res.data)
this.$store.dispatch('setTitle', this.$store.state.meta.title)
this.$store.commit('setAPIVersion', res.data.latestAPIVersion)
this.$store.commit('setLatestVersion', res.data.latestStableVersion)
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
this.closeConn()
}).catch(err => {
this.showConn()
if (err.response.data.errors[0].name === 'noSettings') {
this.showConn()
} else {
this.showConn()
}
})
})
},
closeAccountModal() {
this.showAccountModal = false
this.clearLogin()
this.clearSignup()
this.clearLoginErrors()
this.clearSignupErrors()
},
showConn() {
this.connModal = true
},
closeConn() {
this.connModal = false
},
createAccount() {
this.clearSignupErrors()
let postParams = {
username: this.signup.username,
password: this.signup.password,
email: this.signup.email,
passkey: this.signup.passkey
}
if (this.$store.state.token) {
postParams.admin = true
postParams.token = this.$store.state.token
}
if (this.$store.state.passkey) {
postParams.passkey = this.$store.state.passkey
}
if (this.signup.password !== this.signup.confirmPassword) {
this.signup.errors.confirmPassword = 'Passwords must match'
} else {
this.signup.loading = true
this.axios.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `passkey/${this.$store.state.passkey}`, postParams).then(res => {
this.signup.loading = false
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setAdmin', res.data.admin)
this.closeAccountModal()
this.$socket.emit('accountEvent')
this.axios
.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/email-verify/send')
.then(() => {
this.settings.account.email.loading = false
})
.catch(e => {
this.settings.account.email.loading = false
AjaxErrorHandler(this.$store)(e)
})
}).catch(e => {
this.signup.loading = false
this.ajaxErrorHandler(e, (error) => {
let path = error.path
if (this.signup.errors[path] !== undefined && this.signup.errors[path] !== undefined) {
this.signup.errors[path] = error.message
}
})
})
}
},
createTeam() {
this.clearSignupErrors()
let postParams = {
username: this.tcreate.username,
name: this.tcreate.name
}
this.tcreate.loading = true
this.axios.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/create`, postParams).then(res => {
this.signup.loading = false
this.$store.commit('setUsername', res.data.username)
this.closeAccountModal()
}).catch(e => {
this.tcreate.loading = false
this.ajaxErrorHandler(e, (error) => {
let path = error.path
if (this.tcreate.errors[path] !== undefined && this.tcreate.errors[path] !== undefined) {
this.tcreate.errors[path] = error.message
}
})
})
},
calculateLogin () {
if(!JSON.parse(localStorage.getItem('accessToken')) && !JSON.parse(localStorage.getItem('accessToken2')) && !JSON.parse(localStorage.getItem('accessToken3'))) {
this.loginAs = 'user1'
} else if(JSON.parse(localStorage.getItem('accessToken')) && !JSON.parse(localStorage.getItem('accessToken2'))) {
this.loginAs = 'user2'
} else if(JSON.parse(localStorage.getItem('accessToken')) && JSON.parse(localStorage.getItem('accessToken2')) && JSON.parse(localStorage.getItem('accessToken2')) && !JSON.parse(localStorage.getItem('accessToken3'))) {
this.loginAs = 'user3'
} else if(!JSON.parse(localStorage.getItem('accessToken')) && JSON.parse(localStorage.getItem('accessToken2')) && JSON.parse(localStorage.getItem('accessToken2')) && !JSON.parse(localStorage.getItem('accessToken3'))){
this.loginAs = 'user1'
} else if(!JSON.parse(localStorage.getItem('accessToken')) && !JSON.parse(localStorage.getItem('accessToken2')) && JSON.parse(localStorage.getItem('accessToken2')) && !JSON.parse(localStorage.getItem('accessToken3'))){
this.loginAs = 'user1'
} else if(JSON.parse(localStorage.getItem('accessToken')) && !JSON.parse(localStorage.getItem('accessToken2')) && JSON.parse(localStorage.getItem('accessToken2')) && !JSON.parse(localStorage.getItem('accessToken3'))){
this.loginAs = 'user2'
} else {
this.loginAs = 'limit'
}
},
doLogin () {
this.calculateLogin()
this.clearSignupErrors()
if(!this.login.username.trim().length) {
this.login.errors.username = 'Username must not be blank'
return
}
this.login.loading = true
this.axios.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/users/login`, {
username: this.login.username,
password: this.login.password
}).then(res => {
this.login.loading = false
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setAdmin', res.data.admin)
if(this.loginAs === 'user1') {
localStorage.setItem('accessToken', JSON.stringify(res.data.accessToken));
this.switchUser('user1')
} else if (this.loginAs === 'user2') {
localStorage.setItem('accessToken2', JSON.stringify(res.data.accessToken));
this.switchUser('user2')
} else if (this.loginAs === 'user3') {
localStorage.setItem('accessToken3', JSON.stringify(res.data.accessToken));
this.switchUser('user3')
} else {
console.log('Limit Reached')
}
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
if (localStorage.getItem('accessToken')) {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken')))
}
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
this.closeAccountModal()
this.$socket.emit('accountEvent')
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
this.botLogout()
this.calculateLogin()
})
}).catch(e => {
this.login.loading = false
this.ajaxErrorHandler(e, (error) => {
let path = error.path
if(this.signup.errors[path] !== undefined && this.signup.errors[path] !== undefined) {
this.signup.errors[path] = error.message
}
})
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo', {
headers: {
'Authorization': this.$store.state.availableUsers.user1.sessToken
}
})
.then(res => {
this.$store.commit('setMultiUser1', res.data)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo', {
headers: {
'Authorization': this.$store.state.availableUsers.user2.sessToken
}
})
.then(res => {
this.$store.commit('setMultiUser2', res.data)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo', {
headers: {
'Authorization': this.$store.state.availableUsers.user3.sessToken
}
})
.then(res => {
this.$store.commit('setMultiUser3', res.data)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
},
doRecovery () {
this.clearRecoveryErrors()
if(!this.recovery.email.trim().length) {
this.recovery.errors.email = 'Email must not be blank'
return
}
this.recovery.loading = true
this.axios.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `users/recovery/send`, {
email: this.recovery.email
}).then(res => {
this.recovery.loading = false
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setAdmin', res.data.admin)
this.closeAccountModal()
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
}).catch(e => {
this.recovery.loading = false
this.ajaxErrorHandler(e)
})
}
},
mounted () {
console.log(JSON.parse(localStorage.getItem('currentUser')))
this.$store.commit('setMultiUser1Sess', JSON.parse(localStorage.getItem('accessToken')))
this.$store.commit('setMultiUser2Sess', JSON.parse(localStorage.getItem('accessToken2')))
this.$store.commit('setMultiUser3Sess', JSON.parse(localStorage.getItem('accessToken3')))
if(!JSON.parse(localStorage.getItem('currentUser'))) {
localStorage.setItem('currentUser', JSON.stringify("user1"));
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user1") {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken')))
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user2") {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken2')))
} else if (JSON.parse(localStorage.getItem('currentUser')) === "user3") {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken3')))
} else {
this.$store.commit('setSessionToken', JSON.parse(localStorage.getItem('accessToken')))
}
Object.assign(axios.defaults, {headers: {Authorization: this.$store.state.sessToken}})
this.experimentCheck()
this.calculateLogin()
console.log('%cStop!', 'color: red; font-size: 60px; font-weight: bold;');
console.log('%cIf you have been told to enter something in here, its most likely malicious! Someone might be trying to gain access to your ' + this.name + ' account! Please do not enter anything in here unless you know what you\'re doing', 'font-size: 16px; font-weight: bold;');
console.log('%cStop!', 'color: red; font-size: 60px; font-weight: bold;');
console.log('%cIf you have been told to enter something in here, its most likely malicious! Someone might be trying to gain access to your ' + this.name + ' account! Please do not enter anything in here unless you know what you\'re doing', 'font-size: 16px; font-weight: bold;');
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo', {
headers: {
'Authorization': this.$store.state.availableUsers.user1.sessToken
}
})
.then(res => {
this.$store.commit('setMultiUser1', res.data)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo', {
headers: {
'Authorization': this.$store.state.availableUsers.user2.sessToken
}
})
.then(res => {
this.$store.commit('setMultiUser2', res.data)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo', {
headers: {
'Authorization': this.$store.state.availableUsers.user3.sessToken
}
})
.then(res => {
this.$store.commit('setMultiUser3', res.data)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
this.axios
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.settings.general.description.value = res.data.description || ''
this.settings.general.preferences.developerMode = res.data.developerMode
this.settings.general.preferences.theme = res.data.theme || ''
this.settings.privacy.wall = res.data.userWallOptOut || ''
this.settings.experiments.theme = res.data.theme || ''
})
.catch(e => {
AjaxErrorHandler(this.$store)(e)
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/state')
.then(res => {
this.$store.commit('setSettings', res.data)
this.$store.dispatch('setTitle', this.$store.state.meta.title)
this.$store.commit('setAPIVersion', res.data.latestAPIVersion)
this.$store.commit('setLatestVersion', res.data.latestStableVersion)
this.closeConn()
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
if(res.data.theme === "dark") {
this.darkTheme()
}
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
this.axios.get(
process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'users/reward'
).then(res => {
this.$store.commit('setKoins', res.data.koins)
})
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
console.log(this.$store.state.sessToken + 'IDENTIFY')
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo', {
headers: {
'Authorization': this.$store.state.sessToken
}
})
.then(res => {
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setEmail', res.data.email)
this.$store.commit('setEmailVerified', res.data.emailVerified)
this.$store.commit('setAdmin', res.data.admin)
this.$store.commit('setKoins', res.data.koins)
this.$store.commit('setDevMode', res.data.developerMode)
this.$store.commit('setTheme', res.data.theme)
this.$store.commit('setExecutive', res.data.executive)
this.$store.commit('setUserId', res.data.id)
this.$store.commit('setBot', res.data.bot)
this.closeConn()
}).catch(err => {
this.showConn()
this.pollConn()
if(err.response.data.errors[0].name === 'noSettings') {
this.showConn()
this.pollConn()
} else {
this.showConn()
this.pollConn()
}
})
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'forums/category')
.then(res => {
this.$store.commit('addCategories', res.data)
if(!this.$store.state.meta.title.length && this.$route.params.category) {
let selectedCategory = this.$route.params.category.toUpperCase()
let category = this.categories.find(c => c.value === selectedCategory)
this.$store.dispatch('setTitle', category.name)
}
})
this.setExperimentsStore()
this.pollConnAlt()
console.log('%cStop!', 'color: red; font-size: 60px; font-weight: bold;');
console.log('%cIf you have been told to enter something in here, its most likely malicious! Someone might be trying to gain access to your ' + this.name + ' account! Please do not enter anything in here unless you know what you\'re doing', 'font-size: 16px; font-weight: bold;');
console.log('%cStop!', 'color: red; font-size: 60px; font-weight: bold;');
console.log('%cIf you have been told to enter something in here, its most likely malicious! Someone might be trying to gain access to your ' + this.name + ' account! Please do not enter anything in here unless you know what you\'re doing', 'font-size: 16px; font-weight: bold;');
this.botLogout()
},
watch: {
input () {
let input = this.input.trim();
if(input) {
this.axios
.get('/api/v1/user/' + input)
.then(res => {
this.signup.username.errors = 'Username is taken'
res()
})
.catch(e => {
this.signup.username.errors = 'Username is available'
e()
})
}
},
$route () {
this.showMenu = false
},
'$store.state.ajaxErrorsModal': function(val) {
if(val) {
this.$refs.ajaxErrorsModalButton.focus()
}
},
}
}
</script>
<style lang='scss'>
@import url('https://fonts.googleapis.com/css?family=Lato:400,400i,500,500i,700');
@import './assets/scss/variables.scss';
@import './assets/scss/transitions.scss';
@import url('https://fonts.googleapis.com/css?family=Lato:400,700&subset=latin-ext');
* {
box-sizing: border-box;
}
html, body, #app {
font-family: $font-family;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
$primary: #0ba8e6 ;
$colors: (
"primary": #0ba8e6
);
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
color: $color__text--primary;
@include text;
}
* {
box-sizing: border-box;
}
.route_container {
width: 90%;
max-width: 1500px;
margin: 0 auto;
margin-top: 2rem;
padding-bottom: 2rem;
}
.logo {
@include text($font--role-emphasis, 2rem, 600);
@include user-select(none);
cursor: pointer;
background: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 20rem;
&:hover, &:visited, &:active {
outline: none;
color: $color__text--primary;
}
}
@media (max-width: 870px) {
.route_container {
width: calc(100% - 2rem);
margin: 0 1rem;
margin-top: 0rem;
}
.logo {
position: relative;
z-index: 2;
max-width: calc(100vw - 7rem);
}
.header__menu_button {
display: inline-block;
cursor: pointer;
}
.header__overlay--show {
pointer-events: all;
opacity: 1;
}
.header__group:first-child {
margin-left: 1rem;
}
.header__group:nth-child(2) {
position: fixed;
padding-top: 1.5rem;
width: 17rem;
display: flex;
flex-direction: column;
z-index: 2;
background: #fff;
top: 0;
left: calc(-100% - 2rem);
height: 100%;
box-shadow: none;
transition: left 0.4s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.4s ease-in;
> .button {
width: 100%;
border-radius: 0;
margin: 0;
margin-bottom: 1rem;
}
&::before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 0.3rem;
content: '';
background: linear-gradient(to right, hsl(200, 98%, 43%), hsla(193, 98%, 48%, 1));
}
}
.header__group:nth-child(2).header__group--show {
left: 0;
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.4);
}
.search_box {
margin: 0;
display: inline-block;
}
}
.route_container--settings {
display: flex;
align-items: flex-start
}
.settings_menu {
width: 15rem;
padding: 1rem;
border-radius: 0.25rem;
@at-root #{&}__title {
cursor: default;
font-weight: 500;
font-variant: small-caps;
font-size: 1.125rem;
padding-left: 0.25rem;
margin-bottom: 0.5rem;
}
@at-root #{&}__item {
padding: 0.5rem 1rem;
margin-bottom: 0.25rem;
padding-right: 0;
transition: background-color 0.2s;
cursor: pointer;
position: relative;
border-radius: 0.25rem;
&:first-child { margin-top: 0.5rem; }
&:last-child { margin-bottom: 0.5rem; }
&::before {
content: '';
display: inline-block;
width: 0.25rem;
z-index: 1;
height: 100%;
position: absolute;
left: 0;
border-radius: 0.25rem 0 0 0.25em;
top: 0;
opacity: 0;
transition: opacity 0.2s;
}
span {
color: $color__text--secondary;
margin-right: 0.5rem;
}
@at-root #{&}--selected {
background-color: $color__lightgray--darker;
color: $color__text--primary;
span {
color: $color__text--primary;
}
&:hover { background-color: $color__lightgray--darker; }
&::before {
opacity: 1;
}
}
}
}
.settings_page {
width: calc(100% - 15rem);
border-radius: 0.25rem;
margin-left: 2rem;
}
@media (max-width: $breakpoint--tablet) and (min-width: $breakpoint--phone) {
div.settings_menu, div.settings_page {
width: calc(100% - 4rem);
margin: 0.5rem 2rem;
padding: 1rem;
}
}
@media (max-width: $breakpoint--tablet) {
.route_container--settings {
flex-direction: column;
}
.settings_menu {
width: 100%;
@at-root #{&}__items {
display: flex;
align-items: baseline;
}
@at-root #{&}__item {
width: 7rem;
margin-right: 0.5rem;
color: $color__text--primary;
&:first-child, &:last-child {
margin-bottom: 0;
margin-top: 0;
}
&::before {
height: 0.2rem;
width: 100%;
left: 0;
border-radius: 0 0 1rem 1rem;
top: auto;
bottom: 0;
}
}
}
.settings_page {
width: 100%;
margin: 0;
margin-top: 1rem;
}
}
</style>