diff --git a/hn-vue/package-lock.json b/hn-vue/package-lock.json index aa3f588..ba558eb 100644 --- a/hn-vue/package-lock.json +++ b/hn-vue/package-lock.json @@ -9500,6 +9500,11 @@ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", "dev": true }, + "shvl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.2.tgz", + "integrity": "sha512-G3KkIXPza3dgkt6Bo8zIl5K/KvAAhbG6o9KfAjhPvrIIzzAhnfc2ztv1i+iPTbNNM43MaBUqIaZwqVjkSgY/rw==" + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -11031,6 +11036,22 @@ "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.0.tgz", "integrity": "sha512-W74OO2vCJPs9/YjNjW8lLbj+jzT24waTo2KShI8jLvJW8OaIkgb3wuAMA7D+ZiUxDOx3ubwSZTaJBip9G8a3aQ==" }, + "vuex-persistedstate": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-4.0.0-beta.3.tgz", + "integrity": "sha512-T4IRD27qoUWh+8qr6T6zVp15xO7x/nPgnU13OD0C2uUwA7U9PhGozrj6lvVmMYDyRgc36J0msMXn3GvwHjkIhA==", + "requires": { + "deepmerge": "^4.2.2", + "shvl": "^2.0.2" + }, + "dependencies": { + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + } + } + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", diff --git a/hn-vue/package.json b/hn-vue/package.json index 821e75b..a1c2225 100644 --- a/hn-vue/package.json +++ b/hn-vue/package.json @@ -12,7 +12,8 @@ "vue": "^2.6.11", "vue-router": "^3.4.9", "vuelidate": "^0.7.6", - "vuex": "^3.6.0" + "vuex": "^3.6.0", + "vuex-persistedstate": "^4.0.0-beta.3" }, "devDependencies": { "@vue/cli-plugin-babel": "~4.5.0", diff --git a/hn-vue/src/Layout.vue b/hn-vue/src/Layout.vue index e09ce11..b5792e2 100644 --- a/hn-vue/src/Layout.vue +++ b/hn-vue/src/Layout.vue @@ -4,6 +4,8 @@ Derniers liens publiés / Créer un compte + / + Se connecter diff --git a/hn-vue/src/api.js b/hn-vue/src/api.js index f2ae648..00214ca 100644 --- a/hn-vue/src/api.js +++ b/hn-vue/src/api.js @@ -1,6 +1,11 @@ class Api { constructor(baseUrl) { - this.baseUrl = baseUrl; + this._baseUrl = baseUrl; + this._getToken = null; + } + + useToken(cb) { + this._getToken = cb; } getLinks() { @@ -19,10 +24,29 @@ class Api { return this._post("/api/accounts", data); } + login(data) { + return this._post("/api/accounts/token", data); + } + + _authorizationHeader() { + if (!this._getToken) { + return {}; + } + + const token = this._getToken(); + + if (!token) { + return {}; + } + + return { Authorization: `Bearer ${token}` }; + } + _get(path) { - return fetch(this.baseUrl + path, { + return fetch(this._baseUrl + path, { headers: { Accept: "application/json", + ...this._authorizationHeader(), }, }).then((r) => { if (!r.ok) { @@ -34,11 +58,12 @@ class Api { } _post(path, data) { - return fetch(this.baseUrl + path, { + return fetch(this._baseUrl + path, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", + ...this._authorizationHeader(), }, body: JSON.stringify(data), }).then((r) => { @@ -50,6 +75,8 @@ class Api { return r; } + // if (!r.headers['Content-Length'] === '0') { + return r.json(); }); } diff --git a/hn-vue/src/components/NotificationsPanel.vue b/hn-vue/src/components/NotificationsPanel.vue index 9b934e8..c38ada0 100644 --- a/hn-vue/src/components/NotificationsPanel.vue +++ b/hn-vue/src/components/NotificationsPanel.vue @@ -13,7 +13,7 @@ import { mapState } from "vuex"; export default { computed: { - ...mapState(["messages"]), + ...mapState("notification", ["messages"]), }, }; diff --git a/hn-vue/src/main.js b/hn-vue/src/main.js index 53bdebd..078bec5 100644 --- a/hn-vue/src/main.js +++ b/hn-vue/src/main.js @@ -4,6 +4,7 @@ import Layout from "./Layout.vue"; import createRouter from "./router"; import createStore from "./store"; import Notifications from "./plugins/Notifications"; +import api from "./api"; Vue.config.productionTip = false; Vue.use(Vuelidate); @@ -12,6 +13,8 @@ Vue.use(Notifications); const router = createRouter(); const store = createStore(); +api.useToken(() => store.state.auth.token); + new Vue({ router, store, diff --git a/hn-vue/src/pages/Login.vue b/hn-vue/src/pages/Login.vue new file mode 100644 index 0000000..431221a --- /dev/null +++ b/hn-vue/src/pages/Login.vue @@ -0,0 +1,52 @@ + + + diff --git a/hn-vue/src/plugins/Notifications.js b/hn-vue/src/plugins/Notifications.js index cfd5ec8..8c46935 100644 --- a/hn-vue/src/plugins/Notifications.js +++ b/hn-vue/src/plugins/Notifications.js @@ -1,7 +1,7 @@ export default { install(Vue) { Vue.prototype.$message = function(message) { - this.$store.dispatch("addMessage", message); + this.$store.dispatch("notification/addMessage", message); }; Vue.prototype.$redirectWithMessage = function(to, message) { diff --git a/hn-vue/src/router.js b/hn-vue/src/router.js index 92593d8..f3095ef 100644 --- a/hn-vue/src/router.js +++ b/hn-vue/src/router.js @@ -2,6 +2,7 @@ import Vue from "vue"; import VueRouter from "vue-router"; import Links from "./pages/Links.vue"; import Register from "./pages/Register.vue"; +import Login from "./pages/Login.vue"; import LinkDetail from "./pages/LinkDetail.vue"; Vue.use(VueRouter); @@ -17,6 +18,7 @@ export default function createRouter() { props: true, }, { path: "/register", name: "register", component: Register }, + { path: "/login", name: "login", component: Login }, { path: "*", redirect: "/" }, ], }); diff --git a/hn-vue/src/store.js b/hn-vue/src/store.js index f839a2e..f1f9368 100644 --- a/hn-vue/src/store.js +++ b/hn-vue/src/store.js @@ -1,27 +1,58 @@ import Vue from "vue"; import Vuex from "vuex"; +import createPersistedState from "vuex-persistedstate"; +import api from "./api"; Vue.use(Vuex); export default function createStore() { return new Vuex.Store({ - state: { - messages: [], - }, - mutations: { - pushMessage(state, message) { - state.messages = [...state.messages, message]; - }, - removeMessage(state, message) { - state.messages = state.messages.filter((m) => m !== message); - }, - }, - actions: { - addMessage({ commit }, message) { - commit("pushMessage", message); + modules: { + notification: { + namespaced: true, + state: { + messages: [], + }, + mutations: { + pushMessage(state, message) { + state.messages = [...state.messages, message]; + }, + removeMessage(state, message) { + state.messages = state.messages.filter((m) => m !== message); + }, + }, + actions: { + addMessage({ commit }, message) { + commit("pushMessage", message); - setTimeout(() => commit("removeMessage", message), 3000); + setTimeout(() => commit("removeMessage", message), 3000); + }, + }, + }, + auth: { + namespaced: true, + state: { + token: null, + username: null, + }, + mutations: { + setUser(state, { token, username }) { + state.token = token; + state.username = username; + }, + }, + actions: { + async login({ commit }, data) { + const result = await api.login(data); + commit("setUser", result); + }, + }, }, }, + plugins: [ + createPersistedState({ + paths: ["auth"], + }), + ], }); }