├── vue.config.js
├── public
├── video.mp4
├── favicon.ico
└── index.html
├── babel.config.js
├── src
├── assets
│ ├── logo.png
│ └── logo.svg
├── views
│ ├── About.vue
│ ├── Channel
│ │ ├── Index.vue
│ │ └── Home.vue
│ ├── Studio
│ │ ├── Index.vue
│ │ ├── Dashboard.vue
│ │ ├── Details.vue
│ │ └── Video.vue
│ ├── Auth
│ │ ├── SignIn.vue
│ │ └── SignUp.vue
│ ├── Home.vue
│ ├── LikedVideo.vue
│ ├── Subscription.vue
│ ├── Trending.vue
│ ├── Search.vue
│ └── History.vue
├── plugins
│ └── vuetify.js
├── services
│ ├── CategoryService.js
│ ├── SearchService.js
│ ├── ReplyService.js
│ ├── CommentService.js
│ ├── FeelingService.js
│ ├── HistoryService.js
│ ├── UserService.js
│ ├── AuthenticationService.js
│ ├── VideoService.js
│ ├── Api.js
│ └── SubscriptionService.js
├── App.vue
├── components
│ ├── SigninModal.vue
│ ├── VideoCard.vue
│ ├── comments
│ │ ├── AddComment.vue
│ │ └── CommentList.vue
│ ├── HelloWorld.vue
│ ├── SubscribersModal.vue
│ ├── UploadVideoModal.vue
│ ├── SettingsModal.vue
│ ├── StudioNavBar.vue
│ └── NavBar.vue
├── store
│ ├── modules
│ │ ├── comment.js
│ │ └── auth.js
│ └── index.js
├── main.js
└── router
│ └── index.js
├── screenshots
├── 1 - Home.jpg
├── 7 - Watch.jpg
├── 14 - Videos.jpg
├── 19 - Search.jpg
├── 2 - Trending.jpg
├── 20 - Sign in.jpg
├── 21 - Sign up.jpg
├── 9 - Channel.jpg
├── 10 - Dashboard.jpg
├── 18 - Settings.jpg
├── 12 - Upload Modal.jpg
├── 3 - Subscriptions.jpg
├── 4 - Watch History.jpg
├── 6 - Liked Videos.jpg
├── 13 - Video Details.jpg
├── 5 - Search History.jpg
├── 8 - Comment - Reply.jpg
├── 11 - Subscribers Modal.jpg
├── 15 - Edit Video Details.jpg
├── 17 - Delete Video Modal.jpg
└── 16 - Upload Thumbnail Modal.jpg
├── Dockerfile
├── docker-compose.yml
├── .gitignore
├── package.json
└── README.md
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "transpileDependencies": [
3 | "vuetify"
4 | ]
5 | }
--------------------------------------------------------------------------------
/public/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/public/video.mp4
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/screenshots/1 - Home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/1 - Home.jpg
--------------------------------------------------------------------------------
/screenshots/7 - Watch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/7 - Watch.jpg
--------------------------------------------------------------------------------
/screenshots/14 - Videos.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/14 - Videos.jpg
--------------------------------------------------------------------------------
/screenshots/19 - Search.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/19 - Search.jpg
--------------------------------------------------------------------------------
/screenshots/2 - Trending.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/2 - Trending.jpg
--------------------------------------------------------------------------------
/screenshots/20 - Sign in.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/20 - Sign in.jpg
--------------------------------------------------------------------------------
/screenshots/21 - Sign up.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/21 - Sign up.jpg
--------------------------------------------------------------------------------
/screenshots/9 - Channel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/9 - Channel.jpg
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/screenshots/10 - Dashboard.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/10 - Dashboard.jpg
--------------------------------------------------------------------------------
/screenshots/18 - Settings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/18 - Settings.jpg
--------------------------------------------------------------------------------
/screenshots/12 - Upload Modal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/12 - Upload Modal.jpg
--------------------------------------------------------------------------------
/screenshots/3 - Subscriptions.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/3 - Subscriptions.jpg
--------------------------------------------------------------------------------
/screenshots/4 - Watch History.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/4 - Watch History.jpg
--------------------------------------------------------------------------------
/screenshots/6 - Liked Videos.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/6 - Liked Videos.jpg
--------------------------------------------------------------------------------
/screenshots/13 - Video Details.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/13 - Video Details.jpg
--------------------------------------------------------------------------------
/screenshots/5 - Search History.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/5 - Search History.jpg
--------------------------------------------------------------------------------
/screenshots/8 - Comment - Reply.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/8 - Comment - Reply.jpg
--------------------------------------------------------------------------------
/screenshots/11 - Subscribers Modal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/11 - Subscribers Modal.jpg
--------------------------------------------------------------------------------
/screenshots/15 - Edit Video Details.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/15 - Edit Video Details.jpg
--------------------------------------------------------------------------------
/screenshots/17 - Delete Video Modal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/17 - Delete Video Modal.jpg
--------------------------------------------------------------------------------
/screenshots/16 - Upload Thumbnail Modal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techreagan/vue-nodejs-youtube-clone/HEAD/screenshots/16 - Upload Thumbnail Modal.jpg
--------------------------------------------------------------------------------
/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuetify from 'vuetify/lib';
3 |
4 | Vue.use(Vuetify);
5 |
6 | export default new Vuetify({
7 | });
8 |
--------------------------------------------------------------------------------
/src/services/CategoryService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | getAll() {
5 | return Api().get(`categories`)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/services/SearchService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | search(page, data) {
5 | return Api().post(`search?page=${page}`, data)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/views/Channel/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16-alpine3.17
2 |
3 | WORKDIR /app
4 |
5 | COPY package*.json .
6 |
7 | RUN npm install
8 |
9 | COPY . .
10 |
11 | EXPOSE 8080
12 |
13 | CMD ["npm", "run", "serve"]
--------------------------------------------------------------------------------
/src/views/Studio/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 |
3 | services:
4 | frontend:
5 | build: .
6 | ports:
7 | - 8080:8080
8 | env_file:
9 | - .env.development.local
10 | volumes:
11 | - .:/app
12 | - /app/node_modules
13 |
--------------------------------------------------------------------------------
/src/services/ReplyService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | createReply(data) {
5 | return Api().post('replies', data)
6 | },
7 | deleteById(id) {
8 | return Api().delete(`replies/${id}`)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/services/CommentService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | getCommentByVideoId(filters) {
5 | return Api().get(`comments/${filters}/videos`)
6 | },
7 | createComment(data) {
8 | return Api().post('comments', data)
9 | },
10 | deleteById(id) {
11 | return Api().delete(`comments/${id}`)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/services/FeelingService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | checkFeeling(data) {
5 | return Api().post('feelings/check', data)
6 | },
7 | createFeeling(data) {
8 | return Api().post('feelings', data)
9 | },
10 | getLikedVideos(page) {
11 | return Api().get('feelings/videos', {
12 | params: {
13 | page,
14 | limit: 12
15 | }
16 | })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/services/HistoryService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | getAll(params) {
5 | return Api().get('histories', { params: params })
6 | },
7 | createHistory(data) {
8 | return Api().post('histories', data)
9 | },
10 | deleteById(id) {
11 | return Api().delete(`histories/${id}`)
12 | },
13 | deleteAll(type) {
14 | return Api().delete(`histories/${type}/all`)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/services/UserService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | getAll(filters) {
5 | return Api().get(`users/?${filters}`)
6 | },
7 | getById(id) {
8 | return Api().get(`users/${id}`)
9 | },
10 | createUser(data) {
11 | return Api().post('users', data)
12 | },
13 | updateUser(data, id) {
14 | return Api().put(`users/${id}`, data)
15 | },
16 | deleteById(id) {
17 | return Api().delete(`users/${id}`)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 | Artboard 46
2 |
--------------------------------------------------------------------------------
/src/services/AuthenticationService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | signIn(credentials) {
5 | return Api().post('auth/login', credentials)
6 | },
7 | signUp(data) {
8 | return Api().post('auth/register', data)
9 | },
10 | updateUserDetails(data) {
11 | return Api().put('auth/updatedetails', data)
12 | },
13 | uploadUserAvatar(data) {
14 | return Api().put('auth/avatar', data)
15 | },
16 | updatePassword(data) {
17 | return Api().put('auth/updatepassword', data)
18 | },
19 | me(token) {
20 | return Api().post('auth/me', {
21 | headers: { Authorization: `Bearer ${token}` }
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/services/VideoService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | getAll(data, params) {
5 | return Api().get(`videos/${data}`, {
6 | params
7 | })
8 | },
9 | getById(id) {
10 | return Api().get(`videos/${id}`)
11 | },
12 | uploadVideo(data, optional) {
13 | return Api().post('videos', data, optional)
14 | },
15 | updateVideo(id, data) {
16 | return Api().put(`videos/${id}`, data)
17 | },
18 | updateViews(id) {
19 | return Api().put(`videos/${id}/views`)
20 | },
21 | uploadThumbnail(id, data) {
22 | return Api().put(`videos/${id}/thumbnails`, data)
23 | },
24 | deleteById(id) {
25 | return Api().delete(`videos/${id}`)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/services/Api.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default () => {
4 | const axiosInstance = axios.create({
5 | baseURL: `${process.env.VUE_APP_URL}/api/v1`
6 | })
7 |
8 | const token = localStorage.getItem('token')
9 | if (token) {
10 | axiosInstance.defaults.headers.common.Authorization = `Bearer ${token}`
11 | }
12 |
13 | axiosInstance.interceptors.response.use(
14 | (response) => response,
15 | (error) => {
16 | if (error.response.status === 401) {
17 | localStorage.removeItem('token')
18 | localStorage.removeItem('user')
19 | location.reload()
20 | }
21 | return Promise.reject(error)
22 | }
23 | )
24 |
25 | return axiosInstance
26 | }
27 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
37 |
--------------------------------------------------------------------------------
/src/services/SubscriptionService.js:
--------------------------------------------------------------------------------
1 | import Api from '@/services/Api'
2 |
3 | export default {
4 | getSubscribedChannels(subscriberId) {
5 | return Api().get('subscriptions/channels', {
6 | params: {
7 | subscriberId,
8 | select: 'channelId'
9 | }
10 | })
11 | },
12 | getSubscribers(limit) {
13 | return Api().get('subscriptions/subscribers', {
14 | params: {
15 | limit
16 | }
17 | })
18 | },
19 | checkSubscription(data) {
20 | return Api().post('subscriptions/check', data)
21 | },
22 | createSubscription(data) {
23 | return Api().post('subscriptions', data)
24 | },
25 | getSubscribedVideos(page) {
26 | return Api().get('subscriptions/videos', {
27 | params: {
28 | page,
29 | limit: 12
30 | }
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
13 |
14 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/SigninModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ details.title }}
6 | {{ details.text }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Sign in
15 |
16 |
17 |
18 |
19 |
20 |
21 |
45 |
--------------------------------------------------------------------------------
/src/store/modules/comment.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import CommentService from '@/services/CommentService'
4 |
5 | Vue.use(Vuex)
6 |
7 | export default {
8 | state: {
9 | comments: []
10 | },
11 | getters: {
12 | getComments: (state) => {
13 | return state.comments
14 | }
15 | },
16 | mutations: {
17 | setComments(state, comments) {
18 | state.comments = comments
19 | },
20 | addComment(state, comment) {
21 | // console.log('hello', comment)
22 | state.comments.data.unshift(comment)
23 | // console.log(state.comments.data)
24 | }
25 | },
26 | actions: {
27 | setComments({ commit }, videoId) {
28 | return new Promise((resolve, reject) => {
29 | CommentService.getCommentByVideoId(videoId)
30 | .then((comments) => {
31 | commit('setComments', comments.data)
32 | resolve(comments)
33 | })
34 | .catch((err) => reject(err))
35 | })
36 | },
37 | addComment({ commit }, comment) {
38 | commit('addComment', comment)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuetube",
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.19.2",
12 | "core-js": "^3.6.4",
13 | "moment": "^2.24.0",
14 | "vee-validate": "^3.2.5",
15 | "vue": "^2.6.11",
16 | "vue-image-crop-upload": "^2.5.0",
17 | "vue-infinite-loading": "^2.4.5",
18 | "vue-router": "^3.1.6",
19 | "vuebar": "0.0.20",
20 | "vuetify": "^2.2.11",
21 | "vuex": "^3.1.3"
22 | },
23 | "devDependencies": {
24 | "@vue/cli-plugin-babel": "^4.3.0",
25 | "@vue/cli-plugin-eslint": "^4.3.0",
26 | "@vue/cli-plugin-router": "^4.3.0",
27 | "@vue/cli-plugin-vuex": "^4.3.0",
28 | "@vue/cli-service": "^4.3.0",
29 | "babel-eslint": "^10.1.0",
30 | "eslint": "^6.7.2",
31 | "eslint-plugin-vue": "^6.2.2",
32 | "sass": "^1.19.0",
33 | "sass-loader": "^8.0.0",
34 | "vue-cli-plugin-vuetify": "^2.0.5",
35 | "vue-template-compiler": "^2.6.11",
36 | "vuetify-loader": "^1.3.0"
37 | },
38 | "eslintConfig": {
39 | "root": true,
40 | "env": {
41 | "node": true
42 | },
43 | "extends": [
44 | "plugin:vue/essential",
45 | "eslint:recommended"
46 | ],
47 | "parserOptions": {
48 | "parser": "babel-eslint"
49 | },
50 | "rules": {}
51 | },
52 | "browserslist": [
53 | "> 1%",
54 | "last 2 versions",
55 | "not dead"
56 | ]
57 | }
--------------------------------------------------------------------------------
/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 vuetify from './plugins/vuetify'
6 | import { required, email, max, min, size, oneOf } from 'vee-validate/dist/rules'
7 | import {
8 | extend,
9 | ValidationObserver,
10 | ValidationProvider,
11 | setInteractionMode
12 | } from 'vee-validate'
13 | import Vuebar from 'vuebar'
14 | // import InfiniteLoading from 'vue-infinite-loading'
15 |
16 | setInteractionMode('eager')
17 |
18 | extend('required', {
19 | ...required,
20 | message: 'Enter {_field_}'
21 | })
22 |
23 | extend('oneOf', {
24 | ...oneOf
25 | })
26 |
27 | extend('max', {
28 | ...max,
29 | message: '{_field_} may not be greater than {length} characters'
30 | })
31 |
32 | extend('min', {
33 | ...min,
34 | message: '{_field_} may not be less than {length} characters'
35 | })
36 |
37 | extend('email', {
38 | ...email,
39 | message: 'Email must be valid'
40 | })
41 |
42 | extend('password', {
43 | params: ['target'],
44 | validate(value, { target }) {
45 | return value === target
46 | },
47 | message: 'Password does not match'
48 | })
49 |
50 | extend('size', {
51 | ...size,
52 | message: 'video size should be less than 5 MB!'
53 | })
54 |
55 | Vue.config.productionTip = false
56 | Vue.component('ValidationProvider', ValidationProvider)
57 | Vue.component('ValidationObserver', ValidationObserver)
58 |
59 | // Vue.use(InfiniteLoading, {
60 | // props: {
61 | // distance: null
62 | // /* other props need to configure */
63 | // }
64 | // })
65 |
66 | // Vue.component('InfiniteLoading', InfiniteLoading)
67 |
68 | Vue.use(Vuebar)
69 |
70 | new Vue({
71 | router,
72 | store,
73 | vuetify,
74 | render: (h) => h(App)
75 | }).$mount('#app')
76 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | import auth from './modules/auth'
5 | import comments from './modules/comment'
6 |
7 | Vue.use(Vuex)
8 |
9 | export default new Vuex.Store({
10 | state: {
11 | // url: process.env.VUE_APP_URL,
12 | // token: localStorage.getItem('token') || null,
13 | // user: JSON.parse(localStorage.getItem('user')) || null,
14 | // isUserLoggedIn: localStorage.getItem('token') || false
15 | },
16 | getters: {
17 | // getUrl: (state) => {
18 | // return state.url
19 | // },
20 | // isAuthenticated: (state) => {
21 | // return state.isUserLoggedIn
22 | // },
23 | // getToken: (state) => {
24 | // return state.token
25 | // },
26 | // currentUser: (state) => {
27 | // return state.user
28 | // }
29 | },
30 | mutations: {
31 | // setToken(state, token) {
32 | // state.token = token
33 | // if (token) {
34 | // state.isUserLoggedIn = true
35 | // } else {
36 | // state.isUserLoggedIn = false
37 | // }
38 | // },
39 | // setUser(state, user) {
40 | // state.user = user
41 | // },
42 | // clearAuthData(state) {
43 | // state.token = null
44 | // state.user = null
45 | // state.isUserLoggedIn = false
46 | // }
47 | },
48 | actions: {
49 | // setToken({ commit }, token) {
50 | // commit('setToken', token)
51 | // localStorage.setItem('token', token)
52 | // },
53 | // signin({ commit }, user) {
54 | // commit('setUser', user)
55 | // localStorage.setItem('user', JSON.stringify(user))
56 | // },
57 | // signOut({ commit }) {
58 | // commit('clearAuthData')
59 | // localStorage.removeItem('token')
60 | // localStorage.removeItem('user')
61 | // }
62 | },
63 | modules: { auth, comments }
64 | })
65 |
--------------------------------------------------------------------------------
/src/views/Studio/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
channel dashboard
6 |
7 |
8 |
9 | mdi-upload
16 |
17 | Upload video
18 |
19 |
20 |
21 |
22 |
23 |
24 | Recent subscribers
25 |
26 |
27 |
28 | See all
29 |
30 |
31 |
32 |
33 |
34 |
35 |
39 |
43 |
44 |
45 |
46 |
63 |
64 |
69 |
--------------------------------------------------------------------------------
/src/components/VideoCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
25 | {{ channel.channelName.split('')[0].toUpperCase() }}
27 |
28 |
29 |
30 |
31 |
32 |
36 | {{ video.title }}
37 |
38 |
39 |
40 | {{ channel.name }}
41 |
42 |
43 | {{ video.views }} viewsmdi-circle-small {{ dateFormatter(video.createdAt) }}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/store/modules/auth.js:
--------------------------------------------------------------------------------
1 | import AuthenticationService from '@/services/AuthenticationService'
2 |
3 | const state = {
4 | url: process.env.VUE_APP_URL,
5 | token: localStorage.getItem('token') || null,
6 | user: JSON.parse(localStorage.getItem('user')) || null,
7 | isUserLoggedIn: localStorage.getItem('token') || false
8 | }
9 |
10 | const getters = {
11 | getUrl: (state) => {
12 | return state.url
13 | },
14 | isAuthenticated: (state) => {
15 | return state.isUserLoggedIn
16 | },
17 | getToken: (state) => {
18 | return state.token
19 | },
20 | currentUser: (state) => {
21 | return state.user
22 | }
23 | }
24 |
25 | const mutations = {
26 | SET_TOKEN(state, token) {
27 | state.token = token
28 | if (token) {
29 | state.isUserLoggedIn = true
30 | } else {
31 | state.isUserLoggedIn = false
32 | }
33 | },
34 | SET_USER_DATA(state, data) {
35 | state.user = data
36 | },
37 | CLEAR_AUTH(state) {
38 | state.token = null
39 | state.user = null
40 | state.isUserLoggedIn = false
41 | }
42 | }
43 |
44 | const actions = {
45 | signUp({ commit }, credentials) {
46 | return new Promise((resolve, reject) => {
47 | AuthenticationService.signUp(credentials)
48 | .then(({ data }) => {
49 | commit('SET_TOKEN', data.token)
50 | localStorage.setItem('token', data.token)
51 | resolve(data)
52 | })
53 | .catch((err) => reject(err))
54 | })
55 | },
56 | signIn({ commit }, credentials) {
57 | return new Promise((resolve, reject) => {
58 | AuthenticationService.signIn(credentials)
59 | .then(({ data }) => {
60 | localStorage.setItem('token', data.token)
61 | commit('SET_TOKEN', data.token)
62 |
63 | resolve(data)
64 | })
65 | .catch((err) => reject(err))
66 | })
67 | },
68 | getCurrentUser({ commit }, token) {
69 | return new Promise((resolve, reject) => {
70 | AuthenticationService.me(token)
71 | .then(({ data }) => {
72 | localStorage.setItem('user', JSON.stringify(data.data))
73 | commit('SET_USER_DATA', data.data)
74 | resolve(data)
75 | })
76 | .catch((err) => reject(err))
77 | })
78 | },
79 | signOut({ commit }) {
80 | localStorage.removeItem('token')
81 | localStorage.removeItem('user')
82 | commit('CLEAR_AUTH')
83 | }
84 | }
85 |
86 | export default {
87 | state,
88 | getters,
89 | mutations,
90 | actions
91 | }
92 |
--------------------------------------------------------------------------------
/src/components/comments/AddComment.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | mdi-account
7 |
8 |
12 |
13 |
14 |
15 | {{ currentUser.channelName.split('')[0].toUpperCase() }}
17 |
18 |
19 |
20 |
25 |
26 |
27 | Cancel
28 | Comment
37 |
38 |
39 |
40 |
41 |
42 |
43 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/views/Auth/SignIn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
VueTube
8 |
Sign in
9 |
10 |
11 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Error!
8 |
9 | Something went wrong, but don’t fret — let’s give it another shot.
10 |
11 |
12 |
13 | Take action
14 |
15 |
16 |
17 |
18 |
19 | Recommended
20 |
21 |
30 |
31 |
36 |
37 |
38 |
39 | No videos yet
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Error!
56 |
57 | Something went wrong, but don’t fret — let’s give it
58 | another shot.
59 |
60 |
61 |
62 | Take action
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
127 |
128 |
133 |
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 | Welcome to Vuetify
16 |
17 |
18 |
19 | For help and collaboration with other Vuetify developers,
20 | please join our online
21 | Discord Community
25 |
26 |
27 |
28 |
32 |
33 | What's next?
34 |
35 |
36 |
37 |
44 | {{ next.text }}
45 |
46 |
47 |
48 |
49 |
53 |
54 | Important Links
55 |
56 |
57 |
58 |
65 | {{ link.text }}
66 |
67 |
68 |
69 |
70 |
74 |
75 | Ecosystem
76 |
77 |
78 |
79 |
86 | {{ eco.text }}
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
152 |
--------------------------------------------------------------------------------
/src/views/LikedVideo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Error!
8 |
9 | Something went wrong, but don’t fret — let’s give it another shot.
10 |
11 |
12 |
13 | Take action
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
30 |
31 |
36 |
37 |
38 |
39 | No liked videos yet
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Error!
56 |
57 | Something went wrong, but don’t fret — let’s give it
58 | another shot.
59 |
60 |
61 |
62 | Take action
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
126 |
127 |
132 |
--------------------------------------------------------------------------------
/src/views/Subscription.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Error!
8 |
9 | Something went wrong, but don’t fret — let’s give it another shot.
10 |
11 |
12 |
13 | Take action
14 |
15 |
16 |
17 |
18 |
19 |
20 |
29 |
30 |
35 |
36 |
37 |
38 | You haven't subscribed to any channel yet
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Error!
55 |
56 | Something went wrong, but don’t fret — let’s give it
57 | another shot.
58 |
59 |
60 |
61 | Take action
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
129 |
130 |
135 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VueTube Vue Frontend - YouTube Clone
2 |
3 | > This is the frontend (VueJS) of the VueTube clone
4 |
5 | ## API
6 |
7 | Backend RESTFUL API Repository [API](https://github.com/techreagan/youtube-clone-nodejs-api)
8 |
9 | ## Features
10 |
11 | - Sign in / Sign Up to create channel
12 | - Video
13 | - Upload video
14 | - Upload video thumbnail
15 | - Watch video
16 | - Increase Views
17 | - Like and dislike video
18 | - Download video
19 | - Comment & reply for video
20 | - Update video details
21 | - Delete video
22 | - Subscribe to a channel
23 | - View liked videos
24 | - Trending
25 | - Subscriptions
26 | - History
27 | - Watch history
28 | - Search history
29 | - Settings
30 | - Modify channel name and email
31 | - Change password
32 | - Upload channel avatar
33 |
34 | ## Project setup
35 |
36 | Create .env.development.local for development then .env.production.local for production ready app.
37 | Then put in your api URL
38 |
39 | ```
40 | VUE_APP_URL=http://localhost:3001
41 | ```
42 |
43 | ### Install packages
44 |
45 | ```
46 | npm install
47 | ```
48 |
49 | ### Compiles and hot-reloads for development
50 |
51 | ```
52 | npm run serve
53 | ```
54 |
55 | ### Compiles and minifies for production
56 |
57 | ```
58 | npm run build
59 | ```
60 |
61 | ### Lints and fixes files
62 |
63 | ```
64 | npm run lint
65 | ```
66 |
67 | ### Customize configuration
68 |
69 | See [Configuration Reference](https://cli.vuejs.org/config/).
70 |
71 | ## Screenshots
72 |
73 | > Delete the screenshot folder if you download this code (Screenshots folder is 3.14mb in size).
74 |
75 | ### Sign In (/signin)
76 |
77 | 
78 |
79 | ### Sign Up (/signup)
80 |
81 | 
82 |
83 | ### Home Page (/)
84 |
85 | 
86 |
87 | ### Trending Page (/trending)
88 |
89 | 
90 |
91 | ### Subscriptions Page (/subscriptions)
92 |
93 | 
94 |
95 | ### History (Watch) Page (/history)
96 |
97 | 
98 |
99 | ### History (Search) Page (/history)
100 |
101 | 
102 |
103 | ### Liked Videos Page (/liked-videos)
104 |
105 | 
106 |
107 | ### Search Page (/search)
108 |
109 | 
110 |
111 | ### Watch Page (/watch/:videoId)
112 |
113 | 
114 |
115 | ### Comment & Reply (/watch/:videoId)
116 |
117 | 
118 |
119 | ### Channel Page (/channels/:channelId)
120 |
121 | 
122 |
123 | ### Dashboard Page (/studio)
124 |
125 | 
126 |
127 | ### Subscribers Modal (/studio)
128 |
129 | 
130 |
131 | ### Upload Video Modal
132 |
133 | 
134 |
135 | ### Upload Video Detail Modal
136 |
137 | 
138 |
139 | ### Videos Page (/studio/videos)
140 |
141 | 
142 |
143 | ### Edit Video Details (/studio/details/:videoId)
144 |
145 | 
146 |
147 | ### Upload Thumbnail Modal (/studio/details/:videoId)
148 |
149 | 
150 |
151 | ### Delete Video Modal (/studio/videos)
152 |
153 | 
154 |
155 | ### Settings Modal
156 |
157 | 
158 |
159 | If you like the UI, I developed it in a seperate repo [VueTube](https://github.com/techreagan/vuetify-youtube-clone-template)
160 |
161 | ## License
162 |
163 | This project is licensed under the MIT License
164 |
165 | ## Developed by Reagan Ekhameye (Tech Reagan)
166 |
167 | Reach me on twitter [@techreagan](https://www.twitter.com/techreagan)
168 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import NavBar from '@/components/NavBar.vue'
4 | import StudioNavBar from '@/components/StudioNavBar.vue'
5 |
6 | Vue.use(VueRouter)
7 |
8 | const routes = [
9 | {
10 | path: '/',
11 | name: 'Home',
12 | components: {
13 | NavBar,
14 | default: () => import(/* webpackChunkName: "about" */ '../views/Home.vue')
15 | }
16 | },
17 | {
18 | path: '/subscriptions',
19 | name: 'Subscription',
20 | components: {
21 | NavBar,
22 | default: () =>
23 | import(/* webpackChunkName: "about" */ '../views/Subscription.vue')
24 | },
25 | meta: { requiresAuth: true }
26 | },
27 | {
28 | path: '/liked-videos',
29 | name: 'LikedVideos',
30 | components: {
31 | NavBar,
32 | default: () =>
33 | import(/* webpackChunkName: "about" */ '../views/LikedVideo.vue')
34 | },
35 | meta: { requiresAuth: true }
36 | },
37 | {
38 | path: '/signin',
39 | name: 'SignIn',
40 | component: () =>
41 | import(/* webpackChunkName: "signin" */ '../views/Auth/SignIn.vue'),
42 | meta: { requiresVisitor: true }
43 | },
44 | {
45 | path: '/signup',
46 | name: 'SignUp',
47 | component: () =>
48 | import(/* webpackChunkName: "signup" */ '../views/Auth/SignUp.vue'),
49 | meta: { requiresVisitor: true }
50 | },
51 | {
52 | path: '/trending',
53 | name: 'Trending',
54 | components: {
55 | NavBar,
56 | default: () =>
57 | import(/* webpackChunkName: "trending" */ '../views/Trending.vue')
58 | }
59 | },
60 | {
61 | path: '/studio',
62 | components: {
63 | StudioNavBar,
64 | default: () =>
65 | import(/* webpackChunkName: "dashboard" */ '../views/Studio/Index.vue')
66 | },
67 | children: [
68 | {
69 | path: '/',
70 | name: 'Dashboard',
71 | component: () =>
72 | import(
73 | /* webpackChunkName: "dashboard" */ '../views/Studio/Dashboard.vue'
74 | )
75 | },
76 | {
77 | path: 'videos',
78 | name: 'Video',
79 | component: () =>
80 | import(/* webpackChunkName: "video" */ '../views/Studio/Video.vue')
81 | },
82 | {
83 | path: 'details/:id',
84 | name: 'Detail',
85 | component: () =>
86 | import(/* webpackChunkName: "video" */ '../views/Studio/Details.vue')
87 | }
88 | ],
89 | meta: { requiresAuth: true }
90 | },
91 | {
92 | path: '/channels/:id',
93 | components: {
94 | NavBar,
95 | default: () =>
96 | import(/* webpackChunkName: "dashboard" */ '../views/Channel/Index.vue')
97 | },
98 | children: [
99 | {
100 | path: '/',
101 | name: 'ChannelHome',
102 | component: () =>
103 | import(
104 | /* webpackChunkName: "dashboard" */ '../views/Channel/Home.vue'
105 | )
106 | }
107 | ]
108 | },
109 | {
110 | path: '/watch/:id',
111 | name: 'Watch',
112 | components: {
113 | NavBar,
114 | default: () =>
115 | import(/* webpackChunkName: "video" */ '../views/Watch.vue')
116 | }
117 | },
118 | {
119 | path: '/history',
120 | name: 'History',
121 | components: {
122 | NavBar,
123 | default: () =>
124 | import(/* webpackChunkName: "video" */ '../views/History.vue')
125 | },
126 | meta: { requiresAuth: true }
127 | },
128 | {
129 | path: '/search',
130 | name: 'Search',
131 | components: {
132 | NavBar,
133 | default: () =>
134 | import(/* webpackChunkName: "video" */ '../views/Search.vue')
135 | }
136 | }
137 | ]
138 |
139 | const router = new VueRouter({
140 | mode: 'history',
141 | base: process.env.BASE_URL,
142 | routes
143 | })
144 |
145 | router.beforeEach((to, from, next) => {
146 | const loggedIn = localStorage.getItem('user')
147 |
148 | if (to.matched.some((record) => record.meta.requiresAuth) && !loggedIn) {
149 | next('/')
150 | } else if (
151 | to.matched.some((record) => record.meta.requiresVisitor) &&
152 | loggedIn
153 | ) {
154 | next('/')
155 | } else {
156 | next()
157 | }
158 | })
159 |
160 | export default router
161 |
--------------------------------------------------------------------------------
/src/components/SubscribersModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | Subscribers
11 |
12 |
13 | mdi-close
14 |
15 |
16 |
17 |
18 |
19 |
20 |
26 |
27 |
28 |
31 |
37 |
38 |
39 | {{
40 | item.subscriberId.channelName.split('')[0].toUpperCase()
41 | }}
42 |
43 |
44 | {{ item.subscriberId.channelName }}
45 |
46 |
56 |
57 | Reset
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
139 |
--------------------------------------------------------------------------------
/src/views/Trending.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{ video.title }}
37 |
38 |
39 |
40 | {{ video.userId.channelName }}
41 | mdi-circle-small {{ video.views }} viewsmdi-circle-small {{ dateFormatter(video.createdAt) }}
44 |
45 |
46 | {{ truncateText(video.description, 200) }}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | No trending videos yet
56 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | Error!
72 |
73 | Something went wrong, but don’t fret — let’s give it
74 | another shot.
75 |
76 |
77 |
78 | Take action
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
154 |
155 |
174 |
--------------------------------------------------------------------------------
/src/views/Studio/Details.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mdi-arrow-left Channel videos
8 | Video details
9 |
10 |
11 |
12 |
89 |
90 |
91 |
92 | Upload Thumbnails
93 |
105 |
106 |
107 | mdi-image-plus
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
228 |
229 |
234 |
--------------------------------------------------------------------------------
/src/views/Studio/Video.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
channel videos
5 |
6 |
7 |
8 |
9 | Uploads
10 |
11 |
12 | Live
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
37 |
38 | mdi-thumb-up {{ item.likes }}
42 | mdi-thumb-down {{ item.dislikes }}
46 |
47 |
48 |
49 |
50 |
51 | Permanently delete this video?
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
65 |
66 |
67 |
68 |
69 |
73 | {{ itemToDelete.title }}
74 |
75 |
76 |
80 |
81 | Published
82 | {{ dateFormatter(itemToDelete.createdAt) }}
83 |
84 | {{ itemToDelete.views }} views
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | Cancel
101 |
102 | Delete Forever
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | mdi-pencil
117 |
118 |
119 |
127 |
128 | mdi-youtube
129 |
130 |
131 |
132 |
133 | mdi-delete
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | live
143 |
144 |
145 |
146 |
147 |
148 | Video deleted successfully
149 |
150 | mdi-close-circle
151 |
152 |
153 |
154 |
155 |
156 |
231 |
232 |
238 |
--------------------------------------------------------------------------------
/src/views/Search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Error!
9 |
10 | Something went wrong, but don’t fret — let’s give it another
11 | shot.
12 |
13 |
14 |
15 | Take action
16 |
17 |
18 |
19 |
20 |
21 | Ops! No search results
22 |
23 |
29 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
55 | {{
56 | result.channelName.split('')[0].toUpperCase()
57 | }}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
68 | {{ result.channelName }}
69 |
70 |
71 |
75 | {{ result.subscribers }}
76 | subscribersmdi-circle-small {{ result.videos }} videos
78 |
79 |
80 | {{ result.description }}
81 |
82 |
83 |
84 |
85 |
93 |
94 |
95 |
102 |
103 |
104 |
111 |
112 |
113 |
114 |
115 |
118 | {{ result.title }}
119 |
120 |
121 |
125 | {{ result.userId.channelName }}
126 | {{ result.views }}
127 | viewsmdi-circle-small 6 hours ago
128 |
129 |
130 | {{ result.description }}
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
142 |
143 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | Error!
155 |
156 | Something went wrong, but don’t fret — let’s give it
157 | another shot.
158 |
159 |
160 |
161 | Take action
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
244 |
245 |
246 |
--------------------------------------------------------------------------------
/src/views/Channel/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
80 |
81 |
88 |
89 | {{ item }}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | Uploads
98 |
99 |
100 |
104 |
110 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Uploads
123 |
124 |
133 |
134 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
153 |
154 |
155 |
156 |
288 |
289 |
306 |
--------------------------------------------------------------------------------
/src/components/UploadVideoModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
25 |
26 |
27 | mdi-upload
36 |
37 |
38 |
44 |
53 |
54 |
Select File
61 |
62 |
63 |
71 | {{ value }}
72 |
73 |
74 |
75 | Details
76 |
77 |
88 |
89 |
162 |
163 |
164 |
172 | Upload Thumbnails
173 |
185 |
186 |
187 | mdi-image-plus
188 |
189 |
195 |
196 |
197 | Please upload thumbnail
198 |
199 |
200 |
201 |
202 |
203 |
204 | By submitting your videos to YouTube, you acknowledge that you agree
205 | to YouTube's Terms of Service and Community Guidelines. Please be sure
206 | not to violate others' copyright or privacy rights. Learn more
207 |
208 |
209 |
210 |
211 |
212 |
213 |
371 |
372 |
383 |
--------------------------------------------------------------------------------
/src/views/History.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 | {{ historyType }}
17 |
18 |
19 | No watch history yet.
20 |
21 |
22 |
27 |
34 |
35 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
51 | {{ history.videoId.title }}
52 |
53 |
54 | mdi-close
55 |
56 |
57 |
58 |
62 | {{ history.userId.channelName
63 | }}mdi-circle-small {{ history.videoId.views }} views
65 |
66 |
67 | {{ history.videoId.description }}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
79 |
80 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | Error!
92 |
93 | Something went wrong, but don’t fret — let’s give it
94 | another shot.
95 |
96 |
97 |
98 | Take action
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | No search history yet.
109 |
110 |
111 |
112 |
119 |
120 |
121 | {{ history.searchText }}
125 | {{
126 | dateFormatter(history.createdAt)
127 | }}
128 |
129 |
136 | mdi-close
137 |
139 |
140 |
141 |
142 |
146 |
147 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | Error!
159 |
160 | Something went wrong, but don’t fret — let’s give it
161 | another shot.
162 |
163 |
164 |
165 | Take action
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
188 |
193 |
194 | History Type
195 |
196 |
197 |
198 |
199 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 | Clear all {{ historyType }}
220 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 | {{ deleteMessage }}
230 |
231 | mdi-close-circle
232 |
233 |
234 |
235 |
236 |
237 |
348 |
349 |
350 |
--------------------------------------------------------------------------------
/src/components/SettingsModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
19 |
20 |
32 | Personal Information
33 |
37 |
76 |
77 |
78 | Change Password
79 |
83 |
136 |
137 |
138 |
146 | Upload Avatar
147 |
162 |
167 |
172 | mdi-image-plus
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
338 |
339 |
387 |
--------------------------------------------------------------------------------
/src/components/StudioNavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Studio
13 |
14 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | mdi-video-plus
38 | Create
40 |
41 | Create a video and more
42 |
43 |
44 |
45 |
46 | mdi-play-box-outline
49 | Upload video
50 |
51 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
68 |
69 |
70 |
71 | {{ currentUser.channelName.split('')[0].toUpperCase() }}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
86 |
87 |
88 |
89 |
90 | {{
91 | currentUser.channelName.split('')[0].toUpperCase()
92 | }}
94 |
95 |
96 |
97 |
98 |
99 | {{
100 | currentUser.channelName
101 | }}
102 | {{
103 | currentUser.email
104 | }}
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
116 |
117 | mdi-account-box
118 |
119 | Your channel
120 |
121 |
122 |
123 | mdi-youtube-studio
124 |
125 | VueTube Studio
126 |
127 |
128 |
129 | mdi-login-variant
130 |
131 | Sign out
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
152 |
153 | {{ item.icon }}
154 |
155 |
156 |
157 | {{
158 | item.title
159 | }}
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
178 |
179 | {{ item.icon }}
180 |
181 |
182 |
183 | {{
184 | item.title
185 | }}
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
205 |
210 |
214 |
215 |
216 |
217 | {{ currentUser.channelName.split('')[0].toUpperCase() }}
218 |
219 |
220 |
223 |
224 |
225 |
226 |
227 |
228 | {{
229 | currentUser.channelName
230 | }}
231 | {{
232 | currentUser.email
233 | }}
234 |
235 |
236 |
237 |
238 |
239 |
240 |
244 |
248 |
249 |
250 |
251 |
351 |
352 |
422 |
--------------------------------------------------------------------------------
/src/views/Auth/SignUp.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | VueTube
9 | Create your VueTube account
12 |
13 |
14 |
98 |
99 |
100 |
101 |
102 |
103 |
110 | Create Account
111 |
116 |
121 |
128 |
135 |
140 |
145 |
150 |
158 |
166 |
174 |
179 |
184 |
189 |
194 |
199 |
205 |
210 |
215 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
281 |
282 |
283 |
--------------------------------------------------------------------------------
/src/components/comments/CommentList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
14 |
19 |
20 |
21 | {{
22 | comment.userId.channelName.split('')[0].toUpperCase()
23 | }}
25 |
26 |
27 |
28 |
29 | {{ comment.userId.channelName }}
33 |
34 | {{ dateFormatter(comment.createdAt) }}
36 |
37 |
38 |
39 |
40 | mdi-dots-vertical
41 |
42 |
43 |
44 |
45 |
46 | mdi-trash Delete
49 |
50 |
51 |
52 |
53 | {{ comment.text }}
57 |
58 |
59 | Reply
66 |
67 |
68 |
69 |
74 |
75 | mdi-account
76 |
77 |
78 |
83 |
84 |
85 | {{
86 | currentUser.channelName.split('')[0].toUpperCase()
87 | }}
89 |
90 |
91 |
92 |
93 |
94 |
101 |
102 |
103 |
108 | Cancel
111 | Reply
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | {{
134 | comment.replies.length
135 | }}
136 | replies
138 |
139 |
145 |
149 |
156 |
157 |
158 | {{
159 | reply.userId.channelName
160 | .split('')[0]
161 | .toUpperCase()
162 | }}
164 |
165 |
166 |
167 |
168 | {{ reply.userId.channelName }}
172 |
173 | {{ dateFormatter(reply.createdAt) }}
175 |
176 |
177 |
178 |
179 | mdi-dots-vertical
180 |
181 |
182 |
183 |
184 |
187 | mdi-trash Delete
191 |
192 |
193 |
194 |
195 | {{ reply.text }}
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 | Comment deleted successfully
211 |
212 | mdi-close-circle
213 |
214 |
215 |
216 |
217 |
218 |
348 |
349 |
358 |
--------------------------------------------------------------------------------
/src/components/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | VueTube
10 |
11 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | mdi-video-plus
31 |
32 | Create a video and more
33 |
34 |
35 |
36 |
37 | mdi-play-box-outline
40 | Upload video
41 |
42 |
48 |
49 |
50 |
56 |
57 |
65 |
74 | mdi-account-circle Sign in
75 |
76 |
77 |
78 |
79 |
80 |
81 |
85 |
86 |
87 |
88 | {{ currentUser.channelName.split('')[0].toUpperCase() }}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
103 |
104 |
105 |
106 |
107 | {{
108 | currentUser.channelName.split('')[0].toUpperCase()
109 | }}
111 |
112 |
113 |
114 |
115 |
116 | {{
117 | currentUser.channelName
118 | }}
119 | {{
120 | currentUser.email
121 | }}
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
133 |
134 | mdi-account-box
135 |
136 | Your channel
137 |
138 |
139 |
140 | mdi-youtube-studio
141 |
142 | VueTube Studio
143 |
144 |
145 |
146 | mdi-login-variant
147 |
148 | Sign out
149 |
150 |
151 |
152 |
153 |
154 |
155 |
162 |
163 |
164 |
169 |
173 | VueTube
174 |
175 |
176 |
177 |
{{ parentItem.header }}
182 |
196 |
197 | {{ item.icon }}
198 |
199 |
200 | {{ i }}
201 |
206 |
212 |
213 |
214 |
215 |
216 | {{
217 | item.channelId.channelName.split('')[0].toUpperCase()
218 | }}
220 |
221 |
222 |
223 |
224 | {{
225 | parentItem.header === 'Subscriptions'
226 | ? item.channelId.channelName
227 | : item.title
228 | }}
229 |
230 |
231 |
232 |
244 | {{
245 | channelLength === 3 ? 'mdi-chevron-down' : 'mdi-chevron-up'
246 | }}
247 | {{
248 | channelLength === 3
249 | ? `Show ${items[2].pages.length - 3} more `
250 | : 'Show less'
251 | }}
253 |
254 |
258 |
259 |
260 |
261 |
262 | {{ link.text }}
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
497 |
498 |
576 |
--------------------------------------------------------------------------------