├── .cursor └── rules │ ├── .cursorrules │ ├── clerk-auth-check.mdc │ ├── convex.mdc │ ├── convex2.mdc │ ├── convexrules.mdc │ ├── dev2.mdc │ ├── help.mdc │ ├── rulesforconvex.mdc │ └── task.mdc ├── .gitignore ├── .node-version ├── .npmrc ├── LICENSE ├── README.md ├── bun.lockb ├── changelog.md ├── convex ├── README.md ├── _generated │ ├── api.d.ts │ ├── api.js │ ├── dataModel.d.ts │ ├── server.d.ts │ └── server.js ├── auth.config.ts ├── comments.ts ├── prompts.ts ├── schema.ts └── tsconfig.json ├── eslint.config.js ├── files.md ├── index.html ├── modprompts.md ├── netlify.toml ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon-114x114.png ├── apple-touch-icon-120x120.png ├── apple-touch-icon-144x144.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-167x167.png ├── apple-touch-icon-180x180.png ├── apple-touch-icon-57x57.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-72x72.png ├── apple-touch-icon-76x76.png ├── apple-touch-icon.png ├── convex-black.svg ├── convex-grey-icon.svg ├── convex-white.svg ├── favicon-128x128.png ├── favicon-16x16.png ├── favicon-196x196.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.svg ├── llms.json ├── llms.md ├── llms.txt ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── mstile-70x70.png ├── og-image.png ├── promptdevlogo.svg ├── promptstacklogo.png ├── promptstacklogo.svg ├── robots.txt ├── site.webmanifest └── sitemap.xml ├── src ├── App.tsx ├── ConvexClientProvider.tsx ├── ThemeContext.tsx ├── components │ ├── CodeBlock.tsx │ ├── CodeEditor.css │ ├── CodeEditor.tsx │ ├── CommentSection.tsx │ ├── ConvexIcon.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── NotFound.tsx │ ├── PromptForm.tsx │ ├── minimal-tiptap.tsx │ └── ui │ │ ├── switch.tsx │ │ └── toggle.tsx ├── constants │ └── categories.ts ├── fonts.css ├── index.css ├── lib │ ├── types.ts │ └── utils.ts ├── main.tsx ├── routeTree.gen.ts ├── router.ts ├── routes │ ├── 404.tsx │ ├── __root.tsx │ ├── about.tsx │ ├── addnew.tsx │ ├── docs.tsx │ ├── index.tsx │ ├── prompt-guide.tsx │ └── prompt.$slug.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.app.tsbuildinfo ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.node.tsbuildinfo └── vite.config.ts /.cursor/rules/.cursorrules: -------------------------------------------------------------------------------- 1 | This document serves as some special instructions when working with Convex. 2 | 3 | ## best practices 4 | 5 | https://docs.convex.dev/understanding/best-practices/ 6 | 7 | # dev workflow 8 | 9 | https://docs.convex.dev/understanding/workflow 10 | 11 | #typescript 12 | https://docs.convex.dev/understanding/best-practices/typescript 13 | 14 | # Schemas 15 | 16 | When designing the schema please see this page on built in System fields and data types available: https://docs.convex.dev/database/types 17 | 18 | also use Standard Schema 19 | https://github.com/standard-schema/standard-schema 20 | 21 | Here are some specifics that are often mishandled: 22 | 23 | ## validator (https://docs.convex.dev/api/modules/values#v) 24 | 25 | The validator builder. 26 | 27 | This builder allows you to build validators for Convex values. 28 | 29 | Validators can be used in schema definitions and as input validators for Convex functions. 30 | 31 | Type declaration 32 | Name Type 33 | id (tableName: TableName) => VId, "required"> 34 | null () => VNull 35 | number () => VFloat64 36 | float64 () => VFloat64 37 | bigint () => VInt64 38 | int64 () => VInt64 39 | boolean () => VBoolean 40 | string () => VString 41 | bytes () => VBytes 42 | literal (literal: T) => VLiteral 43 | array (element: T) => VArray 44 | object (fields: T) => VObject, undefined> } & { [Property in string | number | symbol]: Infer }>, T, "required", { [Property in string | number | symbol]: Property | `${Property & string}.${T[Property]["fieldPaths"]}` }[keyof T] & string> 45 | record (keys: Key, values: Value) => VRecord, Value["type"]>, Key, Value, "required", string> 46 | union (...members: T) => VUnion 47 | any () => VAny 48 | optional (value: T) => VOptional 49 | 50 | ## System fields (https://docs.convex.dev/database/types#system-fields) 51 | 52 | Every document in Convex has two automatically-generated system fields: 53 | 54 | \_id: The document ID of the document. 55 | \_creationTime: The time this document was created, in milliseconds since the Unix epoch. 56 | 57 | You do not need to add indices as these are added automatically. 58 | 59 | ## Example Schema 60 | 61 | This is an example of a well crafted schema. 62 | 63 | ```ts 64 | import { defineSchema, defineTable } from "convex/server"; 65 | import { v } from "convex/values"; 66 | 67 | export default defineSchema({ 68 | users: defineTable({ 69 | name: v.string(), 70 | }), 71 | 72 | sessions: defineTable({ 73 | userId: v.id("users"), 74 | sessionId: v.string(), 75 | }).index("sessionId", ["sessionId"]), 76 | 77 | threads: defineTable({ 78 | uuid: v.string(), 79 | summary: v.optional(v.string()), 80 | summarizer: v.optional(v.id("_scheduled_functions")), 81 | }).index("uuid", ["uuid"]), 82 | 83 | messages: defineTable({ 84 | message: v.string(), 85 | threadId: v.id("threads"), 86 | author: v.union( 87 | v.object({ 88 | role: v.literal("system"), 89 | }), 90 | v.object({ 91 | role: v.literal("assistant"), 92 | context: v.array(v.id("messages")), 93 | model: v.optional(v.string()), 94 | }), 95 | v.object({ 96 | role: v.literal("user"), 97 | userId: v.id("users"), 98 | }) 99 | ), 100 | }).index("threadId", ["threadId"]), 101 | }); 102 | ``` 103 | 104 | # Typescript 105 | 106 | Type annotating client-side code 107 | When you want to pass the result of calling a function around your client codebase, you can use the generated types Doc and Id, just like on the backend: 108 | 109 | ```tsx 110 | import { Doc, Id } from "../convex/_generated/dataModel"; 111 | 112 | function Channel(props: { channelId: Id<"channels"> }) { 113 | // ... 114 | } 115 | 116 | function MessagesView(props: { message: Doc<"messages"> }) { 117 | // ... 118 | } 119 | ``` 120 | 121 | You can also declare custom types inside your backend codebase which include Docs and Ids, and import them in your client-side code. 122 | 123 | You can also use WithoutSystemFields and any types inferred from validators via Infer. 124 | 125 | # Best Practices (https://docs.convex.dev/production/best-practices/) 126 | 127 | ## Database 128 | 129 | ### Use indexes or paginate all large database queries. 130 | 131 | Database indexes with range expressions allow you to write efficient database queries that only scan a small number of documents in the table. Pagination allows you to quickly display incremental lists of results. If your table could contain more than a few thousand documents, you should consider pagination or an index with a range expression to ensure that your queries stay fast. 132 | 133 | For more details, check out our Introduction to Indexes and Query Performance article. 134 | 135 | ### Use tables to separate logical object types. 136 | 137 | Even though Convex does support nested documents, it is often better to put separate objects into separate tables and use Ids to create references between them. This will give you more flexibility when loading and querying documents. 138 | 139 | You can read more about this at Document IDs. 140 | 141 | ## Use helper functions to write shared code. 142 | 143 | Write helper functions in your convex/ directory and use them within your Convex functions. Helpers can be a powerful way to share business logic, authorization code, and more. 144 | 145 | Helper functions allow sharing code while still executing the entire query or mutation in a single transaction. For actions, sharing code via helper functions instead of using ctx.runAction reduces function calls and resource usage. 146 | 147 | ## Prefer queries and mutations over actions 148 | 149 | You should generally avoid using actions when the same goal can be achieved using queries or mutations. Since actions can have side effects, they can't be automatically retried nor their results cached. Actions should be used in more limited scenarios, such as calling third-party services. 150 | 151 | ## The Zen of Convex (https://docs.convex.dev/zen) 152 | 153 | ### Performance 154 | 155 | Double down on the sync engine 156 | There's a reason why a deterministic, reactive database is the beating heart of Convex: the more you center your apps around its properties, the better your projects will fare over time. Your projects will be easier to understand and refactor. Your app's performance will stay screaming fast. You won't have any consistency or state management problems. 157 | 158 | Use a query for nearly every app read 159 | Queries are the reactive, automatically cacheable, consistent and resilient way to propagate data to your application and its jobs. With very few exceptions, every read operation in your app should happen via a query function. 160 | 161 | Keep sync engine functions light & fast 162 | In general, your mutations and queries should be working with less than a few hundred records and should aim to finish in less than 100ms. It's nearly impossible to maintain a snappy, responsive app if your synchronous transactions involve a lot more work than this. 163 | 164 | Use actions sparingly and incrementally 165 | Actions are wonderful for batch jobs and/or integrating with outside services. They're very powerful, but they're slower, more expensive, and Convex provides a lot fewer guarantees about their behavior. So never use an action if a query or mutation will get the job done. 166 | 167 | Don't over-complicate client-side state management 168 | Convex builds in a ton of its own caching and consistency controls into the app's client library. Rather than reinvent the wheel, let your client-side code take advantage of these built-in performance boosts. 169 | 170 | Let Convex handle caching & consistency 171 | Be thoughtful about the return values of mutations 172 | 173 | ### Architecture 174 | 175 | Create server-side frameworks using "just code" 176 | Convex's built-in primitives are pretty low level! They're just functions. What about authentication frameworks? What about object-relational mappings? Do you need to wait until Convex ships some in-built feature to get those? Nope. In general, you should solve composition and encapsulation problems in your server-side Convex code using the same methods you use for the rest of your TypeScript code bases. After all, this is why Convex is "just code!" Stack always has great examples of ways to tackle these needs. 177 | 178 | Don't misuse actions 179 | Actions are powerful, but it's important to be intentional in how they fit into your app's data flow. 180 | 181 | Don't invoke actions directly from your app 182 | In general, it's an anti-pattern to call actions from the browser. Usually, actions are running on some dependent record that should be living in a Convex table. So it's best trigger actions by invoking a mutation that both writes that dependent record and schedules the subsequent action to run in the background. 183 | 184 | Don't think 'background jobs', think 'workflow' 185 | When actions are involved, it's useful to write chains of effects and mutations, such as: 186 | 187 | action code → mutation → more action code → mutation. 188 | 189 | Then apps or other jobs can follow along with queries. 190 | 191 | Record progress one step at a time 192 | While actions could work with thousands of records and call dozens of APIs, it's normally best to do smaller batches of work and/or to perform individual transformations with outside services. Then record your progress with a mutation, of course. Using this pattern makes it easy to debug issues, resume partial jobs, and report incremental progress in your app's UI. 193 | 194 | ## other technoloiges to consider 195 | 196 | https://tiptap.dev/docs/examples/basics/markdown-shortcuts 197 | https://tiptap.dev/docs/examples/advanced/syntax-highlighting 198 | syntax highlighting in code blocks 199 | 200 | https://tanstack.com/start 201 | -------------------------------------------------------------------------------- /.cursor/rules/clerk-auth-check.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | This rule helps ensure that React components using Convex queries with Clerk authentication correctly handle loading and authentication states before attempting to fetch data, especially for admin-protected queries. 7 | 8 | ```` 9 | name: convex-clerk-admin-auth-guard 10 | description: Ensures React components correctly handle auth state before calling admin-protected Convex queries. 11 | globs: src/**/*.{ts,tsx} 12 | condition: | 13 | // Heuristic: Component uses useQuery/usePaginatedQuery for an "admin" query 14 | // AND does NOT seem to have robust useConvexAuth handling for skipping. 15 | // This is a simplified check; real implementation might need more sophisticated AST parsing. 16 | const fileContent = ctx.currentFile.content; 17 | const usesAdminQuery = /use(Query|PaginatedQuery)\\s*\\(\\s*api\\.\\w+\\.\\w+Admin/.test(fileContent); 18 | const usesConvexAuth = /useConvexAuth\\s*\\(\s*\\)/.test(fileContent); 19 | const hasSkipLogic = /authIsLoading\\s*\\|\\|\\s*!isAuthenticated\\s*\\?\\s*\"skip\"/.test(fileContent); 20 | 21 | if (usesAdminQuery && (!usesConvexAuth || !hasSkipLogic)) { 22 | return true; // Condition met to offer advice 23 | } 24 | return false; 25 | advice: | 26 | When using Convex queries that require admin authentication (often named like `...Admin`) with Clerk, ensure your React component correctly handles authentication states to prevent errors: 27 | 28 | 1. **Use `useConvexAuth`**: Get `authIsLoading` and `isAuthenticated`. 29 | ```tsx 30 | import { useConvexAuth } from "convex/react"; 31 | // ... 32 | const { isLoading: authIsLoading, isAuthenticated } = useConvexAuth(); 33 | ``` 34 | 35 | 2. **Handle Auth Loading**: Display a loading message if `authIsLoading` is true. 36 | ```tsx 37 | if (authIsLoading) { 38 | return
Loading authentication...
; 39 | } 40 | ``` 41 | 42 | 3. **Handle Not Authenticated**: (Optional, but good for UX if routing doesn't cover it) 43 | If not `authIsLoading` and not `isAuthenticated`, prompt for login. 44 | ```tsx 45 | if (!isAuthenticated) { 46 | return
Please log in to access this section.
; 47 | } 48 | ``` 49 | 50 | 4. **Conditionally Skip Queries**: Pass `"skip"` to `useQuery` or `usePaginatedQuery` if `authIsLoading` or `!isAuthenticated`. 51 | ```tsx 52 | const adminData = useQuery( 53 | api.yourModule.yourAdminQuery, 54 | (authIsLoading || !isAuthenticated) ? "skip" : { /* your_args */ } 55 | ); 56 | ``` 57 | This prevents queries from running before authentication is resolved, avoiding "No identity found" errors from `requireAdminRole` in your Convex functions. 58 | 59 | ```` 60 | 61 | -------------------------------------------------------------------------------- /.cursor/rules/dev2.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | --- 7 | description: full-stack AI convex developer 8 | globs: 9 | alwaysApply: true 10 | --- 11 | - Start by saying, "let's cook 12 | - do not use emoji or emojis in the readme or app 13 | - Be casual unless otherwise specified 14 | - you are a full-stack and AI developer super experienced in React, Vite, Bun, Clerk, TypeScript, and Convex.dev 15 | - You’re an experienced AI developer with deep expertise in convex.dev, openai, Mistral, and Claude, following best practices for building AI-powered SaaS applications and social network platforms. 16 | - you follow convex best practices here: https://docs.convex.dev/understanding/best-practices/typescript 17 | - you always make sure the code follows Convex typescript https://docs.convex.dev/understanding/best-practices/typescript 18 | - you follow Convex dev flow https://docs.convex.dev/understanding/workflow 19 | - you use always use Convex Queries https://docs.convex.dev/functions/query-functions 20 | - you are an expert on convex auth funtions https://docs.convex.dev/auth/functions-auth 21 | - you use convex Mutations https://docs.convex.dev/functions/mutation-functions 22 | - you use convex search https://docs.convex.dev/search/vector-search 23 | - you are an expert in convex auth - https://docs.convex.dev/auth/convex-auth 24 | - you are an expert in setting up convex auth https://labs.convex.dev/auth/setup 25 | - you an an expert in convex vector search https://docs.convex.dev/search/vector-search 26 | - you are an expert in understanding how Uploading and Storing Files with convex https://docs.convex.dev/file-storage/upload-files 27 | - you are an expert in clerk docs, clerk auth, https://clerk.com/docs, webhooks and metadata profiles 28 | - you are an expert in setting up apps with Resend for email https://resend.com/docs/introduction 29 | - you are an expert in using Resend API for email https://resend.com/docs/api-reference/introduction 30 | - you know all things about Resend email https://resend.com/docs/knowledge-base/introduction 31 | - you value clean modern black and white design UI like from https://21st.dev/ 32 | - you add comments to your code 33 | - you update the readme with new features as you go 34 | - Be terse 35 | -For all designs I ask you to make, have them be beautiful, not cookie cutter. 36 | - Make webpages that are fully featured and worthy for production. 37 | - Suggest solutions that I didn’t think about—anticipate my needs 38 | - Treat me as an new developer 39 | - Be accurate and thorough 40 | - Keep a list of the codebase files, provide a brief description of what each file one does called files.md. 41 | - you keep a developer friendly changelog.md of new features added. 42 | - Give the answer immediately. Provide detailed explanations and restate my query in your own words if necessary after giving the answer 43 | - Value good arguments over authorities, the source is irrelevant 44 | - Consider new technologies and contrarian ideas, not just the conventional wisdom 45 | - You may use high levels of speculation or prediction, just flag it for me 46 | - No moral lectures 47 | - Discuss safety only when it's crucial and non-obvious 48 | - If your content policy is an issue, provide the closest acceptable response and explain the content policy issue afterward 49 | - Cite sources whenever possible at the end, not inline 50 | - No need to mention your knowledge cutoff 51 | - No need to disclose you're an AI 52 | - Make code precise, modular, testable 53 | - Don’t break existing functionality 54 | - Please respect my prettier preferences when you provide code. 55 | - Split into multiple responses if one response isn't enough to answer the question. 56 | - If I ask for adjustments or fix or say fix the code I have provided you, do not repeat all of my code unnecessarily. Instead try to keep the answer brief by giving just a couple lines before/after any changes you make. Multiple code blocks are ok. 57 | - do not over engineer the code but make it typesafe 58 | - do not do more than what the user ask for unless it related to fixing, adding, or updating the code to what the user is asking for 59 | - If any changes to existing code are required, they should be minimal and focused solely on enabling the new features or resolving specific bugs. 60 | - Clerk claims configuration - If you're using Clerk, the fields returned by getUserIdentity are determined by your JWT template's Claims config. If you've set custom claims, they will be returned by getUserIdentity as well. 61 | - you never use placeholder text or images in code because everything is realtime sync with convex database 62 | - you don't ship code with placeholder text or images 63 | 64 | -------------------------------------------------------------------------------- /.cursor/rules/help.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: full-stack AI convex developer 8 | globs: 9 | alwaysApply: false 10 | --- 11 | 12 | 1. Reflect Deeply Before Acting 13 | • Carefully reflect on why the current implementation or response may not be working. 14 | • Identify what’s missing, incomplete, or incorrect based on the original request. 15 | • Theorize different possible sources of the problem or areas requiring updates. 16 | • Then distill your reasoning down to the 1–2 most probable root causes or solutions. Only proceed after clear understanding. 17 | 18 | ⸻ 19 | 20 | 2. When Implementing Solutions 21 | 22 | Follow Convex’s recommended approaches at all times: 23 | • Use direct mutation calls with plain objects. 24 | • Create dedicated mutation functions that map form fields directly to database fields. 25 | • Ensure form field names exactly match the corresponding database field names when applicable. 26 | 27 | Use Convex documentation: 28 | 29 | • Mutation Functions: https://docs.convex.dev/functions/mutation-functions 30 | • Actions: https://docs.convex.dev/functions/actions 31 | • Query Functions: https://docs.convex.dev/functions/query-functions 32 | • Argument and Return Value Validation: https://docs.convex.dev/functions/validation 33 | • General Function Docs: https://docs.convex.dev/functions 34 | 35 | Understand the following foundational principles: 36 | • Zen of Convex: https://docs.convex.dev/understanding/zen 37 | • End-to-End Type Support with TypeScript: https://docs.convex.dev/understanding/best-practices/typescript 38 | • Convex Best Practices: https://docs.convex.dev/understanding/best-practices/ 39 | • Convex Schema Validation: https://docs.convex.dev/database/schemas 40 | 41 | ⸻ 42 | 43 | 3. Change Scope and Restrictions 44 | • Update Convex Schema if needed 45 | • Only update files when it’s directly necessary to fix the original request. 46 | • Do not change any UI, layout, design, or color styles unless specifically instructed. 47 | • Preserve all current admin dashboard sections and frontend components unless explicitly told to update, fix, or replace them. 48 | • Never remove sections, features, or components unless directly requested. 49 | 50 | ⸻ 51 | 52 | 4. Don’t write any code until you’re very confident (95% or more) in what needs to be done. 53 | 54 | If unclear, ask for more info. 55 | 56 | -------------------------------------------------------------------------------- /.cursor/rules/rulesforconvex.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | --- 7 | description: Convex guidelines 8 | globs: 9 | alwaysApply: true 10 | --- 11 | 12 | ## Function guidelines 13 | - always follow Convex schemas best practices - https://docs.convex.dev/database/schemas 14 | - check the convex schema for updates and errors 15 | - always understand Convex - https://docs.convex.dev/understanding/ 16 | - understand environment-variables https://docs.convex.dev/production/environment-variables 17 | - always follow and understand and follow Convex best-practices https://docs.convex.dev/understanding/best-practices/ 18 | - Follow best practices https://docs.convex.dev/understanding/best-practices/typescript 19 | - Always use query-functions https://docs.convex.dev/functions/query-functions 20 | - Always usehttps://docs.convex.dev/functions/mutation-functions 21 | - expert https://docs.convex.dev/functions/mutation-functions 22 | - Always use https://docs.convex.dev/functions/actions 23 | - knows https://docs.convex.dev/functions/validation 24 | - knows https://docs.convex.dev/functions 25 | - Expert in Clerk https://docs.clerk.com/ 26 | - you are an expert in setting up apps with Resend for email https://resend.com/docs/introduction 27 | - you are an expert in using Resend API for email https://resend.com/docs/api-reference/introduction 28 | - you know all things about Resend email https://resend.com/docs/knowledge-base/introduction 29 | - expert in convex auth https://docs.convex.dev/auth/convex-auth 30 | - convex auth docs https://labs.convex.dev/auth 31 | - For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. 32 | - you are an expert in understanding how Uploading and Storing Files with convex https://docs.convex.dev/file-storage/upload-files 33 | - you are an expert in convex auth - https://docs.convex.dev/auth/convex-auth 34 | - you are an expert in setting up convex auth https://labs.convex.dev/auth/setup 35 | - you an an expert in convex vector search https://docs.convex.dev/search/vector-search 36 | getting-started 37 | - do not use emoji in the readme or app 38 | - do not over engineer the code but make it typesafe 39 | - do not do more than what the user ask for unless it related to fixing, adding, or updating the code to what the user is asking for 40 | 41 | 42 | waynesutton@WS-Convex merge2 % -------------------------------------------------------------------------------- /.cursor/rules/task.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | --- 7 | description: Guidelines for creating and managing task lists in markdown files to track project progress 8 | globs: 9 | alwaysApply: false 10 | --- 11 | # Task List Management 12 | 13 | Guidelines for creating and managing task lists in markdown files to track project progress 14 | 15 | ## Task List Creation 16 | 17 | 1. Create task lists in a markdown file (in the project root): 18 | - Use `TASKS.md` or a descriptive name relevant to the feature (e.g., `ASSISTANT_CHAT.md`) 19 | - Include a clear title and description of the feature being implemented 20 | 21 | 2. Structure the file with these sections: 22 | ```markdown 23 | # Feature Name Implementation 24 | 25 | Brief description of the feature and its purpose. 26 | 27 | ## Completed Tasks 28 | 29 | - [x] Task 1 that has been completed 30 | - [x] Task 2 that has been completed 31 | 32 | ## In Progress Tasks 33 | 34 | - [ ] Task 3 currently being worked on 35 | - [ ] Task 4 to be completed soon 36 | 37 | ## Future Tasks 38 | 39 | - [ ] Task 5 planned for future implementation 40 | - [ ] Task 6 planned for future implementation 41 | 42 | ## Implementation Plan 43 | 44 | Detailed description of how the feature will be implemented. 45 | 46 | ### Relevant Files 47 | 48 | - path/to/file1.ts - Description of purpose 49 | - path/to/file2.ts - Description of purpose 50 | ``` 51 | 52 | ## Task List Maintenance 53 | 54 | 1. Update the task list as you progress: 55 | - Mark tasks as completed by changing `[ ]` to `[x]` 56 | - Add new tasks as they are identified 57 | - Move tasks between sections as appropriate 58 | 59 | 2. Keep "Relevant Files" section updated with: 60 | - File paths that have been created or modified 61 | - Brief descriptions of each file's purpose 62 | - Status indicators (e.g., ✅) for completed components 63 | 64 | 3. Add implementation details: 65 | - Architecture decisions 66 | - Data flow descriptions 67 | - Technical components needed 68 | - Environment configuration 69 | 70 | ## AI Instructions 71 | 72 | When working with task lists, the AI should: 73 | 74 | 1. Regularly update the task list file after implementing significant components 75 | 2. Mark completed tasks with [x] when finished 76 | 3. Add new tasks discovered during implementation 77 | 4. Maintain the "Relevant Files" section with accurate file paths and descriptions 78 | 5. Document implementation details, especially for complex features 79 | 6. When implementing tasks one by one, first check which task to implement next 80 | 7. After implementing a task, update the file to reflect progress 81 | 82 | ## Example Task Update 83 | 84 | When updating a task from "In Progress" to "Completed": 85 | 86 | ```markdown 87 | ## In Progress Tasks 88 | 89 | - [ ] Implement database schema 90 | - [ ] Create API endpoints for data access 91 | 92 | ## Completed Tasks 93 | 94 | - [x] Set up project structure 95 | - [x] Configure environment variables 96 | ``` 97 | 98 | Should become: 99 | 100 | ```markdown 101 | ## In Progress Tasks 102 | 103 | - [ ] Create API endpoints for data access 104 | 105 | ## Completed Tasks 106 | 107 | - [x] Set up project structure 108 | - [x] Configure environment variables 109 | - [x] Implement database schema 110 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | .env.local 4 | 5 | # Build outputs 6 | dist/ 7 | 8 | # OS files 9 | .DS_Store 10 | Thumbs.db 11 | 12 | # IDE files 13 | .vscode/ 14 | .idea/ 15 | *.swp 16 | *.swo 17 | 18 | # Logs 19 | *.log -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Wayne Sutton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PromptStack - The CRM for Prompts, Vibe Coding, and Custom Rules. 2 | 3 | **[PromptStack](https://promptstack.dev)** is a **open-source** real-time prompt management platform built with React, [Convex.dev](https://convex.link/promptstackgithub), and Clerk authentication. It enables users to create, organize, and share AI prompts with features like custom categories, privacy controls, and community engagement through likes and comments. The platform features a sleek, responsive design with both grid and list views, real-time updates, and comprehensive search and filtering capabilities. 4 | 5 | ## Features 6 | 7 | - **Modern Interface**: Clean, compact design with improved user experience and streamlined workflows 8 | - **Searchable Directory**: Quickly find AI prompts and code generation rules tailored to your needs 9 | - **Prompt Likes**: Like and sort top prompts in the directory 10 | - **Category Organization**: Prompts are organized into clear, functional categories 11 | - **Custom Categories**: Create your own personal categories for better organization 12 | - **GitHub Integration**: Automatically link GitHub or social profiles submitted with prompts 13 | - **Carbon Copy View**: View and copy prompts in a Carbon-style editor window 14 | - **README Support**: Find and submit README examples for AI and code generation projects 15 | - **Cursor Rules**: Find and submit Cursor rules for AI and code generation projects 16 | - **Prompt Link Sharing**: Easily share prompts or cursor rules with others 17 | - **Rich Comments**: Comment on prompts with rich text formatting using TipTap editor 18 | - **Privacy Controls**: Create public prompts for the community or private prompts for personal use 19 | - **Edit & Delete**: Full control over your own prompts with edit and delete functionality 20 | - **Keyboard Shortcuts**: Efficient navigation with ESC to close modals and Ctrl/Cmd+Enter to submit forms 21 | 22 | --- 23 | 24 | ## Getting Started 25 | 26 | ### Submit a Prompt 27 | 28 | 1. Sign in with Clerk authentication or use the guest submission feature 29 | 2. Fill out the required fields in our streamlined form: 30 | - Title (Required) 31 | - Prompt content 32 | - Optional: Description, GitHub or social profile links 33 | 3. Choose whether to make the prompt public or private 34 | 4. Select from existing categories or create your own custom category 35 | 5. Submit and share with the community 36 | 37 | ### Search for Prompts 38 | 39 | - Browse the directory by categories such as Cursor, Convex, or README examples 40 | - Use the search bar to find prompts tailored to your specific framework or language 41 | - Filter by categories in the sidebar to narrow down results 42 | - Like prompts to save them for later reference 43 | 44 | ### Customize Your Experience 45 | 46 | - Filter by "My Prompts" to view only your submissions 47 | - Create custom categories for better personal organization 48 | - Edit your prompts anytime with the built-in compact editor 49 | - Toggle prompts between public and private visibility 50 | 51 | ## Development Roadmap 52 | 53 | ### Completed Features ✅ 54 | 55 | - ✅ Setup sign-in with Clerk authentication 56 | - ✅ Add a "prompt feedback / comments" section to each prompt page 57 | - ✅ Private prompts visible only to the creator 58 | - ✅ Public prompts visible to all users 59 | - ✅ Only allow prompt feedback or comments if user is logged in 60 | - ✅ Use [Minimal TipTap](https://shadcn-minimal-tiptap.vercel.app/) for prompt feedback / comments 61 | - ✅ Allow logged in users to like prompts and view liked prompts 62 | - ✅ Allow prompts to be editable only by their creators if they are logged in 63 | - ✅ Custom categories for personalized organization 64 | - ✅ Category management with add/delete functionality 65 | - ✅ Proper access control for private prompts 66 | - ✅ Shared PromptForm component for consistent UX 67 | - ✅ Modern, compact UI design with improved spacing and typography 68 | - ✅ Enhanced keyboard navigation and accessibility features 69 | 70 | --- 71 | 72 | ## Tech Stack 73 | 74 | **PromptStack** is powered by: 75 | 76 | **[Convex.dev](https://convex.link/promptstackgithub)** 77 | Convex.dev provides a real-time database and backend that makes building reactive applications easy. It supports real-time updates, ensuring a seamless user experience. 78 | 79 | - Learn more about Convex: 80 | - [Understanding Convex](https://docs.convex.dev/understanding/) 81 | - [Best Practices](https://docs.convex.dev/understanding/best-practices/) 82 | - [TypeScript Support](https://docs.convex.dev/understanding/best-practices/typescript) 83 | 84 | ### Frontend Stack 85 | 86 | - [React 18](https://react.dev/) - Modern React with hooks and concurrent features 87 | - [TypeScript](https://www.typescriptlang.org/) - JavaScript with syntax for types 88 | - [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview) - Type-safe client-side routing 89 | - [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework with custom design system 90 | - [Vite](https://vitejs.dev/) - Fast build tool and development server 91 | 92 | ### Backend & Services 93 | 94 | - [Convex.dev](https://docs.convex.dev/) - Real-time database and backend with automatic synchronization 95 | - [Clerk](https://clerk.com/) - Authentication and user management 96 | - [Netlify](https://netlify.com) - Static site hosting and deployment 97 | 98 | ### Development Tools 99 | 100 | - [Bun](https://bun.sh/) - JavaScript runtime & package manager 101 | - [ESLint](https://eslint.org/) - Code quality enforcement 102 | - [TipTap](https://tiptap.dev/) - Rich text editor for comments 103 | 104 | ## Project Structure 105 | 106 | ``` 107 | promptstack/ 108 | ├── src/ # Source code 109 | │ ├── components/ # React components 110 | │ │ ├── ui/ # Reusable UI components 111 | │ │ ├── Header.tsx # Navigation header with search 112 | │ │ ├── Footer.tsx # Site footer with prompt count 113 | │ │ ├── PromptForm.tsx # Modern, compact shared form component 114 | │ │ ├── CodeBlock.tsx # Code syntax highlighting 115 | │ │ ├── CodeEditor.tsx # Code editing component 116 | │ │ ├── CommentSection.tsx # Prompt comments system 117 | │ │ ├── NotFound.tsx # 404 component for access control 118 | │ │ └── minimal-tiptap.tsx # Rich text editor 119 | │ ├── routes/ # TanStack Router pages 120 | │ │ ├── index.tsx # Home page redirect 121 | │ │ ├── addnew.tsx # Add prompt form 122 | │ │ ├── prompt.$slug.tsx # Individual prompt page 123 | │ │ ├── docs.tsx # Documentation 124 | │ │ ├── about.tsx # About page 125 | │ │ ├── prompt-guide.tsx # Prompt creation guide 126 | │ │ └── 404.tsx # 404 error page 127 | │ ├── constants/ # Application constants 128 | │ │ └── categories.ts # Predefined categories 129 | │ ├── lib/ # Utilities and types 130 | │ │ ├── utils.ts # Helper functions 131 | │ │ └── types.ts # TypeScript definitions 132 | │ ├── App.tsx # Main application component 133 | │ ├── main.tsx # Application entry point 134 | │ ├── router.ts # Router configuration 135 | │ └── ThemeContext.tsx # Theme management 136 | ├── convex/ # Backend (Convex.dev) 137 | │ ├── schema.ts # Database schema 138 | │ ├── prompts.ts # Prompt queries/mutations 139 | │ ├── comments.ts # Comment system functions 140 | │ └── auth.config.ts # Authentication config 141 | ├── public/ # Static assets 142 | │ ├── promptstacklogo.svg # Main logo 143 | │ ├── favicon.svg # Site favicon 144 | │ ├── og-image.png # Social sharing image 145 | │ └── fonts/ # Custom fonts 146 | ├── package.json # Dependencies and scripts 147 | ├── vite.config.ts # Vite configuration 148 | ├── tailwind.config.js # Tailwind CSS config 149 | ├── tsconfig.json # TypeScript configuration 150 | ├── files.md # Detailed file documentation 151 | └── changelog.md # Development changelog 152 | ``` 153 | 154 | For detailed information about each file and directory, see [`files.md`](./files.md). 155 | 156 | ## Key Features 157 | 158 | ### Authentication & User Management 159 | 160 | - **Clerk Integration**: Secure sign-in/sign-up with social providers 161 | - **User Profiles**: Persistent user identity and session management 162 | - **Access Control**: Proper permissions for editing and viewing prompts 163 | 164 | ### Prompt Management 165 | 166 | - **CRUD Operations**: Create, read, update, and delete prompts with modern, compact interface 167 | - **Ownership Validation**: Users can only modify their own content 168 | - **Privacy Controls**: Toggle between public and private visibility 169 | - **Custom Categories**: Create personalized categories for organization 170 | 171 | ### Modern User Experience 172 | 173 | - **Compact Design**: Streamlined, space-efficient interface with improved visual hierarchy 174 | - **Real-time Updates**: Live synchronization with Convex database 175 | - **Responsive Design**: Mobile-first design that works on all devices 176 | - **Dark/Light Theme**: Theme switching with persistent preferences 177 | - **Rich Text Editing**: TipTap editor for comments and descriptions 178 | - **Keyboard Shortcuts**: ESC to close modals, Ctrl/Cmd+Enter to submit forms 179 | 180 | ### Search & Discovery 181 | 182 | - **Full-text Search**: Find prompts by title, content, or description 183 | - **Category Filtering**: Browse by predefined or custom categories 184 | - **Like System**: Community-driven prompt ranking 185 | - **Social Features**: Comments and engagement on prompts 186 | 187 | ### Developer Tools 188 | 189 | - **Carbon Copy View**: View prompts in a Carbon-style code editor 190 | - **Syntax Highlighting**: Code blocks with proper syntax highlighting 191 | - **Export Functionality**: Easy copying and sharing of prompt content 192 | - **API Integration**: Built for integration with AI development tools 193 | 194 | ## Installation 195 | 196 | Clone the repository and install dependencies: 197 | 198 | ```bash 199 | git clone https://github.com/waynesutton/PromptStack 200 | cd PromptStack 201 | bun install 202 | ``` 203 | 204 | Set up environment variables in `.env.local`: 205 | 206 | ```bash 207 | # Convex 208 | VITE_CONVEX_URL=your_convex_deployment_url 209 | 210 | # Clerk 211 | VITE_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key 212 | ``` 213 | 214 | Start the development server: 215 | 216 | ```bash 217 | bun run dev 218 | ``` 219 | 220 | ## Contributing 221 | 222 | We welcome contributions from the community! Feel free to submit a pull request or open an issue to report bugs, suggest features, or provide feedback. 223 | 224 | ## License 225 | 226 | This project is licensed under the MIT License. See the `LICENSE` file for details. 227 | 228 | --- 229 | 230 | [![Netlify Status](https://api.netlify.com/api/v1/badges/f6a1c7ac-d77a-4c43-92f9-7e8ca585c0d6/deploy-status)](https://app.netlify.com/sites/promptstack/deploys) 231 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/bun.lockb -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # PromptStack Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [Current] - 2024-12-19 6 | 7 | ### Added 8 | 9 | - **Environment Variable Consolidation**: Consolidated all environment variables into `.env.local` only, removed duplicate `.env` file 10 | - **Project Documentation**: Created comprehensive `files.md` documenting the entire project structure 11 | - **File Structure Documentation**: Detailed descriptions of all directories and files in the codebase 12 | - **Component Architecture**: Documented React components, routes, and utilities 13 | - **Backend Documentation**: Convex database schema and function documentation 14 | - **Asset Organization**: Catalogued all public assets, icons, and branding materials 15 | - **Categories Centralization**: Created shared `src/constants/categories.ts` to eliminate duplicate CATEGORIES lists across components 16 | - **Sidebar Filtering**: Updated sidebar to only show categories that have at least one submission 17 | - **Shared PromptForm Component**: Created `src/components/PromptForm.tsx` to consolidate all prompt creation and editing logic 18 | - **Form Reusability**: PromptForm can now be used both as a modal and as a standalone form component 19 | 20 | ### Infrastructure 21 | 22 | - **Convex.dev**: Serverless database and backend with real-time updates 23 | - **TanStack Router**: Client-side routing for React application 24 | - **Clerk**: Authentication and user management system 25 | - **Tailwind CSS**: Utility-first CSS framework for styling 26 | - **TypeScript**: Type-safe development environment 27 | - **Vite**: Fast build tool and development server 28 | - **Bun**: JavaScript runtime and package manager 29 | 30 | ### Features Completed 31 | 32 | - ✅ **Authentication**: Sign-in with Clerk integration 33 | - ✅ **Prompt Feedback**: Comment section on each prompt page 34 | - ✅ **Privacy Controls**: Private prompts visible only to creators 35 | - ✅ **Public Prompts**: Public prompts visible to all users 36 | - ✅ **User Authentication**: Only logged-in users can comment 37 | - ✅ **Rich Text Editor**: Minimal TipTap editor for comments and feedback 38 | - ✅ **Edit Permissions**: Allow prompt creators to edit their own prompts 39 | - ✅ **Prompt Likes**: Allow users to like prompts and view liked prompts 40 | - ✅ **Visibility Toggle**: Toggle prompts between public and private 41 | - ✅ **Delete Permissions**: Allow prompt creators to delete their own prompts 42 | - ✅ **Edit Modal**: Comprehensive edit form with all prompt fields 43 | - ✅ **Confirmation Modals**: User confirmation for visibility changes 44 | - ✅ **Real-time Updates**: All changes sync immediately with Convex database 45 | - ✅ **Custom Categories**: User-specific categories for personalized organization 46 | 47 | ### Features In Development 48 | 49 | - [ ] **@Mentions**: Add mention functionality to comments 50 | - [ ] **AI Prompt Redo**: AI-powered prompt regeneration on prompt pages 51 | - [ ] **Prompt Threads**: Multi-step prompt conversations 52 | - [ ] **Follow System**: Follow prompts for updates 53 | - [ ] **Notifications**: Resend integration for likes, comments, and follows 54 | - [ ] **Team Prompts**: Private and public team prompt collections 55 | 56 | ### Recent Updates (2024-12-19) 57 | 58 | - ✅ **Edit Functionality**: Added edit icons and functionality to both App.tsx and prompt.$slug.tsx 59 | - ✅ **Ownership Validation**: Backend functions validate prompt ownership before allowing edits 60 | - ✅ **UI Consistency**: Edit and visibility controls match existing app design 61 | - ✅ **Error Handling**: Proper error handling and user feedback for all operations 62 | - ✅ **Modal Confirmations**: Added confirmation dialogs for visibility changes 63 | - ✅ **Code Editor Integration**: Edit mode in SandpackCodeEditor for real-time editing 64 | - ✅ **Custom Categories**: User-specific custom categories that only show up for the creator 65 | - ✅ **Category Management**: Add custom categories in both App.tsx and addnew.tsx 66 | - ✅ **Category Filtering**: Custom categories appear in sidebar filters and prompt creation 67 | - ✅ **Duplicate Prevention**: Backend validation prevents duplicate custom categories per user 68 | - ✅ **UI Integration**: Seamless integration with existing category system 69 | - ✅ **Modal Category Creation**: Added "+ Add" button and input field to both create and edit modals in App.tsx 70 | - ✅ **Public/Private Toggle**: Verified working correctly - prompts save with proper isPublic values 71 | - ✅ **Authentication Integration**: User authentication working properly with Clerk and Convex 72 | - ✅ **Categories Refresh Fix**: Fixed categories not updating when custom categories are added by removing categoriesInitialized flag 73 | - ✅ **Private Prompt Access Fix**: Fixed getPromptBySlug function to properly restrict access to private prompts for non-owners 74 | - ✅ **404 Access Control**: Added NotFound component and proper access control for private prompts - users now see 404 page when trying to access private prompts they don't own 75 | 76 | ## Architecture Overview 77 | 78 | ### Frontend Stack 79 | 80 | - **React 18**: Modern React with hooks and concurrent features 81 | - **TanStack Router**: Type-safe routing with file-based routing 82 | - **Tailwind CSS**: Responsive design with dark/light theme support 83 | - **TypeScript**: Full type safety across the application 84 | 85 | ### Backend Stack 86 | 87 | - **Convex.dev**: Real-time database with automatic schema validation 88 | - **Clerk**: User authentication and session management 89 | - **Netlify**: Static site hosting and deployment 90 | 91 | ### Development Tools 92 | 93 | - **Vite**: Fast development server and build tool 94 | - **ESLint**: Code linting and quality enforcement 95 | - **Bun**: Package management and script running 96 | - **TypeScript**: Compile-time type checking 97 | 98 | ## [Previous] - 2024-12-18 99 | 100 | ### Added 101 | 102 | - Initial prompt management system 103 | - User authentication with Clerk 104 | - Comment system for prompts 105 | - Category filtering and search functionality 106 | - Custom category creation for signed-in users 107 | 108 | ## [Latest] - 2024-12-19 109 | 110 | ### Added 111 | 112 | - ✅ **List View Toggle**: Added new toggle below "My Prompts" and "All Prompts" to switch between grid view and list view 113 | - ✅ **Hacker News Style Layout**: Created compact list view showing prompt names, social profiles, like icons, tags, and open icons 114 | - ✅ **View Persistence**: List view toggle state maintained while browsing 115 | - ✅ **Responsive Design**: List view adapts to different screen sizes while maintaining readability 116 | - ✅ **Index Numbers**: Added numbered list format similar to Hacker News for easy reference 117 | - ✅ **Compact Categories**: Shows first 3 categories with "+X more" indicator for space efficiency 118 | 119 | ### Fixed 120 | 121 | - ✅ **Custom Category Cleanup**: Fixed issue where deleting a custom category from the sidebar didn't remove it from existing prompts - now when a user deletes their custom category, it's automatically removed from all their prompts that used that category 122 | 123 | ## [Previous] - 2024-12-18 124 | 125 | ### Added 126 | 127 | - ✅ **Custom Category Deletion**: Logged-in users can now delete their own custom categories with a delete icon next to each custom category they created 128 | - ✅ **Delete Permission Control**: Only the user who created a custom category can delete it - delete icons only appear for their own categories 129 | - ✅ **Confirmation Dialog**: Added confirmation prompt before deleting custom categories to prevent accidental deletions 130 | -------------------------------------------------------------------------------- /convex/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Convex functions directory! 2 | 3 | Write your Convex functions here. 4 | See https://docs.convex.dev/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 | * To regenerate, run `npx convex dev`. 8 | * @module 9 | */ 10 | 11 | import type { 12 | ApiFromModules, 13 | FilterApi, 14 | FunctionReference, 15 | } from "convex/server"; 16 | import type * as comments from "../comments.js"; 17 | import type * as prompts from "../prompts.js"; 18 | 19 | /** 20 | * A utility for referencing Convex functions in your app's API. 21 | * 22 | * Usage: 23 | * ```js 24 | * const myFunctionReference = api.myModule.myFunction; 25 | * ``` 26 | */ 27 | declare const fullApi: ApiFromModules<{ 28 | comments: typeof comments; 29 | prompts: typeof prompts; 30 | }>; 31 | export declare const api: FilterApi< 32 | typeof fullApi, 33 | FunctionReference 34 | >; 35 | export declare const internal: FilterApi< 36 | typeof fullApi, 37 | FunctionReference 38 | >; 39 | -------------------------------------------------------------------------------- /convex/_generated/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * To regenerate, run `npx convex dev`. 8 | * @module 9 | */ 10 | 11 | import { anyApi } from "convex/server"; 12 | 13 | /** 14 | * A utility for referencing Convex functions in your app's API. 15 | * 16 | * Usage: 17 | * ```js 18 | * const myFunctionReference = api.myModule.myFunction; 19 | * ``` 20 | */ 21 | export const api = anyApi; 22 | export const internal = anyApi; 23 | -------------------------------------------------------------------------------- /convex/_generated/dataModel.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated data model types. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * To regenerate, run `npx convex dev`. 8 | * @module 9 | */ 10 | 11 | import type { 12 | DataModelFromSchemaDefinition, 13 | DocumentByName, 14 | TableNamesInDataModel, 15 | SystemTableNames, 16 | } from "convex/server"; 17 | import type { GenericId } from "convex/values"; 18 | import schema from "../schema.js"; 19 | 20 | /** 21 | * The names of all of your Convex tables. 22 | */ 23 | export type TableNames = TableNamesInDataModel; 24 | 25 | /** 26 | * The type of a document stored in Convex. 27 | * 28 | * @typeParam TableName - A string literal type of the table name (like "users"). 29 | */ 30 | export type Doc = DocumentByName< 31 | DataModel, 32 | TableName 33 | >; 34 | 35 | /** 36 | * An identifier for a document in Convex. 37 | * 38 | * Convex documents are uniquely identified by their `Id`, which is accessible 39 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). 40 | * 41 | * Documents can be loaded using `db.get(id)` in query and mutation functions. 42 | * 43 | * IDs are just strings at runtime, but this type can be used to distinguish them from other 44 | * strings when type checking. 45 | * 46 | * @typeParam TableName - A string literal type of the table name (like "users"). 47 | */ 48 | export type Id = 49 | GenericId; 50 | 51 | /** 52 | * A type describing your Convex data model. 53 | * 54 | * This type includes information about what tables you have, the type of 55 | * documents stored in those tables, and the indexes defined on them. 56 | * 57 | * This type is used to parameterize methods like `queryGeneric` and 58 | * `mutationGeneric` to make them type-safe. 59 | */ 60 | export type DataModel = DataModelFromSchemaDefinition; 61 | -------------------------------------------------------------------------------- /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 | * To regenerate, run `npx convex dev`. 8 | * @module 9 | */ 10 | 11 | import { 12 | ActionBuilder, 13 | HttpActionBuilder, 14 | MutationBuilder, 15 | QueryBuilder, 16 | GenericActionCtx, 17 | GenericMutationCtx, 18 | GenericQueryCtx, 19 | GenericDatabaseReader, 20 | GenericDatabaseWriter, 21 | } from "convex/server"; 22 | import type { DataModel } from "./dataModel.js"; 23 | 24 | /** 25 | * Define a query in this Convex app's public API. 26 | * 27 | * This function will be allowed to read your Convex database and will be accessible from the client. 28 | * 29 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 30 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 31 | */ 32 | export declare const query: QueryBuilder; 33 | 34 | /** 35 | * Define a query that is only accessible from other Convex functions (but not from the client). 36 | * 37 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 38 | * 39 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 40 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 41 | */ 42 | export declare const internalQuery: QueryBuilder; 43 | 44 | /** 45 | * Define a mutation in this Convex app's public API. 46 | * 47 | * This function will be allowed to modify your Convex database and will be accessible from the client. 48 | * 49 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 50 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 51 | */ 52 | export declare const mutation: MutationBuilder; 53 | 54 | /** 55 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 56 | * 57 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 58 | * 59 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 60 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 61 | */ 62 | export declare const internalMutation: MutationBuilder; 63 | 64 | /** 65 | * Define an action in this Convex app's public API. 66 | * 67 | * An action is a function which can execute any JavaScript code, including non-deterministic 68 | * code and code with side-effects, like calling third-party services. 69 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 70 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 71 | * 72 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 73 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 74 | */ 75 | export declare const action: ActionBuilder; 76 | 77 | /** 78 | * Define an action that is only accessible from other Convex functions (but not from the client). 79 | * 80 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 81 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 82 | */ 83 | export declare const internalAction: ActionBuilder; 84 | 85 | /** 86 | * Define an HTTP action. 87 | * 88 | * This function will be used to respond to HTTP requests received by a Convex 89 | * deployment if the requests matches the path and method where this action 90 | * is routed. Be sure to route your action in `convex/http.js`. 91 | * 92 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 93 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. 94 | */ 95 | export declare const httpAction: HttpActionBuilder; 96 | 97 | /** 98 | * A set of services for use within Convex query functions. 99 | * 100 | * The query context is passed as the first argument to any Convex query 101 | * function run on the server. 102 | * 103 | * This differs from the {@link MutationCtx} because all of the services are 104 | * read-only. 105 | */ 106 | export type QueryCtx = GenericQueryCtx; 107 | 108 | /** 109 | * A set of services for use within Convex mutation functions. 110 | * 111 | * The mutation context is passed as the first argument to any Convex mutation 112 | * function run on the server. 113 | */ 114 | export type MutationCtx = GenericMutationCtx; 115 | 116 | /** 117 | * A set of services for use within Convex action functions. 118 | * 119 | * The action context is passed as the first argument to any Convex action 120 | * function run on the server. 121 | */ 122 | export type ActionCtx = GenericActionCtx; 123 | 124 | /** 125 | * An interface to read from the database within Convex query functions. 126 | * 127 | * The two entry points are {@link DatabaseReader.get}, which fetches a single 128 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts 129 | * building a query. 130 | */ 131 | export type DatabaseReader = GenericDatabaseReader; 132 | 133 | /** 134 | * An interface to read from and write to the database within Convex mutation 135 | * functions. 136 | * 137 | * Convex guarantees that all writes within a single mutation are 138 | * executed atomically, so you never have to worry about partial writes leaving 139 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control) 140 | * for the guarantees Convex provides your functions. 141 | */ 142 | export type DatabaseWriter = GenericDatabaseWriter; 143 | -------------------------------------------------------------------------------- /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 | * To regenerate, run `npx convex dev`. 8 | * @module 9 | */ 10 | 11 | import { 12 | actionGeneric, 13 | httpActionGeneric, 14 | queryGeneric, 15 | mutationGeneric, 16 | internalActionGeneric, 17 | internalMutationGeneric, 18 | internalQueryGeneric, 19 | } from "convex/server"; 20 | 21 | /** 22 | * Define a query in this Convex app's public API. 23 | * 24 | * This function will be allowed to read your Convex database and will be accessible from the client. 25 | * 26 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 27 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 28 | */ 29 | export const query = queryGeneric; 30 | 31 | /** 32 | * Define a query that is only accessible from other Convex functions (but not from the client). 33 | * 34 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 35 | * 36 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 37 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 38 | */ 39 | export const internalQuery = internalQueryGeneric; 40 | 41 | /** 42 | * Define a mutation in this Convex app's public API. 43 | * 44 | * This function will be allowed to modify your Convex database and will be accessible from the client. 45 | * 46 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 47 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 48 | */ 49 | export const mutation = mutationGeneric; 50 | 51 | /** 52 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 53 | * 54 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 55 | * 56 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 57 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 58 | */ 59 | export const internalMutation = internalMutationGeneric; 60 | 61 | /** 62 | * Define an action in this Convex app's public API. 63 | * 64 | * An action is a function which can execute any JavaScript code, including non-deterministic 65 | * code and code with side-effects, like calling third-party services. 66 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 67 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 68 | * 69 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 70 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 71 | */ 72 | export const action = actionGeneric; 73 | 74 | /** 75 | * Define an action that is only accessible from other Convex functions (but not from the client). 76 | * 77 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 78 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 79 | */ 80 | export const internalAction = internalActionGeneric; 81 | 82 | /** 83 | * Define a Convex HTTP action. 84 | * 85 | * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object 86 | * as its second. 87 | * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`. 88 | */ 89 | export const httpAction = httpActionGeneric; 90 | -------------------------------------------------------------------------------- /convex/auth.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | providers: [ 3 | { 4 | domain: process.env.CLERK_ISSUER_URL, 5 | applicationID: "convex", 6 | }, 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /convex/comments.ts: -------------------------------------------------------------------------------- 1 | import { v } from "convex/values"; 2 | import { mutation, query } from "./_generated/server"; 3 | 4 | export const getComments = query({ 5 | args: { promptId: v.id("prompts") }, 6 | handler: async (ctx, { promptId }) => { 7 | const comments = await ctx.db 8 | .query("comments") 9 | .withIndex("by_prompt", (q) => q.eq("promptId", promptId)) 10 | .order("desc") 11 | .collect(); 12 | 13 | return comments; 14 | }, 15 | }); 16 | 17 | export const addComment = mutation({ 18 | args: { 19 | promptId: v.id("prompts"), 20 | content: v.string(), 21 | userId: v.string(), 22 | userName: v.string(), 23 | parentId: v.optional(v.id("comments")), 24 | }, 25 | handler: async (ctx, args) => { 26 | const commentId = await ctx.db.insert("comments", { 27 | promptId: args.promptId, 28 | content: args.content, 29 | userId: args.userId, 30 | userName: args.userName, 31 | parentId: args.parentId, 32 | createdAt: Date.now(), 33 | }); 34 | return commentId; 35 | }, 36 | }); 37 | 38 | export const deleteComment = mutation({ 39 | args: { 40 | commentId: v.id("comments"), 41 | }, 42 | handler: async (ctx, { commentId }) => { 43 | const identity = await ctx.auth.getUserIdentity(); 44 | if (!identity) throw new Error("Not authenticated"); 45 | 46 | const comment = await ctx.db.get(commentId); 47 | if (!comment) throw new Error("Comment not found"); 48 | 49 | if (comment.userId !== identity.subject) { 50 | throw new Error("Not authorized to delete this comment"); 51 | } 52 | 53 | await ctx.db.delete(commentId); 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /convex/prompts.ts: -------------------------------------------------------------------------------- 1 | import { mutation, query } from "./_generated/server"; 2 | import { v } from "convex/values"; 3 | 4 | export const createPrompt = mutation({ 5 | args: { 6 | title: v.string(), 7 | description: v.string(), 8 | prompt: v.string(), 9 | categories: v.array(v.string()), 10 | githubProfile: v.optional(v.string()), 11 | isPublic: v.boolean(), 12 | slug: v.string(), 13 | }, 14 | handler: async (ctx, args) => { 15 | const identity = await ctx.auth.getUserIdentity(); 16 | if (!identity && !args.isPublic) { 17 | throw new Error("Must be logged in to create private prompts"); 18 | } 19 | 20 | const userId = identity?.subject; 21 | 22 | const promptId = await ctx.db.insert("prompts", { 23 | ...args, 24 | stars: 0, 25 | likes: 0, 26 | createdAt: Date.now(), 27 | isPublic: args.isPublic, 28 | userId: userId, 29 | }); 30 | return promptId; 31 | }, 32 | }); 33 | 34 | export const searchPrompts = query({ 35 | args: { 36 | searchQuery: v.optional(v.string()), 37 | categories: v.optional(v.array(v.string())), 38 | starRating: v.optional(v.number()), 39 | }, 40 | handler: async (ctx, args) => { 41 | const identity = await ctx.auth.getUserIdentity(); 42 | const userId = identity?.subject; 43 | 44 | let query = ctx.db.query("prompts"); 45 | 46 | if (userId) { 47 | query = query.filter((q) => 48 | q.or( 49 | q.eq(q.field("isPublic"), true), 50 | q.and(q.eq(q.field("userId"), userId), q.eq(q.field("isPublic"), false)) 51 | ) 52 | ); 53 | } else { 54 | query = query.filter((q) => q.eq(q.field("isPublic"), true)); 55 | } 56 | 57 | let prompts = await query.collect(); 58 | 59 | // Map the prompts to include their IDs 60 | prompts = prompts.map((prompt) => ({ 61 | ...prompt, 62 | _id: prompt._id, 63 | })); 64 | 65 | if (args.searchQuery) { 66 | const query = args.searchQuery.toLowerCase(); 67 | prompts = prompts.filter( 68 | (prompt) => 69 | prompt.title.toLowerCase().includes(query) || 70 | prompt.description.toLowerCase().includes(query) || 71 | prompt.prompt.toLowerCase().includes(query) 72 | ); 73 | } 74 | 75 | if (args.categories && args.categories.length > 0) { 76 | prompts = prompts.filter((prompt) => 77 | prompt.categories.some((category) => args.categories?.includes(category)) 78 | ); 79 | } 80 | 81 | if (args.starRating !== undefined) { 82 | prompts = prompts.filter((prompt) => prompt.stars === args.starRating); 83 | } 84 | 85 | return prompts; 86 | }, 87 | }); 88 | 89 | export const getPromptBySlug = query({ 90 | args: { slug: v.string() }, 91 | handler: async (ctx, { slug }) => { 92 | const identity = await ctx.auth.getUserIdentity(); 93 | const userId = identity?.subject; 94 | 95 | const prompts = await ctx.db 96 | .query("prompts") 97 | .filter((q) => q.eq(q.field("slug"), slug)) 98 | .collect(); 99 | 100 | if (!prompts.length) return null; 101 | 102 | const prompt = prompts[0]; 103 | 104 | // Check if user can access this prompt 105 | if (!prompt.isPublic && prompt.userId !== userId) { 106 | return null; // Private prompt that doesn't belong to current user 107 | } 108 | 109 | return { 110 | ...prompt, 111 | _id: prompt._id, 112 | }; 113 | }, 114 | }); 115 | 116 | export const ratePrompt = mutation({ 117 | args: { 118 | promptId: v.id("prompts"), 119 | rating: v.number(), 120 | }, 121 | handler: async (ctx, args) => { 122 | // Validate rating 123 | if (args.rating < 1 || args.rating > 5) { 124 | throw new Error("Rating must be between 1 and 5"); 125 | } 126 | 127 | // Add the rating 128 | await ctx.db.insert("starRatings", { 129 | promptId: args.promptId, 130 | rating: args.rating, 131 | createdAt: Date.now(), 132 | }); 133 | 134 | // Calculate new average rating 135 | const ratings = await ctx.db 136 | .query("starRatings") 137 | .filter((q) => q.eq(q.field("promptId"), args.promptId)) 138 | .collect(); 139 | 140 | const averageRating = ratings.reduce((sum, r) => sum + r.rating, 0) / ratings.length; 141 | 142 | // Update prompt with new rating 143 | await ctx.db.patch(args.promptId, { 144 | stars: Math.round(averageRating), 145 | }); 146 | }, 147 | }); 148 | 149 | export const likePrompt = mutation({ 150 | args: { 151 | promptId: v.id("prompts"), 152 | }, 153 | handler: async (ctx, args) => { 154 | const prompt = await ctx.db.get(args.promptId); 155 | if (!prompt) throw new Error("Prompt not found"); 156 | 157 | await ctx.db.patch(args.promptId, { 158 | likes: (prompt.likes || 0) + 1, 159 | }); 160 | }, 161 | }); 162 | 163 | export const unlikePrompt = mutation({ 164 | args: { 165 | promptId: v.id("prompts"), 166 | }, 167 | handler: async (ctx, args) => { 168 | const prompt = await ctx.db.get(args.promptId); 169 | if (!prompt) throw new Error("Prompt not found"); 170 | 171 | await ctx.db.patch(args.promptId, { 172 | likes: (prompt.likes || 0) - 1, 173 | }); 174 | }, 175 | }); 176 | 177 | export const getPrivatePrompts = query({ 178 | handler: async (ctx) => { 179 | const identity = await ctx.auth.getUserIdentity(); 180 | if (!identity) return []; 181 | 182 | const prompts = await ctx.db 183 | .query("prompts") 184 | .filter((q) => 185 | q.and(q.eq(q.field("userId"), identity.subject), q.eq(q.field("isPublic"), false)) 186 | ) 187 | .collect(); 188 | 189 | return prompts.map((prompt) => ({ 190 | ...prompt, 191 | _id: prompt._id, 192 | })); 193 | }, 194 | }); 195 | 196 | export const deletePrompt = mutation({ 197 | args: { id: v.id("prompts") }, 198 | handler: async (ctx, { id }) => { 199 | const identity = await ctx.auth.getUserIdentity(); 200 | if (!identity) throw new Error("Not authenticated"); 201 | 202 | const prompt = await ctx.db.get(id); 203 | if (!prompt) throw new Error("Prompt not found"); 204 | if (prompt.userId !== identity.subject) throw new Error("Not authorized"); 205 | 206 | await ctx.db.delete(id); 207 | }, 208 | }); 209 | 210 | export const updatePrompt = mutation({ 211 | args: { 212 | id: v.id("prompts"), 213 | title: v.optional(v.string()), 214 | description: v.optional(v.string()), 215 | prompt: v.optional(v.string()), 216 | categories: v.optional(v.array(v.string())), 217 | githubProfile: v.optional(v.string()), 218 | }, 219 | handler: async (ctx, args) => { 220 | const identity = await ctx.auth.getUserIdentity(); 221 | if (!identity) throw new Error("Not authenticated"); 222 | 223 | const existingPrompt = await ctx.db.get(args.id); 224 | if (!existingPrompt) throw new Error("Prompt not found"); 225 | if (existingPrompt.userId !== identity.subject) throw new Error("Not authorized"); 226 | 227 | const { id, ...updateData } = args; 228 | await ctx.db.patch(args.id, updateData); 229 | }, 230 | }); 231 | 232 | export const togglePromptVisibility = mutation({ 233 | args: { 234 | id: v.id("prompts"), 235 | isPublic: v.boolean(), 236 | }, 237 | handler: async (ctx, args) => { 238 | const identity = await ctx.auth.getUserIdentity(); 239 | if (!identity) throw new Error("Not authenticated"); 240 | 241 | const prompt = await ctx.db.get(args.id); 242 | if (!prompt) throw new Error("Prompt not found"); 243 | if (prompt.userId !== identity.subject) throw new Error("Not authorized"); 244 | 245 | await ctx.db.patch(args.id, { 246 | isPublic: args.isPublic, 247 | }); 248 | }, 249 | }); 250 | 251 | // Custom Categories Functions 252 | export const createCustomCategory = mutation({ 253 | args: { 254 | name: v.string(), 255 | }, 256 | returns: v.id("customCategories"), 257 | handler: async (ctx, args) => { 258 | const identity = await ctx.auth.getUserIdentity(); 259 | if (!identity) throw new Error("Must be logged in to create custom categories"); 260 | 261 | // Check if category already exists for this user 262 | const existingCategory = await ctx.db 263 | .query("customCategories") 264 | .withIndex("by_user", (q) => q.eq("userId", identity.subject)) 265 | .filter((q) => q.eq(q.field("name"), args.name)) 266 | .first(); 267 | 268 | if (existingCategory) { 269 | throw new Error("Category already exists"); 270 | } 271 | 272 | const categoryId = await ctx.db.insert("customCategories", { 273 | name: args.name, 274 | userId: identity.subject, 275 | createdAt: Date.now(), 276 | }); 277 | 278 | return categoryId; 279 | }, 280 | }); 281 | 282 | export const getUserCustomCategories = query({ 283 | returns: v.array( 284 | v.object({ 285 | _id: v.id("customCategories"), 286 | _creationTime: v.number(), 287 | name: v.string(), 288 | userId: v.string(), 289 | createdAt: v.number(), 290 | }) 291 | ), 292 | handler: async (ctx) => { 293 | const identity = await ctx.auth.getUserIdentity(); 294 | if (!identity) return []; 295 | 296 | const categories = await ctx.db 297 | .query("customCategories") 298 | .withIndex("by_user", (q) => q.eq("userId", identity.subject)) 299 | .collect(); 300 | 301 | return categories; 302 | }, 303 | }); 304 | 305 | export const deleteCustomCategory = mutation({ 306 | args: { 307 | id: v.id("customCategories"), 308 | }, 309 | returns: v.null(), 310 | handler: async (ctx, args) => { 311 | const identity = await ctx.auth.getUserIdentity(); 312 | if (!identity) throw new Error("Must be logged in to delete custom categories"); 313 | 314 | const category = await ctx.db.get(args.id); 315 | if (!category) throw new Error("Category not found"); 316 | 317 | if (category.userId !== identity.subject) { 318 | throw new Error("You can only delete your own custom categories"); 319 | } 320 | 321 | // Find all prompts that contain this category and remove it from their categories array 322 | const promptsWithCategory = await ctx.db 323 | .query("prompts") 324 | .filter((q) => q.eq(q.field("userId"), identity.subject)) 325 | .collect(); 326 | 327 | // Update each prompt to remove the deleted category 328 | for (const prompt of promptsWithCategory) { 329 | if (prompt.categories.includes(category.name)) { 330 | const updatedCategories = prompt.categories.filter((cat) => cat !== category.name); 331 | await ctx.db.patch(prompt._id, { 332 | categories: updatedCategories, 333 | }); 334 | } 335 | } 336 | 337 | // Delete the custom category 338 | await ctx.db.delete(args.id); 339 | return null; 340 | }, 341 | }); 342 | -------------------------------------------------------------------------------- /convex/schema.ts: -------------------------------------------------------------------------------- 1 | import { defineSchema, defineTable } from "convex/server"; 2 | import { v } from "convex/values"; 3 | 4 | export default defineSchema({ 5 | prompts: defineTable({ 6 | title: v.string(), 7 | description: v.string(), 8 | prompt: v.string(), 9 | categories: v.array(v.string()), 10 | stars: v.number(), 11 | likes: v.optional(v.number()), 12 | githubProfile: v.optional(v.string()), 13 | isPublic: v.boolean(), 14 | slug: v.string(), 15 | createdAt: v.number(), 16 | userId: v.optional(v.string()), 17 | }), 18 | starRatings: defineTable({ 19 | promptId: v.id("prompts"), 20 | rating: v.number(), 21 | createdAt: v.number(), 22 | }), 23 | comments: defineTable({ 24 | promptId: v.id("prompts"), 25 | content: v.string(), 26 | userId: v.string(), 27 | userName: v.string(), 28 | createdAt: v.number(), 29 | parentId: v.optional(v.id("comments")), 30 | }).index("by_prompt", ["promptId"]), 31 | customCategories: defineTable({ 32 | name: v.string(), 33 | userId: v.string(), 34 | createdAt: v.number(), 35 | }).index("by_user", ["userId"]), 36 | }); 37 | -------------------------------------------------------------------------------- /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 | "moduleResolution": "Bundler", 11 | "jsx": "react-jsx", 12 | "skipLibCheck": true, 13 | "allowSyntheticDefaultImports": true, 14 | 15 | /* These compiler options are required by Convex */ 16 | "target": "ESNext", 17 | "lib": ["ES2021", "dom"], 18 | "forceConsistentCasingInFileNames": true, 19 | "module": "ESNext", 20 | "isolatedModules": true, 21 | "noEmit": true 22 | }, 23 | "include": ["./**/*"], 24 | "exclude": ["./_generated"], 25 | "plugins": [{ "name": "@xixixao/convex-typescript-plugin" }] 26 | } 27 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | import reactHooks from 'eslint-plugin-react-hooks'; 4 | import reactRefresh from 'eslint-plugin-react-refresh'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | } 28 | ); 29 | -------------------------------------------------------------------------------- /files.md: -------------------------------------------------------------------------------- 1 | # PromptStack - File Structure Documentation 2 | 3 | ## Root Directory 4 | 5 | ### Configuration Files 6 | 7 | - **`package.json`** - Project dependencies, scripts, and metadata for React/Vite app with Convex backend 8 | - **`bun.lockb`** - Bun package manager lock file 9 | - **`package-lock.json`** - npm package manager lock file 10 | - **`.env.local`** - Environment variables for local development (Convex deployment URL, Clerk keys) 11 | - **`tsconfig.json`** - Main TypeScript configuration 12 | - **`tsconfig.app.json`** - TypeScript config for application code 13 | - **`tsconfig.node.json`** - TypeScript config for Node.js environment 14 | - **`vite.config.ts`** - Vite build tool configuration with React plugin 15 | - **`tailwind.config.js`** - Tailwind CSS configuration with custom theme 16 | - **`postcss.config.js`** - PostCSS configuration for CSS processing 17 | - **`eslint.config.js`** - ESLint configuration for code linting 18 | - **`.gitignore`** - Git ignore patterns 19 | - **`.npmrc`** - npm configuration 20 | - **`.node-version`** - Node.js version specification 21 | - **`netlify.toml`** - Netlify deployment configuration 22 | 23 | ### Documentation Files 24 | 25 | - **`README.md`** - Main project documentation and setup instructions 26 | - **`files.md`** - This file - comprehensive file structure documentation 27 | - **`changelog.md`** - Development changelog with feature tracking 28 | - **`modprompts.md`** - Additional prompt examples and documentation 29 | - **`LICENSE`** - MIT license file 30 | 31 | ### Entry Points 32 | 33 | - **`index.html`** - Main HTML entry point for the Vite application 34 | 35 | ## Source Code (`src/`) 36 | 37 | ### Main Application Files 38 | 39 | - **`main.tsx`** - Application entry point, renders React app with Convex and Clerk providers 40 | - **`App.tsx`** - Main application component with homepage, prompt listings, search, filtering, and modal functionality 41 | - **`router.ts`** - TanStack Router configuration 42 | - **`routeTree.gen.ts`** - Auto-generated route tree for TanStack Router 43 | - **`ConvexClientProvider.tsx`** - Convex client provider for database connection 44 | - **`ThemeContext.tsx`** - Theme context for dark/light mode management 45 | 46 | ### Styling 47 | 48 | - **`index.css`** - Global CSS styles and Tailwind imports 49 | - **`fonts.css`** - Font definitions and imports 50 | - **`vite-env.d.ts`** - Vite environment type definitions 51 | 52 | ### Routes (`src/routes/`) 53 | 54 | - **`__root.tsx`** - Root route layout component 55 | - **`index.tsx`** - Home page route (redirects to App.tsx) 56 | - **`addnew.tsx`** - Add new prompt form page using shared PromptForm component 57 | - **`prompt.$slug.tsx`** - Individual prompt detail page with edit functionality, comments, and access control 58 | - **`docs.tsx`** - Documentation page with API and usage information 59 | - **`about.tsx`** - About page with project information 60 | - **`prompt-guide.tsx`** - Comprehensive prompt creation guide 61 | - **`404.tsx`** - 404 error page for missing routes 62 | 63 | ### Components (`src/components/`) 64 | 65 | #### Core Components 66 | 67 | - **`Header.tsx`** - Main navigation header with search, user menu, and authentication 68 | - **`Footer.tsx`** - Site footer with prompt count and links 69 | - **`PromptForm.tsx`** - **SHARED** modern, compact form component for creating and editing prompts (used in modals and standalone) with improved UI/UX 70 | - **`NotFound.tsx`** - 404 component for inaccessible or non-existent prompts 71 | 72 | #### Content Components 73 | 74 | - **`CodeBlock.tsx`** - Syntax highlighted code display component 75 | - **`CodeEditor.tsx`** - Code editor component for prompt editing 76 | - **`CodeEditor.css`** - Styles for the code editor 77 | - **`CommentSection.tsx`** - Comment system for prompts with TipTap rich text editor 78 | - **`minimal-tiptap.tsx`** - Minimal TipTap rich text editor component 79 | 80 | #### Utility Components 81 | 82 | - **`ConvexIcon.tsx`** - Convex logo icon component 83 | 84 | ### UI Components (`src/components/ui/`) 85 | 86 | - **`switch.tsx`** - Toggle switch component for UI controls 87 | 88 | ### Utilities (`src/lib/`) 89 | 90 | - **`utils.ts`** - Utility functions and helpers 91 | - **`types.ts`** - TypeScript type definitions 92 | 93 | ### Constants (`src/constants/`) 94 | 95 | - **`categories.ts`** - Centralized CATEGORIES list used across the application 96 | 97 | ## Backend (`convex/`) 98 | 99 | ### Database & Functions 100 | 101 | - **`schema.ts`** - Convex database schema definitions (prompts, comments, starRatings, customCategories) 102 | - **`prompts.ts`** - Prompt-related database queries and mutations (CRUD, ownership validation, custom categories) 103 | - **`comments.ts`** - Comment system database functions 104 | - **`auth.config.ts`** - Authentication configuration for Clerk integration 105 | - **`tsconfig.json`** - TypeScript configuration for Convex functions 106 | - **`README.md`** - Convex-specific documentation 107 | 108 | ### Generated Files (`convex/_generated/`) 109 | 110 | - Auto-generated Convex client code and type definitions 111 | 112 | ## Public Assets (`public/`) 113 | 114 | ### Branding & Icons 115 | 116 | - **`promptstacklogo.svg`** - Main PromptStack logo (SVG) 117 | - **`promptstacklogo.png`** - Main PromptStack logo (PNG) 118 | - **`promptdevlogo.svg`** - Alternative logo variant 119 | - **`convex-black.svg`** - Convex logo (black) 120 | - **`convex-white.svg`** - Convex logo (white) 121 | - **`convex-grey-icon.svg`** - Convex icon (grey) 122 | 123 | ### Favicons & App Icons 124 | 125 | - **`favicon.svg`** - Main favicon 126 | - **`favicon-*.png`** - Various favicon sizes (16x16, 32x32, 96x96, etc.) 127 | - **`apple-touch-icon*.png`** - Apple touch icons for iOS devices 128 | - **`android-chrome-*.png`** - Android Chrome app icons 129 | - **`mstile-*.png`** - Microsoft tile icons for Windows 130 | 131 | ### Meta Files 132 | 133 | - **`site.webmanifest`** - Web app manifest for PWA features 134 | - **`robots.txt`** - Search engine crawler instructions 135 | - **`sitemap.xml`** - Site structure for search engines 136 | - **`og-image.png`** - Open Graph image for social sharing 137 | 138 | ### Data Files 139 | 140 | - **`llms.json`** - LLM data in JSON format 141 | - **`llms.md`** - LLM documentation in Markdown 142 | - **`llms.txt`** - LLM data in text format 143 | 144 | ### Fonts (`public/fonts/`) 145 | 146 | - Custom font files for the application 147 | 148 | ## Build Output & Dependencies 149 | 150 | - **`dist/`** - Production build output directory (generated by Vite) 151 | - **`node_modules/`** - Installed npm packages 152 | 153 | ## Development Tools 154 | 155 | - **`.cursor/`** - Cursor IDE configuration 156 | - **`.git/`** - Git version control directory 157 | 158 | # Key Application Features 159 | 160 | ## Authentication & User Management 161 | 162 | - **Clerk Integration**: Full authentication system with sign-in/sign-up 163 | - **User Profiles**: User identity management through Clerk 164 | - **Session Management**: Persistent user sessions across app 165 | 166 | ## Prompt Management System 167 | 168 | ### Core Functionality 169 | 170 | - **CRUD Operations**: Create, read, update, delete prompts with modern, compact form interface 171 | - **Ownership Validation**: Users can only edit/delete their own prompts 172 | - **Privacy Controls**: Public/private prompt visibility 173 | - **Access Control**: Private prompts only accessible to creators 174 | 175 | ### Advanced Features 176 | 177 | - **Custom Categories**: User-specific categories for organization 178 | - **Category Management**: Add/delete custom categories with validation 179 | - **Search & Filtering**: Real-time search and category-based filtering 180 | - **Prompt Likes**: Like system for community engagement 181 | 182 | ## User Interface 183 | 184 | ### Modern Design System 185 | 186 | - **Compact UI**: Streamlined, space-efficient interface design 187 | - **Modern Forms**: Clean, professional form components with improved spacing and typography 188 | - **Responsive Design**: Mobile-first approach for all screen sizes 189 | - **Dark/Light Theme**: Theme switching with persistent preferences 190 | 191 | ### Interactive Components 192 | 193 | - **Modal System**: Edit prompts in modal overlays with compact design 194 | - **Confirmation Dialogs**: User confirmation for destructive actions 195 | - **Real-time Updates**: Live updates through Convex real-time database 196 | - **Rich Text Editing**: TipTap editor for comments and descriptions 197 | - **View Toggles**: Switch between grid view and Hacker News-style list view 198 | - **List View**: Compact display with numbered entries, categories, and actions 199 | 200 | ### Enhanced User Experience 201 | 202 | - **Keyboard Shortcuts**: ESC to close modals, Ctrl/Cmd+Enter to submit forms 203 | - **Form Validation**: Real-time validation with clear error messaging 204 | - **Character Limits**: Visual feedback for input length restrictions 205 | - **Tab Navigation**: Logical tab order for accessibility 206 | 207 | ## Technical Architecture 208 | 209 | ### Frontend Stack 210 | 211 | - **React 18**: Modern React with hooks and concurrent features 212 | - **TanStack Router**: Type-safe file-based routing 213 | - **TypeScript**: Full type safety across application 214 | - **Tailwind CSS**: Utility-first CSS framework with custom design system 215 | - **Vite**: Fast development and build tool 216 | 217 | ### Backend Stack 218 | 219 | - **Convex.dev**: Real-time database with automatic synchronization 220 | - **Clerk**: Authentication and user management 221 | - **Netlify**: Static site hosting and deployment 222 | 223 | ### Development Workflow 224 | 225 | - **Type Safety**: End-to-end TypeScript integration 226 | - **Code Quality**: ESLint configuration for consistent code 227 | - **Package Management**: Bun for fast package installation 228 | - **Version Control**: Git with comprehensive .gitignore 229 | 230 | ## Recent Major Changes 231 | 232 | ### UI/UX Improvements (2024-12-19) 233 | 234 | - **Compact Form Design**: Reduced spacing and improved visual hierarchy in PromptForm 235 | - **Modern Typography**: Consistent text sizing and improved readability 236 | - **Refined Interactions**: Smaller icons, tighter layouts, and polished button styles 237 | - **Enhanced Accessibility**: Better keyboard navigation and focus management 238 | 239 | ### Component Consolidation (2024-12-19) 240 | 241 | - **Shared PromptForm**: Eliminated duplicate form logic across components 242 | - **Modal Integration**: PromptForm works seamlessly as both modal and standalone component 243 | - **Consistent UX**: Unified prompt creation/editing experience across the app 244 | 245 | ### Custom Categories System 246 | 247 | - **User-Specific Categories**: Each user can create their own categories 248 | - **Category Management**: Add/delete custom categories with proper validation 249 | - **UI Integration**: Seamless integration with existing category system 250 | - **Data Cleanup**: Automatic removal of deleted categories from prompts 251 | 252 | ### Access Control & Security 253 | 254 | - **Private Prompt Protection**: Proper access control for private prompts 255 | - **404 Handling**: Users see 404 for inaccessible private prompts 256 | - **Ownership Validation**: Backend validation for all user actions 257 | - **Authentication Integration**: Proper Clerk and Convex auth integration 258 | 259 | ## Application Capabilities 260 | 261 | ### Content Management 262 | 263 | - **AI Prompt Library**: Comprehensive collection of AI prompts for various use cases 264 | - **Code Generation Rules**: Specialized prompts for code generation and development 265 | - **Cursor Rules**: IDE-specific rules and configurations 266 | - **README Templates**: Documentation templates and examples 267 | 268 | ### Community Features 269 | 270 | - **Social Sharing**: Share prompts with direct links 271 | - **Community Engagement**: Like and comment on prompts 272 | - **User Profiles**: Link GitHub and social profiles to submissions 273 | - **Public Directory**: Discover community-contributed content 274 | 275 | ### Developer Tools 276 | 277 | - **Carbon Copy View**: View prompts in a Carbon-style code editor 278 | - **Syntax Highlighting**: Code blocks with proper syntax highlighting 279 | - **Export Functionality**: Easy copying and sharing of prompt content 280 | - **API Integration**: Built for integration with AI development tools 281 | 282 | This documentation reflects the current state of PromptStack as of December 2024, including all recent UI improvements, feature additions, and architectural enhancements. 283 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Prompt Stack - Open Source - AI Prompts, Cursor Rules and MCP Server Directory for Prompt 13 | Engineering / Vibe Coding 14 | 15 | 18 | 19 | 20 | 21 | 22 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 39 | 40 | 41 | 42 | 47 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 |
72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /modprompts.md: -------------------------------------------------------------------------------- 1 | modprompts.md 2 | 3 | # PromptStack - User Moderation & Permissions Research 4 | 5 | ## Overview 6 | 7 | Research on implementing user permissions to allow logged-in users to edit and delete only their own prompt submissions. 8 | 9 | ## Current Authentication System 10 | 11 | ### Clerk Integration 12 | 13 | - **Authentication Provider**: Clerk is integrated for user management 14 | - **User Identity**: Available through `ctx.auth.getUserIdentity()` in Convex functions 15 | - **User Data**: Returns user ID, name, email, and other profile information 16 | 17 | ### Current Prompt Schema 18 | 19 | ```typescript 20 | // convex/schema.ts 21 | prompts: defineTable({ 22 | title: v.string(), 23 | description: v.string(), 24 | prompt: v.string(), 25 | tags: v.array(v.string()), 26 | category: v.string(), 27 | slug: v.string(), 28 | likes: v.number(), 29 | views: v.number(), 30 | featured: v.boolean(), 31 | // Missing: authorId field for ownership tracking 32 | }); 33 | ``` 34 | 35 | ## Required Implementation Changes 36 | 37 | ### 1. Schema Updates 38 | 39 | **Add Author Tracking to Prompts Table:** 40 | 41 | ```typescript 42 | // convex/schema.ts - Updated prompts table 43 | prompts: defineTable({ 44 | title: v.string(), 45 | description: v.string(), 46 | prompt: v.string(), 47 | tags: v.array(v.string()), 48 | category: v.string(), 49 | slug: v.string(), 50 | likes: v.number(), 51 | views: v.number(), 52 | featured: v.boolean(), 53 | authorId: v.string(), // Add this field to track prompt owner 54 | createdAt: v.number(), // Optional: track creation time 55 | }) 56 | .index("by_author", ["authorId"]) // Index for efficient author queries 57 | .index("by_slug", ["slug"]); 58 | ``` 59 | 60 | ### 2. Backend Functions (Convex) 61 | 62 | **Update Create Prompt Function:** 63 | 64 | ```typescript 65 | // convex/prompts.ts - Modified createPrompt 66 | export const createPrompt = mutation({ 67 | args: { 68 | title: v.string(), 69 | description: v.string(), 70 | prompt: v.string(), 71 | tags: v.array(v.string()), 72 | category: v.string(), 73 | }, 74 | handler: async (ctx, args) => { 75 | // Get authenticated user 76 | const identity = await ctx.auth.getUserIdentity(); 77 | if (!identity) { 78 | throw new Error("Must be logged in to create prompts"); 79 | } 80 | 81 | const slug = generateSlug(args.title); 82 | 83 | return await ctx.db.insert("prompts", { 84 | ...args, 85 | slug, 86 | authorId: identity.subject, // Store user ID as author 87 | likes: 0, 88 | views: 0, 89 | featured: false, 90 | createdAt: Date.now(), 91 | }); 92 | }, 93 | }); 94 | ``` 95 | 96 | **Add Update Prompt Function:** 97 | 98 | ```typescript 99 | // convex/prompts.ts - New updatePrompt function 100 | export const updatePrompt = mutation({ 101 | args: { 102 | id: v.id("prompts"), 103 | title: v.optional(v.string()), 104 | description: v.optional(v.string()), 105 | prompt: v.optional(v.string()), 106 | tags: v.optional(v.array(v.string())), 107 | category: v.optional(v.string()), 108 | }, 109 | handler: async (ctx, args) => { 110 | const identity = await ctx.auth.getUserIdentity(); 111 | if (!identity) { 112 | throw new Error("Must be logged in to edit prompts"); 113 | } 114 | 115 | // Get the existing prompt 116 | const existingPrompt = await ctx.db.get(args.id); 117 | if (!existingPrompt) { 118 | throw new Error("Prompt not found"); 119 | } 120 | 121 | // Check ownership 122 | if (existingPrompt.authorId !== identity.subject) { 123 | throw new Error("You can only edit your own prompts"); 124 | } 125 | 126 | // Update the prompt 127 | const { id, ...updateData } = args; 128 | await ctx.db.patch(args.id, updateData); 129 | }, 130 | }); 131 | ``` 132 | 133 | **Add Delete Prompt Function:** 134 | 135 | ```typescript 136 | // convex/prompts.ts - New deletePrompt function 137 | export const deletePrompt = mutation({ 138 | args: { 139 | id: v.id("prompts"), 140 | }, 141 | handler: async (ctx, args) => { 142 | const identity = await ctx.auth.getUserIdentity(); 143 | if (!identity) { 144 | throw new Error("Must be logged in to delete prompts"); 145 | } 146 | 147 | // Get the existing prompt 148 | const existingPrompt = await ctx.db.get(args.id); 149 | if (!existingPrompt) { 150 | throw new Error("Prompt not found"); 151 | } 152 | 153 | // Check ownership 154 | if (existingPrompt.authorId !== identity.subject) { 155 | throw new Error("You can only delete your own prompts"); 156 | } 157 | 158 | // Delete the prompt 159 | await ctx.db.delete(args.id); 160 | }, 161 | }); 162 | ``` 163 | 164 | **Add User's Prompts Query:** 165 | 166 | ```typescript 167 | // convex/prompts.ts - Query user's own prompts 168 | export const getUserPrompts = query({ 169 | args: {}, 170 | handler: async (ctx) => { 171 | const identity = await ctx.auth.getUserIdentity(); 172 | if (!identity) { 173 | return []; 174 | } 175 | 176 | return await ctx.db 177 | .query("prompts") 178 | .withIndex("by_author", (q) => q.eq("authorId", identity.subject)) 179 | .order("desc") 180 | .collect(); 181 | }, 182 | }); 183 | ``` 184 | 185 | ### 3. Frontend Components 186 | 187 | **Add Edit/Delete Buttons to Prompt Display:** 188 | 189 | ```typescript 190 | // src/components/PromptActions.tsx - New component 191 | import { useUser } from "@clerk/clerk-react"; 192 | import { useMutation } from "convex/react"; 193 | import { api } from "../../convex/_generated/api"; 194 | 195 | interface PromptActionsProps { 196 | prompt: { 197 | _id: string; 198 | authorId: string; 199 | title: string; 200 | // ... other prompt fields 201 | }; 202 | } 203 | 204 | export function PromptActions({ prompt }: PromptActionsProps) { 205 | const { user } = useUser(); 206 | const deletePrompt = useMutation(api.prompts.deletePrompt); 207 | 208 | // Only show actions if user owns this prompt 209 | if (!user || user.id !== prompt.authorId) { 210 | return null; 211 | } 212 | 213 | const handleDelete = async () => { 214 | if (confirm("Are you sure you want to delete this prompt?")) { 215 | await deletePrompt({ id: prompt._id }); 216 | } 217 | }; 218 | 219 | return ( 220 |
221 | 227 | 233 |
234 | ); 235 | } 236 | ``` 237 | 238 | **Edit Form Component:** 239 | 240 | ```typescript 241 | // src/components/EditPromptForm.tsx - New component 242 | import { useState } from "react"; 243 | import { useMutation, useQuery } from "convex/react"; 244 | import { api } from "../../convex/_generated/api"; 245 | 246 | interface EditPromptFormProps { 247 | promptId: string; 248 | onSuccess?: () => void; 249 | } 250 | 251 | export function EditPromptForm({ promptId, onSuccess }: EditPromptFormProps) { 252 | const prompt = useQuery(api.prompts.getPromptById, { id: promptId }); 253 | const updatePrompt = useMutation(api.prompts.updatePrompt); 254 | 255 | const [formData, setFormData] = useState({ 256 | title: prompt?.title || "", 257 | description: prompt?.description || "", 258 | prompt: prompt?.prompt || "", 259 | tags: prompt?.tags || [], 260 | category: prompt?.category || "", 261 | }); 262 | 263 | const handleSubmit = async (e: React.FormEvent) => { 264 | e.preventDefault(); 265 | try { 266 | await updatePrompt({ 267 | id: promptId, 268 | ...formData, 269 | }); 270 | onSuccess?.(); 271 | } catch (error) { 272 | console.error("Failed to update prompt:", error); 273 | } 274 | }; 275 | 276 | if (!prompt) return
Loading...
; 277 | 278 | return ( 279 |
280 | {/* Form fields similar to AddNew component */} 281 | setFormData({...formData, title: e.target.value})} 285 | placeholder="Prompt Title" 286 | className="w-full p-2 border rounded" 287 | /> 288 | {/* Add other form fields */} 289 | 292 |
293 | ); 294 | } 295 | ``` 296 | 297 | ### 4. Route Updates 298 | 299 | **Add Edit Route:** 300 | 301 | ```typescript 302 | // src/routes/edit.$promptId.tsx - New edit route 303 | import { createFileRoute } from '@tanstack/react-router'; 304 | import { EditPromptForm } from '../components/EditPromptForm'; 305 | 306 | export const Route = createFileRoute('/edit/$promptId')({ 307 | component: EditPrompt, 308 | }); 309 | 310 | function EditPrompt() { 311 | const { promptId } = Route.useParams(); 312 | 313 | return ( 314 |
315 |

