aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMateja <mail@matejamaric.com>2021-07-29 02:46:33 +0200
committerMateja <mail@matejamaric.com>2021-07-29 02:46:33 +0200
commit015d67cf738e4ad6d397824dc09a44d85d643b75 (patch)
tree122c50c85b5659332117b735790231b257b4724a
parent9f4a1c17d4f544784dc5e11ecf6d04c8b5d0582a (diff)
downloadmevn-ecommerce-015d67cf738e4ad6d397824dc09a44d85d643b75.tar.gz
mevn-ecommerce-015d67cf738e4ad6d397824dc09a44d85d643b75.zip
Fully implement client-side login system...
-rw-r--r--client/src/App.vue27
-rw-r--r--client/src/main.js7
-rw-r--r--client/src/router/index.js27
-rw-r--r--client/src/store/index.js44
-rw-r--r--client/src/views/Checkout.vue8
-rw-r--r--client/src/views/Login.vue29
-rw-r--r--client/src/views/Register.vue32
-rw-r--r--server/routes/api.js4
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;