From bc95fbff572cf3fac793d858f13fc9d853a67901 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2023 22:46:00 -0700 Subject: [PATCH 1/2] first-commit --- package-lock.json | 79 +++++++++++++++++++++ package.json | 3 + src/App.jsx | 105 ++++++++++++++++++++++----- src/components/user/Login.jsx | 0 src/components/user/Profile.jsx | 0 src/components/user/Register.jsx | 117 +++++++++++++++++++++++++++++++ src/store/api/AuthSlice.js | 42 +++++++++++ src/store/api/BaseUrl.js | 3 + src/store/api/UserSlice.js | 37 ++++++++++ src/store/index.js | 13 +++- 10 files changed, 381 insertions(+), 18 deletions(-) create mode 100644 src/components/user/Login.jsx create mode 100644 src/components/user/Profile.jsx create mode 100644 src/components/user/Register.jsx create mode 100644 src/store/api/AuthSlice.js create mode 100644 src/store/api/BaseUrl.js create mode 100644 src/store/api/UserSlice.js diff --git a/package-lock.json b/package-lock.json index 110a876..63d213f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,10 @@ "dependencies": { "@reduxjs/toolkit": "^1.9.5", "axios": "^1.4.0", + "cookie": "^0.5.0", "formik": "^2.2.9", + "js": "^0.1.0", + "js-cookie": "^3.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.8.0", @@ -1462,6 +1465,14 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2807,6 +2818,25 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/js/-/js-0.1.0.tgz", + "integrity": "sha512-ZBbGYOpact8QAH9RprFWL4RAESYwbDodxiuDjOnzwzzk9pBzKycoifGuUrHHcDixE/eLMKPHRaXenTgu1qXBqA==", + "dependencies": { + "commander": "~1.1.1" + }, + "bin": { + "js": "bin/js" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-sdsl": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", @@ -2834,6 +2864,17 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js/node_modules/commander": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-1.1.1.tgz", + "integrity": "sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==", + "dependencies": { + "keypress": "0.1.x" + }, + "engines": { + "node": ">= 0.6.x" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -2883,6 +2924,11 @@ "node": ">=4.0" } }, + "node_modules/keypress": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", + "integrity": "sha512-x0yf9PL/nx9Nw9oLL8ZVErFAk85/lslwEP7Vz7s5SI1ODXZIgit3C5qyWjw4DxOuO/3Hb4866SQh28a1V1d+WA==" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5381,6 +5427,11 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -6345,6 +6396,29 @@ "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", "dev": true }, + "js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/js/-/js-0.1.0.tgz", + "integrity": "sha512-ZBbGYOpact8QAH9RprFWL4RAESYwbDodxiuDjOnzwzzk9pBzKycoifGuUrHHcDixE/eLMKPHRaXenTgu1qXBqA==", + "requires": { + "commander": "~1.1.1" + }, + "dependencies": { + "commander": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-1.1.1.tgz", + "integrity": "sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==", + "requires": { + "keypress": "0.1.x" + } + } + } + }, + "js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==" + }, "js-sdsl": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", @@ -6399,6 +6473,11 @@ "object.assign": "^4.1.3" } }, + "keypress": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", + "integrity": "sha512-x0yf9PL/nx9Nw9oLL8ZVErFAk85/lslwEP7Vz7s5SI1ODXZIgit3C5qyWjw4DxOuO/3Hb4866SQh28a1V1d+WA==" + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", diff --git a/package.json b/package.json index d09831c..84edd25 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,10 @@ "dependencies": { "@reduxjs/toolkit": "^1.9.5", "axios": "^1.4.0", + "cookie": "^0.5.0", "formik": "^2.2.9", + "js": "^0.1.0", + "js-cookie": "^3.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.8.0", diff --git a/src/App.jsx b/src/App.jsx index ac54132..6ed3b3a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,29 +1,100 @@ import AddNote from "./components/AddNote"; import Notes from "./components/Notes"; import EditNote from "./components/EditNote"; -import { Routes, Route } from "react-router-dom"; +import { Routes, Route, Link, useNavigate, useLocation} from "react-router-dom"; +import {Register} from "./components/user/Register"; +import {Login} from "./components/user/Login"; +import {Profile} from "./components/user/Profile"; +import {cookies} from ".js-cookie"; function App() { - + return ( -
-
-
-

My Notes

- - {window.location.pathname === "/" && ( - } /> - ) } else { - } /> - } - - +
+
+ +
- -
-
+ + } /> + } /> + }> + } /> + + }> + } /> + + }> + } /> + + } /> +
); } + + export default App; \ No newline at end of file diff --git a/src/components/user/Login.jsx b/src/components/user/Login.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/user/Profile.jsx b/src/components/user/Profile.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/user/Register.jsx b/src/components/user/Register.jsx new file mode 100644 index 0000000..4de1182 --- /dev/null +++ b/src/components/user/Register.jsx @@ -0,0 +1,117 @@ +import { useEffect, useState } from "react"; +import { Formik, Form, Field, ErrorMessage } from "formik"; +import * as Yup from "yup"; +import { useNavigate } from "react-router-dom"; + +import { useRegisterMutation } from "../../store/api/AuthSlice"; + +const Register = () => { + + const [ register, { error = {} } ] = useRegisterMutation(); + const [ registerError, setRegisterError ] = useState(null); + + const navigate = useNavigate(); + const initialValues = { + name: "", + email: "", + password: "", + }; + + const validationSchema = Yup.object({ + name: Yup.string().required("Name is required"), + email: Yup.string().email("Invalid email format").required("Email is required"), + password: Yup.string().required("Password is required"), + }); + + const handleSubmit = (values) => { + register({ + name: values.name, + email: values.email, + password: values.password, + }).unwrap().then(() => { + navigate("/user/login"); + } + ); + }; + + useEffect(() => { + if (error.status === 409) { + setRegisterError("User already exists"); + } + + if (error.status === 500) { + setRegisterError("Something went wrong, please try again later"); + } + }, [error]); + + + return ( +
+
+

Register

+ +
+
+ {registerError &&
{registerError}
} +
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+
+
+ ); +}; + +export default Register; \ No newline at end of file diff --git a/src/store/api/AuthSlice.js b/src/store/api/AuthSlice.js new file mode 100644 index 0000000..bede30c --- /dev/null +++ b/src/store/api/AuthSlice.js @@ -0,0 +1,42 @@ + +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import BASE_URL from './BaseUrl' + +export const authSlice = createApi({ + reducerPath: 'authApi', + baseQuery: fetchBaseQuery({ + baseUrl: BASE_URL + }), + + endpoints: (builder) => ({ + + // fetch Notes + + register: builder.mutation({ + query: (newUser) => { + return { + url: '/register', + method: 'POST', + body: newUser, + }; + }, + + }), + + // login + + login: builder.mutation({ + query: (user) => ({ + url: 'login', + method: 'POST', + body: user, + }), + + + }), + }), +}) + + +export const {useRegisterMutation,useLoginMutation } = authSlice; +export default authSlice.reducer; \ No newline at end of file diff --git a/src/store/api/BaseUrl.js b/src/store/api/BaseUrl.js new file mode 100644 index 0000000..71c19a6 --- /dev/null +++ b/src/store/api/BaseUrl.js @@ -0,0 +1,3 @@ + const BASE_URL = "https://notes-60by.onrender.com"; + +export default BASE_URL; \ No newline at end of file diff --git a/src/store/api/UserSlice.js b/src/store/api/UserSlice.js new file mode 100644 index 0000000..66c3cd7 --- /dev/null +++ b/src/store/api/UserSlice.js @@ -0,0 +1,37 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import Cookies from "js-cookie"; +import BASE_URL from "./BaseUrl"; + +// Helper function to get the token from the browser's cookie +const getToken = () => { + return Cookies.get("token"); +}; + +export const userSlice = createApi({ + reducerPath: "userApi", + baseQuery: fetchBaseQuery({ + baseUrl: BASE_URL, + prepareHeaders: (headers) => { + const token = getToken(); + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + return headers; + }, + }), + endpoints: (builder) => ({ + // get user + getUser: builder.query({ + query: () => { + return { + url: "user", + method: "GET", + }; + }, + }), + }), +}); + +export const { useGetUserQuery } = userSlice; + +export default userSlice.reducer; \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index dd2a6f5..90fb3d8 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,13 +1,24 @@ import { configureStore } from '@reduxjs/toolkit' import { noteApi } from './api/NoteSlice' import { setupListeners } from '@reduxjs/toolkit/query' +import { authSlice } from './api/AuthSlice' +import { userSlice } from './api/UserSlice' + export const store = configureStore({ reducer: { [noteApi.reducerPath]: noteApi.reducer, + [authSlice.reducerPath]: authSlice.reducer, + [userSlice.reducerPath]: userSlice.reducer, + + + }, middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat(noteApi.middleware), + getDefaultMiddleware() + .concat(noteApi.middleware) + .concat(authSlice.middleware) + .concat(userSlice.middleware), }) From 1ee8d6f3ef025ea74bb2df1109aa78c4a145f8b1 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Jun 2023 20:18:38 -0700 Subject: [PATCH 2/2] lastcommit --- src/App.jsx | 124 ++++++++++++++++---------------- src/PrivateRoute.jsx | 18 +++++ src/components/Notes.jsx | 5 +- src/components/user/Login.jsx | 102 ++++++++++++++++++++++++++ src/components/user/Profile.jsx | 18 +++++ src/store/api/AuthSlice.js | 17 ++++- src/store/api/BaseUrl.js | 3 +- src/store/api/NoteSlice.js | 27 +++++-- src/store/api/UserSlice.js | 2 +- 9 files changed, 241 insertions(+), 75 deletions(-) create mode 100644 src/PrivateRoute.jsx diff --git a/src/App.jsx b/src/App.jsx index 6ed3b3a..c3e60f0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,14 +1,41 @@ +import React, {useEffect, useState} from "react"; + import AddNote from "./components/AddNote"; import Notes from "./components/Notes"; import EditNote from "./components/EditNote"; import { Routes, Route, Link, useNavigate, useLocation} from "react-router-dom"; -import {Register} from "./components/user/Register"; -import {Login} from "./components/user/Login"; -import {Profile} from "./components/user/Profile"; -import {cookies} from ".js-cookie"; +import Register from "./components/user/Register"; +import Login from "./components/user/Login"; +import Profile from "./components/user/Profile"; +import Cookies from "js-cookie"; +import PrivateRoute from "./PrivateRoute"; function App() { - + + + + const [userInfo, setUserInfo] = React.useState(null); + const navigate = useNavigate(); + + React.useEffect(() => { + const token = Cookies.get('token'); + if (token) { + setUserInfo(token); + } + }, [userInfo]); + + const handleLogout = () => { + try { + Cookies.remove('token'); + setUserInfo(null); + window.location.reload(); + navigate('/user/login'); + } catch (error) { + console.log('Logout error:', error); + // Handle error case if needed + } + }; + return (
@@ -17,60 +44,31 @@ function App() {

My Notes

  • - - Home - + Home
  • {userInfo && ( - <> -
  • - - Add Note - -
  • -
  • - - Profile - -
  • -
  • - - Logout - -
  • + <> +
  • + Add Note +
  • +
  • + Profile +
  • +
  • + Logout +
  • )} {!userInfo && ( - <> -
  • - - Login - + <> +
  • + Login
  • +
  • - - Register - + Register
  • - + )}
@@ -78,18 +76,18 @@ function App() { - } /> - } /> - }> - } /> - - }> - } /> - - }> - } /> - - } /> + } /> + } /> + }> + } /> + + }> + } /> + + }> + } /> + + } />
); diff --git a/src/PrivateRoute.jsx b/src/PrivateRoute.jsx new file mode 100644 index 0000000..a490d6d --- /dev/null +++ b/src/PrivateRoute.jsx @@ -0,0 +1,18 @@ + +import React from 'react' +import Cookies from 'js-cookie' +import { Navigate, Outlet } from 'react-router-dom' + + +function PrivateRoute() { + const token = Cookies.get('token') + if (!token) { + return + } + + return ( + + ) +} + +export default PrivateRoute \ No newline at end of file diff --git a/src/components/Notes.jsx b/src/components/Notes.jsx index 83f9456..93282aa 100644 --- a/src/components/Notes.jsx +++ b/src/components/Notes.jsx @@ -4,11 +4,12 @@ import React, { useEffect } from "react"; import { FaEdit, FaTrash } from "react-icons/fa"; import { Link } from "react-router-dom"; import { useGetNotesQuery, useDeleteNoteMutation } from "../store/api/NoteSlice"; - +import { useGetUserQuery } from "../store/api/UserSlice"; function Notes() { const { data: notes = [], status, error } = useGetNotesQuery(); const [deleteNote] = useDeleteNoteMutation(); + const {data: user= {}} = useGetUserQuery() const deleteNoteHandler = (id) => { deleteNote(id); @@ -29,6 +30,7 @@ function Notes() {

{note.content}

+ {user.id === note.user_id && (
+ )}
))} diff --git a/src/components/user/Login.jsx b/src/components/user/Login.jsx index e69de29..0af87da 100644 --- a/src/components/user/Login.jsx +++ b/src/components/user/Login.jsx @@ -0,0 +1,102 @@ + +import React, { useState, useEffect} from "react"; +import { Formik, Form, Field, ErrorMessage } from "formik"; +import * as Yup from "yup"; +import { useNavigate } from "react-router-dom"; + +import { useLoginMutation } from "../../store/api/AuthSlice"; + +const Login = () => { + + const [ login, { error = {} }] = useLoginMutation(); + const navigate = useNavigate(); + const [loginErrror, setLoginError] = useState(null); + + const initialValues = { + email: "", + password: "", + }; + + const validationSchema = Yup.object({ + email: Yup.string().email("Invalid email format").required("Email is required"), + password: Yup.string().required("Password is required"), + }); + + const handleSubmit = (values) => { + login({ + email: values.email, + password: values.password, + }).unwrap() + .then(() => { + navigate("/"); + window.location.reload(); + } + ); + }; + + useEffect(() => { + if (error.status === 401) { + setLoginError("Invalid email or password"); + } + + if (error.status === 500) { + setLoginError("Something went wrong, please try again later"); + } + }, [error]); + + return ( +
+
+

Login

+ +
+
+ {loginErrror &&
{loginErrror}
} +
+
+ + +
+
+ + +
+ + +
+
+
+
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/src/components/user/Profile.jsx b/src/components/user/Profile.jsx index e69de29..e24929e 100644 --- a/src/components/user/Profile.jsx +++ b/src/components/user/Profile.jsx @@ -0,0 +1,18 @@ +import React from "react" +import { useGetUserQuery } from "../../store/api/UserSlice" + +const Profile = () => { + + const {data: user = {}} = useGetUserQuery() + + return ( +
+

Welcome Profile User

+

About

+

Name: {user.name}

+

Email: {user.email}

+
+ ) +} + +export default Profile; \ No newline at end of file diff --git a/src/store/api/AuthSlice.js b/src/store/api/AuthSlice.js index bede30c..487908e 100644 --- a/src/store/api/AuthSlice.js +++ b/src/store/api/AuthSlice.js @@ -1,6 +1,13 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import BASE_URL from './BaseUrl' +import Cookies from 'js-cookie' + +const setToken = (token) => { + Cookies.set('token', token, { expires: 7 }) +}; + + export const authSlice = createApi({ reducerPath: 'authApi', @@ -32,7 +39,15 @@ export const authSlice = createApi({ body: user, }), - + onQueryStarted: async (arg, { dispatch, queryFulfilled }) => { + try { + const result = await queryFulfilled; + const { token } = result.data; + setToken(result.data.token); + } catch (err) { + console.log(err); + } + } }), }), }) diff --git a/src/store/api/BaseUrl.js b/src/store/api/BaseUrl.js index 71c19a6..7876dac 100644 --- a/src/store/api/BaseUrl.js +++ b/src/store/api/BaseUrl.js @@ -1,3 +1,2 @@ - const BASE_URL = "https://notes-60by.onrender.com"; - + const BASE_URL = " https://notes-60by.onrender.com" export default BASE_URL; \ No newline at end of file diff --git a/src/store/api/NoteSlice.js b/src/store/api/NoteSlice.js index 1ce82f4..2b736d6 100644 --- a/src/store/api/NoteSlice.js +++ b/src/store/api/NoteSlice.js @@ -1,9 +1,27 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import BASE_URL from './BaseUrl' +import Cookies from 'js-cookie' + + const getCookies = () => { + return Cookies.get('token') + } + + export const noteApi = createApi({ reducerPath: 'noteApi', - baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:9000' }), + baseQuery: fetchBaseQuery({ + baseUrl: BASE_URL, + prepareHeaders: (headers) => { + const token = getCookies() + if (token) { + headers.set('Authorization', `Bearer ${token}`) + } + return headers; + }, tagTypes: ['Note'], + }), + endpoints: (builder) => ({ getNotes: builder.query({ query: () => '/notes', @@ -35,9 +53,4 @@ export const noteApi = createApi({ }), }) -export const { - useGetNotesQuery, - useAddNoteMutation, - useUpdateNoteMutation, - useDeleteNoteMutation, -} = noteApi +export const {useGetNotesQuery, useAddNoteMutation, useUpdateNoteMutation,useDeleteNoteMutation,} = noteApi diff --git a/src/store/api/UserSlice.js b/src/store/api/UserSlice.js index 66c3cd7..17765d4 100644 --- a/src/store/api/UserSlice.js +++ b/src/store/api/UserSlice.js @@ -3,7 +3,7 @@ import Cookies from "js-cookie"; import BASE_URL from "./BaseUrl"; // Helper function to get the token from the browser's cookie -const getToken = () => { +const getCookie = () => { return Cookies.get("token"); };