├── .eslintrc.cjs
├── .gitignore
├── .prettierrc.json
├── .vscode
└── extensions.json
├── README.md
├── auto-imports.d.ts
├── components.d.ts
├── convex
├── README.md
├── _generated
│ ├── api.d.ts
│ ├── api.js
│ ├── dataModel.d.ts
│ ├── server.d.ts
│ └── server.js
├── auth.config.js
├── schema.ts
├── todos.ts
└── tsconfig.json
├── env.d.ts
├── index.html
├── package.json
├── postcss.config.cjs
├── public
├── favicon.ico
└── icon
│ ├── icon-192x192.png
│ └── icon-512x512.png
├── src
├── App.vue
├── api.ts
├── components
│ ├── AddTodoForm.vue
│ ├── DarkModeToggle.vue
│ ├── ServiceWorkerPrompt.vue
│ ├── Todo.vue
│ ├── TodoListPaginated.vue
│ ├── convex
│ │ ├── EnsureAuthenticated.vue
│ │ ├── PaginatedQuery.vue
│ │ ├── PaginatedQueryInner.vue
│ │ ├── Query.vue
│ │ └── QueryInner.vue
│ └── ui
│ │ ├── UiIcon.vue
│ │ ├── UiSpinner.vue
│ │ ├── buttons
│ │ ├── UiButton.vue
│ │ ├── UiButtonBase.vue
│ │ ├── UiGhostButton.vue
│ │ ├── UiIconButton.vue
│ │ └── UiLinkButton.vue
│ │ ├── drawer
│ │ ├── UiDrawer.vue
│ │ ├── UiDrawerContent.vue
│ │ ├── UiDrawerHeader.vue
│ │ └── UiSimpleDrawer.vue
│ │ ├── forms
│ │ ├── UiFormControl.vue
│ │ ├── UiFormError.vue
│ │ └── UiFormLabel.vue
│ │ ├── inputs
│ │ ├── UiCheckbox.vue
│ │ ├── UiSwitch.vue
│ │ └── UiTextInput.vue
│ │ └── modal
│ │ ├── UiModal.vue
│ │ ├── UiModalContent.vue
│ │ ├── UiModalHeader.vue
│ │ └── UiSimpleModal.vue
├── composables
│ ├── convex
│ │ ├── useAction.ts
│ │ ├── useConvex.ts
│ │ ├── useMutation.ts
│ │ ├── usePaginatedQuery.ts
│ │ ├── useQueries.ts
│ │ ├── useQuery.ts
│ │ └── useSuspenseQuery.ts
│ ├── useFocusOn.ts
│ ├── useSafeInject.ts
│ └── useStyles.ts
├── directives
│ └── vFocusOn.ts
├── main.ts
├── pages
│ ├── index.vue
│ └── profile
│ │ └── [id].vue
├── plugins
│ └── convex.ts
├── styles
│ ├── global.css
│ ├── reset.css
│ ├── theme.css
│ └── utils.css
├── sw.ts
└── utils
│ ├── assertions.ts
│ ├── convex
│ └── QueriesObserver.ts
│ ├── dom.ts
│ └── types.ts
├── stylelint.config.js
├── tools
├── ark-ui-resolver.ts
└── uno-openprops-preset.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── typed-router.d.ts
├── uno.config.ts
├── vite.config.ts
└── yarn.lock
/.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-recommended',
8 | '@vue/eslint-config-typescript/recommended',
9 | '@vue/eslint-config-prettier/skip-formatting',
10 | 'eslint:recommended'
11 | ],
12 | parserOptions: {
13 | ecmaVersion: 'latest'
14 | },
15 | rules: {
16 | 'no-undef': 'off',
17 | 'no-redeclare': 'off',
18 | 'no-unused-vars': 'off',
19 | 'vue/multi-word-component-names': 'off',
20 | 'vue/no-setup-props-destructure': 'off',
21 | '@typescript-eslint/ban-ts-comment': 'off',
22 | '@typescript-eslint/no-non-null-assertion': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off'
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "htmlWhitespaceSensitivity": "ignore",
4 | "semi": true,
5 | "tabWidth": 2,
6 | "singleQuote": true,
7 | "printWidth": 90,
8 | "trailingComma": "none",
9 | "arrowParens": "avoid"
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "Vue.vscode-typescript-vue-plugin",
5 | "dbaeumer.vscode-eslint",
6 | "esbenp.prettier-vscode"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue + Vite + Convex
2 |
3 | Template to easily use [Convex](https://www.convex.dev/) with [Vue](https://vuejs.org/) and [Auth0](https://auth0.com/)
4 |
5 | Everything should be setup to work properly.
6 |
7 | [Click here to go to the deployed app](https://vue-convex-example.vercel.app/)
8 |
9 | **This branch adds a bunch of goodies that I like to use for my vue projects and can be fairly opinionated, if you're only interested in the convex stuff, check the [minimal branch](https://github.com/loicpennequin/convex-vue-vite-template/tree/minimal)**
10 |
11 | - A `createConvex` vue plugin has been created to instanciated a slightly modified version of the `ConvexReactClient`.
12 | - If `auth0` options are provided to the plugin, navigation guards will be added to the application. You can tweak the option to have a different redirect url, or more involved way of determining if an autentication check should be made (by default, add `needsauth: true` to a [route block meta](https://github.com/posva/unplugin-vue-router#sfc-route-custom-block)).
13 | - ⚠️ You will need to add your auth0 credentials (domain and client ID)
14 | - in a .env.local file under the keys `VITE_AUTH0_DOMAIN` and `VITE_AUTH0_CLIENTID`. You can use different names but will have ti make the appropriate changes in `src/main.ts`
15 | - as environment variables through the convex dashboard ([see here](https://docs.convex.dev/production/environment-variables)) under the keys `AUTH0_DOMAIN` and `AUTH0_APPLICATIONID`. You can use different names but will have ti make the appropriate changes in `convex/auh.config.js`
16 |
17 | 🧪 - Experimental: might have bugs
18 | 🔨 - Available soon
19 |
20 | ## Composables
21 |
22 | ### `useQuery`
23 |
24 | ```html
25 |
42 |
43 |
44 |
45 |
{{message.text}}
46 |
47 |
48 | ```
49 |
50 | ### `useSuspenseQuery`
51 |
52 | like useQuery but can be awaited and will resolve once the query result is available (either from cache or from a network call). This enables you to use this composable in conjuction with Vue's [``](https://vuejs.org/guide/built-ins/suspense.html). Note: like useQuery, the value will be reactive and will update automatically when it's value changes on the Convex server.
53 |
54 | ```html
55 |
75 |
76 |
77 |
78 |
106 |
107 |
110 |
111 | ```
112 |
113 | ### `useMutation`
114 |
115 | Now with optimistic updates ! (🧪)
116 |
117 | ```html
118 |
147 |
148 |
149 |
154 |
155 | ```
156 |
157 | ### `useAction`
158 |
159 | ```html
160 |
165 |
166 |
167 |
168 |
169 | ```
170 |
171 | ### `useConvex`
172 |
173 | if you need to use the ConvexVueClient directly. You probably don't need it.
174 |
175 | ### `useConvexAuth`
176 |
177 | if you used the `auth0` option in the plugin, it will return you the loading and authenticated state. For additional auth utilities like login, logout, user etc, please use `useAuth0` from `@auth0/auth0-vue`
178 |
179 | ```html
180 |
183 | ```
184 |
185 | ## Components
186 |
187 | ### ``
188 |
189 | Allows you to display content depending on convex Auth status. ⚠️ you need the autho0 options in the convexPlugin for this component to work.
190 | It accepts the following slots:
191 |
192 | - default: when the user is logged in
193 | - fallback: when the user isn't logged in
194 | - loading: when the authentication process is pending
195 |
196 | ```html
197 |
200 |
201 |
202 |
203 | Authenticating...
204 |
205 |
206 | You need to be logged in to see this content
207 |
208 |
209 |
210 | Welcome back, {{ user.nickname }} !
211 |
212 |
213 | ```
214 |
215 | ### ``
216 |
217 | Allows you to display different UI during the lifecycle of a convex query.
218 | It takes the following props
219 |
220 | - `query`: a function taking the convex api as a parameter and returning a query, eg: `:query="api => api.messages.list"`
221 | - `args`: the arguments for the returned query
222 |
223 | It accepts the following slots:
224 |
225 | - default: should be used to display the query results (avaiable via [scoped slots](https://vuejs.org/guide/components/slots.html#scoped-slots))
226 | - loading: pending state when the query is loading for the first time
227 | - error: should be used to displau an error UI. It takes the error as slot props, as well as a `clearError` function to clear the underlying error b boundary (note: doing so will retry the query).
228 |
229 | It will also emit the error when / if it happens.
230 |
231 | ```html
232 |
235 |
236 |
237 | Loading messages...
238 |
239 |
240 | An error has occured
241 |