├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ └── css
│ │ └── main.css
├── components
│ ├── Dashboard.vue
│ ├── Protected.vue
│ └── auth
│ │ └── Login.vue
├── main.js
├── router.js
├── services
│ ├── API.js
│ └── AuthService.js
└── store
│ ├── modules
│ └── auth.js
│ └── store.js
├── tailwind.config.js
└── vue.config.js
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sanctum-vue
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | yarn run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sanctum-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 | "@vue/cli-plugin-babel": "~4.5.0",
19 | "@vue/cli-plugin-eslint": "~4.5.0",
20 | "@vue/cli-plugin-router": "~4.5.0",
21 | "@vue/cli-plugin-vuex": "~4.5.0",
22 | "@vue/cli-service": "~4.5.0",
23 | "@vue/eslint-config-prettier": "^6.0.0",
24 | "babel-eslint": "^10.1.0",
25 | "eslint": "^6.7.2",
26 | "eslint-plugin-prettier": "^3.4.0",
27 | "eslint-plugin-vue": "^6.2.2",
28 | "prettier": "^1.19.1",
29 | "tailwindcss": "^2.2.4",
30 | "vue-template-compiler": "^2.6.14"
31 | },
32 | "prettier": {
33 | "trailingComma": "es5",
34 | "tabWidth": 2,
35 | "semi": true,
36 | "singleQuote": false
37 | },
38 | "eslintConfig": {
39 | "root": true,
40 | "env": {
41 | "node": true
42 | },
43 | "extends": [
44 | "plugin:vue/essential",
45 | "eslint:recommended"
46 | ],
47 | "rules": {},
48 | "parserOptions": {
49 | "parser": "babel-eslint"
50 | }
51 | },
52 | "browserslist": [
53 | "> 1%",
54 | "last 2 versions"
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const tailwindcss = require("tailwindcss");
2 | const autoprefixer = require("autoprefixer");
3 |
4 | module.exports = {
5 | plugins: [
6 | tailwindcss("./tailwind.config.js"),
7 | autoprefixer({
8 | add: true,
9 | grid: true,
10 | }),
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garethredfern/sanctum-vue/f301222c04947d3b4fd48da33908a34e6039ec8e/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | sanctum-vue
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
31 |
--------------------------------------------------------------------------------
/src/assets/css/main.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @tailwind components;
4 |
5 | @tailwind utilities;
6 |
--------------------------------------------------------------------------------
/src/components/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
Welcome to your dashboard...
8 |
13 |
14 |
15 |
16 | Protected Page
17 |
18 |
19 |
20 |
21 |
22 |
37 |
--------------------------------------------------------------------------------
/src/components/Protected.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
Another protected page...
8 |
13 |
14 |
15 |
16 |
17 |
32 |
--------------------------------------------------------------------------------
/src/components/auth/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
65 |
66 |
67 |
93 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./App.vue";
3 | import router from "./router";
4 | import store from "./store/store";
5 |
6 | Vue.config.productionTip = false;
7 |
8 | new Vue({
9 | store,
10 | router,
11 | render: h => h(App)
12 | }).$mount("#app");
13 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import store from "@/store/store";
3 | import VueRouter from "vue-router";
4 | import Login from "./components/auth/Login";
5 |
6 | Vue.use(VueRouter);
7 |
8 | const routes = [
9 | {
10 | path: "/login",
11 | name: "login",
12 | component: Login,
13 | beforeEnter(to, from, next) {
14 | if (store.state.auth.token) {
15 | next(false);
16 | } else {
17 | next();
18 | }
19 | }
20 | },
21 | {
22 | path: "/",
23 | name: "dashboard",
24 | meta: { requiresAuth: true },
25 | // route level code-splitting
26 | // this generates a separate chunk (dashboard.[hash].js) for this route
27 | // which is lazy-loaded when the route is visited.
28 | component: () =>
29 | import(/* webpackChunkName: "dashboard" */ "./components/Dashboard")
30 | },
31 | {
32 | path: "/protected",
33 | name: "protected",
34 | meta: { requiresAuth: true },
35 | component: () =>
36 | import(/* webpackChunkName: "dashboard" */ "./components/Protected")
37 | }
38 | ];
39 |
40 | const router = new VueRouter({
41 | mode: "history",
42 | routes
43 | });
44 |
45 | router.beforeEach((to, from, next) => {
46 | const token = localStorage.getItem("token");
47 | if (to.matched.some(record => record.meta.requiresAuth) && !token) {
48 | next({ path: "/login", query: { redirect: to.fullPath } });
49 | } else {
50 | next(); // make sure to always call next()!
51 | }
52 | });
53 |
54 | export default router;
55 |
--------------------------------------------------------------------------------
/src/services/API.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is the initial API interface
3 | * we set the base URL for the API
4 | * store the token in local storage
5 | * append the token to all requests
6 | ? Both request & response are logged to the console.
7 | ! Remove the console logs for production.
8 | */
9 |
10 | import axios from "axios";
11 | import store from "../store/store";
12 |
13 | export const apiClient = axios.create({
14 | baseURL: "http://localhost:8080/api"
15 | });
16 |
17 | /*
18 | * Add a request interceptor
19 | @param config
20 | */
21 | apiClient.interceptors.request.use(
22 | function(config) {
23 | const token = window.localStorage.getItem("token");
24 | if (token != null) {
25 | config.headers.Authorization = `Bearer ${token}`;
26 | }
27 | return config;
28 | },
29 | function(error) {
30 | return Promise.reject(error.response);
31 | }
32 | );
33 |
34 | /*
35 | * Add a response interceptor
36 | */
37 | apiClient.interceptors.response.use(
38 | response => {
39 | return response;
40 | },
41 | function(error) {
42 | if (error.response.status === 401) {
43 | store.dispatch("auth/logout");
44 | }
45 | return Promise.reject(error.response);
46 | }
47 | );
48 |
--------------------------------------------------------------------------------
/src/services/AuthService.js:
--------------------------------------------------------------------------------
1 | import * as API from "./API.js";
2 |
3 | export default {
4 | login(payload) {
5 | return API.apiClient.post("/auth/login", payload);
6 | },
7 | logout() {
8 | return API.apiClient.post("/auth/logout");
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/src/store/modules/auth.js:
--------------------------------------------------------------------------------
1 | import AuthService from "@/services/AuthService";
2 |
3 | export const namespaced = true;
4 |
5 | export const state = {
6 | user: null,
7 | token: window.localStorage.getItem("token"),
8 | loading: false,
9 | error: null
10 | };
11 |
12 | export const mutations = {
13 | SET_USER(state, user) {
14 | state.user = user;
15 | },
16 | CLEAR_USER() {
17 | window.localStorage.clear();
18 | location.reload();
19 | },
20 | SET_TOKEN(state, token) {
21 | state.token = token;
22 | window.localStorage.setItem("token", token);
23 | },
24 | SET_LOADING(state, loading) {
25 | state.loading = loading;
26 | },
27 | SET_MESSAGE(state, message) {
28 | state.message = message;
29 | },
30 | SET_ERROR(state, error) {
31 | state.error = error;
32 | }
33 | };
34 |
35 | export const actions = {
36 | login({ commit }, payload) {
37 | commit("SET_LOADING", true);
38 | return AuthService.login(payload)
39 | .then(response => {
40 | commit("SET_TOKEN", response.data.token);
41 | commit("SET_LOADING", false);
42 | })
43 | .catch(error => {
44 | commit("SET_LOADING", false);
45 | commit("SET_ERROR", error.data ? error.data.message : error);
46 | });
47 | },
48 | logout({ commit }) {
49 | return AuthService.logout()
50 | .then(() => {
51 | commit("CLEAR_USER");
52 | })
53 | .catch(() => {
54 | commit("CLEAR_USER");
55 | });
56 | }
57 | };
58 |
59 | export const getters = {
60 | authUser: state => {
61 | return state.user;
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 | };
73 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 |
4 | import * as auth from "@/store/modules/auth.js";
5 |
6 | Vue.use(Vuex);
7 |
8 | export default new Vuex.Store({
9 | strict: true,
10 |
11 | modules: {
12 | auth
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | function({ addUtilities }) {
4 | const newUtilities = {
5 | ".trans": {
6 | transition: "all .25s"
7 | },
8 | ".trans-bg": {
9 | transition: "property background"
10 | },
11 | ".trans-slow": {
12 | transition: "duration .5s"
13 | },
14 | ".trans-slower": {
15 | transition: "duration .5s"
16 | },
17 | ".trans-fast": {
18 | transition: "duration .15s"
19 | },
20 | ".trans-faster": {
21 | transition: "duration .075s"
22 | }
23 | };
24 | addUtilities(newUtilities);
25 | }
26 | ],
27 | theme: {
28 | fontFamily: {
29 | sans: ["Open Sans", "sans-serif"]
30 | }
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | css: {
3 | sourceMap: true
4 | },
5 | devServer: {
6 | // proxy to use http://localhost:8080
7 | // this gets round CORS issues.
8 | proxy: "http://sanctum-api.test"
9 | }
10 | };
11 |
--------------------------------------------------------------------------------