776 lines
24 KiB
Vue
776 lines
24 KiB
Vue
<template lang="pug">
|
|
v-app
|
|
.login(:style='`background-image: url(` + bgUrl + `);`')
|
|
.login-sd
|
|
.d-flex.mb-5
|
|
.login-logo
|
|
v-avatar(tile, size='34')
|
|
v-img(:src='logoUrl')
|
|
.login-title
|
|
.text-h6 {{ siteTitle }}
|
|
v-alert.mb-0(
|
|
v-model='errorShown'
|
|
transition='slide-y-reverse-transition'
|
|
color='red darken-2'
|
|
tile
|
|
dark
|
|
dense
|
|
icon='mdi-alert'
|
|
)
|
|
.body-2 {{errorMessage}}
|
|
//-------------------------------------------------
|
|
//- PROVIDERS LIST
|
|
//-------------------------------------------------
|
|
template(v-if='screen === `login` && strategies.length > 1')
|
|
.login-subtitle
|
|
.text-subtitle-1 Select Authentication Provider
|
|
.login-list
|
|
v-list.elevation-1.radius-7(nav)
|
|
v-list-item-group(v-model='selectedStrategyKey')
|
|
v-list-item(
|
|
v-for='(stg, idx) of filteredStrategies'
|
|
:key='stg.key'
|
|
:value='stg.key'
|
|
:color='stg.strategy.color'
|
|
)
|
|
v-avatar.mr-3(tile, size='24', v-html='stg.strategy.icon')
|
|
span.text-none {{stg.displayName}}
|
|
//-------------------------------------------------
|
|
//- LOGIN FORM
|
|
//-------------------------------------------------
|
|
template(v-if='screen === `login` && selectedStrategy.strategy.useForm')
|
|
.login-subtitle
|
|
.text-subtitle-1 Enter your credentials
|
|
.login-form
|
|
v-text-field(
|
|
solo
|
|
flat
|
|
prepend-inner-icon='mdi-clipboard-account'
|
|
background-color='white'
|
|
hide-details
|
|
ref='iptEmail'
|
|
v-model='username'
|
|
:placeholder='isUsernameEmail ? $t(`auth:fields.email`) : $t(`auth:fields.username`)'
|
|
:type='isUsernameEmail ? `email` : `text`'
|
|
:autocomplete='isUsernameEmail ? `email` : `username`'
|
|
)
|
|
v-text-field.mt-2(
|
|
solo
|
|
flat
|
|
prepend-inner-icon='mdi-form-textbox-password'
|
|
background-color='white'
|
|
hide-details
|
|
ref='iptPassword'
|
|
v-model='password'
|
|
:append-icon='hidePassword ? "mdi-eye-off" : "mdi-eye"'
|
|
@click:append='() => (hidePassword = !hidePassword)'
|
|
:type='hidePassword ? "password" : "text"'
|
|
:placeholder='$t("auth:fields.password")'
|
|
autocomplete='current-password'
|
|
@keyup.enter='login'
|
|
)
|
|
v-btn.mt-2.text-none(
|
|
width='100%'
|
|
large
|
|
color='primary'
|
|
dark
|
|
@click='login'
|
|
:loading='isLoading'
|
|
) {{ $t('auth:actions.login') }}
|
|
.text-center.mt-5
|
|
v-btn.text-none(
|
|
text
|
|
rounded
|
|
color='grey darken-3'
|
|
@click.stop.prevent='forgotPassword'
|
|
href='#forgot'
|
|
): .caption {{ $t('auth:forgotPasswordLink') }}
|
|
v-btn.text-none(
|
|
v-if='selectedStrategyKey === `local` && selectedStrategy.selfRegistration'
|
|
color='indigo darken-2'
|
|
text
|
|
rounded
|
|
href='/register'
|
|
): .caption {{ $t('auth:switchToRegister.link') }}
|
|
//-------------------------------------------------
|
|
//- FORGOT PASSWORD FORM
|
|
//-------------------------------------------------
|
|
template(v-if='screen === `forgot`')
|
|
.login-subtitle
|
|
.text-subtitle-1 Forgot your password
|
|
.login-info {{ $t('auth:forgotPasswordSubtitle') }}
|
|
.login-form
|
|
v-text-field(
|
|
solo
|
|
flat
|
|
prepend-inner-icon='mdi-clipboard-account'
|
|
background-color='white'
|
|
hide-details
|
|
ref='iptForgotPwdEmail'
|
|
v-model='username'
|
|
:placeholder='$t(`auth:fields.email`)'
|
|
type='email'
|
|
autocomplete='email'
|
|
)
|
|
v-btn.mt-2.text-none(
|
|
width='100%'
|
|
large
|
|
color='primary'
|
|
dark
|
|
@click='forgotPasswordSubmit'
|
|
:loading='isLoading'
|
|
) {{ $t('auth:sendResetPassword') }}
|
|
.text-center.mt-5
|
|
v-btn.text-none(
|
|
text
|
|
rounded
|
|
color='grey darken-3'
|
|
@click.stop.prevent='screen = `login`'
|
|
href='#forgot'
|
|
): .caption {{ $t('auth:forgotPasswordCancel') }}
|
|
//-------------------------------------------------
|
|
//- CHANGE PASSWORD FORM
|
|
//-------------------------------------------------
|
|
template(v-if='screen === `changePwd`')
|
|
.login-subtitle
|
|
.text-subtitle-1 {{ $t('auth:changePwd.subtitle') }}
|
|
.login-form
|
|
v-text-field.mt-2(
|
|
type='password'
|
|
solo
|
|
flat
|
|
prepend-inner-icon='mdi-form-textbox-password'
|
|
background-color='white'
|
|
hide-details
|
|
ref='iptNewPassword'
|
|
v-model='newPassword'
|
|
:placeholder='$t(`auth:changePwd.newPasswordPlaceholder`)'
|
|
autocomplete='new-password'
|
|
)
|
|
password-strength(slot='progress', v-model='newPassword')
|
|
v-text-field.mt-2(
|
|
type='password'
|
|
solo
|
|
flat
|
|
prepend-inner-icon='mdi-form-textbox-password'
|
|
background-color='white'
|
|
hide-details
|
|
v-model='newPasswordVerify'
|
|
:placeholder='$t(`auth:changePwd.newPasswordVerifyPlaceholder`)'
|
|
autocomplete='new-password'
|
|
@keyup.enter='changePassword'
|
|
)
|
|
v-btn.mt-2.text-none(
|
|
width='100%'
|
|
large
|
|
color='primary'
|
|
dark
|
|
@click='changePassword'
|
|
:loading='isLoading'
|
|
) {{ $t('auth:changePwd.proceed') }}
|
|
|
|
//-------------------------------------------------
|
|
//- TFA FORM
|
|
//-------------------------------------------------
|
|
v-dialog(v-model='isTFAShown', max-width='500', persistent)
|
|
v-card
|
|
.login-tfa.text-center.pa-5
|
|
img(src='_assets/svg/icon-pin-pad.svg')
|
|
.subtitle-2 Enter the security code generated from your trusted device:
|
|
v-text-field.login-tfa-field.mt-2(
|
|
solo
|
|
flat
|
|
background-color='white'
|
|
hide-details
|
|
ref='iptTFA'
|
|
v-model='securityCode'
|
|
:placeholder='$t("auth:tfa.placeholder")'
|
|
autocomplete='one-time-code'
|
|
@keyup.enter='verifySecurityCode(false)'
|
|
)
|
|
v-btn.mt-2.text-none(
|
|
width='100%'
|
|
large
|
|
color='primary'
|
|
dark
|
|
@click='verifySecurityCode(false)'
|
|
:loading='isLoading'
|
|
) {{ $t('auth:tfa.verifyToken') }}
|
|
|
|
//-------------------------------------------------
|
|
//- SETUP TFA FORM
|
|
//-------------------------------------------------
|
|
v-dialog(v-model='isTFASetupShown', max-width='600', persistent)
|
|
v-card
|
|
.login-tfa.text-center.pa-5
|
|
.subtitle-1.primary--text Your administrator has required Two-Factor Authentication (2FA) to be enabled on your account.
|
|
v-divider.my-5
|
|
.subtitle-2 1) Scan the QR code below from your mobile 2FA application:
|
|
.caption (e.g. #[a(href='https://authy.com/', target='_blank', noopener) Authy], #[a(href='https://support.google.com/accounts/answer/1066447', target='_blank', noopener) Google Authenticator], #[a(href='https://www.microsoft.com/en-us/account/authenticator', target='_blank', noopener) Microsoft Authenticator], etc.)
|
|
.login-tfa-qr.mt-5(v-if='isTFASetupShown', v-html='tfaQRImage')
|
|
.subtitle-2.mt-5 2) Enter the security code generated from your trusted device:
|
|
v-text-field.login-tfa-field.mt-2(
|
|
solo
|
|
flat
|
|
background-color='white'
|
|
hide-details
|
|
ref='iptTFASetup'
|
|
v-model='securityCode'
|
|
:placeholder='$t("auth:tfa.placeholder")'
|
|
autocomplete='one-time-code'
|
|
@keyup.enter='verifySecurityCode(true)'
|
|
)
|
|
v-btn.mt-2.text-none(
|
|
width='100%'
|
|
large
|
|
color='primary'
|
|
dark
|
|
@click='verifySecurityCode(true)'
|
|
:loading='isLoading'
|
|
) {{ $t('auth:tfa.verifyToken') }}
|
|
|
|
loader(v-model='isLoading', :color='loaderColor', :title='loaderTitle', :subtitle='$t(`auth:pleaseWait`)')
|
|
notify(style='padding-top: 64px;')
|
|
</template>
|
|
|
|
<script>
|
|
/* global siteConfig */
|
|
|
|
// <span>Photo by <a href="https://unsplash.com/@isaacquesada?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Isaac Quesada</a> on <a href="/t/textures-patterns?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></span>
|
|
|
|
import _ from 'lodash'
|
|
import Cookies from 'js-cookie'
|
|
import gql from 'graphql-tag'
|
|
import { sync } from 'vuex-pathify'
|
|
|
|
export default {
|
|
i18nOptions: { namespaces: 'auth' },
|
|
props: {
|
|
bgUrl: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
hideLocal: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
changePwdContinuationToken: {
|
|
type: String,
|
|
default: null
|
|
}
|
|
},
|
|
data () {
|
|
return {
|
|
error: false,
|
|
strategies: [],
|
|
selectedStrategyKey: 'unselected',
|
|
selectedStrategy: { key: 'unselected', strategy: { useForm: false, usernameType: 'email' } },
|
|
screen: 'login',
|
|
username: '',
|
|
password: '',
|
|
hidePassword: true,
|
|
securityCode: '',
|
|
continuationToken: '',
|
|
isLoading: false,
|
|
loaderColor: 'grey darken-4',
|
|
loaderTitle: 'Working...',
|
|
isShown: false,
|
|
newPassword: '',
|
|
newPasswordVerify: '',
|
|
isTFAShown: false,
|
|
isTFASetupShown: false,
|
|
tfaQRImage: '',
|
|
errorShown: false,
|
|
errorMessage: ''
|
|
}
|
|
},
|
|
computed: {
|
|
activeModal: sync('editor/activeModal'),
|
|
siteTitle () {
|
|
return siteConfig.title
|
|
},
|
|
isSocialShown () {
|
|
return this.strategies.length > 1
|
|
},
|
|
logoUrl () { return siteConfig.logoUrl },
|
|
filteredStrategies () {
|
|
const qParams = new URLSearchParams(window.location.search)
|
|
if (this.hideLocal && !qParams.has('all')) {
|
|
return _.reject(this.strategies, ['key', 'local'])
|
|
} else {
|
|
return this.strategies
|
|
}
|
|
},
|
|
isUsernameEmail () {
|
|
return this.selectedStrategy.strategy.usernameType === `email`
|
|
}
|
|
},
|
|
watch: {
|
|
filteredStrategies (newValue, oldValue) {
|
|
if (_.head(newValue).strategy.useForm) {
|
|
this.selectedStrategyKey = _.head(newValue).key
|
|
}
|
|
},
|
|
selectedStrategyKey (newValue, oldValue) {
|
|
this.selectedStrategy = _.find(this.strategies, ['key', newValue])
|
|
if (this.screen === 'changePwd') {
|
|
return
|
|
}
|
|
this.screen = 'login'
|
|
if (!this.selectedStrategy.strategy.useForm) {
|
|
this.isLoading = true
|
|
window.location.assign('/login/' + newValue)
|
|
} else {
|
|
this.$nextTick(() => {
|
|
this.$refs.iptEmail.focus()
|
|
})
|
|
}
|
|
}
|
|
},
|
|
mounted () {
|
|
this.isShown = true
|
|
if (this.changePwdContinuationToken) {
|
|
this.screen = 'changePwd'
|
|
this.continuationToken = this.changePwdContinuationToken
|
|
}
|
|
},
|
|
methods: {
|
|
/**
|
|
* LOGIN
|
|
*/
|
|
async login () {
|
|
this.errorShown = false
|
|
if (this.username.length < 2) {
|
|
this.errorMessage = this.$t('auth:invalidEmailUsername')
|
|
this.errorShown = true
|
|
this.$refs.iptEmail.focus()
|
|
} else if (this.password.length < 2) {
|
|
this.errorMessage = this.$t('auth:invalidPassword')
|
|
this.errorShown = true
|
|
this.$refs.iptPassword.focus()
|
|
} else {
|
|
this.loaderColor = 'grey darken-4'
|
|
this.loaderTitle = this.$t('auth:signingIn')
|
|
this.isLoading = true
|
|
try {
|
|
const resp = await this.$apollo.mutate({
|
|
mutation: gql`
|
|
mutation($username: String!, $password: String!, $strategy: String!) {
|
|
authentication {
|
|
login(username: $username, password: $password, strategy: $strategy) {
|
|
responseResult {
|
|
succeeded
|
|
errorCode
|
|
slug
|
|
message
|
|
}
|
|
jwt
|
|
mustChangePwd
|
|
mustProvideTFA
|
|
mustSetupTFA
|
|
continuationToken
|
|
redirect
|
|
tfaQRImage
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
username: this.username,
|
|
password: this.password,
|
|
strategy: this.selectedStrategy.key
|
|
}
|
|
})
|
|
if (_.has(resp, 'data.authentication.login')) {
|
|
const respObj = _.get(resp, 'data.authentication.login', {})
|
|
if (respObj.responseResult.succeeded === true) {
|
|
this.handleLoginResponse(respObj)
|
|
} else {
|
|
throw new Error(respObj.responseResult.message)
|
|
}
|
|
} else {
|
|
throw new Error(this.$t('auth:genericError'))
|
|
}
|
|
} catch (err) {
|
|
console.error(err)
|
|
this.$store.commit('showNotification', {
|
|
style: 'red',
|
|
message: err.message,
|
|
icon: 'alert'
|
|
})
|
|
this.isLoading = false
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* VERIFY TFA CODE
|
|
*/
|
|
async verifySecurityCode (setup = false) {
|
|
if (this.securityCode.length !== 6) {
|
|
this.$store.commit('showNotification', {
|
|
style: 'red',
|
|
message: 'Enter a valid security code.',
|
|
icon: 'alert'
|
|
})
|
|
if (setup) {
|
|
this.$refs.iptTFASetup.focus()
|
|
} else {
|
|
this.$refs.iptTFA.focus()
|
|
}
|
|
} else {
|
|
this.loaderColor = 'grey darken-4'
|
|
this.loaderTitle = this.$t('auth:signingIn')
|
|
this.isLoading = true
|
|
try {
|
|
const resp = await this.$apollo.mutate({
|
|
mutation: gql`
|
|
mutation(
|
|
$continuationToken: String!
|
|
$securityCode: String!
|
|
$setup: Boolean
|
|
) {
|
|
authentication {
|
|
loginTFA(
|
|
continuationToken: $continuationToken
|
|
securityCode: $securityCode
|
|
setup: $setup
|
|
) {
|
|
responseResult {
|
|
succeeded
|
|
errorCode
|
|
slug
|
|
message
|
|
}
|
|
jwt
|
|
mustChangePwd
|
|
continuationToken
|
|
redirect
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
continuationToken: this.continuationToken,
|
|
securityCode: this.securityCode,
|
|
setup
|
|
}
|
|
})
|
|
if (_.has(resp, 'data.authentication.loginTFA')) {
|
|
let respObj = _.get(resp, 'data.authentication.loginTFA', {})
|
|
if (respObj.responseResult.succeeded === true) {
|
|
this.handleLoginResponse(respObj)
|
|
} else {
|
|
if (!setup) {
|
|
this.isTFAShown = false
|
|
}
|
|
throw new Error(respObj.responseResult.message)
|
|
}
|
|
} else {
|
|
throw new Error(this.$t('auth:genericError'))
|
|
}
|
|
} catch (err) {
|
|
console.error(err)
|
|
this.$store.commit('showNotification', {
|
|
style: 'red',
|
|
message: err.message,
|
|
icon: 'alert'
|
|
})
|
|
this.isLoading = false
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* CHANGE PASSWORD
|
|
*/
|
|
async changePassword () {
|
|
this.loaderColor = 'grey darken-4'
|
|
this.loaderTitle = this.$t('auth:changePwd.loading')
|
|
this.isLoading = true
|
|
try {
|
|
const resp = await this.$apollo.mutate({
|
|
mutation: gql`
|
|
mutation (
|
|
$continuationToken: String!
|
|
$newPassword: String!
|
|
) {
|
|
authentication {
|
|
loginChangePassword (
|
|
continuationToken: $continuationToken
|
|
newPassword: $newPassword
|
|
) {
|
|
responseResult {
|
|
succeeded
|
|
errorCode
|
|
slug
|
|
message
|
|
}
|
|
jwt
|
|
continuationToken
|
|
redirect
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
continuationToken: this.continuationToken,
|
|
newPassword: this.newPassword
|
|
}
|
|
})
|
|
if (_.has(resp, 'data.authentication.loginChangePassword')) {
|
|
let respObj = _.get(resp, 'data.authentication.loginChangePassword', {})
|
|
if (respObj.responseResult.succeeded === true) {
|
|
this.handleLoginResponse(respObj)
|
|
} else {
|
|
throw new Error(respObj.responseResult.message)
|
|
}
|
|
} else {
|
|
throw new Error(this.$t('auth:genericError'))
|
|
}
|
|
} catch (err) {
|
|
console.error(err)
|
|
this.$store.commit('showNotification', {
|
|
style: 'red',
|
|
message: err.message,
|
|
icon: 'alert'
|
|
})
|
|
this.isLoading = false
|
|
}
|
|
},
|
|
/**
|
|
* SWITCH TO FORGOT PASSWORD SCREEN
|
|
*/
|
|
forgotPassword () {
|
|
this.screen = 'forgot'
|
|
this.$nextTick(() => {
|
|
this.$refs.iptForgotPwdEmail.focus()
|
|
})
|
|
},
|
|
/**
|
|
* FORGOT PASSWORD SUBMIT
|
|
*/
|
|
async forgotPasswordSubmit () {
|
|
this.loaderColor = 'grey darken-4'
|
|
this.loaderTitle = this.$t('auth:forgotPasswordLoading')
|
|
this.isLoading = true
|
|
try {
|
|
const resp = await this.$apollo.mutate({
|
|
mutation: gql`
|
|
mutation (
|
|
$email: String!
|
|
) {
|
|
authentication {
|
|
forgotPassword (
|
|
email: $email
|
|
) {
|
|
responseResult {
|
|
succeeded
|
|
errorCode
|
|
slug
|
|
message
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables: {
|
|
email: this.username
|
|
}
|
|
})
|
|
if (_.has(resp, 'data.authentication.forgotPassword.responseResult')) {
|
|
let respObj = _.get(resp, 'data.authentication.forgotPassword.responseResult', {})
|
|
if (respObj.succeeded === true) {
|
|
this.$store.commit('showNotification', {
|
|
style: 'success',
|
|
message: this.$t('auth:forgotPasswordSuccess'),
|
|
icon: 'email'
|
|
})
|
|
this.screen = 'login'
|
|
} else {
|
|
throw new Error(respObj.message)
|
|
}
|
|
} else {
|
|
throw new Error(this.$t('auth:genericError'))
|
|
}
|
|
} catch (err) {
|
|
console.error(err)
|
|
this.$store.commit('showNotification', {
|
|
style: 'red',
|
|
message: err.message,
|
|
icon: 'alert'
|
|
})
|
|
}
|
|
this.isLoading = false
|
|
},
|
|
handleLoginResponse (respObj) {
|
|
this.continuationToken = respObj.continuationToken
|
|
if (respObj.mustChangePwd === true) {
|
|
this.screen = 'changePwd'
|
|
this.$nextTick(() => {
|
|
this.$refs.iptNewPassword.focus()
|
|
})
|
|
this.isLoading = false
|
|
} else if (respObj.mustProvideTFA === true) {
|
|
this.securityCode = ''
|
|
this.isTFAShown = true
|
|
setTimeout(() => {
|
|
this.$refs.iptTFA.focus()
|
|
}, 500)
|
|
this.isLoading = false
|
|
} else if (respObj.mustSetupTFA === true) {
|
|
this.securityCode = ''
|
|
this.isTFASetupShown = true
|
|
this.tfaQRImage = respObj.tfaQRImage
|
|
setTimeout(() => {
|
|
this.$refs.iptTFASetup.focus()
|
|
}, 500)
|
|
this.isLoading = false
|
|
} else {
|
|
this.loaderColor = 'green darken-1'
|
|
this.loaderTitle = this.$t('auth:loginSuccess')
|
|
Cookies.set('jwt', respObj.jwt, { expires: 365 })
|
|
_.delay(() => {
|
|
const loginRedirect = Cookies.get('loginRedirect')
|
|
if (loginRedirect) {
|
|
Cookies.remove('loginRedirect')
|
|
window.location.replace(loginRedirect)
|
|
} else if (respObj.redirect) {
|
|
window.location.replace(respObj.redirect)
|
|
} else {
|
|
window.location.replace('/')
|
|
}
|
|
}, 1000)
|
|
}
|
|
}
|
|
},
|
|
apollo: {
|
|
strategies: {
|
|
query: gql`
|
|
{
|
|
authentication {
|
|
activeStrategies {
|
|
key
|
|
strategy {
|
|
key
|
|
logo
|
|
color
|
|
icon
|
|
useForm
|
|
usernameType
|
|
}
|
|
displayName
|
|
order
|
|
selfRegistration
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
update: (data) => _.sortBy(data.authentication.activeStrategies, ['order']),
|
|
watchLoading (isLoading) {
|
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'login-strategies-refresh')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.login {
|
|
// background-image: url('/_assets/img/splash/1.jpg');
|
|
background-color: mc('grey', '900');
|
|
background-size: cover;
|
|
background-position: center center;
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
&-sd {
|
|
background-color: rgba(255,255,255,.8);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border-left: 1px solid rgba(255,255,255,.85);
|
|
border-right: 1px solid rgba(255,255,255,.85);
|
|
width: 450px;
|
|
height: 100%;
|
|
margin-left: 5vw;
|
|
|
|
@at-root .no-backdropfilter & {
|
|
background-color: rgba(255,255,255,.95);
|
|
}
|
|
|
|
@include until($tablet) {
|
|
margin-left: 0;
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
&-logo {
|
|
padding: 12px 0 0 12px;
|
|
width: 58px;
|
|
height: 58px;
|
|
background-color: #222;
|
|
margin-left: 12px;
|
|
border-bottom-left-radius: 7px;
|
|
border-bottom-right-radius: 7px;
|
|
}
|
|
|
|
&-title {
|
|
height: 58px;
|
|
padding-left: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
text-shadow: .5px .5px #FFF;
|
|
}
|
|
|
|
&-subtitle {
|
|
padding: 24px 12px 12px 12px;
|
|
color: #111;
|
|
font-weight: 500;
|
|
text-shadow: 1px 1px rgba(255,255,255,.5);
|
|
background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,.15));
|
|
text-align: center;
|
|
border-bottom: 1px solid rgba(0,0,0,.3);
|
|
}
|
|
|
|
&-info {
|
|
border-top: 1px solid rgba(255,255,255,.85);
|
|
background-color: rgba(255,255,255,.15);
|
|
border-bottom: 1px solid rgba(0,0,0,.15);
|
|
padding: 12px;
|
|
font-size: 13px;
|
|
text-align: center;
|
|
}
|
|
|
|
&-list {
|
|
border-top: 1px solid rgba(255,255,255,.85);
|
|
padding: 12px;
|
|
}
|
|
|
|
&-form {
|
|
padding: 12px;
|
|
border-top: 1px solid rgba(255,255,255,.85);
|
|
}
|
|
|
|
&-main {
|
|
flex: 1 0 100vw;
|
|
height: 100vh;
|
|
}
|
|
|
|
&-tfa {
|
|
background-color: #EEE;
|
|
border: 7px solid #FFF;
|
|
|
|
&-field input {
|
|
text-align: center;
|
|
}
|
|
|
|
&-qr {
|
|
background-color: #FFF;
|
|
padding: 5px;
|
|
border-radius: 5px;
|
|
width: 200px;
|
|
height: 200px;
|
|
margin: 0 auto;
|
|
}
|
|
}
|
|
}
|
|
</style>
|