├── static
└── .gitkeep
├── src
├── assets
│ └── logo.png
├── components
│ ├── marketing
│ │ ├── About.vue
│ │ └── LandingPage.vue
│ ├── TodoItemsRemaining.vue
│ ├── auth
│ │ ├── Logout.vue
│ │ ├── Login.vue
│ │ └── Register.vue
│ ├── TodoCheckAll.vue
│ ├── TodoClearCompleted.vue
│ ├── TodoFiltered.vue
│ ├── TodoItem.vue
│ ├── TodoList.vue
│ └── layouts
│ │ └── Master.vue
├── App.vue
├── routes.js
├── main.js
└── store
│ └── store.js
├── config
├── prod.env.js
├── dev.env.js
└── index.js
├── .editorconfig
├── .gitignore
├── .babelrc
├── .postcssrc.js
├── index.html
├── README.md
└── package.json
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drehimself/todo-vue/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/marketing/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Content for About Page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/marketing/LandingPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Landing Page content here
4 |
5 |
6 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/TodoItemsRemaining.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ remaining }} items left
3 |
4 |
5 |
15 |
16 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | todo-vue
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/auth/Logout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/TodoCheckAll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/TodoClearCompleted.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
18 |
19 |
32 |
--------------------------------------------------------------------------------
/src/components/TodoFiltered.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
24 |
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue.js Todo App
2 |
3 | 
4 |
5 |
6 |
7 | Code for YouTube Series on building a Todo App using Vue.js: [YouTube Link](https://www.youtube.com/playlist?list=PLEhEHUEU3x5q-xB1On4CsLPts0-rZ9oos)
8 |
9 | **Note:** Each video has a corresponding branch. The `master` branch is up to date with the latest video.
10 |
11 | ## Build Setup
12 |
13 | ``` bash
14 | # install dependencies
15 | npm install
16 |
17 | # serve with hot reload at localhost:8080
18 | npm run dev
19 |
20 | # build for production with minification
21 | npm run build
22 |
23 | ```
24 |
25 | ## Specific Branch
26 |
27 | If you want to checkout a certain branch:
28 |
29 | `git branch -a` to list all the branches.
30 |
31 | `git checkout 01-basics` (or any branch) to check it out.
32 |
33 | ## Laravel API
34 |
35 | The Laravel API repo can be [found here](https://github.com/drehimself/todo-laravel).
36 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import App from './App'
2 | import LandingPage from './components/marketing/LandingPage'
3 | import About from './components/marketing/About'
4 | import Login from './components/auth/Login'
5 | import Logout from './components/auth/Logout'
6 | import Register from './components/auth/Register'
7 |
8 | const routes = [
9 | {
10 | path: '/',
11 | name: 'home',
12 | component: LandingPage
13 | },
14 | {
15 | path: '/todo',
16 | name: 'todo',
17 | component: App,
18 | meta: {
19 | requiresAuth: true,
20 | }
21 | },
22 | {
23 | path: '/about',
24 | name: 'about',
25 | component: About
26 | },
27 | {
28 | path: '/login',
29 | name: 'login',
30 | component: Login,
31 | props: true,
32 | meta: {
33 | requiresVisitor: true,
34 | }
35 | },
36 | {
37 | path: '/register',
38 | name: 'register',
39 | component: Register,
40 | meta: {
41 | requiresVisitor: true,
42 | }
43 | },
44 | {
45 | path: '/logout',
46 | name: 'logout',
47 | component: Logout
48 | }
49 | ]
50 |
51 | export default routes
52 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import VueRouter from 'vue-router'
5 | import routes from './routes'
6 | import Master from './components/layouts/Master'
7 | import { store } from './store/store'
8 | import VeeValidate from 'vee-validate'
9 | import CxltToastr from 'cxlt-vue2-toastr'
10 |
11 | const toastrConfigs = {
12 | position: 'bottom right',
13 | showDuration: 2000,
14 | timeOut: 5000,
15 | progressBar: true,
16 | }
17 |
18 | window.eventBus = new Vue()
19 |
20 | Vue.config.productionTip = false
21 | Vue.use(VueRouter)
22 | Vue.use(VeeValidate)
23 | Vue.use(CxltToastr, toastrConfigs)
24 |
25 | const router = new VueRouter({
26 | routes,
27 | mode: 'history'
28 | })
29 |
30 | router.beforeEach((to, from, next) => {
31 | if (to.matched.some(record => record.meta.requiresAuth)) {
32 | if (!store.getters.loggedIn) {
33 | next({
34 | name: 'login',
35 | })
36 | } else {
37 | next()
38 | }
39 | } else if (to.matched.some(record => record.meta.requiresVisitor)) {
40 | if (store.getters.loggedIn) {
41 | next({
42 | name: 'todo',
43 | })
44 | } else {
45 | next()
46 | }
47 | } else {
48 | next()
49 | }
50 | })
51 |
52 | /* eslint-disable no-new */
53 | new Vue({
54 | el: '#app',
55 | router: router,
56 | store: store,
57 | components: { Master },
58 | template: ''
59 | })
60 |
--------------------------------------------------------------------------------
/src/components/auth/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
32 |
70 |
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-vue",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "Andre Madarang ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "build": "node build/build.js"
11 | },
12 | "dependencies": {
13 | "axios": "^0.18.0",
14 | "cxlt-vue2-toastr": "^1.1.0",
15 | "node-sass": "^4.8.3",
16 | "sass-loader": "^6.0.7",
17 | "vee-validate": "^2.0.9",
18 | "vue": "^2.5.2",
19 | "vue-router": "^3.0.1",
20 | "vuex": "^3.0.1"
21 | },
22 | "devDependencies": {
23 | "autoprefixer": "^7.1.2",
24 | "babel-core": "^6.22.1",
25 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
26 | "babel-loader": "^7.1.1",
27 | "babel-plugin-syntax-jsx": "^6.18.0",
28 | "babel-plugin-transform-runtime": "^6.22.0",
29 | "babel-plugin-transform-vue-jsx": "^3.5.0",
30 | "babel-preset-env": "^1.3.2",
31 | "babel-preset-stage-2": "^6.22.0",
32 | "chalk": "^2.0.1",
33 | "copy-webpack-plugin": "^4.0.1",
34 | "css-loader": "^0.28.0",
35 | "extract-text-webpack-plugin": "^3.0.0",
36 | "file-loader": "^1.1.4",
37 | "friendly-errors-webpack-plugin": "^1.6.1",
38 | "html-webpack-plugin": "^2.30.1",
39 | "node-notifier": "^5.1.2",
40 | "optimize-css-assets-webpack-plugin": "^3.2.0",
41 | "ora": "^1.2.0",
42 | "portfinder": "^1.0.13",
43 | "postcss-import": "^11.0.0",
44 | "postcss-loader": "^2.0.8",
45 | "postcss-url": "^7.2.1",
46 | "rimraf": "^2.6.0",
47 | "semver": "^5.3.0",
48 | "shelljs": "^0.7.6",
49 | "uglifyjs-webpack-plugin": "^1.1.1",
50 | "url-loader": "^0.5.8",
51 | "vue-loader": "^13.3.0",
52 | "vue-style-loader": "^3.0.1",
53 | "vue-template-compiler": "^2.5.2",
54 | "webpack": "^3.6.0",
55 | "webpack-bundle-analyzer": "^2.9.0",
56 | "webpack-dev-server": "^2.9.1",
57 | "webpack-merge": "^4.1.0"
58 | },
59 | "engines": {
60 | "node": ">= 6.0.0",
61 | "npm": ">= 3.0.0"
62 | },
63 | "browserslist": [
64 | "> 1%",
65 | "last 2 versions",
66 | "not ie <= 8"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '/',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: true,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/TodoItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 | ×
12 |
13 |
14 |
15 |
16 |
17 |
96 |
97 |
--------------------------------------------------------------------------------
/src/components/auth/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
39 |
40 |
41 |
82 |
--------------------------------------------------------------------------------
/src/components/TodoList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Welcome, {{ name }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
26 |
27 |
28 |
29 |
84 |
85 |
188 |
--------------------------------------------------------------------------------
/src/components/layouts/Master.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Home
5 | - App
6 | - About
7 | - Login
8 | - Register
9 | - Logout
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
29 |
30 |
204 |
205 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import axios from 'axios'
4 |
5 | Vue.use(Vuex)
6 | axios.defaults.baseURL = 'http://todo-laravel.test/api'
7 |
8 | export const store = new Vuex.Store({
9 | state: {
10 | token: localStorage.getItem('access_token') || null,
11 | filter: 'all',
12 | todos: [],
13 | },
14 | getters: {
15 | loggedIn(state) {
16 | return state.token !== null
17 | },
18 | remaining(state) {
19 | return state.todos.filter(todo => !todo.completed).length
20 | },
21 | anyRemaining(state, getters) {
22 | return getters.remaining != 0
23 | },
24 | todosFiltered(state) {
25 | if (state.filter == 'all') {
26 | return state.todos
27 | } else if (state.filter == 'active') {
28 | return state.todos.filter(todo => !todo.completed)
29 | } else if (state.filter == 'completed') {
30 | return state.todos.filter(todo => todo.completed)
31 | }
32 | return state.todos
33 | },
34 | showClearCompletedButton(state) {
35 | return state.todos.filter(todo => todo.completed).length > 0
36 | }
37 | },
38 | mutations: {
39 | addTodo(state, todo) {
40 | state.todos.push({
41 | id: todo.id,
42 | title: todo.title,
43 | completed: false,
44 | editing: false,
45 | })
46 | },
47 | updateTodo(state, todo) {
48 | const index = state.todos.findIndex(item => item.id == todo.id)
49 | state.todos.splice(index, 1, {
50 | 'id': todo.id,
51 | 'title': todo.title,
52 | 'completed': todo.completed,
53 | 'editing': todo.editing,
54 | })
55 | },
56 | deleteTodo(state, id) {
57 | const index = state.todos.findIndex(item => item.id == id)
58 | state.todos.splice(index, 1)
59 | },
60 | checkAll(state, checked) {
61 | state.todos.forEach(todo => (todo.completed = checked))
62 | },
63 | updateFilter(state, filter) {
64 | state.filter = filter
65 | },
66 | clearCompleted(state) {
67 | state.todos = state.todos.filter(todo => !todo.completed)
68 | },
69 | retrieveTodos(state, todos) {
70 | state.todos = todos
71 | },
72 | retrieveToken(state, token) {
73 | state.token = token
74 | },
75 | destroyToken(state) {
76 | state.token = null
77 | },
78 | clearTodos(state) {
79 | state.todos = []
80 | },
81 | },
82 | actions: {
83 | retrieveName(context) {
84 | axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token
85 |
86 | return new Promise((resolve, reject) => {
87 | axios.get('/user')
88 | .then(response => {
89 | resolve(response)
90 | })
91 | .catch(error => {
92 | reject(error)
93 | })
94 | })
95 | },
96 | clearTodos(context) {
97 | context.commit('clearTodos')
98 | },
99 | register(context, data) {
100 | return new Promise((resolve, reject) => {
101 | axios.post('/register', {
102 | name: data.name,
103 | email: data.email,
104 | password: data.password,
105 | })
106 | .then(response => {
107 | resolve(response)
108 | })
109 | .catch(error => {
110 | reject(error)
111 | })
112 | })
113 | },
114 | destroyToken(context) {
115 | axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token
116 |
117 | if (context.getters.loggedIn) {
118 | return new Promise((resolve, reject) => {
119 | axios.post('/logout')
120 | .then(response => {
121 | localStorage.removeItem('access_token')
122 | context.commit('destroyToken')
123 | resolve(response)
124 | // console.log(response);
125 | // context.commit('addTodo', response.data)
126 | })
127 | .catch(error => {
128 | localStorage.removeItem('access_token')
129 | context.commit('destroyToken')
130 | reject(error)
131 | })
132 | })
133 | }
134 | },
135 | retrieveToken(context, credentials) {
136 |
137 | return new Promise((resolve, reject) => {
138 | axios.post('/login', {
139 | username: credentials.username,
140 | password: credentials.password,
141 | })
142 | .then(response => {
143 | const token = response.data.access_token
144 |
145 | localStorage.setItem('access_token', token)
146 | context.commit('retrieveToken', token)
147 | resolve(response)
148 | // console.log(response);
149 | // context.commit('addTodo', response.data)
150 | })
151 | .catch(error => {
152 | console.log(error)
153 | reject(error)
154 | })
155 | })
156 | },
157 | retrieveTodos(context) {
158 | axios.defaults.headers.common['Authorization'] = 'Bearer ' + context.state.token
159 |
160 | axios.get('/todos')
161 | .then(response => {
162 | context.commit('retrieveTodos', response.data)
163 | })
164 | .catch(error => {
165 | console.log(error)
166 | })
167 | },
168 | addTodo(context, todo) {
169 | axios.post('/todos', {
170 | title: todo.title,
171 | completed: false,
172 | })
173 | .then(response => {
174 | context.commit('addTodo', response.data)
175 | })
176 | .catch(error => {
177 | console.log(error)
178 | })
179 | },
180 | updateTodo(context, todo) {
181 | axios.patch('/todos/' + todo.id, {
182 | title: todo.title,
183 | completed: todo.completed,
184 | })
185 | .then(response => {
186 | context.commit('updateTodo', response.data)
187 | })
188 | .catch(error => {
189 | console.log(error)
190 | })
191 | },
192 | deleteTodo(context, id) {
193 | axios.delete('/todos/' + id)
194 | .then(response => {
195 | context.commit('deleteTodo', id)
196 | })
197 | .catch(error => {
198 | console.log(error)
199 | })
200 | },
201 | checkAll(context, checked) {
202 | axios.patch('/todosCheckAll', {
203 | completed: checked,
204 | })
205 | .then(response => {
206 | context.commit('checkAll', checked)
207 | })
208 | .catch(error => {
209 | console.log(error)
210 | })
211 | },
212 | updateFilter(context, filter) {
213 |
214 | context.commit('updateFilter', filter)
215 |
216 | },
217 | clearCompleted(context) {
218 | const completed = context.state.todos
219 | .filter(todo => todo.completed)
220 | .map(todo => todo.id)
221 |
222 | axios.delete('/todosDeleteCompleted', {
223 | data: {
224 | todos: completed
225 | }
226 | })
227 | .then(response => {
228 | context.commit('clearCompleted')
229 | })
230 | .catch(error => {
231 | console.log(error)
232 | })
233 | }
234 | }
235 | })
236 |
--------------------------------------------------------------------------------