├── .cursorrules ├── .gitattributes ├── .gitignore ├── .waspignore ├── .wasproot ├── README.md ├── main.wasp ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public ├── .gitkeep └── logo.webp ├── schema.prisma ├── src ├── auth │ ├── LoginPage.tsx │ └── SignupPage.tsx ├── client │ ├── LandingPage.tsx │ ├── Main.css │ ├── Main.tsx │ └── components │ │ ├── containerWithFlatShadow.tsx │ │ └── templateHero.tsx ├── exampleNotesFeature │ ├── ExampleNotePage.tsx │ ├── ExampleNotesDashboard.tsx │ └── operations.ts └── vite-env.d.ts ├── tailwind.config.cjs ├── tsconfig.json └── vite.config.ts /.cursorrules: -------------------------------------------------------------------------------- 1 | // Wasp Import Rules 2 | - Path to Wasp functions within .ts files must come from 'wasp', not '@wasp'! 3 | ✓ import { Task } from 'wasp/entities' 4 | ✓ import type { GetTasks } from 'wasp/server/operations' 5 | ✓ import { getTasks, useQuery } from 'wasp/client/operations' 6 | ✗ import { getTasks, useQuery } from '@wasp/...' 7 | ✗ import { getTasks, useQuery } from '@src/feature/operations.ts' 8 | 9 | - Path to external imports within 'main.wasp' must start with "@src/"! 10 | ✓ component: import { LoginPage } from "@src/client/pages/auth/LoginPage.tsx" 11 | ✗ component: import { LoginPage } from "@client/pages/auth/LoginPage.tsx" 12 | - In the client's root component, use the Outlet component rather than children 13 | ✓ import { Outlet } from 'react-router-dom'; 14 | 15 | // Wasp DB Schema Rules 16 | - Add databse models to the 'schema.prisma' file, NOT to 'main.wasp' as "entities" 17 | - Do NOT add a db.system nor a db.prisma property to 'main.wasp'. This is taken care of in 'schema.prisma' 18 | - Keep the 'schema.prisma' within the root of the project 19 | 20 | // Wasp Operations 21 | - Types are generated automatically from the function definition in 'main.wasp', 22 | ✓ import type { GetTimeLogs, CreateTimeLog, UpdateTimeLog } from 'wasp/server/operations' 23 | - Wasp also generates entity types based on the models in 'schema.prisma' 24 | ✓ import type { Project, TimeLog } from 'wasp/entities' 25 | - Make sure that all Entities that should be included in the operations context are defined in its definition in 'main.wasp' 26 | ✓ action createTimeLog { fn: import { createTimeLog } from "@src/server/timeLogs/operations.js", entities: [TimeLog, Project] } 27 | 28 | // Wasp Auth 29 | - When creating Auth pages, use the LoginForm and SignupForm components provided by Wasp 30 | ✓ import { LoginForm } from 'wasp/client/auth' 31 | - Wasp takes care of creating the user's auth model id, username, and password for a user, so a user model DOES NOT need these properties 32 | ✓ model User { id Int @id @default(autoincrement()) } 33 | 34 | // Wasp Dependencies 35 | - Do NOT add dependencies to 'main.wasp' 36 | - Install dependencies via 'npm install' instead 37 | 38 | // Wasp 39 | - Use the latest Wasp version, ^0.15.0 40 | - Always use typescript for Wasp code. 41 | - When creating Wasp operations (queries and actions) combine them into an operations.ts file within the feature directory rather than into separate queries.ts and actions.ts files 42 | 43 | // React 44 | - Use relative imports for other react components 45 | - If importing a function from an operations file, defer to the wasp import rules 46 | 47 | // CSS 48 | - Use Tailwind CSS for styling. 49 | - Do not use inline styles unless necessary 50 | 51 | // General 52 | - Use single quotes -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.wasp linguist-language=TypeScript -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .wasp/ 2 | node_modules/ 3 | migrations/ 4 | 5 | # Ignore all dotenv files by default to prevent accidentally committing any secrets. 6 | # To include specific dotenv files, use the `!` operator or adjust these rules. 7 | .env 8 | .env.* 9 | 10 | # Don't ignore example dotenv files. 11 | !.env.example 12 | !.env.*.example 13 | -------------------------------------------------------------------------------- /.waspignore: -------------------------------------------------------------------------------- 1 | # Ignore editor tmp files 2 | **/*~ 3 | **/#*# 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.wasproot: -------------------------------------------------------------------------------- 1 | File marking the root of Wasp project. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Wasp Cursor IDE Template 2 | 3 | This is a basic starter template for [Wasp](https://wasp-lang.dev/) apps but with a couple of modifications to give you the best experience when using the [Cursor IDE](https://cursor.sh/) for development. Specifically, a `.cursorrules` file optimized for Wasp apps, and Wasp example code curated specifically for Cursor. 4 | 5 | ## Getting Started 6 | 7 | 1. Make sure you have Wasp installed: `curl -sSL https://get.wasp-lang.dev/installer.sh | sh -s` 8 | 2. Create a new repo from this template: [Use the Wasp Cursor Template](https://github.com/wasp-lang/cursor-template/generate) 9 | 3. Clone your new repo: `git clone https://github.com//.git` 10 | 4. Position yourself in the project directory: `cd ` 11 | 5. Run `wasp db start` to start the Postgres database. 12 | 6. In a new terminal, run `wasp db migrate-dev` to migrate the database. 13 | 7. Run `wasp start` to start the development server. 14 | 15 | ## How it works with Cursor 16 | 17 | We've included a `.cursorrules` file that provides context to the Cursor AI so that it can help you build your Wasp app. Make sure Cursor can access this file by going to `preferences > cursor settings > general > include .cursorrules file`. 18 | 19 | The `.cursorrules` file is our attempt at fixing the common mistakes the AI assistants make while building a Wasp full-stack app, but if you find that the AI is still making mistakes, you can try to add more context to the `.cursorrules` file, or within the Cursor settings. 20 | 21 | Also, make sure you have the [Wasp docs](https://wasp-lang.dev/docs) indexed in Cursor. You can do this by going to `preferences > cursor settings > features > add new doc `. Then, you can include them in Cursor chat by using the `@docs` keyword. This often improves the AI's ability to help you with Wasp-specific code. 22 | 23 | ## Project Structure 24 | 25 | The project is structured to offer just enough context for the AI to help you with Wasp-specific code, and to get you started with building your app quickly, but without overwhelming you with too much code. 26 | 27 | In the sections below, we'll go through each directory and explain why we've included them and what they do. 28 | 29 | ``` 30 | src/ 31 | ├── auth/ // Auth-related files 32 | ├── client/ // Client-only components and the Main.tsx app wrapper 33 | ├── exampleNotesFeature/ // Example feature code (for Cursor) 34 | │ ├── ExampleNotePage.tsx 35 | │ ├── ExampleNoteDashboard.tsx 36 | │ └── operations.ts // Example server-side functions 37 | ├── main.wasp // App configuration file where you can define routes, auth, operations, etc. 38 | ├── .cursorrules // Cursor rules file / where the magic happens 39 | ``` 40 | 41 | ### `main.wasp` 42 | 43 | The `main.wasp` file is the main configuration file for your app. It's where you define your routes, auth, operations, etc. 44 | 45 | This is Wasp's secret sauce and allows Wasp to generate the full-stack code for your app, so you can just focus on writing the business logic. 46 | 47 | For more info on Wasp's config file and how Wasp works, check out the [Wasp Introduction](https://wasp-lang.dev/docs#so-what-does-the-code-look-like). 48 | 49 | ### Auth 50 | 51 | The `auth` directory contains the login and signup pages. They import and take advantage of Wasp's Auth API to handle the full-stack auth logic for you. E.g., after defining the Auth methods you'd like to use in `main.wasp`, Wasp will generate the login and signup forms for the defined methods, and handle the full-stack auth logic for you, including validation, error handling, etc. 52 | 53 | For more info on Wasp Auth, check out the [Wasp Auth docs](https://wasp-lang.dev/docs/auth/overview). 54 | 55 | ### Client 56 | 57 | The `client` directory contains the client-only components and the `Main.tsx` app wrapper. It also contains the `Main.css` file, which imports Tailwind CSS so you can can get started styling your app with Tailwind without having to configure it yourself. 58 | 59 | ### Example Notes Feature 60 | 61 | The `exampleNotesFeature` directory contains the example full-stack feature code. It's a simple page that allows you to create, read, update, and delete notes. 62 | 63 | This feature is a good starting point for the AI to reference and use an example when creating new features for your app. It is especially convenient when using Cusror Compose to create and edit features across multiple files. Once you start building out a feature set of your own, you can delete this example feature if you like. 64 | 65 | Note that this directory includes an `operations.ts` file, which contains the server-side functions for the feature. Because these functions are defined in the `main.wasp` file, Wasp will configure the server to use them, and also make them available to the client. 66 | 67 | For more info on Wasp Operations, check out the [Wasp Operations docs](https://wasp-lang.dev/docs/data-model/operations/overview). 68 | 69 | ## Extra help 70 | 71 | If you get stuck, you can ask for help on the [Wasp Discord](https://discord.gg/rzdnErX). =} -------------------------------------------------------------------------------- /main.wasp: -------------------------------------------------------------------------------- 1 | app cursorTemplate { 2 | wasp: { 3 | version: "^0.15.0" 4 | }, 5 | title: "Wasp Cursor IDE Template", 6 | client: { 7 | rootComponent: import { Main } from "@src/client/Main.tsx", 8 | }, 9 | auth: { 10 | userEntity: User, 11 | methods: { 12 | usernameAndPassword: {}, 13 | // For more info on using other Auth methods, check out the Wasp Auth docs: https://wasp-lang.dev/docs/auth/overview 14 | // google: {} 15 | // emailAndPassword: {} 16 | }, 17 | onAuthFailedRedirectTo: "/login" 18 | }, 19 | } 20 | 21 | route LandingPageRoute { path: "/", to: LandingPage } 22 | page LandingPage { 23 | component: import { LandingPage } from "@src/client/LandingPage.tsx" 24 | } 25 | 26 | //#region Auth 27 | route LoginRoute { path: "/login", to: LoginPage } 28 | page LoginPage { 29 | authRequired: false, 30 | component: import { LoginPage } from "@src/auth/LoginPage.tsx" 31 | } 32 | 33 | route SignupRoute { path: "/signup", to: SignupPage } 34 | page SignupPage { 35 | authRequired: false, 36 | component: import { SignupPage } from "@src/auth/SignupPage.tsx" 37 | } 38 | //#endregion 39 | 40 | //#region Example Note Feature 41 | route ExampleNotesDashboardRoute { path: "/example-notes", to: ExampleNotesDashboard } 42 | page ExampleNotesDashboard { 43 | component: import { ExampleNotesDashboard } from "@src/exampleNotesFeature/ExampleNotesDashboard.tsx" 44 | } 45 | 46 | route ExampleNoteRoute { path: "/example-note/:id", to: ExampleNotePage } 47 | page ExampleNotePage { 48 | authRequired: true, 49 | component: import { ExampleNotePage } from "@src/exampleNotesFeature/ExampleNotePage.tsx" 50 | } 51 | 52 | query getExampleNote { 53 | fn: import { getExampleNote } from "@src/exampleNotesFeature/operations.ts", 54 | entities: [ExampleNote] 55 | } 56 | 57 | query getExampleNotes { 58 | fn: import { getExampleNotes } from "@src/exampleNotesFeature/operations.ts", 59 | entities: [ExampleNote] 60 | } 61 | 62 | action createExampleNote { 63 | fn: import { createExampleNote } from "@src/exampleNotesFeature/operations.ts", 64 | entities: [ExampleNote] 65 | } 66 | 67 | action updateExampleNote { 68 | fn: import { updateExampleNote } from "@src/exampleNotesFeature/operations.ts", 69 | entities: [ExampleNote] 70 | } 71 | 72 | action deleteExampleNote { 73 | fn: import { deleteExampleNote } from "@src/exampleNotesFeature/operations.ts", 74 | entities: [ExampleNote] 75 | } 76 | //#endregion 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timeTracker", 3 | "type": "module", 4 | "dependencies": { 5 | "wasp": "file:.wasp/out/sdk/wasp", 6 | "react": "^18.2.0" 7 | }, 8 | "devDependencies": { 9 | "typescript": "^5.1.0", 10 | "vite": "^4.3.9", 11 | "@types/react": "^18.0.37", 12 | "prisma": "5.19.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasp-lang/cursor-template/5b645597b140515b7d0ad1b82492bfc5de8e475f/public/.gitkeep -------------------------------------------------------------------------------- /public/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasp-lang/cursor-template/5b645597b140515b7d0ad1b82492bfc5de8e475f/public/logo.webp -------------------------------------------------------------------------------- /schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "postgresql" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | } 9 | 10 | model User { 11 | id Int @id @default(autoincrement()) 12 | exampleNotes ExampleNote[] 13 | } 14 | 15 | model ExampleNote { 16 | id Int @id @default(autoincrement()) 17 | title String 18 | content String 19 | createdAt DateTime @default(now()) 20 | userId Int 21 | user User @relation(fields: [userId], references: [id]) 22 | } 23 | -------------------------------------------------------------------------------- /src/auth/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'wasp/client/router' 2 | import { LoginForm } from 'wasp/client/auth' 3 | 4 | export function LoginPage() { 5 | return ( 6 |
7 |
8 | 9 |
10 | 11 | Don't have an account? Sign up 12 | 13 |
14 |
15 |
16 | ); 17 | } -------------------------------------------------------------------------------- /src/auth/SignupPage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'wasp/client/router' 2 | import { SignupForm } from 'wasp/client/auth' 3 | 4 | export function SignupPage() { 5 | return ( 6 |
7 |
8 | 9 |
10 | 11 | Already have an account? Login 12 | 13 |
14 |
15 |
16 | ) 17 | } -------------------------------------------------------------------------------- /src/client/LandingPage.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | 3 | import { Link } from 'wasp/client/router'; 4 | import { TemplateHero } from '../client/components/templateHero'; 5 | import { ContainerWithFlatShadow } from './components/containerWithFlatShadow'; 6 | 7 | export const LandingPage: FC = () => { 8 | return ( 9 | <> 10 | 11 | 12 |
13 | 14 | Go To Example Notes Feature 15 | 16 | 17 |
18 |
19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/client/Main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /src/client/Main.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | 3 | import './Main.css'; 4 | import logo from './../../public/logo.webp'; 5 | import { Link, routes } from 'wasp/client/router'; 6 | import { logout, useAuth } from 'wasp/client/auth'; 7 | import { Outlet, useLocation } from 'react-router-dom'; 8 | 9 | export const Main: FC = () => { 10 | const { data: user, isLoading } = useAuth(); // We have to get the user from the useAuth hook because it's not passed in as a prop to the Main component by Wasp. 11 | const username = user?.identities.username; 12 | 13 | const location = useLocation(); 14 | const currentPath = location.pathname; 15 | 16 | const isExampleNotesPage = currentPath === routes.ExampleNotesDashboardRoute.to; 17 | 18 | return ( 19 |
20 | 44 |
45 | 46 |
47 |
48 | ); 49 | }; 50 | 51 | export const linkStyles = 'text-yellow-300 hover:text-gray-300 transition-colors'; 52 | -------------------------------------------------------------------------------- /src/client/components/containerWithFlatShadow.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from 'react'; 2 | 3 | interface ContainerWithFlatShadowProps { 4 | children: ReactNode; 5 | bgColor?: string; 6 | shadowColor?: string; 7 | } 8 | 9 | export const ContainerWithFlatShadow: FC = ({ 10 | children, 11 | bgColor = '', 12 | shadowColor = 'rgba(234, 179, 8, 0.7)' // Default yellow shadow 13 | }) => { 14 | return ( 15 |
16 |
17 |
23 | {children} 24 |
25 |
26 |
27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/client/components/templateHero.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from 'react'; 2 | 3 | import { Link } from 'wasp/client/router'; 4 | import { ContainerWithFlatShadow } from './containerWithFlatShadow'; 5 | 6 | export const TemplateHero: FC = () => { 7 | return ( 8 | 9 |

Wasp Cursor IDE Template

10 |

11 | This is a template to help you build your Wasp app with the{' '} 12 | 13 | Cursor IDE 14 | 15 | . 16 |

17 |
18 |

19 | - Context for the AI to build with the Wasp framework is provided in the .cursorrules file. If you find that the AI assistant is making mistakes with regards to Wasp-specific code, you can add more 20 | context here. 21 |

22 |

23 | - Make sure to index the Wasp docs,{' '} 24 | 25 | 26 | https://wasp-lang.dev/docs 27 | 28 | 29 | , under {`preferences > cursor settings > features > add new doc `} 30 | and access them with the {`@docs`} keyword in Cursor chat. 31 |

32 |

33 | - We've provided some example feature code for you in{' '} 34 | 35 | 36 | src/exampleNotesFeature/ 37 | 38 | 39 | . You can leave this code in place to help guide the AI in building the rest of your app, and delete it once you've got your own code in place. 40 |

41 |
42 |
43 | ); 44 | }; 45 | 46 | const Code: FC<{ children: ReactNode }> = ({ children }) => { 47 | return {children}; 48 | }; 49 | 50 | export const codeLinkStyles = 'underline hover:text-yellow-500 transition-colors'; 51 | -------------------------------------------------------------------------------- /src/exampleNotesFeature/ExampleNotePage.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react'; 2 | import type { AuthUser } from 'wasp/auth'; 3 | import type { ExampleNote } from 'wasp/entities'; 4 | 5 | import { useState } from 'react'; 6 | import { useParams, useNavigate } from 'react-router-dom'; 7 | import { useQuery, getExampleNote, deleteExampleNote, updateExampleNote } from 'wasp/client/operations'; 8 | import { ContainerWithFlatShadow } from '../client/components/containerWithFlatShadow'; 9 | 10 | export const ExampleNotePage: FC<{ user: AuthUser }> = ({ user }) => { 11 | const { id } = useParams(); 12 | const navigate = useNavigate(); 13 | 14 | const { 15 | data: note, 16 | isLoading, 17 | error, 18 | } = useQuery(getExampleNote, { 19 | id: parseInt(id!), 20 | }); 21 | 22 | const username = user.identities.username?.id; 23 | 24 | if (isLoading) return Loading...; 25 | if (error) 26 | return ( 27 | 28 |
Error: {error.message}
29 |
30 | ); 31 | if (!note) return Note not found; 32 | 33 | return ( 34 | <> 35 | 36 |
37 | 40 | 53 |
54 |

55 | {username}'s Note #{note.id} 56 |

57 | 58 |
59 | 60 | ); 61 | }; 62 | 63 | type EditableNoteProps = { 64 | note: ExampleNote; 65 | }; 66 | 67 | const EditableNote: FC = ({ note }) => { 68 | const [isEditing, setIsEditing] = useState(false); 69 | const [editTitle, setEditTitle] = useState(note.title); 70 | const [editContent, setEditContent] = useState(note.content); 71 | 72 | const handleEditSubmit = async () => { 73 | try { 74 | await updateExampleNote({ id: note.id, title: editTitle, content: editContent }); 75 | setIsEditing(false); 76 | } catch (err: any) { 77 | window.alert('Error: ' + err.message); 78 | } 79 | }; 80 | 81 | if (isEditing) { 82 | return ( 83 |
84 | setEditTitle(e.target.value)} 88 | className='w-full p-2 border bg-gray-50' 89 | /> 90 |