Skip to content

Commit ccfacff

Browse files
authored
Merge pull request #3413 from IntersectMBO/fix/3246-usersnap-im-unable-to-download-the-file-jsonid-the-button-is-not-working
fix(#3246): missing image sha generation validation
2 parents a0bf75a + 9da4d90 commit ccfacff

File tree

9 files changed

+145
-47
lines changed

9 files changed

+145
-47
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ changes.
1515
- Add Proposal discussion context that manages username [Issue 3341](https://github.com/IntersectMBO/govtool/issues/3341)
1616
- Add epochParams and ada holder balance to Proposal Discussion Pillar [Issue 2243](https://github.com/IntersectMBO/govtool/issues/2243)
1717

18+
- Add uncontrolled image input to improve performance of large base64 encoded image strings
19+
1820
### Fixed
1921

2022
- Fix scroll on a drawer on smaller resolution
2123
- Fix incorrect routing on connecting wallet on budget discussion page
24+
- Fix missing validation on failed image sha generation [Issue 3246](https://github.com/IntersectMBO/govtool/issues/3246)
2225

2326
### Changed
2427

govtool/frontend/src/components/molecules/DRepDataForm.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Rules } from "@consts";
1414
import { useScreenDimension, useTranslation } from "@hooks";
1515
import { DRepDataFormValues } from "@/types/dRep";
1616

17-
import { ControlledField } from "../organisms";
17+
import { ControlledField, UncontrolledImageInput } from "../organisms";
1818

1919
const MAX_NUMBER_OF_LINKS = 7;
2020

@@ -106,9 +106,9 @@ export const DRepDataForm = ({ control, errors, register, watch }: Props) => {
106106
title={t("forms.dRepData.image")}
107107
subtitle={t("forms.dRepData.imageHelpfulText")}
108108
/>
109-
<ControlledField.Input
110-
{...{ control, errors }}
109+
<UncontrolledImageInput
111110
data-testid="image-input"
111+
control={control}
112112
name="image"
113113
rules={Rules.IMAGE_URL}
114114
/>

govtool/frontend/src/components/organisms/ControlledField/Input.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { ControlledInputProps, RenderInputProps } from "./types";
88
export const Input = forwardRef<HTMLInputElement, ControlledInputProps>(
99
({ control, name, errors, rules, ...props }, ref) => {
1010
const errorMessage = get(errors, name)?.message as string;
11-
1211
const renderInput = useCallback(
1312
({ field }: RenderInputProps) => (
1413
<Field.Input
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { useRef } from "react";
3+
import { useController } from "react-hook-form";
4+
import { FormErrorMessage } from "../atoms";
5+
6+
type UncontrolledImageInputProps = {
7+
name: string;
8+
control: any;
9+
rules?: any;
10+
placeholder?: string;
11+
dataTestId?: string;
12+
};
13+
14+
export const UncontrolledImageInput = ({
15+
name,
16+
control,
17+
rules,
18+
placeholder,
19+
dataTestId,
20+
}: UncontrolledImageInputProps) => {
21+
const {
22+
field: { onChange },
23+
fieldState,
24+
} = useController({
25+
name,
26+
control,
27+
rules,
28+
});
29+
30+
const inputRef = useRef<HTMLInputElement>(null);
31+
32+
return (
33+
<div style={{ width: "100%" }}>
34+
<input
35+
ref={inputRef}
36+
onBlur={() => {
37+
const value = inputRef.current?.value;
38+
if (value !== undefined) {
39+
onChange(value);
40+
}
41+
}}
42+
placeholder={placeholder}
43+
data-testid={dataTestId}
44+
defaultValue=""
45+
style={{
46+
width: "100%",
47+
fontSize: "inherit",
48+
padding: "8px 16px",
49+
borderRadius: "50px",
50+
height: "50px",
51+
border: "1px solid",
52+
borderColor: fieldState.error?.message ? "red" : "#6F99FF",
53+
backgroundColor: fieldState.error?.message ? "#FAEAEB" : "white",
54+
boxSizing: "border-box",
55+
margin: 0,
56+
display: "block",
57+
minWidth: 0,
58+
lineHeight: "1.4375em",
59+
WebkitTapHighlightColor: "transparent",
60+
animationDuration: "10ms",
61+
WebkitAnimationDuration: "10ms",
62+
animationName: "mui-auto-fill-cancel",
63+
WebkitAnimationName: "mui-auto-fill-cancel",
64+
outline: "none",
65+
}}
66+
/>
67+
{fieldState.error && (
68+
<FormErrorMessage
69+
dataTestId={`${dataTestId}-error`}
70+
errorMessage={fieldState.error.message}
71+
/>
72+
)}
73+
</div>
74+
);
75+
};

govtool/frontend/src/components/organisms/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export * from "./AutomatedVotingOptions";
22
export * from "./ChooseStakeKeyPanel";
33
export * from "./ControlledField";
44
export * from "./CreateGovernanceActionSteps";
5+
export * from "./DRepCard";
6+
export * from "./DRepDetailsCard";
57
export * from "./DashboardCards";
68
export * from "./DashboardCards";
79
export * from "./DashboardDrawerMobile";
@@ -11,18 +13,17 @@ export * from "./DashboardGovernanceActionsVotedOn";
1113
export * from "./DashboardTopNav";
1214
export * from "./Drawer";
1315
export * from "./DrawerMobile";
14-
export * from "./DRepCard";
15-
export * from "./DRepDetailsCard";
1616
export * from "./EditDRepInfoSteps";
17-
export * from "./Modal";
1817
export * from "./Footer";
1918
export * from "./GovernanceActionDetailsCard";
2019
export * from "./GovernanceActionDetailsCardData";
2120
export * from "./GovernanceActionsToVote";
2221
export * from "./Hero";
2322
export * from "./HomeCards";
23+
export * from "./Modal";
2424
export * from "./RegisterAsDRepSteps";
2525
export * from "./Slider";
2626
export * from "./TopNav";
27+
export * from "./UncontrolledImageInput";
2728
export * from "./VoteContext";
2829
export * from "./WrongRouteInfo";

govtool/frontend/src/consts/dRepActions/fields.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
IMAGE_REGEX,
44
URL_REGEX,
55
isReceivingAddress,
6+
isValidImageUrl,
67
isValidURLLength,
78
} from "@/utils";
89

@@ -76,5 +77,6 @@ export const Rules = {
7677
value: IMAGE_REGEX,
7778
message: i18n.t("registration.fields.validations.image"),
7879
},
80+
validate: isValidImageUrl,
7981
},
8082
};

govtool/frontend/src/i18n/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,8 @@
388388
"errors": {
389389
"tooLongUrl": "Url must be less than 128 bytes",
390390
"mustBeStakeAddress": "It must be reward address in bech32 format",
391-
"mustBeReceivingAddress": "Invalid payment address"
391+
"mustBeReceivingAddress": "Invalid payment address",
392+
"couldNotGenerateImageSha": "Could not generate image sha"
392393
}
393394
},
394395
"proposalDiscussion": {

govtool/frontend/src/utils/generateMetadataBody.ts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,51 @@ export const generateMetadataBody = async ({
1717
data,
1818
acceptedKeys,
1919
}: MetadataConfig) => {
20-
const filteredData = Object.entries(data)
21-
.filter(([key, value]) => value && acceptedKeys.includes(key))
22-
.map(([key, value]) => [key, value]);
23-
24-
const references = data?.references
25-
? // uri should not be optional. It is just not yet supported on govtool
26-
(data.references as Array<Partial<Reference>>)
27-
.filter((link) => link.uri)
28-
.map((link) => ({
29-
"@type": link["@type"] ?? "Other",
30-
label: link.label ?? "Label",
31-
uri: link.uri,
32-
}))
33-
: undefined;
34-
35-
const isUrl = (url?: unknown) => URL_REGEX.test(url as string);
36-
let image;
37-
38-
if (isUrl(data?.image)) {
39-
image = {
40-
"@type": "ImageObject",
41-
contentUrl: data.image,
42-
sha256: await getImageSha(data.image as string),
43-
};
44-
} else {
45-
image = data?.image
46-
? {
47-
"@type": "ImageObject",
48-
contentUrl: data.image,
49-
}
20+
try {
21+
const filteredData = Object.entries(data)
22+
.filter(([key, value]) => value && acceptedKeys.includes(key))
23+
.map(([key, value]) => [key, value]);
24+
25+
const references = data?.references
26+
? // uri should not be optional. It is just not yet supported on govtool
27+
(data.references as Array<Partial<Reference>>)
28+
.filter((link) => link.uri)
29+
.map((link) => ({
30+
"@type": link["@type"] ?? "Other",
31+
label: link.label ?? "Label",
32+
uri: link.uri,
33+
}))
5034
: undefined;
51-
}
5235

53-
const body = Object.fromEntries(filteredData);
54-
if (references?.length) {
55-
body.references = references;
56-
}
36+
const isUrl = (url?: unknown) => URL_REGEX.test(url as string);
37+
let image;
5738

58-
if (image) {
59-
body.image = image;
60-
}
39+
if (isUrl(data?.image)) {
40+
image = {
41+
"@type": "ImageObject",
42+
contentUrl: data.image,
43+
sha256: await getImageSha(data.image as string),
44+
};
45+
} else {
46+
image = data?.image
47+
? {
48+
"@type": "ImageObject",
49+
contentUrl: data.image,
50+
}
51+
: undefined;
52+
}
6153

62-
return body;
54+
const body = Object.fromEntries(filteredData);
55+
if (references?.length) {
56+
body.references = references;
57+
}
58+
59+
if (image) {
60+
body.image = image;
61+
}
62+
63+
return body;
64+
} catch (error) {
65+
console.error({ error });
66+
}
6367
};

govtool/frontend/src/utils/isValidFormat.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from "@emurgo/cardano-serialization-lib-asmjs";
66
import i18n from "@/i18n";
77
import { adaHandleService } from "@/services/AdaHandle";
8+
import { getImageSha } from "./getImageSha";
89

910
export const URL_REGEX =
1011
/^(?:(?:https?:\/\/)?(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(?:\/[^\s]*)?)|(?:ipfs:\/\/(?:[a-zA-Z0-9]+(?:\/[a-zA-Z0-9._-]+)*))$|^$/;
@@ -76,3 +77,15 @@ export async function isDRepView(view?: string) {
7677
}
7778
return i18n.t("forms.errors.mustBeDRepView");
7879
}
80+
81+
export async function isValidImageUrl(url: string) {
82+
if (!url.length) return false;
83+
try {
84+
if (URL_REGEX.test(url)) {
85+
await getImageSha(url);
86+
}
87+
return true;
88+
} catch (error) {
89+
return i18n.t("forms.errors.couldNotGenerateImageSha");
90+
}
91+
}

0 commit comments

Comments
 (0)