@@ -1,6 +0,0 @@ | |||
{ | |||
"verify_email_title": "Please verify your email address", | |||
"resend_verify_email_label": "Did you not received the email?", | |||
"button_label_resend_verify_email": "Resend it.", | |||
"more_features_label": "More Features" | |||
} |
@@ -0,0 +1,7 @@ | |||
{ | |||
"payment_page_title": "Please add a payment method", | |||
"payment_page_sub_title": "We need a CC card to secure your place in the Bubblesphere.", | |||
"label_bubble_free_title": "Bubble is free for 30 days.", | |||
"label_bubble_free_description": "We think you'll love the security of being in your Bubble.<br/> If for any reason you want to cancel, that's easy too!" | |||
} |
@@ -0,0 +1,29 @@ | |||
{ | |||
"verify_email_title": "Please verify your email address", | |||
"resend_verify_email_label": "Did you not received the email?", | |||
"button_label_resend_verify_email": "Resend it.", | |||
"more_features_label": "More Features", | |||
"button_label_add_card": "Add Card", | |||
"label_pricing_option_format": "{{messages[`marketing_pricing_${plan}_title`]}}: {{messages.currency_symbol_USD}} {{price}} monthly (free for 30 days)", | |||
"marketing_pricing_options": "personal,power,mega", | |||
"marketing_pricing_personal_title": "Personal Bubble", | |||
"marketing_pricing_personal_users": "1 User Account", | |||
"marketing_pricing_personal_price": "1200", | |||
"marketing_pricing_personal_options": "1 User Account,1TB/Month of Data Transfer", | |||
"marketing_pricing_personal_link": "/register?plan=bubble", | |||
"marketing_pricing_power_title": "Power Plan", | |||
"marketing_pricing_power_users": "5 User Accounts", | |||
"marketing_pricing_power_price": "1900", | |||
"marketing_pricing_power_options": "5 User Accounts,2TB/Month of Data Transfer", | |||
"marketing_pricing_power_link": "/register?plan=bubble_plus", | |||
"marketing_pricing_mega_title": "Mega Plan", | |||
"marketing_pricing_mega_users": "10 User Accounts", | |||
"marketing_pricing_mega_price": "3100", | |||
"marketing_pricing_mega_options": "10 User Accounts,3TB/Month of Data Transfer", | |||
"marketing_pricing_mega_link": "/register?plan=bubble_super" | |||
} |
@@ -145,11 +145,9 @@ export default { | |||
methods: { | |||
toggleNavbar() { | |||
console.log('toggleNavbar'); | |||
this.menuVisible = !this.prevVisibleState; | |||
}, | |||
hide() { | |||
console.log('hide'); | |||
this.prevVisibleState = this.menuVisible; | |||
this.menuVisible = false; | |||
}, | |||
@@ -10,7 +10,7 @@ | |||
:key="index" | |||
class="col-lg-3 col-md-6 col-sm-12 my-4 px-3" | |||
> | |||
<Card> | |||
<Card class="h-100"> | |||
<div class="card-content"> | |||
<span | |||
class="card-icon" | |||
@@ -48,10 +48,12 @@ | |||
&.block { | |||
display: block; | |||
width: 100%; | |||
} | |||
.btn--text { | |||
text-transform: uppercase; | |||
white-space: nowrap; | |||
padding: 5px 20px; | |||
} | |||
@@ -1,5 +1,5 @@ | |||
<template> | |||
<div class="card-container h-100"> | |||
<div class="card-container" :class="{ 'rounded-corner': roundCorner }"> | |||
<slot></slot> | |||
</div> | |||
</template> | |||
@@ -8,8 +8,11 @@ | |||
.card-container { | |||
background-color: white; | |||
padding: 40px; | |||
border-radius: 10px; | |||
box-shadow: 0px 10px 50px #dee1ec; | |||
&.rounded-corner { | |||
border-radius: 10px; | |||
} | |||
} | |||
.card-content { | |||
@@ -46,5 +49,12 @@ | |||
</style> | |||
<script> | |||
export default {}; | |||
export default { | |||
props: { | |||
roundCorner: { | |||
type: Boolean, | |||
default: true, | |||
}, | |||
}, | |||
}; | |||
</script> |
@@ -7,7 +7,7 @@ | |||
{{ messages.forgot_password_blurb }} | |||
</h4> | |||
<form class="auth-form" @submit.prevent="handleSubmit"> | |||
<form class="bubble-form" @submit.prevent="handleSubmit"> | |||
<div class="form-group"> | |||
<Input | |||
class="form-control" | |||
@@ -29,7 +29,7 @@ | |||
</div> | |||
<Button | |||
color="default" | |||
class="auth-form-submit" | |||
class="bubble-form-submit" | |||
@click="handleSubmit" | |||
:disabled="status.sendingResetPasswordMessage" | |||
> | |||
@@ -41,13 +41,19 @@ export default { | |||
computed: { | |||
backgroundClass() { | |||
const urlNodes = this.$route.fullPath.split('/'); | |||
switch (urlNodes[1]) { | |||
case 'login': | |||
case 'forgotPassword': | |||
return 'background1'; | |||
} | |||
// new pages in progress | |||
switch (urlNodes[2]) { | |||
case 'login': | |||
case 'forgot-password': | |||
case 'forgotPassword': | |||
return 'background1'; | |||
default: | |||
return 'background2'; | |||
} | |||
return 'background2'; | |||
}, | |||
}, | |||
@@ -9,7 +9,7 @@ | |||
{{ messages.login_blurb }} | |||
</h4> | |||
<form class="auth-form" @submit.prevent="submit"> | |||
<form class="bubble-form" @submit.prevent="submit"> | |||
<h4 | |||
v-if="submitted && errors.has('approvalToken')" | |||
class="invalid-feedback d-block" | |||
@@ -130,8 +130,9 @@ | |||
{{ messages.button_label_forgotPassword }} | |||
</router-link> | |||
<Button | |||
block | |||
color="default" | |||
class="auth-form-submit" | |||
class="bubble-form-submit" | |||
@click="submit" | |||
:disabled="status.loggingIn" | |||
> | |||
@@ -7,7 +7,7 @@ | |||
{{ messages.register_blurb }} | |||
</h4> | |||
<form class="auth-form" @submit.prevent="handleSubmit"> | |||
<form class="bubble-form" @submit.prevent="handleSubmit"> | |||
<div class="form-group"> | |||
<Input | |||
class="form-control" | |||
@@ -156,43 +156,6 @@ | |||
> | |||
{{ messages.marketing_pricing_title }} | |||
</a> | |||
<!-- <div class="row px-5 mx-5"> | |||
<div class="col-12 d-flex"> | |||
<div | |||
class="plan flex-grow-1" | |||
v-for="(plan, index) in messages.marketing_pricing_options.split(',')" | |||
:key="index" | |||
> | |||
<p class="plan-name"> | |||
{{ messages[`marketing_pricing_${plan}_title`] }} | |||
</p> | |||
<p class="plan-users"> | |||
{{ messages[`marketing_pricing_${plan}_users`] }} | |||
</p> | |||
<p class="plan-pricing"> | |||
{{ messages[`marketing_pricing_${plan}_users`] }} | |||
</p> | |||
<p | |||
class="plan-common-features" | |||
v-for="option in messages[`marketing_pricing_common_options`].split( | |||
',' | |||
)" | |||
:key="option" | |||
> | |||
{{ option }} | |||
</p> | |||
<p | |||
class="plan-features" | |||
v-for="option in messages[ | |||
`marketing_pricing_${plan}_options` | |||
].split(',')" | |||
:key="option" | |||
> | |||
{{ option }} | |||
</p> | |||
</div> | |||
</div> | |||
</div> --> | |||
</div> | |||
</template> | |||
@@ -1,8 +1,5 @@ | |||
<template> | |||
<div> | |||
Main Layout | |||
<router-view></router-view> | |||
</div> | |||
<router-view></router-view> | |||
</template> | |||
<script> | |||
@@ -0,0 +1,56 @@ | |||
<template> | |||
<div :class="backgroundClass" class="auth-layout d-flex flex-column content"> | |||
<router-view></router-view> | |||
</div> | |||
</template> | |||
<style lang="scss" scoped> | |||
@import '../../../_scss/breakpoints'; | |||
.content { | |||
background-repeat: no-repeat; | |||
background-size: 100% auto; | |||
background-position-x: center; | |||
padding: 80px; | |||
@include respond-below(sm) { | |||
background-size: 200%, auto; | |||
padding: 20px; | |||
} | |||
} | |||
.background1 { | |||
background-image: url('/background1.svg'); | |||
} | |||
.background2 { | |||
background-image: url('/background2.svg'); | |||
} | |||
</style> | |||
<script> | |||
import { Header } from '~/_components/layout'; | |||
export default { | |||
components: { | |||
Header, | |||
}, | |||
computed: { | |||
backgroundClass() { | |||
const urlNodes = this.$route.fullPath.split('/'); | |||
switch (urlNodes[1]) { | |||
case 'payment': | |||
return 'background1'; | |||
} | |||
// new pages in progress | |||
switch (urlNodes[2]) { | |||
case 'payment': | |||
return 'background1'; | |||
} | |||
return 'background2'; | |||
}, | |||
}, | |||
}; | |||
</script> |
@@ -0,0 +1,324 @@ | |||
<template> | |||
<div class="wrapper"> | |||
<h1 class="text-center white-text form-title"> | |||
{{ messages.payment_page_title }} | |||
</h1> | |||
<h4 | |||
class="d-flex align-items-center justify-content-center white-text form-sub-title" | |||
> | |||
{{ messages.payment_page_sub_title }} | |||
</h4> | |||
<form class="bubble-form" @submit.prevent="submit"> | |||
<select class="form-control" v-model="bubblePlan"> | |||
<option | |||
v-for="(plan, index) in messages.marketing_pricing_options.split(',')" | |||
:key="index" | |||
:value="plan" | |||
> | |||
{{ | |||
messages.label_pricing_option_format.parseExpression({ | |||
plan, | |||
messages: messages, | |||
price: messages[`marketing_pricing_${plan}_price`].parsePrice(), | |||
}) | |||
}} | |||
</option> | |||
</select> | |||
<div id="card-number" class="form-control mt-3" /> | |||
<div class="mt-3 d-flex"> | |||
<div id="card-expiry" class="form-control mr-1" /> | |||
<div id="card-cvc" class="form-control" /> | |||
<div class="flex-grow-1"></div> | |||
<div id="card-zip" class="form-control" /> | |||
</div> | |||
<p class="text-center mt-3"> | |||
<small> | |||
{{ messages['label_promotion_FirstMonthFree_description'] }} | |||
</small> | |||
</p> | |||
<Button block color="default" class="bubble-form-submit"> | |||
{{ messages.button_label_add_card }} | |||
</Button> | |||
</form> | |||
<!--- Pricing Section ---> | |||
<div class="mt-5"> | |||
<h2 class="text-center"> | |||
{{ messages.label_bubble_free_title }} | |||
</h2> | |||
<h5 | |||
class="text-center" | |||
v-html="messages.label_bubble_free_description" | |||
></h5> | |||
</div> | |||
<div | |||
class="row px-5 mx-5 mt-5" | |||
v-if="messages && messages.marketing_pricing_common_options" | |||
> | |||
<div class="col-12 d-flex plan-section"> | |||
<Card | |||
class="plan flex-grow-1" | |||
v-for="(plan, index) in messages.marketing_pricing_options.split(',')" | |||
:round-corner="false" | |||
:key="index" | |||
> | |||
<p class="plan-name"> | |||
{{ messages[`marketing_pricing_${plan}_title`] }} | |||
</p> | |||
<p class="plan-users"> | |||
{{ messages[`marketing_pricing_${plan}_users`] }} | |||
</p> | |||
<p class="plan-pricing"> | |||
<span class="symbol"> | |||
{{ messages.currency_symbol_USD }} | |||
</span> | |||
<span class="price"> | |||
{{ messages[`marketing_pricing_${plan}_price`].parsePrice() }} | |||
</span> | |||
<span class="period"> | |||
{{ messages.marketing_pricing_period }} | |||
</span> | |||
</p> | |||
<p | |||
class="plan-common-features text-center" | |||
v-for="option in messages.marketing_pricing_common_options.split( | |||
',' | |||
)" | |||
:key="option" | |||
> | |||
{{ option }} | |||
</p> | |||
<p | |||
class="plan-features text-center" | |||
v-for="option in messages[ | |||
`marketing_pricing_${plan}_options` | |||
].split(',')" | |||
:key="option" | |||
> | |||
{{ option }} | |||
</p> | |||
</Card> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<style lang="scss" scoped> | |||
@import '../../../_scss/components/form'; | |||
.features-section-link { | |||
color: $vivid-navy; | |||
font-size: 16px; | |||
margin-top: 25px; | |||
} | |||
.plan-section { | |||
@include respond-below(md) { | |||
flex-direction: column; | |||
} | |||
} | |||
#card-expiry, | |||
#card-cvc, | |||
#card-zip { | |||
max-width: 25%; | |||
} | |||
.plan { | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
padding: 20px; | |||
background-color: white; | |||
&:nth-child(2n + 1) { | |||
margin-top: 30px; | |||
color: $vivid-navy; | |||
} | |||
&:nth-child(2n) { | |||
z-index: 1; | |||
color: $strong-purple-1; | |||
} | |||
.plan-users { | |||
color: inherit; | |||
font-weight: 700; | |||
font-size: 0.9em; | |||
} | |||
.plan-pricing { | |||
color: inherit; | |||
display: flex; | |||
margin: 2em 0; | |||
.symbol { | |||
font-size: 1.5em; | |||
} | |||
.price { | |||
font-size: 5em; | |||
line-height: 1em; | |||
font-weight: bold; | |||
} | |||
.period { | |||
font-size: 1.5em; | |||
align-self: flex-end; | |||
} | |||
} | |||
.plan-name { | |||
color: #2e2545; | |||
font-size: 1.5em; | |||
font-weight: 500; | |||
} | |||
.plan-common-features { | |||
color: #8585bd; | |||
} | |||
.plan-features { | |||
color: #8585bd; | |||
font-weight: bold; | |||
} | |||
} | |||
</style> | |||
<script> | |||
import { mapState, mapActions } from 'vuex'; | |||
import { Button, Card } from '~/_components/shared'; | |||
export default { | |||
components: { | |||
Button, | |||
Card, | |||
}, | |||
data: () => ({ | |||
bubblePlan: '', | |||
card: { | |||
cvc: '', | |||
number: '', | |||
expiry: '', | |||
zip: '', | |||
}, | |||
cardNumber: '', | |||
cardExpiry: '', | |||
cardCvc: '', | |||
cardZip: '', | |||
stripe: null, | |||
stripeError: '', | |||
}), | |||
computed: { | |||
...mapState('system', ['messages']), | |||
...mapState('paymentMethods', ['paymentMethods']), | |||
}, | |||
created() { | |||
this.initDefaults(); | |||
}, | |||
mounted() {}, | |||
methods: { | |||
...mapActions('paymentMethods', [ | |||
'getAllPaymentMethods', | |||
'getAllAccountPaymentMethods', | |||
'setPaymentMethod', | |||
'getPromosByAccount', | |||
]), | |||
setUpStripe() { | |||
if (window.Stripe === undefined) { | |||
alert('Stripe V3 library not loaded!'); | |||
} else { | |||
const stripe = window.Stripe( | |||
this.paymentMethods[0].driverConfig.publicApiKey | |||
); | |||
this.stripe = stripe; | |||
const style = { | |||
base: { | |||
padding: '.375rem .75rem', | |||
fontSize: '1rem', | |||
fontWeight: '400', | |||
lineHeight: '1.5', | |||
color: '#495057', | |||
}, | |||
}; | |||
const elements = stripe.elements(); | |||
this.cardCvc = elements.create('cardCvc', { style }); | |||
this.cardExpiry = elements.create('cardExpiry', { style }); | |||
this.cardNumber = elements.create('cardNumber', { style }); | |||
this.cardZip = elements.create('postalCode', { | |||
placeholder: 'Zip', | |||
style, | |||
}); | |||
this.cardNumber.update({ | |||
placeholder: 'CC Card #', | |||
showIcon: true, | |||
}); | |||
this.cardCvc.mount('#card-cvc'); | |||
this.cardExpiry.mount('#card-expiry'); | |||
this.cardNumber.mount('#card-number'); | |||
this.cardZip.mount('#card-zip'); | |||
this.listenForErrors(); | |||
} | |||
}, | |||
listenForErrors() { | |||
this.cardNumber.addEventListener('change', (event) => { | |||
this.toggleError(event); | |||
this.cardNumberError = ''; | |||
this.card.number = event.complete ? true : false; | |||
if (this.card.number) { | |||
this.brand = event.brand; | |||
} | |||
}); | |||
this.cardExpiry.addEventListener('change', (event) => { | |||
this.toggleError(event); | |||
this.cardExpiryError = ''; | |||
this.card.expiry = event.complete ? true : false; | |||
}); | |||
this.cardCvc.addEventListener('change', (event) => { | |||
this.toggleError(event); | |||
this.cardCvcError = ''; | |||
this.card.cvc = event.complete ? true : false; | |||
}); | |||
}, | |||
toggleError(event) { | |||
if (event.error) { | |||
this.stripeError = event.error.message; | |||
} else { | |||
this.stripeError = ''; | |||
} | |||
}, | |||
initDefaults() { | |||
this.getAllPaymentMethods(this.messages, this.errors); | |||
}, | |||
submit() {}, | |||
}, | |||
watch: { | |||
paymentMethods() { | |||
this.bubblePlan = this.messages.marketing_pricing_options.split(',')[0]; | |||
this.setUpStripe(); | |||
}, | |||
}, | |||
}; | |||
</script> |
@@ -28,7 +28,7 @@ | |||
</template> | |||
<style lang="scss" scoped> | |||
@import '../../_scss/components/form'; | |||
@import '../../../_scss/components/form'; | |||
.features-section-link { | |||
color: $vivid-navy; |
@@ -73,18 +73,22 @@ export const router = new Router({ | |||
children: [ | |||
{ | |||
path: '', | |||
component: () => import('~/_pages/auth/Layout'), | |||
component: () => import('~/_pages/main/Layout'), | |||
children: [ | |||
{ | |||
path: 'verify-email', | |||
component: () => import('~/_pages/auth/VerifyEmail'), | |||
path: '', | |||
component: () => import('~/_pages/main/account/Layout'), | |||
children: [ | |||
{ | |||
path: 'verify-email', | |||
component: () => import('~/_pages/main/account/VerifyEmail'), | |||
}, | |||
{ | |||
path: 'payment', | |||
component: () => import('~/_pages/main/account/Payment'), | |||
}, | |||
], | |||
}, | |||
], | |||
}, | |||
{ | |||
path: '', | |||
component: () => import('~/_pages/main/Layout'), | |||
children: [ | |||
{ | |||
path: 'test', | |||
component: () => import('~/_pages/main/Test'), | |||
@@ -13,7 +13,7 @@ $form-border-radius: 2px; | |||
margin-top: 16px; | |||
} | |||
.auth-form { | |||
.bubble-form { | |||
background-color: white; | |||
box-shadow: $form-box-shadow; | |||
@@ -33,7 +33,7 @@ $form-border-radius: 2px; | |||
} | |||
} | |||
.auth-form-submit { | |||
.bubble-form-submit { | |||
margin-top: 3rem; | |||
} | |||
@@ -106,3 +106,12 @@ String.prototype.parseDateMessage = function (millis, messages) { | |||
return evalInContext(context, expression) | |||
}) : ''; | |||
}; | |||
String.prototype.parsePrice = function () { | |||
const price = +this; | |||
if (price % 100 === 0) { | |||
return (price / 100).toString(); | |||
} else { | |||
return (price / 100).toFixed(2); | |||
} | |||
} |
@@ -8,7 +8,8 @@ import { router } from '~/_router'; | |||
import { account } from './account.module'; | |||
import staticMessages from '~/_assets/messages.json'; | |||
import preAuthStaticMessages from '~/_assets/pre_auth_messages.json'; | |||
import postAuthStaticMessages from '~/_assets/post_auth_messages.json'; | |||
const state = { | |||
configs: { | |||
@@ -68,7 +69,8 @@ const state = { | |||
} | |||
return { count: parseInt(ms), units: '' }; | |||
}, | |||
...staticMessages, | |||
...preAuthStaticMessages, | |||
...postAuthStaticMessages, | |||
}, | |||
messageGroupsLoaded: [], | |||
countries: [], | |||
@@ -337,9 +339,9 @@ const getters = { | |||
promoCodeRequired: function() { | |||
return state.promoCodePolicy === 'required'; | |||
}, | |||
configs: function () { | |||
configs: function() { | |||
return state.configs.loaded === true ? state.configs : {}; | |||
} | |||
}, | |||
}; | |||
const mutations = { | |||
@@ -368,7 +370,7 @@ const mutations = { | |||
loadSystemConfigsRequest(state) {}, | |||
loadSystemConfigsSuccess(state, configs) { | |||
state.configs = {...configs, loaded: true}; | |||
state.configs = { ...configs, loaded: true }; | |||
}, | |||
loadSystemConfigsFailure(state, error) { | |||
state.error = error; | |||
@@ -423,6 +425,7 @@ const mutations = { | |||
if (state.messageGroupsLoaded.indexOf(group) === -1) | |||
state.messageGroupsLoaded.push(group); | |||
state.messages = util.addMessages(state.messages, messages); | |||
state.messages.marketing_pricing_options = 'personal,power,mega'; | |||
if (messages.country_codes) { | |||
const countries = []; | |||
const codes = messages.country_codes.split(','); | |||