├── .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 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/assets/base.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/components/AppButton.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
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 |
3 |
15 |
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 |
--------------------------------------------------------------------------------