├── .eslintrc.cjs ├── .gitignore ├── LICENSE ├── README.md ├── _gitignore ├── components.json ├── convex ├── README.md ├── _generated │ ├── api.d.ts │ ├── api.js │ ├── dataModel.d.ts │ ├── server.d.ts │ └── server.js ├── _schema.ts ├── myFunctions.ts └── tsconfig.json ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.tsx ├── assets │ └── react.svg ├── components │ ├── typography │ │ ├── code.tsx │ │ └── link.tsx │ └── ui │ │ ├── button.tsx │ │ ├── checkbox.tsx │ │ ├── input.tsx │ │ └── label.tsx ├── index.css ├── lib │ └── utils.tsx ├── main.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | parserOptions: { 12 | project: true, 13 | tsconfigRootDir: __dirname, 14 | }, 15 | plugins: ["react-refresh"], 16 | rules: { 17 | "react-refresh/only-export-components": [ 18 | "warn", 19 | { allowConstantExport: true }, 20 | ], 21 | "@typescript-eslint/no-unused-vars": "warn", 22 | "@typescript-eslint/no-explicit-any": "warn", 23 | "@typescript-eslint/no-floating-promises": "error", 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !**/glob-import/dir/node_modules 2 | .DS_Store 3 | .idea 4 | *.cpuprofile 5 | *.local 6 | *.log 7 | /.vscode/ 8 | /docs/.vitepress/cache 9 | dist 10 | dist-ssr 11 | explorations 12 | node_modules 13 | playground-temp 14 | temp 15 | TODOs.md 16 | .eslintcache 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2024 Convex, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Convex Hack Pack 2 | 3 | Hello, hackers! Welcome to [Convex](https://convex.dev), a full-featured backend 4 | platform you can use to rapidly prototype any hackathon app you can think of! 5 | 6 | ## So you want to build an app, ASAP 7 | 8 | If you're building an app for a hackathon, there's no time to waste! Convex is 9 | the perfect partner for hackers on a deadline because: 10 | 11 | - you get a cloud-hosted database & serverless backend for free 12 | - your frontend clients get automatic, realtime data updates 13 | - you can flexibly change your database schema as your product evolves 14 | - you get file storage, text- and vector-based search, cron jobs, and lots more 15 | out of the box 16 | 17 | Whatever your idea is, Convex can help you make it happen! 18 | 19 | ## What is this hack pack? 20 | 21 | This repository is your starting point for getting hacking with Convex. It 22 | includes: 23 | 24 | - A simple Convex demo app to demonstrate the basics 25 | - Hands-on exercises to get you started (below) 26 | - Lots of further reading links & resources to keep you going (below) 27 | 28 | ## Before you begin 29 | 30 | - Sign up for a free Convex account at [convex.dev](https://convex.dev) 31 | - [highly recommended] Sign up for a [Github](https://github.com) account if you 32 | don't have one already 33 | - Get your development environment set up: 34 | - Make sure you have [git](https://github.com/git-guides/install-git) 35 | installed, or install it if needed 36 | - Install/update [Node.js](https://nodejs.org/en/download) (which includes 37 | [npm](https://www.npmjs.com/)) to the latest LTS version 38 | - [optional] Read up on the technologies we'll be using: 39 | - [JavaScript](https://developer.mozilla.org/en-US/docs/Learn/JavaScript) and 40 | [TypeScript](https://www.typescriptlang.org/) programming languages 41 | - [React](https://react.dev/) JS/TS frontend framework 42 | - [Vite](https://vitejs.dev/) JS/TS build & development tool 43 | - and of course, [Convex](https://docs.convex.dev)! 44 | 45 | ## Install the hack pack 46 | 47 | - Download a local copy of this repository from Github using `git`: 48 | 49 | ``` 50 | git clone https://github.com/get-convex/convex-hack-pack.git 51 | ``` 52 | 53 | (Note: if you prefer, you can also clone the repo with the 54 | [Github CLI](https://cli.github.com/) or 55 | [download a ZIP file](https://github.com/get-convex/convex-hack-pack/archive/refs/heads/main.zip) 56 | of the contents) 57 | 58 | - Install the project dependencies: 59 | 60 | ``` 61 | cd convex-hack-pack 62 | npm install 63 | ``` 64 | 65 | - Run the following command, then follow the prompts to configure a new project 66 | in your Convex account and start the development server for the demo site: 67 | 68 | ``` 69 | npm run dev 70 | ``` 71 | 72 | - You should see the demo app automatically open in your web browser (if not, 73 | navigate to [localhost:5173](http://localhost:5173)) 74 | - In the demo app, type in a new app idea and click "Save", and/or click the 75 | "Generate a random app idea" button, and you should see the ideas appear! 76 | 77 | You may have noticed that "Include random ideas" checkbox in the demo app 78 | doesn't work! Don't worry, we're going to fix it. But before we do, let's take a 79 | closer look at our data in the Convex dashboard. 80 | 81 | --- 82 | 83 | # Exercises 84 | 85 | ## Exercise 1: Update your data 86 | 87 | - In the browser, navigate to the `convex-hack-pack` project in your 88 | [Convex dashboard](https://dashboard.convex.dev) (if it didn't open 89 | automatically) - you'll be taken to the 'Data' tab where you should see the 90 | `ideas` table and any documents inside it 91 | - Edit data: 92 | - Double-click in the 'idea' field of any document and edit the text 93 | - Back in your demo app, you'll see the text has automatically updated! 94 | - Add new data: 95 | - In the dashboard `ideas` table, click the "Add documents" button on the top 96 | right 97 | - In the document editor that opens, type a new app idea to fill out the 98 | `idea` property (e.g. `"A brainstorming app for developers"` - in quotes, 99 | because it's a string value) 100 | - Click "Save" to save the new document 101 | - In both the dashboard and the demo app, you should now see your new idea! 102 | 103 | But we're not done yet - that "Include random ideas" checkbox in our app still 104 | doesn't work! Let's fix that. 105 | 106 | ## Exercise 2: Update your backend 107 | 108 | - Update your `listIdeas` function: 109 | 110 | - In your code editor, open `convex/myFunctions.ts` 111 | - In the `listIdeas` query function, add an additional argument named 112 | `includeRandom` to the `args` object, whose value is a boolean 113 | (`v.boolean()`). The `args` object should now look like this: 114 | 115 | ```js 116 | args: { 117 | includeRandom: v.boolean() 118 | }, 119 | ``` 120 | 121 | - In the `handler` function, add an `if` conditional based on the value of 122 | `args.includeRandom`: if true, return the same query results as before, but 123 | if false, filter the data to only return documents where the `random` field 124 | is _not_ equal to `true`, like so: 125 | 126 | ```js 127 | handler: async (ctx, args) => { 128 | if (args.includeRandom) { 129 | return await ctx.db.query("ideas").collect(); // Returns all documents in the 'ideas' table 130 | } else { 131 | return await ctx.db 132 | .query("ideas") 133 | .filter((q) => q.neq(q.field("random"), true)) // Only returns documents whose 'random' field is not equal to `true` 134 | .collect(); 135 | } 136 | }, 137 | ``` 138 | 139 | - Save the `myFunctions.ts` file, and in the terminal where you have 140 | `npm run dev` running, you should see a log line that says "Convex functions 141 | ready!" (this means your new function code has been successfully deployed) 142 | 143 | - Test out your updated function 144 | - Go to your [Convex dashboard](https://dashboard.convex.dev), navigate to the 145 | "Functions" tab (``) and open `myFunctions:listIdeas`. You should now see 146 | the new version of your code there! 147 | - Click the "Run function" button to try out your new function in the 148 | dashboard 149 | - In the "Arguments" panel, edit the value of `includeRandom` and verify that 150 | you see the correct results in the "Query outcome" panel! 151 | 152 | Great, we've confirmed in the Convex dashboard that our backend change was 153 | successful! 154 | 155 | But now when visiting [localhost:5173](http://localhost:5173) you'll see a whole 156 | lot of nothing. That's because our backend function change broke the frontend 157 | code that invokes that function! Let's fix it and get our ideas back. 158 | 159 | ## Exercise 3: Update your frontend 160 | 161 | - In your code editor, open `src/App.tsx` 162 | - In the `App` function, find the line near the top where `ideas` is defined 163 | using the `useQuery()` hook to call the `api.myFunctions.listIdeas` query 164 | function 165 | - The `useQuery` hook can take an optional second argument, an `args` object 166 | that matches the `args` validator of the given query function. Update the call 167 | to `useQuery()` to pass `{ includeRandom }` as the second argument, like so: 168 | ```js 169 | const ideas = useQuery(api.myFunctions.listIdeas, { includeRandom }); 170 | ``` 171 | - Now, not only are the ideas displaying properly, but when you (un)check the 172 | "Include random ideas" checkbox you should see the results update accordingly! 173 | 174 | ## Bonus: Challenge Exercises 175 | 176 | If you've got extra time, try your hand at implementing some new features for 177 | the app! 178 | 179 | ### Challenge 1: Delete idea button 180 | 181 | Currently, there is no way for a user to delete an idea from the page. Your 182 | challenge is to add a button that fixes that! 183 | 184 | Hints: 185 | 186 | - In `convex/myFunctions.ts` you'll need to create a new mutation function 187 | `deleteIdea`, which 188 | [deletes a document](https://docs.convex.dev/database/writing-data#deleting-documents) 189 | from the `ideas` table using its 190 | [Convex document ID](https://docs.convex.dev/database/document-ids) 191 | - In `src/App.tsx`, you'll need the `useMutation` hook to invoke your new 192 | `deleteIdea` function as needed from the frontend 193 | - In `src/App.tsx`, you can add a new button using the `Button` component (see 194 | the "Generate a random app idea" button for an example) 195 | 196 | ### Challenge 2: Prevent duplicates 197 | 198 | At the moment, the app doesn't prevent you from adding the same idea twice (try 199 | it!), so we might save duplicate ideas to the database. Your challenge is to fix 200 | that by making sure that we check for duplicates before saving a new idea! 201 | 202 | Hints: 203 | 204 | - In `convex/myFunctions.ts`, you can modify the `saveIdea` function to check 205 | for duplicates by performing a 206 | [filtered query](https://docs.convex.dev/database/reading-data#equality-conditions) 207 | before inserting the new document. If the query finds any documents whose 208 | `idea` field exactly matches the new idea, do not insert the new document 209 | - To let users know what's happening, you probably want to treat the 210 | did-not-save-duplicate case as an 211 | [application error](https://docs.convex.dev/functions/error-handling/application-errors) 212 | and handle it in the frontend accordingly 213 | 214 | ### Challenge 3: Pagination 215 | 216 | As the list of ideas grows, the page will get very long! Improve performance by 217 | paginating the list of ideas to show only 20 ideas at a time, and let users page 218 | through the rest of the results. 219 | 220 | Hints: 221 | 222 | - In `convex/myFunctions.ts`, you can change the `listIdeas` query function to a 223 | [paginated query](https://docs.convex.dev/database/pagination) function by 224 | accepting a `paginationOpts` argument 225 | - In `src/App.tsx` you'll also need to update the frontend code to use the 226 | [`usePaginatedQuery` hook](https://docs.convex.dev/database/pagination#paginating-within-react-components) 227 | instead of the `useQuery` hook when invoking the `listIdeas` function 228 | - Don't forget to add some buttons or another way for users to access the 229 | next/previous page(s)! 230 | 231 | --- 232 | 233 | # Next steps 234 | 235 | Now that you've grokked the basics, you're ready to get building! 236 | 237 | ## Create a new app from a starter template 238 | 239 | You can quickly spin up a new Convex app with the command: 240 | 241 | ``` 242 | npm create convex@latest 243 | ``` 244 | 245 | This will install the 246 | [`create-convex`](https://www.npmjs.com/package/create-convex) bootstrapper 247 | tool, which will then ask you a series of questions to configure your starter 248 | code. Walk through the prompts and the instructions that follow. 249 | 250 | ## Explore the Convex platform and everything it can do 251 | 252 | Convex offers lots of functionality, so you can pick and choose the parts of the 253 | platform you need to build the app of your dreams! 254 | 255 | Here are some resources to help get you building: 256 | 257 | - For a more in-depth structured intro to Convex, take the 258 | [guided tour](https://docs.convex.dev/get-started) 259 | - The [Convex docs](https://docs.convex.dev/home) are a comprehensive reference 260 | of platform features and how to use them 261 | - [Stack](https://stack.convex.dev/) is Convex's developer learning portal, with 262 | tons of articles & videos on best practices and how-tos 263 | - You can ask questions, get help, and share your Convex projects in the 264 | community [Discord](https://www.convex.dev/community) 265 | - [Convex Search](https://search.convex.dev/) lets you search across all of the 266 | above to find the info you need! 267 | - The [template gallery](https://www.convex.dev/templates) has tons of sample 268 | apps for different tech stacks and use cases 269 | 270 | And in case you want to jump right in to implementing common app features, here 271 | are some resouces on how to: 272 | 273 | - Model [relationships](https://docs.convex.dev/database/document-ids) between 274 | documents 275 | - [Authenticate & manage users](https://docs.convex.dev/auth) 276 | - [Paginate](https://docs.convex.dev/database/pagination) query results 277 | - Retrieve documents with [text](https://docs.convex.dev/text-search) or 278 | [vector](https://docs.convex.dev/vector-search) search 279 | - [Schedule](https://docs.convex.dev/scheduling) function runs 280 | - [Store and manage files](https://docs.convex.dev/file-storage) 281 | - Build [AI apps](https://stack.convex.dev/tag/AI) 282 | 283 | --- 284 | 285 | # What is Convex? 286 | 287 | ![Diagram of how a Convex backend fits in to your fullstack app](https://docs.convex.dev/assets/images/TutorialFigure0-47bd164e06a7396ba005666938c5005b.png) 288 | 289 | [Convex](https://convex.dev) is a hosted backend platform with a built-in 290 | database that lets you write your 291 | [database schema](https://docs.convex.dev/database/schemas) and 292 | [server functions](https://docs.convex.dev/functions) in 293 | [TypeScript](https://docs.convex.dev/typescript). Server-side database 294 | [queries](https://docs.convex.dev/functions/query-functions) automatically 295 | [cache](https://docs.convex.dev/functions/query-functions#caching--reactivity) 296 | and [subscribe](https://docs.convex.dev/client/react#reactivity) to data, 297 | powering a 298 | [realtime `useQuery` hook](https://docs.convex.dev/client/react#fetching-data) 299 | in our [React client](https://docs.convex.dev/client/react). There are also 300 | [Python](https://docs.convex.dev/client/python), 301 | [Rust](https://docs.convex.dev/client/rust), 302 | [ReactNative](https://docs.convex.dev/client/react-native), and 303 | [Node](https://docs.convex.dev/client/javascript) clients, as well as a 304 | straightforward 305 | [HTTP API](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L40). 306 | 307 | The database supports 308 | [NoSQL-style documents](https://docs.convex.dev/database/document-storage) with 309 | [relationships](https://docs.convex.dev/database/document-ids), 310 | [custom indexes](https://docs.convex.dev/database/indexes/) (including on fields 311 | in nested objects) and [vector search](https://docs.convex.dev/vector-search). 312 | 313 | The [`query`](https://docs.convex.dev/functions/query-functions) and 314 | [`mutation`](https://docs.convex.dev/functions/mutation-functions) server 315 | functions have transactional, low latency access to the database and leverage 316 | our [`v8` runtime](https://docs.convex.dev/functions/runtimes) with 317 | [determinism guardrails](https://docs.convex.dev/functions/runtimes#using-randomness-and-time-in-queries-and-mutations) 318 | to provide the strongest ACID guarantees on the market: immediate consistency, 319 | serializable isolation, and automatic conflict resolution via 320 | [optimistic multi-version concurrency control](https://docs.convex.dev/database/advanced/occ) 321 | (OCC / MVCC). 322 | 323 | The [`action` server functions](https://docs.convex.dev/functions/actions) have 324 | access to external APIs and enable other side-effects and non-determinism in 325 | either our [optimized `v8` runtime](https://docs.convex.dev/functions/runtimes) 326 | or a more 327 | [flexible `node` runtime](https://docs.convex.dev/functions/runtimes#nodejs-runtime). 328 | 329 | Functions can run in the background via 330 | [scheduling](https://docs.convex.dev/scheduling/scheduled-functions) and 331 | [cron jobs](https://docs.convex.dev/scheduling/cron-jobs). 332 | 333 | Development is cloud-first, with 334 | [hot reloads for server function](https://docs.convex.dev/cli#run-the-convex-dev-server) 335 | editing via the [CLI](https://docs.convex.dev/cli). There is a 336 | [dashbord UI](https://docs.convex.dev/dashboard) to 337 | [browse and edit data](https://docs.convex.dev/dashboard/deployments/data), 338 | [edit environment variables](https://docs.convex.dev/production/environment-variables), 339 | [view logs](https://docs.convex.dev/dashboard/deployments/logs), 340 | [run server functions](https://docs.convex.dev/dashboard/deployments/functions), 341 | and more. 342 | 343 | There are built-in features for 344 | [reactive pagination](https://docs.convex.dev/database/pagination), 345 | [file storage](https://docs.convex.dev/file-storage), 346 | [reactive search](https://docs.convex.dev/text-search), 347 | [https endpoints](https://docs.convex.dev/functions/http-actions) (for 348 | webhooks), 349 | [streaming import/export](https://docs.convex.dev/database/import-export/), and 350 | [runtime data validation](https://docs.convex.dev/database/schemas#validators) 351 | for [function arguments](https://docs.convex.dev/functions/args-validation) and 352 | [database data](https://docs.convex.dev/database/schemas#schema-validation). 353 | 354 | Everything scales automatically, and it’s 355 | [free to start](https://www.convex.dev/plans). 356 | -------------------------------------------------------------------------------- /_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 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /convex/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Convex functions directory! 2 | 3 | Write your Convex functions here. See 4 | https://docs.convex.dev/using/writing-convex-functions for more. 5 | 6 | A query function that takes two arguments looks like: 7 | 8 | ```ts 9 | // functions.js 10 | import { query } from "./_generated/server"; 11 | import { v } from "convex/values"; 12 | 13 | export const myQueryFunction = query({ 14 | // Validators for arguments. 15 | args: { 16 | first: v.number(), 17 | second: v.string(), 18 | }, 19 | 20 | // Function implementation. 21 | handler: async (ctx, args) => { 22 | // Read the database as many times as you need here. 23 | // See https://docs.convex.dev/database/reading-data. 24 | const documents = await ctx.db.query("tablename").collect(); 25 | 26 | // Arguments passed from the client are properties of the args object. 27 | console.log(args.first, args.second); 28 | 29 | // Write arbitrary JavaScript here: filter, aggregate, build derived data, 30 | // remove non-public properties, or create new objects. 31 | return documents; 32 | }, 33 | }); 34 | ``` 35 | 36 | Using this query function in a React component looks like: 37 | 38 | ```ts 39 | const data = useQuery(api.functions.myQueryFunction, { 40 | first: 10, 41 | second: "hello", 42 | }); 43 | ``` 44 | 45 | A mutation function looks like: 46 | 47 | ```ts 48 | // functions.js 49 | import { mutation } from "./_generated/server"; 50 | import { v } from "convex/values"; 51 | 52 | export const myMutationFunction = mutation({ 53 | // Validators for arguments. 54 | args: { 55 | first: v.string(), 56 | second: v.string(), 57 | }, 58 | 59 | // Function implementation. 60 | handler: async (ctx, args) => { 61 | // Insert or modify documents in the database here. 62 | // Mutations can also read from the database like queries. 63 | // See https://docs.convex.dev/database/writing-data. 64 | const message = { body: args.first, author: args.second }; 65 | const id = await ctx.db.insert("messages", message); 66 | 67 | // Optionally, return a value from your mutation. 68 | return await ctx.db.get(id); 69 | }, 70 | }); 71 | ``` 72 | 73 | Using this mutation function in a React component looks like: 74 | 75 | ```ts 76 | const mutation = useMutation(api.functions.myMutationFunction); 77 | function handleButtonPress() { 78 | // fire and forget, the most common way to use mutations 79 | mutation({ first: "Hello!", second: "me" }); 80 | // OR 81 | // use the result once the mutation has completed 82 | mutation({ first: "Hello!", second: "me" }).then((result) => 83 | console.log(result), 84 | ); 85 | } 86 | ``` 87 | 88 | Use the Convex CLI to push your functions to a deployment. See everything 89 | the Convex CLI can do by running `npx convex -h` in your project root 90 | directory. To learn more, launch the docs with `npx convex docs`. 91 | -------------------------------------------------------------------------------- /convex/_generated/api.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import type { 13 | ApiFromModules, 14 | FilterApi, 15 | FunctionReference, 16 | } from "convex/server"; 17 | import type * as _schema from "../_schema.js"; 18 | import type * as myFunctions from "../myFunctions.js"; 19 | 20 | /** 21 | * A utility for referencing Convex functions in your app's API. 22 | * 23 | * Usage: 24 | * ```js 25 | * const myFunctionReference = api.myModule.myFunction; 26 | * ``` 27 | */ 28 | declare const fullApi: ApiFromModules<{ 29 | _schema: typeof _schema; 30 | myFunctions: typeof myFunctions; 31 | }>; 32 | export declare const api: FilterApi< 33 | typeof fullApi, 34 | FunctionReference 35 | >; 36 | export declare const internal: FilterApi< 37 | typeof fullApi, 38 | FunctionReference 39 | >; 40 | -------------------------------------------------------------------------------- /convex/_generated/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { anyApi } from "convex/server"; 13 | 14 | /** 15 | * A utility for referencing Convex functions in your app's API. 16 | * 17 | * Usage: 18 | * ```js 19 | * const myFunctionReference = api.myModule.myFunction; 20 | * ``` 21 | */ 22 | export const api = anyApi; 23 | export const internal = anyApi; 24 | -------------------------------------------------------------------------------- /convex/_generated/dataModel.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated data model types. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { AnyDataModel } from "convex/server"; 13 | import type { GenericId } from "convex/values"; 14 | 15 | /** 16 | * No `schema.ts` file found! 17 | * 18 | * This generated code has permissive types like `Doc = any` because 19 | * Convex doesn't know your schema. If you'd like more type safety, see 20 | * https://docs.convex.dev/using/schemas for instructions on how to add a 21 | * schema file. 22 | * 23 | * After you change a schema, rerun codegen with `npx convex dev`. 24 | */ 25 | 26 | /** 27 | * The names of all of your Convex tables. 28 | */ 29 | export type TableNames = string; 30 | 31 | /** 32 | * The type of a document stored in Convex. 33 | */ 34 | export type Doc = any; 35 | 36 | /** 37 | * An identifier for a document in Convex. 38 | * 39 | * Convex documents are uniquely identified by their `Id`, which is accessible 40 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). 41 | * 42 | * Documents can be loaded using `db.get(id)` in query and mutation functions. 43 | * 44 | * IDs are just strings at runtime, but this type can be used to distinguish them from other 45 | * strings when type checking. 46 | */ 47 | export type Id = 48 | GenericId; 49 | 50 | /** 51 | * A type describing your Convex data model. 52 | * 53 | * This type includes information about what tables you have, the type of 54 | * documents stored in those tables, and the indexes defined on them. 55 | * 56 | * This type is used to parameterize methods like `queryGeneric` and 57 | * `mutationGeneric` to make them type-safe. 58 | */ 59 | export type DataModel = AnyDataModel; 60 | -------------------------------------------------------------------------------- /convex/_generated/server.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | ActionBuilder, 14 | HttpActionBuilder, 15 | MutationBuilder, 16 | QueryBuilder, 17 | GenericActionCtx, 18 | GenericMutationCtx, 19 | GenericQueryCtx, 20 | GenericDatabaseReader, 21 | GenericDatabaseWriter, 22 | } from "convex/server"; 23 | import type { DataModel } from "./dataModel.js"; 24 | 25 | /** 26 | * Define a query in this Convex app's public API. 27 | * 28 | * This function will be allowed to read your Convex database and will be accessible from the client. 29 | * 30 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 31 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 32 | */ 33 | export declare const query: QueryBuilder; 34 | 35 | /** 36 | * Define a query that is only accessible from other Convex functions (but not from the client). 37 | * 38 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 39 | * 40 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 41 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 42 | */ 43 | export declare const internalQuery: QueryBuilder; 44 | 45 | /** 46 | * Define a mutation in this Convex app's public API. 47 | * 48 | * This function will be allowed to modify your Convex database and will be accessible from the client. 49 | * 50 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 51 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 52 | */ 53 | export declare const mutation: MutationBuilder; 54 | 55 | /** 56 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 57 | * 58 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 59 | * 60 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 61 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 62 | */ 63 | export declare const internalMutation: MutationBuilder; 64 | 65 | /** 66 | * Define an action in this Convex app's public API. 67 | * 68 | * An action is a function which can execute any JavaScript code, including non-deterministic 69 | * code and code with side-effects, like calling third-party services. 70 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 71 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 72 | * 73 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 74 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 75 | */ 76 | export declare const action: ActionBuilder; 77 | 78 | /** 79 | * Define an action that is only accessible from other Convex functions (but not from the client). 80 | * 81 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 82 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 83 | */ 84 | export declare const internalAction: ActionBuilder; 85 | 86 | /** 87 | * Define an HTTP action. 88 | * 89 | * This function will be used to respond to HTTP requests received by a Convex 90 | * deployment if the requests matches the path and method where this action 91 | * is routed. Be sure to route your action in `convex/http.js`. 92 | * 93 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 94 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. 95 | */ 96 | export declare const httpAction: HttpActionBuilder; 97 | 98 | /** 99 | * A set of services for use within Convex query functions. 100 | * 101 | * The query context is passed as the first argument to any Convex query 102 | * function run on the server. 103 | * 104 | * This differs from the {@link MutationCtx} because all of the services are 105 | * read-only. 106 | */ 107 | export type QueryCtx = GenericQueryCtx; 108 | 109 | /** 110 | * A set of services for use within Convex mutation functions. 111 | * 112 | * The mutation context is passed as the first argument to any Convex mutation 113 | * function run on the server. 114 | */ 115 | export type MutationCtx = GenericMutationCtx; 116 | 117 | /** 118 | * A set of services for use within Convex action functions. 119 | * 120 | * The action context is passed as the first argument to any Convex action 121 | * function run on the server. 122 | */ 123 | export type ActionCtx = GenericActionCtx; 124 | 125 | /** 126 | * An interface to read from the database within Convex query functions. 127 | * 128 | * The two entry points are {@link DatabaseReader.get}, which fetches a single 129 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts 130 | * building a query. 131 | */ 132 | export type DatabaseReader = GenericDatabaseReader; 133 | 134 | /** 135 | * An interface to read from and write to the database within Convex mutation 136 | * functions. 137 | * 138 | * Convex guarantees that all writes within a single mutation are 139 | * executed atomically, so you never have to worry about partial writes leaving 140 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control) 141 | * for the guarantees Convex provides your functions. 142 | */ 143 | export type DatabaseWriter = GenericDatabaseWriter; 144 | -------------------------------------------------------------------------------- /convex/_generated/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | actionGeneric, 14 | httpActionGeneric, 15 | queryGeneric, 16 | mutationGeneric, 17 | internalActionGeneric, 18 | internalMutationGeneric, 19 | internalQueryGeneric, 20 | } from "convex/server"; 21 | 22 | /** 23 | * Define a query in this Convex app's public API. 24 | * 25 | * This function will be allowed to read your Convex database and will be accessible from the client. 26 | * 27 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 28 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 29 | */ 30 | export const query = queryGeneric; 31 | 32 | /** 33 | * Define a query that is only accessible from other Convex functions (but not from the client). 34 | * 35 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 36 | * 37 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 38 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 39 | */ 40 | export const internalQuery = internalQueryGeneric; 41 | 42 | /** 43 | * Define a mutation in this Convex app's public API. 44 | * 45 | * This function will be allowed to modify your Convex database and will be accessible from the client. 46 | * 47 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 48 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 49 | */ 50 | export const mutation = mutationGeneric; 51 | 52 | /** 53 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 54 | * 55 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 56 | * 57 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 58 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 59 | */ 60 | export const internalMutation = internalMutationGeneric; 61 | 62 | /** 63 | * Define an action in this Convex app's public API. 64 | * 65 | * An action is a function which can execute any JavaScript code, including non-deterministic 66 | * code and code with side-effects, like calling third-party services. 67 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 68 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 69 | * 70 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 71 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 72 | */ 73 | export const action = actionGeneric; 74 | 75 | /** 76 | * Define an action that is only accessible from other Convex functions (but not from the client). 77 | * 78 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 79 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 80 | */ 81 | export const internalAction = internalActionGeneric; 82 | 83 | /** 84 | * Define a Convex HTTP action. 85 | * 86 | * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object 87 | * as its second. 88 | * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`. 89 | */ 90 | export const httpAction = httpActionGeneric; 91 | -------------------------------------------------------------------------------- /convex/_schema.ts: -------------------------------------------------------------------------------- 1 | // OPTIONAL: Rename this file to `schema.ts` to declare the shape 2 | // of the data in your database. 3 | // See https://docs.convex.dev/database/schemas. 4 | 5 | import { defineSchema, defineTable } from "convex/server"; 6 | import { v } from "convex/values"; 7 | 8 | export default defineSchema( 9 | { 10 | documents: defineTable({ 11 | fieldOne: v.string(), 12 | fieldTwo: v.object({ 13 | subFieldOne: v.array(v.number()), 14 | }), 15 | }), 16 | // This definition matches the example query and mutation code: 17 | numbers: defineTable({ 18 | value: v.number(), 19 | }), 20 | }, 21 | // If you ever get an error about schema mismatch 22 | // between your data and your schema, and you cannot 23 | // change the schema to match the current data in your database, 24 | // you can: 25 | // 1. Use the dashboard to delete tables or individual documents 26 | // that are causing the error. 27 | // 2. Change this option to `false` and make changes to the data 28 | // freely, ignoring the schema. Don't forget to change back to `true`! 29 | { schemaValidation: true }, 30 | ); 31 | -------------------------------------------------------------------------------- /convex/myFunctions.ts: -------------------------------------------------------------------------------- 1 | import { v } from "convex/values"; 2 | import { query, mutation, action } from "./_generated/server"; 3 | import { api } from "./_generated/api"; 4 | 5 | // Write your Convex functions in any file inside this directory (`convex`). 6 | // See https://docs.convex.dev/functions for more. 7 | 8 | // You can read data from the database via a query function: 9 | export const listIdeas = query({ 10 | // Validators for arguments. 11 | args: {}, 12 | 13 | // Query function implementation. 14 | handler: async (ctx, args) => { 15 | // Read the database as many times as you need here. 16 | // See https://docs.convex.dev/database/reading-data. 17 | return await ctx.db.query("ideas").collect(); 18 | }, 19 | }); 20 | 21 | // You can write data to the database via a mutation function: 22 | export const saveIdea = mutation({ 23 | // Validators for arguments. 24 | args: { 25 | idea: v.string(), 26 | random: v.boolean(), 27 | }, 28 | 29 | // Mutation function implementation. 30 | handler: async (ctx, args) => { 31 | // Insert or modify documents in the database here. 32 | // Mutations can also read from the database like queries. 33 | // See https://docs.convex.dev/database/writing-data. 34 | 35 | // Optionally, capture the ID of the newly created document 36 | const id = await ctx.db.insert("ideas", args); 37 | 38 | // Optionally, return a value from your mutation. 39 | return id; 40 | }, 41 | }); 42 | 43 | // You can fetch data from and send data to third-party APIs via an action: 44 | export const fetchRandomIdea = action({ 45 | // Validators for arguments. 46 | args: {}, 47 | 48 | // Action implementation. 49 | handler: async (ctx) => { 50 | // Use the browser-like `fetch` API to send HTTP requests. 51 | // See https://docs.convex.dev/functions/actions#calling-third-party-apis-and-using-npm-packages. 52 | const response = await fetch("https://appideagenerator.com/call.php"); 53 | const idea = await response.text(); 54 | 55 | // Write or query data by running Convex mutations/queries from within an action 56 | await ctx.runMutation(api.myFunctions.saveIdea, { 57 | idea: idea.trim(), 58 | random: true, 59 | }); 60 | 61 | // Optionally, return a value from your action 62 | return idea; 63 | }, 64 | }); 65 | -------------------------------------------------------------------------------- /convex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | /* This TypeScript project config describes the environment that 3 | * Convex functions run in and is used to typecheck them. 4 | * You can modify it, but some settings required to use Convex. 5 | */ 6 | "compilerOptions": { 7 | /* These settings are not required by Convex and can be modified. */ 8 | "allowJs": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | 12 | /* These compiler options are required by Convex */ 13 | "target": "ESNext", 14 | "lib": ["ES2021", "dom"], 15 | "forceConsistentCasingInFileNames": true, 16 | "allowSyntheticDefaultImports": true, 17 | "module": "ESNext", 18 | "moduleResolution": "Node", 19 | "isolatedModules": true, 20 | "noEmit": true 21 | }, 22 | "include": ["./**/*"], 23 | "exclude": ["./_generated"] 24 | } 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Convex Hack Pack 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "convex-hack-pack", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "npm-run-all dev:init --parallel dev:frontend dev:backend", 8 | "dev:frontend": "vite --open", 9 | "dev:backend": "convex dev", 10 | "dev:init": "convex dev --until-success && convex dashboard", 11 | "build": "tsc && vite build", 12 | "lint": "eslint . --ext ts,tsx ", 13 | "preview": "vite preview" 14 | }, 15 | "dependencies": { 16 | "@hookform/resolvers": "^3.9.0", 17 | "@radix-ui/react-checkbox": "^1.1.1", 18 | "@radix-ui/react-icons": "^1.3.0", 19 | "@radix-ui/react-label": "^2.1.0", 20 | "@radix-ui/react-slot": "^1.1.0", 21 | "class-variance-authority": "^0.7.0", 22 | "clsx": "^2.1.1", 23 | "cmdk": "^1.0.0", 24 | "convex": "^1.16.0", 25 | "date-fns": "^3.6.0", 26 | "react": "^18.3.1", 27 | "react-day-picker": "^9.0.9", 28 | "react-dom": "^18.3.1", 29 | "react-hook-form": "^7.53.0", 30 | "tailwind-merge": "^2.5.2", 31 | "tailwindcss-animate": "^1.0.7", 32 | "zod": "^3.23.8" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^22.5.4", 36 | "@types/react": "^18.3.5", 37 | "@types/react-dom": "^18.3.0", 38 | "@typescript-eslint/eslint-plugin": "^8.5.0", 39 | "@typescript-eslint/parser": "^8.5.0", 40 | "@vitejs/plugin-react": "^4.3.1", 41 | "autoprefixer": "^10.4.20", 42 | "eslint": "^8.57.0", 43 | "eslint-plugin-react-hooks": "^4.6.2", 44 | "eslint-plugin-react-refresh": "^0.4.11", 45 | "npm-run-all": "^4.1.5", 46 | "postcss": "^8.4.45", 47 | "tailwindcss": "^3.4.11", 48 | "typescript": "^5.6.2", 49 | "vite": "^5.4.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useAction, useMutation, useQuery } from "convex/react"; 2 | import { api } from "../convex/_generated/api"; 3 | import { useState } from "react"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Input } from "@/components/ui/input"; 6 | import { Checkbox } from "@/components/ui/checkbox"; 7 | import { Label } from "@/components/ui/label"; 8 | 9 | function App() { 10 | const [newIdea, setNewIdea] = useState(""); 11 | const [includeRandom, setIncludeRandom] = useState(true); 12 | 13 | const ideas = useQuery(api.myFunctions.listIdeas); 14 | const saveIdea = useMutation(api.myFunctions.saveIdea); 15 | const generateIdea = useAction(api.myFunctions.fetchRandomIdea); 16 | 17 | return ( 18 | <> 19 |
20 |

21 | Get hacking with Convex 22 |

23 | 24 |

Let's brainstorm apps to build!

25 | 26 |
27 | setNewIdea(event.target.value)} 31 | placeholder="Type your app idea here" 32 | /> 33 | 50 |
51 | 52 |
53 | 61 | 62 |
66 | setIncludeRandom(!includeRandom)} 70 | /> 71 | 78 |
79 |
80 | 81 |
    82 | {ideas?.map((document, i) => ( 83 |
  • 84 | {document.random ? "🤖 " : "💡 "} 85 | {document.idea} 86 |
  • 87 | ))} 88 |
