├── .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 |
(window.location.href = "/questions")}
67 | className="bg-[#E2FB75] hover:bg-[#E2FB75]/80 text-black"
68 | >
69 | Browse Questions
70 |
71 |
72 |
73 |
74 | );
75 |
76 | if (isError)
77 | return (
78 |
81 |
82 |
83 |
86 |
Unable to Load Editor
87 |
88 | {error?.message ||
89 | "An unexpected error occurred while loading the question."}
90 |
91 |
92 |
(window.location.href = "/questions")}
95 | className="border-gray-300 dark:border-gray-700"
96 | >
97 | Back to Questions
98 |
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 |
86 | Get Started now
87 |
88 |
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 |
184 |
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 |
13 |
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 |
13 |
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 |
82 |
83 | Sort by
84 |
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 |
50 | ) : (
51 |
58 | )}
59 |
60 |
Frontend Forge
61 |
62 |
63 |
64 |
65 |
66 |
67 | {/* Navigation */}
68 |
69 |
73 |
74 | Dashboard
75 |
76 |
77 |
78 |
79 |
80 |
81 | Practice questions
82 |
83 |
84 |
85 |
86 |
90 |
91 | All practice questions
92 |
93 |
97 |
98 | Frameworks / languages
99 |
100 |
101 |
102 |
103 |
107 | Recommended strategy
108 |
109 |
110 |
111 |
115 |
116 |
117 | Time-savers
118 |
119 |
120 |
121 |
122 |
126 | Guides
127 |
128 |
129 |
130 |
131 | {/* Footer */}
132 |
133 |
138 |
139 |
140 |
145 | {theme === "dark" ? (
146 |
147 | ) : (
148 |
149 | )}
150 |
151 |
156 |
157 |
158 |
159 |
160 | );
161 | };
162 |
163 | export function Sidebar() {
164 | return (
165 | <>
166 | {/* Desktop Sidebar */}
167 |
172 |
173 | {/* Mobile Sidebar */}
174 |
175 |
176 |
181 |
182 |
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 |
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 |
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 | handleQuestionTypeChange("ui")}
90 | className={`rounded-full text-sm ${questionType === "ui" ? "" : "bg-transparent"} `}
91 | variant={questionType === "ui" ? "default" : "outline"}
92 | >
93 | UI Components
94 |
95 | handleQuestionTypeChange("logical")}
97 | className={`rounded-full text-sm ${questionType === "logical" ? "" : "bg-transparent"}`}
98 | variant={questionType === "logical" ? "default" : "outline"}
99 | >
100 | JavaScript Function
101 |
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 |
32 | ) : (
33 |
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 |
116 | Get full access
117 |
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 |
14 |
15 |
Email (optional)
16 |
17 | If you'd like a reply, please provide your email address.
18 |
19 |
23 |
24 |
25 |
29 | Send Message
30 |
31 |
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 |
37 | Get full access
38 |
39 |
40 |
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 |
34 | {theme === "dark" ? (
35 |
36 | ) : (
37 |
38 | )}
39 | Continue with GitHub
40 |
41 |
42 |
43 | Continue with Google
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Or continue with
52 |
53 |
54 |
55 |
56 |
57 | Email
58 |
59 |
60 |
61 |
62 |
63 | Password
64 |
65 |
66 |
67 |
71 | Forgot your password?
72 |
73 |
74 | Sign In
75 |
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 |
34 | {theme === "dark" ? (
35 |
36 | ) : (
37 |
38 | )}
39 | Continue with GitHub
40 |
41 |
42 |
43 | Continue with Google
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Or continue with
52 |
53 |
54 |
55 |
56 |
57 | Email
58 |
59 |
60 |
61 |
62 |
63 | Password
64 |
65 |
66 |
67 |
68 | Sign In
69 |
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 |
61 |
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 |
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 |
--------------------------------------------------------------------------------