diff --git a/.env.example b/.env.example index c21a21e..4c1097a 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ -CLIENT_ID= -CLIENT_SECRET= -SITE_ID= -PORT= +WEBFLOW_CLIENT_ID=XXX +WEBFLOW_CLIENT_SECRET=XXX +PORT=8080 +VITE_PORT=3000 +NGROK_AUTH_TOKEN=XXX SITE_TOKEN= -NGROK_AUTH_TOKEN= diff --git a/README.md b/README.md index 6896387..0429da3 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The frontend is a React application that outlines the steps needed to register a ### Prerequisites -1. Ensure you have a Webflow Data Client App in your Webflow Workspace, or a [Site Token](https://university.webflow.com/lesson/intro-to-webflow-apis?topics=cms-dynamic-content#how-to-create-an-API-token) for a specific site. [Read our guide for more information on creating a Data Client App.](https://developers.webflow.com/data/docs/register-an-app) +1. Ensure you have a Webflow Data Client App in your Webflow Workspace with the following scopes: `sites:read`, `sites:write`, `pages:read`, `pages:write`, `custom_code:read`, and `custom_code:write`, or a [Site Token](https://university.webflow.com/lesson/intro-to-webflow-apis?topics=cms-dynamic-content#how-to-create-an-API-token) for a specific site. [Read our guide for more information on creating a Data Client App.](https://developers.webflow.com/data/docs/register-an-app) 2. Create an Ngrok account and obtain your [Ngrok authentication token](https://dashboard.ngrok.com/tunnels/authtokens). ### Setup Guide @@ -78,7 +78,7 @@ The frontend is a React application that outlines the steps needed to register a ```sh npm install - npm install-project // This will install the dependencies in the subdirectories + npm run install-project // This will install the dependencies in the subdirectories npm run dev ``` diff --git a/backend/auth/mydatabase.db b/backend/auth/mydatabase.db index 455ad49..df53a4c 100755 Binary files a/backend/auth/mydatabase.db and b/backend/auth/mydatabase.db differ diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js new file mode 100644 index 0000000..dcd69f2 --- /dev/null +++ b/backend/controllers/authController.js @@ -0,0 +1,101 @@ +import { WebflowClient } from "webflow-api"; +import { getNgrokUrl } from "../utils/ngrokManager.js"; +import { storeToken, getToken } from "../auth/tokens.js"; +import dotenv from "dotenv"; +import path from "path"; +import { fileURLToPath } from "url"; + +// Convert URL to local file path +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Get Environment Variables from .env file in route +dotenv.config({ path: path.resolve(__dirname, "../../.env") }); + +// Include scopes for your App. Be sure your App has been registered with the same scopes. +const scopes = [ + "sites:read", + "sites:write", + "pages:write", + "pages:read", + "pages:write", + "custom_code:read", + "custom_code:write", +]; + +// Handle navigation to the root +export const handleRoot = async (req, res) => { + try { + // Get a token from .db + const token = await getToken("user"); + + // If no token found, redirect user to the Auth Screen + if (!token) { + console.log("No token found. Redirecting to auth screen..."); + return res.redirect("/auth"); + } else { + // If token found, redirect user to the frontend + console.log("Token found. Redirecting to frontend"); + return res.redirect("http://localhost:3000"); + } + } catch (error) { + console.error("Error handling token:", error); + res.status(500).send("Internal Server Error"); + } +}; + +// Start authorization flow +export const startAuthFlow = async (req, res) => { + try { + const siteToken = process.env.SITE_TOKEN; + + // If a site token is included in the .env file , store the token and bypass the auth screen + if (siteToken) { + await storeToken("user", siteToken); + console.log("Site token found and stored."); + return res.redirect("http://localhost:3000"); + } else { + // If the site token is not included in the .env file, use the Webflow Client ID to create an authorization URL + const publicUrl = await getNgrokUrl(); + const authorizeUrl = WebflowClient.authorizeURL({ + scope: scopes, + clientId: process.env.WEBFLOW_CLIENT_ID, + redirectUri: `${publicUrl}/auth/callback`, + }); + + // Redirect the user to the Auth URL + return res.redirect(authorizeUrl); + } + } catch (error) { + console.error("Error starting auth flow:", error); + res.status(500).send("Failed to start auth flow"); + } +}; + +// Handle Callback from Authorization Screen +export const handleAuthCallback = async (req, res) => { + // Get the Authorization Code from the query parameters + const { code } = req.query; + if (!code) { + return res.status(400).send("Authorization code is required"); + } + + try { + // Get the Access Token from Webflow using the Authorization Code + const publicUrl = await getNgrokUrl(); + const accessToken = await WebflowClient.getAccessToken({ + clientId: process.env.WEBFLOW_CLIENT_ID, + clientSecret: process.env.WEBFLOW_CLIENT_SECRET, + code: code, + redirectUri: `${publicUrl}/auth/callback`, + }); + + // Store the Access Token in the DB + await storeToken("user", accessToken); // Use access_token + console.log("Access token obtained and stored. Redirecting to frontend..."); + return res.redirect("http://localhost:3000"); + } catch (error) { + console.error("Error fetching access token:", error); + return res.status(500).send("Failed to fetch access token"); + } +}; diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js index f6c6dff..17ab730 100755 --- a/backend/routes/authRoutes.js +++ b/backend/routes/authRoutes.js @@ -1,94 +1,19 @@ import express from "express"; -import { WebflowClient } from "webflow-api"; -import dotenv from "dotenv"; -import path from "path"; -import { fileURLToPath } from "url"; -import { getNgrokUrl } from "../utils/ngrokManager.js"; -import { storeToken, getToken } from "../auth/tokens.js"; - -// Convert URL to local file path -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -// Get Environment Variables from .env file in route -dotenv.config({ path: path.resolve(__dirname, "../../.env") }); +import { + handleRoot, + startAuthFlow, + handleAuthCallback, +} from "../controllers/authController.js"; const router = express.Router(); -// Include scopes for your app. Be sure your App has been registered with the same scopes. -const scopes = [ - "sites:read", - "sites:write", - "pages:write", - "pages:read", - "pages:write", - "custom_code:read", - "custom_code:write", -]; - // Redirect root to Auth Screen -router.get("/", async (req, res) => { - try { - const token = await getToken("user"); - if (!token) { - console.log("No token found. Redirecting to auth screen..."); - return res.redirect("/auth"); - } else { - console.log("Token found. Redirecting to frontend"); - return res.redirect("http://localhost:3000"); - } - } catch (error) { - console.error("Error handling token:", error); - res.status(500).send("Internal Server Error"); - } -}); +router.get("/", handleRoot); // Route to start Auth Flow. Redirects to Webflow Auth screen -router.get("/auth", async (req, res) => { - try { - // Check if a user is using a Site Token. If so, store the site token in the database and skip auth screen - const siteToken = process.env.SITE_TOKEN; - if (siteToken) { - await storeToken("user", siteToken); - console.log("Site token found and stored."); - } else { - const publicUrl = await getNgrokUrl(); - const authorizeUrl = WebflowClient.authorizeURL({ - scope: scopes, - clientId: process.env.WEBFLOW_CLIENT_ID, - redirectUri: `${publicUrl}/auth/callback`, - }); - res.redirect(authorizeUrl); - } - } catch (error) { - console.error("Error starting auth flow:", error); - res.status(500).send("Failed to start auth flow"); - } -}); +router.get("/auth", startAuthFlow); // Callback URI to get code and access token -router.get("/auth/callback", async (req, res) => { - const { code } = req.query; - if (!code) { - return res.status(400).send("Authorization code is required"); - } - - try { - const publicUrl = await getNgrokUrl(); - const accessToken = await WebflowClient.getAccessToken({ - clientId: process.env.WEBFLOW_CLIENT_ID, - clientSecret: process.env.WEBFLOW_CLIENT_SECRET, - code: code, - redirectUri: `${publicUrl}/auth/callback`, - }); - - await storeToken("user", accessToken); // Use access_token - console.log("Access token obtained and stored. Redirecting to frontend..."); - res.redirect("http://localhost:3000"); - } catch (error) { - console.error("Error fetching access token:", error); - res.status(500).send("Failed to fetch access token"); - } -}); +router.get("/auth/callback", handleAuthCallback); export default router; diff --git a/backend/server.js b/backend/server.js index cdb3211..d60205e 100755 --- a/backend/server.js +++ b/backend/server.js @@ -54,13 +54,13 @@ const startServer = async () => { // Create a table to output in the CLI const table = new Table({ head: ["Location", "URL"], // Define column headers - colWidths: [30, 60], // Define column widths + colWidths: [20, 80], // Define column widths }); // Add URL information to the table table.push( - ["Development URL (Frontend)", "http://localhost:3000"], - ["Development URL (Backend)", `http://localhost:${PORT}`] + ["Backend", `http://localhost:${PORT}`], + ["Frontend", "http://localhost:3000"] ); // If using an App, also add the Redirect URI to the table diff --git a/frontend/package-lock.json b/frontend/package-lock.json index feeeb46..26a578d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,8 +12,10 @@ "@emotion/styled": "^11.11.5", "@mui/material": "^5.15.20", "axios": "^1.7.2", + "dotenv": "^16.4.5", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-syntax-highlighter": "^15.5.0" }, "devDependencies": { "@types/react": "^18.2.66", @@ -1595,6 +1597,14 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1631,6 +1641,11 @@ "@types/react": "*" } }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -2029,6 +2044,33 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2061,6 +2103,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2243,6 +2294,17 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.790", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.790.tgz", @@ -2801,6 +2863,18 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2895,6 +2969,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3149,6 +3231,39 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3221,6 +3336,28 @@ "node": ">= 0.4" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -3338,6 +3475,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3386,6 +3532,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -3724,6 +3879,19 @@ "loose-envify": "cli.js" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3976,6 +4144,23 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4084,6 +4269,14 @@ "node": ">= 0.8.0" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4094,6 +4287,18 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4165,6 +4370,21 @@ "node": ">=0.10.0" } }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -4201,6 +4421,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -4473,6 +4715,15 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -4928,6 +5179,14 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0350079..6eecf52 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,8 +14,10 @@ "@emotion/styled": "^11.11.5", "@mui/material": "^5.15.20", "axios": "^1.7.2", + "dotenv": "^16.4.5", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-syntax-highlighter": "^15.5.0" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7e85145..deb5a04 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,7 +1,8 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { ThemeProvider } from "@mui/material/styles"; -import theme from "./utils/customTheme"; -import { Box, Container } from "@mui/material"; +import { lightTheme, darkTheme } from "./utils/customTheme"; +import { Box, Container, CssBaseline } from "@mui/material"; +import useMediaQuery from "@mui/material/useMediaQuery"; // Components import Header from "./components/Header"; @@ -10,15 +11,26 @@ import Footer from "./components/Footer"; import ExampleStepper from "./components/Examples/ExampleStepper"; function App() { + const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); const [selectedSite, setselectedSite] = useState(null); + const [darkMode, setDarkMode] = useState(false); + + useEffect(() => { + setDarkMode(prefersDarkMode); + }, [prefersDarkMode]); const handleSelectSite = (site) => { console.log("Site selected:", site); setselectedSite(site); }; + const toggleDarkMode = () => { + setDarkMode(!darkMode); + }; + return ( - + +
- - - - + + + + + +