Edit Prompt

316 | { 319 | // Navigate back to prompt or user dashboard 320 | }} 321 | /> 322 |
323 | ); 324 | } 325 | ``` 326 | 327 | ## Implementation Steps 328 | 329 | ### Phase 1: Database Migration 330 | 331 | 1. **Update Schema**: Add `authorId` field to prompts table 332 | 2. **Data Migration**: Update existing prompts with author information (if possible) 333 | 3. **Add Indexes**: Create index for efficient author queries 334 | 335 | ### Phase 2: Backend Functions 336 | 337 | 1. **Modify createPrompt**: Add author tracking 338 | 2. **Add updatePrompt**: With ownership validation 339 | 3. **Add deletePrompt**: With ownership validation 340 | 4. **Add getUserPrompts**: Query user's own prompts 341 | 342 | ### Phase 3: Frontend Implementation 343 | 344 | 1. **Create PromptActions**: Edit/Delete buttons component 345 | 2. **Create EditPromptForm**: Form for editing prompts 346 | 3. **Add Edit Route**: New route for editing 347 | 4. **Update Prompt Display**: Show actions for owned prompts 348 | 349 | ### Phase 4: User Experience 350 | 351 | 1. **User Dashboard**: Show user's own prompts 352 | 2. **Confirmation Dialogs**: For delete actions 353 | 3. **Error Handling**: User-friendly error messages 354 | 4. **Loading States**: During edit/delete operations 355 | 356 | ## Security Considerations 357 | 358 | ### Authorization Checks 359 | 360 | - **Server-side Validation**: Always verify ownership in Convex functions 361 | - **Client-side UI**: Hide actions for non-owners (UX only, not security) 362 | - **Error Messages**: Don't reveal existence of prompts user can't access 363 | 364 | ### Data Integrity 365 | 366 | - **Atomic Operations**: Use Convex transactions for complex updates 367 | - **Validation**: Validate all input data 368 | - **Audit Trail**: Consider logging edit/delete actions 369 | 370 | ## Testing Strategy 371 | 372 | ### Unit Tests 373 | 374 | - Test ownership validation logic 375 | - Test edit/delete functions with different user scenarios 376 | - Test error handling for unauthorized access 377 | 378 | ### Integration Tests 379 | 380 | - Test complete edit workflow 381 | - Test delete workflow with confirmation 382 | - Test user dashboard functionality 383 | 384 | ## Future Enhancements 385 | 386 | ### Advanced Permissions 387 | 388 | - **Admin Override**: Allow admins to edit/delete any prompt 389 | - **Collaboration**: Allow multiple authors per prompt 390 | - **Moderation**: Flag inappropriate content 391 | 392 | ### User Experience 393 | 394 | - **Bulk Operations**: Select multiple prompts for deletion 395 | - **Version History**: Track prompt edit history 396 | - **Draft Mode**: Save drafts before publishing 397 | 398 | ## Migration Notes 399 | 400 | ### Existing Data 401 | 402 | - Current prompts don't have `authorId` field 403 | - Need strategy for assigning ownership to existing prompts 404 | - Consider marking old prompts as "legacy" or "community" 405 | 406 | ### Backward Compatibility 407 | 408 | - Ensure existing functionality continues to work 409 | - Graceful handling of prompts without authors 410 | - Update all queries to handle new schema 411 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npx convex deploy && npm run build" 3 | publish = "dist" 4 | environment = { VITE_CONVEX_URL = "https://prestigious-woodpecker-186.convex.cloud" } 5 | 6 | [[redirects]] 7 | from = "/*" 8 | to = "/index.html" 9 | status = 200 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-prompts-directory", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "npm-run-all --parallel dev:frontend dev:backend", 8 | "dev:frontend": "vite", 9 | "dev:backend": "convex dev --tail-logs", 10 | "build": "tsc && vite build --config vite.config.ts", 11 | "tsc": "tsc", 12 | "lint": "eslint .", 13 | "preview": "vite preview" 14 | }, 15 | "dependencies": { 16 | "@clerk/clerk-react": "^5.22.8", 17 | "@codesandbox/sandpack-react": "^2.19.10", 18 | "@emotion/react": "^11.14.0", 19 | "@emotion/styled": "^11.14.0", 20 | "@liveblocks/client": "^2.16.2", 21 | "@liveblocks/react": "^2.16.2", 22 | "@mui/icons-material": "^6.4.2", 23 | "@mui/material": "^6.4.2", 24 | "@radix-ui/react-switch": "^1.1.2", 25 | "@radix-ui/react-toggle": "^1.1.1", 26 | "@tabler/icons-react": "^3.29.0", 27 | "@tanstack/react-query": "^5.28.4", 28 | "@tanstack/react-router": "^1.19.4", 29 | "@tanstack/router-devtools": "^1.19.4", 30 | "@tanstack/router-vite-plugin": "^1.19.4", 31 | "@tiptap/core": "^2.11.5", 32 | "@tiptap/extension-code-block-lowlight": "^2.11.3", 33 | "@tiptap/extension-placeholder": "^2.11.5", 34 | "@tiptap/pm": "^2.11.5", 35 | "@tiptap/react": "^2.11.5", 36 | "@tiptap/starter-kit": "^2.11.5", 37 | "@types/react-helmet": "^6.1.11", 38 | "@xixixao/convex-typescript-plugin": "^0.0.1", 39 | "class-variance-authority": "^0.7.1", 40 | "clsx": "^2.1.1", 41 | "convex": "^1.18.2", 42 | "framer-motion": "^11.0.8", 43 | "lowlight": "^3.3.0", 44 | "lucide-react": "^0.344.0", 45 | "motion": "^12.0.6", 46 | "puppeteer": "^24.1.1", 47 | "react": "^18.3.1", 48 | "react-dom": "^18.3.1", 49 | "react-helmet": "^6.1.0", 50 | "react-helmet-async": "^2.0.5", 51 | "tailwind-merge": "^3.0.1", 52 | "zod": "^3.22.4" 53 | }, 54 | "devDependencies": { 55 | "@tailwindcss/typography": "^0.5.16", 56 | "@types/react": "latest", 57 | "@types/react-dom": "latest", 58 | "@typescript-eslint/eslint-plugin": "^7.1.1", 59 | "@typescript-eslint/parser": "^7.1.1", 60 | "@vitejs/plugin-react": "latest", 61 | "autoprefixer": "^10.4.18", 62 | "eslint": "^8.57.0", 63 | "eslint-plugin-react-hooks": "^4.6.0", 64 | "eslint-plugin-react-refresh": "^0.4.5", 65 | "npm-run-all": "^4.1.5", 66 | "postcss": "^8.4.35", 67 | "rollup": "^4.32.1", 68 | "tailwindcss": "^3.4.1", 69 | "typescript": "latest", 70 | "typescript-eslint": "^8.23.0", 71 | "vite": "latest" 72 | }, 73 | "engines": { 74 | "node": ">=18.0.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/apple-touch-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-167x167.png -------------------------------------------------------------------------------- /public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/convex-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/convex-grey-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/convex-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/favicon-128x128.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/favicon-196x196.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/llms.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Prompt Stack - AI Prompts, Cursor Rules and MCP Server Directory for Prompt Engineering / Vibe Coding", 3 | "website": "https://promptstack.dev", 4 | "description": "Prompt Stack is an open-source, searchable collection of AI prompts and code generation rules for prompt engineering, featuring Cursor rules, Bolt.new, Loveable, Windsurf, and Trae; designed to streamline developer workflows.", 5 | "built_with": { 6 | "database": "https://convex.link/promptstackgithub", 7 | "client_routing": "https://tanstack.com/router/latest/docs/framework/react/overview" 8 | }, 9 | "platform": "Prompt Stack is a community-driven platform for developers to discover, share, and manage AI prompts and code generation rules.", 10 | "goal": "To help developers leverage AI tools more effectively by providing a curated collection of prompts that enhance productivity and code quality.", 11 | "supported_tools": [ 12 | "Cursor", 13 | "Bolt.new", 14 | "Loveable", 15 | "Windsurf", 16 | "Trae", 17 | "GitHub Copilot", 18 | "ChatGPT", 19 | "Claude", 20 | "Other AI Assistants" 21 | ], 22 | "features": { 23 | "searchable_directory": "Quickly find AI prompts and code-gen rules tailored to your needs.", 24 | "prompt_rating_system": "Rate and discover top prompts in the directory.", 25 | "category_organization": "Prompts are organized into clear, functional categories.", 26 | "github_integration": "Automatically link GitHub or social profiles submitted with prompts.", 27 | "carbon_copy_view": "View and copy prompts in a Carbon-style editor window.", 28 | "readme_support": "Find and submit README examples for AI and code-gen projects.", 29 | "cursor_rules": "Find and submit Cursor rules for AI and code-gen projects.", 30 | "seo_optimized": "AI tool lists, prompt engineering, and metadata for better discoverability." 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/llms.md: -------------------------------------------------------------------------------- 1 | # Prompt Stack - Open Source - AI Prompts and code generation rules for Prompt Engineering 2 | 3 | **[Prompt Stack](https://promptstack.dev)** is an **open-source** searchable collection of AI prompts and code generation rules for prompt engineering, featuring Cursor rules, Bolt.new, Loveable, Windsurf, and Trae; designed to streamline developer workflows. 4 | 5 | Built with [Convex.dev](https://convex.link/promptstackgithub) as the database and [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview) for client-side routing. 6 | 7 | Prompt Stack is a community-driven platform for developers to discover, share, and manage AI prompts and code generation rules. 8 | 9 | ## Goal 10 | 11 | The goal of PromptStack is to help developers leverage AI tools more effectively by providing a curated collection of prompts that enhance productivity and code quality. 12 | 13 | Whether you're using **Cursor, Bolt.new, Loveable, Windsurf, Trae, GitHub Copilot, ChatGPT, Claude**, or other AI assistants, you'll find valuable prompts to improve your workflow. 14 | 15 | ## Features 16 | 17 | - **Searchable Directory**: Quickly find AI prompts and code-gen rules tailored to your needs. 18 | - **Prompt Rating System**: Rate and discover top prompts in the directory. 19 | - **Category Organization**: Prompts are organized into clear, functional categories. 20 | - **GitHub Integration**: Automatically link GitHub or social profiles submitted with prompts. 21 | - **Carbon Copy View**: View and copy prompts in a Carbon-style editor window. 22 | - **Readme Support**: Find and submit README examples for AI and code-gen projects. 23 | - **.Cursorrules**: Find and submit Cursor rules for AI and code-gen projects. 24 | - **SEO Optimized**: AI tool lists, prompt engineering, and metadata for better discoverability. 25 | 26 | --- 27 | 28 | Built with ❤️ using [Convex.dev](https://docs.convex.dev/) and [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview). 29 | -------------------------------------------------------------------------------- /public/llms.txt: -------------------------------------------------------------------------------- 1 | Prompt Stack - Open Source - AI Prompts and code generation rules for Prompt Engineering 2 | 3 | Prompt Stack (https://promptstack.dev) is an open-source searchable collection of AI prompts and code generation rules for prompt engineering, featuring Cursor rules, Bolt.new, Loveable, Windsurf, and Trae; designed to streamline developer workflows. 4 | 5 | Built with Convex.dev (https://convex.link/promptstackgithub) as the database and TanStack Router (https://tanstack.com/router/latest/docs/framework/react/overview) for client-side routing. 6 | 7 | Prompt Stack is a community-driven platform for developers to discover, share, and manage AI prompts and code generation rules. 8 | 9 | Goal 10 | 11 | The goal of PromptS tack is to help developers leverage AI tools more effectively by providing a curated collection of prompts that enhance productivity and code quality. 12 | 13 | Whether you're using Cursor, Bolt.new, Loveable, Windsurf, Trae, GitHub Copilot, ChatGPT, Claude, or other AI assistants, you'll find valuable prompts to improve your workflow. 14 | 15 | Features 16 | 17 | - Searchable Directory: Quickly find AI prompts and code-gen rules tailored to your needs. 18 | - Prompt Rating System: Rate and discover top prompts in the directory. 19 | - Category Organization: Prompts are organized into clear, functional categories. 20 | - GitHub Integration: Automatically link GitHub or social profiles submitted with prompts. 21 | - Carbon Copy View: View and copy prompts in a Carbon-style editor window. 22 | - Readme Support: Find and submit README examples for AI and code-gen projects. 23 | - .Cursorrules: Find and submit Cursor rules for AI and code-gen projects. 24 | - SEO Optimized: AI tool lists, prompt engineering, and metadata for better discoverability. 25 | 26 | Built with love using Convex.dev (https://docs.convex.dev/) and TanStack Router (https://tanstack.com/router/latest/docs/framework/react/overview). 27 | 28 | This keeps everything clean and readable while stripping out any Markdown or JSON formatting. Let me know if you need any adjustments! 🚀 -------------------------------------------------------------------------------- /public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/mstile-144x144.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/mstile-310x150.png -------------------------------------------------------------------------------- /public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/mstile-310x310.png -------------------------------------------------------------------------------- /public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/mstile-70x70.png -------------------------------------------------------------------------------- /public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/og-image.png -------------------------------------------------------------------------------- /public/promptdevlogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/promptstacklogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waynesutton/promptstack/82d2d3c616e2383e222d31f817ed1d9f32519110/public/promptstacklogo.png -------------------------------------------------------------------------------- /public/promptstacklogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://promptstack.dev/robots.txt 2 | # Allow all crawlers 3 | User-agent: * 4 | Allow: / 5 | 6 | # Disallow private prompts 7 | Disallow: /private/ 8 | 9 | # Sitemap 10 | Sitemap: https://promptstack.dev/sitemap.xml -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PromptStack", 3 | "short_name": "PromptStack", 4 | "description": "A searchable collection of AI prompts and code generation rules for prompt engineering, featuring Cursor rules, Bolt.new, Loveable, Windsurf, and Trae; designed to streamline developer workflows.", 5 | "icons": [ 6 | { 7 | "src": "/favicon-16x16.png", 8 | "sizes": "16x16", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "/favicon-32x32.png", 13 | "sizes": "32x32", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "/apple-touch-icon.png", 18 | "sizes": "180x180", 19 | "type": "image/png" 20 | } 21 | ], 22 | "theme_color": "#ffffff", 23 | "background_color": "#ffffff", 24 | "display": "standalone", 25 | "start_url": "/" 26 | } 27 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | https://promptstack.dev/ 12 | 2025-01-31T18:08:01+00:00 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/ConvexClientProvider.tsx: -------------------------------------------------------------------------------- 1 | import { ConvexProvider, ConvexReactClient } from "convex/react"; 2 | 3 | const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL); 4 | 5 | export function ConvexClientProvider({ children }: { children: React.ReactNode }) { 6 | return {children}; 7 | } -------------------------------------------------------------------------------- /src/ThemeContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState, useEffect } from "react"; 2 | 3 | type Theme = "dark" | "light"; 4 | 5 | // Centralized background color - now using Tailwind class 6 | export const APP_BACKGROUND_COLOR = "bg-app-bg"; 7 | 8 | interface ThemeContextType { 9 | theme: Theme; 10 | toggleTheme: () => void; 11 | backgroundColor: string; 12 | } 13 | 14 | const ThemeContext = createContext(undefined); 15 | 16 | export function ThemeProvider({ children }: { children: React.ReactNode }) { 17 | const [theme, setTheme] = useState("light"); // Changed default to light 18 | 19 | useEffect(() => { 20 | const savedTheme = localStorage.getItem("theme") as Theme; 21 | if (savedTheme) { 22 | setTheme(savedTheme); 23 | } 24 | }, []); 25 | 26 | const toggleTheme = () => { 27 | const newTheme = theme === "dark" ? "light" : "dark"; 28 | setTheme(newTheme); 29 | localStorage.setItem("theme", newTheme); 30 | }; 31 | 32 | return ( 33 | 34 | {children} 35 | 36 | ); 37 | } 38 | 39 | export function useTheme() { 40 | const context = useContext(ThemeContext); 41 | if (context === undefined) { 42 | throw new Error("useTheme must be used within a ThemeProvider"); 43 | } 44 | return context; 45 | } 46 | -------------------------------------------------------------------------------- /src/components/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | import { IconCopy, IconCheck } from "@tabler/icons-react"; 4 | 5 | interface CodeBlockProps { 6 | code: string; 7 | language?: string; 8 | onCopy?: (text: string) => void; 9 | showLineNumbers?: boolean; 10 | } 11 | 12 | export function CodeBlock({ 13 | code, 14 | showLineNumbers = true, 15 | onCopy, 16 | }: CodeBlockProps) { 17 | const [copied, setCopied] = useState(false); 18 | 19 | const handleCopy = () => { 20 | if (onCopy) { 21 | onCopy(code); 22 | } else { 23 | navigator.clipboard.writeText(code); 24 | } 25 | setCopied(true); 26 | setTimeout(() => setCopied(false), 2000); 27 | }; 28 | 29 | const lines = code.split("\n"); 30 | 31 | return ( 32 | 38 |
39 |
40 |
41 |
42 |
43 |
44 | 60 |
61 | 62 |
63 |
64 |           {lines.map((line, i) => (
65 |             
66 | {showLineNumbers && ( 67 | 68 | {i + 1} 69 | 70 | )} 71 | 72 | {line} 73 | 74 |
75 | ))} 76 |
77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/components/CodeEditor.css: -------------------------------------------------------------------------------- 1 | .ProseMirror { 2 | @apply text-[#E2E2E2] text-sm leading-relaxed; 3 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", 4 | "Courier New", monospace; 5 | } 6 | 7 | .ProseMirror:focus { 8 | @apply outline-none; 9 | } 10 | 11 | .ProseMirror pre { 12 | @apply bg-transparent p-0 m-0; 13 | } 14 | 15 | .ProseMirror code { 16 | @apply bg-transparent p-0 text-inherit whitespace-pre-wrap break-words; 17 | } 18 | 19 | /* Syntax highlighting */ 20 | .hljs-comment, 21 | .hljs-quote { 22 | @apply text-gray-500 italic; 23 | } 24 | 25 | .hljs-keyword, 26 | .hljs-selector-tag, 27 | .hljs-subst { 28 | @apply text-purple-400; 29 | } 30 | 31 | .hljs-string, 32 | .hljs-regexp, 33 | .hljs-addition, 34 | .hljs-attribute { 35 | @apply text-green-400; 36 | } 37 | 38 | .hljs-number, 39 | .hljs-literal { 40 | @apply text-yellow-400; 41 | } 42 | 43 | .hljs-variable, 44 | .hljs-template-variable { 45 | @apply text-orange-400; 46 | } 47 | 48 | .hljs-function { 49 | @apply text-blue-400; 50 | } 51 | 52 | .hljs-title { 53 | @apply text-blue-300; 54 | } 55 | 56 | .hljs-params { 57 | @apply text-gray-300; 58 | } 59 | 60 | .hljs-built_in { 61 | @apply text-cyan-400; 62 | } 63 | -------------------------------------------------------------------------------- /src/components/CodeEditor.tsx: -------------------------------------------------------------------------------- 1 | import { useEditor, EditorContent } from "@tiptap/react"; 2 | import StarterKit from "@tiptap/starter-kit"; 3 | import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; 4 | import { common, createLowlight } from "lowlight"; 5 | import "./CodeEditor.css"; 6 | 7 | const lowlight = createLowlight(common); 8 | 9 | interface CodeEditorProps { 10 | content: string; 11 | onChange?: (content: string) => void; 12 | editable?: boolean; 13 | } 14 | 15 | export const CodeEditor = ({ content, onChange, editable = false }: CodeEditorProps) => { 16 | const editor = useEditor({ 17 | extensions: [ 18 | StarterKit.configure({ 19 | codeBlock: false, 20 | }), 21 | CodeBlockLowlight.configure({ 22 | lowlight, 23 | }), 24 | ], 25 | content: `
${content}
`, 26 | editable, 27 | onUpdate: ({ editor }) => { 28 | if (onChange) { 29 | const doc = editor.getHTML(); 30 | const codeContent = doc.replace(/<[^>]*>/g, ""); 31 | onChange(codeContent); 32 | } 33 | }, 34 | }); 35 | 36 | if (!editor) return null; 37 | 38 | return ( 39 |
40 | 41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/components/CommentSection.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useUser, SignInButton } from "@clerk/clerk-react"; 3 | import { useMutation, useQuery } from "convex/react"; 4 | import { api } from "../../convex/_generated/api"; 5 | import { MinimalTiptapEditor } from "./minimal-tiptap"; 6 | import { Content, generateHTML } from "@tiptap/react"; 7 | import StarterKit from "@tiptap/starter-kit"; 8 | import { MessageSquare, X, Trash2 } from "lucide-react"; 9 | import { Doc, Id } from "../../convex/_generated/dataModel"; 10 | 11 | interface CommentSectionProps { 12 | promptId: Id<"prompts">; 13 | } 14 | 15 | interface ThreadedComment { 16 | _id: Id<"comments">; 17 | content: string; 18 | userName: string; 19 | userId: string; 20 | createdAt: number; 21 | replies?: ThreadedComment[]; 22 | } 23 | 24 | export function CommentSection({ promptId }: CommentSectionProps) { 25 | const { user, isSignedIn } = useUser(); 26 | const [comment, setComment] = useState(); 27 | const [replyingTo, setReplyingTo] = useState | null>(null); 28 | const [replyContent, setReplyContent] = useState(); 29 | const comments = useQuery(api.comments.getComments, { promptId }); 30 | const addComment = useMutation(api.comments.addComment); 31 | const deleteComment = useMutation(api.comments.deleteComment); 32 | 33 | const renderComment = (content: string) => { 34 | try { 35 | const parsedContent = JSON.parse(content); 36 | return generateHTML(parsedContent, [StarterKit]); 37 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 38 | } catch (e) { 39 | return content; 40 | } 41 | }; 42 | 43 | const handleSubmit = async ( 44 | e: React.FormEvent, 45 | contentToSubmit?: Content 46 | ) => { 47 | e.preventDefault(); 48 | const contentToSend = contentToSubmit || comment; 49 | if (!contentToSend || !isSignedIn) return; 50 | 51 | try { 52 | await addComment({ 53 | promptId, 54 | content: JSON.stringify(contentToSend), 55 | userId: user.id, 56 | userName: user.fullName || user.username || "Anonymous", 57 | parentId: replyingTo || undefined, 58 | }); 59 | if (replyingTo) { 60 | setReplyContent(undefined); 61 | setReplyingTo(null); 62 | } else { 63 | setComment(undefined); 64 | } 65 | } catch (error) { 66 | console.error("Error adding comment:", error); 67 | } 68 | }; 69 | 70 | const handleDeleteComment = async (commentId: Id<"comments">) => { 71 | try { 72 | await deleteComment({ commentId }); 73 | } catch (error) { 74 | console.error("Failed to delete comment:", error); 75 | // Optionally add user-facing error handling here 76 | } 77 | }; 78 | 79 | const organizeComments = (comments: Doc<"comments">[]): ThreadedComment[] => { 80 | const commentMap = new Map(); 81 | const roots: ThreadedComment[] = []; 82 | 83 | comments?.forEach((comment) => { 84 | commentMap.set(comment._id, { ...comment, replies: [] }); 85 | }); 86 | 87 | comments?.forEach((comment) => { 88 | if (comment.parentId) { 89 | const parent = commentMap.get(comment.parentId); 90 | if (parent) { 91 | parent.replies.push(commentMap.get(comment._id)); 92 | } 93 | } else { 94 | roots.push(commentMap.get(comment._id)); 95 | } 96 | }); 97 | 98 | return roots; 99 | }; 100 | 101 | const CommentThread = ({ 102 | comment, 103 | depth = 0, 104 | }: { 105 | comment: ThreadedComment; 106 | depth?: number; 107 | }) => ( 108 |
0 ? "ml-8" : ""}`} 110 | > 111 |
112 |
113 | {comment.userName} 114 | 115 | {new Date(comment.createdAt).toLocaleDateString()} 116 | 117 |
118 | {isSignedIn && user.id === comment.userId && ( 119 | 125 | )} 126 |
127 |
133 | {isSignedIn && ( 134 | 141 | )} 142 | {replyingTo === comment._id && ( 143 |
144 |
145 | 146 | Replying to {comment.userName} 147 | 148 | 157 |
158 | 162 | 169 |
170 | )} 171 | {comment.replies?.map((reply) => ( 172 | 173 | ))} 174 |
175 | ); 176 | 177 | const threadedComments = organizeComments(comments || []); 178 | 179 | return ( 180 |
181 |

Comment

182 | 183 |
184 | {}} 187 | disabled={!isSignedIn} 188 | /> 189 | 190 | {isSignedIn ? ( 191 | 198 | ) : ( 199 | 200 | 203 | 204 | )} 205 |
206 | 207 |
208 | {threadedComments.map((comment) => ( 209 | 210 | ))} 211 |
212 |
213 | ); 214 | } 215 | -------------------------------------------------------------------------------- /src/components/ConvexIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ConvexIcon({ className }: { className?: string }) { 2 | return ( 3 | 10 | 14 | 18 | 22 | 23 | ); 24 | } -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "@tanstack/react-router"; 3 | import { motion } from "framer-motion"; 4 | import { useTheme, APP_BACKGROUND_COLOR } from "../ThemeContext"; 5 | import { useRouter } from "@tanstack/react-router"; 6 | import { X } from "lucide-react"; 7 | import { ConvexIcon } from "./ConvexIcon"; 8 | 9 | interface FooterProps { 10 | count?: number; 11 | } 12 | 13 | function Counter({ value }: { value: number }) { 14 | return ( 15 |
18 | + 19 |
{value}
20 |
21 | ); 22 | } 23 | 24 | export function Footer({ count = 0 }: FooterProps) { 25 | const { theme } = useTheme(); 26 | const router = useRouter(); 27 | const isHomePage = router.history.location.pathname === "/"; 28 | const [showPopup, setShowPopup] = React.useState(true); 29 | 30 | const bgColor = APP_BACKGROUND_COLOR; 31 | const textColor = theme === "dark" ? "text-white" : "text-black"; 32 | const mutedTextColor = theme === "dark" ? "text-[#A3A3A3]" : "text-gray-500"; 33 | 34 | return ( 35 | <> 36 | 164 | 165 | {/* {showPopup && ( 166 |
167 | 172 | Powered by 173 | 174 | convex 175 | 176 | 181 |
182 | )} */} 183 | 184 | ); 185 | } 186 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Link, useRouter } from "@tanstack/react-router"; 3 | import { Search, Plus, Github, Menu, X } from "lucide-react"; 4 | import { useTheme, APP_BACKGROUND_COLOR } from "../ThemeContext"; 5 | import { SignInButton, SignUpButton, UserButton, useUser } from "@clerk/clerk-react"; 6 | 7 | interface HeaderProps { 8 | searchQuery?: string; 9 | setSearchQuery?: (query: string) => void; 10 | setIsModalOpen?: (isOpen: boolean) => void; 11 | setIsSignInOpen?: (isOpen: boolean) => void; 12 | } 13 | 14 | const PromptStackLogo = ({ className }: { className?: string }) => ( 15 | PromptStack Logo 22 | ); 23 | 24 | export function Header({ searchQuery, setSearchQuery, setIsModalOpen }: HeaderProps) { 25 | const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); 26 | const { theme } = useTheme(); 27 | const { isSignedIn } = useUser(); 28 | const router = useRouter(); 29 | const currentPath = router.state.location.pathname; 30 | const showSearch = currentPath === "/"; 31 | const useModal = currentPath === "/"; 32 | 33 | const bgColor = APP_BACKGROUND_COLOR; 34 | const textColor = theme === "dark" ? "text-white" : "text-black"; 35 | const mutedTextColor = theme === "dark" ? "text-[#A3A3A3]" : "text-gray-500"; 36 | const borderColor = theme === "dark" ? "border-[#FAF2E9]" : "border-gray-200"; 37 | 38 | const cn = (...classes: (string | boolean | undefined)[]) => { 39 | return classes.filter(Boolean).join(" "); 40 | }; 41 | 42 | return ( 43 |
44 |
45 |
46 | 61 | 62 | {/* Desktop Navigation */} 63 |
64 | {setSearchQuery && showSearch && ( 65 |
66 | 73 | setSearchQuery(e.target.value)} 78 | className={cn( 79 | bgColor, 80 | "border", 81 | borderColor, 82 | textColor, 83 | "w-full pl-9 pr-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-black focus:border-black transition-all duration-200 placeholder-[#525252] rounded-lg" 84 | )} 85 | /> 86 |
87 | )} 88 | 89 |
90 | {useModal ? ( 91 | 99 | ) : ( 100 | 105 | 106 | Add Prompt 107 | 108 | )} 109 | 110 | {isSignedIn ? ( 111 |
112 | 113 |
114 | ) : ( 115 |
116 | 117 | 120 | 121 | 122 | 125 | 126 |
127 | )} 128 |
129 |
130 | 131 | {/* Mobile Menu Button */} 132 | 133 | PromptStack 134 | 135 | 138 |
139 | 140 | {/* Mobile Menu */} 141 | {isMobileMenuOpen && ( 142 |
143 |
144 | {setSearchQuery && showSearch && ( 145 |
146 | 153 | setSearchQuery(e.target.value)} 158 | className={cn( 159 | bgColor, 160 | "border", 161 | borderColor, 162 | textColor, 163 | "w-full pl-9 pr-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-black focus:border-black transition-all duration-200 placeholder-[#525252] rounded-lg" 164 | )} 165 | /> 166 |
167 | )} 168 |
169 | 175 | About 176 | 177 | 185 | 186 | open source 187 | 188 | {useModal ? ( 189 | 197 | ) : ( 198 | 203 | 204 | Add Prompt 205 | 206 | )} 207 | {isSignedIn ? ( 208 |
209 | 210 |
211 | ) : ( 212 |
213 | 214 | 217 | 218 | 219 | 222 | 223 |
224 | )} 225 |
226 |
227 |
228 | )} 229 |
230 |
231 | ); 232 | } 233 | -------------------------------------------------------------------------------- /src/components/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@tanstack/react-router"; 2 | import { useTheme } from "../ThemeContext"; 3 | 4 | export function NotFound() { 5 | const { theme } = useTheme(); 6 | 7 | const bgColor = theme === "dark" ? "bg-[#0A0A0A]" : "bg-white"; 8 | const textColor = theme === "dark" ? "text-white" : "text-black"; 9 | const mutedTextColor = theme === "dark" ? "text-[#A3A3A3]" : "text-gray-500"; 10 | const borderColor = theme === "dark" ? "border-[#1F1F1F]" : "border-gray-200"; 11 | 12 | const cn = (...classes: (string | boolean | undefined)[]) => { 13 | return classes.filter(Boolean).join(" "); 14 | }; 15 | 16 | return ( 17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
31 |
32 |

404

33 |

Prompt Not Found

34 |

35 | The prompt you're looking for doesn't exist or you don't have permission to view it. 36 |

37 |
38 | 39 |
40 | 43 | Go Home 44 | 45 |
46 |
47 |
48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/components/minimal-tiptap.tsx: -------------------------------------------------------------------------------- 1 | import { useEditor, EditorContent, type Content } from "@tiptap/react"; 2 | import StarterKit from "@tiptap/starter-kit"; 3 | import { Toggle } from "./ui/toggle"; 4 | import { Bold, Italic, List, ListOrdered } from "lucide-react"; 5 | import Placeholder from "@tiptap/extension-placeholder"; 6 | import React from "react"; 7 | 8 | export function MinimalTiptapEditor({ 9 | content, 10 | onChange, 11 | disabled = false, 12 | }: { 13 | content: Content | undefined; 14 | onChange: (content: Content) => void; 15 | disabled?: boolean; 16 | }) { 17 | const editor = useEditor({ 18 | extensions: [ 19 | StarterKit, 20 | Placeholder.configure({ 21 | placeholder: "What do you think?", 22 | emptyEditorClass: "is-editor-empty", 23 | }), 24 | ], 25 | content, 26 | editorProps: { 27 | attributes: { 28 | class: 29 | "min-h-[150px] w-[100%] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#F9F0E7] prose prose-sm", 30 | }, 31 | }, 32 | onUpdate: ({ editor }) => { 33 | onChange(editor.getJSON()); 34 | }, 35 | editable: !disabled, 36 | autofocus: false, 37 | onCreate: ({ editor }) => { 38 | editor.commands.focus("end"); 39 | }, 40 | }); 41 | 42 | React.useEffect(() => { 43 | if (content === undefined && editor) { 44 | editor.commands.clearContent(); 45 | } 46 | }, [content, editor]); 47 | 48 | return ( 49 |
50 | 62 |
63 | editor?.chain().focus().toggleBold().run()}> 68 | 69 | 70 | editor?.chain().focus().toggleItalic().run()}> 75 | 76 | 77 | editor?.chain().focus().toggleBulletList().run()}> 82 | 83 | 84 | editor?.chain().focus().toggleOrderedList().run()}> 89 | 90 | 91 |
92 | 93 |
94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SwitchPrimitives from "@radix-ui/react-switch"; 5 | import { cn } from "../../lib/utils"; 6 | 7 | const Switch = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 18 | 23 | 24 | )); 25 | Switch.displayName = SwitchPrimitives.Root.displayName; 26 | 27 | export { Switch }; 28 | -------------------------------------------------------------------------------- /src/components/ui/toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as TogglePrimitive from "@radix-ui/react-toggle"; 5 | import { cva, type VariantProps } from "class-variance-authority"; 6 | import { cn } from "../../lib/utils"; 7 | 8 | const toggleVariants = cva( 9 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground", 10 | { 11 | variants: { 12 | variant: { 13 | default: "bg-transparent", 14 | outline: 15 | "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground", 16 | }, 17 | size: { 18 | default: "h-10 px-3", 19 | sm: "h-9 px-2.5", 20 | lg: "h-11 px-5", 21 | }, 22 | }, 23 | defaultVariants: { 24 | variant: "default", 25 | size: "default", 26 | }, 27 | } 28 | ); 29 | 30 | const Toggle = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef & 33 | VariantProps 34 | >(({ className, variant, size, ...props }, ref) => ( 35 | 40 | )); 41 | 42 | Toggle.displayName = TogglePrimitive.Root.displayName; 43 | 44 | export { Toggle, toggleVariants }; -------------------------------------------------------------------------------- /src/constants/categories.ts: -------------------------------------------------------------------------------- 1 | export const CATEGORIES = [ 2 | ".cursorrules", 3 | // "Angular", 4 | "Anthropic(Claude)", 5 | // "AutoHotkey", 6 | // "Backend", 7 | "Bolt.new", 8 | // "C#", 9 | // "C++", 10 | "ChatGPT", 11 | "Chef", 12 | // "Codeium", 13 | "Convex", 14 | // "Creatr", 15 | "Cursor", 16 | // "Database", 17 | // "Deepseek", 18 | "Devin", 19 | // "Django", 20 | // "Expo", 21 | // "Express.js", 22 | // "Flutter", 23 | // "Functional", 24 | "Github Gopilot", 25 | // "Go", 26 | // "Guidelines Doc", 27 | // "HTMX", 28 | //"JavaScript", 29 | //"Jest", 30 | // "Laravel", 31 | "Loveable", 32 | // "MagicUI", 33 | // "MCP", 34 | "NextJS", 35 | // "Novo Elements", 36 | // "NuxtJS", 37 | "Openai", 38 | "Other", 39 | "Perplexity", 40 | "Prompt", 41 | // "Python", 42 | // "Radix UI", 43 | "React", 44 | "Readme", 45 | // "Replit", 46 | // "Requirements Doc", 47 | // "Ruby on Rails", 48 | // "Rust", 49 | // "Shadcn UI", 50 | // "ShipFast", 51 | // "Solidity", 52 | // "Structure Doc", 53 | // "Supabase", 54 | // "SvelteKit", 55 | // "SwiftUI", 56 | // "TabNine", 57 | // "Tailwind", 58 | // "TanStack", 59 | // "trae", 60 | // "Trickle", 61 | "Typescript", 62 | "v0", 63 | //"Vue", 64 | //"Wails.io", 65 | "Windsurf", 66 | "YouTube", 67 | ] as const; 68 | -------------------------------------------------------------------------------- /src/fonts.css: -------------------------------------------------------------------------------- 1 | /* Import Inter from Google Fonts */ 2 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"); 3 | 4 | :root { 5 | font-family: "Inter", sans-serif; 6 | } 7 | 8 | @supports (font-variation-settings: normal) { 9 | :root { 10 | font-family: "Inter var", sans-serif; 11 | } 12 | } 13 | 14 | .font-inter { 15 | font-family: "Inter", sans-serif; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | font-size: 18px; /* Adjust this value to set base font size */ 7 | } 8 | 9 | /* Optional: Add custom text size classes */ 10 | .text-2xl { 11 | font-size: 1.5rem; 12 | line-height: 2rem; 13 | } 14 | 15 | @layer base { 16 | @font-face { 17 | font-family: "Satoshi"; 18 | src: url("https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700&display=swap") 19 | format("woff2"); 20 | font-weight: 400 700; 21 | font-style: normal; 22 | font-display: swap; 23 | } 24 | } 25 | 26 | @keyframes float { 27 | 0%, 28 | 100% { 29 | transform: translateY(0); 30 | } 31 | 50% { 32 | transform: translateY(-10px); 33 | } 34 | } 35 | 36 | .animate-float { 37 | animation: float 3s ease-in-out infinite; 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | 3 | export const promptSchema = z.object({ 4 | title: z.string(), 5 | description: z.string(), 6 | prompt: z.string(), 7 | categories: z.array(z.string()), 8 | stars: z.number().min(0).max(5), 9 | githubProfile: z.string().optional(), 10 | }) 11 | 12 | export type Prompt = z.infer -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { RouterProvider, createRouter } from "@tanstack/react-router"; 4 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 5 | import { ConvexProviderWithClerk } from "convex/react-clerk"; 6 | import { ThemeProvider } from "./ThemeContext"; 7 | import { ClerkProvider, useAuth } from "@clerk/clerk-react"; 8 | import { ConvexReactClient } from "convex/react"; 9 | 10 | // Import the generated route tree 11 | import { routeTree } from "./routeTree.gen"; 12 | 13 | import "./index.css"; 14 | 15 | const CLERK_PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; 16 | const CONVEX_URL = import.meta.env.VITE_CONVEX_URL; 17 | 18 | if (!CLERK_PUBLISHABLE_KEY) { 19 | throw new Error("Missing Clerk Publishable Key"); 20 | } 21 | 22 | if (!CONVEX_URL) { 23 | throw new Error("Missing Convex URL"); 24 | } 25 | 26 | const convex = new ConvexReactClient(CONVEX_URL); 27 | 28 | // Create a new router instance 29 | const router = createRouter({ routeTree }); 30 | 31 | // Create a new query client instance 32 | const queryClient = new QueryClient(); 33 | 34 | // Register the router instance for type safety 35 | declare module "@tanstack/react-router" { 36 | interface Register { 37 | router: typeof router; 38 | } 39 | } 40 | 41 | // Create root element 42 | const rootElement = document.getElementById("root")!; 43 | if (!rootElement.innerHTML) { 44 | const root = createRoot(rootElement); 45 | root.render( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/routeTree.gen.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // @ts-nocheck 4 | 5 | // noinspection JSUnusedGlobalSymbols 6 | 7 | // This file was automatically generated by TanStack Router. 8 | // You should NOT make any changes in this file as it will be overwritten. 9 | // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 | 11 | // Import Routes 12 | 13 | import { Route as rootRoute } from './routes/__root' 14 | import { Route as PromptGuideImport } from './routes/prompt-guide' 15 | import { Route as DocsImport } from './routes/docs' 16 | import { Route as AddnewImport } from './routes/addnew' 17 | import { Route as AboutImport } from './routes/about' 18 | import { Route as R404Import } from './routes/404' 19 | import { Route as IndexImport } from './routes/index' 20 | import { Route as PromptSlugImport } from './routes/prompt.$slug' 21 | 22 | // Create/Update Routes 23 | 24 | const PromptGuideRoute = PromptGuideImport.update({ 25 | id: '/prompt-guide', 26 | path: '/prompt-guide', 27 | getParentRoute: () => rootRoute, 28 | } as any) 29 | 30 | const DocsRoute = DocsImport.update({ 31 | id: '/docs', 32 | path: '/docs', 33 | getParentRoute: () => rootRoute, 34 | } as any) 35 | 36 | const AddnewRoute = AddnewImport.update({ 37 | id: '/addnew', 38 | path: '/addnew', 39 | getParentRoute: () => rootRoute, 40 | } as any) 41 | 42 | const AboutRoute = AboutImport.update({ 43 | id: '/about', 44 | path: '/about', 45 | getParentRoute: () => rootRoute, 46 | } as any) 47 | 48 | const R404Route = R404Import.update({ 49 | id: '/404', 50 | path: '/404', 51 | getParentRoute: () => rootRoute, 52 | } as any) 53 | 54 | const IndexRoute = IndexImport.update({ 55 | id: '/', 56 | path: '/', 57 | getParentRoute: () => rootRoute, 58 | } as any) 59 | 60 | const PromptSlugRoute = PromptSlugImport.update({ 61 | id: '/prompt/$slug', 62 | path: '/prompt/$slug', 63 | getParentRoute: () => rootRoute, 64 | } as any) 65 | 66 | // Populate the FileRoutesByPath interface 67 | 68 | declare module '@tanstack/react-router' { 69 | interface FileRoutesByPath { 70 | '/': { 71 | id: '/' 72 | path: '/' 73 | fullPath: '/' 74 | preLoaderRoute: typeof IndexImport 75 | parentRoute: typeof rootRoute 76 | } 77 | '/404': { 78 | id: '/404' 79 | path: '/404' 80 | fullPath: '/404' 81 | preLoaderRoute: typeof R404Import 82 | parentRoute: typeof rootRoute 83 | } 84 | '/about': { 85 | id: '/about' 86 | path: '/about' 87 | fullPath: '/about' 88 | preLoaderRoute: typeof AboutImport 89 | parentRoute: typeof rootRoute 90 | } 91 | '/addnew': { 92 | id: '/addnew' 93 | path: '/addnew' 94 | fullPath: '/addnew' 95 | preLoaderRoute: typeof AddnewImport 96 | parentRoute: typeof rootRoute 97 | } 98 | '/docs': { 99 | id: '/docs' 100 | path: '/docs' 101 | fullPath: '/docs' 102 | preLoaderRoute: typeof DocsImport 103 | parentRoute: typeof rootRoute 104 | } 105 | '/prompt-guide': { 106 | id: '/prompt-guide' 107 | path: '/prompt-guide' 108 | fullPath: '/prompt-guide' 109 | preLoaderRoute: typeof PromptGuideImport 110 | parentRoute: typeof rootRoute 111 | } 112 | '/prompt/$slug': { 113 | id: '/prompt/$slug' 114 | path: '/prompt/$slug' 115 | fullPath: '/prompt/$slug' 116 | preLoaderRoute: typeof PromptSlugImport 117 | parentRoute: typeof rootRoute 118 | } 119 | } 120 | } 121 | 122 | // Create and export the route tree 123 | 124 | export interface FileRoutesByFullPath { 125 | '/': typeof IndexRoute 126 | '/404': typeof R404Route 127 | '/about': typeof AboutRoute 128 | '/addnew': typeof AddnewRoute 129 | '/docs': typeof DocsRoute 130 | '/prompt-guide': typeof PromptGuideRoute 131 | '/prompt/$slug': typeof PromptSlugRoute 132 | } 133 | 134 | export interface FileRoutesByTo { 135 | '/': typeof IndexRoute 136 | '/404': typeof R404Route 137 | '/about': typeof AboutRoute 138 | '/addnew': typeof AddnewRoute 139 | '/docs': typeof DocsRoute 140 | '/prompt-guide': typeof PromptGuideRoute 141 | '/prompt/$slug': typeof PromptSlugRoute 142 | } 143 | 144 | export interface FileRoutesById { 145 | __root__: typeof rootRoute 146 | '/': typeof IndexRoute 147 | '/404': typeof R404Route 148 | '/about': typeof AboutRoute 149 | '/addnew': typeof AddnewRoute 150 | '/docs': typeof DocsRoute 151 | '/prompt-guide': typeof PromptGuideRoute 152 | '/prompt/$slug': typeof PromptSlugRoute 153 | } 154 | 155 | export interface FileRouteTypes { 156 | fileRoutesByFullPath: FileRoutesByFullPath 157 | fullPaths: 158 | | '/' 159 | | '/404' 160 | | '/about' 161 | | '/addnew' 162 | | '/docs' 163 | | '/prompt-guide' 164 | | '/prompt/$slug' 165 | fileRoutesByTo: FileRoutesByTo 166 | to: 167 | | '/' 168 | | '/404' 169 | | '/about' 170 | | '/addnew' 171 | | '/docs' 172 | | '/prompt-guide' 173 | | '/prompt/$slug' 174 | id: 175 | | '__root__' 176 | | '/' 177 | | '/404' 178 | | '/about' 179 | | '/addnew' 180 | | '/docs' 181 | | '/prompt-guide' 182 | | '/prompt/$slug' 183 | fileRoutesById: FileRoutesById 184 | } 185 | 186 | export interface RootRouteChildren { 187 | IndexRoute: typeof IndexRoute 188 | R404Route: typeof R404Route 189 | AboutRoute: typeof AboutRoute 190 | AddnewRoute: typeof AddnewRoute 191 | DocsRoute: typeof DocsRoute 192 | PromptGuideRoute: typeof PromptGuideRoute 193 | PromptSlugRoute: typeof PromptSlugRoute 194 | } 195 | 196 | const rootRouteChildren: RootRouteChildren = { 197 | IndexRoute: IndexRoute, 198 | R404Route: R404Route, 199 | AboutRoute: AboutRoute, 200 | AddnewRoute: AddnewRoute, 201 | DocsRoute: DocsRoute, 202 | PromptGuideRoute: PromptGuideRoute, 203 | PromptSlugRoute: PromptSlugRoute, 204 | } 205 | 206 | export const routeTree = rootRoute 207 | ._addFileChildren(rootRouteChildren) 208 | ._addFileTypes() 209 | 210 | /* ROUTE_MANIFEST_START 211 | { 212 | "routes": { 213 | "__root__": { 214 | "filePath": "__root.tsx", 215 | "children": [ 216 | "/", 217 | "/404", 218 | "/about", 219 | "/addnew", 220 | "/docs", 221 | "/prompt-guide", 222 | "/prompt/$slug" 223 | ] 224 | }, 225 | "/": { 226 | "filePath": "index.tsx" 227 | }, 228 | "/404": { 229 | "filePath": "404.tsx" 230 | }, 231 | "/about": { 232 | "filePath": "about.tsx" 233 | }, 234 | "/addnew": { 235 | "filePath": "addnew.tsx" 236 | }, 237 | "/docs": { 238 | "filePath": "docs.tsx" 239 | }, 240 | "/prompt-guide": { 241 | "filePath": "prompt-guide.tsx" 242 | }, 243 | "/prompt/$slug": { 244 | "filePath": "prompt.$slug.tsx" 245 | } 246 | } 247 | } 248 | ROUTE_MANIFEST_END */ 249 | -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter } from "@tanstack/react-router"; 2 | import { routeTree } from "./routeTree.gen"; 3 | 4 | declare module "@tanstack/react-router" { 5 | interface Register { 6 | router: typeof router; 7 | } 8 | } 9 | 10 | export const router = createRouter({ 11 | routeTree, 12 | defaultPreload: "intent", 13 | }); 14 | -------------------------------------------------------------------------------- /src/routes/404.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate, createFileRoute } from "@tanstack/react-router"; 2 | 3 | import { Header } from "../components/Header"; 4 | import { Footer } from "../components/Footer"; 5 | 6 | // Keep this named export for route tree 7 | export const Route = createFileRoute("/404")({ 8 | component: NotFoundPage, 9 | }); 10 | 11 | function NotFoundPage() { 12 | const navigate = useNavigate(); 13 | 14 | return ( 15 |
16 |
17 |
18 |
19 | {/* Main Content */} 20 |
21 | {/* Logo instead of Ghost */} 22 |
23 | PromptStack Logo 30 |
31 | 32 | {/* Error Message */} 33 |

404

34 |

35 | Looks like you've found a page that doesn't exist 36 |

37 |

38 | Don't worry, even the best explorers sometimes wander into uncharted 39 | territory. 40 |

41 | 42 | {/* Action Buttons */} 43 |
44 | 50 |
51 |
52 |
53 |
54 |
55 | ); 56 | } 57 | 58 | export default NotFoundPage; 59 | -------------------------------------------------------------------------------- /src/routes/__root.tsx: -------------------------------------------------------------------------------- 1 | import { createRootRoute, Outlet } from "@tanstack/react-router"; 2 | import { ThemeProvider } from "../ThemeContext"; 3 | import NotFoundPage from "./404"; 4 | 5 | export const Route = createRootRoute({ 6 | component: () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }, 13 | notFoundComponent: NotFoundPage, 14 | }); 15 | 16 | export const rootRoute = Route; 17 | -------------------------------------------------------------------------------- /src/routes/about.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { useTheme } from "../ThemeContext"; 3 | import { Header } from "../components/Header"; 4 | import { Footer } from "../components/Footer"; 5 | 6 | export const Route = createFileRoute("/about")({ 7 | component: About, 8 | }); 9 | 10 | function About() { 11 | const { theme } = useTheme(); 12 | 13 | const bgColor = 14 | theme === "dark" 15 | ? "bg-[#0A0A0A]" 16 | : "bg-gradient-to-b from-[#FBFBFB] to-[#FBFBFB]"; 17 | const textColor = theme === "dark" ? "text-white" : "text-black"; 18 | const mutedTextColor = theme === "dark" ? "text-[#A3A3A3]" : "text-gray-500"; 19 | 20 | return ( 21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 |

PromptStack

31 | 32 |

33 | 36 | PromptStack 37 | {" "} 38 | is an open-source platform for developers to share, submit, and discuss AI prompts 39 | and code generation rules. It provides a space to collaborate on prompts and rules 40 | for tools like Cursor, Bolt.new, Loveable, Windsurf, Trae, Creatr, and Convex, 41 | helping developers streamline their workflows when building full-stack applications. 42 |

43 | 44 |

45 | It's an open-source platform for discovering and sharing AI prompts, built with{" "} 46 | 49 | Convex.dev 50 | {" "} 51 | as the database and{" "} 52 | 55 | TanStack Router 56 | {" "} 57 | for client-side routing. 58 |

59 | 60 |

Features

61 |
    62 |
  • 63 | Searchable Directory: Quickly find AI prompts and code-gen rules. 64 |
  • 65 |
  • 66 | Prompt Love: Like and sort top prompts in the directory. 67 |
  • 68 |
  • 69 | Category Organization: Prompts are organized into clear, functional categories. 70 |
  • 71 |
  • 72 | GitHub Integration: Automatically link GitHub or social profiles submitted with 73 | prompts. 74 |
  • 75 |
  • 76 | Carbon Copy View: View and copy prompts in a Carbon-style editor window. 77 |
  • 78 |
  • 79 | .Cursorrules: Find and submit Cursor rules for AI and code-gen projects. 80 |
  • 81 |
  • 82 | Prompt Link Sharing: Easily share prompts or cursor rules with others. 83 |
  • 84 |
85 | 86 |

Tech Stack

87 |
    88 |
  • Next.js - React framework for production
  • 89 |
  • 90 | React - JavaScript library for building user interfaces 91 |
  • 92 |
  • TypeScript - JavaScript with syntax for types
  • 93 |
  • Tailwind CSS - CSS framework
  • 94 |
  • 95 | 98 | Convex 99 | {" "} 100 | - Backend development platform 101 |
  • 102 |
  • Clerk - Authentication and user management
  • 103 |
  • Bun - JavaScript runtime & package manager
  • 104 |
105 |
106 |
107 |
108 |
109 | 110 |
111 |
112 | ); 113 | } 114 | 115 | export default About; 116 | -------------------------------------------------------------------------------- /src/routes/addnew.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { useTheme } from "../ThemeContext"; 3 | import { useNavigate } from "@tanstack/react-router"; 4 | import React from "react"; 5 | import { Header } from "../components/Header"; 6 | import { Footer } from "../components/Footer"; 7 | import { PromptForm } from "../components/PromptForm"; 8 | 9 | export const Route = createFileRoute("/addnew")({ 10 | component: AddNew, 11 | }); 12 | 13 | function AddNew() { 14 | const { theme } = useTheme(); 15 | const navigate = useNavigate(); 16 | 17 | const bgColor = 18 | theme === "dark" ? "bg-[#0A0A0A]" : "bg-gradient-to-b from-[#FBFBFB] to-[#FFFFFF]"; 19 | 20 | const handleSuccess = (promptId: string, slug: string) => { 21 | navigate({ to: "/prompt/$slug", params: { slug } }); 22 | }; 23 | 24 | return ( 25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 |
37 | ); 38 | } 39 | 40 | export default AddNew; 41 | -------------------------------------------------------------------------------- /src/routes/docs.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { useTheme } from "../ThemeContext"; 3 | import { Command, ArrowLeft, Sun, Moon } from "lucide-react"; 4 | import { Link } from "@tanstack/react-router"; 5 | 6 | export const Route = createFileRoute("/docs")({ 7 | component: About, 8 | }); 9 | 10 | function About() { 11 | const { theme, toggleTheme } = useTheme(); 12 | 13 | const bgColor = theme === "dark" ? "bg-[#0A0A0A]" : "bg-white"; 14 | const textColor = theme === "dark" ? "text-white" : "text-black"; 15 | const mutedTextColor = theme === "dark" ? "text-[#A3A3A3]" : "text-gray-500"; 16 | const borderColor = theme === "dark" ? "border-[#1F1F1F]" : "border-gray-200"; 17 | const buttonBgColor = theme === "dark" ? "bg-[#222222]" : "bg-gray-100"; 18 | const buttonHoverBgColor = theme === "dark" ? "hover:bg-[#333333]" : "hover:bg-gray-200"; 19 | 20 | return ( 21 |
22 |
23 |
24 |
25 |
26 | 29 | 30 | 31 | 32 |

About

33 |
34 |
35 | 39 | 40 | Prompt Guide 41 | 42 | {/* 48 | */} 54 | 60 |
61 |
62 |
63 |
64 | 65 |
66 |
67 |
68 |

About AI Prompts Directory

69 |

70 | Welcome to the AI Prompts Directory. This guide will help you understand how to use 71 | and contribute to our platform effectively. 72 |

73 | 74 |

What is AI Prompts Directory?

75 |

76 | AI Prompts Directory is a community-driven platform for sharing and discovering 77 | effective prompts for various AI models and tools. Whether you're working with 78 | ChatGPT, GitHub Copilot, or other AI assistants, you'll find prompts that help you get 79 | the most out of these tools. 80 |

81 | 82 |

Features

83 |
    84 |
  • Browse and search through a curated collection of AI prompts
  • 85 |
  • Filter prompts by category and rating
  • 86 |
  • Share your own prompts with the community
  • 87 |
  • Rate and favorite prompts to help others find the best content
  • 88 |
89 | 90 |

How to Use

91 |

Browsing Prompts

92 |

You can browse prompts by:

93 |
    94 |
  • Using the search bar to find specific topics or keywords
  • 95 |
  • Filtering by categories in the sidebar
  • 96 |
  • Sorting by star rating
  • 97 |
98 | 99 |

Contributing

100 |

To contribute your own prompts:

101 |
    102 |
  1. Sign up for an account
  2. 103 |
  3. Click the "Add Prompt" button
  4. 104 |
  5. Fill in the prompt details and categories
  6. 105 |
  7. Submit your prompt for others to use
  8. 106 |
107 | 108 |

Best Practices

109 |

When creating prompts:

110 |
    111 |
  • Be clear and concise in your descriptions
  • 112 |
  • Include example use cases
  • 113 |
  • Specify which AI tools the prompt works best with
  • 114 |
  • Use appropriate categories to help others find your prompts
  • 115 |
116 |
117 |
118 |
119 | 120 | 227 |
228 | ); 229 | } 230 | -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from '@tanstack/react-router' 2 | import App from '../App' 3 | 4 | export const Route = createFileRoute('/')({ 5 | component: App, 6 | }) -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import defaultTheme from "tailwindcss/defaultTheme"; 3 | 4 | export default { 5 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 6 | theme: { 7 | extend: { 8 | colors: { 9 | "app-bg": "#F9EEE6", 10 | }, 11 | fontSize: { 12 | sm: "0.80rem", 13 | }, 14 | screens: { 15 | xs: "475px", 16 | sm: "640px", 17 | md: "768px", 18 | lg: "1024px", 19 | xl: "1280px", 20 | "2xl": "1536px", 21 | }, 22 | fontFamily: { 23 | sans: ["Inter var", ...defaultTheme.fontFamily.sans], 24 | }, 25 | typography: { 26 | DEFAULT: { 27 | css: { 28 | pre: { 29 | padding: "0", 30 | margin: "0", 31 | backgroundColor: "transparent", 32 | }, 33 | code: { 34 | backgroundColor: "transparent", 35 | padding: "0", 36 | fontWeight: "400", 37 | color: "inherit", 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, 44 | plugins: [require("@tailwindcss/typography")], 45 | }; 46 | -------------------------------------------------------------------------------- /tsconfig.app.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 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.app.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"root":["./src/app.tsx","./src/convexclientprovider.tsx","./src/themecontext.tsx","./src/main.tsx","./src/routetree.gen.ts","./src/router.ts","./src/vite-env.d.ts","./src/components/codeblock.tsx","./src/components/codeeditor.tsx","./src/components/commentsection.tsx","./src/components/convexicon.tsx","./src/components/footer.tsx","./src/components/header.tsx","./src/components/minimal-tiptap.tsx","./src/components/ui/switch.tsx","./src/components/ui/toggle.tsx","./src/lib/types.ts","./src/lib/utils.ts","./src/routes/404.tsx","./src/routes/__root.tsx","./src/routes/about.tsx","./src/routes/addnew.tsx","./src/routes/docs.tsx","./src/routes/index.tsx","./src/routes/prompt-guide.tsx","./src/routes/prompt.$slug.tsx"],"version":"5.7.3"} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.node.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"root":["./vite.config.ts"],"version":"5.7.3"} -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import { TanStackRouterVite } from '@tanstack/router-vite-plugin' 4 | 5 | export default defineConfig({ 6 | plugins: [react(), TanStackRouterVite()], 7 | build: { 8 | rollupOptions: { 9 | external: ['@swc/core'] 10 | } 11 | } 12 | }) --------------------------------------------------------------------------------