mirror of
https://gitlab.com/CIEFWorldwideSdnBhd/exchange.git
synced 2026-08-25 23:34:11 +00:00
merge master
This commit is contained in:
+15
-19
@@ -1,22 +1,18 @@
|
||||
import Vue from 'vue'
|
||||
import store from '~/store'
|
||||
import router from '~/router'
|
||||
import i18n from '~/plugins/i18n'
|
||||
import App from '~/components/App'
|
||||
|
||||
/**
|
||||
* First we will load all of this project's JavaScript dependencies which
|
||||
* includes Vue and other libraries. It is a great starting point when
|
||||
* building robust, powerful web applications using Vue and Laravel.
|
||||
*/
|
||||
import '~/plugins'
|
||||
import '~/components'
|
||||
|
||||
require('./bootstrap');
|
||||
Vue.config.productionTip = false
|
||||
|
||||
window.Vue = require('vue');
|
||||
|
||||
/**
|
||||
* Next, we will create a fresh Vue application instance and attach it to
|
||||
* the page. Then, you may begin adding components to this application
|
||||
* or customize the JavaScript scaffolding to fit your unique needs.
|
||||
*/
|
||||
|
||||
Vue.component('example-component', require('./components/ExampleComponent.vue'));
|
||||
|
||||
const app = new Vue({
|
||||
el: '#app'
|
||||
});
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
i18n,
|
||||
store,
|
||||
router,
|
||||
...App
|
||||
})
|
||||
|
||||
Vendored
-56
@@ -1,56 +0,0 @@
|
||||
|
||||
window._ = require('lodash');
|
||||
window.Popper = require('popper.js').default;
|
||||
|
||||
/**
|
||||
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
|
||||
* for JavaScript based Bootstrap features such as modals and tabs. This
|
||||
* code may be modified to fit the specific needs of your application.
|
||||
*/
|
||||
|
||||
try {
|
||||
window.$ = window.jQuery = require('jquery');
|
||||
|
||||
require('bootstrap');
|
||||
} catch (e) {}
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
window.axios = require('axios');
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Next we will register the CSRF Token as a common header with Axios so that
|
||||
* all outgoing HTTP requests automatically have it attached. This is just
|
||||
* a simple convenience so we don't have to attach every token manually.
|
||||
*/
|
||||
|
||||
let token = document.head.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
if (token) {
|
||||
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
|
||||
} else {
|
||||
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo'
|
||||
|
||||
// window.Pusher = require('pusher-js');
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: process.env.MIX_PUSHER_APP_KEY,
|
||||
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
|
||||
// encrypted: true
|
||||
// });
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<loading ref="loading"/>
|
||||
|
||||
<transition name="page" mode="out-in">
|
||||
<component v-if="layout" :is="layout"/>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Loading from './Loading'
|
||||
|
||||
// Load layout components dynamically.
|
||||
const requireContext = require.context('~/layouts', false, /.*\.vue$/)
|
||||
|
||||
const layouts = requireContext.keys()
|
||||
.map(file =>
|
||||
[file.replace(/(^.\/)|(\.vue$)/g, ''), requireContext(file)]
|
||||
)
|
||||
.reduce((components, [name, component]) => {
|
||||
components[name] = component.default || component
|
||||
return components
|
||||
}, {})
|
||||
|
||||
export default {
|
||||
el: '#app',
|
||||
|
||||
components: {
|
||||
Loading
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
layout: null,
|
||||
defaultLayout: 'default'
|
||||
}),
|
||||
|
||||
metaInfo () {
|
||||
const { appName } = window.config
|
||||
|
||||
return {
|
||||
title: appName,
|
||||
titleTemplate: `%s · ${appName}`
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$loading = this.$refs.loading
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Set the application layout.
|
||||
*
|
||||
* @param {String} layout
|
||||
*/
|
||||
setLayout (layout) {
|
||||
if (!layout || !layouts[layout]) {
|
||||
layout = this.defaultLayout
|
||||
}
|
||||
|
||||
this.layout = layouts[layout]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<button :type="nativeType" :disabled="loading" :class="{
|
||||
[`btn-${type}`]: true,
|
||||
'btn-block': block,
|
||||
'btn-lg': large,
|
||||
'btn-loading': loading
|
||||
}" class="btn">
|
||||
<slot/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VButton',
|
||||
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
|
||||
nativeType: {
|
||||
type: String,
|
||||
default: 'submit'
|
||||
},
|
||||
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
block: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
large: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div v-if="title" class="card-header">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Card',
|
||||
|
||||
props: {
|
||||
title: { type: String, default: null }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="custom-control custom-checkbox d-flex">
|
||||
<input
|
||||
:name="name"
|
||||
:checked="internalValue"
|
||||
:id="id || name"
|
||||
type="checkbox"
|
||||
class="custom-control-input"
|
||||
@click="handleClick">
|
||||
<label :for="id || name" class="custom-control-label my-auto">
|
||||
<slot/>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Checkbox',
|
||||
|
||||
props: {
|
||||
id: { type: String, default: null },
|
||||
name: { type: String, default: 'checkbox' },
|
||||
value: { type: Boolean, default: false },
|
||||
checked: { type: Boolean, default: false }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
internalValue: false
|
||||
}),
|
||||
|
||||
watch: {
|
||||
value (val) {
|
||||
this.internalValue = val
|
||||
},
|
||||
|
||||
checked (val) {
|
||||
this.internalValue = val
|
||||
},
|
||||
|
||||
internalValue (val, oldVal) {
|
||||
if (val !== oldVal) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.internalValue = this.value
|
||||
|
||||
if ('checked' in this.$options.propsData) {
|
||||
this.internalValue = this.checked
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick (e) {
|
||||
this.$emit('click', e)
|
||||
|
||||
if (!e.isPropagationStopped) {
|
||||
this.internalValue = e.target.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<transition name="page" mode="out-in">
|
||||
<slot>
|
||||
<router-view/>
|
||||
</slot>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Child'
|
||||
}
|
||||
</script>
|
||||
@@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card card-default">
|
||||
<div class="card-header">Example Component</div>
|
||||
|
||||
<div class="card-body">
|
||||
I'm an example component.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
console.log('Component mounted.')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div :style="{
|
||||
width: `${percent}%`,
|
||||
height: height,
|
||||
opacity: show ? 1 : 0,
|
||||
'background-color': canSuccess ? color : failedColor
|
||||
}" class="progress"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// https://github.com/nuxt/nuxt.js/blob/master/lib/app/components/nuxt-loading.vue
|
||||
export default {
|
||||
data: () => ({
|
||||
percent: 0,
|
||||
show: false,
|
||||
canSuccess: true,
|
||||
duration: 3000,
|
||||
height: '2px',
|
||||
color: '#77b6ff',
|
||||
failedColor: 'red'
|
||||
}),
|
||||
|
||||
methods: {
|
||||
start () {
|
||||
this.show = true
|
||||
this.canSuccess = true
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer)
|
||||
this.percent = 0
|
||||
}
|
||||
this._cut = 10000 / Math.floor(this.duration)
|
||||
this._timer = setInterval(() => {
|
||||
this.increase(this._cut * Math.random())
|
||||
if (this.percent > 95) {
|
||||
this.finish()
|
||||
}
|
||||
}, 100)
|
||||
return this
|
||||
},
|
||||
set (num) {
|
||||
this.show = true
|
||||
this.canSuccess = true
|
||||
this.percent = Math.floor(num)
|
||||
return this
|
||||
},
|
||||
get () {
|
||||
return Math.floor(this.percent)
|
||||
},
|
||||
increase (num) {
|
||||
this.percent = this.percent + Math.floor(num)
|
||||
return this
|
||||
},
|
||||
decrease (num) {
|
||||
this.percent = this.percent - Math.floor(num)
|
||||
return this
|
||||
},
|
||||
finish () {
|
||||
this.percent = 100
|
||||
this.hide()
|
||||
return this
|
||||
},
|
||||
pause () {
|
||||
clearInterval(this._timer)
|
||||
return this
|
||||
},
|
||||
hide () {
|
||||
clearInterval(this._timer)
|
||||
this._timer = null
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.percent = 0
|
||||
}, 200)
|
||||
})
|
||||
}, 500)
|
||||
return this
|
||||
},
|
||||
fail () {
|
||||
this.canSuccess = false
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.progress {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 2px;
|
||||
width: 0%;
|
||||
transition: width 0.2s, opacity 0.4s;
|
||||
opacity: 1;
|
||||
background-color: #efc14e;
|
||||
z-index: 999999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ locales[locale] }}
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
<a v-for="(value, key) in locales" :key="key" class="dropdown-item" href="#"
|
||||
@click.prevent="setLocale(key)">
|
||||
{{ value }}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { loadMessages } from '~/plugins/i18n'
|
||||
|
||||
export default {
|
||||
computed: mapGetters({
|
||||
locale: 'lang/locale',
|
||||
locales: 'lang/locales'
|
||||
}),
|
||||
|
||||
methods: {
|
||||
setLocale (locale) {
|
||||
if (this.$i18n.locale !== locale) {
|
||||
loadMessages(locale)
|
||||
|
||||
this.$store.dispatch('lang/setLocale', { locale })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<button v-if="githubAuth" class="btn btn-dark ml-auto" type="button" @click="login">
|
||||
{{ $t('login_with') }}
|
||||
<fa :icon="['fab', 'github']"/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LoginWithGithub',
|
||||
|
||||
computed: {
|
||||
githubAuth: () => window.config.githubAuth,
|
||||
url: () => `/api/oauth/github`
|
||||
},
|
||||
|
||||
mounted () {
|
||||
window.addEventListener('message', this.onMessage, false)
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('message', this.onMessage)
|
||||
},
|
||||
|
||||
methods: {
|
||||
async login () {
|
||||
const url = await this.$store.dispatch('auth/fetchOauthUrl', {
|
||||
provider: 'github'
|
||||
})
|
||||
|
||||
openWindow(url, this.$t('login'))
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {MessageEvent} e
|
||||
*/
|
||||
onMessage (e) {
|
||||
if (e.origin !== window.origin || !e.data.token) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.dispatch('auth/saveToken', {
|
||||
token: e.data.token
|
||||
})
|
||||
|
||||
this.$router.push({ name: 'home' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @return {Window}
|
||||
*/
|
||||
function openWindow (url, title, options = {}) {
|
||||
if (typeof url === 'object') {
|
||||
options = url
|
||||
url = ''
|
||||
}
|
||||
|
||||
options = { url, title, width: 600, height: 720, ...options }
|
||||
|
||||
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screen.left
|
||||
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screen.top
|
||||
const width = window.innerWidth || document.documentElement.clientWidth || window.screen.width
|
||||
const height = window.innerHeight || document.documentElement.clientHeight || window.screen.height
|
||||
|
||||
options.left = ((width / 2) - (options.width / 2)) + dualScreenLeft
|
||||
options.top = ((height / 2) - (options.height / 2)) + dualScreenTop
|
||||
|
||||
const optionsStr = Object.keys(options).reduce((acc, key) => {
|
||||
acc.push(`${key}=${options[key]}`)
|
||||
return acc
|
||||
}, []).join(',')
|
||||
|
||||
const newWindow = window.open(url, title, optionsStr)
|
||||
|
||||
if (window.focus) {
|
||||
newWindow.focus()
|
||||
}
|
||||
|
||||
return newWindow
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white">
|
||||
<div class="container">
|
||||
<router-link :to="{ name: user ? 'home' : 'welcome' }" class="navbar-brand">
|
||||
{{ appName }}
|
||||
</router-link>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggler" aria-controls="navbarToggler" aria-expanded="false">
|
||||
<span class="navbar-toggler-icon"/>
|
||||
</button>
|
||||
|
||||
<div id="navbarToggler" class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav">
|
||||
<locale-dropdown/>
|
||||
<!-- <li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<!-- Authenticated -->
|
||||
<li v-if="user" class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle text-dark"
|
||||
href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img :src="user.photo_url" class="rounded-circle profile-photo mr-1">
|
||||
{{ user.name }}
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
<router-link :to="{ name: 'settings.profile' }" class="dropdown-item pl-3">
|
||||
<fa icon="cog" fixed-width/>
|
||||
{{ $t('settings') }}
|
||||
</router-link>
|
||||
|
||||
<div class="dropdown-divider"/>
|
||||
<a href="#" class="dropdown-item pl-3" @click.prevent="logout">
|
||||
<fa icon="sign-out-alt" fixed-width/>
|
||||
{{ $t('logout') }}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<!-- Guest -->
|
||||
<template v-else>
|
||||
<li class="nav-item">
|
||||
<router-link :to="{ name: 'login' }" class="nav-link" active-class="active">
|
||||
{{ $t('login') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link :to="{ name: 'register' }" class="nav-link" active-class="active">
|
||||
{{ $t('register') }}
|
||||
</router-link>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import LocaleDropdown from './LocaleDropdown'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LocaleDropdown
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
appName: window.config.appName
|
||||
}),
|
||||
|
||||
computed: mapGetters({
|
||||
user: 'auth/user'
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async logout () {
|
||||
// Log out the user.
|
||||
await this.$store.dispatch('auth/logout')
|
||||
|
||||
// Redirect to login.
|
||||
this.$router.push({ name: 'login' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.profile-photo {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin: -.375rem 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
import Card from './Card'
|
||||
import Child from './Child'
|
||||
import Button from './Button'
|
||||
import Checkbox from './Checkbox'
|
||||
import { HasError, AlertError, AlertSuccess } from 'vform'
|
||||
|
||||
// Components that are registered globaly.
|
||||
[
|
||||
Card,
|
||||
Child,
|
||||
Button,
|
||||
Checkbox,
|
||||
HasError,
|
||||
AlertError,
|
||||
AlertSuccess
|
||||
].forEach(Component => {
|
||||
Vue.component(Component.name, Component)
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"ok": "Ok",
|
||||
"cancel": "Cancel",
|
||||
"error_alert_title": "Oops...",
|
||||
"error_alert_text": "Something went wrong! Please try again.",
|
||||
"token_expired_alert_title": "Session Expired!",
|
||||
"token_expired_alert_text": "Please log in again to continue.",
|
||||
"login": "Log In",
|
||||
"register": "Register",
|
||||
"page_not_found": "Page Not Found",
|
||||
"go_home": "Go Home",
|
||||
"logout": "Logout",
|
||||
"email": "Email",
|
||||
"remember_me": "Remember Me",
|
||||
"password": "Password",
|
||||
"forgot_password": "Forgot Your Password?",
|
||||
"confirm_password": "Confirm Password",
|
||||
"name": "Name",
|
||||
"toggle_navigation": "Toggle navigation",
|
||||
"home": "Home",
|
||||
"you_are_logged_in": "You are logged in!",
|
||||
"reset_password": "Reset Password",
|
||||
"send_password_reset_link": "Send Password Reset Link",
|
||||
"settings": "Settings",
|
||||
"profile": "Profile",
|
||||
"your_info": "Your Info",
|
||||
"info_updated": "Your info has been updated!",
|
||||
"update": "Update",
|
||||
"your_password": "Your Password",
|
||||
"password_updated": "Your password has been updated!",
|
||||
"new_password": "New Password",
|
||||
"login_with": "Login with",
|
||||
"register_with": "Register with"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"ok": "De Acuerdo",
|
||||
"cancel": "Cancelar",
|
||||
"error_alert_title": "Ha ocurrido un problema",
|
||||
"error_alert_text": "¡Algo salió mal! Inténtalo de nuevo.",
|
||||
"token_expired_alert_title": "!Sesión Expirada!",
|
||||
"token_expired_alert_text": "Por favor inicie sesión de nuevo para continuar.",
|
||||
"login": "Iniciar Sesión",
|
||||
"register": "Registro",
|
||||
"page_not_found": "Página No Encontrada",
|
||||
"go_home": "Ir a Inicio",
|
||||
"logout": "Cerrar Sesión",
|
||||
"email": "Correo Electrónico",
|
||||
"remember_me": "Recuérdame",
|
||||
"password": "Contraseña",
|
||||
"forgot_password": "¿Olvidaste tu contraseña?",
|
||||
"confirm_password": "Confirmar Contraseña",
|
||||
"name": "Nombre",
|
||||
"toggle_navigation": "Cambiar Navegación",
|
||||
"home": "Inicio",
|
||||
"you_are_logged_in": "¡Has iniciado sesión!",
|
||||
"reset_password": "Restablecer la contraseña",
|
||||
"send_password_reset_link": "Enviar Enlace de Restablecimiento de Contraseña",
|
||||
"settings": "Configuraciones",
|
||||
"profile": "Perfil",
|
||||
"your_info": "Tu Información",
|
||||
"info_updated": "¡Tu información ha sido actualizada!",
|
||||
"update": "Actualizar",
|
||||
"your_password": "Tu Contraseña",
|
||||
"password_updated": "¡Tu contraseña ha sido actualizada!",
|
||||
"new_password": "Nueva Contraseña",
|
||||
"login_with": "Iniciar Sesión con",
|
||||
"register_with": "Registro con"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"ok": "确定",
|
||||
"cancel": "取消",
|
||||
"error_alert_title": "错误...",
|
||||
"error_alert_text": "遇到一些错误,请稍后重试~",
|
||||
"token_expired_alert_title": "验证过期!",
|
||||
"token_expired_alert_text": "请稍后重新登录系统",
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"page_not_found": "页面不存在",
|
||||
"go_home": "返回首页",
|
||||
"logout": "退出",
|
||||
"email": "邮箱",
|
||||
"remember_me": "记住我",
|
||||
"password": "密码",
|
||||
"forgot_password": "忘记密码?",
|
||||
"confirm_password": "重复密码",
|
||||
"name": "用户名",
|
||||
"toggle_navigation": "切换导航",
|
||||
"home": "首页",
|
||||
"you_are_logged_in": "您已经登录!",
|
||||
"reset_password": "重置密码",
|
||||
"send_password_reset_link": "发送重置链接",
|
||||
"settings": "设置",
|
||||
"profile": "个人设置",
|
||||
"your_info": "您的个人信息",
|
||||
"info_updated": "您的个人信息已经更改!",
|
||||
"update": "更新",
|
||||
"your_password": "您的密码",
|
||||
"password_updated": "您的密码已经更新!",
|
||||
"new_password": "新密码",
|
||||
"login_with": "登录",
|
||||
"register_with": "注册"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="basic-layout d-flex align-items-center justify-content-center m-0 bg-white">
|
||||
<child/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BasicLayout'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.basic-layout {
|
||||
color: #636b6f;
|
||||
height: 100vh;
|
||||
font-weight: 100;
|
||||
position: relative;
|
||||
|
||||
.links > a {
|
||||
color: #636b6f;
|
||||
padding: 0 25px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: .1rem;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="main-layout">
|
||||
<navbar/>
|
||||
|
||||
<div class="container mt-4">
|
||||
<child/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Navbar from '~/components/Navbar'
|
||||
|
||||
export default {
|
||||
name: 'MainLayout',
|
||||
|
||||
components: {
|
||||
Navbar
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
import store from '~/store'
|
||||
|
||||
export default (to, from, next) => {
|
||||
if (store.getters['auth/user'].role !== 'admin') {
|
||||
next({ name: 'home' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import store from '~/store'
|
||||
|
||||
export default async (to, from, next) => {
|
||||
if (!store.getters['auth/check']) {
|
||||
next({ name: 'login' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import store from '~/store'
|
||||
|
||||
export default async (to, from, next) => {
|
||||
if (!store.getters['auth/check'] && store.getters['auth/token']) {
|
||||
try {
|
||||
await store.dispatch('auth/fetchUser')
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import store from '~/store'
|
||||
|
||||
export default (to, from, next) => {
|
||||
if (store.getters['auth/check']) {
|
||||
next({ name: 'home' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import store from '~/store'
|
||||
import { loadMessages } from '~/plugins/i18n'
|
||||
|
||||
export default async (to, from, next) => {
|
||||
await loadMessages(store.getters['lang/locale'])
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<card :title="$t('home')">
|
||||
This is admin
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
middleware: 'admin',
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('home') }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top-right links">
|
||||
<template v-if="authenticated">
|
||||
<router-link :to="{ name: 'home' }">
|
||||
{{ $t('home') }}
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link :to="{ name: 'login' }">
|
||||
{{ $t('login') }}
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'register' }">
|
||||
{{ $t('register') }}
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="title mb-4">
|
||||
This is admin
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
layout: 'basic',
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('home') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
title: window.config.appName
|
||||
}),
|
||||
|
||||
computed: mapGetters({
|
||||
authenticated: 'auth/check'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-right {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 85px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-lg-8 m-auto">
|
||||
<card :title="$t('login')">
|
||||
<form @submit.prevent="login" @keydown="form.onKeydown($event)">
|
||||
<!-- Email -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('email') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.email" :class="{ 'is-invalid': form.errors.has('email') }" class="form-control" type="email" name="email">
|
||||
<has-error :form="form" field="email"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('password') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.password" :class="{ 'is-invalid': form.errors.has('password') }" class="form-control" type="password" name="password">
|
||||
<has-error :form="form" field="password"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me -->
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3"/>
|
||||
<div class="col-md-7 d-flex">
|
||||
<checkbox v-model="remember" name="remember">
|
||||
{{ $t('remember_me') }}
|
||||
</checkbox>
|
||||
|
||||
<router-link :to="{ name: 'password.request' }" class="small ml-auto my-auto">
|
||||
{{ $t('forgot_password') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-7 offset-md-3 d-flex">
|
||||
<!-- Submit Button -->
|
||||
<v-button :loading="form.busy">
|
||||
{{ $t('login') }}
|
||||
</v-button>
|
||||
|
||||
<!-- GitHub Login Button -->
|
||||
<login-with-github/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import LoginWithGithub from '~/components/LoginWithGithub'
|
||||
|
||||
export default {
|
||||
middleware: 'guest',
|
||||
|
||||
components: {
|
||||
LoginWithGithub
|
||||
},
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('login') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
email: '',
|
||||
password: ''
|
||||
}),
|
||||
remember: false
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async login () {
|
||||
// Submit the form.
|
||||
const { data } = await this.form.post('/api/login')
|
||||
|
||||
// Save the token.
|
||||
this.$store.dispatch('auth/saveToken', {
|
||||
token: data.token,
|
||||
remember: this.remember
|
||||
})
|
||||
|
||||
// Fetch the user.
|
||||
await this.$store.dispatch('auth/fetchUser')
|
||||
|
||||
// Redirect home.
|
||||
if(store.getters['auth/user'].role === 'admin'){
|
||||
this.$router.push({ name: 'admin.home' })
|
||||
}
|
||||
this.$router.push({ name: 'home' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-lg-8 m-auto">
|
||||
<card :title="$t('reset_password')">
|
||||
<form @submit.prevent="send" @keydown="form.onKeydown($event)">
|
||||
<alert-success :form="form" :message="status"/>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('email') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.email" :class="{ 'is-invalid': form.errors.has('email') }" class="form-control" type="email" name="email">
|
||||
<has-error :form="form" field="email"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9 ml-md-auto">
|
||||
<v-button :loading="form.busy">
|
||||
{{ $t('send_password_reset_link') }}
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
|
||||
export default {
|
||||
middleware: 'guest',
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('reset_password') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
status: '',
|
||||
form: new Form({
|
||||
email: ''
|
||||
})
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async send () {
|
||||
const { data } = await this.form.post('/api/password/email')
|
||||
|
||||
this.status = data.status
|
||||
|
||||
this.form.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-lg-8 m-auto">
|
||||
<card :title="$t('reset_password')">
|
||||
<form @submit.prevent="reset" @keydown="form.onKeydown($event)">
|
||||
<alert-success :form="form" :message="status"/>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('email') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.email" :class="{ 'is-invalid': form.errors.has('email') }" class="form-control" type="email" name="email" readonly>
|
||||
<has-error :form="form" field="email"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('password') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.password" :class="{ 'is-invalid': form.errors.has('password') }" class="form-control" type="password" name="password">
|
||||
<has-error :form="form" field="password"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Confirmation -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('confirm_password') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.password_confirmation" :class="{ 'is-invalid': form.errors.has('password_confirmation') }" class="form-control" type="password" name="password_confirmation">
|
||||
<has-error :form="form" field="password_confirmation"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9 ml-md-auto">
|
||||
<v-button :loading="form.busy">
|
||||
{{ $t('reset_password') }}
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
|
||||
export default {
|
||||
middleware: 'guest',
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('reset_password') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
status: '',
|
||||
form: new Form({
|
||||
token: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: ''
|
||||
})
|
||||
}),
|
||||
|
||||
created () {
|
||||
this.form.email = this.$route.query.email
|
||||
this.form.token = this.$route.params.token
|
||||
},
|
||||
|
||||
methods: {
|
||||
async reset () {
|
||||
const { data } = await this.form.post('/api/password/reset')
|
||||
|
||||
this.status = data.status
|
||||
|
||||
this.form.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-lg-8 m-auto">
|
||||
<card :title="$t('register')">
|
||||
<form @submit.prevent="register" @keydown="form.onKeydown($event)">
|
||||
<!-- Name -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('name') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.name" :class="{ 'is-invalid': form.errors.has('name') }" class="form-control" type="text" name="name">
|
||||
<has-error :form="form" field="name"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('email') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.email" :class="{ 'is-invalid': form.errors.has('email') }" class="form-control" type="email" name="email">
|
||||
<has-error :form="form" field="email"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('password') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.password" :class="{ 'is-invalid': form.errors.has('password') }" class="form-control" type="password" name="password">
|
||||
<has-error :form="form" field="password"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Confirmation -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('confirm_password') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.password_confirmation" :class="{ 'is-invalid': form.errors.has('password_confirmation') }" class="form-control" type="password" name="password_confirmation">
|
||||
<has-error :form="form" field="password_confirmation"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-7 offset-md-3 d-flex">
|
||||
<!-- Submit Button -->
|
||||
<v-button :loading="form.busy">
|
||||
{{ $t('register') }}
|
||||
</v-button>
|
||||
|
||||
<!-- GitHub Register Button -->
|
||||
<login-with-github/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import LoginWithGithub from '~/components/LoginWithGithub'
|
||||
|
||||
export default {
|
||||
middleware: 'guest',
|
||||
|
||||
components: {
|
||||
LoginWithGithub
|
||||
},
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('register') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: ''
|
||||
})
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async register () {
|
||||
// Register the user.
|
||||
const { data } = await this.form.post('/api/register')
|
||||
|
||||
// Log in the user.
|
||||
const { data: { token } } = await this.form.post('/api/login')
|
||||
|
||||
// Save the token.
|
||||
this.$store.dispatch('auth/saveToken', { token })
|
||||
|
||||
// Update the user.
|
||||
await this.$store.dispatch('auth/updateUser', { user: data })
|
||||
|
||||
// Redirect home.
|
||||
if(store.getters['auth/user'].role === 'admin'){
|
||||
this.$router.push({ name: 'admin.home' })
|
||||
}
|
||||
this.$router.push({ name: 'home' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<card class="text-center">
|
||||
<h3 class="mb-4">{{ $t('page_not_found') }}</h3>
|
||||
|
||||
<div class="links">
|
||||
<router-link :to="{ name: 'welcome' }">
|
||||
{{ $t('go_home') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NotFound'
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<card :title="$t('home')">
|
||||
{{ $t('you_are_logged_in') }}
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
middleware: ['auth', 'admin'],
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('home') }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<card :title="$t('settings')" class="settings-card">
|
||||
<ul class="nav flex-column nav-pills">
|
||||
<li v-for="tab in tabs" :key="tab.route" class="nav-item">
|
||||
<router-link :to="{ name: tab.route }" class="nav-link" active-class="active">
|
||||
<fa :icon="tab.icon" fixed-width/>
|
||||
{{ tab.name }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</card>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
middleware: 'auth',
|
||||
|
||||
computed: {
|
||||
tabs () {
|
||||
return [
|
||||
{
|
||||
icon: 'user',
|
||||
name: this.$t('profile'),
|
||||
route: 'settings.profile'
|
||||
},
|
||||
{
|
||||
icon: 'lock',
|
||||
name: this.$t('password'),
|
||||
route: 'settings.password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.settings-card .card-body {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<card :title="$t('your_password')">
|
||||
<form @submit.prevent="update" @keydown="form.onKeydown($event)">
|
||||
<alert-success :form="form" :message="$t('password_updated')"/>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('new_password') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.password" :class="{ 'is-invalid': form.errors.has('password') }" class="form-control" type="password" name="password">
|
||||
<has-error :form="form" field="password"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Confirmation -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('confirm_password') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.password_confirmation" :class="{ 'is-invalid': form.errors.has('password_confirmation') }" class="form-control" type="password" name="password_confirmation">
|
||||
<has-error :form="form" field="password_confirmation"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9 ml-md-auto">
|
||||
<v-button :loading="form.busy" type="success">{{ $t('update') }}</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
|
||||
export default {
|
||||
scrollToTop: false,
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('settings') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
password: '',
|
||||
password_confirmation: ''
|
||||
})
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async update () {
|
||||
await this.form.patch('/api/settings/password')
|
||||
|
||||
this.form.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<card :title="$t('your_info')">
|
||||
<form @submit.prevent="update" @keydown="form.onKeydown($event)">
|
||||
<alert-success :form="form" :message="$t('info_updated')"/>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('name') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.name" :class="{ 'is-invalid': form.errors.has('name') }" class="form-control" type="text" name="name">
|
||||
<has-error :form="form" field="name"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label text-md-right">{{ $t('email') }}</label>
|
||||
<div class="col-md-7">
|
||||
<input v-model="form.email" :class="{ 'is-invalid': form.errors.has('email') }" class="form-control" type="email" name="email">
|
||||
<has-error :form="form" field="email" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9 ml-md-auto">
|
||||
<v-button :loading="form.busy" type="success">{{ $t('update') }}</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
scrollToTop: false,
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('settings') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
name: '',
|
||||
email: ''
|
||||
})
|
||||
}),
|
||||
|
||||
computed: mapGetters({
|
||||
user: 'auth/user'
|
||||
}),
|
||||
|
||||
created () {
|
||||
// Fill the form with user data.
|
||||
this.form.keys().forEach(key => {
|
||||
this.form[key] = this.user[key]
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
async update () {
|
||||
const { data } = await this.form.patch('/api/settings/profile')
|
||||
|
||||
this.$store.dispatch('auth/updateUser', { user: data })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top-right links">
|
||||
<template v-if="authenticated">
|
||||
<router-link :to="{ name: 'home' }">
|
||||
{{ $t('home') }}
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link :to="{ name: 'login' }">
|
||||
{{ $t('login') }}
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'register' }">
|
||||
{{ $t('register') }}
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="title mb-4">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a href="https://laravel.com/docs">Documentation</a>
|
||||
<a href="https://laracasts.com">Laracasts</a>
|
||||
<a href="https://laravel-news.com">News</a>
|
||||
<a href="https://forge.laravel.com">Forge</a>
|
||||
<a href="https://github.com/laravel/laravel">GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
middleware: 'guest',
|
||||
layout: 'basic',
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('home') }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
title: window.config.appName
|
||||
}),
|
||||
|
||||
computed: mapGetters({
|
||||
authenticated: 'auth/check'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-right {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 85px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
import axios from 'axios'
|
||||
import store from '~/store'
|
||||
import router from '~/router'
|
||||
import swal from 'sweetalert2'
|
||||
import i18n from '~/plugins/i18n'
|
||||
|
||||
// Request interceptor
|
||||
axios.interceptors.request.use(request => {
|
||||
const token = store.getters['auth/token']
|
||||
if (token) {
|
||||
request.headers.common['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const locale = store.getters['lang/locale']
|
||||
if (locale) {
|
||||
request.headers.common['Accept-Language'] = locale
|
||||
}
|
||||
|
||||
// request.headers['X-Socket-Id'] = Echo.socketId()
|
||||
|
||||
return request
|
||||
})
|
||||
|
||||
// Response interceptor
|
||||
axios.interceptors.response.use(response => response, error => {
|
||||
const { status } = error.response
|
||||
|
||||
if (status >= 500) {
|
||||
swal({
|
||||
type: 'error',
|
||||
title: i18n.t('error_alert_title'),
|
||||
text: i18n.t('error_alert_text'),
|
||||
reverseButtons: true,
|
||||
confirmButtonText: i18n.t('ok'),
|
||||
cancelButtonText: i18n.t('cancel')
|
||||
})
|
||||
}
|
||||
|
||||
if (status === 401 && store.getters['auth/check']) {
|
||||
swal({
|
||||
type: 'warning',
|
||||
title: i18n.t('token_expired_alert_title'),
|
||||
text: i18n.t('token_expired_alert_text'),
|
||||
reverseButtons: true,
|
||||
confirmButtonText: i18n.t('ok'),
|
||||
cancelButtonText: i18n.t('cancel')
|
||||
}).then(async () => {
|
||||
await store.dispatch('auth/logout')
|
||||
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
import fontawesome from '@fortawesome/fontawesome'
|
||||
import FontAwesomeIcon from '@fortawesome/vue-fontawesome'
|
||||
|
||||
// import { } from '@fortawesome/fontawesome-free-regular/shakable.es'
|
||||
|
||||
import {
|
||||
faUser, faLock, faSignOutAlt, faCog
|
||||
} from '@fortawesome/fontawesome-free-solid/shakable.es'
|
||||
|
||||
import {
|
||||
faGithub
|
||||
} from '@fortawesome/fontawesome-free-brands/shakable.es'
|
||||
|
||||
fontawesome.library.add(
|
||||
faUser, faLock, faSignOutAlt, faCog, faGithub
|
||||
)
|
||||
|
||||
Vue.component('fa', FontAwesomeIcon)
|
||||
@@ -0,0 +1,30 @@
|
||||
import Vue from 'vue'
|
||||
import store from '~/store'
|
||||
import VueI18n from 'vue-i18n'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: 'en',
|
||||
messages: {}
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {String} locale
|
||||
*/
|
||||
export async function loadMessages (locale) {
|
||||
if (Object.keys(i18n.getLocaleMessage(locale)).length === 0) {
|
||||
const messages = await import(/* webpackChunkName: "lang-[request]" */ `~/lang/${locale}`)
|
||||
i18n.setLocaleMessage(locale, messages)
|
||||
}
|
||||
|
||||
if (i18n.locale !== locale) {
|
||||
i18n.locale = locale
|
||||
}
|
||||
}
|
||||
|
||||
;(async function () {
|
||||
await loadMessages(store.getters['lang/locale'])
|
||||
})()
|
||||
|
||||
export default i18n
|
||||
@@ -0,0 +1,3 @@
|
||||
import './axios'
|
||||
import './fontawesome'
|
||||
import 'bootstrap'
|
||||
@@ -0,0 +1,199 @@
|
||||
import Vue from 'vue'
|
||||
import store from '~/store'
|
||||
import Meta from 'vue-meta'
|
||||
import routes from './routes'
|
||||
import Router from 'vue-router'
|
||||
import { sync } from 'vuex-router-sync'
|
||||
|
||||
Vue.use(Meta)
|
||||
Vue.use(Router)
|
||||
|
||||
// The middleware for every page of the application.
|
||||
const globalMiddleware = ['locale', 'check-auth']
|
||||
|
||||
// Load middleware modules dynamically.
|
||||
const routeMiddleware = resolveMiddleware(
|
||||
require.context('~/middleware', false, /.*\.js$/)
|
||||
)
|
||||
|
||||
const router = createRouter()
|
||||
|
||||
sync(store, router)
|
||||
|
||||
export default router
|
||||
|
||||
/**
|
||||
* Create a new router instance.
|
||||
*
|
||||
* @return {Router}
|
||||
*/
|
||||
function createRouter () {
|
||||
const router = new Router({
|
||||
scrollBehavior,
|
||||
mode: 'history',
|
||||
routes
|
||||
})
|
||||
|
||||
router.beforeEach(beforeEach)
|
||||
router.afterEach(afterEach)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
/**
|
||||
* Global router guard.
|
||||
*
|
||||
* @param {Route} to
|
||||
* @param {Route} from
|
||||
* @param {Function} next
|
||||
*/
|
||||
async function beforeEach (to, from, next) {
|
||||
// Get the matched components and resolve them.
|
||||
const components = await resolveComponents(
|
||||
router.getMatchedComponents({ ...to })
|
||||
)
|
||||
|
||||
if (components.length === 0) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// Start the loading bar.
|
||||
if (components[components.length - 1].loading !== false) {
|
||||
router.app.$nextTick(() => router.app.$loading.start())
|
||||
}
|
||||
|
||||
// Get the middleware for all the matched components.
|
||||
const middleware = getMiddleware(components)
|
||||
|
||||
// Call each middleware.
|
||||
callMiddleware(middleware, to, from, (...args) => {
|
||||
// Set the application layout only if "next()" was called with no args.
|
||||
if (args.length === 0) {
|
||||
router.app.setLayout(components[0].layout || '')
|
||||
}
|
||||
|
||||
next(...args)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Global after hook.
|
||||
*
|
||||
* @param {Route} to
|
||||
* @param {Route} from
|
||||
* @param {Function} next
|
||||
*/
|
||||
async function afterEach (to, from, next) {
|
||||
await router.app.$nextTick()
|
||||
|
||||
router.app.$loading.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Call each middleware.
|
||||
*
|
||||
* @param {Array} middleware
|
||||
* @param {Route} to
|
||||
* @param {Route} from
|
||||
* @param {Function} next
|
||||
*/
|
||||
function callMiddleware (middleware, to, from, next) {
|
||||
const stack = middleware.reverse()
|
||||
|
||||
const _next = (...args) => {
|
||||
// Stop if "_next" was called with an argument or the stack is empty.
|
||||
if (args.length > 0 || stack.length === 0) {
|
||||
if (args.length > 0) {
|
||||
router.app.$loading.finish()
|
||||
}
|
||||
|
||||
return next(...args)
|
||||
}
|
||||
|
||||
const middleware = stack.pop()
|
||||
|
||||
if (typeof middleware === 'function') {
|
||||
middleware(to, from, _next)
|
||||
} else if (routeMiddleware[middleware]) {
|
||||
routeMiddleware[middleware](to, from, _next)
|
||||
} else {
|
||||
throw Error(`Undefined middleware [${middleware}]`)
|
||||
}
|
||||
}
|
||||
|
||||
_next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve async components.
|
||||
*
|
||||
* @param {Array} components
|
||||
* @return {Array}
|
||||
*/
|
||||
function resolveComponents (components) {
|
||||
return Promise.all(components.map(component => {
|
||||
return typeof component === 'function' ? component() : component
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the the global middleware with the components middleware.
|
||||
*
|
||||
* @param {Array} components
|
||||
* @return {Array}
|
||||
*/
|
||||
function getMiddleware (components) {
|
||||
const middleware = [...globalMiddleware]
|
||||
|
||||
components.filter(c => c.middleware).forEach(component => {
|
||||
if (Array.isArray(component.middleware)) {
|
||||
middleware.push(...component.middleware)
|
||||
} else {
|
||||
middleware.push(component.middleware)
|
||||
}
|
||||
})
|
||||
|
||||
return middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll Behavior
|
||||
*
|
||||
* @link https://router.vuejs.org/en/advanced/scroll-behavior.html
|
||||
*
|
||||
* @param {Route} to
|
||||
* @param {Route} from
|
||||
* @param {Object|undefined} savedPosition
|
||||
* @return {Object}
|
||||
*/
|
||||
function scrollBehavior (to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
}
|
||||
|
||||
if (to.hash) {
|
||||
return { selector: to.hash }
|
||||
}
|
||||
|
||||
const [component] = router.getMatchedComponents({ ...to }).slice(-1)
|
||||
|
||||
if (component && component.scrollToTop === false) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} requireContext
|
||||
* @return {Object}
|
||||
*/
|
||||
function resolveMiddleware (requireContext) {
|
||||
return requireContext.keys()
|
||||
.map(file =>
|
||||
[file.replace(/(^.\/)|(\.js$)/g, ''), requireContext(file)]
|
||||
)
|
||||
.reduce((guards, [name, guard]) => (
|
||||
{ ...guards, [name]: guard.default }
|
||||
), {})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
const Welcome = () => import('~/pages/welcome').then(m => m.default || m)
|
||||
const Login = () => import('~/pages/auth/login').then(m => m.default || m)
|
||||
const Register = () => import('~/pages/auth/register').then(m => m.default || m)
|
||||
const PasswordEmail = () => import('~/pages/auth/password/email').then(m => m.default || m)
|
||||
const PasswordReset = () => import('~/pages/auth/password/reset').then(m => m.default || m)
|
||||
const NotFound = () => import('~/pages/errors/404').then(m => m.default || m)
|
||||
|
||||
const Home = () => import('~/pages/home').then(m => m.default || m)
|
||||
const Settings = () => import('~/pages/settings/index').then(m => m.default || m)
|
||||
const SettingsProfile = () => import('~/pages/settings/profile').then(m => m.default || m)
|
||||
const SettingsPassword = () => import('~/pages/settings/password').then(m => m.default || m)
|
||||
|
||||
const AdminHome = () => import('~/pages/admin/home').then(m => m.default || m)
|
||||
|
||||
export default [
|
||||
{ path: '/', name: 'welcome', component: Welcome },
|
||||
|
||||
{ path: '/login', name: 'login', component: Login },
|
||||
{ path: '/register', name: 'register', component: Register },
|
||||
{ path: '/password/reset', name: 'password.request', component: PasswordEmail },
|
||||
{ path: '/password/reset/:token', name: 'password.reset', component: PasswordReset },
|
||||
|
||||
{ path: '/home', name: 'home', component: Home },
|
||||
{ path: '/settings',
|
||||
component: Settings,
|
||||
children: [
|
||||
{ path: '', redirect: { name: 'settings.profile' } },
|
||||
{ path: 'profile', name: 'settings.profile', component: SettingsProfile },
|
||||
{ path: 'password', name: 'settings.password', component: SettingsPassword }
|
||||
] },
|
||||
|
||||
{ path: '/admin', name: 'admin.home', component: AdminHome },
|
||||
|
||||
{ path: '*', component: NotFound }
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
// Load store modules dynamically.
|
||||
const requireContext = require.context('./modules', false, /.*\.js$/)
|
||||
|
||||
const modules = requireContext.keys()
|
||||
.map(file =>
|
||||
[file.replace(/(^.\/)|(\.js$)/g, ''), requireContext(file)]
|
||||
)
|
||||
.reduce((modules, [name, module]) => {
|
||||
if (module.namespaced === undefined) {
|
||||
module.namespaced = true
|
||||
}
|
||||
|
||||
return { ...modules, [name]: module }
|
||||
}, {})
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
import axios from 'axios'
|
||||
import Cookies from 'js-cookie'
|
||||
import * as types from '../mutation-types'
|
||||
|
||||
// state
|
||||
export const state = {
|
||||
user: null,
|
||||
token: Cookies.get('token')
|
||||
}
|
||||
|
||||
// getters
|
||||
export const getters = {
|
||||
user: state => state.user,
|
||||
token: state => state.token,
|
||||
check: state => state.user !== null
|
||||
}
|
||||
|
||||
// mutations
|
||||
export const mutations = {
|
||||
[types.SAVE_TOKEN] (state, { token, remember }) {
|
||||
state.token = token
|
||||
Cookies.set('token', token, { expires: remember ? 365 : null })
|
||||
},
|
||||
|
||||
[types.FETCH_USER_SUCCESS] (state, { user }) {
|
||||
state.user = user
|
||||
},
|
||||
|
||||
[types.FETCH_USER_FAILURE] (state) {
|
||||
state.token = null
|
||||
Cookies.remove('token')
|
||||
},
|
||||
|
||||
[types.LOGOUT] (state) {
|
||||
state.user = null
|
||||
state.token = null
|
||||
|
||||
Cookies.remove('token')
|
||||
},
|
||||
|
||||
[types.UPDATE_USER] (state, { user }) {
|
||||
state.user = user
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
export const actions = {
|
||||
saveToken ({ commit, dispatch }, payload) {
|
||||
commit(types.SAVE_TOKEN, payload)
|
||||
},
|
||||
|
||||
async fetchUser ({ commit }) {
|
||||
try {
|
||||
const { data } = await axios.get('/api/user')
|
||||
|
||||
commit(types.FETCH_USER_SUCCESS, { user: data })
|
||||
} catch (e) {
|
||||
commit(types.FETCH_USER_FAILURE)
|
||||
}
|
||||
},
|
||||
|
||||
updateUser ({ commit }, payload) {
|
||||
commit(types.UPDATE_USER, payload)
|
||||
},
|
||||
|
||||
async logout ({ commit }) {
|
||||
try {
|
||||
await axios.post('/api/logout')
|
||||
} catch (e) { }
|
||||
|
||||
commit(types.LOGOUT)
|
||||
},
|
||||
|
||||
async fetchOauthUrl (ctx, { provider }) {
|
||||
const { data } = await axios.post(`/api/oauth/${provider}`)
|
||||
|
||||
return data.url
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import * as types from '../mutation-types'
|
||||
|
||||
const { locale, locales } = window.config
|
||||
|
||||
// state
|
||||
export const state = {
|
||||
locale: Cookies.get('locale') || locale,
|
||||
locales: locales
|
||||
}
|
||||
|
||||
// getters
|
||||
export const getters = {
|
||||
locale: state => state.locale,
|
||||
locales: state => state.locales
|
||||
}
|
||||
|
||||
// mutations
|
||||
export const mutations = {
|
||||
[types.SET_LOCALE] (state, { locale }) {
|
||||
state.locale = locale
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
export const actions = {
|
||||
setLocale ({ commit }, { locale }) {
|
||||
commit(types.SET_LOCALE, { locale })
|
||||
|
||||
Cookies.set('locale', locale, { expires: 365 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// auth.js
|
||||
export const LOGOUT = 'LOGOUT'
|
||||
export const SAVE_TOKEN = 'SAVE_TOKEN'
|
||||
export const FETCH_USER = 'FETCH_USER'
|
||||
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
|
||||
export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE'
|
||||
export const UPDATE_USER = 'UPDATE_USER'
|
||||
|
||||
// lang.js
|
||||
export const SET_LOCALE = 'SET_LOCALE'
|
||||
@@ -1,8 +1,16 @@
|
||||
// // Body
|
||||
$body-bg: #f7f9fb;
|
||||
|
||||
// Body
|
||||
$body-bg: #f5f8fa;
|
||||
// Cards
|
||||
$card-spacer-x: 0.9375rem;
|
||||
$card-spacer-y: 0.625rem;
|
||||
$card-cap-bg: #fbfbfb;
|
||||
$card-border-color: #e8eced;
|
||||
|
||||
// Typography
|
||||
$font-family-sans-serif: "Raleway", sans-serif;
|
||||
$font-size-base: 0.9rem;
|
||||
$line-height-base: 1.6;
|
||||
// Borders
|
||||
$border-radius: .125rem;
|
||||
$border-radius-lg: .2rem;
|
||||
$border-radius-sm: .15rem;
|
||||
|
||||
// Nav Pills
|
||||
$nav-pills-border-radius: 0;
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
|
||||
// Fonts
|
||||
@import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600");
|
||||
|
||||
// Variables
|
||||
@import "variables";
|
||||
|
||||
// Bootstrap
|
||||
@import 'variables';
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
@import '~sweetalert2/src/sweetalert2';
|
||||
|
||||
.navbar-laravel {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
@import 'elements/card';
|
||||
@import 'elements/navbar';
|
||||
@import 'elements/buttons';
|
||||
@import 'elements/transitions';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.btn-loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
color: transparent !important;
|
||||
|
||||
&:after {
|
||||
animation: spinAround 500ms infinite linear;
|
||||
border: 2px solid #dbdbdb;
|
||||
border-radius: 50%;
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
position: absolute;
|
||||
left: calc(50% - (1em / 2));
|
||||
top: calc(50% - (1em / 2));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spinAround {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(359deg); }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.card {
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.navbar {
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
.dropdown-menu {
|
||||
border: none;
|
||||
margin-top: .5rem;
|
||||
border-top: 1px solid #f2f2f2 !important;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
.svg-inline--fa {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.page-enter-active,
|
||||
.page-leave-active {
|
||||
transition: opacity .2s;
|
||||
}
|
||||
.page-enter,
|
||||
.page-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .15s
|
||||
}
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0
|
||||
}
|
||||
@@ -65,7 +65,6 @@ return [
|
||||
'array' => 'The :attribute must have at least :min items.',
|
||||
],
|
||||
'not_in' => 'The selected :attribute is invalid.',
|
||||
'not_regex' => 'The :attribute format is invalid.',
|
||||
'numeric' => 'The :attribute must be a number.',
|
||||
'present' => 'The :attribute field must be present.',
|
||||
'regex' => 'The :attribute format is invalid.',
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => 'Estas credenciales no coinciden con nuestros registros.',
|
||||
'throttle' => 'Demasiados intentos de acceso. Por favor intente nuevamente en :seconds segundos.',
|
||||
|
||||
];
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'previous' => '« Anterior',
|
||||
'next' => 'Siguiente »',
|
||||
|
||||
];
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reminder Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
|
||||
'password' => 'Las contraseñas deben coincidir y contener al menos 6 caracteres',
|
||||
'reset' => '¡Tu contraseña ha sido restablecida!',
|
||||
'sent' => '¡Te hemos enviado por correo el enlace para restablecer tu contraseña!',
|
||||
'token' => 'El token de recuperación de contraseña es inválido.',
|
||||
'user' => 'No podemos encontrar ningún usuario con ese correo electrónico.',
|
||||
|
||||
];
|
||||
Executable
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages.
|
||||
|
|
||||
*/
|
||||
|
||||
'accepted' => ':attribute debe ser aceptado.',
|
||||
'active_url' => ':attribute no es una URL válida.',
|
||||
'after' => ':attribute debe ser una fecha posterior a :date.',
|
||||
'after_or_equal' => ':attribute debe ser una fecha posterior o igual a :date.',
|
||||
'alpha' => ':attribute sólo debe contener letras.',
|
||||
'alpha_dash' => ':attribute sólo debe contener letras, números y guiones.',
|
||||
'alpha_num' => ':attribute sólo debe contener letras y números.',
|
||||
'array' => ':attribute debe ser un conjunto.',
|
||||
'before' => ':attribute debe ser una fecha anterior a :date.',
|
||||
'before_or_equal' => ':attribute debe ser una fecha anterior o igual a :date.',
|
||||
'between' => [
|
||||
'numeric' => ':attribute tiene que estar entre :min - :max.',
|
||||
'file' => ':attribute debe pesar entre :min - :max kilobytes.',
|
||||
'string' => ':attribute tiene que tener entre :min - :max caracteres.',
|
||||
'array' => ':attribute tiene que tener entre :min - :max ítems.',
|
||||
],
|
||||
'boolean' => 'El campo :attribute debe tener un valor verdadero o falso.',
|
||||
'confirmed' => 'La confirmación de :attribute no coincide.',
|
||||
'date' => ':attribute no es una fecha válida.',
|
||||
'date_format' => ':attribute no corresponde al formato :format.',
|
||||
'different' => ':attribute y :other deben ser diferentes.',
|
||||
'digits' => ':attribute debe tener :digits dígitos.',
|
||||
'digits_between' => ':attribute debe tener entre :min y :max dígitos.',
|
||||
'dimensions' => 'Las dimensiones de la imagen :attribute no son válidas.',
|
||||
'distinct' => 'El campo :attribute contiene un valor duplicado.',
|
||||
'email' => ':attribute no es un correo válido',
|
||||
'exists' => ':attribute es inválido.',
|
||||
'file' => 'El campo :attribute debe ser un archivo.',
|
||||
'filled' => 'El campo :attribute es obligatorio.',
|
||||
'image' => ':attribute debe ser una imagen.',
|
||||
'in' => ':attribute es inválido.',
|
||||
'in_array' => 'El campo :attribute no existe en :other.',
|
||||
'integer' => ':attribute debe ser un número entero.',
|
||||
'ip' => ':attribute debe ser una dirección IP válida.',
|
||||
'ipv4' => ':attribute debe ser un dirección IPv4 válida',
|
||||
'ipv6' => ':attribute debe ser un dirección IPv6 válida.',
|
||||
'json' => 'El campo :attribute debe tener una cadena JSON válida.',
|
||||
'max' => [
|
||||
'numeric' => ':attribute no debe ser mayor a :max.',
|
||||
'file' => ':attribute no debe ser mayor que :max kilobytes.',
|
||||
'string' => ':attribute no debe ser mayor que :max caracteres.',
|
||||
'array' => ':attribute no debe tener más de :max elementos.',
|
||||
],
|
||||
'mimes' => ':attribute debe ser un archivo con formato: :values.',
|
||||
'mimetypes' => ':attribute debe ser un archivo con formato: :values.',
|
||||
'min' => [
|
||||
'numeric' => 'El tamaño de :attribute debe ser de al menos :min.',
|
||||
'file' => 'El tamaño de :attribute debe ser de al menos :min kilobytes.',
|
||||
'string' => ':attribute debe contener al menos :min caracteres.',
|
||||
'array' => ':attribute debe tener al menos :min elementos.',
|
||||
],
|
||||
'not_in' => ':attribute es inválido.',
|
||||
'numeric' => ':attribute debe ser numérico.',
|
||||
'present' => 'El campo :attribute debe estar presente.',
|
||||
'regex' => 'El formato de :attribute es inválido.',
|
||||
'required' => 'El campo :attribute es obligatorio.',
|
||||
'required_if' => 'El campo :attribute es obligatorio cuando :other es :value.',
|
||||
'required_unless' => 'El campo :attribute es obligatorio a menos que :other esté en :values.',
|
||||
'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.',
|
||||
'required_with_all' => 'El campo :attribute es obligatorio cuando :values está presente.',
|
||||
'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.',
|
||||
'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de :values estén presentes.',
|
||||
'same' => ':attribute y :other deben coincidir.',
|
||||
'size' => [
|
||||
'numeric' => 'El tamaño de :attribute debe ser :size.',
|
||||
'file' => 'El tamaño de :attribute debe ser :size kilobytes.',
|
||||
'string' => ':attribute debe contener :size caracteres.',
|
||||
'array' => ':attribute debe contener :size elementos.',
|
||||
],
|
||||
'string' => 'El campo :attribute debe ser una cadena de caracteres.',
|
||||
'timezone' => 'El :attribute debe ser una zona válida.',
|
||||
'unique' => ':attribute ya ha sido registrado.',
|
||||
'uploaded' => 'Subir :attribute ha fallado.',
|
||||
'url' => 'El formato :attribute es inválido.',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
|
||||
'custom' => [
|
||||
'password' => [
|
||||
'min' => 'La :attribute debe contener más de :min caracteres',
|
||||
],
|
||||
'email' => [
|
||||
'unique' => 'El :attribute ya ha sido registrado.',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap attribute place-holders
|
||||
| with something more reader friendly such as E-Mail Address instead
|
||||
| of "email". This simply helps us make messages a little cleaner.
|
||||
|
|
||||
*/
|
||||
|
||||
'attributes' => [
|
||||
'name' => 'nombre',
|
||||
'username' => 'usuario',
|
||||
'email' => 'correo electrónico',
|
||||
'first_name' => 'nombre',
|
||||
'last_name' => 'apellido',
|
||||
'password' => 'contraseña',
|
||||
'password_confirmation' => 'confirmación de la contraseña',
|
||||
'city' => 'ciudad',
|
||||
'country' => 'país',
|
||||
'address' => 'dirección',
|
||||
'phone' => 'teléfono',
|
||||
'mobile' => 'móvil',
|
||||
'age' => 'edad',
|
||||
'sex' => 'sexo',
|
||||
'gender' => 'género',
|
||||
'year' => 'año',
|
||||
'month' => 'mes',
|
||||
'day' => 'día',
|
||||
'hour' => 'hora',
|
||||
'minute' => 'minuto',
|
||||
'second' => 'segundo',
|
||||
'title' => 'título',
|
||||
'content' => 'contenido',
|
||||
'body' => 'contenido',
|
||||
'description' => 'descripción',
|
||||
'excerpt' => 'extracto',
|
||||
'date' => 'fecha',
|
||||
'time' => 'hora',
|
||||
'subject' => 'asunto',
|
||||
'message' => 'mensaje',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
'failed' => '用户名或手机号与密码不匹配或用户被禁用',
|
||||
'throttle' => '失败次数太多,请在:seconds秒后再尝试',
|
||||
];
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
'previous' => '« 上一页',
|
||||
'next' => '下一页 »',
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
'password' => '密码长度至少包含6个字符并且两次输入密码要一致',
|
||||
'reset' => '密码已经被重置!',
|
||||
'sent' => '我们已经发送密码重置链接到您的邮箱',
|
||||
'token' => '密码重置令牌无效',
|
||||
'user' => '抱歉,该邮箱对应的用户不存在!',
|
||||
];
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages here.
|
||||
|
|
||||
*/
|
||||
'unique' => ':attribute 已存在',
|
||||
'accepted' => ':attribute 是被接受的',
|
||||
'active_url' => ':attribute 必须是一个合法的 URL',
|
||||
'after' => ':attribute 必须是 :date 之后的一个日期',
|
||||
'alpha' => ':attribute 必须全部由字母字符构成。',
|
||||
'alpha_dash' => ':attribute 必须全部由字母、数字、中划线或下划线字符构成',
|
||||
'alpha_num' => ':attribute 必须全部由字母和数字构成',
|
||||
'array' => ':attribute 必须是个数组',
|
||||
'before' => ':attribute 必须是 :date 之前的一个日期',
|
||||
'between' => [
|
||||
'numeric' => ':attribute 必须在 :min 到 :max 之间',
|
||||
'file' => ':attribute 必须在 :min 到 :max KB之间',
|
||||
'string' => ':attribute 必须在 :min 到 :max 个字符之间',
|
||||
'array' => ':attribute 必须在 :min 到 :max 项之间',
|
||||
],
|
||||
'boolean' => ':attribute 字符必须是 true 或 false',
|
||||
'confirmed' => ':attribute 两次确认不匹配',
|
||||
'date' => ':attribute 必须是一个合法的日期',
|
||||
'date_format' => ':attribute 与给定的格式 :format 不符合',
|
||||
'different' => ':attribute 必须不同于:other',
|
||||
'digits' => ':attribute 必须是 :digits 位',
|
||||
'digits_between' => ':attribute 必须在 :min and :max 位之间',
|
||||
'email' => ':attribute 必须是一个合法的电子邮件地址。',
|
||||
'filled' => ':attribute 的字段是必填的',
|
||||
'exists' => '选定的 :attribute 是无效的',
|
||||
'image' => ':attribute 必须是一个图片 (jpeg, png, bmp 或者 gif)',
|
||||
'in' => '选定的 :attribute 是无效的',
|
||||
'integer' => ':attribute 必须是个整数',
|
||||
'ip' => ':attribute 必须是一个合法的 IP 地址。',
|
||||
'max' => [
|
||||
'numeric' => ':attribute 的最大长度为 :max 位',
|
||||
'file' => ':attribute 的最大为 :max',
|
||||
'string' => ':attribute 的最大长度为 :max 字符',
|
||||
'array' => ':attribute 的最大个数为 :max 个',
|
||||
],
|
||||
'mimes' => ':attribute 的文件类型必须是:values',
|
||||
'mimetypes' => ':attribute 的文件类型必须是: :values.',
|
||||
'min' => [
|
||||
'numeric' => ':attribute 的最小长度为 :min 位',
|
||||
'string' => ':attribute 的最小长度为 :min 字符',
|
||||
'file' => ':attribute 大小至少为:min KB',
|
||||
'array' => ':attribute 至少有 :min 项',
|
||||
],
|
||||
'not_in' => '选定的 :attribute 是无效的',
|
||||
'numeric' => ':attribute 必须是数字',
|
||||
'regex' => ':attribute 格式是无效的',
|
||||
'required' => ':attribute 字段必须填写',
|
||||
'required_if' => ':attribute 字段是必须的当 :other 是 :value',
|
||||
'required_with' => ':attribute 字段是必须的当 :values 是存在的',
|
||||
'required_with_all' => ':attribute 字段是必须的当 :values 是存在的',
|
||||
'required_without' => ':attribute 字段是必须的当 :values 是不存在的',
|
||||
'required_without_all' => ':attribute 字段是必须的当 没有一个 :values 是存在的',
|
||||
'same' => ':attribute 和 :other 必须匹配',
|
||||
'size' => [
|
||||
'numeric' => ':attribute 必须是 :size 位',
|
||||
'file' => ':attribute 必须是 :size KB',
|
||||
'string' => ':attribute 必须是 :size 个字符',
|
||||
'array' => ':attribute 必须包括 :size 项',
|
||||
],
|
||||
'url' => ':attribute 无效的格式',
|
||||
'timezone' => ':attribute 必须个有效的时区',
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
'custom' => [],
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap attribute place-holders
|
||||
| with something more reader friendly such as E-Mail Address instead
|
||||
| of "email". This simply helps us make messages a little cleaner.
|
||||
|
|
||||
*/
|
||||
'attributes' => [],
|
||||
];
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div>
|
||||
Hi {{ $name }},
|
||||
<br>
|
||||
Thank you for creating an account with us. Don't forget to complete your registration!
|
||||
<br>
|
||||
Please click on the link below or copy it into the address bar of your browser to confirm your email address:
|
||||
<br>
|
||||
|
||||
<a href="{{ url('user/verify', $verification_code)}}">Confirm my email address </a>
|
||||
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,58 @@
|
||||
{{-- Illuminate/Foundation/Exceptions/views --}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>@yield('title')</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
html, body {
|
||||
background-color: #fff;
|
||||
color: #636b6f;
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-weight: 100;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.position-ref {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36px;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex-center position-ref full-height">
|
||||
<div class="content">
|
||||
<div class="title">
|
||||
@yield('message')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
@php
|
||||
$config = [
|
||||
'appName' => config('app.name'),
|
||||
'locale' => $locale = app()->getLocale(),
|
||||
'locales' => config('app.locales'),
|
||||
'githubAuth' => config('services.github.client_id'),
|
||||
];
|
||||
|
||||
$polyfills = [
|
||||
'Promise',
|
||||
'Object.assign',
|
||||
'Object.values',
|
||||
'Array.prototype.find',
|
||||
'Array.prototype.findIndex',
|
||||
'Array.prototype.includes',
|
||||
'String.prototype.includes',
|
||||
'String.prototype.startsWith',
|
||||
'String.prototype.endsWith',
|
||||
];
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ app()->getLocale() }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>{{ config('app.name') }}</title>
|
||||
|
||||
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
{{-- Global configuration object --}}
|
||||
<script>window.config = @json($config);</script>
|
||||
|
||||
{{-- Polyfill JS features via polyfill.io --}}
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features={{ implode(',', $polyfills) }}"></script>
|
||||
|
||||
{{-- Load the application scripts --}}
|
||||
@if (app()->isLocal())
|
||||
<script src="{{ mix('js/app.js') }}"></script>
|
||||
@else
|
||||
<script src="{{ mix('js/manifest.js') }}"></script>
|
||||
<script src="{{ mix('js/vendor.js') }}"></script>
|
||||
<script src="{{ mix('js/app.js') }}"></script>
|
||||
@endif
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ config('app.name') }}</title>
|
||||
<script>
|
||||
window.opener.postMessage({ token: "{{ $token }}" }, "{{ url('/') }}")
|
||||
window.close()
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
@extends('errors.layout')
|
||||
|
||||
@section('title', 'Login Error')
|
||||
|
||||
@section('message', 'Email already taken.')
|
||||
@@ -1,95 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ app()->getLocale() }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Laravel</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
html, body {
|
||||
background-color: #fff;
|
||||
color: #636b6f;
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-weight: 100;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.position-ref {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 84px;
|
||||
}
|
||||
|
||||
.links > a {
|
||||
color: #636b6f;
|
||||
padding: 0 25px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: .1rem;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.m-b-md {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex-center position-ref full-height">
|
||||
@if (Route::has('login'))
|
||||
<div class="top-right links">
|
||||
@auth
|
||||
<a href="{{ url('/home') }}">Home</a>
|
||||
@else
|
||||
<a href="{{ route('login') }}">Login</a>
|
||||
<a href="{{ route('register') }}">Register</a>
|
||||
@endauth
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="content">
|
||||
<div class="title m-b-md">
|
||||
Laravel
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a href="https://laravel.com/docs">Documentation</a>
|
||||
<a href="https://laracasts.com">Laracasts</a>
|
||||
<a href="https://laravel-news.com">News</a>
|
||||
<a href="https://forge.laravel.com">Forge</a>
|
||||
<a href="https://github.com/laravel/laravel">GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user