├── .eslintrc.js ├── .gitignore ├── .npmrc ├── README.md ├── apps └── web │ ├── app │ ├── api │ │ ├── getData │ │ │ └── route.ts │ │ ├── openai │ │ │ └── route.ts │ │ ├── questions │ │ │ └── [id] │ │ │ │ └── route.ts │ │ ├── run │ │ │ ├── route.ts │ │ │ └── runner.js │ │ └── test │ │ │ ├── route.ts │ │ │ └── worker.js │ ├── dashboard │ │ ├── layout.tsx │ │ └── page.tsx │ ├── editor │ │ ├── [id] │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── favicon.ico │ ├── layout.tsx │ ├── page.tsx │ ├── questions │ │ ├── [category] │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── signin │ │ ├── layout.tsx │ │ └── page.tsx │ └── signup │ │ ├── layout.tsx │ │ └── page.tsx │ ├── components.json │ ├── components │ ├── .gitkeep │ ├── AskAI.tsx │ ├── DisplayQuestions.tsx │ ├── Footer.tsx │ ├── QuestionsCard.tsx │ ├── Sidebar.tsx │ ├── SidebarQuestions.tsx │ ├── TableDemo.tsx │ ├── TechIcons.tsx │ ├── accordian.tsx │ ├── companies-cards.tsx │ ├── features-cards.tsx │ ├── hero-scroll.tsx │ ├── landing-header.tsx │ ├── magicui │ │ ├── marquee.tsx │ │ └── sparkles-text.tsx │ ├── message.tsx │ ├── miniTopicsCard.tsx │ ├── progress-cards.tsx │ ├── providers.tsx │ ├── questionCardSkeleton.tsx │ ├── reviews.tsx │ ├── sidebarLayout.tsx │ ├── sign-in-form.tsx │ ├── signup-form.tsx │ └── streak-bar.tsx │ ├── contexts │ └── CodeContext.tsx │ ├── data │ ├── comapines.tsx │ ├── languages.tsx │ └── topics.tsx │ ├── eslint.config.js │ ├── hooks │ ├── .gitkeep │ ├── queries.tsx │ ├── useDarkMode.tsx │ └── useDebounce.tsx │ ├── lib │ ├── .gitkeep │ ├── mongodb.ts │ └── query-client.ts │ ├── models │ └── Question.ts │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ └── images │ │ ├── logo-light.svg │ │ ├── logo.png │ │ ├── logo.svg │ │ └── pratiyank.jpg │ ├── tailwind.config.ts │ └── tsconfig.json ├── package.json ├── packages ├── editor │ ├── package.json │ ├── package.json.2565922834 │ ├── postcss.config.mjs │ ├── src │ │ ├── components │ │ │ ├── Companies.tsx │ │ │ ├── Editor.tsx │ │ │ └── TechLogo.tsx │ │ └── data │ │ │ └── questions.ts │ ├── tailwind.config.ts │ └── tsconfig.json ├── eslint-config │ ├── README.md │ ├── base.js │ ├── next.js │ ├── package.json │ └── react-internal.js ├── typescript-config │ ├── README.md │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── ui │ ├── components.json │ ├── eslint.config.js │ ├── package.json │ ├── postcss.config.mjs │ ├── src │ ├── components │ │ ├── .gitkeep │ │ ├── accordion.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── collapsible.tsx │ │ ├── dialog.tsx │ │ ├── input.tsx │ │ ├── navigation-menu.tsx │ │ ├── progress.tsx │ │ ├── scroll-animation.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── text-generate-effect.tsx │ │ └── tooltip.tsx │ ├── hooks │ │ └── .gitkeep │ ├── lib │ │ └── utils.ts │ └── styles │ │ └── globals.css │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.json └── turbo.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // This configuration only applies to the package manager root. 2 | /** @type {import("eslint").Linter.Config} */ 3 | module.exports = { 4 | ignorePatterns: ["apps/**", "packages/**"], 5 | extends: ["@workspace/eslint-config/library.js"], 6 | parser: "@typescript-eslint/parser", 7 | parserOptions: { 8 | project: true, 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /.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 | 34 | # Misc 35 | .DS_Store 36 | *.pem 37 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/.npmrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontend Forge 2 | 3 | 🚀 **Frontend Forge** is an open-source coding platform where developers can practice frontend challenges using **HTML, CSS, JavaScript, and React**. It features an embedded browser and code editor, allowing users to solve UI design challenges and logical coding problems interactively. 4 | 5 | --- 6 | 7 | ## 🌟 Features 8 | 9 | - 🖥 **UI Design Challenges** – Build and improve frontend skills by solving design problems. 10 | - 🧠 **Logical Coding Problems** – Solve JavaScript-based algorithmic challenges. 11 | - 📝 **Embedded Code Editor** – Write and test code directly in the browser. 12 | - ⚡ **Turborepo Monorepo** – Structured for scalable frontend development. 13 | - 🤝 **Open Source Contributions** – Add new challenges and help improve the platform! 14 | 15 | --- 16 | 17 | ## 📂 Folder Structure 18 | 19 | ``` 20 | frontend-forge/ 21 | ├── apps/ 22 | │ ├── web/ # Main NextJs application (frontend + backend) 23 | │ 24 | ├── packages/ 25 | │ ├── ui/ # ShadCN-based UI components 26 | │ ├── editor/ # Custom code editor component 27 | │ 28 | ├── .turbo/ # Turbo cache (ignored) 29 | ├── .next/ # Next.js build folder (ignored) 30 | ├── node_modules/ # Dependencies (ignored) 31 | ├── package.json # Root package.json for dependencies 32 | ├── turbo.json # Turborepo configuration 33 | └── README.md # You are here! 🎉 34 | ``` 35 | 36 | --- 37 | 38 | ## 🛠 Getting Started 39 | 40 | ### 1️⃣ **Fork & Clone the Repository** 41 | ```sh 42 | git clone https://github.com/Frontend-Forge/frontend-forge 43 | cd frontend-forge 44 | ``` 45 | 46 | ### 2️⃣ **Install Dependencies** 47 | ```sh 48 | pnpm install # Ensure you have pnpm installed 49 | ``` 50 | 51 | ### 3️⃣ **Set Up Environment Variables** 52 | Create a `.env.local` file inside `apps/web/` and add: 53 | ``` 54 | DATABASE_URL=your_database_url 55 | ``` 56 | 57 | ### 4️⃣ **Setup Database** 58 | - Create a collection named `questions` in your database. 59 | - You can find sample questions in [this repository](https://github.com/Frontend-Forge/questions). 60 | 61 | ### 5️⃣ **Run the Project** 62 | ```sh 63 | pnpm dev # Starts the development server 64 | ``` 65 | 🚀 The app should now be running at `http://localhost:3000` 🎉 66 | 67 | --- 68 | 69 | ## 🤝 Contributing 70 | 71 | We welcome contributions! Here’s how you can help: 72 | 73 | ### 🛠 **Setting Up the Project for Contribution** 74 | 1. **Fork the repository** and clone it locally. 75 | 2. Install dependencies using `pnpm install`. 76 | 3. Follow the [installation steps](#-getting-started). 77 | 4. Pick an issue from the [Issues tab](https://github.com/Frontend-Forge/frontend-forge/issues) and start working on it! 78 | 79 | ### 💡 **Want to Contribute Questions?** 80 | If you'd like to add new frontend challenges, visit the [Frontend Forge Questions](https://github.com/Frontend-Forge/questions) repository and follow the contribution guidelines. 81 | 82 | --- 83 | 84 | ## ⭐ Support the Project 85 | - Give this project a **star** ⭐ on GitHub! 86 | - Share with your friends and fellow developers! 87 | 88 | --- 89 | 90 | ## 📜 License 91 | Frontend Forge is open-source under the **MIT License**. 92 | 93 | 🚀 **Happy coding! Let’s build something amazing together.** 🎨💻 94 | -------------------------------------------------------------------------------- /apps/web/app/api/getData/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import connectDB from "@/lib/mongodb"; // Ensure this path is correct 3 | import Question from "@/models/Question"; // Import your Mongoose model 4 | 5 | export async function GET() { 6 | try { 7 | await connectDB(); // Connect to MongoDB using Mongoose 8 | 9 | const questions = await Question.find({}); // Fetch all questions 10 | 11 | return NextResponse.json({ success: true, data: questions }, { status: 200 }); 12 | } catch (error) { 13 | console.error("Database Error:", error); 14 | return NextResponse.json( 15 | { success: false, message: "Internal Server Error" }, 16 | { status: 500 } 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/app/api/openai/route.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable turbo/no-undeclared-env-vars */ 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | const openaiApiKey = process.env.OPENAI_API_KEY as string; 5 | 6 | export async function POST(request: NextRequest) { 7 | try { 8 | const body = await request.json(); 9 | 10 | const systemMessage = { 11 | role: "system", 12 | content: `You are a helpful assistant named Forger, running on model 2s2016 + JavaScript engine. Your job is to help users who are stuck with web development or LeetCode-style problems. You only know HTML, CSS, JavaScript, React, Next.js, Tailwind CSS, and related frontend/backend web development tools. You also understand logical coding problems like those found on LeetCode. 13 | 14 | Whenever a user asks something, assume they are stuck and looking for clear, simple explanations with code examples. You must explain things patiently, like a good teacher who wants the user to truly understand. 15 | 16 | You are strictly not allowed to answer anything outside your scope (e.g., topics like history, cooking, relationships, AI internals, etc.). If someone asks something off-topic, clearly respond: 17 | 18 | "Sorry, I can only help with web development or LeetCode-style logic problems. Please ask something related to that." 19 | 20 | Always provide well-commented and beginner-friendly code when possible, and break down your explanation in easy-to-understand steps. 21 | `, 22 | }; 23 | 24 | const messages = [systemMessage, ...body.messages]; 25 | 26 | const response = await fetch("https://api.openai.com/v1/chat/completions", { 27 | method: "POST", 28 | headers: { 29 | "Content-Type": "application/json", 30 | Authorization: `Bearer ${openaiApiKey}`, 31 | }, 32 | body: JSON.stringify({ 33 | model: "gpt-4", // or "gpt-3.5-turbo" 34 | messages, 35 | temperature: 0.7, 36 | }), 37 | }); 38 | 39 | const data = await response.json(); 40 | return NextResponse.json(data); 41 | } catch (error) { 42 | console.error("Error calling OpenAI API:", error); 43 | return NextResponse.json( 44 | { error: "Failed to fetch from OpenAI API" }, 45 | { status: 500 } 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /apps/web/app/api/questions/[id]/route.ts: -------------------------------------------------------------------------------- 1 | // app/api/questions/[id]/route.ts 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import mongoose from "mongoose"; 4 | import QuestionModel from "@/models/Question"; 5 | import connectDB from "@/lib/mongodb"; 6 | 7 | // Connect to MongoDB 8 | 9 | export async function GET(request: NextRequest) { 10 | // Extract ID from URL pattern 11 | const url = new URL(request.url); 12 | const pathParts = url.pathname.split("/"); 13 | const id = pathParts[pathParts.length - 1]; // Get the last segment of the path 14 | 15 | if (!id) { 16 | return NextResponse.json( 17 | { error: "Question ID is required" }, 18 | { status: 400 } 19 | ); 20 | } 21 | 22 | // Validate MongoDB ID format 23 | if (!mongoose.Types.ObjectId.isValid(id)) { 24 | return NextResponse.json( 25 | { error: "Invalid question ID format" }, 26 | { status: 400 } 27 | ); 28 | } 29 | 30 | try { 31 | await connectDB(); 32 | 33 | const question = await QuestionModel.findById(id).lean(); 34 | 35 | if (!question) { 36 | return NextResponse.json( 37 | { error: "Question not found" }, 38 | { status: 404 } 39 | ); 40 | } 41 | 42 | return NextResponse.json(question); 43 | } catch (error) { 44 | console.error("Error fetching question:", error); 45 | return NextResponse.json( 46 | { error: "Internal Server Error" }, 47 | { status: 500 } 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /apps/web/app/api/run/route.ts: -------------------------------------------------------------------------------- 1 | // app/api/run/route.ts 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import { Worker } from "worker_threads"; 4 | import path from "path"; 5 | import fs from "fs"; 6 | 7 | export async function POST(req: NextRequest) { 8 | try { 9 | const { userCode, input } = await req.json(); 10 | 11 | if (!userCode) { 12 | return NextResponse.json({ error: "Missing userCode" }, { status: 400 }); 13 | } 14 | 15 | // Extract function name from the code 16 | const functionName = extractFunctionName(userCode); 17 | 18 | if (!functionName) { 19 | return NextResponse.json( 20 | { error: "Could not determine function name" }, 21 | { status: 400 } 22 | ); 23 | } 24 | 25 | // Use a relative path that matches your monorepo structure 26 | const workerPath = path.join( 27 | process.cwd(), 28 | "app", 29 | "api", 30 | "run", 31 | "runner.js" 32 | ); 33 | 34 | // Verify the worker file exists 35 | if (!fs.existsSync(workerPath)) { 36 | console.error(`Worker file not found at: ${workerPath}`); 37 | return NextResponse.json( 38 | { error: `Worker file not found at: ${workerPath}` }, 39 | { status: 500 } 40 | ); 41 | } 42 | 43 | console.log("Runner worker path:", workerPath); 44 | 45 | // Execute code in worker 46 | const result = await new Promise((resolve) => { 47 | const worker = new Worker(workerPath, { 48 | workerData: { 49 | userCode, 50 | functionName, 51 | input, 52 | }, 53 | }); 54 | 55 | worker.on("message", (result) => { 56 | console.log("Runner result:", result); 57 | resolve(result); 58 | worker.terminate(); 59 | }); 60 | 61 | worker.on("error", (error) => { 62 | console.error("Runner error event:", error); 63 | resolve({ 64 | success: false, 65 | error: error.message, 66 | }); 67 | worker.terminate(); 68 | }); 69 | 70 | worker.on("exit", (code) => { 71 | if (code !== 0) { 72 | console.error("Runner exited with code:", code); 73 | resolve({ 74 | success: false, 75 | error: `Worker exited with code ${code}`, 76 | }); 77 | } 78 | }); 79 | }); 80 | 81 | return NextResponse.json(result, { status: 200 }); 82 | } catch (error) { 83 | console.error("Main thread error:", error); 84 | return NextResponse.json( 85 | { 86 | success: false, 87 | error: error instanceof Error ? error.message : "Unknown error", 88 | }, 89 | { status: 500 } 90 | ); 91 | } 92 | } 93 | 94 | // Helper function 95 | function extractFunctionName(code: string): string | null { 96 | const functionDecl = code.match(/function\s+(\w+)\s*\(/); 97 | const constAssign = code.match(/const\s+(\w+)\s*=/); 98 | const arrowFunc = code.match(/const\s+(\w+)\s*=\s*\([^)]*\)\s*=>/); 99 | 100 | return functionDecl?.[1] || arrowFunc?.[1] || constAssign?.[1] || null; 101 | } 102 | -------------------------------------------------------------------------------- /apps/web/app/api/run/runner.js: -------------------------------------------------------------------------------- 1 | // app/api/run/runner.js 2 | import { parentPort, workerData } from "worker_threads"; 3 | 4 | const { userCode, functionName, input } = workerData; 5 | 6 | try { 7 | console.log("Runner received:", { 8 | userCodeLength: userCode.length, 9 | functionName, 10 | input, 11 | }); 12 | 13 | // Create a clean execution context 14 | const context = { 15 | console: { 16 | log: (...args) => { 17 | logs.push( 18 | args 19 | .map((arg) => 20 | typeof arg === "object" ? JSON.stringify(arg) : String(arg) 21 | ) 22 | .join(" ") 23 | ); 24 | }, 25 | }, 26 | }; 27 | 28 | // Capture console.log outputs 29 | const logs = []; 30 | 31 | // Execute user code in a new function context 32 | const execFunc = new Function( 33 | "context", 34 | ` 35 | const console = context.console; 36 | ${userCode} 37 | context.${functionName} = ${functionName}; 38 | ` 39 | ); 40 | 41 | execFunc(context); 42 | 43 | const userFunc = context[functionName]; 44 | 45 | if (typeof userFunc !== "function") { 46 | throw new Error(`Function ${functionName} not found or is not a function`); 47 | } 48 | 49 | // Parse input if it's provided as a string 50 | let parsedInput; 51 | if (input) { 52 | try { 53 | // Handle different input formats (string, JSON string, or already parsed) 54 | if (typeof input === "string") { 55 | try { 56 | parsedInput = JSON.parse(input); 57 | } catch { 58 | // If not valid JSON, use as is 59 | parsedInput = input; 60 | } 61 | } else { 62 | parsedInput = input; 63 | } 64 | } catch (error) { 65 | throw new Error(`Failed to parse input: ${error.message}`); 66 | } 67 | } 68 | 69 | // Execute the function with the provided input 70 | const result = input !== undefined ? userFunc(parsedInput) : userFunc(); 71 | 72 | parentPort.postMessage({ 73 | success: true, 74 | result: result, 75 | logs: logs, 76 | type: typeof result, 77 | }); 78 | } catch (error) { 79 | console.error("Runner error:", error); 80 | parentPort.postMessage({ 81 | success: false, 82 | error: error instanceof Error ? error.message : "An error occurred", 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /apps/web/app/api/test/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import connectDB from "@/lib/mongodb"; 3 | import QuestionModel, { IQuestion, TestCases } from "@/models/Question"; 4 | import { Worker } from "worker_threads"; 5 | import path from "path"; 6 | import fs from "fs"; 7 | 8 | export async function POST(req: NextRequest) { 9 | try { 10 | await connectDB(); 11 | 12 | const { questionId, userCode } = await req.json(); 13 | 14 | if (!questionId || !userCode) { 15 | return NextResponse.json( 16 | { error: "Missing questionId or userCode" }, 17 | { status: 400 } 18 | ); 19 | } 20 | 21 | const question: IQuestion | null = await QuestionModel.findById(questionId); 22 | 23 | if (!question) { 24 | return NextResponse.json( 25 | { error: "Question not found" }, 26 | { status: 404 } 27 | ); 28 | } 29 | 30 | const testCases: TestCases[] = question.questionDetails.testCases || []; 31 | 32 | const functionName = extractFunctionName(userCode); 33 | 34 | if (!functionName) { 35 | return NextResponse.json( 36 | { error: "Could not determine function name" }, 37 | { status: 400 } 38 | ); 39 | } 40 | 41 | // Serialize test cases 42 | const serializedTestCases = testCases.map((testCase) => ({ 43 | serializedInput: JSON.stringify(testCase.input), 44 | serializedOutput: JSON.stringify(testCase.output), 45 | description: testCase.description, 46 | })); 47 | 48 | // Use a relative path that matches your monorepo structure 49 | const workerPath = path.join( 50 | process.cwd(), 51 | "app", 52 | "api", 53 | "test", 54 | "worker.js" 55 | ); 56 | 57 | // Verify the worker file exists 58 | if (!fs.existsSync(workerPath)) { 59 | console.error(`Worker file not found at: ${workerPath}`); 60 | return NextResponse.json( 61 | { error: `Worker file not found at: ${workerPath}` }, 62 | { status: 500 } 63 | ); 64 | } 65 | 66 | console.log("Worker path:", workerPath); 67 | 68 | const results = await Promise.all( 69 | serializedTestCases.map( 70 | (serializedTestCase) => 71 | new Promise((resolve) => { 72 | const worker = new Worker(workerPath, { 73 | workerData: { 74 | userCode, 75 | testCase: serializedTestCase, 76 | functionName, 77 | }, 78 | }); 79 | 80 | worker.on("message", (result: unknown) => { 81 | console.log("Worker result:", result); 82 | resolve(result); 83 | worker.terminate(); 84 | }); 85 | 86 | worker.on("error", (error) => { 87 | console.error("Worker error event:", error); 88 | resolve({ 89 | passed: false, 90 | input: serializedTestCase.serializedInput, 91 | expected: serializedTestCase.serializedOutput, 92 | actual: error.message, 93 | description: serializedTestCase.description, 94 | }); 95 | worker.terminate(); 96 | }); 97 | 98 | worker.on("exit", (code) => { 99 | if (code !== 0) { 100 | console.error("Worker exited with code:", code); 101 | resolve({ 102 | passed: false, 103 | input: serializedTestCase.serializedInput, 104 | expected: serializedTestCase.serializedOutput, 105 | actual: `Worker exited with code ${code}`, 106 | description: serializedTestCase.description, 107 | }); 108 | } 109 | }); 110 | }) 111 | ) 112 | ); 113 | 114 | return NextResponse.json({ results }, { status: 200 }); 115 | } catch (error) { 116 | console.error("Main thread error:", error); 117 | return NextResponse.json( 118 | { 119 | error: "Internal server error", 120 | details: error instanceof Error ? error.message : "Unknown error", 121 | }, 122 | { status: 500 } 123 | ); 124 | } 125 | } 126 | 127 | // Helper functions 128 | function extractFunctionName(code: string): string | null { 129 | const functionDecl = code.match(/function\s+(\w+)\s*\(/); 130 | const constAssign = code.match(/const\s+(\w+)\s*=/); 131 | return functionDecl?.[1] || constAssign?.[1] || null; 132 | } 133 | -------------------------------------------------------------------------------- /apps/web/app/api/test/worker.js: -------------------------------------------------------------------------------- 1 | // app/api/test/worker.js 2 | import { parentPort, workerData } from "worker_threads"; 3 | 4 | const { userCode, testCase, functionName } = workerData; 5 | 6 | try { 7 | console.log("Worker received:", { 8 | userCodeLength: userCode.length, 9 | functionName, 10 | testCase, 11 | }); 12 | 13 | // Create a clean execution context 14 | const context = {}; 15 | 16 | // Execute user code in a new function, ensuring no module resolution 17 | const execFunc = new Function( 18 | "context", 19 | ` 20 | ${userCode} 21 | context.${functionName} = ${functionName}; 22 | ` 23 | ); 24 | execFunc(context); 25 | 26 | const userFunc = context[functionName]; 27 | 28 | if (typeof userFunc !== "function") { 29 | throw new Error(`Function ${functionName} not found or is not a function`); 30 | } 31 | 32 | // Deserialize input and output 33 | // Parse twice to handle double-serialized JSON strings 34 | const parsedInput = JSON.parse(JSON.parse(testCase.serializedInput)); 35 | const expectedOutput = JSON.parse(testCase.serializedOutput); 36 | 37 | function areEqual(a, b) { 38 | try { 39 | if (a === b) return true; 40 | if (a === undefined || b === undefined) return false; 41 | if (a === null || b === null) return a === b; 42 | return JSON.stringify(a) === JSON.stringify(b); 43 | } catch { 44 | return false; 45 | } 46 | } 47 | 48 | const actual = userFunc(parsedInput); 49 | const passed = areEqual(actual, expectedOutput); 50 | 51 | parentPort.postMessage({ 52 | passed, 53 | input: testCase.serializedInput, 54 | expected: testCase.serializedOutput, 55 | actual: JSON.stringify(actual), 56 | description: testCase.description, 57 | }); 58 | } catch (error) { 59 | console.error("Worker error:", error); 60 | parentPort.postMessage({ 61 | passed: false, 62 | input: testCase.serializedInput, 63 | expected: testCase.serializedOutput, 64 | actual: error instanceof Error ? error.message : "An error occurred", 65 | description: testCase.description, 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /apps/web/app/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import type React from "react"; 3 | import { SidebarLayout } from "@/components/sidebarLayout"; 4 | 5 | export const metadata: Metadata = { 6 | title: "Sidebar Example", 7 | description: "A sidebar example using Next.js and Tailwind CSS", 8 | }; 9 | 10 | export default function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | return {children}; 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import TopicCards from "@/components/miniTopicsCard"; 2 | import ProgressCards from "@/components/progress-cards"; 3 | import ActivityHeatmap from "@/components/streak-bar"; 4 | import { comapniesArray } from "@/data/comapines"; 5 | import { languages } from "@/data/languages"; 6 | import { topics } from "@/data/topics"; 7 | 8 | interface ActivityData { 9 | date: string; 10 | count: number; 11 | } 12 | 13 | const activityData: ActivityData[] = [ 14 | { date: "2024-02-20", count: 3 }, 15 | { date: "2024-02-21", count: 5 }, 16 | { date: "2024-11-15", count: 2 }, 17 | ]; 18 | 19 | export default function Page() { 20 | return ( 21 |
22 |

