diff options
author | Mateja <mail@matejamaric.com> | 2021-07-29 02:46:33 +0200 |
---|---|---|
committer | Mateja <mail@matejamaric.com> | 2021-07-29 02:46:33 +0200 |
commit | 015d67cf738e4ad6d397824dc09a44d85d643b75 (patch) | |
tree | 122c50c85b5659332117b735790231b257b4724a | |
parent | 9f4a1c17d4f544784dc5e11ecf6d04c8b5d0582a (diff) | |
download | mevn-ecommerce-015d67cf738e4ad6d397824dc09a44d85d643b75.tar.gz mevn-ecommerce-015d67cf738e4ad6d397824dc09a44d85d643b75.zip |
Fully implement client-side login system...
-rw-r--r-- | client/src/App.vue | 27 | ||||
-rw-r--r-- | client/src/main.js | 7 | ||||
-rw-r--r-- | client/src/router/index.js | 27 | ||||
-rw-r--r-- | client/src/store/index.js | 44 | ||||
-rw-r--r-- | client/src/views/Checkout.vue | 8 | ||||
-rw-r--r-- | client/src/views/Login.vue | 29 | ||||
-rw-r--r-- | client/src/views/Register.vue | 32 | ||||
-rw-r--r-- | server/routes/api.js | 4 |
8 files changed, 160 insertions, 18 deletions
diff --git a/client/src/App.vue b/client/src/App.vue index f2bef74..f61f236 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -17,7 +17,7 @@ </router-link> </li> </ul> - <ul class="navbar-nav mb-2 mb-lg-0"> + <ul class="navbar-nav mb-2 mb-lg-0" v-if="!isLoggedIn"> <li class="nav-item"> <router-link class="nav-link" active-class="active" to="/register">Register</router-link> </li> @@ -25,6 +25,11 @@ <router-link class="nav-link" active-class="active" to="/login">Login</router-link> </li> </ul> + <ul class="navbar-nav mb-2 mb-lg-0" v-else> + <li class="nav-item"> + <span class="nav-link" @click="logout">Logout</span> + </li> + </ul> </div> </div> </nav> @@ -34,11 +39,29 @@ </template> <script> +import axios from 'axios'; + export default { - name: 'Checkout', + name: 'App', + created() { + axios.interceptors.response.use(undefined, error => { + if (error.status === 401) { + this.$store.commit('auth_clean'); + } + return Promise.reject(error); + }); + }, computed: { cartSize() { return this.$store.getters.getCartSize; + }, + isLoggedIn() { + return this.$store.getters.isLoggedIn; + } + }, + methods: { + logout() { + this.$store.commit('auth_clean'); } } } diff --git a/client/src/main.js b/client/src/main.js index c210589..5ab733a 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -1,4 +1,11 @@ import {createApp} from 'vue'; +import axios from 'axios'; + +const tokenInStorage = localStorage.getItem('token'); +if (tokenInStorage) { + axios.defaults.headers.common['Authorization'] = `Bearer ${tokenInStorage}`; +} + import App from './App.vue'; import router from './router'; import store from './store'; diff --git a/client/src/router/index.js b/client/src/router/index.js index eee610c..ee78676 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -5,6 +5,8 @@ import Checkout from '@/views/Checkout.vue'; import Login from '@/views/Login.vue'; import Register from '@/views/Register.vue'; +import store from '@/store/index'; + const routes = [ { path: '/', @@ -24,12 +26,18 @@ const routes = [ { path: '/login', name: 'Login', - component: Login + component: Login, + meta: { + guest: true + } }, { path: '/register', name: 'Register', - component: Register + component: Register, + meta: { + guest: true + } } ]; @@ -38,4 +46,19 @@ const router = createRouter({ routes }); +router.beforeEach((to, from) => { + if (to.matched.some(record => record.meta.guest) && store.getters.isLoggedIn) + return false; + + if (to.matched.some(record => record.meta.admin) && !store.getters.isAdmin) + return false; + + if (to.matched.some(record => record.meta.auth) && !store.getters.isLoggedIn) { + from.params.nextUrl = to.fullPath; + return '/login'; + } + + return true; +}); + export default router; diff --git a/client/src/store/index.js b/client/src/store/index.js index ff4648f..47f8f47 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -5,7 +5,9 @@ export default createStore({ state: { products: [], currentProduct: {}, - cart: [] + cart: [], + token: localStorage.getItem('token') || '', + isAdmin: localStorage.getItem('isAdmin') === 'true' }, getters: { getProducts(state) { @@ -37,6 +39,12 @@ export default createStore({ amount = x.quantity; }); return amount; + }, + isLoggedIn(state) { + return !!state.token; + }, + isAdmin(state) { + return state.isAdmin; } }, mutations: { @@ -68,6 +76,20 @@ export default createStore({ }, clearCart(state) { state.cart.length = 0; + }, + auth_set(state, token, isAdmin) { + state.token = token; + state.isAdmin = isAdmin; + localStorage.setItem('token', token); + localStorage.setItem('isAdmin', isAdmin); + axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; + }, + auth_clean(state) { + state.token = null; + state.isAdmin = false; + localStorage.removeItem('token'); + localStorage.removeItem('isAdmin'); + delete axios.defaults.headers.common['Authorization']; } }, actions: { @@ -96,6 +118,26 @@ export default createStore({ .post(`${process.env.VUE_APP_ROOT_API}/transactions/capture`, {orderId}) .then(() => true) .catch(err => console.error(err)); + }, + login(context, loginData) { + return new Promise((resolve, reject) => { + axios.post(`${process.env.VUE_APP_ROOT_API}/login`, loginData) + .then(response => { + context.commit('auth_set', response.data.token, response.data.isAdmin); + resolve(response); + }) + .catch(error => reject(error)); + }); + }, + register(context, registerData) { + return new Promise((resolve, reject) => { + axios.post(`${process.env.VUE_APP_ROOT_API}/register`, registerData) + .then(response => { + context.commit('auth_set', response.data.token, response.data.isAdmin); + resolve(response); + }) + .catch(error => reject(error)); + }); } }, modules: { diff --git a/client/src/views/Checkout.vue b/client/src/views/Checkout.vue index 52da138..7004b1f 100644 --- a/client/src/views/Checkout.vue +++ b/client/src/views/Checkout.vue @@ -16,7 +16,10 @@ <p class="text-center my-3 fw-bold">You can buy {{ cartSize }} items for ${{ cartPrice }}.</p> </div> <div class="col-md-4 card p-2" v-if="cartSize"> - <div ref="paypal"></div> + <div ref="paypal" v-if="isLoggedIn"></div> + <p v-else class="fw-bold text-center my-auto"> + You need to be logged in to make a purchase. + </p> </div> </div> </div> @@ -55,6 +58,9 @@ export default { }, cartPrice() { return this.$store.getters.getCartPrice; + }, + isLoggedIn() { + return this.$store.getters.isLoggedIn; } }, methods: { diff --git a/client/src/views/Login.vue b/client/src/views/Login.vue index 53badcb..f0a1759 100644 --- a/client/src/views/Login.vue +++ b/client/src/views/Login.vue @@ -32,7 +32,7 @@ </div> </div> </div> - <Modal :title="modalTitle" v-if="showModal" @close="showModal = false"> + <Modal :title="modalTitle" v-if="showModal" @close="closeBtnAction"> <p v-text="modalText"></p> </Modal> </template> @@ -55,7 +55,8 @@ export default { passwordBlured:false, showModal: false, modalTitle: '', - modalText: '' + modalText: '', + closeBtnAction: null } }, methods: { @@ -75,15 +76,33 @@ export default { login() { this.validate(); - if (this.valid) { + + const successMsg = () => { this.modalTitle = 'Success!'; this.modalText = 'You successfully logged in!'; this.showModal = true; - } - else { + this.closeBtnAction = () => this.$router.push('/'); + }; + + const failureMsg = () => { this.modalTitle = 'Failure!'; this.modalText = 'You failed to login.'; this.showModal = true; + this.closeBtnAction = () => this.showModal = false; + }; + + if (this.valid) { + const requestData = { + email: this.email, + password: this.password + }; + + this.$store.dispatch('login', requestData) + .then(() => successMsg()) + .catch(() => failureMsg()); + } + else { + failureMsg(); } } } diff --git a/client/src/views/Register.vue b/client/src/views/Register.vue index e26c751..238b5df 100644 --- a/client/src/views/Register.vue +++ b/client/src/views/Register.vue @@ -58,7 +58,7 @@ </div> </div> </div> - <Modal :title="modalTitle" v-if="showModal" @close="showModal = false"> + <Modal :title="modalTitle" v-if="showModal" @close="closeBtnAction"> <p v-text="modalText"></p> </Modal> </template> @@ -87,7 +87,8 @@ export default { passwordConfirmBlured:false, showModal: false, modalTitle: '', - modalText: '' + modalText: '', + closeBtnAction: null } }, methods: { @@ -114,15 +115,36 @@ export default { submit() { this.validate(); - if (this.valid) { + + const successMsg = () => { this.modalTitle = 'Success!'; this.modalText = 'You successfully registered!'; this.showModal = true; - } - else { + this.closeBtnAction = () => this.$router.push('/'); + }; + + const failureMsg = () => { this.modalTitle = 'Failure!'; this.modalText = 'You failed to register.'; this.showModal = true; + this.closeBtnAction = () => this.showModal = false; + }; + + if (this.valid) { + const requestData = { + firstname: this.firstname, + lastname: this.lastname, + email: this.email, + password: this.password, + confirmPassword: this.passwordConfirm, + }; + + this.$store.dispatch('register', requestData) + .then(() => successMsg()) + .catch(() => failureMsg()); + } + else { + failureMsg(); } } } diff --git a/server/routes/api.js b/server/routes/api.js index b680b70..0c73ec0 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -18,7 +18,7 @@ router.patch('/products/:id', isAuth, isAdmin, upload.single('image'), productsC router.delete('/products/:id', isAuth, isAdmin, productsController.destroy); router.get('/transactions/paid', isAuth, isAdmin, transactionController.showPaid); -router.post('/transactions/setup', transactionController.setup); -router.post('/transactions/capture', transactionController.capture); +router.post('/transactions/setup', isAuth, transactionController.setup); +router.post('/transactions/capture', isAuth, transactionController.capture); module.exports = router; |