├── .babelrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── Dockerfile ├── Makefile ├── README.md ├── assets ├── README.md ├── style │ └── app.sass └── variables.scss ├── components ├── Logo.vue ├── README.md ├── SignInPreview.vue ├── VuetifyLogo.vue ├── post │ ├── Form.vue │ └── Show.vue └── settings │ └── ProfileShow.vue ├── docker-compose.yml ├── docker └── start_app.sh ├── jest.config.js ├── layouts ├── README.md └── default.vue ├── middleware └── README.md ├── models ├── Post.ts └── User.ts ├── nuxt.config.ts ├── package.json ├── pages ├── README.md ├── index.vue ├── posts │ ├── index.vue │ └── new.vue ├── settings │ └── profile.vue ├── sign_in.vue └── sign_out.vue ├── plugins ├── README.md ├── axios.ts ├── localStorage.js └── vuetify.js ├── static ├── README.md ├── favicon.ico ├── icon.png ├── nuxtjs_rails_icon.png ├── user.png └── v.png ├── store ├── README.md ├── post.ts └── user.ts ├── test ├── Logo.spec.js └── components │ ├── SignInPreview.spec.ts │ ├── post │ ├── Form.spec.ts │ └── Show.spec.ts │ └── settings │ └── ProfileShow.spec.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | [ 6 | "@babel/preset-env", 7 | { 8 | "targets": { 9 | "node": "current" 10 | } 11 | } 12 | ] 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: node:12 6 | working_directory: ~/repo 7 | 8 | steps: 9 | - checkout 10 | - run: yarn --version 11 | 12 | - restore_cache: 13 | keys: 14 | - v1-dependencies-{{ checksum "yarn.lock" }} 15 | - v1-dependencies- 16 | 17 | - run: yarn install --frozen-lockfile 18 | - run: ls -la node_modules 19 | - run: ls -la node_modules/.bin 20 | 21 | # TODO: not use -u option 22 | - run: yarn test -u 23 | 24 | - save_cache: 25 | paths: 26 | - node_modules 27 | key: v1-dependencies-{{ checksum "yarn.lock" }} 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: '@typescript-eslint/parser', 9 | ecmaFeatures: { 10 | legacyDecorators: true 11 | } 12 | }, 13 | extends: [ 14 | '@nuxtjs', 15 | '@nuxtjs/eslint-config-typescript', 16 | 'plugin:nuxt/recommended', 17 | 'plugin:prettier/recommended', 18 | 'prettier', 19 | 'prettier/vue' 20 | ], 21 | plugins: [ 22 | '@typescript-eslint', 23 | 'prettier' 24 | ], 25 | // add your custom rules here 26 | rules: { 27 | '@typescript-eslint/no-unused-vars': 'error', 28 | "no-console": "warn" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # test snapshots 26 | test/**/__snapshots__ 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # Nuxt generate 75 | dist 76 | 77 | # vuepress build output 78 | .vuepress/dist 79 | 80 | # Serverless directories 81 | .serverless 82 | 83 | # IDE 84 | .idea 85 | 86 | # Service worker 87 | sw.* 88 | 89 | .DS_Store 90 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.4.2] - 2019-07-03 8 | ### Fixed 9 | - [#13](https://github.com/walkersumida/nuxtjs-sample/pull/13) fix header 10 | 11 | ## [0.4.1] - 2019-06-30 12 | ### Changed 13 | - [#12](https://github.com/walkersumida/nuxtjs-sample/pull/12) Update SignIn page 14 | 15 | ## [0.4.0] - 2019-06-10 16 | ### Added 17 | - [#9](https://github.com/walkersumida/nuxtjs-sample/pull/9) delete post function 18 | 19 | ## [0.3.3] - 2019-06-01 20 | ### Changed 21 | - [#10](https://github.com/walkersumida/nuxtjs-sample/pull/10) Update axios(0.18.0->0.19.0) 22 | 23 | ## [0.3.2] - 2019-05-28 24 | ### Changed 25 | - [#8](https://github.com/walkersumida/nuxtjs-sample/pull/8) Change layout base color 26 | 27 | ## [0.3.1] - 2019-05-28 28 | ### Changed 29 | - [#7](https://github.com/walkersumida/nuxtjs-sample/pull/7) Update packages(nuxtjs2.6.3->2.7.1) 30 | 31 | ## [0.3.0] - 2019-05-12 32 | ### Added 33 | - [#6](https://github.com/walkersumida/nuxtjs-sample/pull/6) post new page 34 | 35 | ## [0.2.0] - 2019-05-05 36 | ### Added 37 | - [#5](https://github.com/walkersumida/nuxtjs-sample/pull/5) CircleCI 38 | 39 | ## [0.1.1] - 2019-05-05 40 | ### Changed 41 | - [#4](https://github.com/walkersumida/nuxtjs-sample/pull/4) Update packages 42 | 43 | ## [0.1.0] - 2019-05-05 44 | ### Added 45 | - [#2](https://github.com/walkersumida/nuxtjs-sample/pull/2) Token-base auth 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.22.10 2 | 3 | # install dependency package 4 | RUN apt-get update \ 5 | && apt-get install -y \ 6 | libssl-dev \ 7 | && apt-get clean \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | WORKDIR /app 11 | 12 | ENV HOST 0.0.0.0 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: tasks 2 | 3 | CMD= 4 | 5 | tasks: 6 | @echo Usage: make [task] 7 | @echo ------------------- 8 | @echo 9 | @cat Makefile 10 | 11 | docker_up: 12 | docker-compose up --build 13 | 14 | # e.g. $ make docker_exec CMD='bundle exec rspec' 15 | docker_exec: 16 | docker-compose exec app $(CMD) 17 | 18 | docker_bash: 19 | $(MAKE) docker_exec CMD='bash' 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxtjs-sample 2 | 3 | [![CircleCI](https://circleci.com/gh/walkersumida/nuxtjs-sample.svg?style=svg)](https://circleci.com/gh/walkersumida/nuxtjs-sample) 4 | 5 | TypeScript + Nuxt.js + Vuetify + Jest 6 | 7 | ![nuxtjs-sample](https://user-images.githubusercontent.com/12683375/79627129-b880f200-8170-11ea-8502-0c94c58e65e7.gif) 8 | 9 | ## Run docker 10 | 11 | ```bash 12 | # run docker 13 | $ make docker_up 14 | ``` 15 | 16 | ## Run test 17 | 18 | ```bash 19 | $ make docker_bash 20 | $ yarn test 21 | ``` 22 | 23 | ## Sample Pages 24 | ### Demo user 25 | [Demo user](https://github.com/walkersumida/rails-api-for-nuxtjs#demo-user). 26 | 27 | ### sign in 28 | http://0.0.0.0:8080/sign_in 29 | ### sign out 30 | http://0.0.0.0:8080/sign_out 31 | ### post index page(Authorization: required) 32 | http://0.0.0.0:8080/posts 33 | ### post new page(Authorization: required) 34 | http://0.0.0.0:8080/posts/new 35 | 36 | ## Build Setup 37 | 38 | ``` bash 39 | # install dependencies 40 | $ yarn install 41 | 42 | # serve with hot reload at localhost:3000 43 | $ yarn run dev 44 | 45 | # build for production and launch server 46 | $ yarn run build 47 | $ yarn start 48 | 49 | # generate static project 50 | $ yarn run generate 51 | ``` 52 | 53 | For detailed explanation on how things work, checkout [Nuxt.js docs](https://nuxtjs.org). 54 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /assets/style/app.sass: -------------------------------------------------------------------------------- 1 | // Import Vuetify styling 2 | // @require '~vuetify/src/styles/main' 3 | -------------------------------------------------------------------------------- /assets/variables.scss: -------------------------------------------------------------------------------- 1 | // Ref: https://github.com/nuxt-community/vuetify-module#customvariables 2 | // 3 | // The variables you want to modify 4 | // $font-size-root: 20px; 5 | -------------------------------------------------------------------------------- /components/Logo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /components/SignInPreview.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 53 | -------------------------------------------------------------------------------- /components/VuetifyLogo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /components/post/Form.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 46 | -------------------------------------------------------------------------------- /components/post/Show.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 54 | -------------------------------------------------------------------------------- /components/settings/ProfileShow.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 43 | 44 | 100 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | entrypoint: sh /app/docker/start_app.sh 9 | ports: 10 | - "8080:3000" 11 | volumes: 12 | - ".:/app" 13 | - yarn:/yarn 14 | - node_modules:/app/node_modules 15 | tty: true 16 | volumes: 17 | yarn: 18 | node_modules: 19 | -------------------------------------------------------------------------------- /docker/start_app.sh: -------------------------------------------------------------------------------- 1 | yarn install 2 | yarn run dev 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleNameMapper: { 3 | '^@/(.*)$': '/$1', 4 | '^~/(.*)$': '/$1', 5 | '^vue$': 'vue/dist/vue.common.js' 6 | }, 7 | moduleFileExtensions: ['ts', 'js', 'vue', 'json'], 8 | transform: { 9 | '^.+\\.ts$': 'ts-jest', 10 | '^.+\\.js$': 'babel-jest', 11 | '.*\\.(vue)$': 'vue-jest' 12 | }, 13 | collectCoverage: true, 14 | collectCoverageFrom: [ 15 | '/components/**/*.vue', 16 | '/pages/**/*.vue' 17 | ], 18 | globals: { 19 | 'ts-jest': { 20 | diagnostics: false 21 | } 22 | }, 23 | setupFiles: ['jest-plugin-context/setup'] 24 | } 25 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 101 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /models/Post.ts: -------------------------------------------------------------------------------- 1 | export default interface Post { 2 | id: number 3 | title: string 4 | body: string 5 | } 6 | -------------------------------------------------------------------------------- /models/User.ts: -------------------------------------------------------------------------------- 1 | export default interface User { 2 | id: string 3 | nickname: string 4 | } 5 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from '@nuxt/types' 2 | import vuetifyOptions from './plugins/vuetify.js' 3 | import pkg from './package.json' 4 | 5 | const config: Configuration = { 6 | mode: 'spa', 7 | 8 | /* 9 | ** Headers of the page 10 | */ 11 | head: { 12 | title: pkg.name, 13 | meta: [ 14 | { charset: 'utf-8' }, 15 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 16 | { hid: 'description', name: 'description', content: pkg.description } 17 | ], 18 | link: [ 19 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 20 | { 21 | rel: 'stylesheet', 22 | href: 23 | 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' 24 | } 25 | ] 26 | }, 27 | 28 | /* 29 | ** Environment variables 30 | */ 31 | env: { 32 | apiUrl: process.env.API_URL || 'http://0.0.0.0:3000', 33 | storeUrl: process.env.STORE_URL || 'http://0.0.0.0:3000' 34 | }, 35 | 36 | /* 37 | ** Customize the progress-bar color 38 | */ 39 | loading: { color: '#fff' }, 40 | 41 | /* 42 | ** Global CSS 43 | */ 44 | css: ['~/assets/style/app.sass', '@fortawesome/fontawesome-free/css/all.css'], 45 | 46 | /* 47 | ** Plugins to load before mounting the App 48 | */ 49 | plugins: [{ src: '@/plugins/localStorage.js', ssr: false }], 50 | 51 | /* 52 | ** Nuxt.js modules 53 | */ 54 | buildModules: [ 55 | '@nuxtjs/axios', 56 | '@nuxtjs/pwa', 57 | ['@nuxtjs/vuetify', vuetifyOptions], 58 | [ 59 | '@nuxt/typescript-build', 60 | { 61 | typeCheck: true, 62 | ignoreNotFoundWarnings: true 63 | } 64 | ] 65 | ], 66 | /* 67 | ** Axios module configuration 68 | */ 69 | axios: { 70 | // See https://github.com/nuxt-community/axios-module#options 71 | }, 72 | 73 | /* 74 | ** Build configuration 75 | */ 76 | build: { 77 | /* 78 | ** You can extend webpack config here 79 | */ 80 | extend(config, ctx) { 81 | // Run ESLint on save 82 | if (ctx.isDev && ctx.isClient) { 83 | if (!config.module) return 84 | config.module.rules.push({ 85 | enforce: 'pre', 86 | test: /\.(js|vue)$/, 87 | loader: 'eslint-loader', 88 | exclude: /(node_modules)/ 89 | }) 90 | } 91 | } 92 | } 93 | } 94 | 95 | export default config 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxtjs-sample", 3 | "version": "1.0.0", 4 | "description": "My tremendous Nuxt.js project", 5 | "author": "walkersumida", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt-ts", 9 | "build": "nuxt-ts build", 10 | "start": "nuxt-ts start", 11 | "generate": "nuxt-ts generate", 12 | "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .", 13 | "precommit": "npm run lint", 14 | "test": "jest" 15 | }, 16 | "dependencies": { 17 | "@nuxt/typescript-runtime": "^0.2.0", 18 | "@nuxtjs/axios": "^5.3.6", 19 | "@nuxtjs/pwa": "^2.6.0", 20 | "cross-env": "^5.2.0", 21 | "js-cookie": "^2.2.0", 22 | "nuxt": "~2.14.1", 23 | "vue-property-decorator": "^8.1.0", 24 | "vuex-persistedstate": "^2.5.4" 25 | }, 26 | "devDependencies": { 27 | "@fortawesome/fontawesome-free": "^5.12.1", 28 | "@nuxt/typescript-build": "^0.3.0", 29 | "@nuxtjs/eslint-config-typescript": "^0.1.3", 30 | "@nuxtjs/vuetify": "^1.12.3", 31 | "@types/jest": "^24.0.11", 32 | "@typescript-eslint/parser": "^2.0.0", 33 | "@vue/test-utils": "^1.0.0-beta.27", 34 | "babel-core": "7.0.0-bridge.0", 35 | "babel-eslint": "^10.0.1", 36 | "babel-jest": "^24.1.0", 37 | "eslint": "^5.15.1", 38 | "eslint-config-prettier": "^4.1.0", 39 | "eslint-config-standard": ">=12.0.0", 40 | "eslint-loader": "^2.1.2", 41 | "eslint-plugin-import": ">=2.16.0", 42 | "eslint-plugin-jest": ">=22.3.0", 43 | "eslint-plugin-node": ">=8.0.1", 44 | "eslint-plugin-nuxt": ">=0.4.2", 45 | "eslint-plugin-prettier": "^3.0.1", 46 | "eslint-plugin-promise": ">=4.0.1", 47 | "eslint-plugin-standard": ">=4.0.0", 48 | "eslint-plugin-vue": "^5.2.2", 49 | "jest": "^24.1.0", 50 | "jest-plugin-context": "^2.9.0", 51 | "nodemon": "^1.18.9", 52 | "prettier": "^1.16.4", 53 | "stylus": "^0.54.5", 54 | "stylus-loader": "^3.0.2", 55 | "ts-jest": "^24.0.2", 56 | "vue-jest": "^3.0.3", 57 | "vue-test-utils": "^1.0.0-beta.11" 58 | }, 59 | "resolutions": { 60 | "@babel/preset-env": "~7.12.17" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 71 | -------------------------------------------------------------------------------- /pages/posts/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | -------------------------------------------------------------------------------- /pages/posts/new.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /pages/settings/profile.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /pages/sign_in.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /pages/sign_out.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /plugins/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import * as Cookies from 'js-cookie' 3 | 4 | interface Headers { 5 | 'access-token': string 6 | client: string 7 | uid: string 8 | Accept?: string 9 | 'cache-control'?: string 10 | 'content-type'?: string 11 | expiry?: string 12 | 'token-type'?: string 13 | } 14 | 15 | const removeCookies = () => { 16 | Cookies.remove('headerAccessToken') 17 | Cookies.remove('headerClient') 18 | Cookies.remove('headerUid') 19 | Cookies.remove('headerExpiry') 20 | } 21 | 22 | const setHeaders = (headers: Headers) => { 23 | headers['access-token'] = Cookies.get('headerAccessToken') 24 | headers.client = Cookies.get('headerClient') 25 | headers.uid = Cookies.get('headerUid') 26 | return headers 27 | } 28 | 29 | const setCookie = (name: string, value: string) => { 30 | // TODO: `secure: true` when not dev 31 | Cookies.set(name, value, { secure: false }) 32 | } 33 | 34 | const setCookies = (headers: Headers) => { 35 | setCookie('headerClient', headers.client) 36 | setCookie('headerUid', headers.uid) 37 | if (headers['access-token']) { 38 | setCookie('headerAccessToken', headers['access-token']) 39 | } 40 | if (headers.expiry) { 41 | setCookie('headerExpiry', headers.expiry) 42 | } 43 | } 44 | 45 | const axiosInstance = axios.create({ 46 | baseURL: process.env.apiUrl 47 | }) 48 | 49 | axiosInstance.interceptors.request.use( 50 | config => { 51 | config.headers = setHeaders(config.headers) 52 | return config 53 | }, 54 | error => { 55 | return Promise.reject(error) 56 | } 57 | ) 58 | 59 | axiosInstance.interceptors.response.use( 60 | response => { 61 | // See https://devise-token-auth.gitbook.io/devise-token-auth/conceptual 62 | setCookies(response.headers) 63 | return response 64 | }, 65 | error => { 66 | removeCookies() 67 | // FIXME: redirect without using window.location 68 | if (error.response.status === 401) { 69 | window.location.href = '/sign_in' 70 | } 71 | return Promise.reject(error) 72 | } 73 | ) 74 | export default axiosInstance 75 | -------------------------------------------------------------------------------- /plugins/localStorage.js: -------------------------------------------------------------------------------- 1 | import createPersistedState from 'vuex-persistedstate' 2 | 3 | export default ({ store }) => { 4 | createPersistedState({})(store) 5 | } 6 | -------------------------------------------------------------------------------- /plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/es5/util/colors' 2 | 3 | const options = { 4 | theme: { 5 | dark: true, 6 | themes: { 7 | light: { 8 | primary: colors.blue.darken2, 9 | accent: colors.grey.darken3, 10 | secondary: colors.amber.darken3, 11 | info: colors.lightBlue.lighten1, 12 | warning: colors.amber.base, 13 | error: colors.deepOrange.accent4, 14 | success: colors.green.accent3 15 | }, 16 | dark: { 17 | primary: colors.blue.darken4, 18 | accent: colors.grey.darken4, 19 | secondary: colors.amber.darken4, 20 | info: colors.lightBlue.darken3, 21 | warning: colors.amber.darken4, 22 | error: colors.deepOrange.darken4, 23 | success: colors.green.darken4 24 | } 25 | } 26 | }, 27 | icons: { 28 | iconfont: 'fa', 29 | values: { 30 | post: 'fas fa-file', 31 | profile: 'fas fa-user', 32 | logout: 'fas fa-sign-out-alt' 33 | } 34 | } 35 | } 36 | export default options 37 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkersumida/nuxtjs-sample/5f43fa704529a27ff28d5e0404c350f8c462ea54/static/favicon.ico -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkersumida/nuxtjs-sample/5f43fa704529a27ff28d5e0404c350f8c462ea54/static/icon.png -------------------------------------------------------------------------------- /static/nuxtjs_rails_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkersumida/nuxtjs-sample/5f43fa704529a27ff28d5e0404c350f8c462ea54/static/nuxtjs_rails_icon.png -------------------------------------------------------------------------------- /static/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkersumida/nuxtjs-sample/5f43fa704529a27ff28d5e0404c350f8c462ea54/static/user.png -------------------------------------------------------------------------------- /static/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkersumida/nuxtjs-sample/5f43fa704529a27ff28d5e0404c350f8c462ea54/static/v.png -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /store/post.ts: -------------------------------------------------------------------------------- 1 | import axios from '~/plugins/axios' 2 | import Post from '~/models/Post' 3 | 4 | export const state = () => { 5 | return { 6 | data: [] 7 | } 8 | } 9 | 10 | export const mutations = { 11 | SET_POST(state, data) { 12 | state.data = data || [] 13 | } 14 | } 15 | 16 | export const getters = { 17 | data(state) { 18 | return state.data 19 | } 20 | } 21 | 22 | export const actions = { 23 | async index({ commit }) { 24 | const res = await axios.get('/posts') 25 | commit('SET_POST', res.data) 26 | }, 27 | async create({ _commit }, { title, body }) { 28 | const params = { 29 | post: { 30 | title, 31 | body 32 | } 33 | } 34 | await axios.post('/posts', params) 35 | }, 36 | async delete({ commit, getters }, { post }) { 37 | try { 38 | await axios.delete('/posts/' + post.id) 39 | const data: Post[] = removePost(post.id, getters.data) 40 | commit('SET_POST', data) 41 | } catch (error) { 42 | console.log(error) 43 | } 44 | } 45 | } 46 | 47 | const removePost = (postId: number, posts: Post[]) => { 48 | return posts.filter(p => { 49 | return p.id !== postId 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /store/user.ts: -------------------------------------------------------------------------------- 1 | import axios from '~/plugins/axios' 2 | 3 | export const state = () => { 4 | return { 5 | data: null 6 | } 7 | } 8 | 9 | export const mutations = { 10 | SET_USER(state, data) { 11 | state.data = data || null 12 | }, 13 | SET_USER_PROFILE_IMAGE(state, profileImage) { 14 | state.data.image = profileImage || null 15 | } 16 | } 17 | 18 | export const getters = { 19 | data(state) { 20 | return state.data 21 | } 22 | } 23 | 24 | export const actions = { 25 | async signIn({ commit }, { email, password }) { 26 | const params = new URLSearchParams() 27 | params.append('email', email) 28 | params.append('password', password) 29 | try { 30 | const res = await axios.post('/auth/sign_in', params) 31 | commit('SET_USER', res.data) 32 | } catch (error) { 33 | commit('SET_USER', null) 34 | } 35 | }, 36 | async show({ commit, rootGetters }, id) { 37 | try { 38 | if (rootGetters['user/data'] !== null) { 39 | return 40 | } 41 | const res = await axios.get('/users/' + id) 42 | commit('SET_USER', res.data) 43 | } catch (error) { 44 | commit('SET_USER', null) 45 | } 46 | }, 47 | async update({ commit }, { id, data }) { 48 | try { 49 | const params = { 50 | user: data 51 | } 52 | const res = await axios.patch('/users/' + id, params) 53 | commit('SET_USER', res.data) 54 | } catch (error) { 55 | console.log(error) 56 | commit('SET_USER', null) 57 | } 58 | }, 59 | async uploadProfileImage({ commit }, { id, data }) { 60 | try { 61 | const formData = new FormData() 62 | formData.append('image', data.image.files[0]) 63 | const res = await axios.post('/users/' + id + '/profile_image', formData) 64 | commit('SET_USER_PROFILE_IMAGE', res.data.image) 65 | } catch (error) { 66 | console.log(error) 67 | commit('SET_USER_PROFILE_IMAGE', '') 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Logo.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import Logo from '@/components/Logo.vue' 3 | 4 | describe('Logo', () => { 5 | test('is a Vue instance', () => { 6 | const wrapper = mount(Logo) 7 | expect(wrapper.isVueInstance()).toBeTruthy() 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/components/SignInPreview.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import SignInPreview from '@/components/SignInPreview.vue' 3 | 4 | describe('SignInPreview component', () => { 5 | let wrapper 6 | 7 | beforeEach(() => { 8 | wrapper = shallowMount(SignInPreview) 9 | }) 10 | 11 | it('has the expected html structure', () => { 12 | expect(wrapper.element).toMatchSnapshot() 13 | }) 14 | 15 | it('has the expected email element', () => { 16 | expect(wrapper.contains('v-text-field[type="email"]')).toBe(true) 17 | }) 18 | 19 | it('has the expected password element', () => { 20 | expect(wrapper.contains('v-text-field[type="password"]')).toBe(true) 21 | }) 22 | 23 | it('has the expected Sign In button', () => { 24 | expect(wrapper.contains('v-btn[name="signIn"]')).toBe(true) 25 | }) 26 | 27 | it('is called signIn() when click Sign In button', () => { 28 | const mock = jest.fn() 29 | wrapper.setMethods({ signIn: mock }) 30 | wrapper.find('v-btn[name="signIn"]').trigger('click') 31 | expect(mock).toBeCalled() 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/components/post/Form.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import PostForm from '@/components/post/Form.vue' 3 | 4 | describe('PostForm component', () => { 5 | let wrapper 6 | 7 | beforeEach(() => { 8 | wrapper = shallowMount(PostForm) 9 | }) 10 | 11 | it('has the expected html structure', () => { 12 | expect(wrapper.element).toMatchSnapshot() 13 | }) 14 | 15 | it('has the expected title element', () => { 16 | expect(wrapper.contains('v-text-field[label="Title"]')).toBe(true) 17 | }) 18 | 19 | it('has the expected body element', () => { 20 | expect(wrapper.contains('v-textarea[label="Body"]')).toBe(true) 21 | }) 22 | 23 | it('has the expected button element', () => { 24 | expect(wrapper.contains('v-btn[disabled="true"]')).toBe(true) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /test/components/post/Show.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import PostShow from '@/components/post/Show.vue' 3 | 4 | describe('PostShow component', () => { 5 | let wrapper 6 | 7 | beforeEach(() => { 8 | wrapper = shallowMount(PostShow, { 9 | propsData: { 10 | post: { 11 | id: 1, 12 | title: 'AAA', 13 | body: 'aaa' 14 | } 15 | } 16 | }) 17 | }) 18 | 19 | it('has the expected html structure', () => { 20 | expect(wrapper.element).toMatchSnapshot() 21 | }) 22 | 23 | it('has the expected text', () => { 24 | expect(wrapper.text()).toBe('AAA aaa Delete Are you sure? No Yes') 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /test/components/settings/ProfileShow.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import ProfileShow from '@/components/settings/ProfileShow.vue' 3 | 4 | describe('ProfileShow component', () => { 5 | let wrapper 6 | let $store 7 | 8 | beforeEach(() => { 9 | $store = { getters: { 'user/data': { image: { url: null } } } } 10 | wrapper = shallowMount(ProfileShow, { 11 | propsData: { 12 | user: { nickname: 'demo' } 13 | }, 14 | mocks: { $store } 15 | }) 16 | }) 17 | 18 | it('has the expected html structure', () => { 19 | expect(wrapper.element).toMatchSnapshot() 20 | }) 21 | 22 | it('has the expected nickname element', () => { 23 | expect(wrapper.contains('v-text-field[label="Nickname"]')).toBe(true) 24 | }) 25 | 26 | it('has the expected button element', () => { 27 | expect(wrapper.contains('v-btn[disabled="true"]')).toBe(true) 28 | }) 29 | 30 | it('has the expected default image', () => { 31 | expect(wrapper.contains('v-img[src="/user.png"]')).toBe(true) 32 | }) 33 | }) 34 | 35 | describe('ProfileShow component with custom user image', () => { 36 | let wrapper 37 | let $store 38 | 39 | beforeEach(() => { 40 | $store = { 41 | getters: { 'user/data': { image: { url: '/custom_image.png' } } } 42 | } 43 | wrapper = shallowMount(ProfileShow, { 44 | propsData: { 45 | user: { nickname: 'demo' } 46 | }, 47 | mocks: { $store } 48 | }) 49 | }) 50 | 51 | it('has the expected custom image', () => { 52 | const img = wrapper.find('v-img') 53 | // FIXME: set process.env.storeUrl 54 | expect(img.attributes().src).toBe('undefined/custom_image.png') 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext", 8 | "esnext.asynciterable", 9 | "dom" 10 | ], 11 | "esModuleInterop": true, 12 | "experimentalDecorators": true, 13 | "allowJs": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "noImplicitAny": false, 17 | "noEmit": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "~/*": [ 21 | "./*" 22 | ], 23 | "@/*": [ 24 | "./*" 25 | ] 26 | }, 27 | "types": [ 28 | "@types/node", 29 | "@nuxt/types", 30 | "@nuxtjs/vuetify", 31 | "@types/jest" 32 | ], 33 | "resolveJsonModule": true 34 | }, 35 | "exclude": [ 36 | "node_modules" 37 | ] 38 | } 39 | --------------------------------------------------------------------------------