From 348ccd274deb205020b47ec6364175bf265ec3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Fri, 13 Oct 2023 14:34:28 +0200 Subject: [PATCH 01/47] Changes background color of selected item --- src/lib/components/ProjectComponent.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/components/ProjectComponent.tsx b/src/lib/components/ProjectComponent.tsx index fa2637a2..81face8d 100644 --- a/src/lib/components/ProjectComponent.tsx +++ b/src/lib/components/ProjectComponent.tsx @@ -26,8 +26,10 @@ const ProjectComponent: React.FC = ({ project, selectedPr const theme = useTheme(); return ( - - + + {project.image && ( = ({ project, selectedPr {selectedProject && + {project.name} } From b5c1e488d9559b8041bedf6c37fcd3a61e84abaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Fri, 13 Oct 2023 16:44:29 +0200 Subject: [PATCH 02/47] Reduces logging of sensitive information --- src/app/api/hooks/github/route.ts | 4 ++-- src/lib/components/DocumentationViewerComponent.tsx | 2 -- src/lib/components/ProjectListComponent.tsx | 2 -- src/lib/utils/UrlUtils.ts | 2 -- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/app/api/hooks/github/route.ts b/src/app/api/hooks/github/route.ts index c445cbc7..a30e57cd 100644 --- a/src/app/api/hooks/github/route.ts +++ b/src/app/api/hooks/github/route.ts @@ -99,7 +99,7 @@ export const POST = async (req: NextRequest): Promise => { }) webhooks.on("pull_request.opened", async ({ id, name, payload }) => { - console.log(`Received event ${name}#${id} with action`, payload.action) + console.log(`Received event ${name}#${id}`) if (!alreadyProcessedEvents.has(id)) { console.log("Processing event") alreadyProcessedEvents.add(id) @@ -109,7 +109,7 @@ export const POST = async (req: NextRequest): Promise => { } }) webhooks.on("pull_request.reopened", async ({ id, name, payload }) => { - console.log(`Received event ${name}#${id} with action`, payload.action) + console.log(`Received event ${name}#${id}`) if (!alreadyProcessedEvents.has(id)) { console.log("Processing event") alreadyProcessedEvents.add(id) diff --git a/src/lib/components/DocumentationViewerComponent.tsx b/src/lib/components/DocumentationViewerComponent.tsx index 068988b2..9d4cc5cf 100644 --- a/src/lib/components/DocumentationViewerComponent.tsx +++ b/src/lib/components/DocumentationViewerComponent.tsx @@ -32,11 +32,9 @@ const DocumentationViewerComponent: React.FC< switch (visualizer.toString()) { case DocumentationVisualizer.SWAGGER.toString(): - console.log("here"); return ; case DocumentationVisualizer.REDOCLY.toString(): - console.log("here2"); return ; } }; diff --git a/src/lib/components/ProjectListComponent.tsx b/src/lib/components/ProjectListComponent.tsx index 8b716106..89c324f5 100644 --- a/src/lib/components/ProjectListComponent.tsx +++ b/src/lib/components/ProjectListComponent.tsx @@ -14,8 +14,6 @@ const ProjectListComponent: React.FC = async ({ projectName }) => { const projects = (await projectRepository.getProjects()) as IGitHubProject[]; - console.log(projects, projects) - console.log(projectName, projectName) // projects.push(...projects); // projects.push(...projects); // projects.push(...projects); diff --git a/src/lib/utils/UrlUtils.ts b/src/lib/utils/UrlUtils.ts index 47f54b23..0c126302 100644 --- a/src/lib/utils/UrlUtils.ts +++ b/src/lib/utils/UrlUtils.ts @@ -46,9 +46,7 @@ export function getVersion(url?: string) { export function getSpecification(url?: string) { if (typeof window !== 'undefined') { url = window.location.pathname - console.log(url, url) } const specification = getVersionAndSpecification(url).specification; - console.log(specification, specification) return specification ? decodeURI(specification) : undefined; } \ No newline at end of file From d10c40f0de502549e4f848e97785c295303485f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Fri, 13 Oct 2023 16:46:28 +0200 Subject: [PATCH 03/47] Removes greeting --- src/lib/components/UserComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/UserComponent.tsx b/src/lib/components/UserComponent.tsx index 11511781..91b79f6e 100644 --- a/src/lib/components/UserComponent.tsx +++ b/src/lib/components/UserComponent.tsx @@ -24,7 +24,7 @@ const UserComponent: React.FC = async ({ }} > - Hi {name} 👋 + {name} ); From 1bfb432e8e6e490a7b4e8474e7a5579bbd817e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Fri, 13 Oct 2023 16:52:38 +0200 Subject: [PATCH 04/47] Removes unused code --- src/app/[...slug]/page.tsx | 1 - .../components/OpenApiSpecificationSelectorComponent.tsx | 8 ++------ src/lib/components/OpenApiSpecificationsComponent.tsx | 1 - src/lib/components/ProjectComponent.tsx | 6 ------ src/lib/components/ProjectListComponent.tsx | 1 - src/lib/components/SidebarComponent.tsx | 2 +- src/lib/components/UserComponent.tsx | 1 - src/lib/components/VersionSelectorComponent.tsx | 8 ++------ src/lib/components/VersionsComponent.tsx | 6 +----- src/lib/projects/GitHubOpenAPISpecificationRepository.ts | 1 - 10 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/app/[...slug]/page.tsx b/src/app/[...slug]/page.tsx index aa4c7d52..01f9eafe 100644 --- a/src/app/[...slug]/page.tsx +++ b/src/app/[...slug]/page.tsx @@ -58,7 +58,6 @@ export default async function Page({ versionRepository={githubVersionRepository} projectName={projectName} versionName={versionName} - user={user} /> ), } diff --git a/src/lib/components/OpenApiSpecificationSelectorComponent.tsx b/src/lib/components/OpenApiSpecificationSelectorComponent.tsx index 0f5543d4..ca287d83 100644 --- a/src/lib/components/OpenApiSpecificationSelectorComponent.tsx +++ b/src/lib/components/OpenApiSpecificationSelectorComponent.tsx @@ -1,13 +1,9 @@ "use client"; -import { SelectChangeEvent, Select, MenuItem, Divider } from "@mui/material"; -import { useState } from "react"; +import { SelectChangeEvent, Select, MenuItem } from "@mui/material"; import { IOpenApiSpecification } from "../projects/IOpenAPISpecification"; -import OpenApiSpecificationChangedEvent from "../events/OpenApiSpecificationChangedEvent"; -import { publish } from "../utils/EventsUtils"; -import { getProject, getSpecification, getVersion } from "../utils/UrlUtils"; +import { getProject, getVersion } from "../utils/UrlUtils"; import { useRouter } from "next/navigation"; -import { useForceUpdate } from "../utils/Hooks"; interface OpenApiSpecificationSelectorComponentProps { openApiSpecifications: IOpenApiSpecification[]; diff --git a/src/lib/components/OpenApiSpecificationsComponent.tsx b/src/lib/components/OpenApiSpecificationsComponent.tsx index 26f1ef8e..6f0f45c3 100644 --- a/src/lib/components/OpenApiSpecificationsComponent.tsx +++ b/src/lib/components/OpenApiSpecificationsComponent.tsx @@ -1,4 +1,3 @@ -import { IUser } from "../auth/IUser"; import { IGitHubVersion } from "../projects/IGitHubVersion"; import { IOpenApiSpecificationRepository } from "../projects/IOpenAPISpecificationRepository"; import OpenApiSpecificationSelectorComponent from "./OpenApiSpecificationSelectorComponent"; diff --git a/src/lib/components/ProjectComponent.tsx b/src/lib/components/ProjectComponent.tsx index 81face8d..41cdc539 100644 --- a/src/lib/components/ProjectComponent.tsx +++ b/src/lib/components/ProjectComponent.tsx @@ -2,12 +2,6 @@ import { Avatar, ListItem, ListItemButton, ListItemText, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; -import { IProject } from "../projects/IProject"; -import { Folder, FolderOpen } from "@mui/icons-material"; -import { getProject } from "../utils/UrlUtils"; -import { useState } from "react"; -import ProjectChangedEvent from "../events/ProjectChangedEvent"; -import { publish } from "../utils/EventsUtils"; import { useRouter } from "next/navigation"; import StringAvatar from "./StringAvatar"; import { IGitHubProject } from "../projects/IGitHubProject"; diff --git a/src/lib/components/ProjectListComponent.tsx b/src/lib/components/ProjectListComponent.tsx index 89c324f5..b39626bc 100644 --- a/src/lib/components/ProjectListComponent.tsx +++ b/src/lib/components/ProjectListComponent.tsx @@ -2,7 +2,6 @@ import { List, Divider } from "@mui/material"; import ProjectComponent from "./ProjectComponent"; import { IProjectRepository } from "../projects/IProjectRepository"; import { IGitHubProject } from "../projects/IGitHubProject"; -import { getProject } from "../utils/UrlUtils"; interface ProjectListComponentProps { projectRepository: IProjectRepository; diff --git a/src/lib/components/SidebarComponent.tsx b/src/lib/components/SidebarComponent.tsx index 1490c5b5..7f34a882 100644 --- a/src/lib/components/SidebarComponent.tsx +++ b/src/lib/components/SidebarComponent.tsx @@ -1,6 +1,6 @@ "use client"; -import { Divider, Box, Drawer, IconButton, Typography } from "@mui/material"; +import { Box, Drawer, Typography } from "@mui/material"; import { ReactNode } from "react"; import { LibraryBooks } from '@mui/icons-material'; import { SIDEBAR_SPACING } from "../style/dimensions"; diff --git a/src/lib/components/UserComponent.tsx b/src/lib/components/UserComponent.tsx index 91b79f6e..4cddd673 100644 --- a/src/lib/components/UserComponent.tsx +++ b/src/lib/components/UserComponent.tsx @@ -1,5 +1,4 @@ import { Avatar, Box } from "@mui/material"; -import { IUserProvider } from "../auth/IUserProvider"; import SettingsComponent from "./SettingsComponent" import { SIDEBAR_SPACING } from "../style/dimensions"; import { IUser } from "../auth/IUser"; diff --git a/src/lib/components/VersionSelectorComponent.tsx b/src/lib/components/VersionSelectorComponent.tsx index 200a9c7b..b39195ae 100644 --- a/src/lib/components/VersionSelectorComponent.tsx +++ b/src/lib/components/VersionSelectorComponent.tsx @@ -1,12 +1,8 @@ "use client"; -import { MenuItem, Select, SelectChangeEvent, Divider } from "@mui/material"; +import { MenuItem, Select, SelectChangeEvent } from "@mui/material"; import { IVersion } from "../projects/IVersion"; -import { useState } from "react"; -import { getProject, getVersion } from "../utils/UrlUtils"; -import { publish } from "../utils/EventsUtils"; -import { Events } from "../events/BaseEvent"; -import VersionChangedEvent from "../events/VersionChangedEvent"; +import { getProject } from "../utils/UrlUtils"; import { useRouter } from "next/navigation"; interface VersionSelectorComponentProps { diff --git a/src/lib/components/VersionsComponent.tsx b/src/lib/components/VersionsComponent.tsx index a62b2d58..86fc2f40 100644 --- a/src/lib/components/VersionsComponent.tsx +++ b/src/lib/components/VersionsComponent.tsx @@ -1,6 +1,4 @@ -import { IUser } from "../auth/IUser"; import { IGitHubProject } from "../projects/IGitHubProject"; -import { IProject } from "../projects/IProject"; import { IVersionRepository } from "../projects/IVersionRepository"; import VersionSelectorComponent from "./VersionSelectorComponent"; @@ -8,14 +6,12 @@ interface VersionsComponentProps { versionRepository: IVersionRepository; projectName: string; versionName?: string; - user: IUser; } const VersionsComponent: React.FC = async ({ versionRepository, projectName, - versionName, - user, + versionName }) => { const versions = await versionRepository.getVersions({ name: projectName, diff --git a/src/lib/projects/GitHubOpenAPISpecificationRepository.ts b/src/lib/projects/GitHubOpenAPISpecificationRepository.ts index f6c1214e..88209d98 100644 --- a/src/lib/projects/GitHubOpenAPISpecificationRepository.ts +++ b/src/lib/projects/GitHubOpenAPISpecificationRepository.ts @@ -1,4 +1,3 @@ - import { IGitHubClient } from "@/lib/github/IGitHubClient" import { IGitHubVersion } from "./IGitHubVersion" import { IOpenApiSpecificationRepository } from "./IOpenAPISpecificationRepository" From 67ead105db695185bf0fc8202164d26c4521cdb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Fri, 13 Oct 2023 17:02:36 +0200 Subject: [PATCH 05/47] Shows dividers when menu item is selected --- src/lib/components/ProjectListComponent.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/components/ProjectListComponent.tsx b/src/lib/components/ProjectListComponent.tsx index b39626bc..8ef767a6 100644 --- a/src/lib/components/ProjectListComponent.tsx +++ b/src/lib/components/ProjectListComponent.tsx @@ -23,6 +23,10 @@ const ProjectListComponent: React.FC = async ({ // projects.push(...projects); // projects.push(...projects); // projects.push(...projects); + + const isSelected = (project: IGitHubProject) => { + return projectName == project.repository + } return ( = async ({ > {projects.map((project, index) => (
- - {index < projects.length - 1 && } + {(index == 0 && isSelected(project)) && } + + {(index < projects.length - 1 || isSelected(project)) && }
))}
From 4e309ed9c3d09d4e0e5283cf53b56700bfac2e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Thu, 19 Oct 2023 17:16:55 +0200 Subject: [PATCH 06/47] Improves performance --- .../auth/IdentityAccessTokenProvider.test.ts | 29 --- __test__/projects/ProjectConfigParser.test.ts | 2 +- package-lock.json | 39 +++- package.json | 3 +- src/app/[...slug]/page.tsx | 104 ++-------- .../[owner]/[repository]/[...path]/route.ts | 26 +++ src/app/api/user/projects/route.ts | 7 + src/app/globals.css | 15 +- src/app/layout.tsx | 30 +-- src/app/page.tsx | 18 +- src/app/startup.ts | 40 ---- src/common/LogoHeaderOverlay.tsx | 21 ++ src/common/SidebarContainer.tsx | 31 +++ src/common/SidebarContent.tsx | 21 ++ src/{lib/utils => common}/UrlUtils.ts | 10 +- src/common/client/SidebarContainer.tsx | 163 +++++++++++++++ src/common/client/ThemeRegistry.tsx | 64 ++++++ src/common/client/startup.ts | 3 + src/common/client/theme.ts | 16 ++ src/common/events/BaseEvent.ts | 10 + .../OpenApiSpecificationChangedEvent.ts | 12 ++ src/common/events/ProjectChangedEvent.ts | 11 ++ src/common/events/SettingsChangedEvent.ts | 7 + src/common/events/VersionChangedEvent.ts | 11 ++ src/common/events/utils.ts | 18 ++ src/common/fetcher.ts | 7 + src/common/startup.ts | 23 +++ src/common/useForceUpdate.ts | 6 + .../auth/data/Auth0OAuthTokenRepository.ts | 133 +++++++++++++ .../auth/data/GitHubOAuthTokenRefresher.ts | 68 +++++++ .../auth/domain/AccessTokenService.ts | 40 ++++ .../auth/domain/IOAuthTokenRefresher.ts | 10 + .../auth/domain/IOAuthTokenRepository.ts | 11 ++ .../projects/data/GitHubProjectRepository.ts | 187 ++++++++++++++++++ ...HardcodedGitHubOrganizationNameProvider.ts | 4 +- .../data/IGitHubOrganizationNameProvider.ts | 3 + src/features/projects/data/useProjects.ts | 17 ++ .../projects/domain/IOpenApiSpecification.ts | 5 + src/features/projects/domain/IProject.ts | 8 + .../projects/domain}/IProjectConfig.ts | 2 +- .../projects/domain/IProjectRepository.ts | 5 + src/features/projects/domain/IVersion.ts | 7 + .../projects/domain}/ProjectConfigParser.ts | 4 +- .../projects/domain/ProjectSelection.ts | 74 +++++++ src/features/projects/view/ProjectAvatar.tsx | 43 ++++ .../projects/view/ProjectErrorContent.tsx | 22 +++ src/features/projects/view/ProjectList.tsx | 45 +++++ .../projects/view/ProjectListItem.tsx | 54 +++++ .../view/ProjectListItemPlaceholder.tsx | 27 +++ .../projects/view/ProjectSecondaryContent.tsx | 25 +++ src/features/projects/view/ProjectsPage.tsx | 50 +++++ .../view/docs/DocumentationViewer.tsx | 31 +++ src/features/projects/view/docs/Redocly.tsx | 9 + .../view/docs/SpecificationSelector.tsx | 46 +++++ src/features/projects/view/docs/Swagger.tsx | 10 + .../projects/view/docs/VersionSelector.tsx | 42 ++++ src/features/settings/data/SettingsStore.ts | 36 ++++ .../domain/DocumentationVisualizer.ts | 6 + .../settings/domain/ISettingsStore.ts | 5 + .../view/DocumentationVisualizationPicker.tsx | 38 ++++ src/features/settings/view/SettingsButton.tsx | 44 +++++ src/features/settings/view/SettingsList.tsx | 22 +++ src/features/user/view/UserListItem.tsx | 43 ++++ .../user/view/UserListItemSkeleton.tsx | 22 +++ src/features/welcome/views/WelcomePage.tsx | 14 ++ src/lib/auth/Auth0UserDetailsProvider.ts | 37 ---- src/lib/auth/Auth0UserProvider.ts | 19 -- src/lib/auth/IAccessTokenProvider.ts | 3 - src/lib/auth/IUser.ts | 6 - src/lib/auth/IUserDetails.ts | 3 - src/lib/auth/IUserDetailsProvider.ts | 5 - src/lib/auth/IUserProvider.ts | 5 - src/lib/auth/IdentityAccessTokenProvider.ts | 26 --- src/lib/components/AppBarComponent.tsx | 73 ------- .../DocumentationViewerComponent.tsx | 42 ---- .../OpenApiSpecificationSelectorComponent.tsx | 56 ------ .../OpenApiSpecificationsComponent.tsx | 37 ---- src/lib/components/ProjectComponent.tsx | 60 ------ src/lib/components/ProjectListComponent.tsx | 50 ----- src/lib/components/RedoclyComponent.tsx | 13 -- src/lib/components/SettingsComponent.tsx | 61 ------ src/lib/components/SidebarComponent.tsx | 96 --------- src/lib/components/StringAvatar.tsx | 31 --- src/lib/components/SwaggerComponent.tsx | 14 -- src/lib/components/UserComponent.tsx | 32 --- .../components/VersionSelectorComponent.tsx | 51 ----- src/lib/components/VersionsComponent.tsx | 30 --- .../DocumentationViewerSelectorComponent.tsx | 44 ----- src/lib/events/BaseEvent.ts | 10 - .../OpenApiSpecificationChangedEvent.ts | 12 -- src/lib/events/ProjectChangedEvent.ts | 11 -- src/lib/events/SettingsChangeEvent.ts | 7 - src/lib/events/VersionChangedEvent.ts | 11 -- src/lib/github/DeferredGitHubClient.ts | 66 ------- src/lib/github/IGitHubBranch.ts | 3 - src/lib/github/IGitHubClient.ts | 16 -- src/lib/github/IGitHubContentItem.ts | 5 - .../github/IGitHubOrganizationNameProvider.ts | 3 - src/lib/github/IGitHubRepository.ts | 5 - src/lib/github/IGitHubTag.ts | 3 - src/lib/github/OctokitGitHubClient.ts | 103 ---------- src/lib/networking/AxiosNetworkClient.ts | 38 ---- src/lib/networking/INetworkClient.ts | 7 - src/lib/networking/INetworkRequest.ts | 5 - src/lib/networking/INetworkResponse.ts | 4 - src/lib/pages/App.tsx | 90 --------- src/lib/pages/DocumentationViewerPage.tsx | 21 -- src/lib/pages/NotFoundPage.tsx | 9 - src/lib/pages/WelcomePage.tsx | 7 - .../GitHubOpenAPISpecificationRepository.ts | 25 --- src/lib/projects/GitHubProjectRepository.ts | 86 -------- src/lib/projects/GitHubVersionRepository.ts | 55 ------ src/lib/projects/IGitHubProject.ts | 7 - src/lib/projects/IGitHubVersion.ts | 6 - src/lib/projects/IOpenAPISpecification.ts | 4 - .../IOpenAPISpecificationRepository.ts | 6 - src/lib/projects/IProject.ts | 4 - src/lib/projects/IProjectRepository.ts | 5 - src/lib/projects/IVersion.ts | 3 - src/lib/projects/IVersionRepository.ts | 6 - src/lib/style/dimensions.ts | 1 - src/lib/utils/EventsUtils.ts | 19 -- src/lib/utils/Hooks.ts | 6 - src/lib/utils/SettingsUtils.ts | 22 --- src/middleware.ts | 6 +- types/env.d.ts | 5 + update-github-connection.sh | 51 +++++ 127 files changed, 1823 insertions(+), 1683 deletions(-) delete mode 100644 __test__/auth/IdentityAccessTokenProvider.test.ts create mode 100644 src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts create mode 100644 src/app/api/user/projects/route.ts delete mode 100644 src/app/startup.ts create mode 100644 src/common/LogoHeaderOverlay.tsx create mode 100644 src/common/SidebarContainer.tsx create mode 100644 src/common/SidebarContent.tsx rename src/{lib/utils => common}/UrlUtils.ts (87%) create mode 100644 src/common/client/SidebarContainer.tsx create mode 100644 src/common/client/ThemeRegistry.tsx create mode 100644 src/common/client/startup.ts create mode 100644 src/common/client/theme.ts create mode 100644 src/common/events/BaseEvent.ts create mode 100644 src/common/events/OpenApiSpecificationChangedEvent.ts create mode 100644 src/common/events/ProjectChangedEvent.ts create mode 100644 src/common/events/SettingsChangedEvent.ts create mode 100644 src/common/events/VersionChangedEvent.ts create mode 100644 src/common/events/utils.ts create mode 100644 src/common/fetcher.ts create mode 100644 src/common/startup.ts create mode 100644 src/common/useForceUpdate.ts create mode 100644 src/features/auth/data/Auth0OAuthTokenRepository.ts create mode 100644 src/features/auth/data/GitHubOAuthTokenRefresher.ts create mode 100644 src/features/auth/domain/AccessTokenService.ts create mode 100644 src/features/auth/domain/IOAuthTokenRefresher.ts create mode 100644 src/features/auth/domain/IOAuthTokenRepository.ts create mode 100644 src/features/projects/data/GitHubProjectRepository.ts rename src/{lib/github => features/projects/data}/HardcodedGitHubOrganizationNameProvider.ts (54%) create mode 100644 src/features/projects/data/IGitHubOrganizationNameProvider.ts create mode 100644 src/features/projects/data/useProjects.ts create mode 100644 src/features/projects/domain/IOpenApiSpecification.ts create mode 100644 src/features/projects/domain/IProject.ts rename src/{lib/projects => features/projects/domain}/IProjectConfig.ts (55%) create mode 100644 src/features/projects/domain/IProjectRepository.ts create mode 100644 src/features/projects/domain/IVersion.ts rename src/{lib/projects => features/projects/domain}/ProjectConfigParser.ts (68%) create mode 100644 src/features/projects/domain/ProjectSelection.ts create mode 100644 src/features/projects/view/ProjectAvatar.tsx create mode 100644 src/features/projects/view/ProjectErrorContent.tsx create mode 100644 src/features/projects/view/ProjectList.tsx create mode 100644 src/features/projects/view/ProjectListItem.tsx create mode 100644 src/features/projects/view/ProjectListItemPlaceholder.tsx create mode 100644 src/features/projects/view/ProjectSecondaryContent.tsx create mode 100644 src/features/projects/view/ProjectsPage.tsx create mode 100644 src/features/projects/view/docs/DocumentationViewer.tsx create mode 100644 src/features/projects/view/docs/Redocly.tsx create mode 100644 src/features/projects/view/docs/SpecificationSelector.tsx create mode 100644 src/features/projects/view/docs/Swagger.tsx create mode 100644 src/features/projects/view/docs/VersionSelector.tsx create mode 100644 src/features/settings/data/SettingsStore.ts create mode 100644 src/features/settings/domain/DocumentationVisualizer.ts create mode 100644 src/features/settings/domain/ISettingsStore.ts create mode 100644 src/features/settings/view/DocumentationVisualizationPicker.tsx create mode 100644 src/features/settings/view/SettingsButton.tsx create mode 100644 src/features/settings/view/SettingsList.tsx create mode 100644 src/features/user/view/UserListItem.tsx create mode 100644 src/features/user/view/UserListItemSkeleton.tsx create mode 100644 src/features/welcome/views/WelcomePage.tsx delete mode 100644 src/lib/auth/Auth0UserDetailsProvider.ts delete mode 100644 src/lib/auth/Auth0UserProvider.ts delete mode 100644 src/lib/auth/IAccessTokenProvider.ts delete mode 100644 src/lib/auth/IUser.ts delete mode 100644 src/lib/auth/IUserDetails.ts delete mode 100644 src/lib/auth/IUserDetailsProvider.ts delete mode 100644 src/lib/auth/IUserProvider.ts delete mode 100644 src/lib/auth/IdentityAccessTokenProvider.ts delete mode 100644 src/lib/components/AppBarComponent.tsx delete mode 100644 src/lib/components/DocumentationViewerComponent.tsx delete mode 100644 src/lib/components/OpenApiSpecificationSelectorComponent.tsx delete mode 100644 src/lib/components/OpenApiSpecificationsComponent.tsx delete mode 100644 src/lib/components/ProjectComponent.tsx delete mode 100644 src/lib/components/ProjectListComponent.tsx delete mode 100644 src/lib/components/RedoclyComponent.tsx delete mode 100644 src/lib/components/SettingsComponent.tsx delete mode 100644 src/lib/components/SidebarComponent.tsx delete mode 100644 src/lib/components/StringAvatar.tsx delete mode 100644 src/lib/components/SwaggerComponent.tsx delete mode 100644 src/lib/components/UserComponent.tsx delete mode 100644 src/lib/components/VersionSelectorComponent.tsx delete mode 100644 src/lib/components/VersionsComponent.tsx delete mode 100644 src/lib/components/settings/DocumentationViewerSelectorComponent.tsx delete mode 100644 src/lib/events/BaseEvent.ts delete mode 100644 src/lib/events/OpenApiSpecificationChangedEvent.ts delete mode 100644 src/lib/events/ProjectChangedEvent.ts delete mode 100644 src/lib/events/SettingsChangeEvent.ts delete mode 100644 src/lib/events/VersionChangedEvent.ts delete mode 100644 src/lib/github/DeferredGitHubClient.ts delete mode 100644 src/lib/github/IGitHubBranch.ts delete mode 100644 src/lib/github/IGitHubClient.ts delete mode 100644 src/lib/github/IGitHubContentItem.ts delete mode 100644 src/lib/github/IGitHubOrganizationNameProvider.ts delete mode 100644 src/lib/github/IGitHubRepository.ts delete mode 100644 src/lib/github/IGitHubTag.ts delete mode 100644 src/lib/github/OctokitGitHubClient.ts delete mode 100644 src/lib/networking/AxiosNetworkClient.ts delete mode 100644 src/lib/networking/INetworkClient.ts delete mode 100644 src/lib/networking/INetworkRequest.ts delete mode 100644 src/lib/networking/INetworkResponse.ts delete mode 100644 src/lib/pages/App.tsx delete mode 100644 src/lib/pages/DocumentationViewerPage.tsx delete mode 100644 src/lib/pages/NotFoundPage.tsx delete mode 100644 src/lib/pages/WelcomePage.tsx delete mode 100644 src/lib/projects/GitHubOpenAPISpecificationRepository.ts delete mode 100644 src/lib/projects/GitHubProjectRepository.ts delete mode 100644 src/lib/projects/GitHubVersionRepository.ts delete mode 100644 src/lib/projects/IGitHubProject.ts delete mode 100644 src/lib/projects/IGitHubVersion.ts delete mode 100644 src/lib/projects/IOpenAPISpecification.ts delete mode 100644 src/lib/projects/IOpenAPISpecificationRepository.ts delete mode 100644 src/lib/projects/IProject.ts delete mode 100644 src/lib/projects/IProjectRepository.ts delete mode 100644 src/lib/projects/IVersion.ts delete mode 100644 src/lib/projects/IVersionRepository.ts delete mode 100644 src/lib/style/dimensions.ts delete mode 100644 src/lib/utils/EventsUtils.ts delete mode 100644 src/lib/utils/Hooks.ts delete mode 100644 src/lib/utils/SettingsUtils.ts create mode 100755 update-github-connection.sh diff --git a/__test__/auth/IdentityAccessTokenProvider.test.ts b/__test__/auth/IdentityAccessTokenProvider.test.ts deleted file mode 100644 index 120e005f..00000000 --- a/__test__/auth/IdentityAccessTokenProvider.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { IdentityAccessTokenProvider } from "../../src/lib/auth/IdentityAccessTokenProvider" - -test("It finds the access token for the specified provider", async () => { - const sut = new IdentityAccessTokenProvider({ - async getUserDetails() { - return {identities: [{provider: "github", accessToken: "foo"}]} - } - }, "github") - const accessToken = await sut.getAccessToken() - expect(accessToken).toBe("foo") -}) - -test("It errors when access token could not be found for provider", async () => { - const sut = new IdentityAccessTokenProvider({ - async getUserDetails() { - return {identities: [{provider: "google", accessToken: "foo"}]} - } - }, "github") - expect(sut.getAccessToken()).rejects.toThrow() -}) - -test("It errors when there are no identities", async () => { - const sut = new IdentityAccessTokenProvider({ - async getUserDetails() { - return {identities: []} - } - }, "github") - expect(sut.getAccessToken()).rejects.toThrow() -}) diff --git a/__test__/projects/ProjectConfigParser.test.ts b/__test__/projects/ProjectConfigParser.test.ts index ccb07fee..f859c5f1 100644 --- a/__test__/projects/ProjectConfigParser.test.ts +++ b/__test__/projects/ProjectConfigParser.test.ts @@ -1,4 +1,4 @@ -import { ProjectConfigParser } from "../../src/lib/projects/ProjectConfigParser" +import ProjectConfigParser from "@/features/projects/domain/ProjectConfigParser" test("It parses an empty string", async () => { const sut = new ProjectConfigParser() diff --git a/package-lock.json b/package-lock.json index 6d8f1b8e..bb5108e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,8 @@ "@octokit/core": "^5.0.1", "@octokit/webhooks": "^12.0.3", "auth0": "^4.0.1", - "axios": "^1.5.1", "core-js": "^3.33.0", + "encoding": "^0.1.13", "mobx": "^6.10.2", "next": "13.5.4", "octokit": "^3.1.1", @@ -27,6 +27,7 @@ "redoc": "^2.1.2", "styled-components": "^6.0.8", "swagger-ui-react": "^5.9.0", + "swr": "^2.2.4", "yaml": "^2.3.2" }, "devDependencies": { @@ -5995,6 +5996,14 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -7293,6 +7302,17 @@ "node": ">=10.17.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -11040,6 +11060,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -11742,6 +11767,18 @@ "node": ">= 6" } }, + "node_modules/swr": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz", + "integrity": "sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tailwindcss": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", diff --git a/package.json b/package.json index 034fbdcc..0000c892 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "@octokit/core": "^5.0.1", "@octokit/webhooks": "^12.0.3", "auth0": "^4.0.1", - "axios": "^1.5.1", "core-js": "^3.33.0", + "encoding": "^0.1.13", "mobx": "^6.10.2", "next": "13.5.4", "octokit": "^3.1.1", @@ -29,6 +29,7 @@ "redoc": "^2.1.2", "styled-components": "^6.0.8", "swagger-ui-react": "^5.9.0", + "swr": "^2.2.4", "yaml": "^2.3.2" }, "devDependencies": { diff --git a/src/app/[...slug]/page.tsx b/src/app/[...slug]/page.tsx index 01f9eafe..5c2cf968 100644 --- a/src/app/[...slug]/page.tsx +++ b/src/app/[...slug]/page.tsx @@ -1,90 +1,26 @@ -import ProjectListComponent from "@/lib/components/ProjectListComponent"; -import UserComponent from "@/lib/components/UserComponent"; -import DocumentationViewerPage from "@/lib/pages/DocumentationViewerPage"; -import { - userProvider, - projectRepository, - gitHubOpenApiSpecificationRepository, - githubVersionRepository, -} from "../startup"; -import App from "@/lib/pages/App"; -import OpenApiSpecificationsComponent from "@/lib/components/OpenApiSpecificationsComponent"; -import VersionsComponent from "@/lib/components/VersionsComponent"; -import { getProject, getSpecification, getVersion } from "@/lib/utils/UrlUtils"; -import { IOpenApiSpecification } from "@/lib/projects/IOpenAPISpecification"; -import WelcomePage from "@/lib/pages/WelcomePage"; +import { getProjectId, getSpecificationId, getVersionId } from "@/common/UrlUtils" +import ProjectsPage from "@/features/projects/view/ProjectsPage" -export default async function Page({ - params, -}: { - params: { slug: string | string[] }; -}) { - const user = await userProvider.getUser(); - let url: string; +type PageParams = { slug: string | string[] } + +export default function Page({ params }: { params: PageParams }) { + const url = getURL(params) + return ( + + ) +} + +function getURL(params: PageParams) { if (typeof params.slug === "string") { - url = "/" + params.slug; + return "/" + params.slug } else { - url = params.slug.reduce( + return params.slug.reduce( (previousValue, currentValue) => `${previousValue}/${currentValue}`, "" - ); + ) } - let openApiSpecification: IOpenApiSpecification | undefined; - const projectName = getProject(url); - const versionName = getVersion(url); - const specificationName = getSpecification(url); - if (projectName && versionName && specificationName) { - const specifications = - await gitHubOpenApiSpecificationRepository.getOpenAPISpecifications({ - name: versionName, - owner: "shapehq", - repository: projectName, - }); - openApiSpecification = specifications.find( - (x) => x.name == specificationName - ); - } - - return ( - } - projectListComponent={ - - } - {...(projectName && projectName.length > 0 - ? { - versionSelectorComponent: ( - - ), - } - : {})} - {...(projectName && - projectName.length > 0 && - versionName && - versionName.length > 0 - ? { - openApiSpecificationsComponent: ( - - ), - } - : {})} - > - {openApiSpecification ? ( - - ) : ( - - )} - - ); -} +} \ No newline at end of file diff --git a/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts new file mode 100644 index 00000000..3c0aca58 --- /dev/null +++ b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts @@ -0,0 +1,26 @@ +import { Octokit } from "octokit" +import { NextRequest, NextResponse } from "next/server" +import { accessTokenService } from "@/common/startup" + +type GitHubContentItem = {download_url: string} + +interface GetBlobParams { + owner: string + repository: string + path: [string] +} + +export async function GET(req: NextRequest, { params }: { params: GetBlobParams }) { + const accessToken = await accessTokenService.getAccessToken() + const octokit = new Octokit({ auth: accessToken }) + const fullPath = params.path.join("/") + const ref = req.nextUrl.searchParams.get("ref") + const response = await octokit.rest.repos.getContent({ + owner: params.owner, + repo: params.repository, + path: fullPath, + ref: ref || undefined + }) + let item = response.data as GitHubContentItem + return NextResponse.redirect(new URL(item.download_url)) +} diff --git a/src/app/api/user/projects/route.ts b/src/app/api/user/projects/route.ts new file mode 100644 index 00000000..0d959f1f --- /dev/null +++ b/src/app/api/user/projects/route.ts @@ -0,0 +1,7 @@ +import { NextRequest, NextResponse } from "next/server" +import { projectRepository } from "@/common/startup" + +export async function GET(_req: NextRequest) { + const projects = await projectRepository.getProjects() + return NextResponse.json({projects}) +} diff --git a/src/app/globals.css b/src/app/globals.css index 9d1a5955..345265fa 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -15,9 +15,14 @@ } } -body { +html, body { color: rgb(var(--foreground-rgb)); background: rgb(var(--background-rgb)); + height: 100%; +} + +body { + overflow-y: scroll; } a { @@ -26,4 +31,12 @@ a { a:hover { text-decoration: underline; +} + +.disable-text-selection { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: default; } \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d38019aa..53d809fb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,22 +1,28 @@ -import './globals.css' -import type { Metadata } from 'next' -import { Inter } from 'next/font/google' +import "./globals.css" +import type { Metadata } from "next" +import { Inter } from "next/font/google" +import { CssBaseline } from "@mui/material" +import ThemeRegistry from "@/common/client/ThemeRegistry" +import { UserProvider } from '@auth0/nextjs-auth0/client' -const inter = Inter({ subsets: ['latin'] }) +const inter = Inter({ subsets: ["latin"] }) export const metadata: Metadata = { - title: 'Shape Docs', - description: 'Documentation for Shape\'s APIs', + title: "Shape Docs", + description: "Documentation for Shape\"s APIs", } -export default function RootLayout({ - children, - }: { - children: React.ReactNode -}) { +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - {children} + + + + + {children} + + + ) } diff --git a/src/app/page.tsx b/src/app/page.tsx index 6c1515d7..94c29ddc 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,19 +1,5 @@ -import UserComponent from "@/lib/components/UserComponent"; -import ProjectListComponent from "@/lib/components/ProjectListComponent"; -import App from "@/lib/pages/App"; -import WelcomePage from "@/lib/pages/WelcomePage"; -import { projectRepository, userProvider } from "./startup"; +import ProjectsPage from "@/features/projects/view/ProjectsPage" export default async function Page() { - const user = await userProvider.getUser(); - return ( - } - projectListComponent={ - - } - > - - - ); + return } diff --git a/src/app/startup.ts b/src/app/startup.ts deleted file mode 100644 index df53122a..00000000 --- a/src/app/startup.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Auth0UserDetailsProvider } from "@/lib/auth/Auth0UserDetailsProvider"; -import { Auth0UserProvider } from "@/lib/auth/Auth0UserProvider"; -import { IdentityAccessTokenProvider } from "@/lib/auth/IdentityAccessTokenProvider"; -import { DeferredGitHubClient } from "@/lib/github/DeferredGitHubClient"; -import { HardcodedGitHubOrganizationNameProvider } from "@/lib/github/HardcodedGitHubOrganizationNameProvider"; -import { OctokitGitHubClient } from "@/lib/github/OctokitGitHubClient"; -import { AxiosNetworkClient } from "@/lib/networking/AxiosNetworkClient"; -import { GitHubOpenApiSpecificationRepository } from "@/lib/projects/GitHubOpenAPISpecificationRepository"; -import { GitHubProjectRepository } from "@/lib/projects/GitHubProjectRepository"; -import { GitHubVersionRepository } from "@/lib/projects/GitHubVersionRepository"; - -export const organizationNameProvider = new HardcodedGitHubOrganizationNameProvider( - "shapehq" -); -export const userProvider = new Auth0UserProvider(); -export const userDetailsProvider = new Auth0UserDetailsProvider(userProvider, { - domain: process.env.AUTH0_MANAGEMENT_DOMAIN, - clientId: process.env.AUTH0_MANAGEMENT_CLIENT_ID, - clientSecret: process.env.AUTH0_MANAGEMENT_CLIENT_SECRET, -}); -export const accessTokenProvider = new IdentityAccessTokenProvider( - userDetailsProvider, - "github" -); -export const gitHubClientFactory = ( - accessToken: string, - organizationName: string -) => { - return new OctokitGitHubClient(accessToken, organizationName); -}; -export const gitHubClient = new DeferredGitHubClient( - organizationNameProvider, - accessTokenProvider, - gitHubClientFactory -); -export const networkClient = new AxiosNetworkClient(); -export const projectRepository = new GitHubProjectRepository(gitHubClient, networkClient); -export const githubVersionRepository = new GitHubVersionRepository(gitHubClient); -export const gitHubOpenApiSpecificationRepository = - new GitHubOpenApiSpecificationRepository(gitHubClient); \ No newline at end of file diff --git a/src/common/LogoHeaderOverlay.tsx b/src/common/LogoHeaderOverlay.tsx new file mode 100644 index 00000000..29e8af26 --- /dev/null +++ b/src/common/LogoHeaderOverlay.tsx @@ -0,0 +1,21 @@ +import Image from "next/image" +import { Box } from "@mui/material" +import ShapeLogo from "../../public/shape2023.svg" + +const LogoHeaderOverlay: React.FC = () => { + return ( + + Shape logo + + ) +} + +export default LogoHeaderOverlay diff --git a/src/common/SidebarContainer.tsx b/src/common/SidebarContainer.tsx new file mode 100644 index 00000000..a11eafdd --- /dev/null +++ b/src/common/SidebarContainer.tsx @@ -0,0 +1,31 @@ +import { ReactNode } from "react" +import ClientSidebarContainer from "./client/SidebarContainer" +import SidebarContent from "./SidebarContent" +import LogoHeaderOverlay from "./LogoHeaderOverlay" + +interface SidebarContainerProps { + readonly primaryHeader: ReactNode + readonly primary: ReactNode + readonly secondary: ReactNode +} + +const SidebarContainer: React.FC = ({ + primaryHeader, + primary, + secondary +}) => { + return ( + + {primary} + + } + secondaryHeader={} + secondary={secondary} + /> + ) +} + +export default SidebarContainer diff --git a/src/common/SidebarContent.tsx b/src/common/SidebarContent.tsx new file mode 100644 index 00000000..aef5fc59 --- /dev/null +++ b/src/common/SidebarContent.tsx @@ -0,0 +1,21 @@ +import { ReactNode } from "react" +import { Box } from "@mui/material" +import UserListItem from "@/features/user/view/UserListItem" +import SettingsButton from "@/features/settings/view/SettingsButton" + +const SidebarContent: React.FC<{ + readonly children: ReactNode +}> = ({ + children +}) => { + return ( + <> + {children} + + } /> + + + ) +} + +export default SidebarContent diff --git a/src/lib/utils/UrlUtils.ts b/src/common/UrlUtils.ts similarity index 87% rename from src/lib/utils/UrlUtils.ts rename to src/common/UrlUtils.ts index 0c126302..d2350aa3 100644 --- a/src/lib/utils/UrlUtils.ts +++ b/src/common/UrlUtils.ts @@ -1,4 +1,4 @@ -export function getProject(url?: string) { +export function getProjectId(url?: string) { if (typeof window !== 'undefined') { url = window.location.pathname;// remove first slash } @@ -8,11 +8,11 @@ export function getProject(url?: string) { if (firstSlash != -1 && url) { project = decodeURI(url.substring(0, firstSlash)); } - return project ? project + "-openapi" : project; + return project } function getVersionAndSpecification(url?: string) { - const project = getProject(url)?.replace('-openapi', ''); + const project = getProjectId(url)?.replace('-openapi', ''); if (url && project) { const versionAndSpecification = url.substring(project.length + 2)// remove first slash let specification: string | undefined = undefined; @@ -35,7 +35,7 @@ function getVersionAndSpecification(url?: string) { return {}; } -export function getVersion(url?: string) { +export function getVersionId(url?: string) { if (typeof window !== 'undefined') { url = window.location.pathname } @@ -43,7 +43,7 @@ export function getVersion(url?: string) { return version ? decodeURI(version) : undefined; } -export function getSpecification(url?: string) { +export function getSpecificationId(url?: string) { if (typeof window !== 'undefined') { url = window.location.pathname } diff --git a/src/common/client/SidebarContainer.tsx b/src/common/client/SidebarContainer.tsx new file mode 100644 index 00000000..66bf8778 --- /dev/null +++ b/src/common/client/SidebarContainer.tsx @@ -0,0 +1,163 @@ +"use client" + +import { ReactNode, useState } from "react" +import { Box, Drawer, Divider, IconButton, Toolbar } from "@mui/material" +import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar" +import { ChevronLeft, Menu } from "@mui/icons-material" +import { styled, useTheme } from "@mui/material/styles" + +const drawerWidth = 320 + +const Main = styled("main", { shouldForwardProp: (prop) => prop !== "open" })<{ + open?: boolean +}>(({ theme, open }) => ({ + display: "flex", + flexDirection: "column", + flexGrow: 1, + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + marginLeft: `-${drawerWidth}px`, + ...(open && { + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + marginLeft: 0, + }) +})) + +const DrawerHeaderWrapper = styled("div")(({ theme }) => ({ + display: "flex", + alignItems: "center", + // necessary for content to be below app bar + ...theme.mixins.toolbar +})) + +const DrawerHeader = ({ + primaryHeader, + handleDrawerClose +}: { + primaryHeader: ReactNode, + handleDrawerClose: () => void +}) => { + return ( + + + + + {primaryHeader != null && + + {primaryHeader} + + } + + ) +} + +interface AppBarProps extends MuiAppBarProps { + open?: boolean +} + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== "open", +})(({ theme, open }) => ({ + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + ...(open && { + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: `${drawerWidth}px`, + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }), +})) + +interface SidebarContainerProps { + primaryHeader?: ReactNode + primary: ReactNode + secondaryHeader?: ReactNode + secondary: ReactNode +} + +const SidebarContainer: React.FC = ({ + primaryHeader, + primary, + secondaryHeader, + secondary +}) => { + const [open, setOpen] = useState(true) + const handleDrawerOpen = () => { + setOpen(true) + } + const handleDrawerClose = () => { + setOpen(false) + } + const theme = useTheme() + return ( + + + + + + + {secondaryHeader != null && secondaryHeader} + + + + + + {primary} + +
+ + {secondary} +
+ + ) +} + +export default SidebarContainer diff --git a/src/common/client/ThemeRegistry.tsx b/src/common/client/ThemeRegistry.tsx new file mode 100644 index 00000000..d5327c90 --- /dev/null +++ b/src/common/client/ThemeRegistry.tsx @@ -0,0 +1,64 @@ +"use client" + +import { useState } from "react" +import createCache from "@emotion/cache" +import { useServerInsertedHTML } from "next/navigation" +import { CacheProvider } from "@emotion/react" +import { ThemeProvider } from "@mui/material/styles" +import CssBaseline from "@mui/material/CssBaseline" +import theme from "./theme" + +// This implementation is from emotion-js +// https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902 +export default function ThemeRegistry(props: any) { + const { options, children } = props; + + const [{ cache, flush }] = useState(() => { + const cache = createCache(options); + cache.compat = true; + const prevInsert = cache.insert; + let inserted: string[] = []; + cache.insert = (...args) => { + const serialized = args[1]; + if (cache.inserted[serialized.name] === undefined) { + inserted.push(serialized.name); + } + return prevInsert(...args); + }; + const flush = () => { + const prevInserted = inserted; + inserted = []; + return prevInserted; + }; + return { cache, flush }; + }); + + useServerInsertedHTML(() => { + const names = flush(); + if (names.length === 0) { + return null; + } + let styles = ''; + for (const name of names) { + styles += cache.inserted[name]; + } + return ( +