89 |
90 | 104 | 105 | ); 106 | } 107 | 108 | export default App; 109 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/typography/code.tsx: -------------------------------------------------------------------------------- 1 | import { se } from "@/lib/utils"; 2 | 3 | export const Code = se( 4 | "code", 5 | "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold", 6 | ); 7 | -------------------------------------------------------------------------------- /src/components/typography/link.tsx: -------------------------------------------------------------------------------- 1 | import { se } from "@/lib/utils"; 2 | import { AnchorHTMLAttributes } from "react"; 3 | 4 | export const Link = se< 5 | HTMLAnchorElement, 6 | AnchorHTMLAttributes 7 | >( 8 | "a", 9 | "font-medium text-primary underline underline-offset-4 hover:no-underline", 10 | ); 11 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | }, 35 | ); 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean; 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button"; 46 | return ( 47 | 52 | ); 53 | }, 54 | ); 55 | Button.displayName = "Button"; 56 | 57 | export { Button }; 58 | -------------------------------------------------------------------------------- /src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 3 | import { CheckIcon } from "@radix-ui/react-icons"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const Checkbox = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 19 | 22 | 23 | 24 | 25 | )); 26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 27 | 28 | export { Checkbox }; 29 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | }, 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as LabelPrimitive from "@radix-ui/react-label"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 9 | ); 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )); 22 | Label.displayName = LabelPrimitive.Root.displayName; 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --convex-yellow: #f3b01c; 8 | --convex-red: #ee342f; 9 | --convex-purple: #8d2676; 10 | 11 | --background: 0 0% 100%; 12 | --foreground: 240 10% 3.9%; 13 | 14 | --card: 0 0% 100%; 15 | --card-foreground: 240 10% 3.9%; 16 | 17 | --popover: 0 0% 100%; 18 | --popover-foreground: 240 10% 3.9%; 19 | 20 | --primary: 240 5.9% 10%; 21 | --primary-foreground: 0 0% 98%; 22 | 23 | --secondary: 240 4.8% 95.9%; 24 | --secondary-foreground: 240 5.9% 10%; 25 | 26 | --muted: 240 4.8% 95.9%; 27 | --muted-foreground: 240 3.8% 46.1%; 28 | 29 | --accent: 240 4.8% 95.9%; 30 | --accent-foreground: 240 5.9% 10%; 31 | 32 | --destructive: 0 84.2% 60.2%; 33 | --destructive-foreground: 0 0% 98%; 34 | 35 | --border: 240 5.9% 90%; 36 | --input: 240 5.9% 90%; 37 | --ring: 240 10% 3.9%; 38 | 39 | --radius: 0.5rem; 40 | 41 | font-size: 22px; 42 | } 43 | 44 | a { 45 | color: var(--convex-yellow); 46 | } 47 | 48 | .convex-yellow { 49 | color: var(--convex-yellow); 50 | } 51 | .convex-red { 52 | color: var(--convex-red); 53 | } 54 | .convex-purple { 55 | color: var(--convex-purple); 56 | } 57 | 58 | @media screen and (min-width: 800px) { 59 | :root { 60 | font-size: 26px; 61 | } 62 | } 63 | 64 | @media screen and (min-width: 1000px) { 65 | :root { 66 | font-size: 30px; 67 | } 68 | } 69 | 70 | @media (prefers-color-scheme: dark) { 71 | :root { 72 | --background: 240 10% 3.9%; 73 | --foreground: 0 0% 98%; 74 | 75 | --card: 240 10% 3.9%; 76 | --card-foreground: 0 0% 98%; 77 | 78 | --popover: 240 10% 3.9%; 79 | --popover-foreground: 0 0% 98%; 80 | 81 | --primary: 0 0% 98%; 82 | --primary-foreground: 240 5.9% 10%; 83 | 84 | --secondary: 240 3.7% 15.9%; 85 | --secondary-foreground: 0 0% 98%; 86 | 87 | --muted: 240 3.7% 15.9%; 88 | --muted-foreground: 240 5% 64.9%; 89 | 90 | --accent: 240 3.7% 15.9%; 91 | --accent-foreground: 0 0% 98%; 92 | 93 | --destructive: 0 62.8% 30.6%; 94 | --destructive-foreground: 0 0% 98%; 95 | 96 | --border: 240 3.7% 15.9%; 97 | --input: 240 3.7% 15.9%; 98 | --ring: 240 4.9% 83.9%; 99 | } 100 | } 101 | } 102 | 103 | @layer base { 104 | * { 105 | @apply border-border; 106 | } 107 | body { 108 | @apply bg-background text-foreground; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/lib/utils.tsx: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { ForwardRefRenderFunction, forwardRef } from "react"; 3 | import { twMerge } from "tailwind-merge"; 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | 9 | // forward refs 10 | export function fr>( 11 | component: ForwardRefRenderFunction, 12 | ) { 13 | const wrapped = forwardRef(component); 14 | wrapped.displayName = component.name; 15 | return wrapped; 16 | } 17 | 18 | // styled element 19 | export function se< 20 | T = HTMLElement, 21 | P extends React.HTMLAttributes = React.HTMLAttributes, 22 | >(Tag: keyof React.ReactHTML, ...classNames: ClassValue[]) { 23 | const component = fr(({ className, ...props }, ref) => ( 24 | // @ts-expect-error Too complicated for TypeScript 25 | 26 | )); 27 | component.displayName = Tag[0].toUpperCase() + Tag.slice(1); 28 | return component; 29 | } 30 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { ConvexProvider, ConvexReactClient } from "convex/react"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom/client"; 4 | import App from "./App"; 5 | import "./index.css"; 6 | 7 | const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string); 8 | 9 | ReactDOM.createRoot(document.getElementById("root")!).render( 10 | 11 | 12 | 13 | 14 | , 15 | ); 16 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 5 | theme: { 6 | container: { 7 | center: true, 8 | padding: "2rem", 9 | screens: { 10 | "2xl": "1400px", 11 | }, 12 | }, 13 | extend: { 14 | colors: { 15 | border: "hsl(var(--border))", 16 | input: "hsl(var(--input))", 17 | ring: "hsl(var(--ring))", 18 | background: "hsl(var(--background))", 19 | foreground: "hsl(var(--foreground))", 20 | primary: { 21 | DEFAULT: "hsl(var(--primary))", 22 | foreground: "hsl(var(--primary-foreground))", 23 | }, 24 | secondary: { 25 | DEFAULT: "hsl(var(--secondary))", 26 | foreground: "hsl(var(--secondary-foreground))", 27 | }, 28 | destructive: { 29 | DEFAULT: "hsl(var(--destructive))", 30 | foreground: "hsl(var(--destructive-foreground))", 31 | }, 32 | muted: { 33 | DEFAULT: "hsl(var(--muted))", 34 | foreground: "hsl(var(--muted-foreground))", 35 | }, 36 | accent: { 37 | DEFAULT: "hsl(var(--accent))", 38 | foreground: "hsl(var(--accent-foreground))", 39 | }, 40 | popover: { 41 | DEFAULT: "hsl(var(--popover))", 42 | foreground: "hsl(var(--popover-foreground))", 43 | }, 44 | card: { 45 | DEFAULT: "hsl(var(--card))", 46 | foreground: "hsl(var(--card-foreground))", 47 | }, 48 | }, 49 | borderRadius: { 50 | lg: "var(--radius)", 51 | md: "calc(var(--radius) - 2px)", 52 | sm: "calc(var(--radius) - 4px)", 53 | }, 54 | keyframes: { 55 | "accordion-down": { 56 | from: { height: 0 }, 57 | to: { height: "var(--radix-accordion-content-height)" }, 58 | }, 59 | "accordion-up": { 60 | from: { height: "var(--radix-accordion-content-height)" }, 61 | to: { height: 0 }, 62 | }, 63 | }, 64 | animation: { 65 | "accordion-down": "accordion-down 0.2s ease-out", 66 | "accordion-up": "accordion-up 0.2s ease-out", 67 | }, 68 | }, 69 | }, 70 | plugins: [require("tailwindcss-animate")], 71 | }; 72 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | "baseUrl": ".", 24 | "paths": { 25 | "@/*": ["./src/*"] 26 | } 27 | }, 28 | "references": [{ "path": "./tsconfig.node.json" }] 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import react from "@vitejs/plugin-react"; 3 | import { defineConfig } from "vite"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | }); 14 | --------------------------------------------------------------------------------