@@ -1280,6 +1280,11 @@ | |||||
"integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", | "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", | ||||
"dev": true | "dev": true | ||||
}, | }, | ||||
"blueimp-md5": { | |||||
"version": "2.18.0", | |||||
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz", | |||||
"integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q==" | |||||
}, | |||||
"bn.js": { | "bn.js": { | ||||
"version": "5.1.2", | "version": "5.1.2", | ||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", | ||||
@@ -11,6 +11,7 @@ | |||||
"dev": "webpack-dev-server --open --env.server=$MY_BUBBLE_API" | "dev": "webpack-dev-server --open --env.server=$MY_BUBBLE_API" | ||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"blueimp-md5": "^2.18.0", | |||||
"lottie-web": "^5.7.1", | "lottie-web": "^5.7.1", | ||||
"luxon": "^1.21.3", | "luxon": "^1.21.3", | ||||
"qrcode": "^1.4.4", | "qrcode": "^1.4.4", | ||||
@@ -1,2 +1,5 @@ | |||||
{ | { | ||||
"label_homepage_welcome": "Hello", | |||||
"label_menu_help": "Help", | |||||
"label_menu_settings": "Settings" | |||||
} | } |
@@ -1,2 +1,9 @@ | |||||
{ | { | ||||
"footer_links": "get_help,legal_stuff,about_bubble", | |||||
"title_get_help": "Get Help", | |||||
"title_legal_stuff": "Legal Stuff", | |||||
"title_about_bubble": "About Bubble", | |||||
"link_get_help": "https://example.com/get_help", | |||||
"link_legal_stuff": "https://example.com/legal_stuff", | |||||
"link_about_bubble": "https://example.com/about_bubble" | |||||
} | } |
@@ -0,0 +1,117 @@ | |||||
<template> | |||||
<footer class="footer"> | |||||
<p class="text-white text-center"> | |||||
{{ messages.label_get_bubble_for_devices }} | |||||
</p> | |||||
<div class="devices"> | |||||
<a | |||||
class="device" | |||||
v-for="device in availableDevices" | |||||
:key="device" | |||||
download | |||||
href="" | |||||
> | |||||
<img :src="`/${device}.png`" /> | |||||
<span>{{ messages[`label_device_${device}`] }}</span> | |||||
</a> | |||||
</div> | |||||
<div class="links"> | |||||
<a | |||||
class="text-white link" | |||||
v-for="item in footerLinks" | |||||
:key="item" | |||||
:to="messages[`link_${item}`]" | |||||
> | |||||
{{ messages[`title_${item}`] }} | |||||
</a> | |||||
</div> | |||||
</footer> | |||||
</template> | |||||
<style lang="scss" scoped> | |||||
@import '../../_scss/breakpoints'; | |||||
.footer { | |||||
z-index: 1; | |||||
& > * { | |||||
margin: 20px 0; | |||||
} | |||||
p { | |||||
font-size: 26px; | |||||
line-height: 30px; | |||||
} | |||||
@include respond-below(sm) { | |||||
padding: 0 70px; | |||||
} | |||||
} | |||||
.devices, | |||||
.links { | |||||
display: flex; | |||||
flex-wrap: wrap; | |||||
justify-content: center; | |||||
} | |||||
.device { | |||||
width: 165px; | |||||
padding: 10px; | |||||
@include respond-below(lg) { | |||||
width: 120px; | |||||
padding: 10px 0; | |||||
} | |||||
margin: 10px; | |||||
border-radius: 10px; | |||||
background-color: white; | |||||
box-shadow: 0px 5px 30px #1551ab; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
justify-content: center; | |||||
span { | |||||
font-size: 12px; | |||||
color: #7c7c7c; | |||||
margin-top: 16px; | |||||
} | |||||
} | |||||
.link { | |||||
font-size: 14px; | |||||
&:not(:last-child) { | |||||
display: inline-flex; | |||||
align-items: center; | |||||
&:after { | |||||
content: ''; | |||||
width: 3px; | |||||
height: 3px; | |||||
background-color: white; | |||||
margin: 0 12px; | |||||
display: inline-flex; | |||||
border-radius: 3px; | |||||
} | |||||
} | |||||
} | |||||
</style> | |||||
<script> | |||||
import { mapState } from 'vuex'; | |||||
export default { | |||||
computed: { | |||||
...mapState('system', ['messages']), | |||||
availableDevices() { | |||||
return this.messages.available_devices ? this.messages.available_devices.split(',') : []; | |||||
}, | |||||
footerLinks() { | |||||
return this.messages.footer_links ? this.messages.footer_links.split(',') : []; | |||||
}, | |||||
}, | |||||
}; | |||||
</script> |
@@ -1,6 +1,6 @@ | |||||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | <!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | ||||
<template> | <template> | ||||
<div class="header d-flex align-items-center justify-content-center"> | |||||
<header class="header d-flex align-items-center justify-content-center"> | |||||
<div class="d-flex justify-content-center align-items-center container "> | <div class="d-flex justify-content-center align-items-center container "> | ||||
<router-link to="/"> | <router-link to="/"> | ||||
<img src="/small-BubbleLogo-Horizontal-BlackText.png" height="40" /> | <img src="/small-BubbleLogo-Horizontal-BlackText.png" height="40" /> | ||||
@@ -139,7 +139,7 @@ | |||||
</router-link> | </router-link> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | |||||
</header> | |||||
</template> | </template> | ||||
<style lang="scss" scoped> | <style lang="scss" scoped> | ||||
@@ -0,0 +1,27 @@ | |||||
<template> | |||||
<div class="notification"> | |||||
<i class="fa fa-bell"></i> | |||||
</div> | |||||
</template> | |||||
<style lang="scss" scoped> | |||||
.notification { | |||||
position: absolute; | |||||
top: 10px; | |||||
right: 20px; | |||||
width: 48px; | |||||
height: 48px; | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
border: 1px solid #eaeaea; | |||||
border-radius: 50%; | |||||
color: #c4c4c4; | |||||
} | |||||
</style> | |||||
<script> | |||||
export default {}; | |||||
</script> |
@@ -0,0 +1,245 @@ | |||||
<template> | |||||
<aside class="sidebar"> | |||||
<!-- Logo --> | |||||
<div class="d-flex align-items-center logo"> | |||||
<router-link to="/"> | |||||
<img src="/small-BubbleLogo-Horizontal-BlackText.png" height="40" /> | |||||
</router-link> | |||||
<a @click="toggleMenu" class="toggle-menu"> | |||||
<i class="fa fa-bars"></i> | |||||
</a> | |||||
</div> | |||||
<div | |||||
class="flex-grow-1 d-flex flex-column menu" | |||||
:class="{ 'menu-invisible': !menuVisible }" | |||||
> | |||||
<!-- Profile --> | |||||
<router-link to="/me"> | |||||
<div class="profile"> | |||||
<img | |||||
:src="`https://www.gravatar.com/avatar/${userHash}?r=pg`" | |||||
class="profile-image" | |||||
/> | |||||
<div class="profile-info"> | |||||
<strong>{{ currentUser.name }}</strong> | |||||
<br /> | |||||
<span>{{ currentUser.email }}</span> | |||||
</div> | |||||
<i class="fa fa-chevron-right"></i> | |||||
</div> | |||||
</router-link> | |||||
<!-- Navigation --> | |||||
<div class="navigation"> | |||||
<div class="navigation-item active"> | |||||
<i class="fa fa-home icon icon-home"></i> | |||||
<span>{{ messages.label_menu_network }}</span> | |||||
</div> | |||||
<div class="navigation-item"> | |||||
<i class="fa fa-tablet icon icon-devices"></i> | |||||
<span>{{ messages.label_menu_devices }}</span> | |||||
</div> | |||||
<div class="navigation-item"> | |||||
<i class="fa fa-cog icon icon-settings"></i> | |||||
<span>{{ messages.label_menu_settings }}</span> | |||||
</div> | |||||
<div class="navigation-item"> | |||||
<i class="fa fa-question-circle icon icon-help"></i> | |||||
<span>{{ messages.label_menu_help }}</span> | |||||
</div> | |||||
</div> | |||||
<!-- Upgrade Plan --> | |||||
<div class="flex-grow-1"></div> | |||||
<!-- Logout --> | |||||
<Button color="outline" class="logout-button"> | |||||
{{ messages.log_out }} | |||||
</Button> | |||||
</div> | |||||
</aside> | |||||
</template> | |||||
<style lang="scss" scoped> | |||||
@import '../../_scss/breakpoints'; | |||||
.sidebar { | |||||
z-index: 1; | |||||
padding: 20px 0; | |||||
display: flex; | |||||
flex-direction: column; | |||||
} | |||||
.logout-button { | |||||
margin-top: 30px; | |||||
} | |||||
.profile { | |||||
color: #172239; | |||||
margin-top: 30px; | |||||
padding: 24px; | |||||
border: 1px solid #e0e2f0; | |||||
border-radius: 32px; | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
@include respond-between(sm, lg) { | |||||
margin-top: 0px; | |||||
border: none; | |||||
> *:not(.profile-image) { | |||||
display: none; | |||||
} | |||||
} | |||||
} | |||||
.logo { | |||||
padding: 0 20px; | |||||
} | |||||
.profile-image { | |||||
height: 48px; | |||||
width: 48px; | |||||
border-radius: 50%; | |||||
} | |||||
.profile-info { | |||||
width: 156px; | |||||
overflow: hidden; | |||||
text-overflow: ellipsis; | |||||
margin: 0 20px; | |||||
font-size: 12px; | |||||
strong { | |||||
font-size: 16px; | |||||
} | |||||
} | |||||
.toggle-menu { | |||||
display: none; | |||||
@include respond-below(sm) { | |||||
display: block; | |||||
} | |||||
} | |||||
.menu { | |||||
max-height: 10000px; | |||||
padding: 0 20px 20px; | |||||
transition: max-height 0.5s; | |||||
overflow: hidden; | |||||
background-color: white; | |||||
@include respond-below(sm) { | |||||
position: absolute; | |||||
left: 0; | |||||
right: 0; | |||||
top: 60px; | |||||
&.menu-invisible { | |||||
max-height: 0px; | |||||
} | |||||
} | |||||
} | |||||
.navigation { | |||||
margin-top: 30px; | |||||
border: 1px solid #e0e2f0; | |||||
border-radius: 32px; | |||||
padding: 32px; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
justify-content: center; | |||||
@include respond-between(sm, lg) { | |||||
margin-top: 12px; | |||||
span { | |||||
display: none; | |||||
} | |||||
} | |||||
} | |||||
.navigation-item { | |||||
color: #6d6d78; | |||||
padding: 16px; | |||||
margin: 10px 0; | |||||
width: 240px; | |||||
display: flex; | |||||
align-items: center; | |||||
span { | |||||
margin-left: 20px; | |||||
} | |||||
&.active { | |||||
background-color: #66cda4; | |||||
color: white; | |||||
border-radius: 20px; | |||||
.icon { | |||||
color: white; | |||||
} | |||||
} | |||||
@include respond-between(sm, lg) { | |||||
width: auto; | |||||
} | |||||
} | |||||
.toggle-menu { | |||||
margin-left: 20px; | |||||
font-size: 20px; | |||||
font-weight: bold; | |||||
color: #1930d7 !important; | |||||
} | |||||
.icon { | |||||
font-size: 20px; | |||||
} | |||||
.icon-home { | |||||
color: #5055bd; | |||||
} | |||||
.icon-devices { | |||||
color: #4b53df; | |||||
} | |||||
.icon-settings { | |||||
color: #e6458a; | |||||
} | |||||
.icon-help { | |||||
color: #57c8e9; | |||||
} | |||||
</style> | |||||
<script> | |||||
import { mapState } from 'vuex'; | |||||
import md5 from 'blueimp-md5'; | |||||
import { util } from '~/_helpers'; | |||||
import { Button } from '~/_components/shared'; | |||||
export default { | |||||
components: { | |||||
Button, | |||||
}, | |||||
data: () => ({ | |||||
currentUser: util.currentUser(), | |||||
menuVisible: false, | |||||
}), | |||||
computed: { | |||||
...mapState('system', ['messages']), | |||||
userHash() { | |||||
return md5(this.currentUser.email); | |||||
}, | |||||
}, | |||||
methods: { | |||||
toggleMenu() { | |||||
this.menuVisible = !this.menuVisible; | |||||
}, | |||||
}, | |||||
}; | |||||
</script> |
@@ -3,3 +3,6 @@ | |||||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | ||||
*/ | */ | ||||
export { default as Header } from './Header'; | export { default as Header } from './Header'; | ||||
export { default as Footer } from './Footer'; | |||||
export { default as Notification } from './Notification'; | |||||
export { default as Sidebar } from './Sidebar'; |
@@ -1,8 +1,6 @@ | |||||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | <!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | ||||
<template> | <template> | ||||
<div class="w-100"> | |||||
<router-view></router-view> | |||||
</div> | |||||
<router-view></router-view> | |||||
</template> | </template> | ||||
<script> | <script> | ||||
@@ -1,6 +1,8 @@ | |||||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | <!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | ||||
<template> | <template> | ||||
<div class="background w-100 h-100"> | <div class="background w-100 h-100"> | ||||
<Header></Header> | |||||
<div | <div | ||||
:class="backgroundClass" | :class="backgroundClass" | ||||
class="auth-layout d-flex flex-column content" | class="auth-layout d-flex flex-column content" | ||||
@@ -163,7 +163,7 @@ | |||||
</template> | </template> | ||||
<style lang="scss" scoped> | <style lang="scss" scoped> | ||||
@import '../../../_scss/components/form'; | |||||
@import '../../_scss/components/form'; | |||||
.features-section-link { | .features-section-link { | ||||
color: $vivid-navy; | color: $vivid-navy; |
@@ -8,7 +8,7 @@ | |||||
<span class="text-center white-text"> | <span class="text-center white-text"> | ||||
{{ messages.resend_verify_email_label }} | {{ messages.resend_verify_email_label }} | ||||
</span> | </span> | ||||
<a class="resend-btn" href="#"> | |||||
<a class="resend-btn" href="#" @click="resendVerification(firstContact)"> | |||||
{{ messages.button_label_resend_verify_email }} | {{ messages.button_label_resend_verify_email }} | ||||
</a> | </a> | ||||
</h4> | </h4> | ||||
@@ -53,18 +53,31 @@ | |||||
</style> | </style> | ||||
<script> | <script> | ||||
import { mapState } from 'vuex'; | |||||
import { mapState, mapActions } from 'vuex'; | |||||
import Lottie from 'lottie-web'; | import Lottie from 'lottie-web'; | ||||
import { util } from '~/_helpers' | |||||
import { Features } from '~/_components/sections'; | import { Features } from '~/_components/sections'; | ||||
// convenience methods | |||||
import { isAuthenticator, isNotAuthenticator } from '~/_store/users.module'; | |||||
window.isAuthenticator = isAuthenticator; | |||||
window.isNotAuthenticator = isNotAuthenticator; | |||||
export default { | export default { | ||||
components: { | components: { | ||||
Features, | Features, | ||||
}, | }, | ||||
data() { | |||||
return { | |||||
firstContact: null, | |||||
}; | |||||
}, | |||||
computed: { | computed: { | ||||
...mapState('system', ['messages']), | ...mapState('system', ['messages']), | ||||
...mapState('users', ['policy']), | |||||
}, | }, | ||||
mounted() { | mounted() { | ||||
@@ -76,5 +89,37 @@ export default { | |||||
path: '/green_email_sent.json', | path: '/green_email_sent.json', | ||||
}); | }); | ||||
}, | }, | ||||
methods: { | |||||
...mapActions('account', ['resendVerificationCode']), | |||||
getFirstContact(policy) { | |||||
if (policy && policy.accountContacts) { | |||||
const contacts = policy.accountContacts; | |||||
for (let i = 0; i < contacts.length; i++) { | |||||
if (isNotAuthenticator(contacts[i])) return contacts[i]; | |||||
} | |||||
return null; | |||||
} | |||||
return null; | |||||
}, | |||||
resendVerification(contact) { | |||||
console.log(contact); | |||||
this.resendVerificationCode({ | |||||
userId: util.currentUser().uuid, | |||||
contact: contact, | |||||
messages: this.messages, | |||||
errors: this.errors, | |||||
}); | |||||
return false; // do not follow the click | |||||
}, | |||||
}, | |||||
watch: { | |||||
policy(p) { | |||||
this.firstContact = this.getFirstContact(p); | |||||
}, | |||||
}, | |||||
}; | }; | ||||
</script> | </script> |
@@ -1,21 +1,102 @@ | |||||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | <!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | ||||
<template> | <template> | ||||
<router-view></router-view> | |||||
<div class="container-fluid"> | |||||
<img src="/bubble_bkgrnd.png" alt="" class="background-image" /> | |||||
<div class="content"> | |||||
<Sidebar /> | |||||
<div class="flex-grow-1 p-4"> | |||||
<header> | |||||
<p class="mb-0">{{ messages.label_homepage_welcome }},</p> | |||||
<p class="name">{{ currentUser.name }} 👋</p> | |||||
</header> | |||||
<main> | |||||
<router-view></router-view> | |||||
</main> | |||||
</div> | |||||
<Notification /> | |||||
</div> | |||||
<Footer /> | |||||
</div> | |||||
</template> | </template> | ||||
<style lang="scss" scoped> | |||||
@import '../../_scss/breakpoints'; | |||||
.container-fluid { | |||||
background: linear-gradient(26.64deg, #1933dd -10.01%, #18d59d 86.47%); | |||||
height: 100%; | |||||
padding: 30px 20px; | |||||
@include respond-below(lg) { | |||||
padding: 0; | |||||
.content { | |||||
border-radius: 0; | |||||
} | |||||
} | |||||
display: flex; | |||||
flex-direction: column; | |||||
} | |||||
.background-image { | |||||
position: absolute; | |||||
bottom: 20px; | |||||
left: 100px; | |||||
pointer-events: none; | |||||
@include respond-below(lg) { | |||||
bottom: -20px; | |||||
left: 0px; | |||||
} | |||||
@include respond-below(sm) { | |||||
bottom: -20px; | |||||
left: 0px; | |||||
width: 100%; | |||||
} | |||||
} | |||||
.content { | |||||
position: relative; | |||||
z-index: 2; | |||||
flex-grow: 1; | |||||
background: #fafafd; | |||||
border-radius: 10px; | |||||
box-shadow: 0px 4px 25px rgba(149, 149, 149, 0.25); | |||||
display: flex; | |||||
@include respond-below(sm) { | |||||
flex-direction: column; | |||||
} | |||||
} | |||||
header { | |||||
.name { | |||||
color: #172239; | |||||
font-size: 3rem; | |||||
} | |||||
} | |||||
</style> | |||||
<script> | <script> | ||||
import { mapState, mapActions } from 'vuex'; | import { mapState, mapActions } from 'vuex'; | ||||
import { util } from '~/_helpers'; | import { util } from '~/_helpers'; | ||||
import { isAuthenticator, isNotAuthenticator } from '~/_store/users.module'; | import { isAuthenticator, isNotAuthenticator } from '~/_store/users.module'; | ||||
import { Footer, Notification, Sidebar } from '~/_components/layout'; | |||||
export default { | export default { | ||||
components: { | |||||
Footer, | |||||
Notification, | |||||
Sidebar, | |||||
}, | |||||
data: () => ({ | data: () => ({ | ||||
verifiedContacts: null, | verifiedContacts: null, | ||||
verifiedContactRefresher: null, | verifiedContactRefresher: null, | ||||
accountPlan: { | accountPlan: { | ||||
currentUser: null, | |||||
name: '', | name: '', | ||||
domain: '', | domain: '', | ||||
locale: util.currentUser().locale, | |||||
locale: null, | |||||
timezone: '', | timezone: '', | ||||
plan: 'bubble', | plan: 'bubble', | ||||
footprint: 'Worldwide', | footprint: 'Worldwide', | ||||
@@ -37,7 +118,12 @@ export default { | |||||
computed: { | computed: { | ||||
...mapState('users', ['policy']), | ...mapState('users', ['policy']), | ||||
...mapState('paymentMethods', ['accountPaymentMethods']), | ...mapState('paymentMethods', ['accountPaymentMethods']), | ||||
...mapState('system', ['configs']), | |||||
...mapState('system', ['configs', 'messages']), | |||||
}, | |||||
created() { | |||||
this.currentUser = util.currentUser(); | |||||
this.locale = this.currentUser.locale; | |||||
}, | }, | ||||
mounted() { | mounted() { | ||||
@@ -49,16 +135,15 @@ export default { | |||||
...mapActions('paymentMethods', ['getAllAccountPaymentMethods']), | ...mapActions('paymentMethods', ['getAllAccountPaymentMethods']), | ||||
initDefaults() { | initDefaults() { | ||||
const currentUser = util.currentUser(); | |||||
const selectedLocale = | const selectedLocale = | ||||
currentUser !== null && | |||||
typeof currentUser.locale !== 'undefined' && | |||||
currentUser.locale !== null | |||||
? currentUser.locale | |||||
this.currentUser !== null && | |||||
typeof this.currentUser.locale !== 'undefined' && | |||||
this.currentUser.locale !== null | |||||
? this.currentUser.locale | |||||
: 'detect'; | : 'detect'; | ||||
if (!currentUser.admin) { | |||||
if (!this.currentUser.admin) { | |||||
this.getPolicyByUserId({ | this.getPolicyByUserId({ | ||||
userId: currentUser.uuid, | |||||
userId: this.currentUser.uuid, | |||||
messages: this.messages, | messages: this.messages, | ||||
errors: this.errors, | errors: this.errors, | ||||
}); | }); | ||||
@@ -1,125 +0,0 @@ | |||||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | |||||
<template> | |||||
<div class="wrapper"> | |||||
<h1 class="text-center white-text form-title"> | |||||
{{ messages.verify_email_title }} | |||||
</h1> | |||||
<h4 class="d-flex align-items-center justify-content-center form-sub-title"> | |||||
<span class="text-center white-text"> | |||||
{{ messages.resend_verify_email_label }} | |||||
</span> | |||||
<a class="resend-btn" href="#" @click="resendVerification(firstContact)"> | |||||
{{ messages.button_label_resend_verify_email }} | |||||
</a> | |||||
</h4> | |||||
<div class="d-flex justify-content-center mt-5"> | |||||
<div ref="lottie" class="lottie"></div> | |||||
</div> | |||||
<Features></Features> | |||||
<a | |||||
class="features-section-link text-center d-block" | |||||
href="https://getbubblenow.com/features/" | |||||
> | |||||
{{ messages.more_features_label }} | |||||
</a> | |||||
</div> | |||||
</template> | |||||
<style lang="scss" scoped> | |||||
@import '../../../_scss/components/form'; | |||||
.features-section-link { | |||||
color: $vivid-navy; | |||||
font-size: 16px; | |||||
margin-top: 25px; | |||||
} | |||||
.sub-title { | |||||
font-size: 24px; | |||||
} | |||||
.lottie { | |||||
width: 400px; | |||||
} | |||||
.resend-btn { | |||||
text-decoration: underline; | |||||
color: white; | |||||
margin-left: 10px; | |||||
} | |||||
</style> | |||||
<script> | |||||
import { mapState, mapActions } from 'vuex'; | |||||
import Lottie from 'lottie-web'; | |||||
import { util } from '~/_helpers' | |||||
import { Features } from '~/_components/sections'; | |||||
// convenience methods | |||||
import { isAuthenticator, isNotAuthenticator } from '~/_store/users.module'; | |||||
window.isAuthenticator = isAuthenticator; | |||||
window.isNotAuthenticator = isNotAuthenticator; | |||||
export default { | |||||
components: { | |||||
Features, | |||||
}, | |||||
data() { | |||||
return { | |||||
firstContact: null, | |||||
}; | |||||
}, | |||||
computed: { | |||||
...mapState('system', ['messages']), | |||||
...mapState('users', ['policy']), | |||||
}, | |||||
mounted() { | |||||
Lottie.loadAnimation({ | |||||
container: this.$refs.lottie, | |||||
renderer: '', | |||||
loop: true, | |||||
autoplay: true, | |||||
path: '/green_email_sent.json', | |||||
}); | |||||
}, | |||||
methods: { | |||||
...mapActions('account', ['resendVerificationCode']), | |||||
getFirstContact(policy) { | |||||
if (policy && policy.accountContacts) { | |||||
const contacts = policy.accountContacts; | |||||
for (let i = 0; i < contacts.length; i++) { | |||||
if (isNotAuthenticator(contacts[i])) return contacts[i]; | |||||
} | |||||
return null; | |||||
} | |||||
return null; | |||||
}, | |||||
resendVerification(contact) { | |||||
console.log(contact); | |||||
this.resendVerificationCode({ | |||||
userId: util.currentUser().uuid, | |||||
contact: contact, | |||||
messages: this.messages, | |||||
errors: this.errors, | |||||
}); | |||||
return false; // do not follow the click | |||||
}, | |||||
}, | |||||
watch: { | |||||
policy(p) { | |||||
this.firstContact = this.getFirstContact(p); | |||||
}, | |||||
}, | |||||
}; | |||||
</script> |
@@ -62,23 +62,12 @@ const newNetworkChildren = [ | |||||
export const router = new Router({ | export const router = new Router({ | ||||
mode: 'history', | mode: 'history', | ||||
routes: [ | routes: [ | ||||
// existing pages | // existing pages | ||||
{ path: '', component: DashboardPage }, | |||||
{ path: '/', component: DashboardPage }, | |||||
// { path: '/legal', component: LegalPage }, | // { path: '/legal', component: LegalPage }, | ||||
// { path: '/support', component: SupportPage }, | // { path: '/support', component: SupportPage }, | ||||
// { path: '/me/old', component: ProfilePage }, | // { path: '/me/old', component: ProfilePage }, | ||||
// { path: '/me/policy', component: PolicyPage }, | // { path: '/me/policy', component: PolicyPage }, | ||||
{ | |||||
path: '/me/download/:uuid', | |||||
redirect: (r) => ({ | |||||
path: '/me/policy', | |||||
query: { download: r.params.uuid }, | |||||
}), | |||||
}, | |||||
{ path: '/me/action', component: ActionPage }, | |||||
// { path: '/me/changePassword/old', component: ChangePasswordPage }, | // { path: '/me/changePassword/old', component: ChangePasswordPage }, | ||||
// { path: '/me/setPassword/:code', component: SetPasswordPage }, | // { path: '/me/setPassword/:code', component: SetPasswordPage }, | ||||
// { path: '/me/keys', component: SshKeysPage }, | // { path: '/me/keys', component: SshKeysPage }, | ||||
@@ -89,64 +78,17 @@ export const router = new Router({ | |||||
// children: paymentMethodsChildren, | // children: paymentMethodsChildren, | ||||
// }, | // }, | ||||
// { path: '/devices', component: DevicesPage }, | // { path: '/devices', component: DevicesPage }, | ||||
{ path: '/apps', component: AppsPage }, | |||||
{ path: '/app/:app', component: AppPage }, | |||||
{ path: '/app/:app/config/:view', component: AppConfigPage }, | |||||
{ path: '/app/:app/config/:view/:item', component: AppConfigPage }, | |||||
{ path: '/app/:app/view/:view', component: AppDataViewPage }, | |||||
{ path: '/app/:app/site/:site', component: AppSitePage }, | |||||
{ path: '/app/:app/site/:site/view/:view', component: AppDataViewPage }, | |||||
{ path: '/notifications', component: NotificationsPage }, | |||||
{ | |||||
path: '/bubbles', | |||||
component: NetworksPage, | |||||
children: [ | |||||
{ | |||||
path: '', | |||||
component: NewNetworkPage, | |||||
children: newNetworkChildren, | |||||
}, | |||||
], | |||||
}, | |||||
{ | |||||
path: '/new_bubble', | |||||
component: NewNetworkPage, | |||||
children: newNetworkChildren, | |||||
}, | |||||
{ path: '/bubble/:id', component: NetworkPage }, | |||||
{ path: '/action', component: ActionPage }, | |||||
{ path: '/resetPassword/:code', component: SetPasswordPage }, | |||||
{ path: '/activate', component: ActivationPage }, | |||||
// { | // { | ||||
// path: '/register', | // path: '/register', | ||||
// component: RegisterPage, | // component: RegisterPage, | ||||
// children: paymentMethodsChildren, | // children: paymentMethodsChildren, | ||||
// }, | // }, | ||||
{ path: '/auth', component: MultifactorAuthPage }, | |||||
// { path: '/login', component: LoginPage }, | // { path: '/login', component: LoginPage }, | ||||
{ path: '/logout', component: LogoutPage }, | |||||
// { path: '/forgotPassword', component: ForgotPasswordPage }, | // { path: '/forgotPassword', component: ForgotPasswordPage }, | ||||
// { path: '/appLogin', component: AppLoginPage }, | // { path: '/appLogin', component: AppLoginPage }, | ||||
// { path: '/restore', component: RestorePage }, | // { path: '/restore', component: RestorePage }, | ||||
{ path: '/admin/accounts', component: AccountsPage }, | |||||
{ path: '/admin/new_account', component: ProfilePage }, | |||||
{ path: '/admin/accounts/:id', component: ProfilePage }, | |||||
{ path: '/admin/accounts/:id/policy', component: PolicyPage }, | |||||
{ | |||||
path: '/admin/accounts/:id/changePassword', | |||||
component: ChangePasswordPage, | |||||
}, | |||||
{ path: '/admin/accounts/:id/keys', component: SshKeysPage }, | |||||
{ path: '/admin/accounts/:id/bills', component: BillsPage }, | |||||
{ | |||||
path: '/admin/accounts/:id/payment', | |||||
component: PaymentMethodsPage, | |||||
children: paymentMethodsChildren, | |||||
}, | |||||
{ path: '/admin/model', component: ModelSetupPage }, | |||||
// new route | // new route | ||||
{ | { | ||||
path: '', | path: '', | ||||
@@ -154,100 +96,123 @@ export const router = new Router({ | |||||
children: [ | children: [ | ||||
{ | { | ||||
path: '', | path: '', | ||||
component: () => import('~/_pages/auth/Layout'), | |||||
component: () => import('~/_pages/main/Layout'), | |||||
children: [ | children: [ | ||||
{ path: '', component: DashboardPage }, | |||||
{ | { | ||||
path: 'login', | |||||
component: () => import('~/_pages/auth/Login'), | |||||
path: 'me/download/:uuid', | |||||
redirect: (r) => ({ | |||||
path: 'me/policy', | |||||
query: { download: r.params.uuid }, | |||||
}), | |||||
}, | }, | ||||
{ path: 'me/action', component: ActionPage }, | |||||
{ path: 'apps', component: AppsPage }, | |||||
{ path: 'app/:app', component: AppPage }, | |||||
{ path: 'app/:app/config/:view', component: AppConfigPage }, | |||||
{ | { | ||||
path: 'forgotPassword', | |||||
component: () => import('~/_pages/auth/ForgotPassword'), | |||||
path: 'app/:app/config/:view/:item', | |||||
component: AppConfigPage, | |||||
}, | }, | ||||
{ path: 'app/:app/view/:view', component: AppDataViewPage }, | |||||
{ path: 'app/:app/site/:site', component: AppSitePage }, | |||||
{ | { | ||||
path: 'register', | |||||
component: () => import('~/_pages/auth/Register'), | |||||
path: 'app/:app/site/:site/view/:view', | |||||
component: AppDataViewPage, | |||||
}, | }, | ||||
{ path: 'notifications', component: NotificationsPage }, | |||||
{ | { | ||||
path: 'appLogin', | |||||
component: () => import('~/_pages/auth/AppLogin'), | |||||
}, | |||||
], | |||||
}, | |||||
{ | |||||
path: '', | |||||
component: () => import('~/_pages/main/Layout'), | |||||
children: [ | |||||
{ | |||||
path: '', | |||||
component: () => import('~/_pages/main/account/Layout'), | |||||
path: 'bubbles', | |||||
component: NetworksPage, | |||||
children: [ | children: [ | ||||
{ | { | ||||
path: 'verifyEmail', | |||||
component: () => import('~/_pages/main/account/VerifyEmail'), | |||||
}, | |||||
{ | |||||
path: 'payment', | |||||
component: () => import('~/_pages/main/account/Payment'), | |||||
}, | |||||
{ | |||||
path: 'me', | |||||
exact: true, | |||||
component: () => import('~/_pages/main/account/MyAccount'), | |||||
}, | |||||
{ | |||||
path: 'me/changePassword', | |||||
component: () => | |||||
import('~/_pages/main/account/ChangePassword'), | |||||
}, | |||||
{ | |||||
path: 'me/setPassword/:code', | |||||
component: () => import('~/_pages/main/account/SetPassword'), | |||||
}, | |||||
{ | |||||
path: 'me/keys', | |||||
component: () => import('~/_pages/main/account/ManageSSH'), | |||||
}, | |||||
{ | |||||
path: 'me/payment', | |||||
component: () => | |||||
import('~/_pages/main/account/PaymentMethods'), | |||||
}, | |||||
{ | |||||
path: 'me/bills', | |||||
component: () => import('~/_pages/main/account/Bills'), | |||||
}, | |||||
{ | |||||
path: 'me/policy', | |||||
component: () => import('~/_pages/main/account/Policy'), | |||||
}, | |||||
{ | |||||
path: 'me/delete', | |||||
component: () => import('~/_pages/main/account/Delete'), | |||||
path: '', | |||||
component: NewNetworkPage, | |||||
children: newNetworkChildren, | |||||
}, | }, | ||||
], | |||||
}, | |||||
{ | |||||
path: 'new_bubble', | |||||
component: NewNetworkPage, | |||||
children: newNetworkChildren, | |||||
}, | |||||
{ path: 'bubble/:id', component: NetworkPage }, | |||||
{ path: 'action', component: ActionPage }, | |||||
{ path: 'resetPassword/:code', component: SetPasswordPage }, | |||||
{ | |||||
path: 'devices', | |||||
component: () => import('~/_pages/main/account/Devices'), | |||||
}, | |||||
{ path: 'activate', component: ActivationPage }, | |||||
{ path: 'auth', component: MultifactorAuthPage }, | |||||
{ path: 'admin/accounts', component: AccountsPage }, | |||||
{ path: 'admin/new_account', component: ProfilePage }, | |||||
{ path: 'admin/accounts/:id', component: ProfilePage }, | |||||
{ path: 'admin/accounts/:id/policy', component: PolicyPage }, | |||||
{ | |||||
path: 'admin/accounts/:id/changePassword', | |||||
component: ChangePasswordPage, | |||||
}, | |||||
{ path: 'admin/accounts/:id/keys', component: SshKeysPage }, | |||||
{ path: 'admin/accounts/:id/bills', component: BillsPage }, | |||||
{ | |||||
path: 'admin/accounts/:id/payment', | |||||
component: PaymentMethodsPage, | |||||
children: paymentMethodsChildren, | |||||
}, | |||||
{ path: 'admin/model', component: ModelSetupPage }, | |||||
{ | |||||
path: 'me', | |||||
exact: true, | |||||
component: () => import('~/_pages/main/account/MyAccount'), | |||||
}, | |||||
{ | |||||
path: 'me/changePassword', | |||||
component: () => import('~/_pages/main/account/ChangePassword'), | |||||
}, | |||||
{ | |||||
path: 'me/setPassword/:code', | |||||
component: () => import('~/_pages/main/account/SetPassword'), | |||||
}, | |||||
{ | |||||
path: 'me/keys', | |||||
component: () => import('~/_pages/main/account/ManageSSH'), | |||||
}, | |||||
{ | |||||
path: 'me/payment', | |||||
component: () => import('~/_pages/main/account/PaymentMethods'), | |||||
}, | |||||
{ | |||||
path: 'me/bills', | |||||
component: () => import('~/_pages/main/account/Bills'), | |||||
}, | |||||
{ | |||||
path: 'me/policy', | |||||
component: () => import('~/_pages/main/account/Policy'), | |||||
}, | |||||
{ | |||||
path: 'me/delete', | |||||
component: () => import('~/_pages/main/account/Delete'), | |||||
}, | |||||
{ | |||||
path: 'bubble/:id', | |||||
component: () => import('~/_pages/main/bubble/Network'), | |||||
}, | |||||
{ | |||||
path: 'restore', | |||||
component: () => import('~/_pages/main/bubble/Restore'), | |||||
}, | |||||
{ | |||||
path: 'legal', | |||||
component: () => import('~/_pages/main/account/Legal'), | |||||
}, | |||||
{ | |||||
path: 'support', | |||||
component: () => import('~/_pages/main/account/Support'), | |||||
}, | |||||
], | |||||
{ | |||||
path: 'devices', | |||||
component: () => import('~/_pages/main/account/Devices'), | |||||
}, | |||||
{ | |||||
path: 'bubble/:id', | |||||
component: () => import('~/_pages/main/bubble/Network'), | |||||
}, | |||||
{ | |||||
path: 'restore', | |||||
component: () => import('~/_pages/main/bubble/Restore'), | |||||
}, | |||||
{ | |||||
path: 'legal', | |||||
component: () => import('~/_pages/main/account/Legal'), | |||||
}, | |||||
{ | |||||
path: 'support', | |||||
component: () => import('~/_pages/main/account/Support'), | |||||
}, | }, | ||||
{ | { | ||||
path: 'launch-bubble', | path: 'launch-bubble', | ||||
@@ -259,11 +224,42 @@ export const router = new Router({ | |||||
}, | }, | ||||
], | ], | ||||
}, | }, | ||||
{ | |||||
path: '', | |||||
component: () => import('~/_pages/auth/Layout'), | |||||
children: [ | |||||
{ | |||||
path: 'login', | |||||
component: () => import('~/_pages/auth/Login'), | |||||
}, | |||||
{ | |||||
path: 'forgotPassword', | |||||
component: () => import('~/_pages/auth/ForgotPassword'), | |||||
}, | |||||
{ | |||||
path: 'register', | |||||
component: () => import('~/_pages/auth/Register'), | |||||
}, | |||||
{ | |||||
path: 'appLogin', | |||||
component: () => import('~/_pages/auth/AppLogin'), | |||||
}, | |||||
{ | |||||
path: 'verifyEmail', | |||||
component: () => import('~/_pages/auth/VerifyEmail'), | |||||
}, | |||||
{ | |||||
path: 'payment', | |||||
component: () => import('~/_pages/auth/Payment'), | |||||
}, | |||||
{ path: 'logout', component: () => import('~/auth/LogoutPage') }, | |||||
], | |||||
}, | |||||
], | ], | ||||
}, | }, | ||||
// otherwise redirect to dashboard | // otherwise redirect to dashboard | ||||
{ path: '*', redirect: '/' }, | |||||
{ path: '*', redirect: '' }, | |||||
], | ], | ||||
}); | }); | ||||
@@ -281,10 +277,10 @@ const publicPages = [ | |||||
'/legal', | '/legal', | ||||
// new Pages | // new Pages | ||||
'/new_pages', | |||||
]; | ]; | ||||
router.beforeEach((to, from, next) => { | router.beforeEach((to, from, next) => { | ||||
console.log('to', to); | |||||
const authRequired = | const authRequired = | ||||
!publicPages.includes(to.path) && | !publicPages.includes(to.path) && | ||||
publicPages.filter((p) => to.path.startsWith(p)).length === 0; | publicPages.filter((p) => to.path.startsWith(p)).length === 0; | ||||
@@ -1,18 +1,15 @@ | |||||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | <!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | ||||
<template> | <template> | ||||
<div v-if="isNewPage" class="page-container"> | |||||
<Header></Header> | |||||
<div class="page-container"> | |||||
<router-view></router-view> | <router-view></router-view> | ||||
<vue-snotify></vue-snotify> | <vue-snotify></vue-snotify> | ||||
<totp-modal /> | |||||
</div> | </div> | ||||
<div v-else> | |||||
<!-- <div v-else> | |||||
<Header></Header> | <Header></Header> | ||||
<div v-if="!configs"><img :src="loadingImgSrc" /></div> | <div v-if="!configs"><img :src="loadingImgSrc" /></div> | ||||
<div v-else class="jumbotron"> | <div v-else class="jumbotron"> | ||||
<totp-modal /> | |||||
<table | <table | ||||
v-if=" | v-if=" | ||||
this.user !== null && | this.user !== null && | ||||
@@ -106,14 +103,14 @@ | |||||
> | > | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | |||||
</div> --> | |||||
</template> | </template> | ||||
<style lang="scss"> | <style lang="scss"> | ||||
@import '../_scss/base'; | @import '../_scss/base'; | ||||
.page-container { | .page-container { | ||||
height: 100vh; | |||||
min-height: 100vh; | |||||
} | } | ||||
</style> | </style> | ||||
@@ -121,13 +118,9 @@ | |||||
import { mapState, mapActions, mapGetters } from 'vuex'; | import { mapState, mapActions, mapGetters } from 'vuex'; | ||||
import { util } from '~/_helpers'; | import { util } from '~/_helpers'; | ||||
import { loadingImgSrc } from '~/_store'; | import { loadingImgSrc } from '~/_store'; | ||||
import { Header } from '~/_components/layout'; | |||||
export default { | export default { | ||||
name: 'app', | name: 'app', | ||||
components: { | |||||
Header, | |||||
}, | |||||
data() { | data() { | ||||
return { | return { | ||||
showLocaleSelector: false, | showLocaleSelector: false, | ||||
@@ -197,7 +190,7 @@ export default { | |||||
'/support', | '/support', | ||||
'/legal', | '/legal', | ||||
]; | ]; | ||||
return ( | return ( | ||||
newPages.includes(this.$route.path) || | newPages.includes(this.$route.path) || | ||||
newPages.filter((p) => this.$route.path.startsWith(p)).length | newPages.filter((p) => this.$route.path.startsWith(p)).length | ||||