├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── LICENCE.md ├── README.md ├── apps ├── docs │ ├── .eslintrc.js │ ├── README.md │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ └── app │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── tailwind.config.js │ └── tsconfig.json └── web │ ├── .gitignore │ ├── README.md │ ├── drizzle.config.ts │ ├── drizzle │ ├── 0000_mushy_zarek.sql │ ├── meta │ │ ├── 0000_snapshot.json │ │ └── _journal.json │ ├── relations.ts │ └── schema.ts │ ├── env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── next.svg │ └── vercel.svg │ ├── src │ ├── app │ │ ├── api │ │ │ └── hello │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── not-found.tsx │ │ └── page.tsx │ └── db │ │ ├── index.ts │ │ └── seed.sql │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── wrangler.toml ├── package-lock.json ├── package.json ├── packages ├── config │ ├── .eslintrc.js │ ├── eslint-preset.js │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── tailwind.config.js │ └── vitest.shared.config.js ├── shared-lib │ ├── index.ts │ ├── package.json │ ├── tsconfig.json │ └── utils │ │ └── index.ts ├── tsconfig │ ├── README.md │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── ui │ ├── .eslintrc.js │ ├── components.json │ ├── index.ts │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── components │ │ └── ui │ │ │ ├── alert-dialog.tsx │ │ │ ├── button.tsx │ │ │ ├── index.ts │ │ │ ├── theme-mode-toggle.tsx │ │ │ ├── theme-provider.tsx │ │ │ ├── ui-button.tsx │ │ │ └── ui-dropdown-menu.tsx │ ├── globals.css │ └── lib │ │ └── utils.ts │ ├── tailwind.config.ts │ └── tsconfig.json ├── turbo.json └── vitest.workspaces.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .pnp 3 | .pnp.js 4 | 5 | # testing 6 | coverage 7 | 8 | # next.js 9 | .next/ 10 | out/ 11 | build 12 | dist 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | 18 | # debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | .pnpm-debug.log* 23 | 24 | # local env files 25 | .env.local 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | 30 | # turbo 31 | .turbo -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss"], 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "arrowParens": "always", 6 | "semi": true, 7 | "jsxSingleQuote": true 8 | } 9 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2021] [Carl Nolan](https://www.carlnolan.me/) 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 | --- 2 | # Starker Kit with TurboRepo, Cloudflare Pages, Tailwind CSS, ShadCN UI and Drizzle ORM 3 | 4 | This is a monorepo setup with [Turborepo](https://turborepo.dev/) and [Cloudflare Pages](https://pages.cloudflare.com/) for deploying NextJS applications. It also includes a shared `tailwindcss` configuration and a shared `eslint` configuration. 5 | 6 | ## Installation 7 | 8 | 1. To get this project files locally on your machine, you can clone this repository by running the following command on your terminal or command line: 9 | 10 | ```sh 11 | git clone https://github.com/Saurav-Pant/Starter-Kit.git 12 | ``` 13 | 14 | 2. Install all the dependency packages found in the `package.json` files across the monorepo apps by running `npm install` from the project root directory. 15 | 3. To start the development servers of all the applications in your monorepo in parallel, simply run `npm run dev`. 16 | 17 | ## Apps & Packages 18 | 19 | ### apps/docs 20 | A sample documentation [Next.js](https://nextjs.org/docs) app. 21 | 22 | ### apps/web 23 | NextJS on Cloudflare Pages setup with Drizzle ORM and Cloudflare D1. 24 | 25 | #### Setting up Cloudflare D1 with Drizzle ORM 26 | 27 | 1. To create a database on D1, run: 28 | ```sh 29 | npx wrangler d1 create 30 | ``` 31 | 2. Add the necessary configuration to the `wrangler.toml` file which you get while running above command. 32 | 33 | 3. To initialize the DB: 34 | - Locally: 35 | ```sh 36 | npx wrangler d1 execute --file=src/db/seed.sql --local 37 | ``` 38 | - Remotely: 39 | ```sh 40 | npx wrangler d1 execute --file=src/db/seed.sql --remote 41 | ``` 42 | 43 | 4. Run: 44 | ```sh 45 | npm run cf-typegen 46 | ``` 47 | 48 | 5. In `drizzle.config.ts`, update with your DB Name and URL, then run: 49 | ```sh 50 | npx drizzle-kit introspect 51 | ``` 52 | 53 | 6. To Deploy on Cloudflare Pages: 54 | ```sh 55 | npm run deploy 56 | ``` 57 | 58 | ### packages/config 59 | Shared `tailwindcss` and `eslint` configurations. 60 | 61 | ### packages/tsconfig 62 | `tsconfig.json`s which can be used by extending them throughout the monorepo. 63 | 64 | ### packages/ui 65 | 66 | Shared UI components with ShadCN implementation. 67 | 68 | --- 69 | 70 | 71 | -------------------------------------------------------------------------------- /apps/docs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('config/.eslintrc'); 2 | -------------------------------------------------------------------------------- /apps/docs/README.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | First, run the development server: 4 | 5 | ```bash 6 | yarn dev 7 | ``` 8 | 9 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 10 | 11 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 12 | 13 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 14 | 15 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 16 | 17 | ## Learn More 18 | 19 | To learn more about Next.js, take a look at the following resources: 20 | 21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 23 | 24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 25 | 26 | ## Deploy on Vercel 27 | 28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js. 29 | 30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 31 | -------------------------------------------------------------------------------- /apps/docs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/docs/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('config/next.config'); 2 | -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --port 3001", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "config": "*", 13 | "shared-lib": "*", 14 | "tsconfig": "*", 15 | "ui": "*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('config/postcss.config'); 2 | -------------------------------------------------------------------------------- /apps/docs/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --muted: 210 40% 96.1%; 11 | --muted-foreground: 215.4 16.3% 46.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 222.2 84% 4.9%; 18 | 19 | --border: 214.3 31.8% 91.4%; 20 | --input: 214.3 31.8% 91.4%; 21 | 22 | --primary: 222.2 47.4% 11.2%; 23 | --primary-foreground: 210 40% 98%; 24 | 25 | --secondary: 210 40% 96.1%; 26 | --secondary-foreground: 222.2 47.4% 11.2%; 27 | 28 | --accent: 210 40% 96.1%; 29 | --accent-foreground: 222.2 47.4% 11.2%; 30 | 31 | --destructive: 0 84.2% 60.2%; 32 | --destructive-foreground: 210 40% 98%; 33 | 34 | --ring: 215 20.2% 65.1%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 222.2 84% 4.9%; 41 | --foreground: 210 40% 98%; 42 | 43 | --muted: 217.2 32.6% 17.5%; 44 | --muted-foreground: 215 20.2% 65.1%; 45 | 46 | --popover: 222.2 84% 4.9%; 47 | --popover-foreground: 210 40% 98%; 48 | 49 | --card: 222.2 84% 4.9%; 50 | --card-foreground: 210 40% 98%; 51 | 52 | --border: 217.2 32.6% 17.5%; 53 | --input: 217.2 32.6% 17.5%; 54 | 55 | --primary: 210 40% 98%; 56 | --primary-foreground: 222.2 47.4% 11.2%; 57 | 58 | --secondary: 217.2 32.6% 17.5%; 59 | --secondary-foreground: 210 40% 98%; 60 | 61 | --accent: 217.2 32.6% 17.5%; 62 | --accent-foreground: 210 40% 98%; 63 | 64 | --destructive: 0 62.8% 30.6%; 65 | --destructive-foreground: 0 85.7% 97.3%; 66 | 67 | --ring: 217.2 32.6% 17.5%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /apps/docs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | 3 | import type { Metadata } from 'next'; 4 | import { Inter } from 'next/font/google'; 5 | import { ReactNode } from 'react'; 6 | import { ModeToggle, ThemeProvider } from 'ui'; 7 | 8 | export const metadata: Metadata = { 9 | title: 'Doc App', 10 | description: 'Welcome to Next.js 13', 11 | }; 12 | 13 | const inter = Inter({ subsets: ['latin'] }); 14 | 15 | export default function RootLayout({ children }: { children: ReactNode }) { 16 | return ( 17 | 18 | 19 | 20 |
21 | 22 |
23 | {children} 24 |
25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/docs/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const page = () => { 4 | return ( 5 |
page
6 | ) 7 | } 8 | 9 | export default page -------------------------------------------------------------------------------- /apps/docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('config/tailwind.config'); 2 | -------------------------------------------------------------------------------- /apps/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "incremental": true, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ], 24 | "paths": { 25 | "ui": [ 26 | "../../packages/ui/index.ts" 27 | ] 28 | }, 29 | "forceConsistentCasingInFileNames": true 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".next/types/**/*.ts" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # wrangler files 39 | .wrangler 40 | .dev.vars 41 | -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`c3`](https://developers.cloudflare.com/pages/get-started/c3). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | ## Cloudflare integration 20 | 21 | Besides the `dev` script mentioned above `c3` has added a few extra scripts that allow you to integrate the application with the [Cloudflare Pages](https://pages.cloudflare.com/) environment, these are: 22 | - `pages:build` to build the application for Pages using the [`@cloudflare/next-on-pages`](https://github.com/cloudflare/next-on-pages) CLI 23 | - `preview` to locally preview your Pages application using the [Wrangler](https://developers.cloudflare.com/workers/wrangler/) CLI 24 | - `deploy` to deploy your Pages application using the [Wrangler](https://developers.cloudflare.com/workers/wrangler/) CLI 25 | 26 | > __Note:__ while the `dev` script is optimal for local development you should preview your Pages application as well (periodically or before deployments) in order to make sure that it can properly work in the Pages environment (for more details see the [`@cloudflare/next-on-pages` recommended workflow](https://github.com/cloudflare/next-on-pages/blob/05b6256/internal-packages/next-dev/README.md#recommended-workflow)) 27 | 28 | ### Bindings 29 | 30 | Cloudflare [Bindings](https://developers.cloudflare.com/pages/functions/bindings/) are what allows you to interact with resources available in the Cloudflare Platform. 31 | 32 | You can use bindings during development, when previewing locally your application and of course in the deployed application: 33 | 34 | - To use bindings in dev mode you need to define them in the `next.config.js` file under `setupDevBindings`, this mode uses the `next-dev` `@cloudflare/next-on-pages` submodule. For more details see its [documentation](https://github.com/cloudflare/next-on-pages/blob/05b6256/internal-packages/next-dev/README.md). 35 | 36 | - To use bindings in the preview mode you need to add them to the `pages:preview` script accordingly to the `wrangler pages dev` command. For more details see its [documentation](https://developers.cloudflare.com/workers/wrangler/commands/#dev-1) or the [Pages Bindings documentation](https://developers.cloudflare.com/pages/functions/bindings/). 37 | 38 | - To use bindings in the deployed application you will need to configure them in the Cloudflare [dashboard](https://dash.cloudflare.com/). For more details see the [Pages Bindings documentation](https://developers.cloudflare.com/pages/functions/bindings/). 39 | 40 | #### KV Example 41 | 42 | `c3` has added for you an example showing how you can use a KV binding. 43 | 44 | In order to enable the example: 45 | - Search for javascript/typescript lines containing the following comment: 46 | ```ts 47 | // KV Example: 48 | ``` 49 | and uncomment the commented lines below it. 50 | - Do the same in the `wrangler.toml` file, where 51 | the comment is: 52 | ``` 53 | # KV Example: 54 | ``` 55 | - If you're using TypeScript run the `cf-typegen` script to update the `env.d.ts` file: 56 | ```bash 57 | npm run cf-typegen 58 | # or 59 | yarn cf-typegen 60 | # or 61 | pnpm cf-typegen 62 | # or 63 | bun cf-typegen 64 | ``` 65 | 66 | After doing this you can run the `dev` or `preview` script and visit the `/api/hello` route to see the example in action. 67 | 68 | Finally, if you also want to see the example work in the deployed application make sure to add a `MY_KV_NAMESPACE` binding to your Pages application in its [dashboard kv bindings settings section](https://dash.cloudflare.com/?to=/:account/pages/view/:pages-project/settings/functions#kv_namespace_bindings_section). After having configured it make sure to re-deploy your application. 69 | -------------------------------------------------------------------------------- /apps/web/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dialect: 'sqlite', 3 | url: 'file:.wrangler/state/v3/d1/miniflare-D1DatabaseObject/aca2988ff8960b2915fa4d6b7f7274c8f593c746bc6a063b910f1b8b4240cb3e.sqlite', 4 | output: './src/db', 5 | dbCredentials: { 6 | wranglerConfigPath: './wrangler.toml', 7 | dbName: 'Learning', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /apps/web/drizzle/0000_mushy_zarek.sql: -------------------------------------------------------------------------------- 1 | -- Current sql file was generated after introspecting the database 2 | -- If you want to run this migration please uncomment this code before executing migrations 3 | /* 4 | CREATE TABLE `tasks` ( 5 | `id` integer PRIMARY KEY, 6 | `title` text NOT NULL, 7 | `description` text, 8 | `due_date` numeric, 9 | `priority` text DEFAULT 'Medium', 10 | `category_id` integer, 11 | `completed` numeric DEFAULT 0, 12 | `created_at` numeric DEFAULT (CURRENT_TIMESTAMP), 13 | FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`) ON UPDATE no action ON DELETE no action 14 | ); 15 | --> statement-breakpoint 16 | CREATE TABLE `categories` ( 17 | `id` integer PRIMARY KEY, 18 | `name` text NOT NULL 19 | ); 20 | --> statement-breakpoint 21 | CREATE TABLE `task_categories` ( 22 | `category_id` integer PRIMARY KEY AUTOINCREMENT, 23 | `category_name` text NOT NULL 24 | ); 25 | --> statement-breakpoint 26 | CREATE TABLE `task_list` ( 27 | `task_id` integer PRIMARY KEY AUTOINCREMENT, 28 | `task_title` text NOT NULL, 29 | `task_description` text, 30 | `due_date` numeric, 31 | `task_priority` text DEFAULT 'Medium', 32 | `task_category_id` integer, 33 | `is_completed` numeric DEFAULT 0, 34 | `created_on` numeric DEFAULT (CURRENT_TIMESTAMP), 35 | FOREIGN KEY (`task_category_id`) REFERENCES `task_categories`(`category_id`) ON UPDATE no action ON DELETE no action 36 | ); 37 | 38 | */ -------------------------------------------------------------------------------- /apps/web/drizzle/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "00000000-0000-0000-0000-000000000000", 3 | "prevId": "", 4 | "version": "6", 5 | "dialect": "sqlite", 6 | "tables": { 7 | "tasks": { 8 | "name": "tasks", 9 | "columns": { 10 | "id": { 11 | "autoincrement": false, 12 | "name": "id", 13 | "type": "integer", 14 | "primaryKey": true, 15 | "notNull": false 16 | }, 17 | "title": { 18 | "autoincrement": false, 19 | "name": "title", 20 | "type": "text", 21 | "primaryKey": false, 22 | "notNull": true 23 | }, 24 | "description": { 25 | "autoincrement": false, 26 | "name": "description", 27 | "type": "text", 28 | "primaryKey": false, 29 | "notNull": false 30 | }, 31 | "due_date": { 32 | "autoincrement": false, 33 | "name": "due_date", 34 | "type": "numeric", 35 | "primaryKey": false, 36 | "notNull": false 37 | }, 38 | "priority": { 39 | "default": "'Medium'", 40 | "autoincrement": false, 41 | "name": "priority", 42 | "type": "text", 43 | "primaryKey": false, 44 | "notNull": false 45 | }, 46 | "category_id": { 47 | "autoincrement": false, 48 | "name": "category_id", 49 | "type": "integer", 50 | "primaryKey": false, 51 | "notNull": false 52 | }, 53 | "completed": { 54 | "default": 0, 55 | "autoincrement": false, 56 | "name": "completed", 57 | "type": "numeric", 58 | "primaryKey": false, 59 | "notNull": false 60 | }, 61 | "created_at": { 62 | "default": "(CURRENT_TIMESTAMP)", 63 | "autoincrement": false, 64 | "name": "created_at", 65 | "type": "numeric", 66 | "primaryKey": false, 67 | "notNull": false 68 | } 69 | }, 70 | "compositePrimaryKeys": {}, 71 | "indexes": {}, 72 | "foreignKeys": { 73 | "tasks_category_id_categories_id_fk": { 74 | "name": "tasks_category_id_categories_id_fk", 75 | "tableFrom": "tasks", 76 | "tableTo": "categories", 77 | "columnsFrom": [ 78 | "category_id" 79 | ], 80 | "columnsTo": [ 81 | "id" 82 | ], 83 | "onDelete": "no action", 84 | "onUpdate": "no action" 85 | } 86 | }, 87 | "uniqueConstraints": {} 88 | }, 89 | "categories": { 90 | "name": "categories", 91 | "columns": { 92 | "id": { 93 | "autoincrement": false, 94 | "name": "id", 95 | "type": "integer", 96 | "primaryKey": true, 97 | "notNull": false 98 | }, 99 | "name": { 100 | "autoincrement": false, 101 | "name": "name", 102 | "type": "text", 103 | "primaryKey": false, 104 | "notNull": true 105 | } 106 | }, 107 | "compositePrimaryKeys": {}, 108 | "indexes": {}, 109 | "foreignKeys": {}, 110 | "uniqueConstraints": {} 111 | }, 112 | "task_categories": { 113 | "name": "task_categories", 114 | "columns": { 115 | "category_id": { 116 | "autoincrement": true, 117 | "name": "category_id", 118 | "type": "integer", 119 | "primaryKey": true, 120 | "notNull": false 121 | }, 122 | "category_name": { 123 | "autoincrement": false, 124 | "name": "category_name", 125 | "type": "text", 126 | "primaryKey": false, 127 | "notNull": true 128 | } 129 | }, 130 | "compositePrimaryKeys": {}, 131 | "indexes": {}, 132 | "foreignKeys": {}, 133 | "uniqueConstraints": {} 134 | }, 135 | "task_list": { 136 | "name": "task_list", 137 | "columns": { 138 | "task_id": { 139 | "autoincrement": true, 140 | "name": "task_id", 141 | "type": "integer", 142 | "primaryKey": true, 143 | "notNull": false 144 | }, 145 | "task_title": { 146 | "autoincrement": false, 147 | "name": "task_title", 148 | "type": "text", 149 | "primaryKey": false, 150 | "notNull": true 151 | }, 152 | "task_description": { 153 | "autoincrement": false, 154 | "name": "task_description", 155 | "type": "text", 156 | "primaryKey": false, 157 | "notNull": false 158 | }, 159 | "due_date": { 160 | "autoincrement": false, 161 | "name": "due_date", 162 | "type": "numeric", 163 | "primaryKey": false, 164 | "notNull": false 165 | }, 166 | "task_priority": { 167 | "default": "'Medium'", 168 | "autoincrement": false, 169 | "name": "task_priority", 170 | "type": "text", 171 | "primaryKey": false, 172 | "notNull": false 173 | }, 174 | "task_category_id": { 175 | "autoincrement": false, 176 | "name": "task_category_id", 177 | "type": "integer", 178 | "primaryKey": false, 179 | "notNull": false 180 | }, 181 | "is_completed": { 182 | "default": 0, 183 | "autoincrement": false, 184 | "name": "is_completed", 185 | "type": "numeric", 186 | "primaryKey": false, 187 | "notNull": false 188 | }, 189 | "created_on": { 190 | "default": "(CURRENT_TIMESTAMP)", 191 | "autoincrement": false, 192 | "name": "created_on", 193 | "type": "numeric", 194 | "primaryKey": false, 195 | "notNull": false 196 | } 197 | }, 198 | "compositePrimaryKeys": {}, 199 | "indexes": {}, 200 | "foreignKeys": { 201 | "task_list_task_category_id_task_categories_category_id_fk": { 202 | "name": "task_list_task_category_id_task_categories_category_id_fk", 203 | "tableFrom": "task_list", 204 | "tableTo": "task_categories", 205 | "columnsFrom": [ 206 | "task_category_id" 207 | ], 208 | "columnsTo": [ 209 | "category_id" 210 | ], 211 | "onDelete": "no action", 212 | "onUpdate": "no action" 213 | } 214 | }, 215 | "uniqueConstraints": {} 216 | } 217 | }, 218 | "enums": {}, 219 | "_meta": { 220 | "schemas": {}, 221 | "tables": {}, 222 | "columns": {} 223 | } 224 | } -------------------------------------------------------------------------------- /apps/web/drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6", 3 | "dialect": "postgresql", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "6", 8 | "when": 1716665192742, 9 | "tag": "0000_mushy_zarek", 10 | "breakpoints": true 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /apps/web/drizzle/relations.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm/relations"; 2 | import { categories, tasks, task_categories, task_list } from "./schema"; 3 | 4 | export const tasksRelations = relations(tasks, ({one}) => ({ 5 | category: one(categories, { 6 | fields: [tasks.category_id], 7 | references: [categories.id] 8 | }), 9 | })); 10 | 11 | export const categoriesRelations = relations(categories, ({many}) => ({ 12 | tasks: many(tasks), 13 | })); 14 | 15 | export const task_listRelations = relations(task_list, ({one}) => ({ 16 | task_category: one(task_categories, { 17 | fields: [task_list.task_category_id], 18 | references: [task_categories.category_id] 19 | }), 20 | })); 21 | 22 | export const task_categoriesRelations = relations(task_categories, ({many}) => ({ 23 | task_lists: many(task_list), 24 | })); -------------------------------------------------------------------------------- /apps/web/drizzle/schema.ts: -------------------------------------------------------------------------------- 1 | import { sqliteTable, AnySQLiteColumn, foreignKey, integer, text, numeric } from "drizzle-orm/sqlite-core" 2 | import { sql } from "drizzle-orm" 3 | 4 | export const tasks = sqliteTable("tasks", { 5 | id: integer("id").primaryKey(), 6 | title: text("title").notNull(), 7 | description: text("description"), 8 | due_date: numeric("due_date"), 9 | priority: text("priority").default("Medium"), 10 | category_id: integer("category_id").references(() => categories.id), 11 | completed: numeric("completed"), 12 | created_at: numeric("created_at").default(sql`(CURRENT_TIMESTAMP)`), 13 | }); 14 | 15 | export const categories = sqliteTable("categories", { 16 | id: integer("id").primaryKey(), 17 | name: text("name").notNull(), 18 | }); 19 | 20 | export const task_categories = sqliteTable("task_categories", { 21 | category_id: integer("category_id").primaryKey({ autoIncrement: true }), 22 | category_name: text("category_name").notNull(), 23 | }); 24 | 25 | export const task_list = sqliteTable("task_list", { 26 | task_id: integer("task_id").primaryKey({ autoIncrement: true }), 27 | task_title: text("task_title").notNull(), 28 | task_description: text("task_description"), 29 | due_date: numeric("due_date"), 30 | task_priority: text("task_priority").default("Medium"), 31 | task_category_id: integer("task_category_id").references(() => task_categories.category_id), 32 | is_completed: numeric("is_completed"), 33 | created_on: numeric("created_on").default(sql`(CURRENT_TIMESTAMP)`), 34 | }); -------------------------------------------------------------------------------- /apps/web/env.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by Wrangler on Sat May 25 2024 23:46:52 GMT+0530 (India Standard Time) 2 | // by running `wrangler types --env-interface CloudflareEnv env.d.ts` 3 | 4 | interface CloudflareEnv { 5 | DB: D1Database; 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev'; 2 | 3 | // Here we use the @cloudflare/next-on-pages next-dev module to allow us to use bindings during local development 4 | // (when running the application with `next dev`), for more information see: 5 | // https://github.com/cloudflare/next-on-pages/blob/5712c57ea7/internal-packages/next-dev/README.md 6 | if (process.env.NODE_ENV === 'development') { 7 | await setupDevPlatform(); 8 | } 9 | 10 | /** @type {import('next').NextConfig} */ 11 | const nextConfig = {}; 12 | 13 | export default nextConfig; 14 | -------------------------------------------------------------------------------- /apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --port 3000", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages", 11 | "preview": "npm run pages:build && wrangler pages dev", 12 | "deploy": "npm run pages:build && wrangler pages deploy", 13 | "cf-typegen": "wrangler types --env-interface CloudflareEnv env.d.ts" 14 | }, 15 | "dependencies": { 16 | "@libsql/client": "^0.6.0", 17 | "drizzle-orm": "^0.30.10", 18 | "libsql": "^0.3.18", 19 | "next": "14.1.0", 20 | "react": "^18", 21 | "react-dom": "^18" 22 | }, 23 | "devDependencies": { 24 | "@cloudflare/next-on-pages": "^1.11.3", 25 | "@cloudflare/workers-types": "^4.20240512.0", 26 | "@types/node": "^20", 27 | "@types/react": "^18", 28 | "@types/react-dom": "^18", 29 | "autoprefixer": "^10.0.1", 30 | "drizzle-kit": "^0.21.4", 31 | "postcss": "^8", 32 | "tailwindcss": "^3.3.0", 33 | "typescript": "^5", 34 | "vercel": "^34.2.3", 35 | "wrangler": "^3.57.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('config/postcss.config'); 2 | -------------------------------------------------------------------------------- /apps/web/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web/src/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server'; 2 | import { getRequestContext } from '@cloudflare/next-on-pages'; 3 | import { createDb } from '../../../db/index'; 4 | import { task_list } from '../../../../drizzle/schema'; 5 | 6 | interface TaskList { 7 | task_title: string; 8 | task_description?: string; 9 | due_date?: string; 10 | task_priority?: 'Low' | 'Medium' | 'High'; 11 | task_category_id?: number; 12 | is_completed?: boolean; 13 | } 14 | 15 | export const runtime = 'edge'; 16 | 17 | export async function GET(request: NextRequest) { 18 | const DB = getRequestContext().env.DB; 19 | 20 | const drizzle = createDb(DB); 21 | const result = await drizzle.select().from(task_list); 22 | 23 | return new Response(JSON.stringify(result), { 24 | headers: { 'Content-Type': 'application/json' }, 25 | }); 26 | } 27 | 28 | export async function POST(request: NextRequest) { 29 | try { 30 | const DB = getRequestContext().env.DB; 31 | const drizzle = createDb(DB); 32 | 33 | const body: TaskList = await request.json(); 34 | 35 | const data = { 36 | task_title: body.task_title, 37 | task_description: body.task_description, 38 | due_date: body.due_date, 39 | task_priority: body.task_priority ?? 'Medium', 40 | task_category_id: body.task_category_id, 41 | is_completed: body.is_completed ? '1' : '0', 42 | }; 43 | 44 | // Insert the parsed data into the database 45 | const result = await drizzle.insert(task_list).values(data); 46 | 47 | return new Response(JSON.stringify(result), { 48 | headers: { 'Content-Type': 'application/json' }, 49 | }); 50 | } catch (error: any) { 51 | return new Response(JSON.stringify({ error: error.message }), { 52 | status: 500, 53 | headers: { 'Content-Type': 'application/json' }, 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /apps/web/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Saurav-Pant/Starter-Kit/c478c4bbb8bae9990007568068eeb8cfa2172234/apps/web/src/app/favicon.ico -------------------------------------------------------------------------------- /apps/web/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 20 14.3% 4.1%; 9 | 10 | --muted: 60 4.8% 95.9%; 11 | --muted-foreground: 25 5.3% 44.7%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 20 14.3% 4.1%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 20 14.3% 4.1%; 18 | 19 | --border: 20 5.9% 90%; 20 | --input: 20 5.9% 90%; 21 | 22 | --primary: 24 9.8% 10%; 23 | --primary-foreground: 60 9.1% 97.8%; 24 | 25 | --secondary: 60 4.8% 95.9%; 26 | --secondary-foreground: 24 9.8% 10%; 27 | 28 | --accent: 60 4.8% 95.9%; 29 | --accent-foreground: 24 9.8% 10%; 30 | 31 | --destructive: 0 84.2% 60.2%; 32 | --destructive-foreground: 60 9.1% 97.8%; 33 | 34 | --ring: 24 5.4% 63.9%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 20 14.3% 4.1%; 41 | --foreground: 60 9.1% 97.8%; 42 | 43 | --muted: 12 6.5% 15.1%; 44 | --muted-foreground: 24 5.4% 63.9%; 45 | 46 | --popover: 20 14.3% 4.1%; 47 | --popover-foreground: 60 9.1% 97.8%; 48 | 49 | --card: 20 14.3% 4.1%; 50 | --card-foreground: 60 9.1% 97.8%; 51 | 52 | --border: 12 6.5% 15.1%; 53 | --input: 12 6.5% 15.1%; 54 | 55 | --primary: 60 9.1% 97.8%; 56 | --primary-foreground: 24 9.8% 10%; 57 | 58 | --secondary: 12 6.5% 15.1%; 59 | --secondary-foreground: 60 9.1% 97.8%; 60 | 61 | --accent: 12 6.5% 15.1%; 62 | --accent-foreground: 60 9.1% 97.8%; 63 | 64 | --destructive: 0 62.8% 30.6%; 65 | --destructive-foreground: 0 85.7% 97.3%; 66 | 67 | --ring: 12 6.5% 15.1%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /apps/web/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | 3 | import type { Metadata } from 'next'; 4 | import { Inter } from 'next/font/google'; 5 | import { ReactNode } from 'react'; 6 | import { ModeToggle, ThemeProvider } from 'ui'; 7 | 8 | export const metadata: Metadata = { 9 | title: 'Web App', 10 | description: 'Welcome to Next.js 13', 11 | }; 12 | 13 | const inter = Inter({ subsets: ['latin'] }); 14 | 15 | export default function RootLayout({ children }: { children: ReactNode }) { 16 | return ( 17 | 18 | 19 | 20 |
21 | 22 |
23 | {children} 24 |
25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/web/src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const notFound = () => { 4 | return
not-found
; 5 | }; 6 | 7 | export default notFound; 8 | -------------------------------------------------------------------------------- /apps/web/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React, { useEffect, useState, ChangeEvent, FormEvent } from 'react'; 3 | import { Button } from '../../../../packages/ui/src/components/ui'; 4 | 5 | interface Task { 6 | task_id: number; 7 | task_title: string; 8 | task_description: string; 9 | due_date: string; 10 | task_priority: 'Low' | 'Medium' | 'High'; 11 | task_category_id: number; 12 | is_completed: boolean; 13 | created_on: string; 14 | } 15 | 16 | interface NewTask { 17 | task_title: string; 18 | task_description?: string; 19 | due_date?: string; 20 | task_priority?: 'Low' | 'Medium' | 'High'; 21 | task_category_id?: number; 22 | is_completed?: boolean; 23 | } 24 | 25 | const TaskPage: React.FC = () => { 26 | const [tasks, setTasks] = useState([]); 27 | const [newTask, setNewTask] = useState({ 28 | task_title: '', 29 | task_description: '', 30 | due_date: '', 31 | task_priority: 'Medium', 32 | task_category_id: 1, 33 | is_completed: false, 34 | }); 35 | 36 | const fetchTasks = async () => { 37 | const response = await fetch('/api/hello'); 38 | const data = await response.json(); 39 | setTasks(data as Task[]); 40 | }; 41 | 42 | useEffect(() => { 43 | fetchTasks(); 44 | }, []); 45 | 46 | const handleInputChange = ( 47 | e: ChangeEvent 48 | ) => { 49 | const { name, value, type } = e.target; 50 | 51 | if (type === 'checkbox') { 52 | const checked = (e.target as HTMLInputElement).checked; 53 | setNewTask((prevTask) => ({ 54 | ...prevTask, 55 | [name]: checked, 56 | })); 57 | } else { 58 | setNewTask((prevTask) => ({ 59 | ...prevTask, 60 | [name]: value, 61 | })); 62 | } 63 | }; 64 | 65 | const handleFormSubmit = async (e: FormEvent) => { 66 | e.preventDefault(); 67 | const response = await fetch('/api/hello', { 68 | method: 'POST', 69 | headers: { 70 | 'Content-Type': 'application/json', 71 | }, 72 | body: JSON.stringify({ 73 | ...newTask, 74 | is_completed: newTask.is_completed ? '1' : '0', 75 | }), 76 | }); 77 | const result = await response.json(); 78 | console.log(result); 79 | fetchTasks(); 80 | 81 | }; 82 | 83 | return ( 84 |
85 |

Add New Task

86 |
87 |
88 | 89 | 97 |
98 |
99 | 100 | 107 |
108 |
109 | 110 | 117 |
118 |
119 | 120 | 130 |
131 |
132 | 135 | 143 |
144 |
145 | 146 | 153 |
154 | 155 | 156 |
157 |

Task List

158 |
    159 | {tasks.map((task) => ( 160 |
  • 164 |

    {task.task_title}

    165 |

    {task.task_description}

    166 |

    Due Date: {task.due_date}

    167 |

    Priority: {task.task_priority}

    168 |

    Completed: {task.is_completed ? 'Yes' : 'No'}

    169 |
  • 170 | ))} 171 |
172 |
173 | ); 174 | }; 175 | 176 | export default TaskPage; 177 | -------------------------------------------------------------------------------- /apps/web/src/db/index.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from 'drizzle-orm/d1'; 2 | import * as schema from '../../drizzle/schema'; 3 | 4 | export const createDb = (d1: D1Database) => { 5 | return drizzle(d1, { schema }); 6 | }; 7 | -------------------------------------------------------------------------------- /apps/web/src/db/seed.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS task_categories ( 2 | category_id INTEGER PRIMARY KEY AUTOINCREMENT, 3 | category_name TEXT NOT NULL UNIQUE 4 | ); 5 | 6 | CREATE TABLE IF NOT EXISTS task_list ( 7 | task_id INTEGER PRIMARY KEY AUTOINCREMENT, 8 | task_title TEXT NOT NULL, 9 | task_description TEXT, 10 | due_date DATE, 11 | task_priority TEXT CHECK(task_priority IN ('Low', 'Medium', 'High')) DEFAULT 'Medium', 12 | task_category_id INTEGER, 13 | is_completed BOOLEAN DEFAULT 0, 14 | created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 15 | FOREIGN KEY (task_category_id) REFERENCES task_categories(category_id) 16 | ); 17 | 18 | INSERT OR IGNORE INTO task_categories (category_name) VALUES 19 | ('Work'), 20 | ('Personal'), 21 | ('Shopping'), 22 | ('Study'); 23 | 24 | INSERT INTO task_list (task_title, task_description, due_date, task_priority, task_category_id, is_completed, created_on) VALUES 25 | ('Finish project report', 'Complete and submit the final project report', '2024-05-10', 'High', 1, 0, '2024-05-01 10:00:00'), 26 | ('Morning exercise', 'Go for a morning run in the park', '2024-05-05', 'Medium', 2, 0, '2024-05-01 07:00:00'), 27 | ('Weekly grocery shopping', 'Buy groceries including fruits, vegetables, and dairy', '2024-05-07', 'Medium', 3, 0, '2024-05-02 15:00:00'), 28 | ('Study for exam', 'Read Chapter 4 of the course textbook', '2024-05-12', 'Low', 4, 0, '2024-05-03 20:00:00'); 29 | -------------------------------------------------------------------------------- /apps/web/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | module.exports = require('config/tailwind.config'); 2 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | // "@/*": ["./src/*"], 22 | "ui": ["../../packages/ui/index.ts"] 23 | 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /apps/web/wrangler.toml: -------------------------------------------------------------------------------- 1 | #:schema node_modules/wrangler/config-schema.json 2 | name = "web2" 3 | compatibility_date = "2024-05-24" 4 | compatibility_flags = ["nodejs_compat"] 5 | pages_build_output_dir = ".vercel/output/static" 6 | 7 | [[d1_databases]] 8 | binding = "DB" 9 | database_name = "Learning" 10 | database_id = "b65d7050-a8b2-45ad-8dac-0a41b4fbc339" 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turborepo-basic-shared", 3 | "version": "0.0.0", 4 | "private": true, 5 | "workspaces": [ 6 | "apps/*", 7 | "packages/*" 8 | ], 9 | "scripts": { 10 | "build": "turbo run build", 11 | "dev": "turbo run dev --parallel", 12 | "lint": "turbo run lint", 13 | "test": "turbo run test", 14 | "format": "prettier --write \"**/*.{ts,tsx,md}\"", 15 | "prepare": "husky install" 16 | }, 17 | "dependencies": { 18 | "@radix-ui/react-dropdown-menu": "^2.0.5", 19 | "@radix-ui/react-icons": "^1.3.0", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "class-variance-authority": "^0.7.0", 22 | "clsx": "^2.0.0", 23 | "commander": "^10.0.0", 24 | "lucide-react": "^0.261.0", 25 | "next": "^13.4.10", 26 | "next-themes": "^0.2.1", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "tailwind-merge": "^1.14.0", 30 | "tailwindcss-animate": "^1.0.6" 31 | }, 32 | "devDependencies": { 33 | "@testing-library/react": "^14.0.0", 34 | "@types/node": "20.4.2", 35 | "@types/react": "^18.2.15", 36 | "@types/react-dom": "^18.2.7", 37 | "@vitejs/plugin-react": "^4.0.3", 38 | "autoprefixer": "^10.4.0", 39 | "eslint": "^8.8.0", 40 | "eslint-config-next": "^13.4.10", 41 | "eslint-config-prettier": "^8.3.0", 42 | "eslint-plugin-prettier": "^4.0.0", 43 | "eslint-plugin-simple-import-sort": "^7.0.0", 44 | "husky": "^8.0.0", 45 | "import": "^0.0.6", 46 | "jsdom": "^22.1.0", 47 | "next-transpile-modules": "9.0.0", 48 | "postcss": "^8.4.5", 49 | "prettier": "^2.5.1", 50 | "prettier-plugin-tailwindcss": "^0.4.1", 51 | "pretty-quick": "^3.1.3", 52 | "tailwindcss": "3.3.2", 53 | "turbo": "^1.10.8", 54 | "typescript": "^5.1.6", 55 | "vitest": "^0.33.0" 56 | }, 57 | "engines": { 58 | "npm": ">=7.0.0", 59 | "node": ">=14.0.0" 60 | }, 61 | "husky": { 62 | "hooks": { 63 | "pre-commit": "pretty-quick --staged" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'next', 4 | 'prettier', 5 | 'next/core-web-vitals', 6 | 'plugin:prettier/recommended', 7 | ], 8 | plugins: ['simple-import-sort', 'prettier'], 9 | settings: { 10 | next: { 11 | rootDir: ['apps/*/', 'packages/*/'], 12 | }, 13 | }, 14 | rules: { 15 | '@next/next/no-html-link-for-pages': 'off', 16 | 'simple-import-sort/imports': 'error', 17 | 'simple-import-sort/exports': 'error', 18 | 'prettier/prettier': ['error'], 19 | 'no-unused-vars': 'error', 20 | 'prefer-const': 'error', 21 | 'no-irregular-whitespace': 'error', 22 | 'no-trailing-spaces': 'error', 23 | semi: 'error', 24 | 'no-empty-function': 'error', 25 | 'no-duplicate-imports': 'error', 26 | 'newline-after-var': 'error', 27 | camelcase: 'warn', 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/config/eslint-preset.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['next', 'prettier', 'next/core-web-vitals'], 3 | plugins: ['simple-import-sort', 'prettier'], 4 | settings: { 5 | next: { 6 | rootDir: ['apps/*/', 'packages/*/', './'], 7 | }, 8 | }, 9 | rules: { 10 | '@next/next/no-html-link-for-pages': 'off', 11 | 'simple-import-sort/imports': 'error', 12 | 'simple-import-sort/exports': 'error', 13 | 'prettier/prettier': ['error'], 14 | 'no-unused-vars': 'error', 15 | 'prefer-const': 'error', 16 | 'no-irregular-whitespace': 'error', 17 | 'no-trailing-spaces': 'error', 18 | semi: 'error', 19 | 'no-empty-function': 'error', 20 | 'no-duplicate-imports': 'error', 21 | 'newline-after-var': 'error', 22 | camelcase: 'warn', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/config/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | transpilePackages: ['ui', 'shared-lib', 'config'], 4 | }; 5 | 6 | module.exports = nextConfig; 7 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "files": [ 7 | ".eslintrc.js", 8 | "eslint-preset.js", 9 | "postcss.config.js", 10 | "tailwind.config.js" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/config/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme'); 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | darkMode: ['class'], 6 | content: [ 7 | './pages/**/*.{js,ts,jsx,tsx}', 8 | './components/**/*.{js,ts,jsx,tsx}', 9 | './app/**/*.{js,ts,jsx,tsx}', 10 | './src/**/*.{js,ts,jsx,tsx}', 11 | '../../packages/ui/**/*.{js,ts,jsx,tsx}', 12 | ], 13 | theme: { 14 | screens: { 15 | xs: '280px', 16 | '2xl': '1400px', 17 | ...defaultTheme.screens, 18 | }, 19 | container: { 20 | center: true, 21 | padding: { 22 | DEFAULT: '2rem', 23 | xs: '1rem', 24 | sm: '1rem', 25 | }, 26 | }, 27 | extend: { 28 | colors: { 29 | border: 'hsl(var(--border))', 30 | input: 'hsl(var(--input))', 31 | ring: 'hsl(var(--ring))', 32 | background: 'hsl(var(--background))', 33 | foreground: 'hsl(var(--foreground))', 34 | primary: { 35 | DEFAULT: 'hsl(var(--primary))', 36 | foreground: 'hsl(var(--primary-foreground))', 37 | }, 38 | secondary: { 39 | DEFAULT: 'hsl(var(--secondary))', 40 | foreground: 'hsl(var(--secondary-foreground))', 41 | }, 42 | destructive: { 43 | DEFAULT: 'hsl(var(--destructive))', 44 | foreground: 'hsl(var(--destructive-foreground))', 45 | }, 46 | muted: { 47 | DEFAULT: 'hsl(var(--muted))', 48 | foreground: 'hsl(var(--muted-foreground))', 49 | }, 50 | accent: { 51 | DEFAULT: 'hsl(var(--accent))', 52 | foreground: 'hsl(var(--accent-foreground))', 53 | }, 54 | popover: { 55 | DEFAULT: 'hsl(var(--popover))', 56 | foreground: 'hsl(var(--popover-foreground))', 57 | }, 58 | card: { 59 | DEFAULT: 'hsl(var(--card))', 60 | foreground: 'hsl(var(--card-foreground))', 61 | }, 62 | }, 63 | borderRadius: { 64 | lg: 'var(--radius)', 65 | md: 'calc(var(--radius) - 2px)', 66 | sm: 'calc(var(--radius) - 4px)', 67 | }, 68 | keyframes: { 69 | 'accordion-down': { 70 | from: { height: 0 }, 71 | to: { height: 'var(--radix-accordion-content-height)' }, 72 | }, 73 | 'accordion-up': { 74 | from: { height: 'var(--radix-accordion-content-height)' }, 75 | to: { height: 0 }, 76 | }, 77 | }, 78 | animation: { 79 | 'accordion-down': 'accordion-down 0.2s ease-out', 80 | 'accordion-up': 'accordion-up 0.2s ease-out', 81 | }, 82 | }, 83 | }, 84 | plugins: [require('tailwindcss-animate')], 85 | }; 86 | -------------------------------------------------------------------------------- /packages/config/vitest.shared.config.js: -------------------------------------------------------------------------------- 1 | const { configDefaults, defineConfig } = require('vitest/config'); 2 | 3 | module.exports = defineConfig({ 4 | test: { 5 | globals: true, 6 | exclude: [...configDefaults.exclude], 7 | deps: { 8 | experimentalOptimizer: { 9 | web: { enabled: true }, 10 | ssr: { enabled: true }, 11 | }, 12 | }, 13 | benchmark: { reporters: ['default'] }, 14 | reporters: ['default'], 15 | passWithNoTests: true, 16 | coverage: { 17 | provider: 'v8', 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /packages/shared-lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /packages/shared-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shared-lib", 3 | "version": "1.0.0", 4 | "main": "index.ts", 5 | "types": "index.ts", 6 | "files": [ 7 | "./*" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/shared-lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/shared-lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export const cn = (...inputs: ClassValue[]) => { 5 | return twMerge(clsx(inputs)); 6 | }; 7 | -------------------------------------------------------------------------------- /packages/tsconfig/README.md: -------------------------------------------------------------------------------- 1 | # `tsconfig` 2 | 3 | These are base shared `tsconfig.json`s from which all other `tsconfig.json`'s inherit from. 4 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "preserveWatchOutput": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "types": ["vitest/globals"] 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "target": "es5", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "incremental": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": ["src", "next-env.d.ts"], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "files": [ 7 | "base.json", 8 | "nextjs.json", 9 | "react-library.json" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["ES2015"], 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "jsx": "react-jsx" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | module.exports = { 3 | root: true, 4 | extends: ['@repo/eslint-config/react-internal.js'], 5 | parser: '@typescript-eslint/parser', 6 | rules: { 7 | 'no-redeclare': 'off', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@ui/components", 15 | "utils": "@ui/lib/utils", 16 | "compilerOptions": { 17 | "moduleResolution": "node" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /packages/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/components/ui'; 2 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/ui", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "ui:add": "npm dlx shadcn-ui@latest add", 7 | "lint": "eslint ." 8 | }, 9 | "devDependencies": { 10 | "@types/node": "^20.10.6", 11 | "@types/react": "^18.2.46", 12 | "autoprefixer": "^10.4.17", 13 | "postcss": "^8.4.33", 14 | "tailwindcss": "^3.4.1", 15 | "typescript": "^5.3.3" 16 | }, 17 | "dependencies": { 18 | "@radix-ui/react-alert-dialog": "^1.0.5", 19 | "@radix-ui/react-dropdown-menu": "^2.0.6", 20 | "@radix-ui/react-icons": "^1.3.0", 21 | "@radix-ui/react-label": "^2.0.2", 22 | "@radix-ui/react-slot": "^1.0.2", 23 | "@tabler/icons-react": "^3.4.0", 24 | "class-variance-authority": "^0.7.0", 25 | "clsx": "^2.1.1", 26 | "lucide-react": "^0.379.0", 27 | "next-themes": "^0.3.0", 28 | "shared-lib": "*", 29 | "tailwind-merge": "^2.3.0", 30 | "tailwindcss-animate": "^1.0.7", 31 | "tsconfig": "*" 32 | }, 33 | "exports": { 34 | "./globals.css": "./src/globals.css", 35 | "./postcss.config": "./postcss.config.js", 36 | "./tailwind.config": "./tailwind.config.ts", 37 | "./lib/*": "./src/lib/*.ts", 38 | "./components/*": "./src/components/ui/*.tsx" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | plugins: { 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/ui/src/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 5 | 6 | import { cn } from "@ui/lib/utils" 7 | import { buttonVariants } from "@ui/components/ui/button" 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )) 28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName 29 | 30 | const AlertDialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, ...props }, ref) => ( 34 | 35 | 36 | 44 | 45 | )) 46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName 47 | 48 | const AlertDialogHeader = ({ 49 | className, 50 | ...props 51 | }: React.HTMLAttributes) => ( 52 |
59 | ) 60 | AlertDialogHeader.displayName = "AlertDialogHeader" 61 | 62 | const AlertDialogFooter = ({ 63 | className, 64 | ...props 65 | }: React.HTMLAttributes) => ( 66 |
73 | ) 74 | AlertDialogFooter.displayName = "AlertDialogFooter" 75 | 76 | const AlertDialogTitle = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef 79 | >(({ className, ...props }, ref) => ( 80 | 85 | )) 86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName 87 | 88 | const AlertDialogDescription = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 97 | )) 98 | AlertDialogDescription.displayName = 99 | AlertDialogPrimitive.Description.displayName 100 | 101 | const AlertDialogAction = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName 112 | 113 | const AlertDialogCancel = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 126 | )) 127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName 128 | 129 | export { 130 | AlertDialog, 131 | AlertDialogPortal, 132 | AlertDialogOverlay, 133 | AlertDialogTrigger, 134 | AlertDialogContent, 135 | AlertDialogHeader, 136 | AlertDialogFooter, 137 | AlertDialogTitle, 138 | AlertDialogDescription, 139 | AlertDialogAction, 140 | AlertDialogCancel, 141 | } 142 | -------------------------------------------------------------------------------- /packages/ui/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@ui/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /packages/ui/src/components/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from './theme-mode-toggle'; 2 | export * from './theme-provider'; 3 | export * from './ui-button'; 4 | export * from './ui-dropdown-menu'; 5 | -------------------------------------------------------------------------------- /packages/ui/src/components/ui/theme-mode-toggle.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Moon, Sun } from 'lucide-react'; 4 | import { useTheme } from 'next-themes'; 5 | import { FC } from 'react'; 6 | 7 | import { Button } from './ui-button'; 8 | import { 9 | DropdownMenu, 10 | DropdownMenuContent, 11 | DropdownMenuItem, 12 | DropdownMenuTrigger, 13 | } from './ui-dropdown-menu'; 14 | 15 | export const ModeToggle: FC = () => { 16 | const { setTheme } = useTheme(); 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme('light')}> 29 | Light 30 | 31 | setTheme('dark')}> 32 | Dark 33 | 34 | setTheme('system')}> 35 | System 36 | 37 | 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /packages/ui/src/components/ui/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 4 | import { type ThemeProviderProps } from 'next-themes/dist/types'; 5 | import { FC } from 'react'; 6 | 7 | export const ThemeProvider: FC = ({ 8 | children, 9 | ...props 10 | }) => { 11 | return {children}; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/ui/src/components/ui/ui-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Slot } from '@radix-ui/react-slot'; 4 | import { type VariantProps, cva } from 'class-variance-authority'; 5 | import * as React from 'react'; 6 | import { cn } from 'shared-lib'; 7 | 8 | const buttonVariants = cva( 9 | 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', 10 | { 11 | variants: { 12 | variant: { 13 | default: 'bg-primary text-primary-foreground hover:bg-primary/90', 14 | destructive: 15 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 16 | outline: 17 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 18 | secondary: 19 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 20 | ghost: 'hover:bg-accent hover:text-accent-foreground', 21 | link: 'text-primary underline-offset-4 hover:underline', 22 | }, 23 | size: { 24 | default: 'h-10 px-4 py-2', 25 | sm: 'h-9 rounded-md px-3', 26 | lg: 'h-11 rounded-md px-8', 27 | icon: 'h-10 w-10', 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: 'default', 32 | size: 'default', 33 | }, 34 | } 35 | ); 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean; 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : 'button'; 46 | 47 | return ( 48 | 53 | ); 54 | } 55 | ); 56 | 57 | Button.displayName = 'Button'; 58 | 59 | export { Button, buttonVariants }; 60 | -------------------------------------------------------------------------------- /packages/ui/src/components/ui/ui-dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; 4 | import { Check, ChevronRight, Circle } from 'lucide-react'; 5 | import * as React from 'react'; 6 | import { cn } from 'shared-lib'; 7 | 8 | const DropdownMenu = DropdownMenuPrimitive.Root; 9 | 10 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; 11 | 12 | const DropdownMenuGroup = DropdownMenuPrimitive.Group; 13 | 14 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal; 15 | 16 | const DropdownMenuSub = DropdownMenuPrimitive.Sub; 17 | 18 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; 19 | 20 | const DropdownMenuSubTrigger = React.forwardRef< 21 | React.ElementRef, 22 | React.ComponentPropsWithoutRef & { 23 | inset?: boolean; 24 | } 25 | >(({ className, inset, children, ...props }, ref) => ( 26 | 35 | {children} 36 | 37 | 38 | )); 39 | 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName; 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )); 56 | 57 | DropdownMenuSubContent.displayName = 58 | DropdownMenuPrimitive.SubContent.displayName; 59 | 60 | const DropdownMenuContent = React.forwardRef< 61 | React.ElementRef, 62 | React.ComponentPropsWithoutRef 63 | >(({ className, sideOffset = 4, ...props }, ref) => ( 64 | 65 | 74 | 75 | )); 76 | 77 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; 78 | 79 | const DropdownMenuItem = React.forwardRef< 80 | React.ElementRef, 81 | React.ComponentPropsWithoutRef & { 82 | inset?: boolean; 83 | } 84 | >(({ className, inset, ...props }, ref) => ( 85 | 94 | )); 95 | 96 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; 97 | 98 | const DropdownMenuCheckboxItem = React.forwardRef< 99 | React.ElementRef, 100 | React.ComponentPropsWithoutRef 101 | >(({ className, children, checked, ...props }, ref) => ( 102 | 111 | 112 | 113 | 114 | 115 | 116 | {children} 117 | 118 | )); 119 | 120 | DropdownMenuCheckboxItem.displayName = 121 | DropdownMenuPrimitive.CheckboxItem.displayName; 122 | 123 | const DropdownMenuRadioItem = React.forwardRef< 124 | React.ElementRef, 125 | React.ComponentPropsWithoutRef 126 | >(({ className, children, ...props }, ref) => ( 127 | 135 | 136 | 137 | 138 | 139 | 140 | {children} 141 | 142 | )); 143 | 144 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; 145 | 146 | const DropdownMenuLabel = React.forwardRef< 147 | React.ElementRef, 148 | React.ComponentPropsWithoutRef & { 149 | inset?: boolean; 150 | } 151 | >(({ className, inset, ...props }, ref) => ( 152 | 161 | )); 162 | 163 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; 164 | 165 | const DropdownMenuSeparator = React.forwardRef< 166 | React.ElementRef, 167 | React.ComponentPropsWithoutRef 168 | >(({ className, ...props }, ref) => ( 169 | 174 | )); 175 | 176 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; 177 | 178 | const DropdownMenuShortcut = ({ 179 | className, 180 | ...props 181 | }: React.HTMLAttributes) => { 182 | return ( 183 | 187 | ); 188 | }; 189 | 190 | DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; 191 | 192 | export { 193 | DropdownMenu, 194 | DropdownMenuCheckboxItem, 195 | DropdownMenuContent, 196 | DropdownMenuGroup, 197 | DropdownMenuItem, 198 | DropdownMenuLabel, 199 | DropdownMenuPortal, 200 | DropdownMenuRadioGroup, 201 | DropdownMenuRadioItem, 202 | DropdownMenuSeparator, 203 | DropdownMenuShortcut, 204 | DropdownMenuSub, 205 | DropdownMenuSubContent, 206 | DropdownMenuSubTrigger, 207 | DropdownMenuTrigger, 208 | }; 209 | -------------------------------------------------------------------------------- /packages/ui/src/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /packages/ui/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 | } 7 | -------------------------------------------------------------------------------- /packages/ui/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | module.exports = require('config/tailwind.config'); 2 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@ui/*": ["./src/*"], 7 | "ui2": ["./src/components/ui/index.ts"] 8 | }, 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true 12 | }, 13 | "include": ["src", "index.ts"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**", ".next/**"] 7 | }, 8 | "lint": { 9 | "outputs": [] 10 | }, 11 | "test": {}, 12 | "dev": {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vitest.workspaces.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from 'vitest/config'; 2 | 3 | export default defineWorkspace(['apps/*', 'packages/*']); 4 | --------------------------------------------------------------------------------