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..c3e60f0 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,29 +1,98 @@
+import React, {useEffect, useState} from "react";
+
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";
+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 (
-
-
-
-
My Notes
-
- {window.location.pathname === "/" && (
- } />
- ) } else {
- } />
- }
-
-
-
-
-
-
+
+
+
+
+
+
+ } />
+ } />
+ }>
+ } />
+
+ }>
+ } />
+
+ }>
+ } />
+
+ } />
+
);
}
+
+
export default App;
\ No newline at end of file
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
new file mode 100644
index 0000000..0af87da
--- /dev/null
+++ 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 (
+
+ );
+};
+
+export default Login;
\ No newline at end of file
diff --git a/src/components/user/Profile.jsx b/src/components/user/Profile.jsx
new file mode 100644
index 0000000..e24929e
--- /dev/null
+++ 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/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 (
+
+ );
+};
+
+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..487908e
--- /dev/null
+++ b/src/store/api/AuthSlice.js
@@ -0,0 +1,57 @@
+
+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',
+ 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,
+ }),
+
+ onQueryStarted: async (arg, { dispatch, queryFulfilled }) => {
+ try {
+ const result = await queryFulfilled;
+ const { token } = result.data;
+ setToken(result.data.token);
+ } catch (err) {
+ console.log(err);
+ }
+ }
+ }),
+ }),
+})
+
+
+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..7876dac
--- /dev/null
+++ b/src/store/api/BaseUrl.js
@@ -0,0 +1,2 @@
+ 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
new file mode 100644
index 0000000..17765d4
--- /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 getCookie = () => {
+ 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),
})