diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 383cc112..7cef5a7f 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -1,3 +1,7 @@ +### [6.4.0] - 2023-10-11 + +- Add github automation + ### [6.3.0] - 2023-10-03 - Fix Test diff --git a/ui/package.json b/ui/package.json index 2ea186a9..b9a692d3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -43,8 +43,10 @@ "jszip-utils": "^0.1.0", "lucide-react": "^0.262.0", "marked": "^7.0.1", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-cookie-consent": "^8.0.1", + "react-csv": "^2.2.2", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.10", "react-ga4": "^2.1.0", @@ -87,7 +89,9 @@ "@types/markdown-it": "^13.0.0", "@types/marked": "^5.0.1", "@types/node": "^20.4.4", + "@types/papaparse": "^5.3.9", "@types/react": "^18.2.15", + "@types/react-csv": "^1.1.6", "@types/react-dom": "^18.2.7", "@types/react-syntax-highlighter": "^15.5.7", "@types/webfontloader": "^1.6.35", diff --git a/ui/src/data/featureData.ts b/ui/src/data/featureData.ts index 36c1e9bb..334fbbc1 100644 --- a/ui/src/data/featureData.ts +++ b/ui/src/data/featureData.ts @@ -247,11 +247,11 @@ export const FEATURE_DATA: Feature[] = [ ], }, { - key: routesById.svgformatter.id, - name: routesById.svgformatter.title, - shortDescription: routesById.svgformatter.description, + key: routesById.svg.id, + name: routesById.svg.title, + shortDescription: routesById.svg.description, fullDescription: "", - link: routesById.svgformatter.path, + link: routesById.svg.path, library: [{ name: "Vanilla JS", url: "" }], }, { diff --git a/ui/src/data/menuData.ts b/ui/src/data/menuData.ts index 4ec13bd6..716b1e00 100644 --- a/ui/src/data/menuData.ts +++ b/ui/src/data/menuData.ts @@ -81,9 +81,9 @@ export const MENU_ITEMS = [ show: true, }, { - name: routesById.codeformatter.title, - url: routesById.codeformatter.path, - icon: "Code", + name: routesById.svg.title, + url: routesById.svg.path, + icon: "Command", show: true, }, ], @@ -168,12 +168,7 @@ export const MENU_ITEMS = [ icon: "PencilRuler", show: true, }, - { - name: routesById.svgformatter.title, - url: routesById.svgformatter.path, - icon: "Command", - show: true, - }, + { name: routesById.sorting.title, url: routesById.sorting.path, @@ -186,6 +181,12 @@ export const MENU_ITEMS = [ icon: "Diff", show: true, }, + { + name: routesById.codeformatter.title, + url: routesById.codeformatter.path, + icon: "Code", + show: true, + }, ], }, { @@ -194,8 +195,8 @@ export const MENU_ITEMS = [ show: true, children: [ { - name: routesById.creategithubissue.title, - url: routesById.creategithubissue.path, + name: routesById.githubissue.title, + url: routesById.githubissue.path, icon: "Bug", show: IN_DEVELOPMENT, }, diff --git a/ui/src/data/routeData.tsx b/ui/src/data/routeData.tsx index 560600a9..269ffdd2 100644 --- a/ui/src/data/routeData.tsx +++ b/ui/src/data/routeData.tsx @@ -42,7 +42,7 @@ type RouteId = | "qrcode" | "shadesandtints" | "sorting" - | "svgformatter" + | "svg" | "table" | "tableofcontent" | "terms" @@ -50,6 +50,7 @@ type RouteId = | "tool" | "tvseries" | "uiux" + | "githubissue" | "youtube"; interface Route { @@ -95,7 +96,7 @@ import { QRcode, ShadesAndTints, Sorting, - SvgFormatter, + Svg, TableGenerator, TableOfContent, Terms, @@ -104,6 +105,7 @@ import { TvSeries, UiUx, YouTube, + GithubIsuue, } from "pages/pages"; export const routes: Route[] = [ @@ -143,11 +145,11 @@ export const routes: Route[] = [ component: BoxShadow, }, { - id: "svgformatter", - path: "/tools/svg-formatter", - title: "SVG Formatter", + id: "svg", + path: "/converter/svg", + title: "SVG", description: "Get your SVGs in shipshape.", - component: SvgFormatter, + component: Svg, }, { id: "base64", @@ -172,7 +174,7 @@ export const routes: Route[] = [ }, { id: "codeformatter", - path: "/converter/code-formatter", + path: "/tools/code-formatter", title: "Code Formatter", description: "Embrace your inner Picasso of Programming. Who said formatting code can't be a masterpiece?", @@ -372,11 +374,11 @@ export const routes: Route[] = [ component: Diffchecker, }, { - id: "creategithubissue", - path: "/automation-scripts/create-github-issue", + id: "githubissue", + path: "/automation/github-issue", title: "Create Github Issue", description: "Create Github Issue", - component: PageNotFound, + component: GithubIsuue, }, { id: "/", diff --git a/ui/src/pages/Automation/GithubIssue/components/CsvTable.tsx b/ui/src/pages/Automation/GithubIssue/components/CsvTable.tsx new file mode 100644 index 00000000..a6f5be71 --- /dev/null +++ b/ui/src/pages/Automation/GithubIssue/components/CsvTable.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Table } from "antd"; +import { IssueType } from "pages/Automation/GithubIssue/types"; + +interface CsvTableProps { + data: IssueType[]; +} + +type DataColumn = { + title: string; + dataIndex: string; + key: string; +}; + +const CsvTable: React.FC = ({ data }) => { + const columns: DataColumn[] = []; + + if (data.length > 0) { + const headers = Object.keys(data[0]); + + headers.forEach((header) => { + columns.push({ + title: header, + dataIndex: header, + key: header, + }); + }); + } + + return ( + + ); +}; + +export default CsvTable; diff --git a/ui/src/pages/Automation/GithubIssue/index.tsx b/ui/src/pages/Automation/GithubIssue/index.tsx new file mode 100644 index 00000000..4a2c3a02 --- /dev/null +++ b/ui/src/pages/Automation/GithubIssue/index.tsx @@ -0,0 +1,168 @@ +import { Alert, Button, Card, Form, Progress, Steps, Upload } from "antd"; +import { ResponsiveInputWithLabel } from "components/General/FormComponents"; +import InputGrid from "components/Layouts/InputGrid"; +import PageGrid from "components/Layouts/PageGrid"; +import React, { useState } from "react"; +import Papa from "papaparse"; +import CsvTable from "./components/CsvTable"; +import { CSVLink } from "react-csv"; +import { calculateSteps, createGitHubIssue } from "./utils/helper"; +import { steps } from "./utils/constants"; +import { IssueType, SavedIssueType } from "./types"; + +const GithubIssue: React.FC = () => { + //? input state + const [owner, setOwner] = useState(""); + const [repo, setRepo] = useState(""); + const [token, setToken] = useState(""); + const [fileData, setFileData] = useState([]); + const [isValidInput, setIsValidInput] = useState(false); + + // ? output state + const [progress, setProgress] = useState(0); + const [isError, setIsError] = useState(false); + const [savedIssues, setSavedIssues] = useState([]); + + const handleUpload = (file: File) => { + Papa.parse(file, { + complete: (result) => { + const responseIssue = result.data; + + // Convert keys to lowercase for each object in the array + const formatData = responseIssue.map((issue) => { + const formattedIssue: IssueType = {} as IssueType; + for (const key in issue) { + if (Object.prototype.hasOwnProperty.call(issue, key)) { + // ts-ignore + formattedIssue[key.toLowerCase()] = issue[key]; + } + } + return formattedIssue; + }); + + const checkValidity = formatData.every((dt) => dt?.title); + setIsValidInput(checkValidity); + if (checkValidity) { + setFileData(formatData); + } + }, + header: true, + skipEmptyLines: true, + }); + }; + + const handleCreateGitHubIssue = () => { + createGitHubIssue( + owner, + repo, + token, + fileData, + setProgress, + setSavedIssues, + setIsError + ); + }; + + const haveConfig = repo.length > 0 && token.length > 0 && owner.length > 0; + const haveFileData = fileData.length > 0; + + return ( + + + +
+ {!isValidInput && fileData.length > 0 && ( + <> + +
+ + )} +
+ setToken(e.target.value)} + min={0} + type="text" + /> + + + setOwner(e.target.value)} + min={0} + type="text" + /> + + setRepo(e.target.value)} + min={0} + type="text" + /> + + + + + + + + + + + + + +
+ + + {isError && ( + <> + +
+ + )} + + {progress > 0 && } + + +
+ + + +
+
+ ); +}; + +export default GithubIssue; diff --git a/ui/src/pages/Automation/GithubIssue/issue.csv b/ui/src/pages/Automation/GithubIssue/issue.csv new file mode 100644 index 00000000..581696b2 --- /dev/null +++ b/ui/src/pages/Automation/GithubIssue/issue.csv @@ -0,0 +1,40 @@ +Title,Body,Labels +Side navigation toogle button,Move the side navigation toggle button from the right panel to left panel,enhancement +Use appropriate icons for side naviation ,"Currently, we are using @ant-design/icons. If we can find all the icons, then use this. If not, check other free icon libraries.",enhancement +Add tests for Base64,"Add tests, and refactor the code if needed",enhancement +Add tests for ColorPicker,"Add tests, and refactor the code if needed +https://hslpicker.com/",enhancement +Add tests for DataGenerator,"Add tests, and refactor the code if needed",enhancement +Add tests for Icons,"Add tests, and refactor the code if needed",enhancement +Add tests for ImageGeneratorFromColors,"Add tests, and refactor the code if needed",enhancement +Add tests for MarkdownEditor,"Add tests, and refactor the code if needed",enhancement +Add tests for MdTableGenerator,"Add tests, and refactor the code if needed +https://github.com/alanwsmith/markdown_table_formatter +http://markdowntable.com/ +https://webutility.io/markdown-beautifier",enhancement +Add tests for PixelConverter,"Add tests, and refactor the code if needed",enhancement +Add tests for Shades,"Add tests, and refactor the code if needed +RGBA to HSLA +https://www.had2know.org/technology/hsl-rgb-color-converter.html",enhancement +Add tests for TableOfContent,"Add tests, and refactor the code if needed +https://github.com/lifeparticle/Houdini/issues/11 +https://github.com/lifeparticle/Houdini/issues/41",enhancement +Add tests for TextEditor,"Add tests, and refactor the code if needed",enhancement +Inevestigate how to show tips on the UI,Research and come up with few solutions,enhancement +Add show tips option the UI,Add show tips option the UI,enhancement +Add new feature Text Diff,Add new feature Text Diff,enhancement +Add new feature QR code generator,Add new feature QR code generator,enhancement +Add new feature JSON to TypeScript,Generate TypeScript interface from JSON data,enhancement +Add new feature Code formatter,"HTML, CSS, JavaScript, JSON, YML",enhancement +Add new feature Markdown to pdf ,Add new feature Markdown to pdf ,enhancement +Add new feature Syntax highlight,Markdown code blocks - Syntax highlight,enhancement +Add new feature Avatar generator ,"For example, https://www.dicebear.com/how-to-use/http-api",enhancement +Update DataGenerator,https://github.com/lifeparticle/Houdini/issues/3,enhancement +Add new feature A list of plugins,https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?hl=en,enhancement +Investigate about dashbaord,Do we a need a dashaboard?,enhancement +Add new feature GitHub emoji API,"Web interface for this API +https://docs.github.com/en/rest/emojis?apiVersion=2022-11-28",enhancement +Add new feature Mime types,"Web interface for +https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types",enhancement +Add feature SVG,"Combining SVG Path Data +https://codepen.io/shshaw/pen/mwKvZr/?editors=1000",enhancement \ No newline at end of file diff --git a/ui/src/pages/Automation/GithubIssue/types/index.ts b/ui/src/pages/Automation/GithubIssue/types/index.ts new file mode 100644 index 00000000..16c2678f --- /dev/null +++ b/ui/src/pages/Automation/GithubIssue/types/index.ts @@ -0,0 +1,15 @@ +interface IssueType { + title: string; + body?: string; + assignee?: string | null; + assignees?: string[]; + milestone?: null | string | number; + labels?: string[]; +} + +interface SavedIssueType { + url: string; + title: string; +} + +export type { IssueType, SavedIssueType }; diff --git a/ui/src/pages/Automation/GithubIssue/utils/constants.ts b/ui/src/pages/Automation/GithubIssue/utils/constants.ts new file mode 100644 index 00000000..376ac382 --- /dev/null +++ b/ui/src/pages/Automation/GithubIssue/utils/constants.ts @@ -0,0 +1,13 @@ +const steps = [ + { + title: "Config", + }, + { + title: "File uploaded", + }, + { + title: "CSV file validation", + }, +]; + +export { steps }; diff --git a/ui/src/pages/Automation/GithubIssue/utils/helper.ts b/ui/src/pages/Automation/GithubIssue/utils/helper.ts new file mode 100644 index 00000000..71664a57 --- /dev/null +++ b/ui/src/pages/Automation/GithubIssue/utils/helper.ts @@ -0,0 +1,74 @@ +import { Dispatch, SetStateAction } from "react"; +import { IssueType, SavedIssueType } from "pages/Automation/GithubIssue/types"; + +const createGitHubIssue = async ( + owner: string, + repo: string, + token: string, + fileData: IssueType[], + setProgress: Dispatch>, + setSavedIssues: Dispatch>, + setIsError: Dispatch> +) => { + const savedIssues: SavedIssueType[] = []; + + try { + setProgress(0); + const apiUrl = `https://api.github.com/repos/${owner}/${repo}/issues`; + + for (let i = 0; i < fileData.length; i++) { + const issueData = fileData[i]; + const response: Response = await fetch(apiUrl, { + method: "POST", + headers: { + Authorization: `token ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + title: issueData.title, + body: issueData.body, + }), + }); + + if (!response.ok) { + throw new Error("Something went wrong"); + } + + // Check if the response contains JSON + const contentType = response.headers.get("content-type"); + if (contentType && contentType.includes("application/json")) { + const data = await response.json(); + savedIssues.push({ title: data.title, url: data?.html_url }); + } else { + // Handle non-JSON response here (e.g., templates) + const textData = await response.text(); + console.error(`Non-JSON response: ${textData}`); + } + + const newProgress = ((i + 1) / fileData.length) * 100; + setProgress(newProgress); + } + setSavedIssues(savedIssues); + setIsError(false); + } catch (err) { + setIsError(true); + } +}; + +const calculateSteps = ( + haveConfig: boolean, + haveFileData: boolean, + isValidInput: boolean +) => { + if (haveConfig && haveFileData && isValidInput) { + return 3; + } else if (haveConfig && haveFileData) { + return 2; + } else if (haveConfig) { + return 1; + } else { + return 0; + } +}; + +export { createGitHubIssue, calculateSteps }; diff --git a/ui/src/pages/CSS/BoxShadow/index.tsx b/ui/src/pages/CSS/BoxShadow/index.tsx index 38850aa0..08ddd58d 100644 --- a/ui/src/pages/CSS/BoxShadow/index.tsx +++ b/ui/src/pages/CSS/BoxShadow/index.tsx @@ -60,29 +60,30 @@ const BoxShadow = () => { { if (value) { setHorizontalLength(value); } }} + min={-100} + max={100} /> { if (value) { setVerticalLength(value); } }} + min={-100} + max={100} /> { if (value) { diff --git a/ui/src/pages/Tools/SvgFormatter/SvgFormatter.module.scss b/ui/src/pages/Converter/Svg/Svg.module.scss similarity index 100% rename from ui/src/pages/Tools/SvgFormatter/SvgFormatter.module.scss rename to ui/src/pages/Converter/Svg/Svg.module.scss diff --git a/ui/src/pages/Tools/SvgFormatter/index.tsx b/ui/src/pages/Converter/Svg/index.tsx similarity index 94% rename from ui/src/pages/Tools/SvgFormatter/index.tsx rename to ui/src/pages/Converter/Svg/index.tsx index 1a766d80..cc028841 100644 --- a/ui/src/pages/Tools/SvgFormatter/index.tsx +++ b/ui/src/pages/Converter/Svg/index.tsx @@ -3,12 +3,12 @@ import CodeHighlightWithCopy from "components/General/CodeHighlightWithCopy"; import PageGrid from "components/Layouts/PageGrid"; import React, { ChangeEvent, useState } from "react"; import { combineSVGPaths } from "./utils/helper"; -import style from "./SvgFormatter.module.scss"; +import style from "./Svg.module.scss"; import Warning from "components/General/Warning"; const { TextArea } = Input; -const SvgFormatter: React.FC = () => { +const Svg: React.FC = () => { const [inputSVG, setInputSVG] = useState(""); const [outputSVG, setOutputSVG] = useState(""); @@ -75,4 +75,4 @@ const SvgFormatter: React.FC = () => { ); }; -export default SvgFormatter; +export default Svg; diff --git a/ui/src/pages/Tools/SvgFormatter/utils/helper.ts b/ui/src/pages/Converter/Svg/utils/helper.ts similarity index 100% rename from ui/src/pages/Tools/SvgFormatter/utils/helper.ts rename to ui/src/pages/Converter/Svg/utils/helper.ts diff --git a/ui/src/pages/Information/Npmpackages/components/Package.tsx b/ui/src/pages/Information/Npmpackages/components/Package.tsx index 7acd72d5..82fbac31 100644 --- a/ui/src/pages/Information/Npmpackages/components/Package.tsx +++ b/ui/src/pages/Information/Npmpackages/components/Package.tsx @@ -1,11 +1,11 @@ import { Card, Col, Skeleton, Space, Tag, Typography } from "antd"; import React from "react"; -import { Package } from "pages/Information/Npmpackages/utils/types"; +import { Package as PackagType } from "pages/Information/Npmpackages/utils/types"; const { Title } = Typography; import style from "pages/Information/Npmpackages/Npmpackages.module.scss"; interface PackageProps { - resource: Package; + resource: PackagType; } const Package: React.FC = (props) => { diff --git a/ui/src/pages/Converter/CodeFormatter/CodeFormatter.module.scss b/ui/src/pages/Tools/CodeFormatter/CodeFormatter.module.scss similarity index 100% rename from ui/src/pages/Converter/CodeFormatter/CodeFormatter.module.scss rename to ui/src/pages/Tools/CodeFormatter/CodeFormatter.module.scss diff --git a/ui/src/pages/Converter/CodeFormatter/index.tsx b/ui/src/pages/Tools/CodeFormatter/index.tsx similarity index 100% rename from ui/src/pages/Converter/CodeFormatter/index.tsx rename to ui/src/pages/Tools/CodeFormatter/index.tsx diff --git a/ui/src/pages/Converter/CodeFormatter/utils/constants.ts b/ui/src/pages/Tools/CodeFormatter/utils/constants.ts similarity index 100% rename from ui/src/pages/Converter/CodeFormatter/utils/constants.ts rename to ui/src/pages/Tools/CodeFormatter/utils/constants.ts diff --git a/ui/src/pages/pages.ts b/ui/src/pages/pages.ts index 63b04266..3e1b763f 100644 --- a/ui/src/pages/pages.ts +++ b/ui/src/pages/pages.ts @@ -18,7 +18,7 @@ const BoxShadow = lazy(() => import("pages/CSS/BoxShadow")); const Base64 = lazy(() => import("pages/Converter/Base64")); const Pixel = lazy(() => import("pages/Converter/Pixel")); const JsonToTypescript = lazy(() => import("pages/Converter/JsonToTypescript")); -const CodeFormatter = lazy(() => import("pages/Converter/CodeFormatter")); +const CodeFormatter = lazy(() => import("pages/Tools/CodeFormatter")); const DataGenerator = lazy(() => import("pages/Generator/DataGenerator")); const ImageGeneratorFromColors = lazy( @@ -49,13 +49,15 @@ const TableOfContent = lazy(() => import("pages/Markdown/TableOfContent")); const Diagramming = lazy(() => import("pages/Tools/Diagramming")); const Diffchecker = lazy(() => import("pages/Tools/Diffchecker")); const Sorting = lazy(() => import("pages/Tools/Sorting")); -const SvgFormatter = lazy(() => import("pages/Tools/SvgFormatter")); +const Svg = lazy(() => import("pages/Converter/Svg")); const TextEditor = lazy(() => import("pages/Text/TextEditor")); const Mimetype = lazy(() => import("pages/Information/Mimetype")); const Npmpackages = lazy(() => import("pages/Information/Npmpackages")); +const GithubIsuue = lazy(() => import("pages/Automation/GithubIssue")); + export { About, Avatar, @@ -92,7 +94,7 @@ export { QRcode, ShadesAndTints, Sorting, - SvgFormatter, + Svg, TableGenerator, TableOfContent, Terms, @@ -100,5 +102,6 @@ export { Tool, TvSeries, UiUx, + GithubIsuue, YouTube, }; diff --git a/ui/yarn.lock b/ui/yarn.lock index bb3c24d7..b54d4544 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1938,6 +1938,15 @@ __metadata: languageName: node linkType: hard +"@types/papaparse@npm:^5.3.9": + version: 5.3.9 + resolution: "@types/papaparse@npm:5.3.9" + dependencies: + "@types/node": "*" + checksum: 05e6e3463f8ac6615d5bb793253faeaa49fac6f59ffd50c2c93373be503f057abc8c9bab889a6623a1c5cdcad39c57e5eaaa1164ce00a5926d8b53df598fb9a5 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -1973,6 +1982,15 @@ __metadata: languageName: node linkType: hard +"@types/react-csv@npm:^1.1.6": + version: 1.1.6 + resolution: "@types/react-csv@npm:1.1.6" + dependencies: + "@types/react": "*" + checksum: 21b25cc55a20ea9dd5a214c6a4b9a2bc7dbfb742b15308a77bfb3a6e6da0850f02befb07fecff8c868619ef7710e383fb50daa09ea219d78bd2a4fb56a5f0c6b + languageName: node + linkType: hard + "@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.7": version: 18.2.7 resolution: "@types/react-dom@npm:18.2.7" @@ -3146,7 +3164,9 @@ __metadata: "@types/markdown-it": ^13.0.0 "@types/marked": ^5.0.1 "@types/node": ^20.4.4 + "@types/papaparse": ^5.3.9 "@types/react": ^18.2.15 + "@types/react-csv": ^1.1.6 "@types/react-dom": ^18.2.7 "@types/react-router-dom": ^5.3.3 "@types/react-syntax-highlighter": ^15.5.7 @@ -3176,8 +3196,10 @@ __metadata: jszip-utils: ^0.1.0 lucide-react: ^0.262.0 marked: ^7.0.1 + papaparse: ^5.4.1 react: ^18.2.0 react-cookie-consent: ^8.0.1 + react-csv: ^2.2.2 react-dom: ^18.2.0 react-error-boundary: ^4.0.10 react-ga4: ^2.1.0 @@ -7362,6 +7384,13 @@ __metadata: languageName: node linkType: hard +"papaparse@npm:^5.4.1": + version: 5.4.1 + resolution: "papaparse@npm:5.4.1" + checksum: fc9e52f7158dca3517c229e3309065b1ab5da6c7194572fba4f31ff138bc43e3c91182cc40365cc828f97fe10d0aca416068fd731661058bea0f69ddb84a411a + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -8361,6 +8390,13 @@ __metadata: languageName: node linkType: hard +"react-csv@npm:^2.2.2": + version: 2.2.2 + resolution: "react-csv@npm:2.2.2" + checksum: a6ffabd67fc9ba1478b003977923cfa3ccdd9553ac5edcc3824b4a5ea849b87cb2acfcc0781b0cd0a259bf56e6a7dfd35cfc307576aa51819fc5a32b03118fab + languageName: node + linkType: hard + "react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0"