diff --git a/package-lock.json b/package-lock.json index 5e904ac8..852c3f59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,12 @@ "version": "0.0.0", "dependencies": { "@aws-amplify/ui-react": "^6.5.5", + "@saffron/core-components": "^3.10.0", + "@saffron/core-styles": "^3.6.0", "aws-amplify": "^6.6.6", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^7.9.1" }, "devDependencies": { "@aws-amplify/backend": "^1.5.0", @@ -16138,6 +16141,12 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@graphql-codegen/core": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-4.0.2.tgz", @@ -17137,6 +17146,15 @@ "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==", "dev": true }, + "node_modules/@microsoft/fast-web-utilities": { + "version": "6.0.0", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@microsoft/fast-web-utilities/-/fast-web-utilities-6.0.0.tgz", + "integrity": "sha512-ckCA4Xn91ja1Qz+jhGGL1Q3ZeuRpA5VvYcRA7GzA1NP545sl14bwz3tbHCq8jIk+PL7mkSaIveGMYuJB2L4Izg==", + "license": "MIT", + "dependencies": { + "exenv-es6": "^1.1.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -18051,6 +18069,74 @@ "win32" ] }, + "node_modules/@saffron/config": { + "version": "1.1.11", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@saffron/config/-/@saffron/config-1.1.11.tgz", + "integrity": "sha512-s6UKd+obVMFJQSv7DJtHRfTVyNBK/NjtYo7VX/ajRv+8dnexF9NREmx2MKjNI+rqnHIKSPUsi3lDj5wDGI7hsw==", + "license": "MIT" + }, + "node_modules/@saffron/core-components": { + "version": "3.10.0", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@saffron/core-components/-/@saffron/core-components-3.10.0.tgz", + "integrity": "sha512-f8ozG2JUuda99vqCClX0jNJZ88hCeeSkWMm2ImbqjbwvYf4JpC27bUB16Su3WQuCyMdRegRalRtUf+TiovlE2Q==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@floating-ui/dom": "1.5.4", + "@microsoft/fast-web-utilities": "6.0.0", + "@saffron/config": "1.1.11", + "@saffron/core-styles": "3.6.0", + "@saffron/date-fns": "4.1.3", + "tabbable": "6.2.0", + "tslib": "2.6.3" + }, + "engines": { + "node": ">= 16.13.0" + } + }, + "node_modules/@saffron/core-components/node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@saffron/core-components/node_modules/@floating-ui/dom": { + "version": "1.5.4", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@floating-ui/dom/-/dom-1.5.4.tgz", + "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.5.3", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@saffron/core-components/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" + }, + "node_modules/@saffron/core-styles": { + "version": "3.6.0", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@saffron/core-styles/-/@saffron/core-styles-3.6.0.tgz", + "integrity": "sha512-JMTUwu/H2ozgvpmG4UKz4d3jV/mYkLib6P3MfohkIZVEz/Kl6Drdwqe5Ncpx3zWLRApa+FG+mZSWMXQybC10OQ==", + "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">= 14.14.22" + } + }, + "node_modules/@saffron/date-fns": { + "version": "4.1.3", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/@saffron/date-fns/-/@saffron/date-fns-4.1.3.tgz", + "integrity": "sha512-OCZ3ANjGQMw/5nezn3yK3N6bb7IcCke6Y0OSIObfCDUYo2BpTA7LUwxkEL7SBhicK5Z6kGbmFhjDvOLZKvd52A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/@smithy/abort-controller": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", @@ -20692,6 +20778,15 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/core-js": { "version": "3.38.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", @@ -21600,6 +21695,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv-es6": { + "version": "1.1.1", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/exenv-es6/-/exenv-es6-1.1.1.tgz", + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", + "license": "MIT" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -24479,6 +24580,44 @@ } } }, + "node_modules/react-router": { + "version": "7.9.1", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/react-router/-/react-router-7.9.1.tgz", + "integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.1", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/react-router-dom/-/react-router-dom-7.9.1.tgz", + "integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==", + "license": "MIT", + "dependencies": { + "react-router": "7.9.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -25005,6 +25144,12 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -25404,6 +25549,12 @@ "tslib": "^2.0.3" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://tr1.jfrog.io/artifactory/api/npm/npm/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tarn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", diff --git a/package.json b/package.json index f3d74a93..5dc6a54e 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,12 @@ }, "dependencies": { "@aws-amplify/ui-react": "^6.5.5", + "@saffron/core-components": "^3.10.0", + "@saffron/core-styles": "^3.6.0", "aws-amplify": "^6.6.6", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^7.9.1" }, "devDependencies": { "@aws-amplify/backend": "^1.5.0", diff --git a/src/App.tsx b/src/App.tsx index d7a72df3..873456e4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,32 @@ import { useEffect, useState } from "react"; import type { Schema } from "../amplify/data/resource"; import { generateClient } from "aws-amplify/data"; +import NavBar from "./components/NavBar/NavBar.tsx"; +import SideNav from "./components/SideNav/SideNav.tsx"; -const client = generateClient(); +// const client = generateClient(); function App() { const [todos, setTodos] = useState>([]); useEffect(() => { - client.models.Todo.observeQuery().subscribe({ - next: (data) => setTodos([...data.items]), - }); + // client.models.Todo.observeQuery().subscribe({ + // next: (data) => setTodos([...data.items]), + // }); }, []); function createTodo() { - client.models.Todo.create({ content: window.prompt("Todo content") }); + // client.models.Todo.create({ content: window.prompt("Todo content") }); } + + return ( -
+ <> + + +
+

My todos

    @@ -34,6 +42,8 @@ function App() {
+ + ); } diff --git a/src/components/NavBar/NavBar.tsx b/src/components/NavBar/NavBar.tsx new file mode 100644 index 00000000..97e4f78d --- /dev/null +++ b/src/components/NavBar/NavBar.tsx @@ -0,0 +1,26 @@ +import { Link } from "react-router-dom"; +import { SafProductHeader, SafProductHeaderItem } from '@saffron/core-components/react'; + + +export default function NavBar() { + return ( + + + SONCA Logo + Home + + + Projects & Assessments + + + Users & Organisations + + + Framework Management + + + Notifications + + + ); +} \ No newline at end of file diff --git a/src/components/SideNav/SideNav.tsx b/src/components/SideNav/SideNav.tsx new file mode 100644 index 00000000..483836a0 --- /dev/null +++ b/src/components/SideNav/SideNav.tsx @@ -0,0 +1,29 @@ +import React, { Suspense } from "react"; + +const SideNavContent = React.lazy(() => import("./SideNavContent")); + +// Create a loading component +const LoadingNav = () => ( +
+
+ Loading navigation... +
+
+); + +export default function SideNav() { + return ( + }> + + + ); +} diff --git a/src/components/SideNav/SideNavContent.tsx b/src/components/SideNav/SideNavContent.tsx new file mode 100644 index 00000000..0152bcfb --- /dev/null +++ b/src/components/SideNav/SideNavContent.tsx @@ -0,0 +1,95 @@ +'use client'; +import { SafSideNav, SafMenuItem, SafIcon } from "@saffron/core-components/react"; +import { useNavigate } from "react-router-dom"; +import { useState } from "react"; + +export default function SideNavContent() { + + const [stateRouter, setStateRouter] = useState<'open' | 'closed'>('open'); + const navigate = useNavigate(); + const isRoot = location.pathname === "/" || location.pathname === "/home"; + + return ( + setStateRouter('open')} + onClose={() => setStateRouter('closed')} + > + {isRoot ? ( + <> + {/* + Overview + + + Projects + + + Assessments + + + Review Queue + + + Reports + + + Interviews + + + Documents + */} + + ) : location.pathname.startsWith("/projects-and-assessments") ? ( + <> + + Overview + + + Projects + + + Assessments + + + Review Queue + + + Reports + + + Interviews + + + Documents + + + ) : location.pathname.startsWith("/users-and-organisations") ? ( + <> + + Users + + + Organisations + + + Permissions + + + ) : location.pathname.startsWith("/framework-management") ? ( + <> + + Frameworks + + + ) : location.pathname.startsWith("/projects-and-assessments/projects") ? ( + <> + + ) : null} + + ); +} diff --git a/src/index.css b/src/index.css index 7a464bdf..120cc8b5 100644 --- a/src/index.css +++ b/src/index.css @@ -1,6 +1,56 @@ +@import './node_modules/@saffron/core-styles/dist/index.css'; + +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', + 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: #CBBEFF; + --background-end-rgb: 255, 255, 255; + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: #6649AE; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); margin: 0; - background: linear-gradient(180deg, rgb(117, 81, 194), rgb(255, 255, 255)); display: flex; font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; height: 100vh; @@ -9,10 +59,15 @@ body { align-items: center; } -main { - display: flex; - flex-direction: column; - align-items: stretch; +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } } button { @@ -35,6 +90,12 @@ button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } +main { + display: flex; + flex-direction: column; + align-items: stretch; +} + ul { padding-inline-start: 0; margin-block-start: 0; @@ -50,16 +111,75 @@ ul { overflow: auto; } -li { - background-color: white; - padding: 8px; +saf-side-nav { + height: 100vh; + position: fixed; + top: 64px; + left: 0; + border-right: 1px solid rgb(var(--callout-border-rgb)); + background: linear-gradient(180deg, #f5f5f5 80%, #e9e6f7 100%); + z-index: 50; + padding: 32px 0; + display: flex; + flex-direction: column; + gap: 20px; + box-sizing: border-box; + min-width: 220px; + box-shadow: 2px 0 8px 0 rgba(102, 73, 174, 0.04); } -li:hover { - background: #dadbf9; +saf-side-nav > saf-menu-item::part(root) { + margin: 0 24px; + border-radius: 8px; + transition: background 0.2s, color 0.2s; + font-size: 1.08rem; + font-weight: 500; + color: #3a2e5a; + padding: 10px 16px; + cursor: pointer; + outline: none; + border: none; + background: none; + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; } -a { - font-weight: 800; +saf-side-nav > saf-menu-item::part(root):hover, +saf-side-nav > saf-menu-item::part(root):focus-visible { + box-shadow: 0 2px 8px 0 rgba(102, 73, 174, 0.08); text-decoration: none; +} + +saf-product-header { + position: fixed; + top: 0; + left: 0; + width: 100vw; + z-index: 100; + color: white; + box-shadow: 0 2px 8px rgba(0,0,0,0.04); +} + +.sonca-logo { + margin-right: 8px; + vertical-align: middle; +} + +body, #root { + padding-top: 64px; +} + +saf-product-header, saf-product-header-item { + font-size: 1.08rem; + font-weight: 500; +} + +saf-product-header-item { + margin: 0 32px; +} + +saf-product-header a:hover, saf-product-header-item a:hover { + text-decoration: underline; } \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index 36c530d7..e0e53c00 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,14 +1,16 @@ import React from "react"; import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; import App from "./App.tsx"; import "./index.css"; -import { Amplify } from "aws-amplify"; -import outputs from "../amplify_outputs.json"; +// import outputs from "../amplify_outputs.json"; -Amplify.configure(outputs); +// Amplify.configure(outputs); ReactDOM.createRoot(document.getElementById("root")!).render( - + + + );