diff --git a/package-lock.json b/package-lock.json
index faa2f35..7ef275d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,10 @@
"name": "techtalk",
"version": "0.1.0",
"dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^6.4.0",
+ "@fortawesome/free-brands-svg-icons": "^6.4.0",
+ "@fortawesome/free-solid-svg-icons": "^6.4.0",
+ "@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
@@ -2371,6 +2375,63 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz",
+ "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==",
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz",
+ "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.4.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-brands-svg-icons": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz",
+ "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.4.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz",
+ "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.4.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/react-fontawesome": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
+ "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
+ "dependencies": {
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "@fortawesome/fontawesome-svg-core": "~1 || ~6",
+ "react": ">=16.3"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
diff --git a/package.json b/package.json
index b783c93..c2e2499 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,10 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^6.4.0",
+ "@fortawesome/free-brands-svg-icons": "^6.4.0",
+ "@fortawesome/free-solid-svg-icons": "^6.4.0",
+ "@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
diff --git a/src/App.js b/src/App.js
index 7d5d882..dc42127 100644
--- a/src/App.js
+++ b/src/App.js
@@ -2,42 +2,48 @@ import { HomePage, PostForm, NotFoundPage } from "./pages/index";
import { Routes, Route } from "react-router-dom";
import { PostProvider } from "./context/postContext";
import { Toaster } from "react-hot-toast";
-import Signup from './components/Signup';
-import Login from './components/Login';
+import Signup from "./components/Signup";
+import Login from "./components/Login";
import RequiresAuth from "./components/RequiresAuth";
-
+import { HomePageUser } from "./pages/HomePageUser";
+import Navbar from "./components/Navbar/Navbar";
+import { useLocation } from "react-router-dom";
+import ContactForm from "./pages/Contact";
function App() {
-
+ const location = useLocation();
+ const isLoginPage = location.pathname === "/login";
return (
-
+
-
+
{/* Public routes */}
- }/>
- }/>
-
- {/* Private routes */}
- }>
- } />
- } />
- } />
-
+ } />
+ } />
+ } />
+ } />
-
- }>
-
-
+ {/* Private routes */}
+ }>
+ } />
+ } />
+ } />
{/* Catch all */}
} />
-
-
+
-
+
);
}
diff --git a/src/api/posts.js b/src/api/posts.js
index d68177f..428e75b 100644
--- a/src/api/posts.js
+++ b/src/api/posts.js
@@ -1,28 +1,37 @@
-import axios from 'axios';
-
-export const getPostsRequest = async () => await axios.get('http://localhost:4000/api/posts',{
-
-})
-
-export const createPostRequest = async (post) => {
- const token = localStorage.getItem('auth')
- const form = new FormData()
-
- for (let key in post){
- form.append(key, post[key])
- }
-
- return await axios.post('http://localhost:4000/api/posts', form, {
- headers: {
- "Content-Type": "multipart/form-data",
- // eslint-disable-next-line no-template-curly-in-string
- 'Authorization': `Bearer ${token}`
- }
- });
-}
-
-export const deletePostRequest = async id => await axios.delete("http://localhost:4000/api/posts/" + id)
-
-export const getPostRequest = async id => await axios.get("http://localhost:4000/api/posts/" + id)
-
-export const updatePostRequest = async (id, newFields) => await axios.put(`http://localhost:4000/api/posts/${id}`, newFields)
\ No newline at end of file
+import axios from "axios";
+
+export const getPostsRequest = async () =>
+ await axios.get("http://localhost:4000/api/posts", {});
+
+export const createPostRequest = async (post, token) => {
+ const form = new FormData();
+
+ for (let key in post) {
+ form.append(key, post[key]);
+ }
+
+ return await axios.post("http://localhost:4000/api/posts", form, {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ // eslint-disable-next-line no-template-curly-in-string
+ Authorization: `Bearer ${token}`,
+ },
+ });
+};
+
+export const deletePostRequest = async (id, token) =>
+ await axios.delete("http://localhost:4000/api/posts/" + id, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+export const getPostRequest = async (id) =>
+ await axios.get("http://localhost:4000/api/posts/" + id);
+
+export const updatePostRequest = async (id, newFields, token) =>
+ await axios.put(`http://localhost:4000/api/posts/${id}`, newFields, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
diff --git a/src/components/Login/index.jsx b/src/components/Login/index.jsx
index 2c30bfc..44278b9 100644
--- a/src/components/Login/index.jsx
+++ b/src/components/Login/index.jsx
@@ -1,20 +1,20 @@
import { useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import useAuth from "../../hooks/useAuth";
-import axios from 'axios'
+import axios from "axios";
const Signin = () => {
const [data, setData] = useState({
email: "",
password: "",
});
- const [error, setError] = useState("")
+ const [error, setError] = useState("");
- const { setAuth } = useAuth()
+ const { setAuth } = useAuth();
- const location = useLocation()
- const navigate = useNavigate()
- const from = location.state?.from?.pathname || "/"
+ const location = useLocation();
+ const navigate = useNavigate();
+ const from = location.state?.from?.pathname || "/";
const handleChange = ({ currentTarget: input }) => {
setData({ ...data, [input.name]: input.value });
@@ -23,97 +23,138 @@ const Signin = () => {
const handleSubmit = async (e) => {
e.preventDefault();
try {
- const url = "http://localhost:4000/api/auth/signin";
- const {data:res} = await axios.post(url, data);
- const roles = res?.roles
- const token = res?.token
- setAuth({roles, token})
- console.log(roles)
- navigate(from, {replace: true})
-
+ const url = "http://localhost:4000/api/auth/signin";
+ const { data: res } = await axios.post(url, data);
+ const roles = res?.roles;
+ const token = res?.token;
+ setAuth({ roles, token });
+
+ console.log(roles);
+ navigate(from, { replace: true });
} catch (error) {
- if(error.response &&
- error.response.status >=400 &&
- error.response.status <=500
- ){
- setError(error.response.data.message)
- }
-
+ if (
+ error.response &&
+ error.response.status >= 400 &&
+ error.response.status <= 500
+ ) {
+ setError(error.response.data.message);
+ }
}
-
- }
+ };
return (
-
-
-
-
-
TechTalk News
-
Welcome to TechTalk, the go-to social network for technology and news enthusiasts! Here, you can connect with a passionate community that shares your interests and discover the latest trends in the digital world.
-
-
-
+
+
+
+
+
TechTalk News
+
+ Welcome to TechTalk, the go-to social network for technology and
+ news enthusiasts! Here, you can connect with a passionate
+ community that shares your interests and discover the latest
+ trends in the digital world.
+
+
+
+
-
-
-
-
Techtalk
-
Sign in to access your account
-
+
+
+
+
+ TechTalk{" "}
+
+
+ Sign in to access your account
+
+
-
-
-
+
+ Sign in
+
+
+
-);
-}
-export default Signin;
\ No newline at end of file
+
+ Don't have an account yet?{" "}
+
+ Sign up
+
+ .
+
+
+
+
+
+
+ );
+};
+export default Signin;
diff --git a/src/components/Main/index.jsx b/src/components/Main/index.jsx
deleted file mode 100644
index a6efd9e..0000000
--- a/src/components/Main/index.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import styles from './style.module.css';
-
-
-const Main = () => {
- const handleLogout = ()=> {
- localStorage.removeItem("token");
- window.location.reload();
- }
-
-return (
-
-
- Techtalk
-
- Logout
-
-
-
- );
-};
-
-export default Main;
\ No newline at end of file
diff --git a/src/components/Main/style.module.css b/src/components/Main/style.module.css
deleted file mode 100644
index 3cb8dac..0000000
--- a/src/components/Main/style.module.css
+++ /dev/null
@@ -1,27 +0,0 @@
-.navbar {
- width: 100%;
- height: 70px;
- background-color: #3bb19b;
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.navbar h1 {
- color: white;
- font-size: 25px;
- margin-left: 20px;
-}
-
-.white_btn {
- border: none;
- outline: none;
- padding: 12px 0;
- background-color: white;
- border-radius: 20px;
- width: 120px;
- font-weight: bold;
- font-size: 14px;
- cursor: pointer;
- margin-right: 20px;
-}
\ No newline at end of file
diff --git a/src/components/Navbar/Navbar.jsx b/src/components/Navbar/Navbar.jsx
new file mode 100644
index 0000000..827680d
--- /dev/null
+++ b/src/components/Navbar/Navbar.jsx
@@ -0,0 +1,61 @@
+import React, { useState } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
+
+
+
+
+ const Navbar = () => {
+ const handleLogout = ()=> {
+ localStorage.removeItem("token");
+ window.location.reload("/login");
+ }
+ const [open, setOpen] = useState(true);
+
+ const toggleMenu = () => {
+ setOpen(!open);
+ };
+
+
+
+
+
+ return (
+
+
+
+ );
+};
+
+export default Navbar;
\ No newline at end of file
diff --git a/src/components/PostCard.jsx b/src/components/PostCard.jsx
index df330f9..3eaf805 100644
--- a/src/components/PostCard.jsx
+++ b/src/components/PostCard.jsx
@@ -43,32 +43,45 @@ export function PostCard({ post }) {
};
return (
-
-
-
+
+
+
+
+
+ TechTalk
+
+
+ {post.createdAt}
+
+
+
+
+ {" "}
+
navigate(`/posts/${post._id}`)}
>
Edit
-
+ {" "}
handleDelete(post._id)}
>
Delete
-
-
{post.title}
-
-
-
{post.description}
-
- {post.image &&
}
+
+ {post.image &&
}
+
+
{post.title}
+
{post.description}
+
);
}
diff --git a/src/components/PostCardUser.jsx b/src/components/PostCardUser.jsx
new file mode 100644
index 0000000..8130285
--- /dev/null
+++ b/src/components/PostCardUser.jsx
@@ -0,0 +1,32 @@
+
+
+
+export function PostCardUser({ post }) {
+
+ return (
+
+
+
+
+
+
+
TechTalk
+
{post.createdAt}
+
+
+
+
+ {post.image &&
}
+
+
+
{post.title}
+
{post.description}
+
+
+
+);
+};
\ No newline at end of file
diff --git a/src/components/RequiresAuth.jsx b/src/components/RequiresAuth.jsx
index 03cab63..40822f7 100644
--- a/src/components/RequiresAuth.jsx
+++ b/src/components/RequiresAuth.jsx
@@ -7,7 +7,7 @@ const RequiresAuth = ( {allowedRoles } ) => {
const location = useLocation()
return (
- auth?.roles?.find(role => allowedRoles?.includes(role.name)) ? :
+ auth?.roles?.find(role => allowedRoles?.includes(role.name)) ? :
)
}
diff --git a/src/context/AuthProvider.js b/src/context/AuthProvider.js
index 73b881e..9388742 100644
--- a/src/context/AuthProvider.js
+++ b/src/context/AuthProvider.js
@@ -3,15 +3,14 @@ import { createContext, useState } from "react";
const AuthContext = createContext({});
-export const AuthProvider = ({children}) => {
- const [auth, setAuth] = useState({})
+export const AuthProvider = ({ children }) => {
+ const [auth, setAuth] = useState({});
- return (
-
- {children}
-
- )
-}
+ return (
+
+ {children}
+
+ );
+};
-
-export default AuthContext
\ No newline at end of file
+export default AuthContext;
diff --git a/src/context/postContext.js b/src/context/postContext.js
index dc37877..c8d7d45 100644
--- a/src/context/postContext.js
+++ b/src/context/postContext.js
@@ -6,6 +6,7 @@ import {
getPostRequest,
updatePostRequest,
} from "../api/posts";
+import useAuth from "../hooks/useAuth";
export const postContext = createContext();
@@ -16,6 +17,7 @@ export const usePosts = () => {
export const PostProvider = ({ children }) => {
const [posts, setPosts] = useState([]);
+ const {auth } = useAuth()
useEffect(() => {
(async () => {
@@ -26,7 +28,7 @@ export const PostProvider = ({ children }) => {
const createPost = async (post) => {
try {
- const res = await createPostRequest(post);
+ const res = await createPostRequest(post, auth.token);
setPosts([...posts, res.data]);
} catch (error) {
console.log(error)
@@ -34,7 +36,7 @@ export const PostProvider = ({ children }) => {
};
const deletePost = async (id) => {
- await deletePostRequest(id);
+ await deletePostRequest(id, auth.token);
setPosts(posts.filter((post) => post._id !== id));
};
@@ -49,7 +51,7 @@ export const PostProvider = ({ children }) => {
const updatePost = async (id, post) => {
- const res = await updatePostRequest(id, post);
+ const res = await updatePostRequest(id, post, auth.token);
setPosts(posts.map((post) => (post.id === id ? res.data : post)));
};
diff --git a/src/index.js b/src/index.js
index 799f591..81592b2 100644
--- a/src/index.js
+++ b/src/index.js
@@ -19,7 +19,5 @@ root.render(
);
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+
reportWebVitals();
diff --git a/src/pages/Contact.js b/src/pages/Contact.js
new file mode 100644
index 0000000..0d308d0
--- /dev/null
+++ b/src/pages/Contact.js
@@ -0,0 +1,119 @@
+import { useState } from "react";
+
+const ContactForm = () => {
+ const [formValues, setFormValues] = useState({
+ name: "",
+ email: "",
+ message: "",
+ });
+
+ const handleChange = (e) => {
+ setFormValues({
+ ...formValues,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ const formData = new FormData(e.target);
+
+ fetch("https://api.web3forms.com/submit", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(Object.fromEntries(formData)),
+ })
+ .then(async (response) => {
+ let json = await response.json();
+ if (response.status === 200) {
+ console.log(json.message);
+ } else {
+ console.log(response);
+ }
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+
+ setFormValues({
+ name: "",
+ email: "",
+ message: "",
+ });
+ };
+
+ return (
+
+
+
Contact us
+
+ Email us at help@techtalk12.com or message us here:
+
+
+
+
+
+
+
+
+ Your name
+
+
+
+
+
+ Your email
+
+
+
+
+
+ Your message
+
+
+
+
+ Send Message
+
+
+
+
+ );
+};
+
+export default ContactForm;
diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js
index 0ed75f4..8f9bf0b 100644
--- a/src/pages/HomePage.js
+++ b/src/pages/HomePage.js
@@ -1,13 +1,10 @@
import { usePosts } from "../context/postContext";
import {VscEmptyWindow} from 'react-icons/vsc'
-import { Link } from "react-router-dom";
import { PostCard } from "../components/PostCard";
+
export function HomePage() {
- const handleLogout = ()=> {
- localStorage.removeItem("token");
- window.location.reload();
-}
+
const { posts } = usePosts()
@@ -19,24 +16,11 @@ export function HomePage() {
)
return (
-
-
- Techtalk
-
- Logout
-
-
-
-
- Post ({posts.length})
- Create new Post
-
-
-
+
);
}
diff --git a/src/pages/HomePageUser.js b/src/pages/HomePageUser.js
new file mode 100644
index 0000000..9e90c75
--- /dev/null
+++ b/src/pages/HomePageUser.js
@@ -0,0 +1,29 @@
+import { usePosts } from "../context/postContext";
+import {VscEmptyWindow} from 'react-icons/vsc'
+
+import { PostCardUser } from "../components/PostCardUser";
+
+export function HomePageUser() {
+
+ const { posts } = usePosts()
+
+ if (posts.length === 0 )
+ return (
+
+
there are not posts
+
+ )
+
+ return (
+
+
+
+
+
+ {posts.map(post => (
+
+ ))}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/PostForm.js b/src/pages/PostForm.js
index 1574875..c1e9690 100644
--- a/src/pages/PostForm.js
+++ b/src/pages/PostForm.js
@@ -107,7 +107,16 @@ export function PostForm() {
className="bg-indigo-600 hover:bg-indigo-500 px-4 py-2 rounded mt-2 text-white focus:outline-none disabled:bg-indigo-400"
disable={isSubmitting}
>
- {isSubmitting ? 'Loading' : 'Save'}
+ {isSubmitting ?
+
+ : 'Save'}
)}
diff --git a/yarn.lock b/yarn.lock
index 93aac5a..82f7395 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1308,6 +1308,39 @@
resolved "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz"
integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==
+"@fortawesome/fontawesome-common-types@6.4.0":
+ version "6.4.0"
+ resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz"
+ integrity sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==
+
+"@fortawesome/fontawesome-svg-core@^6.4.0", "@fortawesome/fontawesome-svg-core@~1 || ~6":
+ version "6.4.0"
+ resolved "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz"
+ integrity sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.4.0"
+
+"@fortawesome/free-brands-svg-icons@^6.4.0":
+ version "6.4.0"
+ resolved "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz"
+ integrity sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.4.0"
+
+"@fortawesome/free-solid-svg-icons@^6.4.0":
+ version "6.4.0"
+ resolved "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz"
+ integrity sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.4.0"
+
+"@fortawesome/react-fontawesome@^0.2.0":
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz"
+ integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==
+ dependencies:
+ prop-types "^15.8.1"
+
"@humanwhocodes/config-array@^0.11.10":
version "0.11.10"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz"
@@ -7909,7 +7942,7 @@ react-scripts@5.0.1:
optionalDependencies:
fsevents "^2.3.2"
-react@*, react@^18.0.0, react@^18.2.0, "react@>= 16", react@>=16, react@>=16.8, react@>=16.8.0:
+react@*, react@^18.0.0, react@^18.2.0, "react@>= 16", react@>=16, react@>=16.3, react@>=16.8, react@>=16.8.0:
version "18.2.0"
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==