diff --git a/.gitignore b/.gitignore index a38f3252..401002d6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ yarn-error.log* # local env files .env*.local - +.env # vercel .vercel diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..3d95fb8c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev" + }, + { + "name": "Next.js: debug client-side", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/.bin/next", + "runtimeArgs": ["--inspect"], + "skipFiles": ["/**"], + "serverReadyAction": { + "action": "debugWithChrome", + "killOnServerStop": true, + "pattern": "- Local:.+(https?://.+)", + "uriFormat": "%s", + "webRoot": "${workspaceFolder}" + } + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 35861fc7..38e7900f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "next-auth": "^5.0.0-beta.20", "nodemailer": "^6.9.14", "npm": "^10.8.3", + "nprogress": "^0.2.0", "octokit": "^4.0.2", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -50,6 +51,7 @@ "@types/jest": "^29.5.12", "@types/node": "^22.5.4", "@types/nodemailer": "^6.4.15", + "@types/nprogress": "^0.2.3", "@types/pg": "^8.11.6", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", @@ -668,15 +670,6 @@ "react-dom": ">=18" } }, - "node_modules/@emnapi/runtime": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", - "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emotion/babel-plugin": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", @@ -1072,27 +1065,6 @@ "@img/sharp-libvips-darwin-arm64": "1.0.4" } }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", @@ -1108,291 +1080,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", @@ -4762,6 +4449,12 @@ "@types/node": "*" } }, + "node_modules/@types/nprogress": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.3.tgz", + "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==", + "dev": true + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -5473,9 +5166,9 @@ } }, "node_modules/axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -15689,6 +15382,11 @@ "inBundle": true, "license": "ISC" }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -19605,9 +19303,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.6.tgz", - "integrity": "sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unified": { "version": "9.2.2", diff --git a/package.json b/package.json index 32a6889b..2e24a19a 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "next-auth": "^5.0.0-beta.20", "nodemailer": "^6.9.14", "npm": "^10.8.3", + "nprogress": "^0.2.0", "octokit": "^4.0.2", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -57,6 +58,7 @@ "@types/jest": "^29.5.12", "@types/node": "^22.5.4", "@types/nodemailer": "^6.4.15", + "@types/nprogress": "^0.2.3", "@types/pg": "^8.11.6", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", diff --git a/src/app/(authed)/(home)/new/loading.tsx b/src/app/(authed)/(home)/new/loading.tsx new file mode 100644 index 00000000..27527f36 --- /dev/null +++ b/src/app/(authed)/(home)/new/loading.tsx @@ -0,0 +1,5 @@ +import LoadingIndicator from "@/common/ui/LoadingIndicator"; + +export default function Loading() { + return +} \ No newline at end of file diff --git a/src/app/(authed)/(home)/new/page.tsx b/src/app/(authed)/(home)/new/page.tsx index c8359ee3..7bc8d5a3 100644 --- a/src/app/(authed)/(home)/new/page.tsx +++ b/src/app/(authed)/(home)/new/page.tsx @@ -1,10 +1,12 @@ import { Box, Typography } from "@mui/material" -import NewProjectSteps from "@/features/new-project/view/NewProjectSteps" import { env, splitOwnerAndRepository } from "@/common" import MessageLinkFooter from "@/common/ui/MessageLinkFooter" +import dynamic from 'next/dynamic' const HELP_URL = env.get("FRAMNA_DOCS_HELP_URL") +const NewProjectSteps = dynamic(() => import("@/features/new-project/view/NewProjectSteps")) + const Page = () => { const repositoryNameSuffix = env.getOrThrow("REPOSITORY_NAME_SUFFIX") const templateName = env.get("NEW_PROJECT_TEMPLATE_REPOSITORY") diff --git a/src/app/(authed)/(home)/[...slug]/layout.tsx b/src/app/(authed)/(project-doc)/[...slug]/layout.tsx similarity index 79% rename from src/app/(authed)/(home)/[...slug]/layout.tsx rename to src/app/(authed)/(project-doc)/[...slug]/layout.tsx index 9a46405e..62f1ab7e 100644 --- a/src/app/(authed)/(home)/[...slug]/layout.tsx +++ b/src/app/(authed)/(project-doc)/[...slug]/layout.tsx @@ -2,11 +2,19 @@ import { Box, Stack } from "@mui/material" import { useTheme } from "@mui/material/styles" -import SecondarySplitHeader from "@/features/sidebar/view/SecondarySplitHeader" import TrailingToolbarItem from "@/features/projects/view/toolbar/TrailingToolbarItem" import MobileToolbar from "@/features/projects/view/toolbar/MobileToolbar" import { useProjectSelection } from "@/features/projects/data" import NotFound from "@/features/projects/view/NotFound" +import dynamic from "next/dynamic" +import SecondaryHeaderPlaceholder from "@/features/sidebar/view/SecondarySplitHeaderPlaceholder" + +const SecondarySplitHeader = dynamic(() => import("@/features/sidebar/view/SecondarySplitHeader"), + { + loading: () => , + ssr: false, + } +) export default function Page({ children }: { children: React.ReactNode }) { const { project } = useProjectSelection() diff --git a/src/app/(authed)/(home)/[...slug]/page.tsx b/src/app/(authed)/(project-doc)/[...slug]/page.tsx similarity index 100% rename from src/app/(authed)/(home)/[...slug]/page.tsx rename to src/app/(authed)/(project-doc)/[...slug]/page.tsx diff --git a/src/app/(authed)/layout.tsx b/src/app/(authed)/layout.tsx index 72f56799..4f004aa8 100644 --- a/src/app/(authed)/layout.tsx +++ b/src/app/(authed)/layout.tsx @@ -1,7 +1,6 @@ import { redirect } from "next/navigation" import { SessionProvider } from "next-auth/react" import { session, projectRepository } from "@/composition" -import { Box } from "@mui/material" import ErrorHandler from "@/common/ui/ErrorHandler" import SessionBarrier from "@/features/auth/view/SessionBarrier" import ProjectsContextProvider from "@/features/projects/view/ProjectsContextProvider" @@ -20,9 +19,7 @@ export default async function Layout({ children }: { children: React.ReactNode } - - {children} - + {children} @@ -31,26 +28,3 @@ export default async function Layout({ children }: { children: React.ReactNode } ) } - -const RaisedMainContent = ({ children }: { children?: React.ReactNode }) => { - return ( -
- - - {children} - - -
- ) -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9769fd6b..26a9d612 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,7 +17,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( - + {children} diff --git a/src/common/contexts.ts b/src/common/context/ProjectsContext.ts similarity index 100% rename from src/common/contexts.ts rename to src/common/context/ProjectsContext.ts diff --git a/src/common/index.ts b/src/common/index.ts index 83b57c0c..d0d4d9e1 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -1,4 +1,4 @@ -export * from "./contexts" +export * from "./context/ProjectsContext" export * from "./db" export * from "./errors" export * from "./github" diff --git a/src/common/ui/CustomTopLoader/index.tsx b/src/common/ui/CustomTopLoader/index.tsx new file mode 100644 index 00000000..c310c1c7 --- /dev/null +++ b/src/common/ui/CustomTopLoader/index.tsx @@ -0,0 +1,115 @@ +'use client' + +import { useRouter as useNextRouter, usePathname } from 'next/navigation'; +import Router from 'next/router'; +import 'node_modules/nprogress/nprogress.css'; +import NProgress from 'nprogress'; +import { useEffect } from 'react'; + +type CustomTopLoaderProps = { + color?: string; + initialPosition?: number; + crawlSpeed?: number; + height?: number; + crawl?: boolean; + showSpinner?: boolean; + easing?: string; + speed?: number; + shadow?: string | false; + template?: string; + zIndex?: number; + parentSelector?: string; +}; + +const CustomTopLoader: React.FC = ({ + color = '#000', + showSpinner = false, + crawl = true, + crawlSpeed = 600, + easing = 'ease', + speed = 600, + template = '
', + zIndex = 100000000, + parentSelector = 'body', +}) => { + const router = useNextRouter(); + const pathname = usePathname(); + + useEffect(() => { + const initNProgress = () => { + // Ensure the parent exists before configuring NProgress + const parentElement = document.querySelector(parentSelector); + if (!parentElement) { + return; + } + + NProgress.configure({ + parent: parentSelector, + showSpinner, + trickle: crawl, + trickleSpeed: crawlSpeed, + easing, + speed, + template, + }); + + const handleStart = () => { + if (document.querySelector(parentSelector)) { + NProgress.start(); + } + }; + const handleComplete = () => NProgress.done(); + + Router.events.on('routeChangeStart', handleStart); + Router.events.on('routeChangeComplete', handleComplete); + Router.events.on('routeChangeError', handleComplete); + + return () => { + Router.events.off('routeChangeStart', handleStart); + Router.events.off('routeChangeComplete', handleComplete); + Router.events.off('routeChangeError', handleComplete); + NProgress.remove(); // Ensure NProgress cleans up + }; + }; + + // Wait until the parent element appears before initializing NProgress + const waitForParent = setInterval(() => { + if (document.querySelector(parentSelector)) { + clearInterval(waitForParent); + initNProgress(); + } + }, 100); // Checks every 100ms + + return () => clearInterval(waitForParent); + }, [ + router, + parentSelector, + showSpinner, + crawl, + crawlSpeed, + easing, + speed, + template, + ]); + + // Re-run when the pathname changes to ensure progress bar finishes loading + useEffect(() => { + NProgress.done(); + }, [pathname]); + + return ( + + ) +}; + +export default CustomTopLoader; \ No newline at end of file diff --git a/src/features/projects/data/useProjectSelection.ts b/src/features/projects/data/useProjectSelection.ts index 190b673e..2ff26e96 100644 --- a/src/features/projects/data/useProjectSelection.ts +++ b/src/features/projects/data/useProjectSelection.ts @@ -1,5 +1,6 @@ "use client" +import NProgress from "nprogress" import { useRouter, usePathname } from "next/navigation" import { useContext } from "react" import { ProjectsContext } from "@/common" @@ -29,6 +30,7 @@ export default function useProjectSelection() { selectProject: (project: Project) => { const version = project.versions[0] const specification = version.specifications[0] + NProgress.start() projectNavigator.navigate( project.owner, project.name, @@ -37,6 +39,7 @@ export default function useProjectSelection() { ) }, selectVersion: (versionId: string) => { + NProgress.start() projectNavigator.navigateToVersion( selection.project!, versionId, @@ -44,6 +47,7 @@ export default function useProjectSelection() { ) }, selectSpecification: (specificationId: string) => { + NProgress.start() projectNavigator.navigate( selection.project!.owner, selection.project!.name, diff --git a/src/features/sidebar/view/SecondarySplitHeaderPlaceholder.tsx b/src/features/sidebar/view/SecondarySplitHeaderPlaceholder.tsx new file mode 100644 index 00000000..be78957e --- /dev/null +++ b/src/features/sidebar/view/SecondarySplitHeaderPlaceholder.tsx @@ -0,0 +1,48 @@ +import { Box, Stack, Divider, Skeleton, Typography } from "@mui/material"; +import React from "react"; + +const SecondaryHeaderPlaceholder = () => { + const skeletonCount = 4; + + return ( + + + + + {Array.from({ length: skeletonCount }, (_, index) => ( + + + {index < skeletonCount - 1 && ( + + / + + )} + + ))} + + + + + + + + ); +}; + +export default SecondaryHeaderPlaceholder; diff --git a/src/features/sidebar/view/internal/ClientSplitView.tsx b/src/features/sidebar/view/internal/ClientSplitView.tsx index 209e5800..38c6735f 100644 --- a/src/features/sidebar/view/internal/ClientSplitView.tsx +++ b/src/features/sidebar/view/internal/ClientSplitView.tsx @@ -1,7 +1,7 @@ "use client" import { useEffect, useContext } from "react" -import { Stack } from "@mui/material" +import { Stack, useMediaQuery, useTheme } from "@mui/material" import { isMac, useKeyboardShortcut, SidebarTogglableContext } from "@/common" import { useSidebarOpen } from "../../data" import PrimaryContainer from "./primary/Container" @@ -16,6 +16,10 @@ const ClientSplitView = ({ }) => { const [isSidebarOpen, setSidebarOpen] = useSidebarOpen() const isSidebarTogglable = useContext(SidebarTogglableContext) + const theme = useTheme() + // Determine if the screen size is small or larger + const isSM = useMediaQuery(theme.breakpoints.up("sm")) + useEffect(() => { if (!isSidebarTogglable && !isSidebarOpen) { setSidebarOpen(true) @@ -31,6 +35,7 @@ const ClientSplitView = ({ } }, [isSidebarTogglable, setSidebarOpen]) const sidebarWidth = 320 + return ( {sidebar} - + {children} diff --git a/src/features/sidebar/view/internal/secondary/Container.tsx b/src/features/sidebar/view/internal/secondary/Container.tsx index 7de24922..999b61a6 100644 --- a/src/features/sidebar/view/internal/secondary/Container.tsx +++ b/src/features/sidebar/view/internal/secondary/Container.tsx @@ -1,30 +1,26 @@ import { SxProps } from "@mui/system" -import { Stack } from "@mui/material" +import { Box, Stack } from "@mui/material" import { styled } from "@mui/material/styles" +import CustomTopLoader from "@/common/ui/CustomTopLoader" const SecondaryContainer = ({ sidebarWidth, offsetContent, - children + children, + isSM, }: { sidebarWidth: number offsetContent: boolean - children?: React.ReactNode + children?: React.ReactNode, + isSM: boolean, }) => { const sx = { overflow: "hidden" } return ( <> <_SecondaryContainer - sidebarWidth={0} - isSidebarOpen={false} - sx={{ ...sx, display: { xs: "flex", sm: "none" } }} - > - {children} - - <_SecondaryContainer - sidebarWidth={sidebarWidth} - isSidebarOpen={offsetContent} - sx={{ ...sx, display: { xs: "none", sm: "flex" } }} + sidebarWidth={isSM ? sidebarWidth : 0} + isSidebarOpen={isSM ? offsetContent: false} + sx={{ ...sx }} > {children} @@ -74,8 +70,35 @@ const _SecondaryContainer = ({ sidebarWidth={sidebarWidth} isSidebarOpen={isSidebarOpen} sx={{ ...sx, width: "100%", overflowY: "auto" }} + > - {children} + + {children} + ) } + +const RaisedMainContent = ({ children }: { children?: React.ReactNode }) => { + return ( +
+ + + + {children} + + +
+ ) +} diff --git a/src/features/sidebar/view/internal/sidebar/Header.tsx b/src/features/sidebar/view/internal/sidebar/Header.tsx index 59c425a7..3c17d48c 100644 --- a/src/features/sidebar/view/internal/sidebar/Header.tsx +++ b/src/features/sidebar/view/internal/sidebar/Header.tsx @@ -3,6 +3,7 @@ import Image from "next/image" import { Box, Button, Typography } from "@mui/material" import { useRouter } from "next/navigation" +import * as NProgress from "nprogress" import { useCloseSidebarOnSelection } from "@/features/sidebar/data" const Header = ({ siteName }: { siteName?: string }) => { @@ -29,6 +30,7 @@ const Header = ({ siteName }: { siteName?: string }) => { }} onClick={() => { closeSidebarIfNeeded() + NProgress.start() router.push("/") }} > diff --git a/src/features/sidebar/view/internal/sidebar/NewProjectButton.tsx b/src/features/sidebar/view/internal/sidebar/NewProjectButton.tsx index dbfd01a9..09fd5369 100644 --- a/src/features/sidebar/view/internal/sidebar/NewProjectButton.tsx +++ b/src/features/sidebar/view/internal/sidebar/NewProjectButton.tsx @@ -4,6 +4,7 @@ import { useRouter, usePathname } from "next/navigation" import { Stack, List } from "@mui/material" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faPlus } from "@fortawesome/free-solid-svg-icons" +import * as NProgress from "nprogress" import { Template as ProjectListItemTemplate } from "./projects/ProjectListItem" import { Squircle as ProjectAvatarSquircle } from "./projects/ProjectAvatar" import useCloseSidebarOnSelection from "@/features/sidebar/data/useCloseSidebarOnSelection" @@ -30,6 +31,7 @@ const NewProjectButton = () => { } onSelect={() => { closeSidebarIfNeeded() + NProgress.start() router.push("/new") }} />