├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── README.md ├── apps ├── backend │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── middlewares │ │ │ ├── ErrorHandler.ts │ │ │ ├── Limiter.ts │ │ │ └── Logger.ts │ │ ├── oauthStrategy │ │ │ ├── github-oauth.ts │ │ │ └── google-oauth.ts │ │ └── routes │ │ │ └── userRoutes.ts │ ├── tests │ │ └── app.test.ts │ ├── tsconfig.jest.json │ ├── tsconfig.json │ └── turbo.json └── frontend │ ├── .gitignore │ ├── README.md │ ├── eslint.config.js │ ├── eslintrc.json │ ├── index.html │ ├── jest.config.ts │ ├── jest.setup.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ └── vite.svg │ ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── components │ │ └── Main │ │ │ └── Main.tsx │ ├── index.css │ ├── main.tsx │ ├── store │ │ └── booksStore.tsx │ ├── test │ │ └── App.test.tsx │ └── vite-env.d.ts │ ├── tailwind.config.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── turbo.json │ └── vite.config.ts ├── package-lock.json ├── package.json ├── packages ├── db │ ├── .env.example │ ├── .gitignore │ ├── package.json │ ├── prisma │ │ ├── migrations │ │ │ ├── 20240901144037_init │ │ │ │ └── migration.sql │ │ │ ├── 20240902175240_user │ │ │ │ └── migration.sql │ │ │ ├── 20240902175506_fg │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ └── schema.prisma │ └── src │ │ └── index.ts ├── eslint-config │ ├── README.md │ ├── library.js │ ├── next.js │ ├── package.json │ └── react-internal.js ├── typescript-config │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── ui │ ├── .eslintrc.js │ ├── README.md │ ├── components.json │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── components │ │ └── ui │ │ │ ├── button.tsx │ │ │ └── card.tsx │ ├── global.css │ └── lib │ │ └── utils.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── tsconfig.lint.json └── turbo.json /.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 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/build-your-own/1fbad37e557cba5ab662d5ddbba7e7aa9de2942b/.npmrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build Your Own X 2 | 3 | ## Overview 4 | 5 | The goal of this project is to create a robust platform where users can build various "Build Your Own X" projects using multiple programming languages. 6 | 7 | ## Key Features 8 | 9 | 1. **Documentation Templates** 10 | 11 | - Provide detailed, language-specific documentation templates for each "Build Your Own X" project. 12 | - Support multiple languages such as Python, JavaScript, Go, Ruby, and more. 13 | 14 | 2. **Private Git Repositories** 15 | 16 | - Host projects in private Git repositories to ensure secure and isolated environments. 17 | - Repositories named with unique project and user identifiers (e.g., `project--user-`). 18 | 19 | 3. **User Workflow** 20 | 21 | - Users clone their assigned repositories to start working on projects. 22 | - Make incremental commits as they progress, following the documentation steps. 23 | - Push changes to the remote repository regularly to track progress. 24 | 25 | 4. **Automated Testing & Real-time Feedback** 26 | - Run automated tests after every commit to validate progress. 27 | - Provide real-time feedback using WebSockets or polling to indicate pass/fail status of each step. 28 | - Use visual indicators for easy tracking of progress and status. 29 | 30 | ## Architecture 31 | 32 | ### 1. Monorepo Structure with Turborepo 33 | 34 | - **Monorepo Tooling:** Use **Turborepo** to manage the monorepo, which will host both frontend and backend projects. This setup will facilitate shared configurations, faster build times, and easier dependency management. 35 | 36 | ### 2. Frontend: React Application 37 | 38 | - **Framework:** React with Vite 39 | - **State Management:** Zustand and React Query for data fetching and state management. 40 | - **UI Library:** Tailwind CSS + ShadCN for styling components. 41 | - **Routing:** React Router for routing needs. 42 | - **Testing:** Jest and React Testing Library for unit and integration tests. 43 | - **Build and Dev:** Use Turborepo to run build and development tasks efficiently. 44 | 45 | ### 3. Backend: Express Application 46 | 47 | - **Framework:** Express.js for handling HTTP requests and routing. 48 | - **Database:** Prisma ORM for database interactions (PostgreSQL as the database). 49 | - **Authentication:** Passport.js with Google and Github OAuth for authentication. 50 | - **Middleware:** Include common middleware for logging, error handling, and security. 51 | - **Testing:** Use Jest for backend testing. 52 | - **Build and Dev:** Integrated with Turborepo for streamlined development. 53 | 54 | ## Setup and Configuration 55 | 56 | ### 1. TypeScript Configuration 57 | 58 | - **Request:** Add TypeScript configurations to both frontend and backend projects to enable strong typing and better developer experience. 59 | - Configure TypeScript in the root of the monorepo for shared settings. 60 | - Specific `tsconfig.json` files for React and Express apps for tailored configurations. 61 | 62 | ### 2. ESLint and Prettier Configuration 63 | 64 | - **Request:** Add Eslint and Prettier as a common package to enable strong linting and better developer experience. 65 | - Specific eslint and prettier config files for React and Express apps for tailored configurations . 66 | 67 | ### 3. Tailwind CSS Configuration 68 | 69 | - **Request:** Set up Tailwind CSS configuration as a separate package to maintain a consistent design system across projects. 70 | - Centralize `tailwind.config.js` in a shared package within the monorepo. 71 | - Ensure that both frontend apps and UI components can import and utilize the same Tailwind setup. 72 | 73 | ### 4. ShadCN UI Library 74 | 75 | - **Request:** Create a separate package for ShadCN UI components to promote reuse and consistency. 76 | - Develop shared React components with ShadCN, stored in a `ui` package. 77 | - Ensure these components are easily importable into any React application within the monorepo. 78 | 79 | ### 5. Database Setup 80 | 81 | - **Request:** Configure a separate package for database interactions using Prisma. 82 | - Define the database schema and migrations within a dedicated `db` package. 83 | - Centralize Prisma configurations to streamline database setup and ensure consistent database access across different services. -------------------------------------------------------------------------------- /apps/backend/.env.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/build-your-own/1fbad37e557cba5ab662d5ddbba7e7aa9de2942b/apps/backend/.env.example -------------------------------------------------------------------------------- /apps/backend/.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 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | -------------------------------------------------------------------------------- /apps/backend/README.md: -------------------------------------------------------------------------------- 1 | # build-your-own-x backend -------------------------------------------------------------------------------- /apps/backend/jest.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', // Test environment setup for Node.js 5 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], // Extensions Jest will look for 6 | transform: { 7 | '^.+\\.tsx?$': 'ts-jest', 8 | }, 9 | testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], // Specify test file patterns 10 | globals: { 11 | 'ts-jest': { 12 | isolatedModules: true, // Optional for performance: isolates modules to speed up tests 13 | }, 14 | }, 15 | collectCoverage: true, 16 | coverageDirectory: 'coverage', 17 | coverageReporters: ['text', 'lcov'], 18 | }; -------------------------------------------------------------------------------- /apps/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "apps/backend/src/index.ts", 7 | "scripts": { 8 | "dev": "npx tsx watch src/index.ts", 9 | "start": "node dist/index.js", 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build": "esbuild src/index.ts --platform=node --bundle --outdir=dist" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@jest/types": "^29.6.3", 18 | "@types/cookie-parser": "^1.4.7", 19 | "@types/cors": "^2.8.17", 20 | "@types/express": "^4.17.21", 21 | "@types/express-session": "^1.18.0", 22 | "@types/jest": "^29.5.12", 23 | "@types/morgan": "^1.9.9", 24 | "@types/node": "^22.5.1", 25 | "@types/passport": "^1.0.16", 26 | "@types/passport-github2": "^1.2.9", 27 | "@types/passport-google-oauth2": "^0.1.10", 28 | "@types/passport-local": "^1.0.38", 29 | "dotenv": "^16.4.5", 30 | "eslint": "^9.9.1", 31 | "jest": "^29.7.0", 32 | "morgan": "^1.10.0", 33 | "passport-github2": "^0.1.12", 34 | "passport-google-oauth2": "^0.2.0", 35 | "prettier": "^3.3.3", 36 | "ts-jest": "^29.2.5", 37 | "ts-node": "^10.9.2", 38 | "ts-node-dev": "^2.0.0", 39 | "tsx": "^4.19.0", 40 | "turbo": "^2.1.1", 41 | "typescript": "^5.5.4" 42 | }, 43 | "dependencies": { 44 | "@repo/db": "*", 45 | "@repo/eslint-config": "*", 46 | "@repo/typescript-config": "*", 47 | "cors": "^2.8.5", 48 | "esbuild": "^0.23.1", 49 | "express": "^4.19.2", 50 | "express-rate-limit": "^7.4.0", 51 | "express-session": "^1.18.0", 52 | "helmet": "^7.1.0", 53 | "nodemon": "^3.1.4", 54 | "passport": "^0.7.0", 55 | "passport-local": "^1.0.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | import { prisma } from "@repo/db"; 4 | import cookieParser from "cookie-parser"; 5 | import cors from "cors"; 6 | import express, { Request, Response } from "express"; 7 | import session from "express-session"; 8 | import helmet from "helmet"; 9 | import morgan from "morgan"; 10 | import passport from "passport"; 11 | import { Strategy as LocalStrategy } from "passport-local"; 12 | import ErrorHandler from "./middlewares/ErrorHandler"; 13 | import limiter from "./middlewares/Limiter"; 14 | import Logger from "./middlewares/Logger"; 15 | import { userRouter } from "./routes/userRoutes"; 16 | 17 | const app = express(); 18 | const port = process.env.PORT || 3000; 19 | 20 | app.use(express.json()); 21 | app.use(ErrorHandler); 22 | app.use(Logger); 23 | app.use(morgan("common")); 24 | app.use(helmet()); 25 | app.use(cors()); 26 | app.use(limiter); 27 | app.use(express.json()); 28 | app.use(cookieParser()); 29 | 30 | app.use( 31 | session({ 32 | secret: "your-secret-key", 33 | resave: false, 34 | saveUninitialized: false, 35 | cookie: { secure: false }, 36 | }) 37 | ); 38 | // Initialize Passport 39 | app.use(passport.initialize()); 40 | app.use(passport.session()); 41 | 42 | app.use("/", userRouter); 43 | app.get("/", (req: Request, res: Response) => { 44 | res.send("Welcome"); 45 | }); 46 | 47 | const users = [{ id: 1, username: "user", password: "password" }]; 48 | 49 | passport.use( 50 | new LocalStrategy((username: string, password: string, done: any) => { 51 | const user = users.find((u) => u.username === username); 52 | if (!user) { 53 | return done(null, false, { message: "Incorrect username." }); 54 | } 55 | if (user.password !== password) { 56 | return done(null, false, { message: "Incorrect password." }); 57 | } 58 | return done(null, user); 59 | }) 60 | ); 61 | 62 | passport.serializeUser((user: any, done: any) => { 63 | done(null, user.id); 64 | }); 65 | passport.deserializeUser((id: any, done: any) => { 66 | const user = users.find((u) => u.id === id); 67 | done(null, user); 68 | }); 69 | 70 | app.post( 71 | "/login", 72 | passport.authenticate("local", { 73 | successRedirect: "/profile", 74 | failureRedirect: "/login", 75 | failureFlash: true, 76 | }) 77 | ); 78 | 79 | app.get("/profile", (req: any, res: any) => { 80 | if (req.isAuthenticated()) { 81 | res.send("Welcome to your profile"); 82 | } else { 83 | res.redirect("/login"); 84 | } 85 | }); 86 | app.get("/login", (req, res) => { 87 | res.send("Login page"); 88 | }); 89 | 90 | app.post("/post", async (req: Request, res: Response) => { 91 | try { 92 | const { title, content } = req.body; 93 | if (!title || !content) { 94 | return res 95 | .status(400) 96 | .json({ message: "Please input Title and Content" }); 97 | } 98 | 99 | function ensureAuthenticated(req: any, res: any, next: any) { 100 | if (req.isAuthenticated()) { 101 | return next(); 102 | } 103 | res.redirect("/login"); 104 | } 105 | app.get("/protected", ensureAuthenticated, (req, res) => { 106 | res.send("This is a protected route"); 107 | }); 108 | 109 | const blog = await prisma.post.create({ 110 | data: { title, content }, 111 | }); 112 | 113 | return res 114 | .status(201) 115 | .json({ message: "Blog created successfully", data: blog }); 116 | } catch (error) { 117 | return res.status(500).json({ message: "Error creating blog" }); 118 | } 119 | }); 120 | 121 | app.get("/posts", async (req: Request, res: Response) => { 122 | try { 123 | const blogs = await prisma.post.findMany(); 124 | return res.status(201).json({ data: blogs.length, blogs }); 125 | } catch (error) { 126 | return res.status(500).json({ message: "Error fetching blogs" }); 127 | } 128 | }); 129 | 130 | app.listen(port, () => { 131 | console.log(`Server listening on ${port}`); 132 | }); 133 | -------------------------------------------------------------------------------- /apps/backend/src/middlewares/ErrorHandler.ts: -------------------------------------------------------------------------------- 1 | const ErrorHandler = ( 2 | err: { statusCode: number; message: string; stack: any }, 3 | req: any, 4 | res: any, 5 | next: any 6 | ) => { 7 | console.log("Middleware Error handling"); 8 | const errStatus = err.statusCode || 500; 9 | const errMsg = err.message || "Something went wrong"; 10 | res.status(errStatus).json({ 11 | success: false, 12 | status: errStatus, 13 | message: errMsg, 14 | stack: process.env.NODE_ENV === "development" ? err.stack : {}, 15 | }); 16 | }; 17 | 18 | export default ErrorHandler; 19 | -------------------------------------------------------------------------------- /apps/backend/src/middlewares/Limiter.ts: -------------------------------------------------------------------------------- 1 | import rateLimit from "express-rate-limit" 2 | 3 | const limiter = rateLimit({ 4 | windowMs: 60 * 1000, // 1 minute 5 | max: 50, // limit each IP to 50 requests per windowMs 6 | message: "Too many accounts created from this IP, please try again after a minute" 7 | }); 8 | export default limiter -------------------------------------------------------------------------------- /apps/backend/src/middlewares/Logger.ts: -------------------------------------------------------------------------------- 1 | const Logger = ( 2 | req: any, 3 | res: any, 4 | next: any 5 | ) => { 6 | console.log("Middleware Error handling"); 7 | req.time = new Date(Date.now()).toString(); 8 | console.log(req.method,req.hostname, req.path, req.time); 9 | next(); 10 | }; 11 | 12 | export default Logger; 13 | -------------------------------------------------------------------------------- /apps/backend/src/oauthStrategy/github-oauth.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "@repo/db"; 2 | import passport from "passport"; 3 | import { Strategy as GithubStrategy } from "passport-github2"; 4 | 5 | const git_client_id = process.env.GIT_CLIENT_ID!; 6 | const git_client_secret = process.env.GIT_CLIENT_SECRET!; 7 | 8 | passport.use( 9 | new GithubStrategy( 10 | { 11 | clientID: git_client_id, 12 | clientSecret: git_client_secret, 13 | callbackURL: "http://localhost:3000/auth/github/callback", 14 | }, 15 | async (accessToken: any, refreshToken: string, profile: any, done: any) => { 16 | const name = profile.displayName || profile.username || "No Name"; 17 | const user = await prisma.user.upsert({ 18 | where: { email: "" }, 19 | update: { name: name }, 20 | create: { 21 | email: "", 22 | name: name, 23 | }, 24 | }); 25 | console.log(user); 26 | 27 | return done(null, profile); 28 | } 29 | ) 30 | ); 31 | 32 | passport.serializeUser(function (user: any, done: any) { 33 | done(null, user.id); // Serialize user by ID 34 | }); 35 | 36 | passport.deserializeUser(async function (id: any, done: any) { 37 | try { 38 | const user = await prisma.user.findUnique({ where: { id: id } }); 39 | done(null, user); 40 | } catch (error) { 41 | done(error, null); 42 | } 43 | }); 44 | 45 | export default passport; 46 | -------------------------------------------------------------------------------- /apps/backend/src/oauthStrategy/google-oauth.ts: -------------------------------------------------------------------------------- 1 | import { prisma } from "@repo/db"; 2 | import passport from "passport"; 3 | import { 4 | Strategy as GoogleStrategy 5 | } from "passport-google-oauth2"; 6 | 7 | 8 | const client_id = process.env.CLIENT_ID!; 9 | const client_secret = process.env.CLIENT_SECRET!; 10 | 11 | interface types { 12 | email: string; 13 | given_name: string; 14 | name: string; 15 | } 16 | passport.use( 17 | new GoogleStrategy( 18 | { 19 | // clientID: client_id, 20 | clientID: client_id, 21 | clientSecret: client_secret, 22 | callbackURL: "http://localhost:3000/auth/google/callback", 23 | passReqToCallback: true, 24 | }, 25 | 26 | async function ( 27 | request: any, 28 | accessToken: any, 29 | refreshToken: any, 30 | profile: types, 31 | done: (arg0: { 32 | id: string; 33 | name: string; 34 | email: string; 35 | password: string | null; 36 | }) => any 37 | ) { 38 | const { email, given_name }: types = profile; 39 | 40 | const upsertUser = await prisma.user.upsert({ 41 | where: { 42 | email: email, 43 | }, 44 | update: { 45 | name: given_name, 46 | }, 47 | create: { 48 | email: email, 49 | name: given_name, 50 | }, 51 | }); 52 | console.log(upsertUser); 53 | 54 | return done(upsertUser); 55 | } 56 | ) 57 | ); 58 | 59 | export default passport; 60 | -------------------------------------------------------------------------------- /apps/backend/src/routes/userRoutes.ts: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import passport from "../oauthStrategy/google-oauth" 3 | import gitPassport from "../oauthStrategy/github-oauth" 4 | export const userRouter=express.Router() 5 | 6 | userRouter.get("/success",(req,res)=>{ 7 | res.send("jelllpo") 8 | }) 9 | userRouter.get( 10 | "/auth/google", 11 | passport.authenticate("google", { scope: ["email", "profile"] }) 12 | 13 | ); 14 | 15 | userRouter.get( 16 | "/auth/google/callback", 17 | passport.authenticate("google", { 18 | 19 | failureRedirect: "/auth/google/failure", 20 | }),(req,res)=>{ 21 | console.log("reached here"); 22 | 23 | res.redirect("/") 24 | } 25 | ); 26 | 27 | 28 | userRouter.get('/auth/github',gitPassport.authenticate("github",{ 29 | 30 | scope: [ 'user:email' ] })) 31 | userRouter.get( 32 | "/auth/github/callback", 33 | gitPassport.authenticate("github", { 34 | 35 | failureRedirect: "/auth/github/failure", 36 | }),(req,res)=>{ 37 | res.redirect('/') } 38 | ); 39 | 40 | userRouter.get("/auth/github/failure", (req, res) => { 41 | res.send("GitHub authentication failed"); 42 | }); 43 | 44 | 45 | -------------------------------------------------------------------------------- /apps/backend/tests/app.test.ts: -------------------------------------------------------------------------------- 1 | // your tests -------------------------------------------------------------------------------- /apps/backend/tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "baseUrl": ".", 10 | "paths": { 11 | "@src/*": [ 12 | "src/*" 13 | ] 14 | } 15 | }, 16 | "include": [ 17 | "src/**/*.ts", 18 | "tests/**/*.ts" 19 | ], 20 | "exclude": [ 21 | "node_modules", 22 | "dist" 23 | ] 24 | } -------------------------------------------------------------------------------- /apps/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@repo/typescript-config/nextjs.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "module": "CommonJS", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "types": ["node", "jest"] 14 | }, 15 | "include": ["src"], 16 | "exclude": ["node_modules", "dist"] 17 | } -------------------------------------------------------------------------------- /apps/backend/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": ["dist/**"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /apps/frontend/README.md: -------------------------------------------------------------------------------- 1 | # build-your-own-x frontend -------------------------------------------------------------------------------- /apps/frontend/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 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 9 | files: ['**/*.{ts,tsx}'], 10 | languageOptions: { 11 | ecmaVersion: 2020, 12 | globals: globals.browser, 13 | }, 14 | plugins: { 15 | 'react-hooks': reactHooks, 16 | 'react-refresh': reactRefresh, 17 | }, 18 | rules: { 19 | ...reactHooks.configs.recommended.rules, 20 | 'react-refresh/only-export-components': [ 21 | 'warn', 22 | { allowConstantExport: true }, 23 | ], 24 | }, 25 | ignores: [ 26 | 'dist', 27 | 'tailwind.config.ts', 28 | 'postcss.config.mjs' 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /apps/frontend/eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "jest/globals": true 6 | }, 7 | "extends": [ 8 | "plugin:react/recommended", 9 | "plugin:jest/recommended", 10 | "airbnb", 11 | "prettier" 12 | ], 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | }, 17 | "ecmaVersion": "latest", 18 | "sourceType": "module" 19 | }, 20 | "plugins": ["react", "jest"], 21 | "rules": { 22 | "no-underscore-dangle": 0, 23 | "import/extensions": [ 24 | "error", 25 | "ignorePackages", 26 | { 27 | "js": "always", 28 | "jsx": "always" 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /apps/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/frontend/jest.config.ts: -------------------------------------------------------------------------------- 1 | export { }; 2 | module.exports = { 3 | collectCoverage: true, 4 | collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts', 5 | '!**/vendor/**'], 6 | coverageDirectory: 'coverage', 7 | testEnvironment: 'jsdom', 8 | transform: { 9 | ".(ts|tsx)": "ts-jest" 10 | }, 11 | 12 | coveragePathIgnorePatterns: [ 13 | "/node_modules/", 14 | "/coverage", 15 | "package.json", 16 | "package-lock.json", 17 | "reportWebVitals.ts", 18 | "setupTests.ts", 19 | "index.tsx" 20 | ], 21 | setupFilesAfterEnv: ['/setupTests.ts'], 22 | }; -------------------------------------------------------------------------------- /apps/frontend/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | -------------------------------------------------------------------------------- /apps/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview", 11 | "test": "jest", 12 | "coverage": "jest --coverage" 13 | }, 14 | "dependencies": { 15 | "@testing-library/react": "^16.0.1", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1", 18 | "react-router-dom": "^6.26.1", 19 | "tailwind-merge": "^2.5.2", 20 | "tailwindcss-animate": "^1.0.7", 21 | "zustand": "^4.5.5" 22 | }, 23 | "devDependencies": { 24 | "@babel/preset-typescript": "^7.24.7", 25 | "@eslint/js": "^9.9.0", 26 | "@repo/eslint-config": "*", 27 | "@repo/typescript-config": "*", 28 | "@repo/ui": "*", 29 | "@types/jest": "^29.5.12", 30 | "@types/node": "^22.5.1", 31 | "@types/react": "^18.3.3", 32 | "@types/react-dom": "^18.3.0", 33 | "@vitejs/plugin-react": "^4.3.1", 34 | "autoprefixer": "^10.4.20", 35 | "eslint": "^9.9.0", 36 | "eslint-plugin-jest": "^28.8.1", 37 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 38 | "eslint-plugin-react-refresh": "^0.4.9", 39 | "globals": "^15.9.0", 40 | "jest": "^29.7.0", 41 | "jest-environment-jsdom": "^29.7.0", 42 | "postcss": "^8.4.42", 43 | "tailwindcss": "^3.4.10", 44 | "typescript": "^5.5.3", 45 | "typescript-eslint": "^8.0.1", 46 | "vite": "^5.4.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /apps/frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /apps/frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 2 | import Main from "./components/Main/Main"; 3 | 4 | const App = () => { 5 | return ( 6 | <> 7 | 8 | 9 | } path="/" /> 10 | 11 | 12 | 13 | ); 14 | }; 15 | export default App; 16 | -------------------------------------------------------------------------------- /apps/frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/frontend/src/components/Main/Main.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@repo/ui/components/ui/button"; 2 | import { useBookStore } from '@/store/booksStore'; 3 | 4 | const Main = () => { 5 | const amount = useBookStore(state => state.amount) 6 | const updateAmount = useBookStore(state => state.updateAmount) 7 | return ( 8 | <> 9 |
Buid your own X
10 | 11 |
12 |

