forked from M3-Academy/desafio-react-e-typescript
development #17
20
package-lock.json
generated
20
package-lock.json
generated
@ -23,6 +23,7 @@
|
||||
"react-bootstrap": "^2.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-text-mask": "^5.5.0",
|
||||
"typescript": "^4.9.4",
|
||||
"web-vitals": "^2.1.4",
|
||||
"yup": "^0.32.11"
|
||||
@ -15398,6 +15399,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-text-mask": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.5.0.tgz",
|
||||
"integrity": "sha512-SLJlJQxa0uonMXsnXRpv5abIepGmHz77ylQcra0GNd7Jtk4Wj2Mtp85uGQHv1avba2uI8ZvRpIEQPpJKsqRGYw==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.5.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
@ -29686,6 +29698,14 @@
|
||||
"workbox-webpack-plugin": "^6.4.1"
|
||||
}
|
||||
},
|
||||
"react-text-mask": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.5.0.tgz",
|
||||
"integrity": "sha512-SLJlJQxa0uonMXsnXRpv5abIepGmHz77ylQcra0GNd7Jtk4Wj2Mtp85uGQHv1avba2uI8ZvRpIEQPpJKsqRGYw==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.6"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"react-bootstrap": "^2.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-text-mask": "^5.5.0",
|
||||
"typescript": "^4.9.4",
|
||||
"web-vitals": "^2.1.4",
|
||||
"yup": "^0.32.11"
|
||||
|
36
src/components/AccordionBody/AccordionBody.tsx
Normal file
36
src/components/AccordionBody/AccordionBody.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import Col from "react-bootstrap/Col";
|
||||
import ListGroup from "react-bootstrap/ListGroup";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import Tab from "react-bootstrap/Tab";
|
||||
|
||||
import styles from "./accordionBody.module.scss";
|
||||
const AccordionBody = () => {
|
||||
return (
|
||||
<Tab.Container id="list-group-tabs" defaultActiveKey="#link1">
|
||||
<Row>
|
||||
<Col sm={4}>
|
||||
<ListGroup>
|
||||
<ListGroup.Item className={styles["acc-item"]} action href="#link1">
|
||||
Link 1
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item action href="#link2">
|
||||
Link 2
|
||||
</ListGroup.Item>
|
||||
</ListGroup>
|
||||
</Col>
|
||||
<Col sm={8}>
|
||||
<Tab.Content>
|
||||
<Tab.Pane eventKey="#link1">
|
||||
<p>oi</p>
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="#link2">
|
||||
<p>tchau</p>
|
||||
</Tab.Pane>
|
||||
</Tab.Content>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export { AccordionBody };
|
17
src/components/AccordionBody/accordionBody.module.scss
Normal file
17
src/components/AccordionBody/accordionBody.module.scss
Normal file
@ -0,0 +1,17 @@
|
||||
.accordion-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.accordion-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.accordion-header {
|
||||
}
|
||||
/*
|
||||
.list-group-tabs.active {
|
||||
background-color: red;
|
||||
}*/
|
||||
.acc-item .active {
|
||||
background-color: red;
|
||||
}
|
@ -1,9 +1,35 @@
|
||||
import React from "react";
|
||||
import { useFormik } from "formik";
|
||||
import {
|
||||
useFormik,
|
||||
Formik,
|
||||
Form,
|
||||
Field,
|
||||
ErrorMessage,
|
||||
FormikHelpers,
|
||||
} from "formik";
|
||||
import * as Yup from "yup";
|
||||
|
||||
import styles from "./formInput.module.scss";
|
||||
|
||||
interface IFormikValues {
|
||||
nome: string;
|
||||
email: string;
|
||||
cpf: string;
|
||||
nascimento: string;
|
||||
tel: string;
|
||||
instagram: string;
|
||||
termos: boolean;
|
||||
}
|
||||
const FormInput = () => {
|
||||
const initialValues = {
|
||||
nome: "",
|
||||
email: "",
|
||||
cpf: "",
|
||||
nascimento: "",
|
||||
tel: "",
|
||||
instagram: "",
|
||||
termos: false,
|
||||
}; /*
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
nome: "",
|
||||
@ -12,78 +38,245 @@ const FormInput = () => {
|
||||
nascimento: "",
|
||||
tel: "",
|
||||
instagram: "",
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
nome: Yup.string().label("Seu nome completo").required(),
|
||||
email: Yup.string().email().required(),
|
||||
cpf: Yup.string().required(),
|
||||
nascimento: Yup.string().required(),
|
||||
tel: Yup.string().required(),
|
||||
instagram: Yup.string().required(),
|
||||
}),
|
||||
termos: false,
|
||||
},*/
|
||||
validationSchema: Yup.object({
|
||||
nome: Yup.string()
|
||||
.required("*Campo Obrigatório")
|
||||
.test(
|
||||
"is-full-name",
|
||||
"Please enter both your first and last name",
|
||||
function (value: any) {
|
||||
const nameArr = value.split(" ");
|
||||
return nameArr.length >= 2;
|
||||
}
|
||||
),
|
||||
email: Yup.string().required("*Campo Obrigatório"),
|
||||
cpf: Yup.string().required("*Campo Obrigatório"),
|
||||
nascimento: Yup.string().required("*Campo Obrigatório"),
|
||||
tel: Yup.string().required("*Campo Obrigatório"),
|
||||
instagram: Yup.string().required("*Campo Obrigatório"),
|
||||
termos: Yup.boolean().required("*").isTrue(),
|
||||
}); /*
|
||||
onSubmit: function (values) {
|
||||
alert(`You are registered! Name: ${values.nome}. Email: ${values.email}. Profession: ${values.cpf}.
|
||||
Age: ${values.nascimento}`);
|
||||
Age: ${values.nascimento},${values.tel},${values.instagram}`);
|
||||
},
|
||||
});*/
|
||||
const validacao = Yup.object().shape({
|
||||
nome: Yup.string()
|
||||
.required("*Campo Obrigatório")
|
||||
.test(
|
||||
"Nome Completo",
|
||||
"Preencha com um nome e sobrenome válido.",
|
||||
function (value: any) {
|
||||
if (value === "" || value === undefined) {
|
||||
return false;
|
||||
} else {
|
||||
const nameArr = value.split(" ");
|
||||
return nameArr.length >= 2;
|
||||
}
|
||||
}
|
||||
),
|
||||
email: Yup.string().required("*Campo Obrigatório").email("E-mail inválido"),
|
||||
cpf: Yup.string()
|
||||
.required("*Campo Obrigatório")
|
||||
.test("cpf", "Preencha com um cpf válido.", function (value: any) {
|
||||
if (value === "" || value === undefined) {
|
||||
return false;
|
||||
} else {
|
||||
const cpfRegex = /^\d{3}\.\d{3}\.\d{3}\-\d{2}$/;
|
||||
const regex = new RegExp(cpfRegex);
|
||||
return regex.test(value);
|
||||
}
|
||||
}),
|
||||
nascimento: Yup.string()
|
||||
.required("*Campo Obrigatório")
|
||||
.test(
|
||||
"nascimento",
|
||||
"Preencha com uma data válida.",
|
||||
function (value: any) {
|
||||
if (value === "" || value === undefined) {
|
||||
return false;
|
||||
} else {
|
||||
const nascimentoRegex =
|
||||
"^(0[1-9]|[12][0-9]|3[01]).(0[1-9]|1[012]).[12][0-9]{3}$";
|
||||
const regex = new RegExp(nascimentoRegex);
|
||||
return regex.test(value);
|
||||
}
|
||||
}
|
||||
),
|
||||
tel: Yup.string()
|
||||
.required("*Campo Obrigatório")
|
||||
.test(
|
||||
"telefone",
|
||||
"Preencha com um telefone válido.",
|
||||
function (value: any) {
|
||||
if (value === "" || value === undefined) {
|
||||
return false;
|
||||
} else {
|
||||
const telefoneRegex =
|
||||
"^((\\+\\d{2}\\s)?\\(\\d{2}\\)\\s?\\d{4}\\d?\\-\\d{4})?$";
|
||||
const regex = new RegExp(telefoneRegex);
|
||||
return regex.test(value);
|
||||
}
|
||||
}
|
||||
),
|
||||
instagram: Yup.string(),
|
||||
termos: Yup.boolean().oneOf([true], "*"),
|
||||
});
|
||||
const handleFormikSubmit = (values: IFormikValues) => {
|
||||
console.log(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles["form"]}>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<h2>Preencha o formulário</h2>
|
||||
<ul className={styles["form-itens"]}>
|
||||
<li className={styles["form-item"]}>
|
||||
<label>Nome</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Seu nome completo"
|
||||
value={formik.values.nome}
|
||||
/>
|
||||
</li>
|
||||
<li className={styles["form-item"]}>
|
||||
<label>E-mail</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Seu email"
|
||||
value={formik.values.email}
|
||||
/>
|
||||
</li>
|
||||
<li className={styles["form-item"]}>
|
||||
<label>CPF</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="000.000.000-00"
|
||||
value={formik.values.cpf}
|
||||
/>
|
||||
</li>
|
||||
<div className={styles["form-wrapper"]}>
|
||||
<h2>Preencha o formulário</h2>
|
||||
<Formik
|
||||
onSubmit={handleFormikSubmit}
|
||||
initialValues={initialValues}
|
||||
validationSchema={validacao}
|
||||
>
|
||||
{({ errors, touched }) => (
|
||||
<Form>
|
||||
<div className={styles["form-col"]}>
|
||||
<div className={styles["form-text"]}>
|
||||
<label htmlFor="nome">Nome</label>
|
||||
<ErrorMessage
|
||||
component="span"
|
||||
name="nome"
|
||||
className={styles["form-error"]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<li className={styles["form-item"]}>
|
||||
<label>Data de Nascimento</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="00.00.00"
|
||||
value={formik.values.nascimento}
|
||||
/>
|
||||
</li>
|
||||
<li className={styles["form-item"]}>
|
||||
<label>Telefone</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="(00)00000-0000"
|
||||
value={formik.values.tel}
|
||||
/>
|
||||
</li>
|
||||
<li className={styles["form-item"]}>
|
||||
<label>Instagram</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="@seuuser"
|
||||
value={formik.values.instagram}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<button>Cadastre-se</button>
|
||||
</form>
|
||||
<Field
|
||||
type="text"
|
||||
id="nome"
|
||||
name="nome"
|
||||
placeholder="Seu nome completo"
|
||||
className={errors.nome && touched.nome && "invalid"}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form-col"]}>
|
||||
<div className={styles["form-text"]}>
|
||||
<label htmlFor="email">E-mail</label>
|
||||
<ErrorMessage
|
||||
component="span"
|
||||
name="email"
|
||||
className={styles["form-error"]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="text"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Seu e-mail"
|
||||
className={errors.email && touched.email && "invalid"}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form-col"]}>
|
||||
<div className={styles["form-text"]}>
|
||||
<label htmlFor="cpf">CPF</label>
|
||||
<ErrorMessage
|
||||
component="span"
|
||||
name="cpf"
|
||||
className={styles["form-error"]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="text"
|
||||
id="cpf"
|
||||
name="cpf"
|
||||
placeholder="000.000.000-00"
|
||||
className={errors.cpf && touched.cpf && "invalid"}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form-col"]}>
|
||||
<div className={styles["form-text"]}>
|
||||
<label htmlFor="nascimento">Data de Nascimento:</label>
|
||||
<ErrorMessage
|
||||
component="span"
|
||||
name="nascimento"
|
||||
className={styles["form-error"]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="text"
|
||||
id="nascimento"
|
||||
name="nascimento"
|
||||
placeholder="00.00.0000"
|
||||
className={errors.nascimento && touched.nascimento && "invalid"}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form-col"]}>
|
||||
<div className={styles["form-text"]}>
|
||||
<label htmlFor="tel">Telefone</label>
|
||||
<ErrorMessage
|
||||
component="span"
|
||||
name="tel"
|
||||
className={styles["form-error"]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="text"
|
||||
id="tel"
|
||||
name="tel"
|
||||
placeholder="(00) 00000-0000"
|
||||
className={errors.tel && touched.tel && "invalid"}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form-col"]}>
|
||||
<div className={styles["form-text"]}>
|
||||
<label htmlFor="instagram">Instagram</label>
|
||||
<ErrorMessage
|
||||
component="span"
|
||||
name="instagram"
|
||||
className={styles["form-error"]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="text"
|
||||
id="instagram"
|
||||
name="instagram"
|
||||
placeholder="@seuuser"
|
||||
className={errors.instagram && touched.instagram && "invalid"}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form-col"] && styles["form-check"]}>
|
||||
<div className={styles["form-text-check"]}>
|
||||
<ErrorMessage
|
||||
component="span"
|
||||
name="termos"
|
||||
className={styles["form-error"]}
|
||||
/>
|
||||
<label htmlFor="check">
|
||||
<a href="/">Declaro que li e aceito</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="check"
|
||||
name="termos"
|
||||
className={
|
||||
errors.termos &&
|
||||
touched.termos &&
|
||||
"invalid" &&
|
||||
styles["form-checkbox"]
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button className={styles["form-btn"]} type="submit">
|
||||
Cadastre-se
|
||||
</button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
.form {
|
||||
/*.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -7,3 +7,114 @@
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.textForm {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.errorFormik {
|
||||
margin-left: 80px;
|
||||
color: red;
|
||||
}
|
||||
*/
|
||||
.form-wrapper {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form-wrapper h2 {
|
||||
font-weight: 700;
|
||||
font-size: 27px;
|
||||
margin: 0 0 12px 0;
|
||||
|
||||
@media screen and (min-width: 2500px) {
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
.form-wrapper form {
|
||||
width: 100%;
|
||||
}
|
||||
.form-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-col label {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #100d0e;
|
||||
margin: 0 0 4px 0;
|
||||
@media screen and (min-width: 2500px) {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
.form-col input {
|
||||
background: #ffffff;
|
||||
border: 1px solid #100d0e;
|
||||
border-radius: 25px;
|
||||
height: 46px;
|
||||
padding: 10px 0 15px 20px;
|
||||
&::placeholder {
|
||||
font-family: "Roboto";
|
||||
font-size: 14px;
|
||||
color: #b9b7b7;
|
||||
}
|
||||
@media screen and (min-width: 2500px) {
|
||||
height: 63px;
|
||||
&::placeholder {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-wrapper button {
|
||||
width: 100%;
|
||||
height: 52.44px;
|
||||
background: #000000;
|
||||
border-radius: 25px;
|
||||
color: #ffffff;
|
||||
font-family: "Roboto";
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 400;
|
||||
@media screen and (min-width: 2500px) {
|
||||
height: 71px;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
.form-text {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.form-error {
|
||||
color: red;
|
||||
}
|
||||
.form-check {
|
||||
display: flex;
|
||||
flex-direction: rows;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.form-check label {
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
margin-right: 5px;
|
||||
}
|
||||
.form-checkbox {
|
||||
}
|
||||
|
||||
#check {
|
||||
font-size: 15px;
|
||||
border: 1px solid #000000;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.form-btn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { NavigationBar } from "../components/NavigationBar/NavigationBar";
|
||||
import { AccordionBody } from "../components/AccordionBody/AccordionBody";
|
||||
|
||||
const Home = () => {
|
||||
//const style = { background: "black" };
|
||||
return (
|
||||
<div /*style={style}*/>
|
||||
<NavigationBar></NavigationBar>
|
||||
<AccordionBody />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -18,3 +18,12 @@ button {
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
/*
|
||||
$grid-breakpoints: (
|
||||
xs: 0,
|
||||
sm: 375px,
|
||||
md: 768px,
|
||||
lg: 1025px,
|
||||
xl: 1280px,
|
||||
xxl: 2500px,
|
||||
);*/
|
||||
|
11
yarn.lock
11
yarn.lock
@ -8183,7 +8183,7 @@
|
||||
"react-is" "^16.3.2"
|
||||
"warning" "^4.0.0"
|
||||
|
||||
"prop-types@^15.6.2", "prop-types@^15.8.1":
|
||||
"prop-types@^15.5.6", "prop-types@^15.6.2", "prop-types@^15.8.1":
|
||||
"integrity" "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="
|
||||
"resolved" "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
||||
"version" "15.8.1"
|
||||
@ -8449,6 +8449,13 @@
|
||||
optionalDependencies:
|
||||
"fsevents" "^2.3.2"
|
||||
|
||||
"react-text-mask@^5.5.0":
|
||||
"integrity" "sha512-SLJlJQxa0uonMXsnXRpv5abIepGmHz77ylQcra0GNd7Jtk4Wj2Mtp85uGQHv1avba2uI8ZvRpIEQPpJKsqRGYw=="
|
||||
"resolved" "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.5.0.tgz"
|
||||
"version" "5.5.0"
|
||||
dependencies:
|
||||
"prop-types" "^15.5.6"
|
||||
|
||||
"react-transition-group@^4.4.2":
|
||||
"integrity" "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="
|
||||
"resolved" "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz"
|
||||
@ -8459,7 +8466,7 @@
|
||||
"loose-envify" "^1.4.0"
|
||||
"prop-types" "^15.6.2"
|
||||
|
||||
"react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react@^18.0.0", "react@^18.2.0", "react@>= 16", "react@>=0.14.0", "react@>=15.0.0", "react@>=16.14.0", "react@>=16.6.0", "react@>=16.8.0":
|
||||
"react@^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react@^18.0.0", "react@^18.2.0", "react@>= 16", "react@>=0.14.0", "react@>=15.0.0", "react@>=16.14.0", "react@>=16.6.0", "react@>=16.8.0":
|
||||
"integrity" "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="
|
||||
"resolved" "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
|
||||
"version" "18.2.0"
|
||||
|
Loading…
Reference in New Issue
Block a user