diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 8ec029f8..82d2d9f9 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -123,7 +123,7 @@ jobs: NEXT_PUBLIC_USERSNAP_PROJECT_API_KEY=${{ secrets.QA_NEXT_PUBLIC_USERSNAP_PROJECT_API_KEY }} NEXT_PUBLIC_HIDDEN_USERSNAP_PROJECT_IDS=${{ secrets.QA_NEXT_PUBLIC_HIDDEN_USERSNAP_PROJECT_IDS }} NEXT_PUBLIC_IS_MAINNET=${{ secrets.QA_NEXT_PUBLIC_IS_MAINNET }} - + - name: Scan Docker image with Dockle id: dockle run: | diff --git a/backend/migrations/1731932955459-limit-users-name-field.ts b/backend/migrations/1731932955459-limit-users-name-field.ts new file mode 100644 index 00000000..ebb5844e --- /dev/null +++ b/backend/migrations/1731932955459-limit-users-name-field.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class LimitUsersNameField1731932955459 implements MigrationInterface { + name = 'LimitUsersNameField1731932955459'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "name" TYPE varchar(64)`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "name" TYPE varchar(50)`, + ); + } +} diff --git a/backend/src/governance/api/governance.controller.ts b/backend/src/governance/api/governance.controller.ts index bf74c9dc..c7da5a75 100644 --- a/backend/src/governance/api/governance.controller.ts +++ b/backend/src/governance/api/governance.controller.ts @@ -133,8 +133,8 @@ export class GovernanceController { description: 'Unauthorized', }) @ApiResponse({ - status: 409, - description: 'Rationale already exists for this user', + status: 400, + description: 'Bad Request', }) @UseGuards(JwtAuthGuard, UserPathGuard) @Post('users/:id/proposals/:proposalId/rationale') diff --git a/backend/src/governance/facade/governance.facade.spec.ts b/backend/src/governance/facade/governance.facade.spec.ts index 5acc4595..3b5003ec 100644 --- a/backend/src/governance/facade/governance.facade.spec.ts +++ b/backend/src/governance/facade/governance.facade.spec.ts @@ -119,22 +119,21 @@ describe('GovernanceFacade', () => { ]; const mockRationaleRequest: RationaleRequest = { - title: 'Rationale title', - content: 'Rationale content', + summary: 'Rationale title', + rationaleStatement: 'Rationale content', }; const mockRationaleJson = { govActionProposalHash: mockGovActionProposalDtos[0].txHash, - title: mockRationaleRequest.title, - content: mockRationaleRequest.content, + title: mockRationaleRequest.summary, + content: mockRationaleRequest.rationaleStatement, }; const mockIpfsContentDto: IpfsContentDto = { blake2b: 'blake2b_example', cid: 'cid_example', url: 'ipfs_url_example', - title: mockRationaleRequest.title, - contents: mockRationaleRequest.content, + contents: mockRationaleRequest.rationaleStatement, }; const mockGovActionProposals: GovActionProposal[] = [ @@ -215,8 +214,8 @@ describe('GovernanceFacade', () => { { userId: mockUsers[0].id, govActionProposalId: mockGovActionProposals[0].id, - title: 'Rationale title 1', - content: 'Rationale content 1', + summary: 'Rationale title 1', + rationaleStatement: 'Rationale content 1', cid: 'cid1', blake2b: 'blake2b1', url: 'http://test.com', @@ -225,8 +224,8 @@ describe('GovernanceFacade', () => { { userId: mockUsers[0].id, govActionProposalId: mockGovActionProposals[1].id, - title: 'Rationale title 2', - content: 'Rationale content 2', + summary: 'Rationale title 2', + rationaleStatement: 'Rationale content 2', cid: 'cid2', blake2b: 'blake2b2', url: 'http://test.com', diff --git a/backend/src/governance/services/governance.service.spec.ts b/backend/src/governance/services/governance.service.spec.ts index 4138b0d1..76f630b5 100644 --- a/backend/src/governance/services/governance.service.spec.ts +++ b/backend/src/governance/services/governance.service.spec.ts @@ -16,6 +16,7 @@ import { VoteValue } from '../enums/vote-value.enum'; import { User } from 'src/users/entities/user.entity'; import { UserStatusEnum } from 'src/users/enums/user-status.enum'; import { RoleEnum } from 'src/users/enums/role.enum'; +import { RationaleDto } from '../dto/rationale.dto'; describe('IpfsService', () => { let service: GovernanceService; @@ -53,11 +54,11 @@ describe('IpfsService', () => { }, ]; - const mockRationaleDto = { + const mockRationaleDto: RationaleDto = { userId: 'mockedId', govActionProposalId: '1', - title: 'Rationale title', - content: 'Rationale content', + summary: 'Rationale title', + rationaleStatement: 'Rationale content', cid: 'rationalecid', blake2b: 'rationaleblake2b', url: 'rationaleurl', diff --git a/backend/src/users/api/request/update-user.request.ts b/backend/src/users/api/request/update-user.request.ts index a9cc6b81..f70d1c4f 100644 --- a/backend/src/users/api/request/update-user.request.ts +++ b/backend/src/users/api/request/update-user.request.ts @@ -14,7 +14,7 @@ export class UpdateUserRequest { example: 'John Doe', }) @MinLength(2, { message: `Minimum character length is 2` }) - @MaxLength(30, { message: `Maximum character length is 30` }) + @MaxLength(64, { message: `Maximum character length is 64` }) @Matches(/^[a-zA-Z0-9_|.\s]+$/, { message: `Name can't contain special characters & symbols`, }) diff --git a/backend/src/users/entities/user.entity.ts b/backend/src/users/entities/user.entity.ts index 6cbecf6d..1d345d6c 100644 --- a/backend/src/users/entities/user.entity.ts +++ b/backend/src/users/entities/user.entity.ts @@ -25,7 +25,7 @@ export class User extends CommonEntity { @Column({ name: 'name', type: 'varchar', - length: 50, + length: 64, nullable: true, }) name: string; diff --git a/frontend/messages/de.json b/frontend/messages/de.json index b068c1a6..4e4d1360 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -5,6 +5,8 @@ "headline": "Wir setzen uns für die von der Community geführte Governance von Cardano ein", "description": "Bis eine endgültige Cardano-Verfassung entwickelt und von der Community ratifiziert wurde – ein Konsultationsprogramm, das das ganze Jahr 2024 über läuft – ist eine Reihe von Übergangsregeln erforderlich, um den Übergang zu unterstützen.", "seeConstitution": "Lesen Sie die Interimsverfassung", + "guardrailsScript": "Guardrails Script", + "guardrailsRationale": "Guardrails Rationale", "signIn": "Anmelden", "constitutionalCommitteePortal": "Portal des Verfassungsausschusses" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index b4a9f141..7e3c9b36 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -5,6 +5,8 @@ "headline": "Championing Cardano's community-led governance", "description": "Until a final Cardano Constitution has been developed and ratified by the community - a programme of consultation taking place throughout 2024 - a set of interim rules is required to support the transition.", "seeConstitution": "Read the Interim Constitution", + "guardrailsScript": "Guardrails Script", + "guardrailsRationale": "Guardrails Rationale", "signIn": "Sign In", "constitutionalCommitteePortal": "Constitutional Committee Portal" } diff --git a/frontend/src/components/atoms/UploadFileButton.tsx b/frontend/src/components/atoms/UploadFileButton.tsx index 9d105600..827f4b78 100644 --- a/frontend/src/components/atoms/UploadFileButton.tsx +++ b/frontend/src/components/atoms/UploadFileButton.tsx @@ -1,6 +1,7 @@ "use client"; import { Button } from "@atoms"; import { ICONS } from "@consts"; +import { Box } from "@mui/material"; import { useState } from "react"; import { FormErrorMessage } from "./FormErrorMessage"; import { ButtonProps, FormErrorMessageProps } from "./types"; @@ -43,14 +44,24 @@ export const UploadFileButton = ({ onChange={fileChange} data-testid={`${dataTestId}-input`} /> - + + + ); diff --git a/frontend/src/components/molecules/UserCard/UserBasicInfo.tsx b/frontend/src/components/molecules/UserCard/UserBasicInfo.tsx index febeef44..410f484c 100644 --- a/frontend/src/components/molecules/UserCard/UserBasicInfo.tsx +++ b/frontend/src/components/molecules/UserCard/UserBasicInfo.tsx @@ -1,15 +1,15 @@ +import { Tooltip, Typography } from "@atoms"; import { IMAGES } from "@consts"; -import { Typography, Tooltip } from "@atoms"; -import { Grid } from "@mui/material"; +import { Box, Grid } from "@mui/material"; import { getShortenedGovActionId, truncateText } from "@utils"; -import { CopyPill } from "../CopyPill"; import ConditionalWrapper from "../ConditionalWrapper"; +import { CopyPill } from "../CopyPill"; export const UserBasicInfo = ({ name, email, hotAddress, - maxWidth = 300, + maxWidth = 300 }: { name: string; email?: string; @@ -22,14 +22,17 @@ export const UserBasicInfo = ({ paddingRight: 3, width: { xxs: "auto", md: maxWidth }, height: "100%", + textAlign: { xxs: "center", sm: "left" } }} px={{ xxs: 1, lg: 3 }} + display="flex" + flexDirection="column" > )} {hotAddress && ( - + + + )} ); diff --git a/frontend/src/components/molecules/UserProfileButton.tsx b/frontend/src/components/molecules/UserProfileButton.tsx index 80514c43..c93fa7c5 100644 --- a/frontend/src/components/molecules/UserProfileButton.tsx +++ b/frontend/src/components/molecules/UserProfileButton.tsx @@ -1,16 +1,16 @@ "use client"; -import * as React from "react"; -import Menu from "@mui/material/Menu"; +import { FetchUserData } from "@/lib/requests"; import { ICONS, IMAGES, PATHS } from "@consts"; -import { Button } from "../atoms"; import { useModal } from "@context"; -import { FetchUserData } from "@/lib/requests"; -import { useTranslations } from "next-intl"; -import { Grid, Hidden } from "@mui/material"; import { UserAvatar } from "@molecules"; +import { Grid, Hidden, Tooltip } from "@mui/material"; +import Menu from "@mui/material/Menu"; +import { useTranslations } from "next-intl"; +import * as React from "react"; +import { Button } from "../atoms"; export default function UserProfileButton({ - user, + user }: { user: Pick; }) { @@ -31,8 +31,8 @@ export default function UserProfileButton({ type: "signUpModal", state: { showCloseButton: true, - title: t("Modals.editProfile.headline"), - }, + title: t("Modals.editProfile.headline") + } }); handleClose(); }; @@ -41,11 +41,12 @@ export default function UserProfileButton({ openModal({ type: "signOutModal", state: { - homeRedirectionPath: PATHS.home, - }, + homeRedirectionPath: PATHS.home + } }); }; + const USERNAME_MAX_LENGTH = 32; return ( <> @@ -67,7 +68,18 @@ export default function UserProfileButton({ endIcon={} dataTestId="user-profile-menu-button" > - {user?.name || "User"} + USERNAME_MAX_LENGTH ? user.name : ""} + placement="top" + disableFocusListener + disableTouchListener + > + + {user?.name?.length > USERNAME_MAX_LENGTH + ? `${user.name.slice(0, USERNAME_MAX_LENGTH)}...` + : user?.name || "User"} + + { @@ -55,12 +55,7 @@ export function Constitution({ constitution, metadata }: ConstitutionProps) { ) : ( - setIsOpen(!isOpen)} - top={{ xxs: 0, md: 85 }} - left={0} - > + ), @@ -97,27 +92,25 @@ export function Constitution({ constitution, metadata }: ConstitutionProps) { data-testid="constitution-page-wrapper" container position="relative" - justifyContent={{ xxs: "flex-start" }} + justifyContent={{ xxs: "center", lg: "flex-start" }} flex={1} > - {/* fake elemnt to push content to the right since tos is fixed on left */} - -   - - - + {t("title")} @@ -126,7 +119,7 @@ export function Constitution({ constitution, metadata }: ConstitutionProps) { onClick={() => setIsOpen(true)} sx={{ bgcolor: customPalette.arcticWhite, - display: { xxs: "flex", md: "none" }, + display: { xxs: "flex", lg: "none" }, justifyContent: "center" }} > diff --git a/frontend/src/components/organisms/Constitution/MDXComponents.tsx b/frontend/src/components/organisms/Constitution/MDXComponents.tsx index e7304e57..26759f37 100644 --- a/frontend/src/components/organisms/Constitution/MDXComponents.tsx +++ b/frontend/src/components/organisms/Constitution/MDXComponents.tsx @@ -180,15 +180,11 @@ export const TABLE_OF_CONTENTS_WRAPPER_STYLE_PROPS = { export const NavDrawerDesktop = ({ children, - onClick, - isOpen, left = 0, top = { xxs: 75, md: 90 }, dataTestId }: { children: ReactNode; - onClick: () => void; - isOpen: boolean; left: number; top: { xxs: number; md: number }; dataTestId?: string; @@ -196,7 +192,7 @@ export const NavDrawerDesktop = ({ return ( - {/* TODO remove whole logic around this arrow or redesign it */} - {/* - - */} - {isOpen && <>{children}} + {children} ); diff --git a/frontend/src/components/organisms/Footer/AdminFooter.tsx b/frontend/src/components/organisms/Footer/AdminFooter.tsx index f9ffb1c1..fa7a9163 100644 --- a/frontend/src/components/organisms/Footer/AdminFooter.tsx +++ b/frontend/src/components/organisms/Footer/AdminFooter.tsx @@ -32,7 +32,7 @@ export const AdminFooter = () => { openModal({ type: "signOutModal", state: { - homeRedirectionPath: PATHS.admin.home + homeRedirectionPath: PATHS.home } }) } diff --git a/frontend/src/components/organisms/Footer/Footer.tsx b/frontend/src/components/organisms/Footer/Footer.tsx index cf22f562..f3c47fbf 100644 --- a/frontend/src/components/organisms/Footer/Footer.tsx +++ b/frontend/src/components/organisms/Footer/Footer.tsx @@ -67,7 +67,7 @@ export const Footer = ({ data-testid="footer-privacy-policy-hyperlink" > + {isAmdmin ? ( <> @@ -21,7 +27,7 @@ export function HeroActions({ role }: HeroActionsProps) { startIcon={} onClick={() => { openModal({ - type: "signIn", + type: "signIn" }); }} data-testid="admin-hero-sign-in-button" @@ -43,7 +49,12 @@ export function HeroActions({ role }: HeroActionsProps) { ) : ( - + + {/** * temporarily hidden diff --git a/frontend/src/components/organisms/Modals/SignUpModal.tsx b/frontend/src/components/organisms/Modals/SignUpModal.tsx index 75728b85..48444e68 100644 --- a/frontend/src/components/organisms/Modals/SignUpModal.tsx +++ b/frontend/src/components/organisms/Modals/SignUpModal.tsx @@ -100,6 +100,10 @@ export const SignUpModal = () => { errors={errors} control={control} {...register("name", { + maxLength: { + value: 64, + message: "Display name cannot exceed 64 characters" + }, pattern: { value: PATTERNS.username, message: diff --git a/frontend/src/components/organisms/PageTitleTabs.tsx b/frontend/src/components/organisms/PageTitleTabs.tsx index b3d2da13..8cc31fc0 100644 --- a/frontend/src/components/organisms/PageTitleTabs.tsx +++ b/frontend/src/components/organisms/PageTitleTabs.tsx @@ -9,7 +9,7 @@ export const PageTitleTabs = ({ tabs, onChange, selectedValue, - sx, + sx }: { tabs: TabI[]; onChange: (newValue: TabI) => void; @@ -26,8 +26,8 @@ export const PageTitleTabs = ({ sx={{ mb: { xxs: 1, md: 0 } }} value={selectedIndex} onChange={handleChange} - textColor="secondary" - indicatorColor="secondary" + textColor="inherit" + indicatorColor="primary" > {tabs.map((tab) => ( diff --git a/frontend/src/components/organisms/VotesTable/VotesTable.tsx b/frontend/src/components/organisms/VotesTable/VotesTable.tsx index f65733b6..dbe6f0ab 100644 --- a/frontend/src/components/organisms/VotesTable/VotesTable.tsx +++ b/frontend/src/components/organisms/VotesTable/VotesTable.tsx @@ -1,7 +1,6 @@ -import React from "react"; +import { VotesTableI } from "@/lib/requests"; import { Grid } from "@mui/material"; import { VotesTableRow } from "./VotesTableRow"; -import { VotesTableI } from "@/lib/requests"; interface Props { votes: VotesTableI[]; @@ -14,26 +13,22 @@ export const VotesTable = ({ votes, onActionClick, actionTitle, - isDisabled, + isDisabled }: Props) => { return ( - + {votes && votes.map((data, index) => { const disabled = isDisabled && isDisabled(data); return ( - - - + key={index} + /> ); })} diff --git a/frontend/src/components/organisms/VotesTable/VotesTableRow.tsx b/frontend/src/components/organisms/VotesTable/VotesTableRow.tsx index 3d154229..6fffb2d5 100644 --- a/frontend/src/components/organisms/VotesTable/VotesTableRow.tsx +++ b/frontend/src/components/organisms/VotesTable/VotesTableRow.tsx @@ -4,7 +4,7 @@ import { VotesTableI } from "@/lib/requests"; import { Button, OutlinedLightButton, Typography, VotePill } from "@atoms"; import { customPalette } from "@consts"; import { Card, TableDivider, UserAvatar, UserBasicInfo } from "@molecules"; -import { Box, Grid } from "@mui/material"; +import { Box, Grid, Stack } from "@mui/material"; import { formatDisplayDate, getProposalTypeLabel, truncateText } from "@utils"; import { useTranslations } from "next-intl"; @@ -34,201 +34,184 @@ export const VotesTableRow = ({ } = votes; return ( - - - - - - - - - - - - - - + + + + + + + + - + + + + - - - - - {t("govAction")} - - - - {gov_action_proposal_title - ? truncateText(gov_action_proposal_title, 40) - : t("notAvailable")} - - - - - - - - {t("govActionCategoryShort")} - - - {getProposalTypeLabel(gov_action_proposal_type)} - - - - - - - - {t("voted")} - + {t("govAction")} + - - - - - - - - - - {t("time")} - + + {gov_action_proposal_title + ? truncateText(gov_action_proposal_title, 40) + : t("notAvailable")} + + + + + + + + {t("govActionCategoryShort")} + + + {getProposalTypeLabel(gov_action_proposal_type)} + + + + + + + + {t("voted")} + - - - {formatDisplayDate(vote_submit_time)} - - - - - - + + + + + + + + + + {t("time")} + - - - {t("rationale")} - - - {rationale_url ? t("available") : t("notAvailable")} - - - - - - - + + + {formatDisplayDate(vote_submit_time)} + + - - + + + + + {t("rationale")} + + + {rationale_url ? t("available") : t("notAvailable")} + + + + + + + + + + ); }; \ No newline at end of file diff --git a/frontend/src/constants/paths.ts b/frontend/src/constants/paths.ts index 1c1a12d6..1a7777c3 100644 --- a/frontend/src/constants/paths.ts +++ b/frontend/src/constants/paths.ts @@ -14,7 +14,15 @@ export const PATHS = { export const EXTERNAL_LINKS = { guides: - "https://docs.gov.tools/about/what-is-the-constitutional-committee-portal" + "https://docs.gov.tools/about/what-is-the-constitutional-committee-portal", + termsOfUse: + "https://docs.intersectmbo.org/legal/policies-and-conditions/terms-of-use", + privacyPolicy: + "https://docs.intersectmbo.org/legal/policies-and-conditions/privacy-policy", + guardrailsScript: + "https://github.com/IntersectMBO/plutus/tree/master/cardano-constitution", + guardrailsRationale: + "https://docs.google.com/document/d/1FDVnDwugtA5RlgH8a-_8pWL_W-VGvMYA" }; export const adminProtectedPath = PATHS.admin.dashboard;