diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index 470f9416..d0c64845 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -79,7 +79,6 @@ jobs:
set -o pipefail
sudo chmod +x lint.sh && ./lint.sh 2>&1 | tee code_lint_output.txt
-
- name: Unit tests
id: unit_tests
run: |
@@ -127,15 +126,15 @@ jobs:
- name: Scan Docker image with Dockle
id: dockle
run: |
- wget -q https://github.com/goodwithtech/dockle/releases/download/v0.4.14/dockle_0.4.14_Linux-64bit.tar.gz
- tar zxf dockle_0.4.14_Linux-64bit.tar.gz
- sudo mv dockle /usr/local/bin
+ wget -q https://github.com/goodwithtech/dockle/releases/download/v0.4.14/dockle_0.4.14_Linux-64bit.tar.gz
+ tar zxf dockle_0.4.14_Linux-64bit.tar.gz
+ sudo mv dockle /usr/local/bin
- dockle --exit-code 1 --exit-level fatal --format json --input '/tmp/image-${{ matrix.name }}-${{ github.sha }}-pr.tar' --output ${{ matrix.workdir }}/dockle_scan_output.json
- rm -rf '/tmp/image-${{ matrix.name }}-${{ github.sha }}-pr.tar'
- cat ${{ matrix.workdir }}/dockle_scan_output.json
+ dockle --exit-code 1 --exit-level fatal --format json --input '/tmp/image-${{ matrix.name }}-${{ github.sha }}-pr.tar' --output ${{ matrix.workdir }}/dockle_scan_output.json
+ rm -rf '/tmp/image-${{ matrix.name }}-${{ github.sha }}-pr.tar'
+ cat ${{ matrix.workdir }}/dockle_scan_output.json
- echo "outcome=success" >> $GITHUB_OUTPUT
+ echo "outcome=success" >> $GITHUB_OUTPUT
- name: Create PR comment
if: always()
diff --git a/frontend/messages/de.json b/frontend/messages/de.json
index 4e4d1360..771fa703 100644
--- a/frontend/messages/de.json
+++ b/frontend/messages/de.json
@@ -15,9 +15,11 @@
"title": "Cardano-Verfassung",
"drawer": {
"compare": "Vergleichen",
- "latestRevisions": "Latest Revisions",
+ "versionHistory": "Version History",
"tableOfContents": "Contents",
- "latest": "Latest"
+ "latest": "Latest",
+ "uploadNewVersion": "Upload new version",
+ "backToContents": "Back to contents"
}
},
"Members": {
@@ -204,10 +206,10 @@
"description": "Please provide rationale for your vote on specific governance action.",
"fields": {
"title": {
- "label": "Title"
+ "label": "Summary"
},
"rationale": {
- "label": "Rationale"
+ "label": "Rationale Statement"
}
},
"alerts": {
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index 7e3c9b36..345898db 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -16,7 +16,7 @@
"drawer": {
"compare": "Compare",
"latestRevisions": "Latest Revisions",
- "tableOfContents": "Contents",
+ "tableOfContents": "Content",
"latest": "Latest"
}
},
@@ -204,10 +204,10 @@
"description": "Please provide rationale for your vote on specific governance action.",
"fields": {
"title": {
- "label": "Title"
+ "label": "Summary"
},
"rationale": {
- "label": "Rationale"
+ "label": "Rationale Statement"
}
},
"alerts": {
diff --git a/frontend/public/icons/DocumentSearch.svg b/frontend/public/icons/DocumentSearch.svg
new file mode 100644
index 00000000..6e726f38
--- /dev/null
+++ b/frontend/public/icons/DocumentSearch.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/public/icons/Eye.svg b/frontend/public/icons/Eye.svg
new file mode 100644
index 00000000..8dd41185
--- /dev/null
+++ b/frontend/public/icons/Eye.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/app/[locale]/interim-constitution/layout.tsx b/frontend/src/app/[locale]/interim-constitution/layout.tsx
index 740fcaaf..95004f6f 100644
--- a/frontend/src/app/[locale]/interim-constitution/layout.tsx
+++ b/frontend/src/app/[locale]/interim-constitution/layout.tsx
@@ -2,9 +2,13 @@ import { customPalette } from "@/constants";
import { Box } from "@mui/material";
export default function ConstitutionLayout({
- children
+ children,
}: {
children: React.ReactNode;
}) {
- return {children};
+ return (
+
+ {children}
+
+ );
}
diff --git a/frontend/src/app/[locale]/interim-constitution/version-history/page.tsx b/frontend/src/app/[locale]/interim-constitution/version-history/page.tsx
new file mode 100644
index 00000000..76a6c254
--- /dev/null
+++ b/frontend/src/app/[locale]/interim-constitution/version-history/page.tsx
@@ -0,0 +1,34 @@
+import { ContentWrapper } from "@/components/atoms";
+import { VersionHistory } from "@/components/organisms/Constitution/VersionHistory";
+import { getConstitutionMetadata } from "@/lib/api";
+import { Loading } from "@molecules";
+import { Footer, NotFound, TopNav } from "@organisms";
+import { isResponseErrorI } from "@utils";
+import { Suspense } from "react";
+
+export default async function VersionHistoryPage() {
+ const metadata = await getConstitutionMetadata();
+
+ return (
+ <>
+
+ }>
+ {metadata && !isResponseErrorI(metadata) ? (
+
+
+
+ ) : (
+ <>
+
+
+
+
+ >
+ )}
+
+ >
+ );
+}
diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx
index 1b680bde..9e4830fb 100644
--- a/frontend/src/app/[locale]/page.tsx
+++ b/frontend/src/app/[locale]/page.tsx
@@ -1,18 +1,16 @@
-import React from "react";
-
-import { Footer, Hero, HeroActions, TopNav } from "@organisms";
-import { unstable_setRequestLocale } from "next-intl/server"; // Import function to set the request-specific locale (unstable API).
+import ClientRedirect from "@/components/molecules/ClientRedirect";
import { decodeUserToken } from "@/lib/api";
-import { redirect } from "next/navigation";
-import { PATHS } from "@consts";
import { ContentWrapper } from "@atoms";
+import { PATHS } from "@consts";
+import { Footer, Hero, HeroActions, TopNav } from "@organisms";
+import { unstable_setRequestLocale } from "next-intl/server"; // Import function to set the request-specific locale (unstable API).
export default async function Home({ params: { locale } }) {
unstable_setRequestLocale(locale); // Sets the locale for the request. Use cautiously due to its unstable nature.
const user = await decodeUserToken();
if (user) {
- redirect(`/${locale}/${PATHS.constitution}`);
+ return ;
}
return (
<>
diff --git a/frontend/src/components/atoms/TextArea.tsx b/frontend/src/components/atoms/TextArea.tsx
index d189e7c5..10cec7ef 100644
--- a/frontend/src/components/atoms/TextArea.tsx
+++ b/frontend/src/components/atoms/TextArea.tsx
@@ -1,9 +1,9 @@
"use client";
-import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
import { TextareaAutosize, styled } from "@mui/material";
+import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
-import { TextAreaProps } from "./types";
import { poppins } from "@consts";
+import { TextAreaProps } from "./types";
const TextAreaBase = styled(TextareaAutosize)(
() => `
@@ -20,7 +20,7 @@ const TextAreaBase = styled(TextareaAutosize)(
);
export const TextArea = forwardRef(
- ({ errorMessage, maxLength = 500, onBlur, onFocus, ...props }, ref) => {
+ ({ errorMessage, maxLength, onBlur, onFocus, ...props }, ref) => {
const textAraeRef = useRef(null);
const handleFocus = useCallback(
@@ -45,7 +45,7 @@ export const TextArea = forwardRef(
({
focus: handleFocus,
blur: handleBlur,
- ...textAraeRef.current,
+ ...textAraeRef.current
} as unknown as HTMLTextAreaElement),
[handleBlur, handleFocus]
);
@@ -61,7 +61,7 @@ export const TextArea = forwardRef(
outline: "none",
padding: "12px 14px",
resize: "none",
- overflow: "scroll",
+ overflow: "scroll"
}}
maxLength={maxLength}
ref={textAraeRef}
diff --git a/frontend/src/components/molecules/ClientRedirect.tsx b/frontend/src/components/molecules/ClientRedirect.tsx
new file mode 100644
index 00000000..6b94b27c
--- /dev/null
+++ b/frontend/src/components/molecules/ClientRedirect.tsx
@@ -0,0 +1,31 @@
+"use client";
+import type { Route } from "next";
+import { useRouter } from "next/navigation";
+import { useEffect } from "react";
+
+/**
+ * For client-side redirects in React Server Components (RSC)
+ *
+ * Trying to redirect in server using `redirect` from `next/navigation`
+ * gives an odd error "Rendered more hooks than during the previous render"
+ * which seems impossible to debug, as we don't have any hooks that are
+ * conditionally rendered or skipped. There's no solution available for this
+ * specific problem; most solutions point to checking for conditional hooks.
+ *
+ * At present, client-based redirection works correctly, therefore we are
+ * creating a client component whose function is purely to redirect. It
+ * doesn't return any JSX.Element. Note that we should return this component
+ * early, otherwise the other components will flash while redirecting.
+ *
+ * @param {Route} href - The route path to which the client will be redirected.
+ * @returns {null} - Returns null as void is not assignable to JSX.Element.
+ */
+export default function ClientRedirect({ href }: { href: Route }): null {
+ const router = useRouter();
+
+ useEffect(() => {
+ router.push(href);
+ }, [href, router]);
+
+ return null;
+}
diff --git a/frontend/src/components/molecules/Field/TextArea.tsx b/frontend/src/components/molecules/Field/TextArea.tsx
index 1f3579a7..28e584ec 100644
--- a/frontend/src/components/molecules/Field/TextArea.tsx
+++ b/frontend/src/components/molecules/Field/TextArea.tsx
@@ -5,7 +5,7 @@ import {
FormErrorMessage,
FormHelpfulText,
TextArea as TextAreaBase,
- Typography,
+ Typography
} from "@atoms";
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
@@ -21,7 +21,7 @@ export const TextArea = forwardRef(
label,
labelStyles,
layoutStyles,
- maxLength = 500,
+ maxLength,
onBlur,
onFocus,
...props
@@ -52,7 +52,7 @@ export const TextArea = forwardRef(
({
focus: handleFocus,
blur: handleBlur,
- ...textAreaRef.current,
+ ...textAreaRef.current
} as unknown as HTMLTextAreaElement),
[handleBlur, handleFocus]
);
@@ -63,7 +63,7 @@ export const TextArea = forwardRef(
flexDirection: "column",
width: "100%",
position: "relative",
- ...layoutStyles,
+ ...layoutStyles
}}
>
{label && (
@@ -95,11 +95,11 @@ export const TextArea = forwardRef(
sx={{
bottom: errorMessage ? 35 : 14,
position: "absolute",
- right: 15,
+ right: 15
}}
variant="caption"
>
- {props?.value?.toString()?.length ?? 0}/{maxLength}
+ {maxLength && `${props?.value?.toString()?.length ?? 0}/${maxLength}`}
);
diff --git a/frontend/src/components/organisms/Constitution/Constitution.tsx b/frontend/src/components/organisms/Constitution/Constitution.tsx
index 106174f1..a5d254ed 100644
--- a/frontend/src/components/organisms/Constitution/Constitution.tsx
+++ b/frontend/src/components/organisms/Constitution/Constitution.tsx
@@ -6,11 +6,11 @@ import { useScreenDimension } from "@hooks";
import { Box, Grid, IconButton } from "@mui/material";
import { useTranslations } from "next-intl";
import { MDXRemote } from "next-mdx-remote";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { Footer } from "../Footer";
import { DrawerMobile } from "../TopNavigation";
import { ConstitutionProps } from "../types";
-import { ConstitutionSidebar } from "./ConstitutionSidebar";
+import { ContentsSidebar } from "./ContentsSidebar";
import {
Code,
Heading1,
@@ -20,23 +20,17 @@ import {
ListItem,
NavDrawerDesktop,
Paragraph,
- TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS
+ TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS,
} from "./MDXComponents";
import { TocAccordion } from "./TOCAccordion";
import TOCLink from "./TOCLink";
export function Constitution({ constitution, metadata }: ConstitutionProps) {
const { screenWidth } = useScreenDimension();
- const [isOpen, setIsOpen] = useState(true);
+ const [isOpen, setIsOpen] = useState(false);
const isMobile = screenWidth < 1025;
const t = useTranslations("Constitution");
- useEffect(() => {
- if (isMobile) {
- setIsOpen(false);
- }
- }, [isMobile]);
-
const onTOCLinkClick = () => {
if (isMobile) {
setIsOpen(false);
@@ -52,11 +46,11 @@ export function Constitution({ constitution, metadata }: ConstitutionProps) {
sx={TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS}
rowGap={0}
>
-
+
) : (
-
+
),
h1: Heading1,
@@ -84,7 +78,7 @@ export function Constitution({ constitution, metadata }: ConstitutionProps) {
);
}
return ;
- }
+ },
};
return (
@@ -102,9 +96,9 @@ export function Constitution({ constitution, metadata }: ConstitutionProps) {
md={isOpen ? 8 : 11}
ml={{ xxs: 0, lg: "404px" }}
>
-
+
-
+
@@ -136,4 +130,4 @@ export function Constitution({ constitution, metadata }: ConstitutionProps) {
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/organisms/Constitution/ConstitutionSidebar.tsx b/frontend/src/components/organisms/Constitution/ConstitutionSidebar.tsx
index dae59467..1bc9c261 100644
--- a/frontend/src/components/organisms/Constitution/ConstitutionSidebar.tsx
+++ b/frontend/src/components/organisms/Constitution/ConstitutionSidebar.tsx
@@ -1,97 +1,27 @@
"use client";
-import { useModal } from "@/context";
import { Grid, Typography } from "@mui/material";
import { useTranslations } from "next-intl";
-export const ConstitutionSidebar = ({ tableOfContents, metadata }) => {
- const { openModal } = useModal();
- // const { userSession } = useAppContext();
- // const [tab, setTab] = useState("contents");
+export const ContentsSidebar = ({ tableOfContents }) => {
const t = useTranslations("Constitution");
- // const onCompare = (
- // target: Omit
- // ) => {
- // openModal({
- // type: "compareConstitutionModal",
- // state: {
- // base: metadata[0],
- // target
- // }
- // });
- // };
return (
<>
- {/*
- setTab(tab.value)}
- tabs={CONSTITUTION_SIDEBAR_TABS}
- selectedValue={tab}
- sx={{ fontSize: { xxs: 16 } }}
- />
- */}
{t("drawer.tableOfContents")}
-
- {/* {tab === "revisions" ? (
-
- {metadata ? (
- metadata.map(({ title, created_date, cid, blake2b, url }) => {
- return (
- {
- metadata[0].cid === cid
- ? null
- : onCompare({ title, created_date, cid });
- }}
- hash={blake2b}
- title={title}
- description={created_date}
- buttonLabel={metadata[0].cid !== cid && t("drawer.compare")}
- isActiveLabel={
- metadata[0].cid === cid && t("drawer.latest")
- }
- url={
- userSession &&
- isAnyAdminRole(userSession?.role) &&
- userSession?.permissions.includes(
- "add_constitution_version"
- )
- ? url
- : null
- }
- key={cid}
- />
- );
- })
- ) : (
-
- )}
-
- ) : ( */}
+
{tableOfContents}
- {/* )} */}
>
);
diff --git a/frontend/src/components/organisms/Constitution/ContentsSidebar.tsx b/frontend/src/components/organisms/Constitution/ContentsSidebar.tsx
new file mode 100644
index 00000000..1bc9c261
--- /dev/null
+++ b/frontend/src/components/organisms/Constitution/ContentsSidebar.tsx
@@ -0,0 +1,28 @@
+"use client";
+import { Grid, Typography } from "@mui/material";
+import { useTranslations } from "next-intl";
+
+export const ContentsSidebar = ({ tableOfContents }) => {
+ const t = useTranslations("Constitution");
+
+ return (
+ <>
+
+ {t("drawer.tableOfContents")}
+
+
+
+ {tableOfContents}
+
+
+ >
+ );
+};
diff --git a/frontend/src/components/organisms/Constitution/MDXComponents.tsx b/frontend/src/components/organisms/Constitution/MDXComponents.tsx
index 26759f37..0c29333c 100644
--- a/frontend/src/components/organisms/Constitution/MDXComponents.tsx
+++ b/frontend/src/components/organisms/Constitution/MDXComponents.tsx
@@ -1,10 +1,11 @@
import { Button, CopyButton, Typography } from "@atoms";
-import { customPalette, ICONS, orange } from "@consts";
-import { Card } from "@molecules";
-import { Box, Grid } from "@mui/material";
+import { customPalette, ICONS, PATHS } from "@consts";
+import { Box, Grid, IconButton } from "@mui/material";
import { getShortenedGovActionId } from "@utils";
+import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
+import { usePathname } from "next/navigation";
import { ReactNode } from "react";
const Anchor = ({ id, offset = "-20vh " }) => {
@@ -15,7 +16,7 @@ const Anchor = ({ id, offset = "-20vh " }) => {
display: "block",
position: "relative",
top: offset,
- visibility: "hidden"
+ visibility: "hidden",
}}
/>
);
@@ -35,7 +36,7 @@ export const Heading1 = ({ children, id }) => (
marginTop: "24px",
marginBottom: "16px",
lineHeight: "1.25em",
- fontSize: { xxs: 20, md: 32 }
+ fontSize: { xxs: 20, md: 32 },
}}
variant="headline4"
>
@@ -54,7 +55,7 @@ export const Heading2 = ({ children, id }) => (
fontWeight: 600,
fontSize: { xxs: 16, md: 20 },
- lineHeight: "1.25em"
+ lineHeight: "1.25em",
}}
>
{children}
@@ -71,7 +72,7 @@ export const Heading3 = ({ children, id }) => (
marginBottom: "16px",
fontWeight: 600,
fontSize: { xxs: 14, md: 18 },
- lineHeight: "1.25em"
+ lineHeight: "1.25em",
}}
>
{children}
@@ -88,7 +89,7 @@ export const Heading5 = ({ children }) => (
fontWeight: 800,
fontSize: { xxs: 12, md: 14 },
lineHeight: "1em",
- overflowWrap: "break-word"
+ overflowWrap: "break-word",
}}
>
{children}
@@ -102,7 +103,7 @@ export const Paragraph = ({ children, id }) => (
lineHeight: "1.5",
marginBottom: "16px",
fontSize: "14px",
- color: customPalette.textGray
+ color: customPalette.textGray,
}}
variant="caption"
>
@@ -121,7 +122,7 @@ export const ListItem = ({ children, id }) => (
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
- wordBreak: "break-all"
+ wordBreak: "break-all",
}}
>
{children}
@@ -137,7 +138,7 @@ export const Code = ({ children }) => (
margin: 0,
whiteSpace: "break-spaces",
backgroundColor: "#afb8c133",
- borderRadius: "6px"
+ borderRadius: "6px",
}}
>
{children}
@@ -149,7 +150,7 @@ export const TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS = {
borderRadius: "16px",
padding: "12px",
"& ol.toc-level": {
- margin: 0
+ margin: 0,
},
"& ol.toc-level-1": {
paddingInlineStart: "20px",
@@ -157,12 +158,12 @@ export const TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS = {
"& li": {
listStyle: "outside !important",
"& a.toc-link-h1": {
- fontWeight: 600
- }
- }
+ fontWeight: 600,
+ },
+ },
},
"& ol.toc-level-2": {
- margin: "10px 0px 10px 0px"
+ margin: "10px 0px 10px 0px",
},
"& li": {
width: "100%",
@@ -173,16 +174,40 @@ export const TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS = {
fontSize: "1rem",
fontWeight: 400,
lineHeight: "56px",
- color: customPalette.textBlack
- }
- }
+ color: customPalette.textBlack,
+ },
+ },
};
+export const DrawerNav = () => {
+ const t = useTranslations("Constitution");
+ const pathname = usePathname();
+ const linkPath = pathname.includes(PATHS.versionHistory)
+ ? PATHS.constitution
+ : PATHS.versionHistory;
+ const buttonLabel = pathname.includes(PATHS.versionHistory)
+ ? t("drawer.backToContents")
+ : t("drawer.versionHistory");
+ const buttonEndIcon = pathname.includes(PATHS.versionHistory)
+ ? ICONS.arrowLeft
+ : ICONS.documentSearch;
+ return (
+
+ {/* */}
+
+
+
+
+ );
+};
export const NavDrawerDesktop = ({
children,
left = 0,
top = { xxs: 75, md: 90 },
- dataTestId
+ dataTestId,
}: {
children: ReactNode;
left: number;
@@ -203,22 +228,23 @@ export const NavDrawerDesktop = ({
borderRadius: "16px",
height: { xxs: "95vh", md: "calc(100vh - 118px)" },
zIndex: 1,
- ...TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS
+ ...TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS,
}}
>
{children}
+
);
};
@@ -227,98 +253,104 @@ export const NavCard = ({
onClick,
title,
description,
- buttonLabel,
hash,
url,
- isActiveLabel
-}) => (
-
-
+ isActive,
+ isLatest,
+}) => {
+ return (
+
-
+
{title}
{description}
-
-
-
-
-
-
- {getShortenedGovActionId(hash)}
-
-
-
-
- {url && (
-
+
+
+
+
+
+ {getShortenedGovActionId(hash)}
+
+
+
+ {url && (
+
+
-
+
-
-
- )}
-
-
- {buttonLabel && (
-
- )}
- {isActiveLabel && (
-
- {isActiveLabel}
-
- )}
+
+
+ )}
+
+ {!isActive && !isLatest && (
+
+
+
+ )}
+ {isLatest && {isLatest}}
-
-
-);
+
+ );
+};
diff --git a/frontend/src/components/organisms/Constitution/TOCLink.tsx b/frontend/src/components/organisms/Constitution/TOCLink.tsx
index aaf274de..68f7993d 100644
--- a/frontend/src/components/organisms/Constitution/TOCLink.tsx
+++ b/frontend/src/components/organisms/Constitution/TOCLink.tsx
@@ -5,7 +5,6 @@ interface Props {
href: string;
children: React.ReactNode;
callback: () => void;
- disabled: boolean;
}
/**
* TOCLink Component
@@ -19,7 +18,7 @@ interface Props {
* @param {Function} props.callback - A callback function to be executed after the link is clicked.
*/
-const TOCLink = ({ href, children, callback, disabled }: Props) => {
+const TOCLink = ({ href, children, callback }: Props) => {
const [isActive, setIsActive] = useState(false);
const [isTruncated, setIsTruncated] = useState(false);
const linkRef = useRef(null);
@@ -89,8 +88,8 @@ const TOCLink = ({ href, children, callback, disabled }: Props) => {
display: "inline-block",
backgroundColor: isActive ? customPalette.accordionBg : undefined,
borderRadius: "30px",
- padding: "0 16px",
- boxSizing: "border-box"
+ padding: "0 8px",
+ boxSizing: "border-box",
}}
>
{children}
diff --git a/frontend/src/components/organisms/Constitution/VersionHistory.tsx b/frontend/src/components/organisms/Constitution/VersionHistory.tsx
new file mode 100644
index 00000000..a1d39f20
--- /dev/null
+++ b/frontend/src/components/organisms/Constitution/VersionHistory.tsx
@@ -0,0 +1,212 @@
+"use client";
+import { Loading } from "@/components/molecules";
+import { customPalette, IMAGES, poppins } from "@/constants";
+import { useSnackbar } from "@/context/snackbar";
+import { getConstitutionByCid } from "@/lib/api";
+import { useScreenDimension } from "@/lib/hooks";
+import { ContentWrapper, Typography } from "@atoms";
+import { Box, Grid, IconButton, Paper } from "@mui/material";
+import { isResponseErrorI } from "@utils";
+import { useTranslations } from "next-intl";
+import { useCallback, useEffect, useState } from "react";
+import ReactDiffViewer from "react-diff-viewer-continued";
+import { Footer } from "../Footer";
+import { DrawerMobile } from "../TopNavigation";
+import {
+ CompareConstitutionState,
+ ConstitutionMetadata,
+ TargetConstitutionState
+} from "../types";
+import { NavDrawerDesktop } from "./MDXComponents";
+import { VersionHistorySidebar } from "./VersionHistorySidebar";
+
+export function VersionHistory({ metadata }) {
+ const { screenWidth } = useScreenDimension();
+ const [isOpen, setIsOpen] = useState(false);
+ const isMobile = screenWidth < 1025;
+ const t = useTranslations("Modals");
+
+ const { addErrorAlert } = useSnackbar();
+ const [currentVersion, setCurrentVersion] = useState("");
+ const [targetVersion, setTargetVersion] = useState("");
+ const [compareState, setCompareState] = useState({
+ base: metadata[0],
+ target: metadata[1]
+ });
+
+ const { base, target } = compareState;
+
+ const TitleBlock = ({ title, created_date }: TargetConstitutionState) => (
+
+
+ {title}
+
+ {created_date}
+
+ );
+
+ useEffect(() => {
+ let ignore = false;
+ setCurrentVersion(null);
+ setTargetVersion(null);
+
+ async function fetchVersions({
+ base,
+ target
+ }: Pick) {
+ const currentVersion = await getConstitutionByCid(base.cid);
+ const targetVersion = await getConstitutionByCid(target.cid);
+ if (isResponseErrorI(currentVersion) || isResponseErrorI(targetVersion)) {
+ addErrorAlert(t("compareConstitution.alerts.error"));
+ } else {
+ if (!ignore) {
+ setCurrentVersion(currentVersion.contents);
+ setTargetVersion(targetVersion.contents);
+ }
+ }
+ }
+
+ if (base && target) {
+ fetchVersions({ base, target });
+ }
+ return () => {
+ ignore = true;
+ };
+ }, [base, target]);
+
+ const onCompare = useCallback(
+ (target: ConstitutionMetadata) => {
+ setCompareState({
+ base: metadata[0],
+ target
+ });
+ },
+ [metadata]
+ );
+
+ return (
+ <>
+ {isMobile ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+
+
+ {t("compareConstitution.headline")}
+
+ setIsOpen(true)}
+ sx={{
+ bgcolor: customPalette.arcticWhite,
+ display: { xxs: "flex", lg: "none" },
+ justifyContent: "center"
+ }}
+ >
+
+
+
+
+ {currentVersion && targetVersion ? (
+
+ }
+ rightTitle={}
+ styles={{
+ variables: {
+ light: {
+ diffViewerBackground: "white",
+ diffViewerTitleBackground: "white",
+ codeFoldBackground: "white",
+ emptyLineBackground: "white",
+ diffViewerColor: customPalette.textBlack
+ }
+ },
+ diffContainer: {
+ fontSize: "16px",
+ lineHeight: "1.6",
+ borderRadius: "16px"
+ },
+ contentText: {
+ fontFamily: poppins.style.fontFamily
+ }
+ }}
+ />
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/components/organisms/Constitution/VersionHistorySidebar.tsx b/frontend/src/components/organisms/Constitution/VersionHistorySidebar.tsx
new file mode 100644
index 00000000..b9fb272e
--- /dev/null
+++ b/frontend/src/components/organisms/Constitution/VersionHistorySidebar.tsx
@@ -0,0 +1,69 @@
+"use client";
+import { Typography } from "@/components/atoms";
+import { Grid } from "@mui/material";
+import { useTranslations } from "next-intl";
+import React from "react";
+import { NotFound } from "../NotFound";
+import { ConstitutionMetadata, TargetConstitutionState } from "../types";
+import { NavCard } from "./MDXComponents";
+
+export type VersionHistorySidebarProps = {
+ metadata?: Array;
+ onCompare: ({ title, created_date, cid }: TargetConstitutionState) => void;
+ ativeTarget?: string;
+};
+
+export const VersionHistorySidebar = React.memo(
+ ({ metadata, onCompare, ativeTarget }) => {
+ const t = useTranslations("Constitution");
+ return (
+ <>
+
+ {t("drawer.versionHistory")}
+
+
+ {metadata ? (
+ metadata.map(({ title, created_date, cid, blake2b, url }) => {
+ return (
+ {
+ metadata[0].cid === cid
+ ? null
+ : onCompare({ title, created_date, cid });
+ }}
+ hash={blake2b}
+ title={title}
+ description={created_date}
+ isActive={ativeTarget === cid}
+ isLatest={metadata[0].cid === cid && t("drawer.latest")}
+ url={url}
+ key={cid}
+ />
+ );
+ })
+ ) : (
+
+ )}
+
+ >
+ );
+ }
+);
diff --git a/frontend/src/components/organisms/Modals/AddReasoningModal.tsx b/frontend/src/components/organisms/Modals/AddReasoningModal.tsx
index ed31747e..cde6f427 100644
--- a/frontend/src/components/organisms/Modals/AddReasoningModal.tsx
+++ b/frontend/src/components/organisms/Modals/AddReasoningModal.tsx
@@ -1,23 +1,23 @@
"use client";
-import React, { useState } from "react";
+import { useSnackbar } from "@/context/snackbar";
+import { addOrUpdateReasoning } from "@/lib/api";
+import { ReasoningResponseI } from "@/lib/requests";
import {
- ModalWrapper,
- ModalHeader,
- ModalContents,
ModalActions,
- Typography,
+ ModalContents,
+ ModalHeader,
+ ModalWrapper,
+ Typography
} from "@atoms";
-import { useForm } from "react-hook-form";
import { customPalette, IMAGES } from "@consts";
-import { useTranslations } from "next-intl";
-import { ControlledField } from "@organisms";
import { useModal } from "@context";
+import { ControlledField } from "@organisms";
+import { isResponseErrorI } from "@utils";
+import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
-import { useSnackbar } from "@/context/snackbar";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
import { OpenAddReasoningModalState } from "../types";
-import { addOrUpdateReasoning } from "@/lib/api";
-import { ReasoningResponseI } from "@/lib/requests";
-import { isResponseErrorI } from "@utils";
interface AddReasoningFormData {
title: string;
@@ -26,7 +26,7 @@ interface AddReasoningFormData {
export const AddReasoningModal = () => {
const t = useTranslations("Modals");
const {
- state: { callback, id },
+ state: { callback, id }
} = useModal();
const router = useRouter();
const { addSuccessAlert, addErrorAlert } = useSnackbar();
@@ -36,7 +36,7 @@ export const AddReasoningModal = () => {
register,
handleSubmit,
formState: { errors },
- control,
+ control
} = useForm();
const onSubmit = async (data: AddReasoningFormData) => {
@@ -69,18 +69,21 @@ export const AddReasoningModal = () => {
>
{t("addRationale.description")}
-
diff --git a/frontend/src/components/organisms/Modals/CompareConstitutionModal.tsx b/frontend/src/components/organisms/Modals/CompareConstitutionModal.tsx
deleted file mode 100644
index aa160be4..00000000
--- a/frontend/src/components/organisms/Modals/CompareConstitutionModal.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { getConstitutionByCid } from "@/lib/api";
-import { useModal } from "@/context";
-import { CompareConstitutionModalState, ConstitutionMetadata } from "../types";
-import {
- ModalWrapper,
- ModalHeader,
- ModalContents,
- Button,
- Typography,
-} from "@atoms";
-import { customPalette, IMAGES, poppins } from "@consts";
-import { useTranslations } from "next-intl";
-import ReactDiffViewer from "react-diff-viewer-continued";
-import { Card, Loading } from "@molecules";
-import { Box } from "@mui/material";
-import { isResponseErrorI } from "@utils";
-import { useSnackbar } from "@/context/snackbar";
-
-export const CompareConstitutionModal = () => {
- const t = useTranslations("Modals");
- const {
- state: { base, target },
- closeModal,
- } = useModal();
- const { addErrorAlert } = useSnackbar();
- const [currentVersion, setCurrentVersion] = useState("");
- const [targetVersion, setTargetVersion] = useState("");
-
- useEffect(() => {
- async function fetchVersions({
- base,
- target,
- }: Pick) {
- const currentVersion = await getConstitutionByCid(base.cid);
- const targetVersion = await getConstitutionByCid(target.cid);
- if (isResponseErrorI(currentVersion) || isResponseErrorI(targetVersion)) {
- closeModal();
- addErrorAlert(t("compareConstitution.alerts.error"));
- } else {
- setCurrentVersion(currentVersion.contents);
- setTargetVersion(targetVersion.contents);
- }
- }
-
- if (base && target) {
- fetchVersions({ base, target });
- }
- }, [base, target]);
-
- const TitleBlock = ({ title, created_date }: ConstitutionMetadata) => (
-
-
- {title}
-
- {created_date}
-
- );
-
- return (
-
- {t("compareConstitution.headline")}
-
- {currentVersion && targetVersion ? (
-
- }
- rightTitle={}
- styles={{
- variables: {
- light: {
- diffViewerBackground: "white",
- diffViewerTitleBackground: "white",
- codeFoldBackground: "white",
- emptyLineBackground: "white",
- diffViewerColor: customPalette.textBlack,
- },
- },
- diffContainer: {
- fontSize: "16px",
- lineHeight: "1.6",
- borderRadius: "4px",
- },
- contentText: {
- fontFamily: poppins.style.fontFamily,
- },
- }}
- />
-
- ) : (
-
- )}
-
-
-
- );
-};
diff --git a/frontend/src/components/organisms/Modals/GovActionModal.tsx b/frontend/src/components/organisms/Modals/GovActionModal.tsx
index f6ae2723..97b92d4b 100644
--- a/frontend/src/components/organisms/Modals/GovActionModal.tsx
+++ b/frontend/src/components/organisms/Modals/GovActionModal.tsx
@@ -7,7 +7,7 @@ import {
ModalHeader,
ModalWrapper,
OutlinedLightButton,
- Typography
+ Typography,
} from "@atoms";
import { customPalette, IMAGES } from "@consts";
import { useModal } from "@context";
@@ -19,7 +19,7 @@ import {
formatDisplayDate,
getProposalTypeLabel,
getShortenedGovActionId,
- isResponseErrorI
+ isResponseErrorI,
} from "@utils";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
diff --git a/frontend/src/components/organisms/Modals/PreviewReasoningModal.tsx b/frontend/src/components/organisms/Modals/PreviewReasoningModal.tsx
index 53937966..cffbbfbb 100644
--- a/frontend/src/components/organisms/Modals/PreviewReasoningModal.tsx
+++ b/frontend/src/components/organisms/Modals/PreviewReasoningModal.tsx
@@ -9,7 +9,7 @@ import {
ModalWrapper,
OutlinedLightButton,
Typography,
- VotePill
+ VotePill,
} from "@atoms";
import { IMAGES } from "@consts";
import { useModal } from "@context";
@@ -19,7 +19,7 @@ import {
formatDisplayDate,
getProposalTypeLabel,
getShortenedGovActionId,
- isResponseErrorI
+ isResponseErrorI,
} from "@utils";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
@@ -29,7 +29,7 @@ export const PreviewReasoningModal = () => {
const t = useTranslations("Modals");
const {
closeModal,
- state: { govAction, onActionClick, actionTitle }
+ state: { govAction, onActionClick, actionTitle },
} = useModal();
const onClose = () => {
closeModal();
@@ -74,7 +74,7 @@ export const PreviewReasoningModal = () => {
sx={{
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
- backgroundColor: "rgba(255, 255, 255, 0.3)"
+ backgroundColor: "rgba(255, 255, 255, 0.3)",
}}
pt={2}
pb={2}
@@ -208,20 +208,13 @@ export const PreviewReasoningModal = () => {
)}
-
+
{onActionClick && (
diff --git a/frontend/src/components/organisms/types.ts b/frontend/src/components/organisms/types.ts
index 53a6beeb..2908b494 100644
--- a/frontend/src/components/organisms/types.ts
+++ b/frontend/src/components/organisms/types.ts
@@ -45,9 +45,14 @@ export interface SignupModalState {
description?: string;
}
-export interface CompareConstitutionModalState {
+export type TargetConstitutionState = Pick<
+ ConstitutionMetadata,
+ "title" | "created_date" | "cid"
+>;
+
+export interface CompareConstitutionState {
base: ConstitutionMetadata;
- target: ConstitutionMetadata;
+ target: TargetConstitutionState;
}
export interface SignOutModalState {
diff --git a/frontend/src/constants/icons.ts b/frontend/src/constants/icons.ts
index 1736aab2..68172736 100644
--- a/frontend/src/constants/icons.ts
+++ b/frontend/src/constants/icons.ts
@@ -22,4 +22,6 @@ export const ICONS = {
sortWhite: "/icons/SortWhite.svg",
govAction: "/icons/GovAction.svg",
externalLink: "/icons/ExternalLink.svg",
+ eye: "/icons/Eye.svg",
+ documentSearch: "/icons/DocumentSearch.svg"
};
diff --git a/frontend/src/constants/paths.ts b/frontend/src/constants/paths.ts
index 1a7777c3..53cc18c1 100644
--- a/frontend/src/constants/paths.ts
+++ b/frontend/src/constants/paths.ts
@@ -1,6 +1,7 @@
export const PATHS = {
home: "/",
constitution: "/interim-constitution",
+ versionHistory: "/interim-constitution/version-history",
members: "/members",
latestUpdates: "/latest-updates",
myActions: "/my-actions",
diff --git a/frontend/src/context/modal.tsx b/frontend/src/context/modal.tsx
index 9b29b39d..359cadd4 100644
--- a/frontend/src/context/modal.tsx
+++ b/frontend/src/context/modal.tsx
@@ -1,7 +1,6 @@
"use client";
import { createContext, useContext, useMemo, useReducer } from "react";
-import { CompareConstitutionModal } from "@/components/organisms/Modals/CompareConstitutionModal";
import { DeleteUser } from "@/components/organisms/Modals/DeleteUser";
import { GovActionModal } from "@/components/organisms/Modals/GovActionModal";
import { PreviewReasoningModal } from "@/components/organisms/Modals/PreviewReasoningModal";
@@ -15,7 +14,7 @@ import {
DeleteRole,
ReasoningLinkModal,
SignInModal,
- UploadConstitution
+ UploadConstitution,
} from "@organisms";
import { basicReducer, BasicReducer, callAll } from "@utils";
@@ -39,7 +38,6 @@ export type ModalType =
| "uploadConstitution"
| "deleteRole"
| "deleteUser"
- | "compareConstitutionModal"
| "addReasoningModal"
| "reasoningLinkModal"
| "previewReasoningModal"
@@ -48,48 +46,46 @@ export type ModalType =
const modals: Record = {
none: {
- component: null
+ component: null,
},
signIn: {
- component:
+ component: ,
},
signUpModal: {
component: ,
- preventDismiss: true
+ preventDismiss: true,
},
signOutModal: {
- component:
+ component: ,
},
addMember: {
- component:
+ component: ,
},
uploadConstitution: {
- component:
+ component: ,
},
deleteRole: {
- component:
+ component: ,
},
deleteUser: {
- component:
- },
- compareConstitutionModal: {
- component:
+ component: ,
},
+
addReasoningModal: {
- component:
+ component: ,
},
reasoningLinkModal: {
- component:
+ component: ,
},
previewReasoningModal: {
- component:
+ component: ,
},
govActionModal: {
- component:
+ component: ,
},
switchUserStatus: {
- component:
- }
+ component: ,
+ },
};
type Optional = Pick, K> & Omit;
@@ -116,7 +112,7 @@ function ModalProvider(props: ProviderProps) {
basicReducer,
{
state: null,
- type: "none"
+ type: "none",
}
);
@@ -128,7 +124,7 @@ function ModalProvider(props: ProviderProps) {
openModal,
closeModal: callAll(modals[modal.type]?.onClose, () =>
openModal({ type: "none", state: null })
- )
+ ),
}),
[modal, openModal]
);
diff --git a/frontend/src/lib/utils/routes.ts b/frontend/src/lib/utils/routes.ts
index 896cfa8f..28260951 100644
--- a/frontend/src/lib/utils/routes.ts
+++ b/frontend/src/lib/utils/routes.ts
@@ -1,7 +1,7 @@
import {
adminProtectedPath,
PATHS,
- userProtectedPaths,
+ userProtectedPaths
} from "@/constants/paths";
import { NextRequest } from "next/server";
import { UserRole } from "../requests";
@@ -54,7 +54,7 @@ export const isUserProtectedRoute = (req: NextRequest): boolean =>
export const getRoleBasedHomeRedirectURL = (role: UserRole): string => {
let redirectUrl = PATHS.home;
if (isAnyAdminRole(role)) {
- redirectUrl = PATHS.admin.home;
+ redirectUrl = PATHS.home;
}
return redirectUrl;
};
diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts
index c89d2f0f..fc77f545 100644
--- a/frontend/src/middleware.ts
+++ b/frontend/src/middleware.ts
@@ -1,20 +1,20 @@
// Import createMiddleware from next-intl to configure internationalization middleware for Next.js.
-import createMiddleware from "next-intl/middleware";
-import { NextRequest, NextResponse } from "next/server";
import { defaultLocale, locales, PATHS } from "@consts";
-import { isTokenExpired, refreshToken, decodeUserToken } from "./lib/api";
-import * as cookie from "cookie";
import {
+ getAuthCookies,
isAdminProtectedRoute,
isAnyAdminRole,
- isUserProtectedRoute,
- getAuthCookies,
+ isUserProtectedRoute
} from "@utils";
+import * as cookie from "cookie";
+import createMiddleware from "next-intl/middleware";
+import { NextRequest, NextResponse } from "next/server";
+import { decodeUserToken, isTokenExpired, refreshToken } from "./lib/api";
// Export the middleware configuration to define supported locales and the default locale.
const intlMiddleware = createMiddleware({
locales: locales, // Specify the supported locales for the application.
- defaultLocale: defaultLocale, // Set the default locale to be used when no other locale matches.
+ defaultLocale: defaultLocale // Set the default locale to be used when no other locale matches.
});
// Define and export a config object to specify which paths the middleware should apply to.
@@ -33,8 +33,8 @@ export const config = {
"/(de|en)/:path*",
"/((?!api|_next|_vercel|.*\\..*).*)",
// However, match all pathnames within `/users`, optionally with a locale prefix
- "/([\\w-]+)?/users/(.+)",
- ], // Apply middleware to the root path and any path prefixed with supported locales.
+ "/([\\w-]+)?/users/(.+)"
+ ] // Apply middleware to the root path and any path prefixed with supported locales.
};
export async function middleware(req: NextRequest) {
@@ -76,8 +76,8 @@ export async function middleware(req: NextRequest) {
// Return updated response with new token
const response = NextResponse.next({
request: {
- headers: newRequestHeaders,
- },
+ headers: newRequestHeaders
+ }
});
response.cookies.set("token", newToken?.access_token);
response.cookies.set("refresh_token", newToken?.refresh_token);
@@ -101,7 +101,7 @@ export async function middleware(req: NextRequest) {
return NextResponse.redirect(new URL(PATHS.home, req.url));
}
// If no access token is found, redirect to the admin home page
- return NextResponse.redirect(new URL(PATHS.admin.home, req.url));
+ return NextResponse.redirect(new URL(PATHS.home, req.url));
}
// Check if the route requires user role