├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── netlify.toml
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ └── css
│ │ └── tailwind.css
├── components
│ ├── AuthUser.vue
│ ├── AuthUserForm.vue
│ ├── BaseBtn.vue
│ ├── BaseInput.vue
│ ├── BasePagination.vue
│ ├── FileUpload.vue
│ ├── FlashMessage.vue
│ ├── Header.vue
│ ├── LoginForm.vue
│ ├── Logout.vue
│ ├── MessageForm.vue
│ ├── Messages.vue
│ ├── RegisterForm.vue
│ ├── UpdatePassword.vue
│ ├── VerifyEmail.vue
│ └── icons
│ │ ├── AvatarIcon.vue
│ │ ├── HomeIcon.vue
│ │ ├── LoginIcon.vue
│ │ ├── LogoutIcon.vue
│ │ └── MailIcon.vue
├── main.js
├── middleware
│ ├── admin.js
│ ├── auth.js
│ └── guest.js
├── router
│ ├── index.js
│ └── middlewarePipeline.js
├── services
│ ├── API.js
│ ├── AuthService.js
│ ├── FileService.js
│ ├── MessageService.js
│ └── UserService.js
├── store
│ ├── index.js
│ └── modules
│ │ ├── Auth.js
│ │ ├── Message.js
│ │ └── User.js
├── utils
│ └── helpers.js
└── views
│ ├── Dashboard.vue
│ ├── ForgotPassword.vue
│ ├── Home.vue
│ ├── Login.vue
│ ├── NotFound.vue
│ ├── Register.vue
│ ├── ResetPassword.vue
│ ├── User.vue
│ └── Users.vue
└── tailwind.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Gareth Redfern
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Vue SPA Using Sanctum & Fortify Authentication
2 |
3 | ## Documentation
4 |
5 | The full documentation for this demo app can be found at [Build a Laravel Vue Spa](https://laravelvuespa.com/).
6 |
7 | ## Project setup
8 | ```
9 | npm install
10 | ```
11 |
12 | **IMPORTANT** make sure you have a .env.local file with your API URL added
13 | ```
14 | VUE_APP_API_URL=http://localhost
15 | ```
16 |
17 | ### Compiles and hot-reloads for development
18 | ```
19 | npm run serve
20 | ```
21 |
22 | ### Compiles and minifies for production
23 | ```
24 | npm run build
25 | ```
26 |
27 | ### Lints and fixes files
28 | ```
29 | npm run lint
30 | ```
31 |
32 | ### Customize configuration
33 | See [Configuration Reference](https://cli.vuejs.org/config/).
34 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/cli-plugin-babel/preset"],
3 | };
4 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish = "dist"
3 | command = "npm run build"
4 | [[redirects]]
5 | from = "/*"
6 | to = "/index.html"
7 | status = 200
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel-vue",
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.21.1",
12 | "core-js": "^3.15.2",
13 | "vue": "^2.6.14",
14 | "vue-router": "^3.5.2",
15 | "vuex": "^3.6.2"
16 | },
17 | "devDependencies": {
18 | "@tailwindcss/forms": "^0.2.1",
19 | "@tailwindcss/postcss7-compat": "^2.2.4",
20 | "@vue/cli-plugin-babel": "^4.5.10",
21 | "@vue/cli-plugin-eslint": "^4.5.10",
22 | "@vue/cli-plugin-router": "^4.5.10",
23 | "@vue/cli-plugin-vuex": "^4.5.10",
24 | "@vue/cli-service": "^4.5.10",
25 | "@vue/eslint-config-prettier": "^6.0.0",
26 | "autoprefixer": "^9.8.6",
27 | "babel-eslint": "^10.1.0",
28 | "eslint": "^7.18.0",
29 | "eslint-plugin-prettier": "^3.4.0",
30 | "eslint-plugin-vue": "^7.13.0",
31 | "postcss": "^7.0.36",
32 | "prettier": "^2.3.2",
33 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2",
34 | "vue-template-compiler": "^2.6.14"
35 | },
36 | "prettier": {
37 | "trailingComma": "es5",
38 | "tabWidth": 2,
39 | "semi": true,
40 | "singleQuote": false
41 | },
42 | "eslintConfig": {
43 | "root": true,
44 | "env": {
45 | "node": true
46 | },
47 | "extends": [
48 | "plugin:vue/essential",
49 | "eslint:recommended",
50 | "@vue/prettier"
51 | ],
52 | "parserOptions": {
53 | "parser": "babel-eslint"
54 | },
55 | "rules": {}
56 | },
57 | "browserslist": [
58 | "> 1%",
59 | "last 2 versions",
60 | "not dead"
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garethredfern/laravel-vue/16d56b9c79a9d761d65251ef0d1927d24d45b75d/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
33 |
--------------------------------------------------------------------------------
/src/assets/css/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | body {
7 | @apply bg-gray-50;
8 | }
9 | .base-link {
10 | @apply text-blue-400;
11 | @apply hover:text-blue-500;
12 | @apply hover:underline;
13 | @apply transition;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/AuthUser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 | - {{ authUser.name }}
13 | - Email: {{ authUser.email }}
14 | -
15 | Emailed Verified
16 |
17 |
18 |
19 |
20 |
21 |
22 |
36 |
--------------------------------------------------------------------------------
/src/components/AuthUserForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
70 |
--------------------------------------------------------------------------------
/src/components/BaseBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
26 |
--------------------------------------------------------------------------------
/src/components/BaseInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
21 |
22 |
23 |
24 |
68 |
--------------------------------------------------------------------------------
/src/components/BasePagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Page {{ meta.current_page }} of {{ meta.last_page }}
5 |
6 |
7 |
16 |
17 |
27 |
28 |
38 |
39 |
48 |
49 |
50 |
51 |
52 |
116 |
--------------------------------------------------------------------------------
/src/components/FileUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
73 |
--------------------------------------------------------------------------------
/src/components/FlashMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ message }}
6 |
7 |
12 | {{ error }}
13 |
14 |
19 | -
20 | {{ key | titleCase }}
21 |
22 | -
23 | {{ item }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
68 |
--------------------------------------------------------------------------------
/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
27 |
28 |
29 |
30 |
48 |
--------------------------------------------------------------------------------
/src/components/LoginForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
77 |
--------------------------------------------------------------------------------
/src/components/Logout.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
24 |
--------------------------------------------------------------------------------
/src/components/MessageForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
52 |
--------------------------------------------------------------------------------
/src/components/Messages.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 | -
15 |
16 |
![]()
22 |
23 |
24 |
25 |
26 | {{ message.user.name }}
27 | {{ message.createdAt }}
28 |
29 |
{{ message.body }}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
66 |
--------------------------------------------------------------------------------
/src/components/RegisterForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
36 |
37 |
38 |
77 |
--------------------------------------------------------------------------------
/src/components/UpdatePassword.vue:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
39 |
76 |
--------------------------------------------------------------------------------
/src/components/VerifyEmail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
46 |
--------------------------------------------------------------------------------
/src/components/icons/AvatarIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
19 |
--------------------------------------------------------------------------------
/src/components/icons/HomeIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
19 |
--------------------------------------------------------------------------------
/src/components/icons/LoginIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
21 |
--------------------------------------------------------------------------------
/src/components/icons/LogoutIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
21 |
--------------------------------------------------------------------------------
/src/components/icons/MailIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./App.vue";
3 | import router from "./router";
4 | import store from "./store";
5 | import "@/assets/css/tailwind.css";
6 |
7 | Vue.config.productionTip = false;
8 |
9 | new Vue({
10 | router,
11 | store,
12 | render: (h) => h(App),
13 | }).$mount("#app");
14 |
--------------------------------------------------------------------------------
/src/middleware/admin.js:
--------------------------------------------------------------------------------
1 | export default function admin({ next, store }) {
2 | if (store.getters["auth/isAdmin"]) next();
3 | else next({ name: "notFound" });
4 | }
5 |
--------------------------------------------------------------------------------
/src/middleware/auth.js:
--------------------------------------------------------------------------------
1 | export default function auth({ to, next, store }) {
2 | const loginQuery = { path: "/login", query: { redirect: to.fullPath } };
3 |
4 | if (!store.getters["auth/authUser"]) {
5 | store.dispatch("auth/getAuthUser").then(() => {
6 | if (!store.getters["auth/authUser"]) next(loginQuery);
7 | else next();
8 | });
9 | } else {
10 | next();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/middleware/guest.js:
--------------------------------------------------------------------------------
1 | export default function guest({ next, store }) {
2 | const storageItem = window.localStorage.getItem("guest");
3 | if (storageItem === "isNotGuest" && !store.getters["auth/authUser"]) {
4 | store.dispatch("auth/getAuthUser").then(() => {
5 | if (store.getters["auth/authUser"]) {
6 | next({ name: "dashboard" });
7 | } else {
8 | store.dispatch("auth/setGuest", { value: "isGuest" });
9 | next();
10 | }
11 | });
12 | } else {
13 | next();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import store from "@/store/index";
3 | import VueRouter from "vue-router";
4 | import auth from "@/middleware/auth";
5 | import admin from "@/middleware/admin";
6 | import guest from "@/middleware/guest";
7 | import middlewarePipeline from "@/router/middlewarePipeline";
8 |
9 | Vue.use(VueRouter);
10 |
11 | const routes = [
12 | {
13 | path: "/",
14 | name: "home",
15 | meta: { middleware: [guest] },
16 | component: () => import(/* webpackChunkName: "home" */ "../views/Home"),
17 | },
18 | {
19 | path: "/dashboard",
20 | name: "dashboard",
21 | meta: { middleware: [auth] },
22 | component: () =>
23 | import(/* webpackChunkName: "dashboard" */ "../views/Dashboard"),
24 | },
25 | {
26 | path: "/user",
27 | name: "user",
28 | meta: { middleware: [auth] },
29 | component: () => import(/* webpackChunkName: "user" */ "../views/User"),
30 | },
31 | {
32 | path: "/users",
33 | name: "users",
34 | meta: { middleware: [auth, admin] },
35 | component: () => import(/* webpackChunkName: "users" */ "../views/Users"),
36 | },
37 | {
38 | path: "/login",
39 | name: "login",
40 | meta: { middleware: [guest] },
41 | component: () => import(/* webpackChunkName: "login" */ "../views/Login"),
42 | },
43 | {
44 | path: "/register",
45 | name: "register",
46 | meta: { middleware: [guest] },
47 | component: () =>
48 | import(/* webpackChunkName: "register" */ "../views/Register"),
49 | },
50 | {
51 | path: "/reset-password",
52 | name: "resetPassword",
53 | meta: { middleware: [guest] },
54 | component: () =>
55 | import(/* webpackChunkName: "reset-password" */ "../views/ResetPassword"),
56 | },
57 | {
58 | path: "/forgot-password",
59 | name: "forgotPassword",
60 | meta: { middleware: [guest] },
61 | component: () =>
62 | import(
63 | /* webpackChunkName: "forgot-password" */ "../views/ForgotPassword"
64 | ),
65 | },
66 | {
67 | path: "/:catchAll(.*)",
68 | name: "notFound",
69 | component: () =>
70 | import(/* webpackChunkName: "not-found" */ "../views/NotFound"),
71 | },
72 | ];
73 |
74 | const router = new VueRouter({
75 | mode: "history",
76 | routes,
77 | scrollBehavior(to, from, savedPosition) {
78 | if (savedPosition) {
79 | return savedPosition;
80 | } else {
81 | return { x: 0, y: 0 };
82 | }
83 | },
84 | });
85 |
86 | router.beforeEach((to, from, next) => {
87 | const middleware = to.meta.middleware;
88 | const context = { to, from, next, store };
89 |
90 | if (!middleware) {
91 | return next();
92 | }
93 |
94 | middleware[0]({
95 | ...context,
96 | next: middlewarePipeline(context, middleware, 1),
97 | });
98 | });
99 |
100 | export default router;
101 |
--------------------------------------------------------------------------------
/src/router/middlewarePipeline.js:
--------------------------------------------------------------------------------
1 | export default function middlewarePipeline(context, middleware, index) {
2 | const nextMiddleware = middleware[index];
3 | if (!nextMiddleware) {
4 | return context.next;
5 | }
6 | return () => {
7 | nextMiddleware({
8 | ...context,
9 | next: middlewarePipeline(context, middleware, index + 1),
10 | });
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/services/API.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is the initial API interface
3 | * we set the base URL for the API
4 | */
5 |
6 | import axios from "axios";
7 | import store from "@/store";
8 |
9 | export const apiClient = axios.create({
10 | baseURL: process.env.VUE_APP_API_URL + "/api",
11 | withCredentials: true, // required to handle the CSRF token
12 | });
13 |
14 | /*
15 | * Add a response interceptor
16 | */
17 | apiClient.interceptors.response.use(
18 | (response) => {
19 | return response;
20 | },
21 | function (error) {
22 | if (
23 | error.response &&
24 | [401, 419].includes(error.response.status) &&
25 | store.getters["auth/authUser"] &&
26 | !store.getters["auth/guest"]
27 | ) {
28 | store.dispatch("auth/logout");
29 | }
30 | return Promise.reject(error);
31 | }
32 | );
33 |
--------------------------------------------------------------------------------
/src/services/AuthService.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import store from "@/store";
3 |
4 | export const authClient = axios.create({
5 | baseURL: process.env.VUE_APP_API_URL,
6 | withCredentials: true, // required to handle the CSRF token
7 | });
8 |
9 | /*
10 | * Add a response interceptor
11 | */
12 | authClient.interceptors.response.use(
13 | (response) => {
14 | return response;
15 | },
16 | function (error) {
17 | if (
18 | error.response &&
19 | [401, 419].includes(error.response.status) &&
20 | store.getters["auth/authUser"] &&
21 | !store.getters["auth/guest"]
22 | ) {
23 | store.dispatch("auth/logout");
24 | }
25 | return Promise.reject(error);
26 | }
27 | );
28 |
29 | export default {
30 | async login(payload) {
31 | await authClient.get("/sanctum/csrf-cookie");
32 | return authClient.post("/login", payload);
33 | },
34 | logout() {
35 | return authClient.post("/logout");
36 | },
37 | async forgotPassword(payload) {
38 | await authClient.get("/sanctum/csrf-cookie");
39 | return authClient.post("/forgot-password", payload);
40 | },
41 | getAuthUser() {
42 | return authClient.get("/api/users/auth");
43 | },
44 | async resetPassword(payload) {
45 | await authClient.get("/sanctum/csrf-cookie");
46 | return authClient.post("/reset-password", payload);
47 | },
48 | updatePassword(payload) {
49 | return authClient.put("/user/password", payload);
50 | },
51 | async registerUser(payload) {
52 | await authClient.get("/sanctum/csrf-cookie");
53 | return authClient.post("/register", payload);
54 | },
55 | sendVerification(payload) {
56 | return authClient.post("/email/verification-notification", payload);
57 | },
58 | updateUser(payload) {
59 | return authClient.put("/user/profile-information", payload);
60 | },
61 | };
62 |
--------------------------------------------------------------------------------
/src/services/FileService.js:
--------------------------------------------------------------------------------
1 | import * as API from "@/services/API";
2 |
3 | export default {
4 | uploadFile(payload) {
5 | return API.apiClient.post(payload.endpoint, payload.file);
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/src/services/MessageService.js:
--------------------------------------------------------------------------------
1 | import * as API from "@/services/API";
2 |
3 | export default {
4 | getMessages(page) {
5 | return API.apiClient.get(`/messages/?page=${page}`);
6 | },
7 | postMessage(payload) {
8 | return API.apiClient.post("/messages", payload);
9 | },
10 | paginateMessages(link) {
11 | return API.apiClient.get(link);
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/services/UserService.js:
--------------------------------------------------------------------------------
1 | import * as API from "@/services/API";
2 |
3 | export default {
4 | getUser(userId) {
5 | return API.apiClient.get(`/users/${userId}`);
6 | },
7 | getUsers(page) {
8 | return API.apiClient.get(`/users/?page=${page}`);
9 | },
10 | paginateUsers(link) {
11 | return API.apiClient.get(link);
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 |
4 | import * as auth from "@/store/modules/Auth";
5 | import * as user from "@/store/modules/User";
6 | import * as message from "@/store/modules/Message";
7 |
8 | Vue.use(Vuex);
9 |
10 | export default new Vuex.Store({
11 | strict: true,
12 |
13 | modules: {
14 | auth,
15 | user,
16 | message,
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/src/store/modules/Auth.js:
--------------------------------------------------------------------------------
1 | import router from "@/router";
2 | import { getError } from "@/utils/helpers";
3 | import AuthService from "@/services/AuthService";
4 |
5 | export const namespaced = true;
6 |
7 | export const state = {
8 | user: null,
9 | loading: false,
10 | error: null,
11 | };
12 |
13 | export const mutations = {
14 | SET_USER(state, user) {
15 | state.user = user;
16 | },
17 | SET_LOADING(state, loading) {
18 | state.loading = loading;
19 | },
20 | SET_ERROR(state, error) {
21 | state.error = error;
22 | },
23 | };
24 |
25 | export const actions = {
26 | logout({ commit, dispatch }) {
27 | return AuthService.logout()
28 | .then(() => {
29 | commit("SET_USER", null);
30 | dispatch("setGuest", { value: "isGuest" });
31 | if (router.currentRoute.name !== "login")
32 | router.push({ path: "/login" });
33 | })
34 | .catch((error) => {
35 | commit("SET_ERROR", getError(error));
36 | });
37 | },
38 | async getAuthUser({ commit }) {
39 | commit("SET_LOADING", true);
40 | try {
41 | const response = await AuthService.getAuthUser();
42 | commit("SET_USER", response.data.data);
43 | commit("SET_LOADING", false);
44 | return response.data.data;
45 | } catch (error) {
46 | commit("SET_LOADING", false);
47 | commit("SET_USER", null);
48 | commit("SET_ERROR", getError(error));
49 | }
50 | },
51 | setGuest(context, { value }) {
52 | window.localStorage.setItem("guest", value);
53 | },
54 | };
55 |
56 | export const getters = {
57 | authUser: (state) => {
58 | return state.user;
59 | },
60 | isAdmin: (state) => {
61 | return state.user ? state.user.isAdmin : false;
62 | },
63 | error: (state) => {
64 | return state.error;
65 | },
66 | loading: (state) => {
67 | return state.loading;
68 | },
69 | loggedIn: (state) => {
70 | return !!state.user;
71 | },
72 | guest: () => {
73 | const storageItem = window.localStorage.getItem("guest");
74 | if (!storageItem) return false;
75 | if (storageItem === "isGuest") return true;
76 | if (storageItem === "isNotGuest") return false;
77 | },
78 | };
79 |
--------------------------------------------------------------------------------
/src/store/modules/Message.js:
--------------------------------------------------------------------------------
1 | import { getError } from "@/utils/helpers";
2 | import MessageService from "@/services/MessageService";
3 |
4 | export const namespaced = true;
5 |
6 | function setPaginatedMessages(commit, response) {
7 | commit("SET_MESSAGES", response.data.data);
8 | commit("SET_META", response.data.meta);
9 | commit("SET_LINKS", response.data.links);
10 | commit("SET_LOADING", false);
11 | }
12 |
13 | export const state = {
14 | messages: [],
15 | meta: null,
16 | links: null,
17 | loading: false,
18 | error: null,
19 | };
20 |
21 | export const mutations = {
22 | SET_MESSAGES(state, messages) {
23 | state.messages = messages;
24 | },
25 | SET_META(state, meta) {
26 | state.meta = meta;
27 | },
28 | SET_LINKS(state, links) {
29 | state.links = links;
30 | },
31 | SET_LOADING(state, loading) {
32 | state.loading = loading;
33 | },
34 | SET_ERROR(state, error) {
35 | state.error = error;
36 | },
37 | };
38 |
39 | export const actions = {
40 | getMessages({ commit }, page) {
41 | commit("SET_LOADING", true);
42 | MessageService.getMessages(page)
43 | .then((response) => {
44 | setPaginatedMessages(commit, response);
45 | })
46 | .catch((error) => {
47 | commit("SET_LOADING", false);
48 | commit("SET_ERROR", getError(error));
49 | });
50 | },
51 | postMessage({ commit }, payload) {
52 | return MessageService.postMessage(payload).then((response) => {
53 | setPaginatedMessages(commit, response);
54 | });
55 | },
56 | paginateMessages({ commit }, link) {
57 | commit("SET_LOADING", true);
58 | MessageService.paginateMessages(link)
59 | .then((response) => {
60 | setPaginatedMessages(commit, response);
61 | })
62 | .catch((error) => {
63 | commit("SET_LOADING", false);
64 | commit("SET_ERROR", getError(error));
65 | });
66 | },
67 | };
68 |
69 | export const getters = {
70 | messages: (state) => {
71 | return state.messages;
72 | },
73 | meta: (state) => {
74 | return state.meta;
75 | },
76 | links: (state) => {
77 | return state.links;
78 | },
79 | loading: (state) => {
80 | return state.loading;
81 | },
82 | error: (state) => {
83 | return state.error;
84 | },
85 | };
86 |
--------------------------------------------------------------------------------
/src/store/modules/User.js:
--------------------------------------------------------------------------------
1 | import { getError } from "@/utils/helpers";
2 | import UserService from "@/services/UserService";
3 |
4 | export const namespaced = true;
5 |
6 | function setPaginatedUsers(commit, response) {
7 | commit("SET_USERS", response.data.data);
8 | commit("SET_META", response.data.meta);
9 | commit("SET_LINKS", response.data.links);
10 | commit("SET_LOADING", false);
11 | }
12 |
13 | export const state = {
14 | users: [],
15 | meta: null,
16 | links: null,
17 | loading: false,
18 | error: null,
19 | };
20 |
21 | export const mutations = {
22 | SET_USERS(state, users) {
23 | state.users = users;
24 | },
25 | SET_META(state, meta) {
26 | state.meta = meta;
27 | },
28 | SET_LINKS(state, links) {
29 | state.links = links;
30 | },
31 | SET_LOADING(state, loading) {
32 | state.loading = loading;
33 | },
34 | SET_ERROR(state, error) {
35 | state.error = error;
36 | },
37 | };
38 |
39 | export const actions = {
40 | getUsers({ commit }, page) {
41 | commit("SET_LOADING", true);
42 | UserService.getUsers(page)
43 | .then((response) => {
44 | setPaginatedUsers(commit, response);
45 | })
46 | .catch((error) => {
47 | commit("SET_LOADING", false);
48 | commit("SET_ERROR", getError(error));
49 | });
50 | },
51 | paginateUsers({ commit }, link) {
52 | commit("SET_LOADING", true);
53 | UserService.paginateUsers(link)
54 | .then((response) => {
55 | setPaginatedUsers(commit, response);
56 | })
57 | .catch((error) => {
58 | commit("SET_LOADING", false);
59 | commit("SET_ERROR", getError(error));
60 | });
61 | },
62 | };
63 |
64 | export const getters = {
65 | users: (state) => {
66 | return state.users;
67 | },
68 | meta: (state) => {
69 | return state.meta;
70 | },
71 | links: (state) => {
72 | return state.links;
73 | },
74 | loading: (state) => {
75 | return state.loading;
76 | },
77 | error: (state) => {
78 | return state.error;
79 | },
80 | };
81 |
--------------------------------------------------------------------------------
/src/utils/helpers.js:
--------------------------------------------------------------------------------
1 | export const getError = (error) => {
2 | const errorMessage = "API Error, please try again.";
3 |
4 | if (error.name === "Fetch User") {
5 | return error.message;
6 | }
7 |
8 | if (!error.response) {
9 | console.error(`API ${error.config.url} not found`);
10 | return errorMessage;
11 | }
12 | if (process.env.NODE_ENV === "development") {
13 | console.error(error.response.data);
14 | console.error(error.response.status);
15 | console.error(error.response.headers);
16 | }
17 | if (error.response.data && error.response.data.errors) {
18 | return error.response.data.errors;
19 | }
20 |
21 | return errorMessage;
22 | };
23 |
--------------------------------------------------------------------------------
/src/views/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
17 |
18 |
19 |
20 |
36 |
--------------------------------------------------------------------------------
/src/views/ForgotPassword.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Forgot Password
4 |
19 |
20 |
21 |
22 |
23 |
58 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 | Laravel Vue Demo SPA.
8 |
9 |
10 | You can register for an account
11 | here.
12 |
13 |
14 |
15 |
16 | Docs on how this application is built can be found at:
17 | laravelvuespa.com
20 |
21 |
22 |
23 |
24 |
25 |
30 |
--------------------------------------------------------------------------------
/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Login
4 |
5 |
6 | Register for an account
11 |
12 |
13 |
14 |
15 |
25 |
--------------------------------------------------------------------------------
/src/views/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 | 4Oh4
8 |
9 |
10 | The page you're looking for is not here.
11 | Back home.
12 |
13 |
14 |
15 |
16 |
17 |
22 |
--------------------------------------------------------------------------------
/src/views/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Register
4 |
5 |
6 |
7 |
8 |
18 |
--------------------------------------------------------------------------------
/src/views/ResetPassword.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Reset Password
4 |
39 |
40 |
41 |
42 |
43 |
81 |
--------------------------------------------------------------------------------
/src/views/User.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
39 |
--------------------------------------------------------------------------------
/src/views/Users.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
38 |
39 |
40 |
41 |
42 |
43 |
50 |
51 |
52 |
53 |
54 |
77 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ["./src/**/*.html", "./src/**/*.vue"],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {
6 | fontFamily: {
7 | sans: ["'Open Sans'", "sans-serif"],
8 | },
9 | },
10 | },
11 | variants: {
12 | extend: {},
13 | },
14 | plugins: [require("@tailwindcss/forms")],
15 | };
16 |
--------------------------------------------------------------------------------