Books: {amount}

13 | 17 | 18 |
19 | 20 | ) 21 | } 22 | 23 | export default Main -------------------------------------------------------------------------------- /apps/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | @layer base { 5 | :root { 6 | --background: 0 0% 100%; 7 | --foreground: 0 0% 3.9%; 8 | --card: 0 0% 100%; 9 | --card-foreground: 0 0% 3.9%; 10 | --popover: 0 0% 100%; 11 | --popover-foreground: 0 0% 3.9%; 12 | --primary: 0 0% 9%; 13 | --primary-foreground: 0 0% 98%; 14 | --secondary: 0 0% 96.1%; 15 | --secondary-foreground: 0 0% 9%; 16 | --muted: 0 0% 96.1%; 17 | --muted-foreground: 0 0% 45.1%; 18 | --accent: 0 0% 96.1%; 19 | --accent-foreground: 0 0% 9%; 20 | --destructive: 0 84.2% 60.2%; 21 | --destructive-foreground: 0 0% 98%; 22 | --border: 0 0% 89.8%; 23 | --input: 0 0% 89.8%; 24 | --ring: 0 0% 3.9%; 25 | --chart-1: 12 76% 61%; 26 | --chart-2: 173 58% 39%; 27 | --chart-3: 197 37% 24%; 28 | --chart-4: 43 74% 66%; 29 | --chart-5: 27 87% 67%; 30 | --radius: 0.5rem 31 | } 32 | .dark { 33 | --background: 0 0% 3.9%; 34 | --foreground: 0 0% 98%; 35 | --card: 0 0% 3.9%; 36 | --card-foreground: 0 0% 98%; 37 | --popover: 0 0% 3.9%; 38 | --popover-foreground: 0 0% 98%; 39 | --primary: 0 0% 98%; 40 | --primary-foreground: 0 0% 9%; 41 | --secondary: 0 0% 14.9%; 42 | --secondary-foreground: 0 0% 98%; 43 | --muted: 0 0% 14.9%; 44 | --muted-foreground: 0 0% 63.9%; 45 | --accent: 0 0% 14.9%; 46 | --accent-foreground: 0 0% 98%; 47 | --destructive: 0 62.8% 30.6%; 48 | --destructive-foreground: 0 0% 98%; 49 | --border: 0 0% 14.9%; 50 | --input: 0 0% 14.9%; 51 | --ring: 0 0% 83.1%; 52 | --chart-1: 220 70% 50%; 53 | --chart-2: 160 60% 45%; 54 | --chart-3: 30 80% 55%; 55 | --chart-4: 280 65% 60%; 56 | --chart-5: 340 75% 55% 57 | } 58 | } 59 | @layer base { 60 | * { 61 | @apply border-border; 62 | } 63 | body { 64 | @apply bg-background text-foreground; 65 | } 66 | } -------------------------------------------------------------------------------- /apps/frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /apps/frontend/src/store/booksStore.tsx: -------------------------------------------------------------------------------- 1 | import create from 'zustand' 2 | 3 | interface IBook { 4 | amount: number 5 | updateAmount: (newAmount: number) => void 6 | } 7 | 8 | export const useBookStore = create( (set, get) => ({ 9 | amount: 40, 10 | updateAmount: (newAmount: number ) => { 11 | 12 | const amountState = get().amount 13 | 14 | set({ amount: newAmount + amountState }) 15 | }, 16 | })); -------------------------------------------------------------------------------- /apps/frontend/src/test/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from '@/App'; 3 | 4 | describe('App tests', () => { 5 | it('should contains the heading 1', () => { 6 | render(); 7 | const heading = screen.getByText(/Hello world! I am using React/i); 8 | expect(heading).toBeDefined() 9 | }); 10 | }); -------------------------------------------------------------------------------- /apps/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /apps/frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | darkMode: ["class"], 4 | content: [ 5 | "./index.html", 6 | "./src/**/*.{js,ts,jsx,tsx}", 7 | ], 8 | theme: { 9 | extend: { 10 | borderRadius: { 11 | lg: 'var(--radius)', 12 | md: 'calc(var(--radius) - 2px)', 13 | sm: 'calc(var(--radius) - 4px)' 14 | }, 15 | colors: { 16 | background: 'hsl(var(--background))', 17 | foreground: 'hsl(var(--foreground))', 18 | card: { 19 | DEFAULT: 'hsl(var(--card))', 20 | foreground: 'hsl(var(--card-foreground))' 21 | }, 22 | popover: { 23 | DEFAULT: 'hsl(var(--popover))', 24 | foreground: 'hsl(var(--popover-foreground))' 25 | }, 26 | primary: { 27 | DEFAULT: 'hsl(var(--primary))', 28 | foreground: 'hsl(var(--primary-foreground))' 29 | }, 30 | secondary: { 31 | DEFAULT: 'hsl(var(--secondary))', 32 | foreground: 'hsl(var(--secondary-foreground))' 33 | }, 34 | muted: { 35 | DEFAULT: 'hsl(var(--muted))', 36 | foreground: 'hsl(var(--muted-foreground))' 37 | }, 38 | accent: { 39 | DEFAULT: 'hsl(var(--accent))', 40 | foreground: 'hsl(var(--accent-foreground))' 41 | }, 42 | destructive: { 43 | DEFAULT: 'hsl(var(--destructive))', 44 | foreground: 'hsl(var(--destructive-foreground))' 45 | }, 46 | border: 'hsl(var(--border))', 47 | input: 'hsl(var(--input))', 48 | ring: 'hsl(var(--ring))', 49 | chart: { 50 | '1': 'hsl(var(--chart-1))', 51 | '2': 'hsl(var(--chart-2))', 52 | '3': 'hsl(var(--chart-3))', 53 | '4': 'hsl(var(--chart-4))', 54 | '5': 'hsl(var(--chart-5))' 55 | } 56 | } 57 | } 58 | }, 59 | plugins: [require("tailwindcss-animate")], 60 | } -------------------------------------------------------------------------------- /apps/frontend/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 | "baseUrl": ".", 10 | "paths": { 11 | "@/*": [ 12 | "./src/*" 13 | ] 14 | }, 15 | 16 | /* Bundler mode */ 17 | "moduleResolution": "bundler", 18 | "allowImportingTsExtensions": true, 19 | "isolatedModules": true, 20 | "moduleDetection": "force", 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | 24 | /* Linting */ 25 | "strict": true, 26 | "noUnusedLocals": true, 27 | "noUnusedParameters": true, 28 | "noFallthroughCasesInSwitch": true 29 | }, 30 | "include": ["src"] 31 | } 32 | -------------------------------------------------------------------------------- /apps/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@repo/typescript-config/nextjs.json", 3 | "files": [], 4 | "references": [ 5 | { 6 | "path": "./tsconfig.app.json" 7 | }, 8 | { 9 | "path": "./tsconfig.node.json" 10 | } 11 | ], 12 | "compilerOptions": { 13 | "baseUrl": ".", 14 | "paths": { 15 | "@/*": ["./src/*"] 16 | } 17 | }, 18 | "exclude": ["node_modules"], 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/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 | -------------------------------------------------------------------------------- /apps/frontend/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": ["dist/**"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /apps/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import react from "@vitejs/plugin-react" 3 | import { defineConfig } from "vite" 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "@": path.resolve(__dirname, "./src"), 10 | }, 11 | }, 12 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build-your-own", 3 | "private": true, 4 | "scripts": { 5 | "build": "turbo build", 6 | "dev": "turbo dev", 7 | "lint": "turbo lint", 8 | "format": "prettier --write \"**/*.{ts,tsx, js, jsx, md, mdx, css}\"", 9 | "prepare": "husky install" 10 | }, 11 | "lint-staged": { 12 | "src/**/*.{ts,tsx,js,jsx,md,mdx,css}": [ 13 | "npm run format", 14 | "npm run lint" 15 | ] 16 | }, 17 | "devDependencies": { 18 | "husky": "^8.0.0", 19 | "prettier": "^3.2.5", 20 | "turbo": "^2.1.1", 21 | "typescript": "^5.4.5" 22 | }, 23 | "engines": { 24 | "node": ">=18" 25 | }, 26 | "packageManager": "npm@10.5.0", 27 | "workspaces": [ 28 | "apps/*", 29 | "packages/*" 30 | ], 31 | "dependencies": { 32 | "cookie-parser": "^1.4.6", 33 | "passport-google-oauth2": "^0.2.0", 34 | "ts-node": "^10.9.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/db/.env.example: -------------------------------------------------------------------------------- 1 | 2 | DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public" -------------------------------------------------------------------------------- /packages/db/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env -------------------------------------------------------------------------------- /packages/db/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/db", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.ts", 6 | "type": "module", 7 | "scripts": { 8 | "db:seed": "prisma db seed" 9 | }, 10 | "prisma": { 11 | "seed": "ts-node prisma/seed.ts" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@prisma/client": "^5.19.0", 18 | "prisma": "^5.12.0" 19 | }, 20 | "exports": { 21 | ".": "./src/index.ts" 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/db/prisma/migrations/20240901144037_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Post" ( 3 | "id" SERIAL NOT NULL, 4 | "title" TEXT NOT NULL, 5 | "content" TEXT NOT NULL, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | "updatedAt" TIMESTAMP(3) NOT NULL, 8 | 9 | CONSTRAINT "Post_pkey" PRIMARY KEY ("id") 10 | ); 11 | -------------------------------------------------------------------------------- /packages/db/prisma/migrations/20240902175240_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "email" TEXT NOT NULL, 6 | "password" TEXT NOT NULL, 7 | 8 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 13 | -------------------------------------------------------------------------------- /packages/db/prisma/migrations/20240902175506_fg/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ALTER COLUMN "password" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /packages/db/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /packages/db/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // prisma/schema.prisma 2 | 3 | generator client { 4 | provider = "prisma-client-js" 5 | } 6 | 7 | datasource db { 8 | provider = "postgresql" 9 | url = env("DATABASE_URL") 10 | } 11 | 12 | // Post model represents individual blog posts 13 | 14 | model User { 15 | id String @id @default(uuid()) 16 | name String 17 | email String @unique 18 | password String? 19 | } 20 | 21 | model Post { 22 | id Int @id @default(autoincrement()) 23 | title String 24 | content String 25 | createdAt DateTime @default(now()) 26 | updatedAt DateTime @updatedAt 27 | } 28 | -------------------------------------------------------------------------------- /packages/db/src/index.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prismaClientSingleton = () => { 4 | return new PrismaClient(); 5 | }; 6 | 7 | type PrismaClientSingleton = ReturnType; 8 | 9 | // eslint-disable-next-line 10 | const globalForPrisma = globalThis as unknown as { 11 | prisma: PrismaClientSingleton | undefined; 12 | }; 13 | 14 | export const prisma = globalForPrisma.prisma ?? prismaClientSingleton(); 15 | 16 | if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; -------------------------------------------------------------------------------- /packages/eslint-config/README.md: -------------------------------------------------------------------------------- 1 | # `@turbo/eslint-config` 2 | 3 | Collection of internal eslint configurations. 4 | -------------------------------------------------------------------------------- /packages/eslint-config/library.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("node:path"); 2 | 3 | const project = resolve(process.cwd(), "tsconfig.json"); 4 | 5 | /** @type {import("eslint").Linter.Config} */ 6 | module.exports = { 7 | extends: ["eslint:recommended", "prettier", "turbo"], 8 | plugins: ["only-warn"], 9 | globals: { 10 | React: true, 11 | JSX: true, 12 | }, 13 | env: { 14 | node: true, 15 | }, 16 | settings: { 17 | "import/resolver": { 18 | typescript: { 19 | project, 20 | }, 21 | }, 22 | }, 23 | ignorePatterns: [ 24 | // Ignore dotfiles 25 | ".*.js", 26 | "node_modules/", 27 | "dist/", 28 | ], 29 | overrides: [ 30 | { 31 | files: ["*.js?(x)", "*.ts?(x)"], 32 | }, 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /packages/eslint-config/next.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("node:path"); 2 | 3 | const project = resolve(process.cwd(), "tsconfig.json"); 4 | 5 | /** @type {import("eslint").Linter.Config} */ 6 | module.exports = { 7 | extends: [ 8 | "eslint:recommended", 9 | "prettier", 10 | require.resolve("@vercel/style-guide/eslint/next"), 11 | "turbo", 12 | ], 13 | globals: { 14 | React: true, 15 | JSX: true, 16 | }, 17 | env: { 18 | node: true, 19 | browser: true, 20 | }, 21 | plugins: ["only-warn"], 22 | settings: { 23 | "import/resolver": { 24 | typescript: { 25 | project, 26 | }, 27 | }, 28 | }, 29 | ignorePatterns: [ 30 | // Ignore dotfiles 31 | ".*.js", 32 | "node_modules/", 33 | ], 34 | overrides: [{ files: ["*.js?(x)", "*.ts?(x)"] }], 35 | }; 36 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/eslint-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "files": [ 6 | "library.js", 7 | "next.js", 8 | "react-internal.js" 9 | ], 10 | "devDependencies": { 11 | "@vercel/style-guide": "^5.2.0", 12 | "eslint-config-turbo": "^2.0.0", 13 | "eslint-config-prettier": "^9.1.0", 14 | "eslint-plugin-only-warn": "^1.1.0", 15 | "@typescript-eslint/parser": "^7.1.0", 16 | "@typescript-eslint/eslint-plugin": "^7.1.0", 17 | "typescript": "^5.3.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/eslint-config/react-internal.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("node:path"); 2 | 3 | const project = resolve(process.cwd(), "tsconfig.json"); 4 | 5 | /* 6 | * This is a custom ESLint configuration for use with 7 | * internal (bundled by their consumer) libraries 8 | * that utilize React. 9 | */ 10 | 11 | /** @type {import("eslint").Linter.Config} */ 12 | module.exports = { 13 | extends: ["eslint:recommended", "prettier", "turbo"], 14 | plugins: ["only-warn"], 15 | globals: { 16 | React: true, 17 | JSX: true, 18 | }, 19 | env: { 20 | browser: true, 21 | }, 22 | settings: { 23 | "import/resolver": { 24 | typescript: { 25 | project, 26 | }, 27 | }, 28 | }, 29 | ignorePatterns: [ 30 | // Ignore dotfiles 31 | ".*.js", 32 | "node_modules/", 33 | "dist/", 34 | ], 35 | overrides: [ 36 | // Force ESLint to detect .tsx files 37 | { files: ["*.js?(x)", "*.ts?(x)"] }, 38 | ], 39 | }; 40 | -------------------------------------------------------------------------------- /packages/typescript-config/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "incremental": false, 9 | "isolatedModules": true, 10 | "lib": ["es2022", "DOM", "DOM.Iterable"], 11 | "module": "NodeNext", 12 | "moduleDetection": "force", 13 | "moduleResolution": "NodeNext", 14 | "noUncheckedIndexedAccess": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "target": "ES2022" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/typescript-config/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "plugins": [{ "name": "next" }], 7 | "module": "ESNext", 8 | "moduleResolution": "Bundler", 9 | "allowJs": true, 10 | "jsx": "preserve", 11 | "noEmit": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/typescript-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/typescript-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "publishConfig": { 7 | "access": "public" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/typescript-config/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "jsx": "react-jsx" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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 | parserOptions: { 7 | project: "./tsconfig.lint.json", 8 | tsconfigRootDir: __dirname, 9 | }, 10 | rules: { 11 | "no-redeclare": "off", 12 | }, 13 | ignorePatterns: [ 14 | "postcss.config.js", 15 | "tailwind.config.ts" 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /packages/ui/README.md: -------------------------------------------------------------------------------- 1 | # @repo/ui -------------------------------------------------------------------------------- /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": "@repo/ui/components", 15 | "utils": "@repo/ui/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/ui", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "ui:add": "npx shadcn@latest add", 7 | "lint": "eslint .", 8 | "generate:component": "turbo gen react-component" 9 | }, 10 | "peerDependencies": { 11 | "react": "^18" 12 | }, 13 | "devDependencies": { 14 | "@repo/eslint-config": "*", 15 | "@repo/typescript-config": "*", 16 | "@turbo/gen": "^1.12.4", 17 | "@types/eslint": "^8.56.5", 18 | "@types/node": "^20.11.24", 19 | "@types/react": "^18.2.61", 20 | "@types/react-dom": "^18.2.19", 21 | "autoprefixer": "^10.4.20", 22 | "eslint": "^8.57.0", 23 | "postcss": "^8.4.42", 24 | "postcss-load-config": "^6.0.1", 25 | "tailwindcss": "^3.4.10", 26 | "typescript": "^5.3.3" 27 | }, 28 | "dependencies": { 29 | "@radix-ui/react-slot": "^1.1.0", 30 | "class-variance-authority": "^0.7.0", 31 | "clsx": "^2.1.1", 32 | "lucide-react": "^0.437.0", 33 | "react": "^18", 34 | "tailwind-merge": "^2.5.2", 35 | "tailwindcss-animate": "^1.0.7" 36 | }, 37 | "exports": { 38 | "./globals.css": "./src/globals.css", 39 | "./postcss.config": "./postcss.config.mjs", 40 | "./tailwind.config": "./tailwind.config.ts", 41 | "./lib/*": "./src/lib/*.ts", 42 | "./components/*": [ 43 | "./src/components/*.tsx", 44 | "./src/components/*.ts" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /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 "@repo/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/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@repo/ui/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /packages/ui/src/global.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 | } -------------------------------------------------------------------------------- /packages/ui/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import tailwindcssAnimate from "tailwindcss-animate"; 3 | 4 | const config = { 5 | darkMode: ["class"], 6 | content: [ 7 | "./pages/**/*.{ts,tsx}", 8 | "./components/**/*.{ts,tsx}", 9 | "./app/**/*.{ts,tsx}", 10 | "./src/**/*.{ts,tsx}", 11 | "../../packages/ui/src/**/*.{ts,tsx}", 12 | ], 13 | prefix: "", 14 | theme: { 15 | container: { 16 | center: true, 17 | padding: "2rem", 18 | screens: { 19 | "2xl": "1400px", 20 | }, 21 | }, 22 | extend: { 23 | colors: { 24 | border: "hsl(var(--border))", 25 | input: "hsl(var(--input))", 26 | ring: "hsl(var(--ring))", 27 | background: "hsl(var(--background))", 28 | foreground: "hsl(var(--foreground))", 29 | primary: { 30 | DEFAULT: "hsl(var(--primary))", 31 | foreground: "hsl(var(--primary-foreground))", 32 | }, 33 | secondary: { 34 | DEFAULT: "hsl(var(--secondary))", 35 | foreground: "hsl(var(--secondary-foreground))", 36 | }, 37 | destructive: { 38 | DEFAULT: "hsl(var(--destructive))", 39 | foreground: "hsl(var(--destructive-foreground))", 40 | }, 41 | muted: { 42 | DEFAULT: "hsl(var(--muted))", 43 | foreground: "hsl(var(--muted-foreground))", 44 | }, 45 | accent: { 46 | DEFAULT: "hsl(var(--accent))", 47 | foreground: "hsl(var(--accent-foreground))", 48 | }, 49 | popover: { 50 | DEFAULT: "hsl(var(--popover))", 51 | foreground: "hsl(var(--popover-foreground))", 52 | }, 53 | card: { 54 | DEFAULT: "hsl(var(--card))", 55 | foreground: "hsl(var(--card-foreground))", 56 | }, 57 | }, 58 | borderRadius: { 59 | lg: "var(--radius)", 60 | md: "calc(var(--radius) - 2px)", 61 | sm: "calc(var(--radius) - 4px)", 62 | }, 63 | keyframes: { 64 | "accordion-down": { 65 | from: { height: "0" }, 66 | to: { height: "var(--radix-accordion-content-height)" }, 67 | }, 68 | "accordion-up": { 69 | from: { height: "var(--radix-accordion-content-height)" }, 70 | to: { height: "0" }, 71 | }, 72 | }, 73 | animation: { 74 | "accordion-down": "accordion-down 0.2s ease-out", 75 | "accordion-up": "accordion-up 0.2s ease-out", 76 | }, 77 | }, 78 | }, 79 | plugins: [tailwindcssAnimate], 80 | } satisfies Config; 81 | 82 | export default config; -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@repo/typescript-config/react-library.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@repo/ui/*": ["./src/*"] 7 | } 8 | }, 9 | "include": ["src"], 10 | "exclude": ["node_modules"] 11 | } -------------------------------------------------------------------------------- /packages/ui/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@repo/typescript-config/react-library.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src", "turbo"], 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "ui": "tui", 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "inputs": ["$TURBO_DEFAULT$", ".env*"], 8 | "outputs": [".next/**", "!.next/cache/**"] 9 | }, 10 | "lint": { 11 | "dependsOn": ["^lint"] 12 | }, 13 | "dev": { 14 | "cache": false, 15 | "persistent": true 16 | } 17 | } 18 | } 19 | --------------------------------------------------------------------------------