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 +

+
-
-
-
- - -
+
+ +
+ + +
-
-
- - Forgot password? -
+
+
+ + + Forgot password? + +
- -
+ +
-
- {error &&
{error}
} - - -
- +
+ {error && ( +
+ {error} +
+ )} -

Don't have an account yet? Sign up.

-
-
-
-
-
+ +
+ -); -} -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 ( -
- -
- ); -}; - -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 ( + +
+
+
+
+ TechTalk News + +
+ +
+
+
+ + ); +}; + +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 ( -
-
- + {" "} -
-

{post.title}

- -
-

{post.description}

-
- {post.image && imagen proporcionada por el Moderador}
+ + {post.image && imagen } +
+

{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 ( + +
+
+
+ Alex +
+

TechTalk

+

{post.createdAt}

+
+
+
+ + + +
+
+ {post.image && imagen } +
+ +

{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: +

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ ); +}; + +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 ( -
- - -
-

Post ({posts.length})

- Create new Post -
-
{posts.map(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==