From 47ce5db22382929b6734f06170b176e8779779f0 Mon Sep 17 00:00:00 2001 From: Ishaan Dey Date: Sun, 5 May 2024 16:51:30 -0700 Subject: [PATCH] add ai generations limit + random bug fixes --- backend/database/drizzle/meta/_journal.json | 7 ++ backend/database/src/index.ts | 29 +++++- backend/database/src/schema.ts | 1 + backend/kubernetes/ingressController.yaml | 2 - backend/server/dist/index.js | 47 ++++----- backend/server/dist/utils.js | 34 ++++++- backend/server/src/index.ts | 70 ++++++------- backend/server/src/types.ts | 16 +-- backend/server/src/utils.ts | 46 +++++++++ frontend/components/editor/generate.tsx | 14 +++ frontend/components/editor/index.tsx | 4 +- frontend/components/editor/sidebar/file.tsx | 2 +- frontend/components/editor/sidebar/folder.tsx | 2 +- frontend/components/editor/sidebar/index.tsx | 2 +- frontend/components/editor/sidebar/types.ts | 21 ---- frontend/components/ui/userButton.tsx | 19 +++- frontend/lib/types.ts | 23 +++++ frontend/package-lock.json | 98 ++++++++++++++++++- frontend/package.json | 2 + frontend/postcss.config.js | 13 ++- 20 files changed, 330 insertions(+), 122 deletions(-) delete mode 100644 frontend/components/editor/sidebar/types.ts diff --git a/backend/database/drizzle/meta/_journal.json b/backend/database/drizzle/meta/_journal.json index 38d79b6..4a81b4d 100644 --- a/backend/database/drizzle/meta/_journal.json +++ b/backend/database/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1714565073180, "tag": "0004_cuddly_wolf_cub", "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1714950365718, + "tag": "0005_last_the_twelve", + "breakpoints": true } ] } \ No newline at end of file diff --git a/backend/database/src/index.ts b/backend/database/src/index.ts index 4162019..02f287f 100644 --- a/backend/database/src/index.ts +++ b/backend/database/src/index.ts @@ -5,7 +5,7 @@ import { ZodError, z } from "zod"; import { user, sandbox, usersToSandboxes } from "./schema"; import * as schema from "./schema"; -import { and, eq } from "drizzle-orm"; +import { and, eq, sql } from "drizzle-orm"; export interface Env { DB: D1Database; @@ -86,7 +86,6 @@ export default { const sb = await db.insert(sandbox).values({ type, name, userId, visibility }).returning().get(); - // console.log("sb:", sb); await fetch("https://storage.ishaan1013.workers.dev/api/init", { method: "POST", body: JSON.stringify({ sandboxId: sb.id, type }), @@ -95,7 +94,6 @@ export default { return new Response(sb.id, { status: 200 }); } else { - console.log(method); return methodNotAllowed; } } else if (path === "/api/sandbox/share") { @@ -162,12 +160,35 @@ export default { const body = await request.json(); const { sandboxId, userId } = deleteShareSchema.parse(body); - console.log("DELETE", sandboxId, userId); await db.delete(usersToSandboxes).where(and(eq(usersToSandboxes.userId, userId), eq(usersToSandboxes.sandboxId, sandboxId))); return success; } else return methodNotAllowed; + } else if (path === "/api/sandbox/generate" && method === "POST") { + const generateSchema = z.object({ + userId: z.string(), + }); + const body = await request.json(); + const { userId } = generateSchema.parse(body); + + const dbUser = await db.query.user.findFirst({ + where: (user, { eq }) => eq(user.id, userId), + }); + if (!dbUser) { + return new Response("User not found.", { status: 400 }); + } + if (dbUser.generations !== null && dbUser.generations >= 30) { + return new Response("You reached the maximum # of generations.", { status: 400 }); + } + + await db + .update(user) + .set({ generations: sql`${user.generations} + 1` }) + .where(eq(user.id, userId)) + .get(); + + return success; } else if (path === "/api/user") { if (method === "GET") { const params = url.searchParams; diff --git a/backend/database/src/schema.ts b/backend/database/src/schema.ts index 279a3b9..ac52c82 100644 --- a/backend/database/src/schema.ts +++ b/backend/database/src/schema.ts @@ -10,6 +10,7 @@ export const user = sqliteTable("user", { name: text("name").notNull(), email: text("email").notNull(), image: text("image"), + generations: integer("generations").default(0), }); export type User = typeof user.$inferSelect; diff --git a/backend/kubernetes/ingressController.yaml b/backend/kubernetes/ingressController.yaml index f20c82b..f174346 100644 --- a/backend/kubernetes/ingressController.yaml +++ b/backend/kubernetes/ingressController.yaml @@ -1,6 +1,4 @@ --- -# Credit: Harkirat Singh https://github.com/hkirat/repl - # Source: ingress-nginx/templates/controller-serviceaccount.yaml apiVersion: v1 kind: ServiceAccount diff --git a/backend/server/dist/index.js b/backend/server/dist/index.js index 747f7ae..25220dd 100644 --- a/backend/server/dist/index.js +++ b/backend/server/dist/index.js @@ -65,14 +65,14 @@ io.use((socket, next) => __awaiter(void 0, void 0, void 0, function* () { return; } socket.data = { - id: sandboxId, userId, + sandboxId: sandboxId, }; next(); })); io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () { const data = socket.data; - const sandboxFiles = yield (0, utils_1.getSandboxFiles)(data.id); + const sandboxFiles = yield (0, utils_1.getSandboxFiles)(data.sandboxId); sandboxFiles.fileData.forEach((file) => { const filePath = path_1.default.join(dirName, file.id); fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true }); @@ -113,7 +113,7 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () { socket.on("createFile", (name) => __awaiter(void 0, void 0, void 0, function* () { try { yield ratelimit_1.createFileRL.consume(data.userId, 1); - const id = `projects/${data.id}/${name}`; + const id = `projects/${data.sandboxId}/${name}`; fs_1.default.writeFile(path_1.default.join(dirName, id), "", function (err) { if (err) throw err; @@ -165,7 +165,7 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () { }); sandboxFiles.fileData = sandboxFiles.fileData.filter((f) => f.id !== fileId); yield (0, utils_1.deleteFile)(fileId); - const newFiles = yield (0, utils_1.getSandboxFiles)(data.id); + const newFiles = yield (0, utils_1.getSandboxFiles)(data.sandboxId); callback(newFiles.files); } catch (e) { @@ -184,7 +184,7 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () { const pty = (0, node_pty_1.spawn)(os_1.default.platform() === "win32" ? "cmd.exe" : "bash", [], { name: "xterm", cols: 100, - cwd: path_1.default.join(dirName, "projects", data.id), + cwd: path_1.default.join(dirName, "projects", data.sandboxId), }); const onData = pty.onData((data) => { socket.emit("terminalResponse", { @@ -213,37 +213,26 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () { } }); socket.on("generateCode", (fileName, code, line, instructions, callback) => __awaiter(void 0, void 0, void 0, function* () { - console.log("Generating code..."); - const res = yield fetch(`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_USER_ID}/ai/run/@cf/meta/llama-3-8b-instruct`, { + const fetchPromise = fetch(`http://localhost:8787/api/sandbox/generate`, { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${process.env.CF_API_TOKEN}`, }, body: JSON.stringify({ - messages: [ - { - role: "system", - content: "You are an expert coding assistant. You read code from a file, and you suggest new code to add to the file. You may be given instructions on what to generate, which you should follow. You should generate code that is correct, efficient, and follows best practices. You should also generate code that is clear and easy to read. When you generate code, you should only return the code, and nothing else. You should not include backticks in the code you generate.", - }, - { - role: "user", - content: `The file is called ${fileName}.`, - }, - { - role: "user", - content: `Here are my instructions on what to generate: ${instructions}.`, - }, - { - role: "user", - content: `Suggest me code to insert at line ${line} in my file. Give only the code, and NOTHING else. DO NOT include backticks in your response. My code file content is as follows - -${code}`, - }, - ], + userId: data.userId, }), }); - const json = yield res.json(); + const generateCodePromise = (0, utils_1.generateCode)({ + fileName, + code, + line, + instructions, + }); + const [fetchResponse, generateCodeResponse] = yield Promise.all([ + fetchPromise, + generateCodePromise, + ]); + const json = yield generateCodeResponse.json(); callback(json); })); socket.on("disconnect", () => { diff --git a/backend/server/dist/utils.js b/backend/server/dist/utils.js index 08efd43..cc94441 100644 --- a/backend/server/dist/utils.js +++ b/backend/server/dist/utils.js @@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.deleteFile = exports.saveFile = exports.renameFile = exports.createFile = exports.getSandboxFiles = void 0; +exports.generateCode = exports.deleteFile = exports.saveFile = exports.renameFile = exports.createFile = exports.getSandboxFiles = void 0; const getSandboxFiles = (id) => __awaiter(void 0, void 0, void 0, function* () { const sandboxRes = yield fetch(`https://storage.ishaan1013.workers.dev/api?sandboxId=${id}`); const sandboxData = yield sandboxRes.json(); @@ -121,3 +121,35 @@ const deleteFile = (fileId) => __awaiter(void 0, void 0, void 0, function* () { return res.ok; }); exports.deleteFile = deleteFile; +const generateCode = (_a) => __awaiter(void 0, [_a], void 0, function* ({ fileName, code, line, instructions, }) { + return yield fetch(`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_USER_ID}/ai/run/@cf/meta/llama-3-8b-instruct`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.CF_API_TOKEN}`, + }, + body: JSON.stringify({ + messages: [ + { + role: "system", + content: "You are an expert coding assistant. You read code from a file, and you suggest new code to add to the file. You may be given instructions on what to generate, which you should follow. You should generate code that is correct, efficient, and follows best practices. You should also generate code that is clear and easy to read. When you generate code, you should only return the code, and nothing else. You should not include backticks in the code you generate.", + }, + { + role: "user", + content: `The file is called ${fileName}.`, + }, + { + role: "user", + content: `Here are my instructions on what to generate: ${instructions}.`, + }, + { + role: "user", + content: `Suggest me code to insert at line ${line} in my file. Give only the code, and NOTHING else. DO NOT include backticks in your response. My code file content is as follows + +${code}`, + }, + ], + }), + }); +}); +exports.generateCode = generateCode; diff --git a/backend/server/src/index.ts b/backend/server/src/index.ts index ce6b919..12c1045 100644 --- a/backend/server/src/index.ts +++ b/backend/server/src/index.ts @@ -11,6 +11,7 @@ import { User } from "./types" import { createFile, deleteFile, + generateCode, getSandboxFiles, renameFile, saveFile, @@ -81,8 +82,8 @@ io.use(async (socket, next) => { } socket.data = { - id: sandboxId, userId, + sandboxId: sandboxId, } next() @@ -91,10 +92,10 @@ io.use(async (socket, next) => { io.on("connection", async (socket) => { const data = socket.data as { userId: string - id: string + sandboxId: string } - const sandboxFiles = await getSandboxFiles(data.id) + const sandboxFiles = await getSandboxFiles(data.sandboxId) sandboxFiles.fileData.forEach((file) => { const filePath = path.join(dirName, file.id) fs.mkdirSync(path.dirname(filePath), { recursive: true }) @@ -142,7 +143,7 @@ io.on("connection", async (socket) => { try { await createFileRL.consume(data.userId, 1) - const id = `projects/${data.id}/${name}` + const id = `projects/${data.sandboxId}/${name}` fs.writeFile(path.join(dirName, id), "", function (err) { if (err) throw err @@ -206,7 +207,7 @@ io.on("connection", async (socket) => { await deleteFile(fileId) - const newFiles = await getSandboxFiles(data.id) + const newFiles = await getSandboxFiles(data.sandboxId) callback(newFiles.files) } catch (e) { socket.emit("rateLimit", "Rate limited: file deletion. Please slow down.") @@ -226,7 +227,7 @@ io.on("connection", async (socket) => { const pty = spawn(os.platform() === "win32" ? "cmd.exe" : "bash", [], { name: "xterm", cols: 100, - cwd: path.join(dirName, "projects", data.id), + cwd: path.join(dirName, "projects", data.sandboxId), }) const onData = pty.onData((data) => { @@ -269,42 +270,29 @@ io.on("connection", async (socket) => { instructions: string, callback ) => { - console.log("Generating code...") - const res = await fetch( - `https://api.cloudflare.com/client/v4/accounts/${process.env.CF_USER_ID}/ai/run/@cf/meta/llama-3-8b-instruct`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.CF_API_TOKEN}`, - }, - body: JSON.stringify({ - messages: [ - { - role: "system", - content: - "You are an expert coding assistant. You read code from a file, and you suggest new code to add to the file. You may be given instructions on what to generate, which you should follow. You should generate code that is correct, efficient, and follows best practices. You should also generate code that is clear and easy to read. When you generate code, you should only return the code, and nothing else. You should not include backticks in the code you generate.", - }, - { - role: "user", - content: `The file is called ${fileName}.`, - }, - { - role: "user", - content: `Here are my instructions on what to generate: ${instructions}.`, - }, - { - role: "user", - content: `Suggest me code to insert at line ${line} in my file. Give only the code, and NOTHING else. DO NOT include backticks in your response. My code file content is as follows - -${code}`, - }, - ], - }), - } - ) + const fetchPromise = fetch(`http://localhost:8787/api/sandbox/generate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userId: data.userId, + }), + }) - const json = await res.json() + const generateCodePromise = generateCode({ + fileName, + code, + line, + instructions, + }) + + const [fetchResponse, generateCodeResponse] = await Promise.all([ + fetchPromise, + generateCodePromise, + ]) + + const json = await generateCodeResponse.json() callback(json) } ) diff --git a/backend/server/src/types.ts b/backend/server/src/types.ts index 650f529..7a847d4 100644 --- a/backend/server/src/types.ts +++ b/backend/server/src/types.ts @@ -4,11 +4,9 @@ export type User = { id: string name: string email: string + generations: number sandbox: Sandbox[] - usersToSandboxes: { - userId: string - sandboxId: string - }[] + usersToSandboxes: UsersToSandboxes[] } export type Sandbox = { @@ -17,10 +15,12 @@ export type Sandbox = { type: "react" | "node" visibility: "public" | "private" userId: string - usersToSandboxes: { - userId: string - sandboxId: string - }[] + usersToSandboxes: UsersToSandboxes[] +} + +export type UsersToSandboxes = { + userId: string + sandboxId: string } export type TFolder = { diff --git a/backend/server/src/utils.ts b/backend/server/src/utils.ts index 46e2cbb..e07b69c 100644 --- a/backend/server/src/utils.ts +++ b/backend/server/src/utils.ts @@ -134,3 +134,49 @@ export const deleteFile = async (fileId: string) => { }) return res.ok } + +export const generateCode = async ({ + fileName, + code, + line, + instructions, +}: { + fileName: string + code: string + line: number + instructions: string +}) => { + return await fetch( + `https://api.cloudflare.com/client/v4/accounts/${process.env.CF_USER_ID}/ai/run/@cf/meta/llama-3-8b-instruct`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.CF_API_TOKEN}`, + }, + body: JSON.stringify({ + messages: [ + { + role: "system", + content: + "You are an expert coding assistant. You read code from a file, and you suggest new code to add to the file. You may be given instructions on what to generate, which you should follow. You should generate code that is correct, efficient, and follows best practices. You should also generate code that is clear and easy to read. When you generate code, you should only return the code, and nothing else. You should not include backticks in the code you generate.", + }, + { + role: "user", + content: `The file is called ${fileName}.`, + }, + { + role: "user", + content: `Here are my instructions on what to generate: ${instructions}.`, + }, + { + role: "user", + content: `Suggest me code to insert at line ${line} in my file. Give only the code, and NOTHING else. DO NOT include backticks in your response. My code file content is as follows + +${code}`, + }, + ], + }), + } + ) +} diff --git a/frontend/components/editor/generate.tsx b/frontend/components/editor/generate.tsx index c56ed56..275ebda 100644 --- a/frontend/components/editor/generate.tsx +++ b/frontend/components/editor/generate.tsx @@ -5,9 +5,13 @@ import { Button } from "../ui/button" import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react" import { Socket } from "socket.io-client" import { Editor } from "@monaco-editor/react" +import { User } from "@/lib/types" +import { toast } from "sonner" +import { usePathname, useRouter } from "next/navigation" // import monaco from "monaco-editor" export default function GenerateInput({ + user, socket, width, data, @@ -15,6 +19,7 @@ export default function GenerateInput({ onExpand, onAccept, }: { + user: User socket: Socket width: number data: { @@ -28,6 +33,8 @@ export default function GenerateInput({ onExpand: () => void onAccept: (code: string) => void }) { + const pathname = usePathname() + const router = useRouter() const inputRef = useRef(null) const [code, setCode] = useState("") @@ -50,6 +57,12 @@ export default function GenerateInput({ }: { regenerate?: boolean }) => { + if (user.generations >= 30) { + toast.error( + "You reached the maximum # of generations. Contact @ishaandey_ on X/Twitter to reset :)" + ) + } + setLoading({ generate: !regenerate, regenerate }) setCurrentPrompt(input) socket.emit( @@ -72,6 +85,7 @@ export default function GenerateInput({ } setCode(res.result.response) + router.refresh() } ) } diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 908d58b..20556a3 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -34,8 +34,7 @@ import Sidebar from "./sidebar" import EditorTerminal from "./terminal" import { Button } from "../ui/button" import GenerateInput from "./generate" -import { TFile, TFileData, TFolder, TTab } from "./sidebar/types" -import { Sandbox, User } from "@/lib/types" +import { Sandbox, User, TFile, TFileData, TFolder, TTab } from "@/lib/types" import { processFileType, validateName } from "@/lib/utils" import { Cursors } from "./live/cursors" @@ -455,6 +454,7 @@ export default function CodeEditor({
{generate.show && ai ? ( name[0].toUpperCase())}
- +
{userData.name}
@@ -37,6 +37,21 @@ export default function UserButton({ userData }: { userData: User }) {
+ +
+
+ + AI Usage: {userData.generations}/30 +
+
+
+
+
diff --git a/frontend/lib/types.ts b/frontend/lib/types.ts index 01739ca..6e547fd 100644 --- a/frontend/lib/types.ts +++ b/frontend/lib/types.ts @@ -4,6 +4,7 @@ export type User = { id: string name: string email: string + generations: number sandbox: Sandbox[] usersToSandboxes: UsersToSandboxes[] } @@ -38,3 +39,25 @@ export type R2FileData = { version: string key: string } + +export type TFolder = { + id: string + type: "folder" + name: string + children: (TFile | TFolder)[] +} + +export type TFile = { + id: string + type: "file" + name: string +} + +export type TTab = TFile & { + saved: boolean +} + +export type TFileData = { + id: string + data: string +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8061baa..0dd2316 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -54,6 +54,8 @@ "@types/react-dom": "^18", "autoprefixer": "^10.0.1", "postcss": "^8", + "postcss-import": "^16.1.0", + "postcss-nesting": "^12.1.2", "tailwindcss": "^3.3.0", "typescript": "^5" } @@ -247,6 +249,50 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz", + "integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.3.tgz", + "integrity": "sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" + } + }, "node_modules/@floating-ui/core": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", @@ -2810,16 +2856,17 @@ } }, "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz", + "integrity": "sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==", + "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "postcss": "^8.0.0" @@ -2906,6 +2953,33 @@ "postcss": "^8.2.14" } }, + "node_modules/postcss-nesting": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.2.tgz", + "integrity": "sha512-FUmTHGDNundodutB4PUBxt/EPuhgtpk8FJGRsBhOuy+6FnkR2A8RZWIsyyy6XmhvX2DZQQWIkvu+HB4IbJm+Ew==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-resolve-nested": "^1.1.0", + "@csstools/selector-specificity": "^3.0.3", + "postcss-selector-parser": "^6.0.13" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.16", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", @@ -3476,6 +3550,22 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tailwindcss/node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 016b772..e18df8c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,6 +55,8 @@ "@types/react-dom": "^18", "autoprefixer": "^10.0.1", "postcss": "^8", + "postcss-import": "^16.1.0", + "postcss-nesting": "^12.1.2", "tailwindcss": "^3.3.0", "typescript": "^5" } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 12a703d..5ba02fa 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -1,6 +1,9 @@ module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; + plugins: [ + "postcss-import", + "tailwindcss/nesting", + "postcss-nesting", + "autoprefixer", + "tailwindcss", + ], +}