├── .env ├── babel.config.js ├── .postcssrc.js ├── public ├── favicon.ico ├── img │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── msapplication-icon-144x144.png │ │ └── safari-pinned-tab.svg ├── manifest.json └── index.html ├── src ├── assets │ └── logo.png ├── views │ ├── About.vue │ ├── Logout.vue │ ├── Login.vue │ ├── Signup.vue │ ├── LostPassword.vue │ ├── ResetPassword.vue │ ├── PageNotFound.vue │ ├── ProfileSecurity.vue │ ├── Profile.vue │ ├── ProfileEdit.vue │ └── Home.vue ├── mixins │ ├── formError.js │ └── loading.js ├── registerServiceWorker.js ├── main.js ├── components │ ├── PasswordLostForm.vue │ ├── Dropdown.vue │ ├── LoginForm.vue │ ├── PasswordForm.vue │ ├── PasswordResetForm.vue │ ├── ProfileForm.vue │ └── SignupForm.vue ├── store.js ├── App.vue └── router.js ├── .editorconfig ├── .gitignore ├── .eslintrc.js ├── package.json └── readme.md /.env: -------------------------------------------------------------------------------- 1 | VUE_APP_API_ENDPOINT=http://example.com 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareq1988/vue-lumen-starter/HEAD/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /src/views/Logout.vue: -------------------------------------------------------------------------------- 1 | 4 | 19 | -------------------------------------------------------------------------------- /src/mixins/formError.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | errors: {} 5 | } 6 | }, 7 | 8 | methods: { 9 | hasError(field) { 10 | return this.errors && this.errors.hasOwnProperty(field) 11 | }, 12 | 13 | getError(field) { 14 | if (this.hasError(field)) { 15 | return this.errors[field][0] 16 | } 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/mixins/loading.js: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import 'nprogress/nprogress.css' 3 | 4 | export default { 5 | watch: { 6 | loading: function(value) { 7 | if ( value ) { 8 | NProgress.configure({ 9 | showSpinner: false 10 | }); 11 | NProgress.start(); 12 | } else { 13 | NProgress.done(); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /src/views/Signup.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /src/views/LostPassword.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /src/views/ResetPassword.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "short_name": "auth", 4 | "icons": [ 5 | { 6 | "src": "/img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /src/views/PageNotFound.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /src/views/ProfileSecurity.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue Auth 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready () { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | cached () { 14 | console.log('Content has been cached for offline use.') 15 | }, 16 | updated () { 17 | console.log('New content is available; please refresh.') 18 | }, 19 | offline () { 20 | console.log('No internet connection found. App is running in offline mode.') 21 | }, 22 | error (error) { 23 | console.error('Error during service worker registration:', error) 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.0", 12 | "bootstrap": "^4.1.1", 13 | "nprogress": "^0.2.0", 14 | "register-service-worker": "^1.0.0", 15 | "vue": "^2.5.16", 16 | "vue-axios": "^2.1.1", 17 | "vue-gravatar": "^1.2.1", 18 | "vue-router": "^3.0.1", 19 | "vuex": "^3.0.1" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^3.0.0-beta.15", 23 | "@vue/cli-plugin-eslint": "^3.0.0-beta.15", 24 | "@vue/cli-plugin-pwa": "^3.0.0-beta.15", 25 | "@vue/cli-service": "^3.0.0-beta.15", 26 | "less": "^3.0.4", 27 | "less-loader": "^4.1.0", 28 | "vue-template-compiler": "^2.5.16" 29 | }, 30 | "browserslist": [ 31 | "> 1%", 32 | "last 2 versions", 33 | "not ie <= 8" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Vue Lumen Starter 2 | 3 | A Vue frontend app, powered by REST API with [Lumen](https://lumen.laravel.com/) framework. 4 | 5 | Using with the [Lumen Starter](https://github.com/tareq1988/lumen-starter) app, Login/Registration/Profile updating is implemented for rapid frontend app development. 6 | 7 | ## Inside 8 | 9 | * Vue 10 | * Vue Router 11 | * Vuex 12 | * Axios 13 | * Vue Gravatar 14 | * Bootstrap 15 | 16 | ## Functionalities 17 | 18 | * Login / Logout 19 | * Signup 20 | * Profile Updating 21 | * Change Password 22 | 23 | ## Installation 24 | 25 | Clone the repository and create a `.env` file by copying the `.env.local` file. 26 | 27 | Set the REST API endpoint variable (`VUE_APP_API_ENDPOINT`) to your [Lumen Starter](https://github.com/tareq1988/lumen-starter) installation. 28 | 29 | Then... 30 | 31 | ```js 32 | npm install 33 | npm run serve 34 | 35 | npm run build # to build the distribution 36 | ``` 37 | 38 | ## Credits 39 | 40 | [Tareq Hasan](https://tareq.co) 41 | -------------------------------------------------------------------------------- /src/views/Profile.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import VueAxios from 'vue-axios' 5 | import axios from 'axios'; 6 | import store from './store' 7 | import Gravatar from 'vue-gravatar'; 8 | import './registerServiceWorker' 9 | 10 | Vue.use(VueAxios, axios) 11 | Vue.component('v-gravatar', Gravatar) 12 | 13 | Vue.config.productionTip = false 14 | 15 | Vue.prototype.$api = function(endpoint) { 16 | return process.env.VUE_APP_API_ENDPOINT + endpoint 17 | } 18 | 19 | // set auth token if presen 20 | const token = localStorage.getItem('token') 21 | if (token) { 22 | axios.defaults.headers.common['Authorization'] = 'Bearer ' + token 23 | } 24 | 25 | new Vue({ 26 | router, 27 | store, 28 | render: h => h(App), 29 | 30 | created() { 31 | this.$http.interceptors.response.use(undefined, function (err) { 32 | if (err.response.status === 401) { 33 | router.push('/login') 34 | } 35 | 36 | return Promise.reject(err) 37 | }) 38 | } 39 | }).$mount('#app') 40 | -------------------------------------------------------------------------------- /src/components/PasswordLostForm.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /src/views/ProfileEdit.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 56 | 57 | 59 | -------------------------------------------------------------------------------- /src/components/Dropdown.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 61 | 62 | 65 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | 8 | state: { 9 | isAuthenticated: !!localStorage.getItem('token'), 10 | }, 11 | 12 | getters: { 13 | isAuthenticated (state) { 14 | return state.isAuthenticated; 15 | }, 16 | 17 | user(state) { 18 | if (!state.isAuthenticated) { 19 | return false; 20 | } 21 | 22 | return JSON.parse(localStorage.getItem('user')); 23 | } 24 | }, 25 | 26 | mutations: { 27 | authenticate (state, payload) { 28 | state.isAuthenticated = true; 29 | 30 | localStorage.setItem('token', payload.api_token); 31 | localStorage.setItem('user', JSON.stringify({ 32 | 'id': payload.id, 33 | 'first_name': payload.first_name, 34 | 'last_name': payload.last_name, 35 | 'email': payload.email, 36 | })); 37 | }, 38 | 39 | updateUser (state, payload) { 40 | localStorage.setItem('user', JSON.stringify({ 41 | 'id': payload.id, 42 | 'first_name': payload.first_name, 43 | 'last_name': payload.last_name, 44 | 'email': payload.email, 45 | })); 46 | }, 47 | 48 | logout(state) { 49 | state.isAuthenticated = false; 50 | localStorage.removeItem('token'); 51 | localStorage.removeItem('user'); 52 | } 53 | }, 54 | 55 | actions: { 56 | 57 | login (context, payload) { 58 | context.commit('authenticate', payload); 59 | }, 60 | 61 | updateUser (context, payload) { 62 | context.commit('updateUser', payload); 63 | }, 64 | 65 | logout (context) { 66 | context.commit('logout'); 67 | }, 68 | } 69 | }) 70 | -------------------------------------------------------------------------------- /src/components/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 60 | 61 | 64 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 41 | 42 | 52 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 66 | 67 | 70 | -------------------------------------------------------------------------------- /src/components/PasswordForm.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 75 | 76 | 78 | -------------------------------------------------------------------------------- /src/components/PasswordResetForm.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 81 | 82 | 85 | -------------------------------------------------------------------------------- /src/components/ProfileForm.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 79 | 80 | 88 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import store from './store' 4 | import Home from './views/Home.vue' 5 | import About from './views/About.vue' 6 | import Profile from './views/Profile.vue' 7 | import ProfileEdit from './views/ProfileEdit.vue' 8 | import ProfileSecurity from './views/ProfileSecurity.vue' 9 | import Login from './views/Login.vue' 10 | import Logout from './views/Logout.vue' 11 | import Signup from './views/Signup.vue' 12 | import LostPassword from './views/LostPassword.vue' 13 | import ResetPassword from './views/ResetPassword.vue' 14 | import PageNotFound from './views/PageNotFound.vue' 15 | 16 | Vue.use(VueRouter) 17 | 18 | const router = new VueRouter({ 19 | mode: 'history', 20 | linkActiveClass: 'active', 21 | routes: [ 22 | { 23 | path: '/', 24 | name: 'home', 25 | component: Home 26 | }, 27 | { 28 | path: '/about', 29 | name: 'about', 30 | component: About 31 | }, 32 | { 33 | path: '/profile', 34 | component: Profile, 35 | meta: { requiresAuth: true }, 36 | children: [ 37 | { 38 | path: '', 39 | name: 'profile', 40 | component: ProfileEdit 41 | }, 42 | { 43 | path: 'security', 44 | component: ProfileSecurity 45 | } 46 | ] 47 | }, 48 | { 49 | path: '/login', 50 | name: 'login', 51 | component: Login, 52 | meta: { guestOnly: true } 53 | }, 54 | { 55 | path: '/signup', 56 | name: 'signup', 57 | component: Signup, 58 | meta: { guestOnly: true } 59 | }, 60 | { 61 | path: '/logout', 62 | name: 'logout', 63 | component: Logout, 64 | meta: { requiresAuth: true } 65 | }, 66 | { 67 | path: '/lost-password', 68 | name: 'lost-password', 69 | component: LostPassword, 70 | meta: { guestOnly: true } 71 | }, 72 | { 73 | path: '/password-reset/:key', 74 | name: 'reset-password', 75 | component: ResetPassword, 76 | meta: { guestOnly: true } 77 | }, 78 | { 79 | path: '*', 80 | component: PageNotFound 81 | } 82 | ] 83 | }) 84 | 85 | router.beforeEach((to, from, next) => { 86 | if (to.matched.some(record => record.meta.requiresAuth)) { 87 | 88 | if (!store.getters.isAuthenticated) { 89 | next({ 90 | path: '/login', 91 | query: { redirect: to.fullPath } 92 | }) 93 | } else { 94 | next() 95 | } 96 | } else { 97 | next() 98 | } 99 | 100 | if (to.matched.some(record => record.meta.guestOnly)) { 101 | if (store.getters.isAuthenticated) { 102 | next({ 103 | path: '/' 104 | }) 105 | } else { 106 | next() 107 | } 108 | } else { 109 | next() 110 | } 111 | 112 | }) 113 | 114 | export default router 115 | -------------------------------------------------------------------------------- /src/components/SignupForm.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 93 | 94 | 96 | -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | --------------------------------------------------------------------------------