Dashbaord

23 |

Your Progress at a glance

24 | 25 | 26 |
27 |

28 | Focus Areas 29 |

30 |

31 | Deep-dive into topical focus areas critical for front end interviews 32 |

33 | 34 |
35 |
36 |

37 | Frameworks and languages 38 |

39 |

40 | Targeted practice in specific front end frameworks and languages. 41 |

42 | 43 |
44 |
45 |

46 | Company Guides 47 |

48 |

49 | Prepare for specific companies by learning insider tips and practicing 50 | known questions. 51 |

52 | 53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /apps/web/app/editor/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useParams } from "next/navigation"; 4 | import Editor from "@workspace/editor/components/Editor"; 5 | 6 | import { Geist, Geist_Mono } from "next/font/google"; 7 | 8 | import "@workspace/ui/globals.css"; 9 | import { useCode } from "@/contexts/CodeContext"; 10 | import { useDarkMode } from "@/hooks/useDarkMode"; 11 | import { useGetQuestion } from "@/hooks/queries"; 12 | import { AlertCircle, Code, Loader2 } from "lucide-react"; 13 | import { Button } from "@workspace/ui/components/button"; 14 | 15 | const fontSans = Geist({ 16 | subsets: ["latin"], 17 | variable: "--font-sans", 18 | }); 19 | 20 | const fontMono = Geist_Mono({ 21 | subsets: ["latin"], 22 | variable: "--font-mono", 23 | }); 24 | 25 | export default function QuestionPage() { 26 | const params = useParams(); 27 | const id = params.id as string; 28 | 29 | const { data: question, isLoading, isError, error } = useGetQuestion(id); 30 | 31 | const { code, setCode, testCases } = useCode(); 32 | const { theme } = useDarkMode(); 33 | 34 | if (isLoading) 35 | return ( 36 |
39 |
40 |
41 |
42 | 43 |
44 |

Loading Editor

45 |

46 | Preparing your coding environment... 47 |

48 |
49 |
50 |
51 | ); 52 | 53 | if (!question) 54 | return ( 55 |
58 |
59 |
60 |

Question Not Found

61 |

62 | We couldn't find the question you're looking for. It may have been 63 | removed or doesn't exist. 64 |

65 | 71 |
72 |
73 |
74 | ); 75 | 76 | if (isError) 77 | return ( 78 |
81 |
82 |
83 |
84 | 85 |
86 |

Unable to Load Editor

87 |

88 | {error?.message || 89 | "An unexpected error occurred while loading the question."} 90 |

91 | 92 | 99 |
100 |
101 |
102 | ); 103 | 104 | return ( 105 |
108 | 115 |
116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /apps/web/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/apps/web/app/favicon.ico -------------------------------------------------------------------------------- /apps/web/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Inter, Roboto } from "next/font/google"; 2 | 3 | import "@workspace/ui/globals.css"; 4 | import { Providers } from "@/components/providers"; 5 | import { CodeProvider } from "@/contexts/CodeContext"; 6 | 7 | const fontInter = Inter({ 8 | subsets: ["latin"], 9 | variable: "--font-inter", 10 | }); 11 | 12 | const fontRoboto = Roboto({ 13 | subsets: ["latin"], 14 | variable: "--font-roboto", 15 | weight: "400", 16 | }); 17 | 18 | export const metadata = { 19 | title: "Frontend Forge", 20 | icons: { 21 | icon: [ 22 | { 23 | rel: "icon", 24 | url: "/images/logo-light.svg", 25 | sizes: "32x32", 26 | type: "image/svg+xml", 27 | }, 28 | { 29 | rel: "icon", 30 | url: "/images/logo-light.svg", 31 | sizes: "64x64", 32 | type: "image/svg+xml", 33 | }, 34 | ], 35 | }, 36 | }; 37 | 38 | export default function RootLayout({ 39 | children, 40 | }: Readonly<{ 41 | children: React.ReactNode; 42 | }>) { 43 | return ( 44 | 45 | 48 | 49 | {children} 50 | 51 | 52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /apps/web/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { HeroScrollDemo } from "@/components/hero-scroll"; 4 | import LandingHeader from "@/components/landing-header"; 5 | import { Button } from "@workspace/ui/components/button"; 6 | import { ArrowRight } from "lucide-react"; 7 | import TextGenerateEffect from "@workspace/ui/components/text-generate-effect"; 8 | import { motion } from "framer-motion"; 9 | import FeatureCards from "@/components/features-cards"; 10 | import CompaniesCards from "@/components/companies-cards"; 11 | import { MarqueeDemo } from "@/components/reviews"; 12 | import MessageBox from "@/components/message"; 13 | import Footer from "@/components/Footer"; 14 | import { AccordionDemo } from "@/components/accordian"; 15 | 16 | export default function Page() { 17 | // Animation variants 18 | const containerVariants = { 19 | hidden: { opacity: 0 }, 20 | visible: { 21 | opacity: 1, 22 | transition: { 23 | staggerChildren: 0.2, 24 | delayChildren: 0.1, 25 | }, 26 | }, 27 | }; 28 | 29 | const itemVariants = { 30 | hidden: { opacity: 0, y: -20 }, 31 | visible: { 32 | opacity: 1, 33 | y: 0, 34 | transition: { 35 | type: "spring", 36 | stiffness: 100, 37 | damping: 10, 38 | duration: 0.5, 39 | }, 40 | }, 41 | }; 42 | 43 | return ( 44 |
45 | 46 | 47 | 53 | 54 | 58 | 59 | 60 | 61 | 65 | 66 | 67 | 71 | Meet the{" "} 72 | 73 | {" "} 74 | front-end interview prep platform 75 | {" "} 76 | built to make your interviews much easier—streamlining your practice, 77 | enhancing your skills, and giving you the edge to succeed with 78 | confidence. 79 | 80 | 81 | 82 | 89 | 90 | 91 | 92 | 98 | 99 | 100 | 101 |
102 |

103 | Practice in an environment{" "} 104 | that 105 |

106 |

107 | that simulates real 108 | interviews 109 |

110 | 111 |

112 | Our in-browser coding workspace allows you to simulate a real 113 | interview environment with no set up required. 114 |

115 | 116 |
117 | 118 |
119 |
120 |
121 |

122 | Let experienced developers 123 |

124 |

125 | craft your learning resources. 126 |

127 | 128 |

129 | Gain valuable best practices and advanced techniques, carefully 130 | refined through extensive hands-on experience and deep industry 131 | knowledge. 132 |

133 | 134 |
135 | 136 |
137 |
138 | 139 | {/* */} 140 | 141 |
142 |

143 | What People are Saying! 144 |

145 | 146 |
147 | 148 | 149 |
150 |
151 | 152 |
153 |

154 | Your commonly asked 155 |

156 |

157 | questions, answered 158 |

159 |
160 | 161 | 162 | 163 |
164 |

165 | Don‘t hesitate to reach out.{" "} 166 | We‘re always here to help. 167 |

168 |

169 | We‘re always here to help. 170 |

171 | 172 |

173 | Have questions, feedback, or anything to say? Tell us. We usually get 174 | back within 1-2 days. 175 |

176 | 177 |
178 | 179 |
180 |
181 | 182 |
183 |
185 |
186 | ); 187 | } 188 | -------------------------------------------------------------------------------- /apps/web/app/questions/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import type React from "react"; 3 | import { SidebarLayout } from "@/components/sidebarLayout"; 4 | 5 | export const metadata: Metadata = { 6 | title: "Sidebar Example", 7 | description: "A sidebar example using Next.js and Tailwind CSS", 8 | }; 9 | 10 | export default function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | return {children}; 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/app/signin/layout.tsx: -------------------------------------------------------------------------------- 1 | import "@workspace/ui/globals.css"; 2 | import { Providers } from "@/components/providers"; 3 | import { CodeProvider } from "@/contexts/CodeContext"; 4 | 5 | export const metadata = { 6 | title: "Sign In", 7 | icons: { 8 | icon: [ 9 | { 10 | rel: "icon", 11 | url: "/images/logo-light.svg", 12 | sizes: "32x32", 13 | type: "image/svg+xml", 14 | }, 15 | { 16 | rel: "icon", 17 | url: "/images/logo-light.svg", 18 | sizes: "64x64", 19 | type: "image/svg+xml", 20 | }, 21 | ], 22 | }, 23 | }; 24 | 25 | export default function RootLayout({ 26 | children, 27 | }: Readonly<{ 28 | children: React.ReactNode; 29 | }>) { 30 | return ( 31 |
32 | 33 | {children} 34 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /apps/web/app/signin/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Footer from "@/components/Footer"; 4 | import LandingHeader from "@/components/landing-header"; 5 | import { SignInForm } from "@/components/sign-in-form"; 6 | 7 | export default function Page() { 8 | return ( 9 |
10 | 11 | 12 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/app/signup/layout.tsx: -------------------------------------------------------------------------------- 1 | import "@workspace/ui/globals.css"; 2 | import { Providers } from "@/components/providers"; 3 | import { CodeProvider } from "@/contexts/CodeContext"; 4 | 5 | export const metadata = { 6 | title: "Sign Up", 7 | icons: { 8 | icon: [ 9 | { 10 | rel: "icon", 11 | url: "/images/logo-light.svg", 12 | sizes: "32x32", 13 | type: "image/svg+xml", 14 | }, 15 | { 16 | rel: "icon", 17 | url: "/images/logo-light.svg", 18 | sizes: "64x64", 19 | type: "image/svg+xml", 20 | }, 21 | ], 22 | }, 23 | }; 24 | 25 | export default function RootLayout({ 26 | children, 27 | }: Readonly<{ 28 | children: React.ReactNode; 29 | }>) { 30 | return ( 31 |
32 | 33 | {children} 34 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /apps/web/app/signup/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Footer from "@/components/Footer"; 4 | import LandingHeader from "@/components/landing-header"; 5 | import { SignUpForm } from "@/components/signup-form"; 6 | 7 | export default function Page() { 8 | return ( 9 |
10 | 11 | 12 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "../../packages/ui/tailwind.config.ts", 8 | "css": "../../packages/ui/src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "iconLibrary": "lucide", 13 | "aliases": { 14 | "components": "@/components", 15 | "hooks": "@/hooks", 16 | "lib": "@/lib", 17 | "utils": "@workspace/ui/lib/utils", 18 | "ui": "@workspace/ui/components" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/apps/web/components/.gitkeep -------------------------------------------------------------------------------- /apps/web/components/DisplayQuestions.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | AlertCircle, 5 | Book, 6 | Clock, 7 | FunctionSquare, 8 | GitGraph, 9 | Paperclip, 10 | Search, 11 | SortAsc, 12 | } from "lucide-react"; 13 | import { Badge } from "@workspace/ui/components/badge"; 14 | import { Button } from "@workspace/ui/components/button"; 15 | import { Input } from "@workspace/ui/components/input"; 16 | import { 17 | Tabs, 18 | TabsContent, 19 | TabsList, 20 | TabsTrigger, 21 | } from "@workspace/ui/components/tabs"; 22 | import { QuestionCard } from "@/components/QuestionsCard"; 23 | import { Question } from "@workspace/editor/data/questions"; 24 | import TechIcons from "@/components/TechIcons"; 25 | import { QuestionCardSkeleton } from "./questionCardSkeleton"; 26 | 27 | export type ExtendedQuestion = Question & { 28 | _id: string; 29 | }; 30 | 31 | type DisplayQuestionsProps = { 32 | heading: string; 33 | description: string; 34 | techIcons: boolean; 35 | headingIcon: JSX.Element; 36 | questions: ExtendedQuestion[]; 37 | loading: boolean; 38 | error: string | null; 39 | language?: "html" | "css" | "javascript" | "react"; 40 | }; 41 | 42 | export default function DisplayQuestions({ 43 | heading, 44 | description, 45 | techIcons, 46 | headingIcon, 47 | questions, 48 | loading, 49 | error, 50 | language, 51 | }: DisplayQuestionsProps) { 52 | return ( 53 |
54 |
55 | {techIcons && language && } 56 | 57 | {/* Header Section */} 58 |
59 |
60 |
61 |
62 | {headingIcon} 63 |
64 |

65 | {heading} 66 |

67 |
68 |

{description}

69 |
70 |
71 | 72 | {/* Search and Sort */} 73 |
74 |
75 | 76 | 80 |
81 | 85 |
86 | 87 | {/* Main Content */} 88 | 89 | 90 | Coding 91 | System design 92 | Quiz 93 | 94 | 95 | 96 | {/* Category Tags */} 97 |
98 | 99 | 100 | JavaScript functions 101 | 102 | 103 | 104 | User interface coding 105 | 106 | 107 | 108 | Algorithmic coding 109 | 110 |
111 | 112 | {/* Stats */} 113 |
114 |
115 | 116 | {questions.length} questions 117 |
118 |
119 | 120 | 100 hours total 121 |
122 |
123 | 124 | {/* Loading/Error Messages */} 125 | {loading && [...Array(4)].map((_, i) => )} 126 | 127 | {error && ( 128 |
129 |
130 | 131 |
132 |

133 | Error loading questions 134 |

135 |

136 | {error} 137 |

138 |
139 |
140 |
141 | )} 142 | 143 | {/* Question Cards */} 144 |
145 | {questions.map((question) => ( 146 | 154 | ))} 155 |
156 |
157 |
158 |
159 |
160 | ); 161 | } 162 | -------------------------------------------------------------------------------- /apps/web/components/QuestionsCard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import TechLogoComponent from "@workspace/editor/components/TechLogo"; 4 | import { 5 | Card, 6 | CardHeader, 7 | CardTitle, 8 | CardContent, 9 | } from "@workspace/ui/components/card"; 10 | import { Badge } from "@workspace/ui/components/badge"; 11 | import { ArrowRight } from "lucide-react"; 12 | import Link from "next/link"; 13 | 14 | type Tech = "html" | "css" | "js" | "react"; 15 | 16 | type QuestionCardProps = { 17 | tech: Tech[]; 18 | questionName: string; 19 | desc: string; 20 | difficulty: "Easy" | "Medium" | "Hard"; 21 | _id: string; 22 | }; 23 | 24 | export function QuestionCard({ 25 | tech, 26 | questionName, 27 | desc, 28 | difficulty, 29 | _id, 30 | }: QuestionCardProps) { 31 | return ( 32 | 33 | 34 | 35 | {questionName} 36 | {questionName === "Counter" && ( 37 | 41 | Warm up question 42 | 43 | )} 44 | 45 | 46 |

47 | {desc.length < 200 ? desc : `${desc.slice(0, 200)}...`} 48 |

49 |
50 | UI coding 51 | 55 | {difficulty} 56 | 57 | 58 |
59 | 60 |
61 |
62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /apps/web/components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Link from "next/link"; 3 | import { 4 | ChevronDown, 5 | ChevronRight, 6 | Code2, 7 | FileQuestion, 8 | LayoutDashboard, 9 | Library, 10 | Menu, 11 | Timer, 12 | } from "lucide-react"; 13 | import { Button } from "@workspace/ui/components/button"; 14 | import { 15 | Collapsible, 16 | CollapsibleContent, 17 | CollapsibleTrigger, 18 | } from "@workspace/ui/components/collapsible"; 19 | import { 20 | Sheet, 21 | SheetContent, 22 | SheetTrigger, 23 | } from "@workspace/ui/components/sheet"; 24 | import Image from "next/image"; 25 | import { 26 | DiscordIcon, 27 | GitHubDarkIcon, 28 | GitHubLightIcon, 29 | LinkedInIcon, 30 | } from "@trigger.dev/companyicons"; 31 | import { useDarkMode } from "@/hooks/useDarkMode"; 32 | 33 | const SidebarContent = () => { 34 | const { theme } = useDarkMode(); 35 | 36 | return ( 37 |
38 | {/* Header */} 39 |
40 |
41 |
42 | {theme === "dark" ? ( 43 | logo 50 | ) : ( 51 | logo 58 | )} 59 |
60 | Frontend Forge 61 |
62 | 65 |
66 | 67 | {/* Navigation */} 68 | 130 | 131 | {/* Footer */} 132 |
133 | 140 | 151 | 158 |
159 |
160 | ); 161 | }; 162 | 163 | export function Sidebar() { 164 | return ( 165 | <> 166 | {/* Desktop Sidebar */} 167 |
168 |
169 | 170 |
171 |
172 | 173 | {/* Mobile Sidebar */} 174 | 175 | 176 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | ); 190 | } 191 | -------------------------------------------------------------------------------- /apps/web/components/SidebarQuestions.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "@workspace/ui/components/input"; 2 | import { X } from "lucide-react"; 3 | import { motion, AnimatePresence } from "framer-motion"; 4 | import { TableDemo } from "./TableDemo"; 5 | import { useState } from "react"; 6 | 7 | export function QuestionSidebar({ 8 | showQuestionBar, 9 | setShowQuestionBar, 10 | setQuestionIndex, 11 | }: { 12 | showQuestionBar: boolean; 13 | setShowQuestionBar: React.Dispatch>; 14 | setQuestionIndex: React.Dispatch>; 15 | }) { 16 | const [query, setQuery] = useState(""); 17 | 18 | return ( 19 | 20 | {showQuestionBar && ( 21 | 27 | 34 | setShowQuestionBar(false)} 36 | className="w-4 h-4 absolute cursor-pointer top-4 right-4" 37 | /> 38 |
39 | setQuery(e.target.value)} 44 | /> 45 |
46 | 47 |
48 | 53 |
54 |
55 | 56 |
setShowQuestionBar(false)} 58 | className="w-[65%] h-screen bg-transparent" 59 | >
60 |
61 | )} 62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /apps/web/components/TableDemo.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-unknown-property */ 2 | "use client"; 3 | 4 | import { 5 | Table, 6 | TableBody, 7 | TableCell, 8 | TableHead, 9 | TableHeader, 10 | TableRow, 11 | } from "@workspace/ui/components/table"; 12 | import { CheckCircle2 } from "lucide-react"; 13 | import { useEffect, useState } from "react"; 14 | import { ExtendedQuestion } from "./DisplayQuestions"; 15 | import { usePathname, useRouter } from "next/navigation"; 16 | 17 | function styleDifficultyText(diff: string) { 18 | switch (diff) { 19 | case "Easy": 20 | return "text-green-500"; 21 | case "Medium": 22 | return "text-yellow-500"; 23 | case "Hard": 24 | return "text-red-500"; 25 | default: 26 | return "Unknow Case"; 27 | } 28 | } 29 | 30 | export function TableDemo({ 31 | setShowQuestionBar, 32 | setQuestionIndex, 33 | query, 34 | }: { 35 | setShowQuestionBar: React.Dispatch>; 36 | setQuestionIndex: React.Dispatch>; 37 | query: string; 38 | }) { 39 | const [isHovered, setIsHovered] = useState(false); 40 | const [questions, setQuestions] = useState([]); 41 | const [filteredQuestions, setFilteredQuestions] = useState< 42 | ExtendedQuestion[] 43 | >([]); 44 | const [loading, setLoading] = useState(true); 45 | const [error, setError] = useState(null); 46 | 47 | const router = useRouter(); 48 | const pathName = usePathname(); 49 | 50 | const questionId = pathName.slice(8); 51 | 52 | useEffect(() => { 53 | const fetchQuestions = async () => { 54 | try { 55 | const response = await fetch("/api/getData"); 56 | const data = await response.json(); 57 | 58 | if (response.ok) { 59 | setQuestions(data.data); 60 | setFilteredQuestions(data.data); // Initialize filtered questions with all questions 61 | } else { 62 | setError(data.message || "Failed to load data"); 63 | } 64 | } catch (err) { 65 | setError(`An error occurred while fetching data: ${err}`); 66 | } finally { 67 | setLoading(false); 68 | } 69 | }; 70 | 71 | fetchQuestions(); 72 | }, []); 73 | 74 | // Update filtered questions whenever the query or questions change 75 | useEffect(() => { 76 | if (query) { 77 | const filtered = questions.filter((q) => 78 | q.questionDetails.name.toLowerCase().includes(query.toLowerCase()) 79 | ); 80 | setFilteredQuestions(filtered); 81 | } else { 82 | setFilteredQuestions(questions); 83 | } 84 | }, [query, questions]); 85 | 86 | if (loading) { 87 | return

Loading...

; 88 | } 89 | 90 | if (error) { 91 | return

An error occured

; 92 | } 93 | 94 | return ( 95 |
setIsHovered(true)} 98 | onMouseLeave={() => setIsHovered(false)} 99 | > 100 | 127 | 128 |
131 | 132 | 133 | 134 | Name 135 | Format 136 | Difficulty 137 | 138 | 139 | 140 | {filteredQuestions.map((question, i) => ( 141 | { 143 | router.push(`/editor/${question._id}`); 144 | setQuestionIndex(i + 1); 145 | setShowQuestionBar(false); 146 | }} 147 | key={i} 148 | className={`cursor-pointer ${questionId === question._id ? "dark:bg-[#222225] bg-gray-100" : ""}`} 149 | > 150 | 151 | 152 | 155 | 156 | {question.questionDetails.name}{" "} 157 | 158 | 159 | {question.questionDetails.questionType === "logical" 160 | ? "JS Function" 161 | : "UI Coding"} 162 | 163 | 168 | 🔥 {question.questionDetails.difficulty} 169 | 170 | 171 | ))} 172 | 173 |
174 |
175 |
176 | ); 177 | } 178 | -------------------------------------------------------------------------------- /apps/web/components/TechIcons.tsx: -------------------------------------------------------------------------------- 1 | import { languages } from "@/data/languages"; 2 | import Link from "next/link"; 3 | 4 | type Dark = { 5 | dark: "html" | "css" | "javascript" | "react"; 6 | }; 7 | 8 | export default function TechIcons({ dark }: Dark) { 9 | return ( 10 |
11 | {languages.map((lang, i) => { 12 | return ( 13 | 22 |
{lang.icon}
23 |

26 | {lang.title} 27 |

28 | 29 | ); 30 | })} 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /apps/web/components/accordian.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Accordion, 3 | AccordionContent, 4 | AccordionItem, 5 | AccordionTrigger, 6 | } from "@workspace/ui/components/accordion"; 7 | 8 | export function AccordionDemo() { 9 | return ( 10 | 11 | 12 | 13 | What frameworks/libraries does the platform support? 14 | 15 | 16 |

17 | Our platform supports all major frontend frameworks and libraries, 18 | including React and vanilla JavaScript. Whether you prefer the 19 | flexibility of React’s component-based structure or the simplicity 20 | of vanilla JavaScript, our environment has you covered. You can view 21 | live what you build, allowing you to see changes instantly as you 22 | write and modify your code. This real-time feedback ensures a smooth 23 | development experience, reducing the time spent debugging and 24 | refining your work. 25 |

26 |

27 | Additionally, our platform provides a robust testing environment to 28 | validate your solutions efficiently. Test cases are available for 29 | logical questions, allowing you to verify the correctness of your 30 | code against multiple scenarios. This feature is particularly useful 31 | for developers who want to sharpen their problem-solving skills and 32 | ensure their applications function as expected. 33 |

34 |

35 | By combining a live preview feature with structured test cases, our 36 | platform bridges the gap between development and testing, helping 37 | developers improve their coding accuracy and efficiency. Whether 38 | you're building interactive UIs, testing JavaScript logic, or 39 | refining your frontend skills, our platform provides the ideal space 40 | to code, test, and iterate seamlessly. 🚀 41 |

42 |
43 |
44 | 45 | 46 | How do the test cases work? 47 | 48 | 49 | Each challenge comes with predefined test cases designed to validate 50 | various aspects of your solution, including functionality, 51 | accessibility, and responsiveness. These test cases simulate 52 | real-world conditions to ensure your code behaves correctly under 53 | different scenarios. You can run tests against your code at any time 54 | during development, and our platform provides detailed feedback, 55 | highlighting which tests have passed and which have failed. 56 | Additionally, if a test fails, you will receive insights into what 57 | went wrong and possible ways to fix the issue. This feature helps 58 | developers debug efficiently, refine their approach, and build robust 59 | components that meet high-quality standards. 60 | 61 | 62 | 63 | 64 | Does the platform have difficulty levels? 65 | 66 | 67 | Yes, our challenges are structured into three difficulty levels: Easy, 68 | Medium, and Hard. This categorization allows developers to choose 69 | tasks that match their current skill level and progress gradually. 70 | Beginners are encouraged to start with Easy challenges, which focus on 71 | fundamental concepts such as HTML structure, CSS styling, and basic 72 | JavaScript interactions. As developers gain confidence, they can move 73 | on to Medium challenges, which introduce more complex component logic, 74 | state management, and event handling. Finally, Hard challenges are 75 | designed for advanced users who want to tackle intricate problems, 76 | optimize performance, and explore cutting-edge frontend techniques. 77 | This tiered structure ensures that learners of all levels can find 78 | suitable challenges to enhance their skills progressively. 79 | 80 | 81 | 82 | 83 | Is it open source? 84 | 85 | 86 | Yes! Our platform is open source, allowing developers to contribute, 87 | modify, and extend its features. Being open source means that the 88 | entire codebase is accessible to the community, enabling transparency, 89 | collaboration, and continuous improvement. Developers can report 90 | issues, suggest new features, or even submit pull requests to enhance 91 | the platform’s functionality. Additionally, this openness fosters a 92 | strong developer ecosystem where users can learn from the underlying 93 | architecture, customize the environment to fit their needs, and even 94 | create their own versions of the platform. Whether you are interested 95 | in improving existing features or simply exploring how everything 96 | works, our open-source model provides you with full access to 97 | contribute and innovate. 98 | 99 | 100 | 101 | 102 | Is it completely free? 103 | 104 | 105 | Yes, it is completely free! Our platform is designed to provide 106 | unrestricted access to all users, regardless of their experience level 107 | or location. There are no hidden charges, premium subscriptions, or 108 | paywalls—every feature, challenge, and tool is available at no cost. 109 | This ensures that learners, hobbyists, and professional developers can 110 | improve their coding skills without financial barriers. You can 111 | participate in coding challenges, test your solutions, and explore 112 | community resources without any limitations. Our goal is to make 113 | high-quality learning accessible to everyone, so you can focus on 114 | honing your skills and building amazing projects without worrying 115 | about expenses. 116 | 117 | 118 |
119 | ); 120 | } 121 | -------------------------------------------------------------------------------- /apps/web/components/hero-scroll.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | import { ContainerScroll } from "@workspace/ui/components/scroll-animation"; 4 | import Editor from "@workspace/editor/components/Editor"; 5 | import { 6 | question as question1, 7 | question2, 8 | } from "@workspace/editor/data/questions"; 9 | import { useDarkMode } from "@/hooks/useDarkMode"; 10 | import Link from "next/link"; 11 | import { Link as LinkIcon } from "lucide-react"; 12 | import { Button } from "@workspace/ui/components/button"; 13 | 14 | export function HeroScrollDemo() { 15 | const { theme } = useDarkMode(); 16 | 17 | const [questionType, setQuestionType] = useState<"logical" | "ui">("ui"); 18 | 19 | function handleQuestionTypeChange(input: "logical" | "ui") { 20 | if (input === "ui") { 21 | setQuestionType("ui"); 22 | } else { 23 | setQuestionType("logical"); 24 | } 25 | } 26 | 27 | return ( 28 |
29 | 30 |
31 | {/* Mac window header */} 32 |
33 |
34 |
38 |
42 |
46 |
47 |
48 |
49 | 50 |

frontend-forge-web.vercel.app

51 |
52 |
53 |
54 | 55 | {questionType === "ui" ? ( 56 | 62 | ) : ( 63 | 69 | )} 70 | 71 | {/* Status bar at the bottom like in VSCode */} 72 | 81 |

82 | Click here to try actual workspace And run test cases 83 |

84 | 85 |
86 | 87 |
88 | 95 | 102 |
103 |
104 |
105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /apps/web/components/landing-header.tsx: -------------------------------------------------------------------------------- 1 | import { useDarkMode } from "@/hooks/useDarkMode"; 2 | import Image from "next/image"; 3 | import { 4 | NavigationMenu, 5 | NavigationMenuContent, 6 | NavigationMenuItem, 7 | NavigationMenuLink, 8 | NavigationMenuList, 9 | NavigationMenuTrigger, 10 | } from "@workspace/ui/components/navigation-menu"; 11 | import React from "react"; 12 | import { Moon, Sun } from "lucide-react"; 13 | import { Button } from "@workspace/ui/components/button"; 14 | import Link from "next/link"; 15 | 16 | export default function LandingHeader(): React.ReactElement { 17 | const { theme, toggleTheme } = useDarkMode(); 18 | 19 | return ( 20 |
21 |
22 | 23 |
24 | {theme === "dark" ? ( 25 | logo 32 | ) : ( 33 | logo 40 | )} 41 |
42 | Frontend Forge 43 | 44 |

|

45 |

46 | Get Started 47 |

48 | 49 | 50 | 51 | 52 | 53 | Prepare 54 | 55 | 56 | 86 | 87 | 88 | 89 | 90 |
91 |
92 |

93 | Pricing 94 |

95 | 99 | Sign in/up 100 | 101 |
105 | {theme === "dark" ? ( 106 | 107 | ) : ( 108 | 109 | )} 110 |
111 | 112 | 118 |
119 |
120 | ); 121 | } 122 | 123 | interface ListItemProps { 124 | className?: string; 125 | title: string; 126 | children: React.ReactNode; 127 | href: string; 128 | } 129 | 130 | const ListItem: React.FC = ({ 131 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 132 | className, 133 | title, 134 | children, 135 | href, 136 | }) => { 137 | return ( 138 |
  • 139 | 140 | 144 |
    {title}
    145 |

    146 | {children} 147 |

    148 |
    149 |
    150 |
  • 151 | ); 152 | }; 153 | -------------------------------------------------------------------------------- /apps/web/components/magicui/marquee.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@workspace/ui/lib/utils"; 2 | import { ComponentPropsWithoutRef } from "react"; 3 | 4 | interface MarqueeProps extends ComponentPropsWithoutRef<"div"> { 5 | /** 6 | * Optional CSS class name to apply custom styles 7 | */ 8 | className?: string; 9 | /** 10 | * Whether to reverse the animation direction 11 | * @default false 12 | */ 13 | reverse?: boolean; 14 | /** 15 | * Whether to pause the animation on hover 16 | * @default false 17 | */ 18 | pauseOnHover?: boolean; 19 | /** 20 | * Content to be displayed in the marquee 21 | */ 22 | children: React.ReactNode; 23 | /** 24 | * Whether to animate vertically instead of horizontally 25 | * @default false 26 | */ 27 | vertical?: boolean; 28 | /** 29 | * Number of times to repeat the content 30 | * @default 4 31 | */ 32 | repeat?: number; 33 | } 34 | 35 | export function Marquee({ 36 | className, 37 | reverse = false, 38 | pauseOnHover = false, 39 | children, 40 | vertical = false, 41 | repeat = 4, 42 | ...props 43 | }: MarqueeProps) { 44 | return ( 45 |
    56 | {Array(repeat) 57 | .fill(0) 58 | .map((_, i) => ( 59 |
    68 | {children} 69 |
    70 | ))} 71 |
    72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /apps/web/components/magicui/sparkles-text.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "motion/react"; 4 | import { CSSProperties, ReactElement, useEffect, useState } from "react"; 5 | 6 | import { cn } from "@workspace/ui/lib/utils"; 7 | 8 | interface Sparkle { 9 | id: string; 10 | x: string; 11 | y: string; 12 | color: string; 13 | delay: number; 14 | scale: number; 15 | lifespan: number; 16 | } 17 | 18 | const Sparkle: React.FC = ({ id, x, y, color, delay, scale }) => { 19 | return ( 20 | 34 | 38 | 39 | ); 40 | }; 41 | 42 | interface SparklesTextProps { 43 | /** 44 | * @default
    45 | * @type ReactElement 46 | * @description 47 | * The component to be rendered as the text 48 | * */ 49 | as?: ReactElement; 50 | 51 | /** 52 | * @default "" 53 | * @type string 54 | * @description 55 | * The className of the text 56 | */ 57 | className?: string; 58 | 59 | /** 60 | * @required 61 | * @type ReactNode 62 | * @description 63 | * The content to be displayed 64 | * */ 65 | children: React.ReactNode; 66 | 67 | /** 68 | * @default 10 69 | * @type number 70 | * @description 71 | * The count of sparkles 72 | * */ 73 | sparklesCount?: number; 74 | 75 | /** 76 | * @default "{first: '#9E7AFF', second: '#FE8BBB'}" 77 | * @type string 78 | * @description 79 | * The colors of the sparkles 80 | * */ 81 | colors?: { 82 | first: string; 83 | second: string; 84 | }; 85 | } 86 | 87 | export const SparklesText: React.FC = ({ 88 | children, 89 | colors = { first: "#9E7AFF", second: "#FE8BBB" }, 90 | className, 91 | sparklesCount = 10, 92 | ...props 93 | }) => { 94 | const [sparkles, setSparkles] = useState([]); 95 | 96 | useEffect(() => { 97 | const generateStar = (): Sparkle => { 98 | const starX = `${Math.random() * 100}%`; 99 | const starY = `${Math.random() * 100}%`; 100 | const color = Math.random() > 0.5 ? colors.first : colors.second; 101 | const delay = Math.random() * 2; 102 | const scale = Math.random() * 1 + 0.3; 103 | const lifespan = Math.random() * 10 + 5; 104 | const id = `${starX}-${starY}-${Date.now()}`; 105 | return { id, x: starX, y: starY, color, delay, scale, lifespan }; 106 | }; 107 | 108 | const initializeStars = () => { 109 | const newSparkles = Array.from({ length: sparklesCount }, generateStar); 110 | setSparkles(newSparkles); 111 | }; 112 | 113 | const updateStars = () => { 114 | setSparkles((currentSparkles) => 115 | currentSparkles.map((star) => { 116 | if (star.lifespan <= 0) { 117 | return generateStar(); 118 | } else { 119 | return { ...star, lifespan: star.lifespan - 0.1 }; 120 | } 121 | }), 122 | ); 123 | }; 124 | 125 | initializeStars(); 126 | const interval = setInterval(updateStars, 100); 127 | 128 | return () => clearInterval(interval); 129 | }, [colors.first, colors.second, sparklesCount]); 130 | 131 | return ( 132 |
    142 | 143 | {sparkles.map((sparkle) => ( 144 | 145 | ))} 146 | {children} 147 | 148 |
    149 | ); 150 | }; 151 | -------------------------------------------------------------------------------- /apps/web/components/message.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@workspace/ui/components/button"; 2 | import { ArrowRight } from "lucide-react"; 3 | 4 | export default function MessageBox() { 5 | return ( 6 |
    7 |
    8 |

    Message

    9 | 13 |
    14 |
    15 |

    Email (optional)

    16 |

    17 | If you'd like a reply, please provide your email address. 18 |

    19 | 23 |
    24 | 25 | 32 |
    33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /apps/web/components/miniTopicsCard.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowRight, MessageSquare } from "lucide-react"; 2 | import { Card } from "@workspace/ui/components/card"; 3 | import Link from "next/link"; 4 | import { TopicCard } from "@/data/topics"; 5 | 6 | interface PropTypes { 7 | topics: TopicCard[]; 8 | } 9 | 10 | export default function TopicCards({ topics }: PropTypes) { 11 | return ( 12 |
    13 | {topics.map((topic: TopicCard) => ( 14 | 18 | 19 |
    20 | { 21 |
    22 | {topic.icon} 23 |
    24 | } 25 |
    26 |

    27 | {topic.title} 28 |

    29 |

    30 | 31 | {topic.questions} questions 32 |

    33 |
    34 |
    35 | 36 |
    37 | 38 | ))} 39 |
    40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /apps/web/components/progress-cards.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | Card, 5 | CardContent, 6 | CardHeader, 7 | CardTitle, 8 | } from "@workspace/ui/components/card"; 9 | import { Progress } from "@workspace/ui/components/progress"; 10 | 11 | export default function ProgressCards() { 12 | return ( 13 |
    14 | {/* Solved Problems Card */} 15 | 16 | 17 | Solved problems 18 | 19 | 20 |
    21 |
    22 | 23 | {/* Background circle */} 24 | 32 | {/* Progress circle - only showing a small portion to match the low completion rate */} 33 | 43 | 44 |
    45 | 12 46 | Solved 47 |
    48 |
    49 |
    50 |
    51 |
    52 | Easy 53 | 7/221 54 |
    55 | 56 |
    57 | 58 |
    59 |
    60 | Medium 61 | 5/254 62 |
    63 | 64 |
    65 | 66 |
    67 |
    68 | Hard 69 | 0/78 70 |
    71 | 72 |
    73 |
    74 |
    75 |
    76 |
    77 | 78 | {/* Solved by Question Type Card */} 79 | 80 | 81 | 82 | Solved by question type 83 | 84 | 85 | 86 |
    87 |
    88 |
    89 | Coding 90 |
    91 | 12/251 completed 92 | 5% 93 |
    94 |
    95 | 96 |
    97 | 98 |
    99 |
    100 | Quizzes 101 |
    102 | 0/283 completed 103 | 0% 104 |
    105 |
    106 | 107 |
    108 | 109 |
    110 |
    111 | System Design 112 |
    113 | 0/19 completed 114 | 0% 115 |
    116 |
    117 | 118 |
    119 |
    120 |
    121 |
    122 |
    123 | ); 124 | } 125 | -------------------------------------------------------------------------------- /apps/web/components/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { QueryClientProvider } from "@tanstack/react-query"; 6 | import queryClient from "@/lib/query-client"; 7 | 8 | export function Providers({ children }: { children: React.ReactNode }) { 9 | return ( 10 | 17 | {children} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/components/questionCardSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardHeader, CardContent } from "@workspace/ui/components/card"; 2 | import { Skeleton } from "@workspace/ui/components/skeleton"; 3 | 4 | export function QuestionCardSkeleton() { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 | 17 | 18 | 19 |
    20 |
    21 |
    22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /apps/web/components/reviews.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import { cn } from "@workspace/ui/lib/utils"; 3 | import { Marquee } from "@/components/magicui/marquee"; 4 | 5 | const reviews = [ 6 | { 7 | name: "Jack", 8 | username: "@jack", 9 | body: "I've never seen anything like this before. It's amazing. I love it.", 10 | img: "https://avatar.vercel.sh/jack", 11 | }, 12 | { 13 | name: "Jill", 14 | username: "@jill", 15 | body: "I don't know what to say. I'm speechless. This is amazing.", 16 | img: "https://avatar.vercel.sh/jill", 17 | }, 18 | { 19 | name: "John", 20 | username: "@john", 21 | body: "I'm at a loss for words. This is amazing. I love it.", 22 | img: "https://avatar.vercel.sh/john", 23 | }, 24 | { 25 | name: "Jane", 26 | username: "@jane", 27 | body: "I'm at a loss for words. This is amazing. I love it.", 28 | img: "https://avatar.vercel.sh/jane", 29 | }, 30 | { 31 | name: "Jenny", 32 | username: "@jenny", 33 | body: "I'm at a loss for words. This is amazing. I love it.", 34 | img: "https://avatar.vercel.sh/jenny", 35 | }, 36 | { 37 | name: "James", 38 | username: "@james", 39 | body: "I'm at a loss for words. This is amazing. I love it.", 40 | img: "https://avatar.vercel.sh/james", 41 | }, 42 | ]; 43 | 44 | const firstRow = reviews.slice(0, reviews.length / 2); 45 | const secondRow = reviews.slice(reviews.length / 2); 46 | 47 | const ReviewCard = ({ 48 | img, 49 | name, 50 | username, 51 | body, 52 | }: { 53 | img: string; 54 | name: string; 55 | username: string; 56 | body: string; 57 | }) => { 58 | return ( 59 |
    68 |
    69 | 70 |
    71 |
    72 | {name} 73 |
    74 |

    {username}

    75 |
    76 |
    77 |
    {body}
    78 |
    79 | ); 80 | }; 81 | 82 | export function MarqueeDemo() { 83 | return ( 84 |
    85 | 86 | {firstRow.map((review) => ( 87 | 88 | ))} 89 | 90 | 91 | {secondRow.map((review) => ( 92 | 93 | ))} 94 | 95 |
    96 |
    97 |
    98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /apps/web/components/sidebarLayout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@workspace/ui/components/button"; 4 | import { Sidebar } from "./Sidebar"; 5 | import { Inter } from "next/font/google"; 6 | import { Moon, Sun } from "lucide-react"; 7 | import Image from "next/image"; 8 | import { useDarkMode } from "@/hooks/useDarkMode"; 9 | 10 | const inter = Inter({ subsets: ["latin"] }); 11 | 12 | export function SidebarLayout({ children }: { children: React.ReactNode }) { 13 | const { toggleTheme, theme } = useDarkMode(); 14 | return ( 15 |
    18 | 19 |
    {children}
    20 |
    21 |

    Pricing

    22 |
    26 | {theme === "dark" ? ( 27 | 28 | ) : ( 29 | 30 | )} 31 |
    32 | 33 | 39 |
    40 | pratiyank-image 47 |
    48 |
    49 |
    50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /apps/web/components/sign-in-form.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { Button } from "@workspace/ui/components/button"; 4 | import { Input } from "@workspace/ui/components/input"; 5 | import { Separator } from "@workspace/ui/components/separator"; 6 | import { 7 | GitHubDarkIcon, 8 | GitHubLightIcon, 9 | GoogleIcon, 10 | } from "@trigger.dev/companyicons"; 11 | import { useDarkMode } from "@/hooks/useDarkMode"; 12 | 13 | export function SignInForm() { 14 | const { theme } = useDarkMode(); 15 | 16 | return ( 17 |
    18 |
    19 |

    20 | Sign in to your account 21 |

    22 |

    23 | Don't have an account?{" "} 24 | 28 | Sign up for free 29 | 30 |

    31 |
    32 |
    33 | 41 | 45 |
    46 |
    47 | 48 |
    49 |
    50 | 51 | Or continue with 52 | 53 |
    54 |
    55 |
    56 | 59 | 60 |
    61 |
    62 | 65 | 66 |
    67 | 71 | Forgot your password? 72 | 73 | 76 |

    77 | By proceeding, you agree to GreatFontEnd's{" "} 78 | 79 | Terms of Service 80 | {" "} 81 | and{" "} 82 | 83 | Privacy Policy 84 | 85 | . 86 |

    87 |
    88 |
    89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /apps/web/components/signup-form.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { Button } from "@workspace/ui/components/button"; 4 | import { Input } from "@workspace/ui/components/input"; 5 | import { Separator } from "@workspace/ui/components/separator"; 6 | import { 7 | GitHubDarkIcon, 8 | GitHubLightIcon, 9 | GoogleIcon, 10 | } from "@trigger.dev/companyicons"; 11 | import { useDarkMode } from "@/hooks/useDarkMode"; 12 | 13 | export function SignUpForm() { 14 | const { theme } = useDarkMode(); 15 | 16 | return ( 17 |
    18 |
    19 |

    20 | Create a new Account 21 |

    22 |

    23 | Already have an account?{" "} 24 | 28 | Sign In 29 | 30 |

    31 |
    32 |
    33 | 41 | 45 |
    46 |
    47 | 48 |
    49 |
    50 | 51 | Or continue with 52 | 53 |
    54 |
    55 |
    56 | 59 | 60 |
    61 |
    62 | 65 | 66 |
    67 | 70 |

    71 | By proceeding, you agree to GreatFontEnd's{" "} 72 | 73 | Terms of Service 74 | {" "} 75 | and{" "} 76 | 77 | Privacy Policy 78 | 79 | . 80 |

    81 |
    82 |
    83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /apps/web/components/streak-bar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Tooltip, 4 | TooltipContent, 5 | TooltipProvider, 6 | TooltipTrigger, 7 | } from "@workspace/ui/components/tooltip"; 8 | 9 | interface ActivityData { 10 | date: string; 11 | count: number; 12 | } 13 | 14 | interface ActivityHeatmapProps { 15 | data?: ActivityData[]; 16 | startDate?: Date | string; 17 | endDate?: Date | string; // Add endDate prop 18 | } 19 | 20 | interface MonthData { 21 | [key: string]: Date[]; 22 | } 23 | 24 | const ActivityHeatmap: React.FC = ({ 25 | data = [], 26 | startDate = new Date(new Date().setFullYear(new Date().getFullYear() - 1)), 27 | // Default endDate to current date or one year from start date, whichever is earlier 28 | endDate = new Date( 29 | Math.min( 30 | new Date().getTime(), 31 | new Date(startDate).getTime() + 365 * 24 * 60 * 60 * 1000 32 | ) 33 | ), 34 | }) => { 35 | const startDateTime = 36 | typeof startDate === "string" ? new Date(startDate) : startDate; 37 | const endDateTime = typeof endDate === "string" ? new Date(endDate) : endDate; 38 | 39 | const getDateKey = (date: Date): string => { 40 | return date.toISOString().split("T")[0] as string; 41 | }; 42 | 43 | // Format date for tooltip display 44 | const formatDate = (date: Date): string => { 45 | return date.toLocaleDateString("en-US", { 46 | weekday: "long", 47 | year: "numeric", 48 | month: "long", 49 | day: "numeric", 50 | }); 51 | }; 52 | 53 | const activityMap: Map = new Map( 54 | data.map((item) => { 55 | const dateKey = getDateKey(new Date(item.date)); 56 | return [dateKey, item.count]; 57 | }) 58 | ); 59 | 60 | const generateDates = (): Date[] => { 61 | const dates: Date[] = []; 62 | const currentDate = new Date(startDateTime); 63 | 64 | while (currentDate <= endDateTime) { 65 | dates.push(new Date(currentDate)); 66 | currentDate.setDate(currentDate.getDate() + 1); 67 | } 68 | return dates; 69 | }; 70 | 71 | const groupByMonth = (dates: Date[]): MonthData => { 72 | const months: MonthData = {}; 73 | dates.forEach((date) => { 74 | const monthKey = date.toLocaleString("default", { month: "short" }); 75 | if (!months[monthKey]) { 76 | months[monthKey] = []; 77 | } 78 | months[monthKey].push(date); 79 | }); 80 | return months; 81 | }; 82 | 83 | const getColor = (count: number = 0): string => { 84 | if (count === 0) return "bg-gray-200"; 85 | if (count <= 2) return "bg-green-200"; 86 | if (count <= 4) return "bg-green-400"; 87 | if (count <= 6) return "bg-green-600"; 88 | return "bg-green-800"; 89 | }; 90 | 91 | const dates = generateDates(); 92 | const monthsData = groupByMonth(dates); 93 | 94 | const colorScale = [ 95 | "bg-gray-100", 96 | "bg-green-200", 97 | "bg-green-400", 98 | "bg-green-600", 99 | "bg-green-800", 100 | ]; 101 | 102 | return ( 103 | 104 |
    105 |
    106 |
    107 |

    Activity Heatmap

    108 |
    109 | Less 110 |
    111 | {colorScale.map((color, i) => ( 112 |
    113 | ))} 114 |
    115 | More 116 |
    117 |
    118 | 119 |
    120 |
    121 | {Object.entries(monthsData).map(([month, dates]) => ( 122 |
    123 |
    {month}
    124 |
    125 | {dates.map((date) => { 126 | const dateKey = getDateKey(date); 127 | const count = activityMap.get(dateKey) ?? 0; 128 | return ( 129 | 130 | 131 |
    134 | 135 | 136 |
    137 |
    138 | {formatDate(date)} 139 |
    140 |
    141 | {count}{" "} 142 | {count === 1 ? "activity" : "activities"} 143 |
    144 |
    145 |
    146 | 147 | ); 148 | })} 149 |
    150 |
    151 | ))} 152 |
    153 |
    154 |
    155 |
    156 | 157 | ); 158 | }; 159 | 160 | export default ActivityHeatmap; 161 | -------------------------------------------------------------------------------- /apps/web/contexts/CodeContext.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { TestCases } from "@/models/Question"; 4 | import { createContext, ReactNode, useContext, useState } from "react"; 5 | 6 | export type ResultTest = TestCases & { 7 | passed: boolean; 8 | actual: number | boolean | string | number[] | object; 9 | expected: number | boolean | string | number[] | object; 10 | }; 11 | 12 | interface CodeContextType { 13 | code: string | undefined; 14 | setCode: (code: string | undefined) => void; 15 | testCases: ResultTest[] | undefined; 16 | setTestCases: (testCases: ResultTest[] | undefined) => void; 17 | } 18 | 19 | const CodeContext = createContext(undefined); 20 | 21 | export const CodeProvider: React.FC<{ children: ReactNode }> = ({ 22 | children, 23 | }) => { 24 | const [code, setCode] = useState(); 25 | const [testCases, setTestCases] = useState(); 26 | 27 | return ( 28 | 29 | {children} 30 | 31 | ); 32 | }; 33 | 34 | export const useCode = (): CodeContextType => { 35 | const context = useContext(CodeContext); 36 | if (!context) { 37 | throw new Error("useCode must be used within a CodeProvider"); 38 | } 39 | return context; 40 | }; 41 | -------------------------------------------------------------------------------- /apps/web/data/languages.tsx: -------------------------------------------------------------------------------- 1 | import { TopicCard } from "./topics"; 2 | 3 | type LogoName = "html" | "css" | "js" | "react"; 4 | 5 | export const logoSvgs: Record = { 6 | html: ( 7 | <> 8 | 14 | 15 | 16 | 20 | 24 | 25 | 26 | ), 27 | css: ( 28 | <> 29 | 35 | 39 | 40 | 44 | 48 | 49 | 50 | ), 51 | js: ( 52 | <> 53 | 59 | 60 | 61 | 62 | 63 | ), 64 | react: ( 65 | <> 66 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | ), 81 | }; 82 | 83 | export const languages: TopicCard[] = [ 84 | { 85 | icon: logoSvgs.html, 86 | title: "HTML", 87 | questions: 12, 88 | href: "/html", 89 | }, 90 | { 91 | icon: logoSvgs.css, 92 | title: "CSS", 93 | questions: 12, 94 | href: "/css", 95 | }, 96 | { 97 | icon: logoSvgs.js, 98 | title: "JavaScript", 99 | questions: 12, 100 | href: "/javascript", 101 | }, 102 | { 103 | icon: logoSvgs.react, 104 | title: "React", 105 | questions: 12, 106 | href: "/react", 107 | }, 108 | ]; 109 | -------------------------------------------------------------------------------- /apps/web/data/topics.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Accessibility, 3 | FileCode, 4 | FormInput, 5 | GitBranch, 6 | LayoutGrid, 7 | MonitorSmartphone, 8 | Network, 9 | RefreshCcw, 10 | } from "lucide-react"; 11 | 12 | export interface TopicCard { 13 | icon: JSX.Element; 14 | title: string; 15 | questions: number; 16 | href: string; 17 | } 18 | 19 | export const topics: TopicCard[] = [ 20 | { 21 | icon: , 22 | title: "Accessibility", 23 | questions: 12, 24 | href: "/accessibility", 25 | }, 26 | { 27 | icon: , 28 | title: "Async Operations", 29 | questions: 33, 30 | href: "/async", 31 | }, 32 | { 33 | icon: , 34 | title: "Data Structures & Algorithms", 35 | questions: 22, 36 | href: "/dsa", 37 | }, 38 | { 39 | icon: , 40 | title: "Design System Components", 41 | questions: 15, 42 | href: "/design-system", 43 | }, 44 | { 45 | icon: , 46 | title: "DOM Manipulation", 47 | questions: 10, 48 | href: "/dom", 49 | }, 50 | { 51 | icon: , 52 | title: "Forms", 53 | questions: 10, 54 | href: "/forms", 55 | }, 56 | { 57 | icon: , 58 | title: "State Management", 59 | questions: 17, 60 | href: "/state", 61 | }, 62 | ]; 63 | -------------------------------------------------------------------------------- /apps/web/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { nextJsConfig } from "@workspace/eslint-config/next-js" 2 | 3 | /** @type {import("eslint").Linter.Config} */ 4 | export default nextJsConfig 5 | -------------------------------------------------------------------------------- /apps/web/hooks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/apps/web/hooks/.gitkeep -------------------------------------------------------------------------------- /apps/web/hooks/queries.tsx: -------------------------------------------------------------------------------- 1 | import { ExtendedQuestion } from "@/app/questions/page"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | 4 | export const useGetQuestions = () => { 5 | return useQuery({ 6 | queryKey: ["questions"], 7 | queryFn: async () => { 8 | const response = await fetch("/api/getData"); 9 | if (!response.ok) { 10 | throw new Error("Failed to fetch questions !"); 11 | } 12 | const questions = await response.json(); 13 | return questions.data as ExtendedQuestion[]; 14 | }, 15 | }); 16 | }; 17 | 18 | export const useGetQuestion = (id: string) => { 19 | return useQuery({ 20 | queryKey: ["question", id], 21 | queryFn: async () => { 22 | const response = await fetch(`/api/questions/${id}`); 23 | if (!response.ok) { 24 | throw new Error("Failed to fetch question !"); 25 | } 26 | const question = await response.json(); 27 | return question as ExtendedQuestion; 28 | }, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /apps/web/hooks/useDarkMode.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | 3 | export const useDarkMode = () => { 4 | const { theme, setTheme } = useTheme(); 5 | 6 | const toggleTheme = () => { 7 | setTheme(theme === "light" ? "dark" : "light"); 8 | }; 9 | 10 | return { theme, toggleTheme }; 11 | }; 12 | -------------------------------------------------------------------------------- /apps/web/hooks/useDebounce.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | function useDebounce(value: T, delay: number) { 4 | const [debounceValue, setDebounceValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebounceValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value, delay]); 15 | 16 | return debounceValue; 17 | } 18 | 19 | export default useDebounce; 20 | -------------------------------------------------------------------------------- /apps/web/lib/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/apps/web/lib/.gitkeep -------------------------------------------------------------------------------- /apps/web/lib/mongodb.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | // Use environment variable for flexibility 4 | const DATABASE_URL = process.env.DATABASE_URL as string; 5 | 6 | if (!DATABASE_URL) { 7 | throw new Error("No database url"); // If no database throws url 8 | } 9 | 10 | const connectDB = async () => { 11 | if (mongoose.connection.readyState >= 1) { 12 | return; 13 | } 14 | 15 | try { 16 | await mongoose.connect(DATABASE_URL); 17 | console.log("MongoDB Connected"); 18 | } catch (error) { 19 | console.error("MongoDB Connection Error:", error); 20 | throw error; // Propagate error for handling in routes 21 | } 22 | }; 23 | 24 | export default connectDB; 25 | -------------------------------------------------------------------------------- /apps/web/lib/query-client.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/react-query"; 2 | 3 | const queryClient = new QueryClient(); 4 | export default queryClient; 5 | -------------------------------------------------------------------------------- /apps/web/models/Question.ts: -------------------------------------------------------------------------------- 1 | // models/Question.ts 2 | import mongoose, { Document, Model, Schema, SchemaTypes } from "mongoose"; 3 | 4 | // Define the nested interfaces 5 | export interface IFile { 6 | name: string; 7 | language: "javascript" | "css" | "html"; 8 | content: string; 9 | } 10 | 11 | export interface IQuestionerInfo { 12 | name: string; 13 | profilePic: string; 14 | additionalInfo?: string; 15 | } 16 | 17 | export type CommonInput = number[] | string | number | object; 18 | export type CommonOutput = number | boolean | string | number[] | object; 19 | 20 | export type TestCases = { 21 | input: CommonInput; // More specific but still flexible input types 22 | output: CommonOutput; // More specific but still flexible output types 23 | description: string; // Description of the test case 24 | }; 25 | 26 | export interface IQuestionDetails { 27 | name: string; 28 | questionaerInfo: IQuestionerInfo; 29 | techStack: string[]; 30 | difficulty: "Easy" | "Medium" | "Hard"; 31 | time: number; 32 | questionDescription: string; 33 | requirements: string[]; 34 | notes: string[]; 35 | companies: string[]; 36 | questionType: "ui" | "logical"; 37 | testCases?: TestCases[]; 38 | } 39 | 40 | // Main document interface 41 | export interface IQuestion extends Document { 42 | initialVanillaFiles: IFile[]; 43 | initialReactFiles: IFile[]; 44 | solutionVanillaFiles: IFile[]; 45 | solutionReactFiles: IFile[]; 46 | questionDetails: IQuestionDetails; 47 | } 48 | 49 | // Define the schemas 50 | const FileSchema = new Schema({ 51 | name: { type: String, required: true }, 52 | language: { 53 | type: String, 54 | enum: ["javascript", "css", "html"], 55 | required: true, 56 | }, 57 | content: { type: String, required: true }, 58 | }); 59 | 60 | const QuestionerInfoSchema = new Schema({ 61 | name: { type: String, required: true }, 62 | profilePic: { type: String, required: true }, 63 | additionalInfo: { type: String }, 64 | }); 65 | 66 | const TestCasesSchema = new Schema({ 67 | input: { type: SchemaTypes.Mixed, required: true }, 68 | output: { type: SchemaTypes.Mixed, required: true }, 69 | description: { type: String, required: true }, 70 | }); 71 | 72 | const QuestionSchema = new Schema({ 73 | initialVanillaFiles: [FileSchema], 74 | initialReactFiles: [FileSchema], 75 | solutionVanillaFiles: [FileSchema], 76 | solutionReactFiles: [FileSchema], 77 | questionDetails: { 78 | name: { type: String, required: true }, 79 | questionaerInfo: { type: QuestionerInfoSchema, required: true }, 80 | techStack: { 81 | type: [String], 82 | enum: ["html", "css", "js", "react"], 83 | required: true, 84 | }, 85 | difficulty: { 86 | type: String, 87 | enum: ["Easy", "Medium", "Hard"], 88 | required: true, 89 | }, 90 | time: { type: Number, required: true }, 91 | questionDescription: { type: String, required: true }, 92 | requirements: [String], 93 | notes: [String], 94 | companies: { 95 | type: [String], 96 | enum: ["Google", "Facebook", "Twitter", "Apple", "Microsoft"], 97 | }, 98 | questionType: { 99 | type: String, 100 | enum: ["ui", "logical"], 101 | required: true, 102 | }, 103 | testCases: [TestCasesSchema], 104 | }, 105 | }); 106 | 107 | // Use type assertion for model to avoid issues with existing models 108 | const modelName = "Question"; 109 | const QuestionModel: Model = 110 | (mongoose.models[modelName] as Model) || 111 | mongoose.model(modelName, QuestionSchema); 112 | 113 | export default QuestionModel; 114 | -------------------------------------------------------------------------------- /apps/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/web/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | transpilePackages: ["@workspace/ui", "@workspace/editor"], 4 | images: { 5 | domains: ["ui.aceternity.com"], 6 | }, 7 | }; 8 | 9 | export default nextConfig; 10 | -------------------------------------------------------------------------------- /apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "node --no-node-snapshot node_modules/next/dist/bin/next dev --turbopack", 8 | "build": "node --no-node-snapshot node_modules/next/dist/bin/next build", 9 | "start": "node --no-node-snapshot node_modules/next/dist/bin/next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-accordion": "^1.2.3", 14 | "@radix-ui/react-avatar": "^1.1.7", 15 | "@radix-ui/react-collapsible": "^1.1.3", 16 | "@radix-ui/react-dialog": "^1.1.6", 17 | "@radix-ui/react-navigation-menu": "^1.2.5", 18 | "@radix-ui/react-progress": "^1.1.2", 19 | "@radix-ui/react-scroll-area": "^1.2.6", 20 | "@radix-ui/react-select": "^2.1.6", 21 | "@radix-ui/react-separator": "^1.1.2", 22 | "@radix-ui/react-slot": "^1.1.1", 23 | "@radix-ui/react-tabs": "^1.1.3", 24 | "@radix-ui/react-tooltip": "^1.1.8", 25 | "@react-three/drei": "^10.0.3", 26 | "@react-three/fiber": "9.0.0-alpha.8", 27 | "@tanstack/react-query": "^5.74.3", 28 | "@trigger.dev/companyicons": "^1.5.42", 29 | "@types/react-syntax-highlighter": "^15.5.13", 30 | "@types/three": "^0.174.0", 31 | "@workspace/editor": "workspace:*", 32 | "@workspace/ui": "workspace:*", 33 | "axios": "^1.8.2", 34 | "better-auth": "^1.2.8", 35 | "cobe": "^0.6.3", 36 | "framer-motion": "^12.4.7", 37 | "isolated-vm": "^5.0.3", 38 | "lucide-react": "0.456.0", 39 | "mongodb": "^6.13.0", 40 | "mongoose": "^8.10.0", 41 | "motion": "^12.4.7", 42 | "next": "^15.2.3", 43 | "next-themes": "^0.4.3", 44 | "react": "^19.0.0", 45 | "react-dom": "^19.0.0", 46 | "react-markdown": "^10.1.0", 47 | "react-query": "^3.39.3", 48 | "react-syntax-highlighter": "^15.6.1", 49 | "rehype-raw": "^7.0.0", 50 | "rehype-sanitize": "^6.0.0", 51 | "remark-gfm": "^4.0.1", 52 | "three": "^0.174.0", 53 | "three-globe": "^2.41.12" 54 | }, 55 | "devDependencies": { 56 | "@types/node": "^20", 57 | "@types/react": "18.3.0", 58 | "@types/react-dom": "18.3.1", 59 | "@workspace/eslint-config": "workspace:^", 60 | "@workspace/typescript-config": "workspace:*", 61 | "autoprefixer": "^10.4.20", 62 | "postcss": "^8", 63 | "tailwindcss": "^3.4.1", 64 | "typescript": "^5" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /apps/web/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from "@workspace/ui/postcss.config"; -------------------------------------------------------------------------------- /apps/web/public/images/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/web/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/apps/web/public/images/logo.png -------------------------------------------------------------------------------- /apps/web/public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/web/public/images/pratiyank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/apps/web/public/images/pratiyank.jpg -------------------------------------------------------------------------------- /apps/web/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | export * from "@workspace/ui/tailwind.config"; -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@workspace/typescript-config/nextjs.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["./*"], 7 | "@workspace/ui/*": ["../../packages/ui/src/*"] 8 | }, 9 | "plugins": [ 10 | { 11 | "name": "next" 12 | } 13 | ] 14 | }, 15 | "include": [ 16 | "next-env.d.ts", 17 | "next.config.mjs", 18 | "**/*.ts", 19 | "**/*.tsx", 20 | ".next/types/**/*.ts" 21 | ], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadcn-ui-monorepo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "build": "turbo build", 7 | "dev": "turbo dev", 8 | "lint": "turbo lint", 9 | "format": "prettier --write \"**/*.{ts,tsx,md}\"" 10 | }, 11 | "devDependencies": { 12 | "@workspace/eslint-config": "workspace:*", 13 | "@workspace/typescript-config": "workspace:*", 14 | "prettier": "^3.2.5", 15 | "turbo": "^2.3.0", 16 | "typescript": "5.5.4" 17 | }, 18 | "packageManager": "pnpm@9.12.3", 19 | "engines": { 20 | "node": ">=20" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workspace/editor", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "lint": "eslint . --max-warnings 0" 8 | }, 9 | "dependencies": { 10 | "@babel/standalone": "^7.26.8", 11 | "@monaco-editor/react": "^4.4.6", 12 | "@types/babel__standalone": "^7.1.9", 13 | "@workspace/ui": "workspace:*", 14 | "lucide-react": "0.456.0", 15 | "monaco-editor": "^0.52.2", 16 | "monaco-types": "^0.1.0", 17 | "react": "^18.2.0", 18 | "react-dom": "18.2.0", 19 | "react-split": "^2.0.14", 20 | "tailwind-scrollbar-hide": "^2.0.0", 21 | "tailwindcss-animate": "^1.0.7" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^18.0.0", 25 | "@workspace/eslint-config": "workspace:^", 26 | "@workspace/typescript-config": "workspace:*", 27 | "postcss": "^8", 28 | "tailwindcss": "^3.4.1", 29 | "typescript": "^5.0.0" 30 | }, 31 | "exports": { 32 | "./components/*": "./src/components/*.tsx", 33 | "./hooks/*": "./src/hooks/*.ts", 34 | "./data/*": "./src/data/*.ts" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/editor/package.json.2565922834: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workspace/editor", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "lint": "eslint . --max-warnings 0" 8 | }, 9 | "dependencies": { 10 | "@babel/standalone": "^7.26.8", 11 | "@monaco-editor/react": "^4.4.6", 12 | "@types/babel__standalone": "^7.1.9", 13 | "@workspace/ui": "workspace:*", 14 | "lucide-react": "0.456.0", 15 | "monaco-editor": "^0.52.2", 16 | "monaco-types": "^0.1.0", 17 | "react": "^18.2.0", 18 | "react-dom": "18.2.0", 19 | "react-split": "^2.0.14", 20 | "tailwind-scrollbar-hide": "^2.0.0", 21 | "tailwindcss-animate": "^1.0.7" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^18.0.0", 25 | "@workspace/eslint-config": "workspace:^", 26 | "@workspace/typescript-config": "workspace:*", 27 | "postcss": "^8", 28 | "tailwindcss": "^3.4.1", 29 | "typescript": "^5.0.0" 30 | }, 31 | "exports": { 32 | "./components/*": "./src/components/*.tsx", 33 | "./hooks/*": "./src/hooks/*.ts" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/editor/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from "@workspace/ui/postcss.config"; 2 | -------------------------------------------------------------------------------- /packages/editor/src/components/Companies.tsx: -------------------------------------------------------------------------------- 1 | const companies = [ 2 | { 3 | name: "Google", 4 | logo: ( 5 | 12 | 16 | 20 | 24 | 28 | 29 | ), 30 | }, 31 | { 32 | name: "Facebook", 33 | logo: ( 34 | 42 | 46 | 54 | 55 | 56 | 57 | 61 | 69 | 70 | 71 | 72 | 76 | 80 | 84 | 85 | ), 86 | }, 87 | { 88 | name: "Twitter", 89 | logo: ( 90 | 98 | 99 | 100 | ), 101 | }, 102 | { 103 | name: "Apple", 104 | logo: ( 105 | 113 | 114 | 115 | ), 116 | }, 117 | { 118 | name: "Microsoft", 119 | logo: ( 120 | 121 | 122 | 123 | 124 | 125 | 126 | ), 127 | }, 128 | ]; 129 | type AllowedCompanies = 130 | | "Google" 131 | | "Facebook" 132 | | "Twitter" 133 | | "Apple" 134 | | "Microsoft"; 135 | 136 | type Companies = { 137 | companiesArray: AllowedCompanies[]; 138 | }; 139 | export default function CompanyLogo({ companiesArray }: Companies) { 140 | const logos = companies.filter((comp) => 141 | companiesArray.includes(comp.name as AllowedCompanies) 142 | ); 143 | 144 | return ( 145 |
    146 | {logos.map((company) => ( 147 |
    148 | {company.logo} 149 |
    150 | ))} 151 |
    152 | ); 153 | } 154 | -------------------------------------------------------------------------------- /packages/editor/src/components/TechLogo.tsx: -------------------------------------------------------------------------------- 1 | interface LogoProps { 2 | logos?: LogoName[]; 3 | } 4 | 5 | type LogoName = "html" | "css" | "js" | "react"; 6 | 7 | const logoSvgs: Record = { 8 | html: ` 9 | 10 | 11 | 12 | 13 | `, 14 | css: ` 15 | 16 | 17 | 18 | 19 | `, 20 | js: ` 21 | 22 | 23 | `, 24 | react: ` 25 | 26 | 27 | 28 | 29 | 30 | 31 | `, 32 | }; 33 | 34 | export default function TechLogoComponent({ logos }: LogoProps) { 35 | return ( 36 |
    37 | {logos && 38 | logos.map((logoName, i) => ( 39 |
    44 | ))} 45 |
    46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /packages/editor/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | export * from "@workspace/ui/tailwind.config"; 2 | -------------------------------------------------------------------------------- /packages/editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@workspace/typescript-config/react-library.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@workspace/editor/*": ["./src/*"] 7 | }, 8 | "jsx": "react-jsx", // ✅ Fixes the JSX error 9 | "jsxImportSource": "react" // ✅ Needed if using "react-jsx" 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", "dist"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/eslint-config/README.md: -------------------------------------------------------------------------------- 1 | # `@workspace/eslint-config` 2 | 3 | Shared eslint configuration for the workspace. 4 | -------------------------------------------------------------------------------- /packages/eslint-config/base.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js" 2 | import eslintConfigPrettier from "eslint-config-prettier" 3 | import onlyWarn from "eslint-plugin-only-warn" 4 | import turboPlugin from "eslint-plugin-turbo" 5 | import tseslint from "typescript-eslint" 6 | 7 | /** 8 | * A shared ESLint configuration for the repository. 9 | * 10 | * @type {import("eslint").Linter.Config} 11 | * */ 12 | export const config = [ 13 | js.configs.recommended, 14 | eslintConfigPrettier, 15 | ...tseslint.configs.recommended, 16 | { 17 | plugins: { 18 | turbo: turboPlugin, 19 | }, 20 | rules: { 21 | "turbo/no-undeclared-env-vars": "warn", 22 | }, 23 | }, 24 | { 25 | plugins: { 26 | onlyWarn, 27 | }, 28 | }, 29 | { 30 | ignores: ["dist/**"], 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /packages/eslint-config/next.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js" 2 | import pluginNext from "@next/eslint-plugin-next" 3 | import eslintConfigPrettier from "eslint-config-prettier" 4 | import pluginReact from "eslint-plugin-react" 5 | import pluginReactHooks from "eslint-plugin-react-hooks" 6 | import globals from "globals" 7 | import tseslint from "typescript-eslint" 8 | 9 | import { config as baseConfig } from "./base.js" 10 | 11 | /** 12 | * A custom ESLint configuration for libraries that use Next.js. 13 | * 14 | * @type {import("eslint").Linter.Config} 15 | * */ 16 | export const nextJsConfig = [ 17 | ...baseConfig, 18 | js.configs.recommended, 19 | eslintConfigPrettier, 20 | ...tseslint.configs.recommended, 21 | { 22 | ...pluginReact.configs.flat.recommended, 23 | languageOptions: { 24 | ...pluginReact.configs.flat.recommended.languageOptions, 25 | globals: { 26 | ...globals.serviceworker, 27 | }, 28 | }, 29 | }, 30 | { 31 | plugins: { 32 | "@next/next": pluginNext, 33 | }, 34 | rules: { 35 | ...pluginNext.configs.recommended.rules, 36 | ...pluginNext.configs["core-web-vitals"].rules, 37 | }, 38 | }, 39 | { 40 | plugins: { 41 | "react-hooks": pluginReactHooks, 42 | }, 43 | settings: { react: { version: "detect" } }, 44 | rules: { 45 | ...pluginReactHooks.configs.recommended.rules, 46 | // React scope no longer necessary with new JSX transform. 47 | "react/react-in-jsx-scope": "off", 48 | "react/prop-types": "off", 49 | }, 50 | }, 51 | ] 52 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workspace/eslint-config", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "exports": { 7 | "./base": "./base.js", 8 | "./next-js": "./next.js", 9 | "./react-internal": "./react-internal.js" 10 | }, 11 | "devDependencies": { 12 | "@next/eslint-plugin-next": "^15.1.0", 13 | "@typescript-eslint/eslint-plugin": "^8.15.0", 14 | "@typescript-eslint/parser": "^8.15.0", 15 | "eslint": "^9.15.0", 16 | "eslint-config-prettier": "^9.1.0", 17 | "eslint-plugin-only-warn": "^1.1.0", 18 | "eslint-plugin-react": "^7.37.2", 19 | "eslint-plugin-react-hooks": "^5.0.0", 20 | "eslint-plugin-turbo": "^2.3.0", 21 | "globals": "^15.12.0", 22 | "typescript": "^5.3.3", 23 | "typescript-eslint": "^8.15.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/eslint-config/react-internal.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js" 2 | import eslintConfigPrettier from "eslint-config-prettier" 3 | import pluginReact from "eslint-plugin-react" 4 | import pluginReactHooks from "eslint-plugin-react-hooks" 5 | import globals from "globals" 6 | import tseslint from "typescript-eslint" 7 | 8 | import { config as baseConfig } from "./base.js" 9 | 10 | /** 11 | * A custom ESLint configuration for libraries that use React. 12 | * 13 | * @type {import("eslint").Linter.Config} */ 14 | export const config = [ 15 | ...baseConfig, 16 | js.configs.recommended, 17 | eslintConfigPrettier, 18 | ...tseslint.configs.recommended, 19 | pluginReact.configs.flat.recommended, 20 | { 21 | languageOptions: { 22 | ...pluginReact.configs.flat.recommended.languageOptions, 23 | globals: { 24 | ...globals.serviceworker, 25 | ...globals.browser, 26 | }, 27 | }, 28 | }, 29 | { 30 | plugins: { 31 | "react-hooks": pluginReactHooks, 32 | }, 33 | settings: { react: { version: "detect" } }, 34 | rules: { 35 | ...pluginReactHooks.configs.recommended.rules, 36 | // React scope no longer necessary with new JSX transform. 37 | "react/react-in-jsx-scope": "off", 38 | "react/prop-types": "off", 39 | }, 40 | }, 41 | ] 42 | -------------------------------------------------------------------------------- /packages/typescript-config/README.md: -------------------------------------------------------------------------------- 1 | # `@workspace/typescript-config` 2 | 3 | Shared typescript configuration for the workspace. 4 | -------------------------------------------------------------------------------- /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": "@workspace/typescript-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "PROPRIETARY", 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/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "iconLibrary": "lucide", 13 | "aliases": { 14 | "components": "@workspace/ui/components", 15 | "utils": "@workspace/ui/lib/utils", 16 | "hooks": "@workspace/ui/hooks", 17 | "lib": "@workspace/ui/lib", 18 | "ui": "@workspace/ui/components" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/ui/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { config } from "@workspace/eslint-config/react-internal" 2 | 3 | /** @type {import("eslint").Linter.Config} */ 4 | export default config 5 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workspace/ui", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "lint": "eslint . --max-warnings 0" 8 | }, 9 | "dependencies": { 10 | "@radix-ui/react-accordion": "^1.2.3", 11 | "@radix-ui/react-avatar": "^1.1.7", 12 | "@radix-ui/react-collapsible": "^1.1.3", 13 | "@radix-ui/react-dialog": "^1.1.6", 14 | "@radix-ui/react-navigation-menu": "^1.2.5", 15 | "@radix-ui/react-progress": "^1.1.2", 16 | "@radix-ui/react-scroll-area": "^1.2.6", 17 | "@radix-ui/react-select": "^2.1.6", 18 | "@radix-ui/react-separator": "^1.1.2", 19 | "@radix-ui/react-slot": "^1.1.1", 20 | "@radix-ui/react-tabs": "^1.1.3", 21 | "@radix-ui/react-tooltip": "^1.1.8", 22 | "class-variance-authority": "^0.7.0", 23 | "clsx": "^2.1.1", 24 | "framer-motion": "^12.4.7", 25 | "lucide-react": "0.456.0", 26 | "motion": "^12.4.7", 27 | "next-themes": "^0.4.3", 28 | "react": "^19.0.0", 29 | "react-dom": "^19.0.0", 30 | "tailwind-merge": "^2.5.5", 31 | "tailwind-scrollbar-hide": "^2.0.0", 32 | "tailwindcss-animate": "^1.0.7", 33 | "zod": "^3.23.8" 34 | }, 35 | "devDependencies": { 36 | "@turbo/gen": "^2.2.3", 37 | "@types/node": "^22.9.0", 38 | "@types/react": "18.3.0", 39 | "@types/react-dom": "18.3.1", 40 | "@workspace/eslint-config": "workspace:*", 41 | "@workspace/typescript-config": "workspace:*", 42 | "autoprefixer": "^10.4.20", 43 | "postcss": "^8.4.47", 44 | "react": "^18.3.1", 45 | "tailwindcss": "^3.4.14", 46 | "typescript": "^5.6.3" 47 | }, 48 | "exports": { 49 | "./globals.css": "./src/styles/globals.css", 50 | "./postcss.config": "./postcss.config.mjs", 51 | "./tailwind.config": "./tailwind.config.ts", 52 | "./lib/*": "./src/lib/*.ts", 53 | "./components/*": "./src/components/*.tsx", 54 | "./hooks/*": "./src/hooks/*.ts" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | 9 | export default config; -------------------------------------------------------------------------------- /packages/ui/src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/packages/ui/src/components/.gitkeep -------------------------------------------------------------------------------- /packages/ui/src/components/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"; 5 | import { ChevronDown } from "lucide-react"; 6 | 7 | import { cn } from "@workspace/ui/lib/utils"; 8 | 9 | const Accordion = AccordionPrimitive.Root; 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )); 21 | AccordionItem.displayName = "AccordionItem"; 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )); 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
    {children}
    53 |
    54 | )); 55 | AccordionContent.displayName = AccordionPrimitive.Content.displayName; 56 | 57 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; 58 | -------------------------------------------------------------------------------- /packages/ui/src/components/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@workspace/ui/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /packages/ui/src/components/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@workspace/ui/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
    33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /packages/ui/src/components/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 "@workspace/ui/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 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/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@workspace/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 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
    41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
    53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
    61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
    73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /packages/ui/src/components/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@workspace/ui/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
    67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
    81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogTrigger, 116 | DialogClose, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /packages/ui/src/components/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@workspace/ui/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /packages/ui/src/components/navigation-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" 3 | import { cva } from "class-variance-authority" 4 | import { ChevronDown } from "lucide-react" 5 | 6 | import { cn } from "@workspace/ui/lib/utils" 7 | 8 | const NavigationMenu = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 20 | {children} 21 | 22 | 23 | )) 24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName 25 | 26 | const NavigationMenuList = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, ...props }, ref) => ( 30 | 38 | )) 39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName 40 | 41 | const NavigationMenuItem = NavigationMenuPrimitive.Item 42 | 43 | const navigationMenuTriggerStyle = cva( 44 | "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" 45 | ) 46 | 47 | const NavigationMenuTrigger = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef 50 | >(({ className, children, ...props }, ref) => ( 51 | 56 | {children}{" "} 57 | 62 | )) 63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName 64 | 65 | const NavigationMenuContent = React.forwardRef< 66 | React.ElementRef, 67 | React.ComponentPropsWithoutRef 68 | >(({ className, ...props }, ref) => ( 69 | 77 | )) 78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName 79 | 80 | const NavigationMenuLink = NavigationMenuPrimitive.Link 81 | 82 | const NavigationMenuViewport = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef 85 | >(({ className, ...props }, ref) => ( 86 |
    87 | 95 |
    96 | )) 97 | NavigationMenuViewport.displayName = 98 | NavigationMenuPrimitive.Viewport.displayName 99 | 100 | const NavigationMenuIndicator = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 112 |
    113 | 114 | )) 115 | NavigationMenuIndicator.displayName = 116 | NavigationMenuPrimitive.Indicator.displayName 117 | 118 | export { 119 | navigationMenuTriggerStyle, 120 | NavigationMenu, 121 | NavigationMenuList, 122 | NavigationMenuItem, 123 | NavigationMenuContent, 124 | NavigationMenuTrigger, 125 | NavigationMenuLink, 126 | NavigationMenuIndicator, 127 | NavigationMenuViewport, 128 | } 129 | -------------------------------------------------------------------------------- /packages/ui/src/components/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from "@workspace/ui/lib/utils" 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )) 26 | Progress.displayName = ProgressPrimitive.Root.displayName 27 | 28 | export { Progress } 29 | -------------------------------------------------------------------------------- /packages/ui/src/components/scroll-animation.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useRef } from "react"; 3 | import { useScroll, useTransform, motion, MotionValue } from "framer-motion"; 4 | 5 | export const ContainerScroll = ({ 6 | children, 7 | }: { 8 | children: React.ReactNode; 9 | }) => { 10 | const containerRef = useRef(null); 11 | const { scrollYProgress } = useScroll({ 12 | target: containerRef, 13 | }); 14 | const [isMobile, setIsMobile] = React.useState(false); 15 | 16 | React.useEffect(() => { 17 | const checkMobile = () => { 18 | setIsMobile(window.innerWidth <= 768); 19 | }; 20 | checkMobile(); 21 | window.addEventListener("resize", checkMobile); 22 | return () => { 23 | window.removeEventListener("resize", checkMobile); 24 | }; 25 | }, []); 26 | 27 | const scaleDimensions = () => { 28 | return isMobile ? [0.7, 0.9] : [1.05, 1]; 29 | }; 30 | 31 | // Modified rotation transform to be straight (0 degrees) when in view 32 | const rotate = useTransform(scrollYProgress, [0, 0.1, 1], [20, 0, 0]); 33 | const scale = useTransform(scrollYProgress, [0, 1], scaleDimensions()); 34 | const translate = useTransform(scrollYProgress, [0, 1], [0, -100]); 35 | // Added opacity transform that increases as content comes into view 36 | const opacity = useTransform(scrollYProgress, [0, 0.05, 0.15], [0.2, 0.6, 1]); 37 | 38 | return ( 39 |
    44 |
    50 | 56 | {children} 57 | 58 |
    59 |
    60 | ); 61 | }; 62 | 63 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 64 | export const Header = ({ translate, titleComponent }: any) => { 65 | return ( 66 | 72 | {titleComponent} 73 | 74 | ); 75 | }; 76 | 77 | export const Card = ({ 78 | rotate, 79 | scale, 80 | opacity, 81 | children, 82 | }: { 83 | rotate: MotionValue; 84 | scale: MotionValue; 85 | translate: MotionValue; 86 | opacity: MotionValue; 87 | children: React.ReactNode; 88 | }) => { 89 | return ( 90 | 97 | 98 | {children} 99 | 100 | 101 | ); 102 | }; 103 | -------------------------------------------------------------------------------- /packages/ui/src/components/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "@workspace/ui/lib/utils" 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )) 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )) 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 47 | 48 | export { ScrollArea, ScrollBar } 49 | -------------------------------------------------------------------------------- /packages/ui/src/components/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 6 | 7 | import { cn } from "@workspace/ui/lib/utils" 8 | 9 | const Select = SelectPrimitive.Root 10 | 11 | const SelectGroup = SelectPrimitive.Group 12 | 13 | const SelectValue = SelectPrimitive.Value 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | span]:line-clamp-1", 23 | className 24 | )} 25 | {...props} 26 | > 27 | {children} 28 | 29 | 30 | 31 | 32 | )) 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 34 | 35 | const SelectScrollUpButton = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | 48 | 49 | )) 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 51 | 52 | const SelectScrollDownButton = React.forwardRef< 53 | React.ElementRef, 54 | React.ComponentPropsWithoutRef 55 | >(({ className, ...props }, ref) => ( 56 | 64 | 65 | 66 | )) 67 | SelectScrollDownButton.displayName = 68 | SelectPrimitive.ScrollDownButton.displayName 69 | 70 | const SelectContent = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >(({ className, children, position = "popper", ...props }, ref) => ( 74 | 75 | 86 | 87 | 94 | {children} 95 | 96 | 97 | 98 | 99 | )) 100 | SelectContent.displayName = SelectPrimitive.Content.displayName 101 | 102 | const SelectLabel = React.forwardRef< 103 | React.ElementRef, 104 | React.ComponentPropsWithoutRef 105 | >(({ className, ...props }, ref) => ( 106 | 111 | )) 112 | SelectLabel.displayName = SelectPrimitive.Label.displayName 113 | 114 | const SelectItem = React.forwardRef< 115 | React.ElementRef, 116 | React.ComponentPropsWithoutRef 117 | >(({ className, children, ...props }, ref) => ( 118 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | {children} 133 | 134 | )) 135 | SelectItem.displayName = SelectPrimitive.Item.displayName 136 | 137 | const SelectSeparator = React.forwardRef< 138 | React.ElementRef, 139 | React.ComponentPropsWithoutRef 140 | >(({ className, ...props }, ref) => ( 141 | 146 | )) 147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 148 | 149 | export { 150 | Select, 151 | SelectGroup, 152 | SelectValue, 153 | SelectTrigger, 154 | SelectContent, 155 | SelectLabel, 156 | SelectItem, 157 | SelectSeparator, 158 | SelectScrollUpButton, 159 | SelectScrollDownButton, 160 | } 161 | -------------------------------------------------------------------------------- /packages/ui/src/components/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@workspace/ui/lib/utils" 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /packages/ui/src/components/sheet.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SheetPrimitive from "@radix-ui/react-dialog" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | import { X } from "lucide-react" 7 | 8 | import { cn } from "@workspace/ui/lib/utils" 9 | 10 | const Sheet = SheetPrimitive.Root 11 | 12 | const SheetTrigger = SheetPrimitive.Trigger 13 | 14 | const SheetClose = SheetPrimitive.Close 15 | 16 | const SheetPortal = SheetPrimitive.Portal 17 | 18 | const SheetOverlay = React.forwardRef< 19 | React.ElementRef, 20 | React.ComponentPropsWithoutRef 21 | >(({ className, ...props }, ref) => ( 22 | 30 | )) 31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName 32 | 33 | const sheetVariants = cva( 34 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", 35 | { 36 | variants: { 37 | side: { 38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", 39 | bottom: 40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", 41 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", 42 | right: 43 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", 44 | }, 45 | }, 46 | defaultVariants: { 47 | side: "right", 48 | }, 49 | } 50 | ) 51 | 52 | interface SheetContentProps 53 | extends React.ComponentPropsWithoutRef, 54 | VariantProps {} 55 | 56 | const SheetContent = React.forwardRef< 57 | React.ElementRef, 58 | SheetContentProps 59 | >(({ side = "right", className, children, ...props }, ref) => ( 60 | 61 | 62 | 67 | {children} 68 | 69 | 70 | Close 71 | 72 | 73 | 74 | )) 75 | SheetContent.displayName = SheetPrimitive.Content.displayName 76 | 77 | const SheetHeader = ({ 78 | className, 79 | ...props 80 | }: React.HTMLAttributes) => ( 81 |
    88 | ) 89 | SheetHeader.displayName = "SheetHeader" 90 | 91 | const SheetFooter = ({ 92 | className, 93 | ...props 94 | }: React.HTMLAttributes) => ( 95 |
    102 | ) 103 | SheetFooter.displayName = "SheetFooter" 104 | 105 | const SheetTitle = React.forwardRef< 106 | React.ElementRef, 107 | React.ComponentPropsWithoutRef 108 | >(({ className, ...props }, ref) => ( 109 | 114 | )) 115 | SheetTitle.displayName = SheetPrimitive.Title.displayName 116 | 117 | const SheetDescription = React.forwardRef< 118 | React.ElementRef, 119 | React.ComponentPropsWithoutRef 120 | >(({ className, ...props }, ref) => ( 121 | 126 | )) 127 | SheetDescription.displayName = SheetPrimitive.Description.displayName 128 | 129 | export { 130 | Sheet, 131 | SheetPortal, 132 | SheetOverlay, 133 | SheetTrigger, 134 | SheetClose, 135 | SheetContent, 136 | SheetHeader, 137 | SheetFooter, 138 | SheetTitle, 139 | SheetDescription, 140 | } 141 | -------------------------------------------------------------------------------- /packages/ui/src/components/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@workspace/ui/lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
    12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /packages/ui/src/components/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@workspace/ui/lib/utils" 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
    10 | 15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className 48 | )} 49 | {...props} 50 | /> 51 | )) 52 | TableFooter.displayName = "TableFooter" 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )) 67 | TableRow.displayName = "TableRow" 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
    [role=checkbox]]:translate-y-[2px]", 77 | className 78 | )} 79 | {...props} 80 | /> 81 | )) 82 | TableHead.displayName = "TableHead" 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | [role=checkbox]]:translate-y-[2px]", 92 | className 93 | )} 94 | {...props} 95 | /> 96 | )) 97 | TableCell.displayName = "TableCell" 98 | 99 | const TableCaption = React.forwardRef< 100 | HTMLTableCaptionElement, 101 | React.HTMLAttributes 102 | >(({ className, ...props }, ref) => ( 103 |
    108 | )) 109 | TableCaption.displayName = "TableCaption" 110 | 111 | export { 112 | Table, 113 | TableHeader, 114 | TableBody, 115 | TableFooter, 116 | TableHead, 117 | TableRow, 118 | TableCell, 119 | TableCaption, 120 | } 121 | -------------------------------------------------------------------------------- /packages/ui/src/components/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TabsPrimitive from "@radix-ui/react-tabs" 5 | 6 | import { cn } from "@workspace/ui/lib/utils" 7 | 8 | const Tabs = TabsPrimitive.Root 9 | 10 | const TabsList = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )) 23 | TabsList.displayName = TabsPrimitive.List.displayName 24 | 25 | const TabsTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )) 38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName 39 | 40 | const TabsContent = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | )) 53 | TabsContent.displayName = TabsPrimitive.Content.displayName 54 | 55 | export { Tabs, TabsList, TabsTrigger, TabsContent } 56 | -------------------------------------------------------------------------------- /packages/ui/src/components/text-generate-effect.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useEffect } from "react"; 3 | import { motion, stagger, useAnimate } from "framer-motion"; 4 | import { cn } from "@workspace/ui/lib/utils"; 5 | 6 | const TextGenerateEffect = ({ 7 | words, 8 | className, 9 | filter = true, 10 | duration = 0.5, 11 | }: { 12 | words: string; 13 | className?: string; 14 | filter?: boolean; 15 | duration?: number; 16 | }) => { 17 | const [scope, animate] = useAnimate(); 18 | const wordsArray = words.split(" "); 19 | useEffect(() => { 20 | animate( 21 | "span", 22 | { 23 | opacity: 1, 24 | filter: filter ? "blur(0px)" : "none", 25 | }, 26 | { 27 | duration: duration ? duration : 1, 28 | delay: stagger(0.2), 29 | } 30 | ); 31 | }, [scope.current]); 32 | 33 | const renderWords = () => { 34 | return ( 35 | 36 | {wordsArray.map((word, idx) => { 37 | return ( 38 | 45 | {word}{" "} 46 | 47 | ); 48 | })} 49 | 50 | ); 51 | }; 52 | 53 | return ( 54 |
    55 |
    56 |
    {renderWords()}
    57 |
    58 |
    59 | ); 60 | }; 61 | 62 | export default TextGenerateEffect; 63 | -------------------------------------------------------------------------------- /packages/ui/src/components/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 5 | 6 | import { cn } from "@workspace/ui/lib/utils" 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider 9 | 10 | const Tooltip = TooltipPrimitive.Root 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 27 | )) 28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName 29 | 30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 31 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Frontend-Forge/frontend-forge/f472fcc030f824e7cf7e5f9b32037bfa363d9d14/packages/ui/src/hooks/.gitkeep -------------------------------------------------------------------------------- /packages/ui/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/ui/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 240 5.9% 10%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 240 10% 3.9%; 26 | --chart-1: 12 76% 61%; 27 | --chart-2: 173 58% 39%; 28 | --chart-3: 197 37% 24%; 29 | --chart-4: 43 74% 66%; 30 | --chart-5: 27 87% 67%; 31 | --radius: 0.5rem; 32 | } 33 | .dark { 34 | --background: 240 10% 3.9%; 35 | --foreground: 0 0% 98%; 36 | --card: 240 10% 3.9%; 37 | --card-foreground: 0 0% 98%; 38 | --popover: 240 10% 3.9%; 39 | --popover-foreground: 0 0% 98%; 40 | --primary: 0 0% 98%; 41 | --primary-foreground: 240 5.9% 10%; 42 | --secondary: 240 3.7% 15.9%; 43 | --secondary-foreground: 0 0% 98%; 44 | --muted: 240 3.7% 15.9%; 45 | --muted-foreground: 240 5% 64.9%; 46 | --accent: 240 3.7% 15.9%; 47 | --accent-foreground: 0 0% 98%; 48 | --destructive: 0 62.8% 30.6%; 49 | --destructive-foreground: 0 0% 98%; 50 | --border: 240 3.7% 15.9%; 51 | --input: 240 3.7% 15.9%; 52 | --ring: 240 4.9% 83.9%; 53 | --chart-1: 220 70% 50%; 54 | --chart-2: 160 60% 45%; 55 | --chart-3: 30 80% 55%; 56 | --chart-4: 280 65% 60%; 57 | --chart-5: 340 75% 55%; 58 | } 59 | } 60 | 61 | @layer base { 62 | * { 63 | @apply border-border; 64 | } 65 | body { 66 | @apply bg-background text-foreground; 67 | } 68 | } 69 | 70 | @layer base { 71 | * { 72 | @apply border-border outline-ring/50; 73 | } 74 | body { 75 | @apply bg-background text-foreground; 76 | } 77 | } 78 | 79 | .gutter { 80 | @apply bg-gray-300 dark:bg-[#2d2d2d] bg-no-repeat bg-center transition-colors; 81 | } 82 | 83 | .gutter:hover { 84 | @apply bg-gray-400 dark:bg-[#404040]; 85 | } 86 | 87 | .gutter.gutter-horizontal { 88 | @apply cursor-col-resize; 89 | } 90 | -------------------------------------------------------------------------------- /packages/ui/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import tailwindcssAnimate from "tailwindcss-animate"; 3 | import { fontFamily } from "tailwindcss/defaultTheme"; 4 | import tailwindScrollbarHide from "tailwind-scrollbar-hide"; 5 | 6 | const config = { 7 | darkMode: ["class"], 8 | content: [ 9 | "app/**/*.{ts,tsx}", 10 | "components/**/*.{ts,tsx}", 11 | "../../packages/ui/src/components/**/*.{ts,tsx}", 12 | "../../packages/editor/src/components/**/*.{ts,tsx}", 13 | ], 14 | theme: { 15 | extend: { 16 | fontFamily: { 17 | sans: ["var(--font-sans)", ...fontFamily.sans], 18 | mono: ["var(--font-mono)", ...fontFamily.mono], 19 | }, 20 | colors: { 21 | border: "hsl(var(--border))", 22 | input: "hsl(var(--input))", 23 | ring: "hsl(var(--ring))", 24 | background: "hsl(var(--background))", 25 | foreground: "hsl(var(--foreground))", 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 | destructive: { 35 | DEFAULT: "hsl(var(--destructive))", 36 | foreground: "hsl(var(--destructive-foreground))", 37 | }, 38 | muted: { 39 | DEFAULT: "hsl(var(--muted))", 40 | foreground: "hsl(var(--muted-foreground))", 41 | }, 42 | accent: { 43 | DEFAULT: "hsl(var(--accent))", 44 | foreground: "hsl(var(--accent-foreground))", 45 | }, 46 | popover: { 47 | DEFAULT: "hsl(var(--popover))", 48 | foreground: "hsl(var(--popover-foreground))", 49 | }, 50 | card: { 51 | DEFAULT: "hsl(var(--card))", 52 | foreground: "hsl(var(--card-foreground))", 53 | }, 54 | }, 55 | borderRadius: { 56 | lg: "var(--radius)", 57 | md: "calc(var(--radius) - 2px)", 58 | sm: "calc(var(--radius) - 4px)", 59 | }, 60 | keyframes: { 61 | "accordion-down": { 62 | from: { 63 | height: "0", 64 | }, 65 | to: { 66 | height: "var(--radix-accordion-content-height)", 67 | }, 68 | }, 69 | "accordion-up": { 70 | from: { 71 | height: "var(--radix-accordion-content-height)", 72 | }, 73 | to: { 74 | height: "0", 75 | }, 76 | }, 77 | marquee: { 78 | from: { 79 | transform: "translateX(0)", 80 | }, 81 | to: { 82 | transform: "translateX(calc(-100% - var(--gap)))", 83 | }, 84 | }, 85 | "marquee-vertical": { 86 | from: { 87 | transform: "translateY(0)", 88 | }, 89 | to: { 90 | transform: "translateY(calc(-100% - var(--gap)))", 91 | }, 92 | }, 93 | }, 94 | animation: { 95 | "accordion-down": "accordion-down 0.2s ease-out", 96 | "accordion-up": "accordion-up 0.2s ease-out", 97 | marquee: "marquee var(--duration) infinite linear", 98 | "marquee-vertical": "marquee-vertical var(--duration) linear infinite", 99 | }, 100 | screens: { 101 | bp1: "800px", 102 | bp2: "1057px", 103 | bp3: "566px", 104 | bp4: "422px", 105 | bp5: "776px", 106 | bp6: "722px", 107 | bp7: "769px", 108 | }, 109 | }, 110 | }, 111 | plugins: [tailwindcssAnimate, tailwindScrollbarHide], 112 | } satisfies Config; 113 | 114 | export default config; 115 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@workspace/typescript-config/react-library.json", 3 | "compilerOptions": { 4 | //"outDir": "dist" 5 | "baseUrl": ".", 6 | "paths": { 7 | "@workspace/ui/*": ["./src/*"] 8 | } 9 | }, 10 | "include": ["."], 11 | "exclude": ["node_modules", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@workspace/typescript-config/react-library.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src", "turbo"], 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | - "backend/*" 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@workspace/typescript-config/base.json" 3 | } 4 | -------------------------------------------------------------------------------- /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 | "env": ["DATABASE_URL", "OPENAI_API_KEY"] 10 | }, 11 | "lint": { 12 | "dependsOn": ["^lint"] 13 | }, 14 | "check-types": { 15 | "dependsOn": ["^check-types"] 16 | }, 17 | "dev": { 18 | "cache": false, 19 | "persistent": true, 20 | "env": ["DATABASE_URL", "OPENAI_API_KEY"] 21 | } 22 | } 23 | } 24 | --------------------------------------------------------------------------------