├── .eslintrc.cjs ├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── components.d.ts ├── cypress.config.ts ├── cypress ├── e2e │ ├── example.cy.ts │ └── tsconfig.json ├── fixtures │ └── example.json └── support │ ├── commands.ts │ └── e2e.ts ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── assets │ └── base.css ├── components │ └── AppButton.vue ├── composables │ └── use8baseStorage.ts ├── main.ts ├── pages │ └── index.vue ├── router │ └── index.ts └── types │ └── index.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.config.json ├── tsconfig.json ├── tsconfig.vitest.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution"); 3 | 4 | module.exports = { 5 | "root": true, 6 | "extends": [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/eslint-config-typescript/recommended", 10 | "@vue/eslint-config-prettier" 11 | ], 12 | "overrides": [ 13 | { 14 | "files": [ 15 | "cypress/e2e/**.{cy,spec}.{js,ts,jsx,tsx}" 16 | ], 17 | "extends": [ 18 | "plugin:cypress/recommended" 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-forge-boilerplate 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. 12 | 13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 14 | 15 | 1. Disable the built-in TypeScript Extension 16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 19 | 20 | ## Customize configuration 21 | 22 | See [Vite Configuration Reference](https://vitejs.dev/config/). 23 | 24 | ## Project Setup 25 | 26 | ```sh 27 | npm install 28 | ``` 29 | 30 | ### Compile and Hot-Reload for Development 31 | 32 | ```sh 33 | npm run dev 34 | ``` 35 | 36 | ### Type-Check, Compile and Minify for Production 37 | 38 | ```sh 39 | npm run build 40 | ``` 41 | 42 | ### Run Unit Tests with [Vitest](https://vitest.dev/) 43 | 44 | ```sh 45 | npm run test:unit 46 | ``` 47 | 48 | ### Run End-to-End Tests with [Cypress](https://www.cypress.io/) 49 | 50 | ```sh 51 | npm run build 52 | npm run test:e2e # or `npm run test:e2e:ci` for headless testing 53 | ``` 54 | 55 | ### Lint with [ESLint](https://eslint.org/) 56 | 57 | ```sh 58 | npm run lint 59 | ``` 60 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/core/pull/3399 4 | import '@vue/runtime-core' 5 | 6 | declare module '@vue/runtime-core' { 7 | export interface GlobalComponents { 8 | AppButton: typeof import('./src/components/AppButton.vue')['default'] 9 | RouterLink: typeof import('vue-router')['RouterLink'] 10 | RouterView: typeof import('vue-router')['RouterView'] 11 | } 12 | } 13 | 14 | export {} 15 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', 6 | baseUrl: 'http://localhost:4173' 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /cypress/e2e/example.cy.ts: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'You did it!') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /cypress/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["./**/*", "../support/**/*"], 4 | "compilerOptions": { 5 | "isolatedModules": false, 6 | "target": "es5", 7 | "lib": ["es5", "dom"], 8 | "types": ["cypress"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } 38 | -------------------------------------------------------------------------------- /cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-forge-boilerplate", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "run-p type-check build-only", 7 | "preview": "vite preview --port 4173", 8 | "test:unit": "vitest --environment jsdom", 9 | "test:e2e": "start-server-and-test preview http://127.0.0.1:4173/ 'cypress open --e2e'", 10 | "test:e2e:ci": "start-server-and-test preview http://127.0.0.1:4173/ 'cypress run --e2e'", 11 | "build-only": "vite build", 12 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", 13 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 14 | }, 15 | "dependencies": { 16 | "@vue/apollo-composable": "^4.0.0-alpha.18", 17 | "graphql-tag": "^2.12.6", 18 | "lodash-es": "^4.17.21", 19 | "unplugin-vue-components": "^0.20.1", 20 | "uuid": "^8.3.2", 21 | "vite-plugin-pages": "^0.24.3", 22 | "vue": "^3.2.37", 23 | "vue-router": "^4.0.16" 24 | }, 25 | "devDependencies": { 26 | "@progress/kendo-licensing": "^1.2.2", 27 | "@progress/kendo-theme-default": "^5.5.0", 28 | "@progress/kendo-vue-animation": "^3.4.0", 29 | "@progress/kendo-vue-buttons": "^3.4.0", 30 | "@progress/kendo-vue-dialogs": "^3.4.0", 31 | "@progress/kendo-vue-editor": "^3.4.0", 32 | "@progress/kendo-vue-form": "^3.4.0", 33 | "@progress/kendo-vue-indicators": "^3.4.0", 34 | "@progress/kendo-vue-inputs": "^3.4.0", 35 | "@progress/kendo-vue-layout": "^3.4.0", 36 | "@progress/kendo-vue-notification": "^3.4.0", 37 | "@progress/kendo-vue-popup": "^3.4.0", 38 | "@rushstack/eslint-patch": "^1.1.0", 39 | "@types/jsdom": "^16.2.14", 40 | "@types/lodash-es": "^4.17.6", 41 | "@types/node": "^16.11.41", 42 | "@vitejs/plugin-vue": "^2.3.3", 43 | "@vue/eslint-config-prettier": "^7.0.0", 44 | "@vue/eslint-config-typescript": "^11.0.0", 45 | "@vue/test-utils": "^2.0.0", 46 | "@vue/tsconfig": "^0.1.3", 47 | "autoprefixer": "^10.4.7", 48 | "cypress": "^10.1.0", 49 | "eslint": "^8.5.0", 50 | "eslint-plugin-cypress": "^2.12.1", 51 | "eslint-plugin-vue": "^9.0.0", 52 | "jsdom": "^20.0.0", 53 | "npm-run-all": "^4.1.5", 54 | "postcss": "^8.4.14", 55 | "prettier": "^2.5.1", 56 | "start-server-and-test": "^1.14.0", 57 | "tailwindcss": "^3.1.4", 58 | "typescript": "~4.7.4", 59 | "vite": "^2.9.12", 60 | "vitest": "^0.15.1", 61 | "vue-tsc": "^0.38.1" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softwarearchitect817/vuejs-forge-boilerplate/2600f7bbb18d17166920c3121581d42456eddd1a/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/assets/base.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/components/AppButton.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/composables/use8baseStorage.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | import { useMutation, useQuery } from "@vue/apollo-composable"; 3 | 4 | const IMAGE_UPLOAD_QUERY = gql` 5 | query { 6 | fileUploadInfo { 7 | policy 8 | signature 9 | apiKey 10 | path 11 | } 12 | } 13 | `; 14 | 15 | const FILE_CREATE_MUTATION = gql` 16 | mutation CREATE_FILE($fileId: String!, $filename: String!) { 17 | fileCreate(data: { fileId: $fileId, filename: $filename }) { 18 | id 19 | } 20 | } 21 | `; 22 | 23 | export function useStorage() { 24 | const { result } = useQuery(IMAGE_UPLOAD_QUERY); 25 | const { mutate: createFileIn8base } = useMutation(FILE_CREATE_MUTATION); 26 | 27 | async function uploadAsset(file: File) { 28 | if (!result.value || !result.value.fileUploadInfo) { 29 | throw new Error("File Upload info not yet available"); 30 | } 31 | const res = await fetch( 32 | `https://www.filestackapi.com/api/store/S3?key=${result.value.fileUploadInfo.apiKey}&policy=${result.value.fileUploadInfo.policy}&signature=${result.value.fileUploadInfo.signature}&path=${result.value.fileUploadInfo.path}`, 33 | { 34 | method: "POST", 35 | headers: { 36 | "Content-Type": file.type, 37 | }, 38 | body: file, 39 | } 40 | ); 41 | const data = await res.json(); 42 | return createFileIn8base({ 43 | fileId: data.url.split("/").at(-1), 44 | filename: data.filename, 45 | }); 46 | } 47 | return { 48 | uploadAsset, 49 | }; 50 | } 51 | export default useStorage; 52 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import "@/assets/base.css"; 5 | import "@progress/kendo-theme-default/dist/all.css"; 6 | 7 | const app = createApp(App); 8 | 9 | app.use(router); 10 | 11 | app.mount("#app"); 12 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | 3 | // this works with the vite plugin to support file based routing 4 | import routes from "~pages"; 5 | 6 | const router = createRouter({ 7 | history: createWebHistory(import.meta.env.BASE_URL), 8 | routes, 9 | }); 10 | 11 | export default router; 12 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | type ID = string; 2 | 3 | export interface Resource8base { 4 | id: ID; 5 | createdAt: Date; 6 | updatedAt: Date; 7 | deletedAt: Date; 8 | } 9 | 10 | export interface User extends Resource8base { 11 | email: string; 12 | roles: { items: Role[] }; 13 | } 14 | export interface Role { 15 | name: string; 16 | } 17 | 18 | export interface Board extends Resource8base { 19 | title: string; 20 | 21 | // Board order JSON encoded in DB and thus can be a string 22 | // when decoded it's an array of Columns 23 | order: string | Column[]; 24 | 25 | // relationships 26 | image?: Partial; 27 | tasks?: Partial[]; 28 | } 29 | 30 | export interface Column { 31 | id: ID; 32 | title: string; 33 | taskIds: ID[]; 34 | } 35 | 36 | export interface Task extends Resource8base { 37 | title: string; 38 | description: string; 39 | labels: Label[]; 40 | dueAt: Date; 41 | 42 | // relationships 43 | board?: Partial; 44 | comments?: Partial[]; 45 | } 46 | 47 | export interface Comment extends Resource8base { 48 | message: string; 49 | 50 | // relationships 51 | task?: Partial; 52 | } 53 | 54 | type LabelColor = 55 | | "red" 56 | | "orange" 57 | | "yellow" 58 | | "green" 59 | | "blue" 60 | | "purple" 61 | | "pink"; 62 | 63 | export interface Label extends Resource8base { 64 | label: string; 65 | color: LabelColor; 66 | 67 | // relationships 68 | board?: Partial; 69 | tasks?: Partial[]; 70 | } 71 | 72 | export interface File extends Resource8base { 73 | downloadStorageUrl: string; 74 | downloadUrl: string; 75 | filename: string; 76 | meta: { 77 | path: string; 78 | size: number; 79 | mimetype: string; 80 | workspaceId: string; 81 | }; 82 | previewUrl: string; 83 | provider: string; 84 | public: boolean; 85 | shareUrl: string; 86 | uploadUrl: string; 87 | uploaded: boolean; 88 | } 89 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "compilerOptions": { 4 | "allowJs": true 5 | }, 6 | "references": [ 7 | { 8 | "path": "./tsconfig.config.json" 9 | }, 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.vitest.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.vitest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "exclude": [], 4 | "compilerOptions": { 5 | "composite": true, 6 | "lib": [], 7 | "types": ["node", "jsdom"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "url"; 2 | 3 | import { defineConfig } from "vite"; 4 | 5 | // plugins 6 | import vue from "@vitejs/plugin-vue"; 7 | import Pages from "vite-plugin-pages"; 8 | import Components from "unplugin-vue-components/vite"; 9 | 10 | // https://vitejs.dev/config/ 11 | export default defineConfig({ 12 | plugins: [vue(), Pages(), Components()], 13 | resolve: { 14 | alias: { 15 | "@": fileURLToPath(new URL("./src", import.meta.url)), 16 | }, 17 | }, 18 | }); 19 | --------------------------------------------------------------------------------