From b19410d1420c23f6eaebb306d1ee3e2fad6cd699 Mon Sep 17 00:00:00 2001 From: ArkadiK94 Date: Fri, 8 Dec 2023 22:35:39 +0200 Subject: [PATCH 1/2] [Feat] Connect Notes To Backend why: so the notes will be stored in db how: change the redux slice file, add fetching data with axios --- .../Dashboard/Notetaker/NoteApp.jsx | 32 ++- .../Dashboard/Notetaker/NoteDescription.jsx | 23 +-- .../Dashboard/Notetaker/NoteItem.jsx | 12 +- .../Dashboard/Notetaker/NoteList.jsx | 2 +- src/features/notes/notesService.js | 66 ++++++- src/features/notes/notesSlice.js | 183 ++++++++++++++---- 6 files changed, 257 insertions(+), 61 deletions(-) diff --git a/src/components/Dashboard/Notetaker/NoteApp.jsx b/src/components/Dashboard/Notetaker/NoteApp.jsx index f94c3aaf..d99bdbde 100644 --- a/src/components/Dashboard/Notetaker/NoteApp.jsx +++ b/src/components/Dashboard/Notetaker/NoteApp.jsx @@ -13,18 +13,27 @@ import "./NoteApp.css"; import NoteList from "./NoteList"; import NoteDescription from "./NoteDescription"; import { useDispatch, useSelector } from "react-redux"; -import { notePin } from "../../../features/notes/notesSlice"; +import { getNotes, noteReset, pinNote } from "../../../features/notes/notesSlice"; +import LoadingSpinner from "../../Other/MixComponents/Spinner/LoadingSpinner"; const NoteApp = () => { const dispatch = useDispatch(); - const { notes } = useSelector(({ notes }) => notes); + const { notes, isNoteLoading, isNoteError, noteMessage } = useSelector((state) => state.notes); const [searchTerm, setSearchTerm] = useState(""); const [filteredNotes, setFilteredNotes] = useState([]); const [pickedNote, setPickedNote] = useState({}); const [needToAdd, setNeedToAdd] = useState(false); useEffect(() => { - const newFilteredNotes = notes.filter((note) => { + if (isNoteError) { + console.log(noteMessage); + } + dispatch(getNotes()); + return () => dispatch(noteReset()); + }, [dispatch, isNoteError, noteMessage]); + + useEffect(() => { + const newFilteredNotes = notes?.filter((note) => { return ( note?.title?.toLowerCase().includes(searchTerm?.toLowerCase()) || note?.description?.toLowerCase().includes(searchTerm?.toLowerCase()) @@ -37,11 +46,13 @@ const NoteApp = () => { setSearchTerm(e.target.value); }; const handlePickNote = (noteId) => { - const pickedNote = notes.find((note) => note.id === noteId); + const pickedNote = notes.find((note) => note._id === noteId); setPickedNote(pickedNote !== -1 ? pickedNote : {}); }; const handlePinNote = (noteId) => { - dispatch(notePin(noteId)); + const pinnedNote = notes.find((note) => note._id === noteId); + const noteData = { ...pinnedNote, pinned: !pinnedNote.pinned }; + dispatch(pinNote({ id: noteId, noteData })); }; const handleOpenAddNewNoteMode = () => { setNeedToAdd(true); @@ -51,6 +62,7 @@ const NoteApp = () => { setNeedToAdd(false); setPickedNote({}); }; + console.log(isNoteLoading); return ( @@ -66,9 +78,13 @@ const NoteApp = () => { onChange={handleSearchTermChange} /> - - {filteredNotes} - + {isNoteLoading ? ( + + ) : ( + + {filteredNotes} + + )} { const dispatch = useDispatch(); @@ -26,7 +26,7 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP }, [children]); const handleDeleteNote = () => { - dispatch(noteRemove(children.id)); + dispatch(deleteNote(children._id)); setShowNote({}); }; const handleClose = () => { @@ -36,6 +36,7 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP }; const handleCopyNoteData = (label, content) => { setShowNote((prevCopyNote) => { + if (label === "description") label = "content"; return { ...prevCopyNote, [label]: content, @@ -43,16 +44,16 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP }); }; const handleSaveNote = (newNote) => { - if (!newNote.title && !newNote.description) { - dispatch(noteRemove(newNote.id)); + if (!newNote.title && !newNote.content) { + dispatch(deleteNote(newNote._id)); onChangePickedNote({}); handleClose(); return; } if (needToEdit) { - dispatch(noteEdit({ ...newNote, id: children.id })); + dispatch(updateNote({ id: children._id, noteData: newNote })); } else if (needToAdd) { - dispatch(noteAdd(newNote)); + dispatch(createNote(newNote)); } onChangePickedNote(newNote); handleClose(); @@ -60,10 +61,10 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP return ( - {!needToAdd && !needToEdit && (showNote.title || showNote.description) && ( + {!needToAdd && !needToEdit && (showNote.title || showNote.content) && ( - + ) : ( - {showNote.title || (showNote.id ? `UntitledNote #${showNote.id.substr(0, 5)}` : "")} + {showNote.title || (showNote._id ? `UntitledNote #${showNote._id.substr(0, 5)}` : "")} )} {needToAdd || needToEdit ? ( ) : ( )} diff --git a/src/components/Dashboard/Notetaker/NoteItem.jsx b/src/components/Dashboard/Notetaker/NoteItem.jsx index 0f458f95..b66e5919 100644 --- a/src/components/Dashboard/Notetaker/NoteItem.jsx +++ b/src/components/Dashboard/Notetaker/NoteItem.jsx @@ -13,23 +13,23 @@ const shortText = (text, letters) => { return textCleanFromTags?.length > letters ? `${textCleanFromTags.slice(0, letters)}...` : textCleanFromTags; }; -const NoteItem = ({ id, title, description, pinned, onPick, onPin }) => { +const NoteItem = ({ _id, title, content, pinned, onPick, onPin }) => { const [shortTitle, setShortTitle] = useState(""); const [shortDescr, setShortDescr] = useState(""); useEffect(() => { - setShortTitle(() => (title ? shortText(title, 30) : `UntitledNote #${id.substr(0, 5)}`)); - setShortDescr(() => (description ? shortText(description, 60) : "undescribedNote")); - }, [title, description]); + setShortTitle(() => (title ? shortText(title, 30) : `UntitledNote #${_id.substr(0, 5)}`)); + setShortDescr(() => (content ? shortText(content, 60) : "undescribedNote")); + }, [title, content]); return ( - onPick(id)}> + onPick(_id)}> {shortTitle} {shortDescr} - + ); diff --git a/src/components/Dashboard/Notetaker/NoteList.jsx b/src/components/Dashboard/Notetaker/NoteList.jsx index c7bff4a8..d5c61180 100644 --- a/src/components/Dashboard/Notetaker/NoteList.jsx +++ b/src/components/Dashboard/Notetaker/NoteList.jsx @@ -7,7 +7,7 @@ const NoteList = ({ children, onPick, onPin }) => { {!children.length && There Are No Notes} {children.map((note) => ( - + ))} ); diff --git a/src/features/notes/notesService.js b/src/features/notes/notesService.js index 5fc87a40..f9cef1e1 100644 --- a/src/features/notes/notesService.js +++ b/src/features/notes/notesService.js @@ -1 +1,65 @@ -// we will add here the services after finising with the back-end +import axios from "axios"; +import { getApiUrl } from "../apiUrl"; + +const API_URL = getApiUrl("api/notes/"); + +// Create new note +const createNote = async (noteData, token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.post(API_URL, noteData, config); + + return response.data; +}; + +// Update note +const updateNote = async (id, noteData, token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.put(API_URL + id, noteData, config); + + return response.data; +}; + +// Get user notes +const getNotes = async (token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.get(API_URL, config); + + return response.data; +}; + +// Delete user note +const deleteNote = async (noteId, token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.delete(API_URL + noteId, config); + + return response.data; +}; + +const notesService = { + createNote, + updateNote, + getNotes, + deleteNote, +}; + +export default notesService; diff --git a/src/features/notes/notesSlice.js b/src/features/notes/notesSlice.js index 9d10765e..8df3237d 100644 --- a/src/features/notes/notesSlice.js +++ b/src/features/notes/notesSlice.js @@ -1,46 +1,161 @@ -import { createSlice, nanoid } from "@reduxjs/toolkit"; +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import notesService from "./notesService"; const initialState = { notes: [], + isNoteError: false, + isNoteSuccess: false, + isNoteLoading: false, + noteMessage: "", }; -export const notesSlice = createSlice({ +// Create new note +export const createNote = createAsyncThunk("notes/create", async (noteData, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.createNote(noteData, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Update existing note +export const updateNote = createAsyncThunk("notes/update", async ({ id, noteData }, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.updateNote(id, noteData, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Update note pinned status +export const pinNote = createAsyncThunk("notes/pin", async ({ id, noteData }, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.updateNote(id, noteData, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Get user notes +export const getNotes = createAsyncThunk("notes/getNotes", async (_, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.getNotes(token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Delete user note +export const deleteNote = createAsyncThunk("notes/delete", async (id, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.deleteNote(id, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +export const noteSlice = createSlice({ name: "notes", initialState, reducers: { - noteAdd: { - reducer: (state, action) => { - state.notes = [...state.notes, action.payload]; - }, - prepare: (note) => { - const id = nanoid(); - return { payload: { ...note, id } }; - }, - }, - noteRemove: (state, action) => { - state.notes = state.notes.filter((note) => note.id !== action.payload); - }, - noteEdit: (state, action) => { - const editedNote = action.payload; - const indexOfEditedNote = state.notes.findIndex((note) => note.id === editedNote.id); - if (indexOfEditedNote < 0) state.notes = [...state.notes, editedNote]; - state.notes[indexOfEditedNote] = { ...state.notes[indexOfEditedNote], ...editedNote }; - }, - notePin: (state, action) => { - const pinnedNoteIndex = state.notes.findIndex((note) => note.id === action.payload); - if (pinnedNoteIndex > -1) { - state.notes[pinnedNoteIndex].pinned = !state.notes[pinnedNoteIndex].pinned; - } - state.notes.sort((a, b) => { - if (a.pinned !== b.pinned) { - if (a.pinned === true) return -1; - return 1; - } - return 0; + noteReset: () => initialState, + }, + extraReducers: (builder) => { + builder + .addCase(createNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(createNote.fulfilled, (state, action) => { + state.isNoteSuccess = true; + state.isNoteLoading = false; + state.isNoteError = false; + state.notes.push(action.payload); + }) + .addCase(createNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.isNoteSuccess = false; + state.noteMessage = action.payload; + }) + .addCase(updateNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(updateNote.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = state.notes.map((note) => + note._id === action.payload._id ? { ...note, ...action.payload } : note, + ); + }) + .addCase(updateNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; + }) + .addCase(pinNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(pinNote.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = state.notes + .map((note) => (note._id === action.payload._id ? { ...note, ...action.payload } : note)) + .toSorted((a, b) => { + if (a.pinned !== b.pinned) { + if (a.pinned === true) return -1; + return 1; + } + return 0; + }); + }) + .addCase(pinNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; + }) + .addCase(getNotes.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(getNotes.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = action.payload; + }) + .addCase(getNotes.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; + }) + .addCase(deleteNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(deleteNote.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = state.notes.filter((note) => note._id !== action.payload.id); + }) + .addCase(deleteNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; }); - }, }, }); -export const { notesReset, noteAdd, noteRemove, noteEdit, notePin } = notesSlice.actions; -export default notesSlice.reducer; +export const { noteReset } = noteSlice.actions; +export default noteSlice.reducer; From a6efcc38f619da1e0273996ea70658607540b785 Mon Sep 17 00:00:00 2001 From: ArkadiK94 Date: Fri, 8 Dec 2023 22:35:39 +0200 Subject: [PATCH 2/2] [Feat] Connect Notes To Backend why: so the notes will be stored in db how: change the redux slice file, add fetching data with axios --- .../Dashboard/Notetaker/NoteApp.jsx | 31 ++- .../Dashboard/Notetaker/NoteDescription.jsx | 23 +-- .../Dashboard/Notetaker/NoteItem.jsx | 12 +- .../Dashboard/Notetaker/NoteList.jsx | 2 +- src/features/notes/notesService.js | 66 ++++++- src/features/notes/notesSlice.js | 183 ++++++++++++++---- 6 files changed, 256 insertions(+), 61 deletions(-) diff --git a/src/components/Dashboard/Notetaker/NoteApp.jsx b/src/components/Dashboard/Notetaker/NoteApp.jsx index f94c3aaf..7e51e5f5 100644 --- a/src/components/Dashboard/Notetaker/NoteApp.jsx +++ b/src/components/Dashboard/Notetaker/NoteApp.jsx @@ -13,18 +13,27 @@ import "./NoteApp.css"; import NoteList from "./NoteList"; import NoteDescription from "./NoteDescription"; import { useDispatch, useSelector } from "react-redux"; -import { notePin } from "../../../features/notes/notesSlice"; +import { getNotes, noteReset, pinNote } from "../../../features/notes/notesSlice"; +import LoadingSpinner from "../../Other/MixComponents/Spinner/LoadingSpinner"; const NoteApp = () => { const dispatch = useDispatch(); - const { notes } = useSelector(({ notes }) => notes); + const { notes, isNoteLoading, isNoteError, noteMessage } = useSelector((state) => state.notes); const [searchTerm, setSearchTerm] = useState(""); const [filteredNotes, setFilteredNotes] = useState([]); const [pickedNote, setPickedNote] = useState({}); const [needToAdd, setNeedToAdd] = useState(false); useEffect(() => { - const newFilteredNotes = notes.filter((note) => { + if (isNoteError) { + console.log(noteMessage); + } + dispatch(getNotes()); + return () => dispatch(noteReset()); + }, [dispatch, isNoteError, noteMessage]); + + useEffect(() => { + const newFilteredNotes = notes?.filter((note) => { return ( note?.title?.toLowerCase().includes(searchTerm?.toLowerCase()) || note?.description?.toLowerCase().includes(searchTerm?.toLowerCase()) @@ -37,11 +46,13 @@ const NoteApp = () => { setSearchTerm(e.target.value); }; const handlePickNote = (noteId) => { - const pickedNote = notes.find((note) => note.id === noteId); + const pickedNote = notes.find((note) => note._id === noteId); setPickedNote(pickedNote !== -1 ? pickedNote : {}); }; const handlePinNote = (noteId) => { - dispatch(notePin(noteId)); + const pinnedNote = notes.find((note) => note._id === noteId); + const noteData = { ...pinnedNote, pinned: !pinnedNote.pinned }; + dispatch(pinNote({ id: noteId, noteData })); }; const handleOpenAddNewNoteMode = () => { setNeedToAdd(true); @@ -66,9 +77,13 @@ const NoteApp = () => { onChange={handleSearchTermChange} /> - - {filteredNotes} - + {isNoteLoading ? ( + + ) : ( + + {filteredNotes} + + )} { const dispatch = useDispatch(); @@ -26,7 +26,7 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP }, [children]); const handleDeleteNote = () => { - dispatch(noteRemove(children.id)); + dispatch(deleteNote(children._id)); setShowNote({}); }; const handleClose = () => { @@ -36,6 +36,7 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP }; const handleCopyNoteData = (label, content) => { setShowNote((prevCopyNote) => { + if (label === "description") label = "content"; return { ...prevCopyNote, [label]: content, @@ -43,16 +44,16 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP }); }; const handleSaveNote = (newNote) => { - if (!newNote.title && !newNote.description) { - dispatch(noteRemove(newNote.id)); + if (!newNote.title && !newNote.content) { + dispatch(deleteNote(newNote._id)); onChangePickedNote({}); handleClose(); return; } if (needToEdit) { - dispatch(noteEdit({ ...newNote, id: children.id })); + dispatch(updateNote({ id: children._id, noteData: newNote })); } else if (needToAdd) { - dispatch(noteAdd(newNote)); + dispatch(createNote(newNote)); } onChangePickedNote(newNote); handleClose(); @@ -60,10 +61,10 @@ const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangeP return ( - {!needToAdd && !needToEdit && (showNote.title || showNote.description) && ( + {!needToAdd && !needToEdit && (showNote.title || showNote.content) && ( - + ) : ( - {showNote.title || (showNote.id ? `UntitledNote #${showNote.id.substr(0, 5)}` : "")} + {showNote.title || (showNote._id ? `UntitledNote #${showNote._id.substr(0, 5)}` : "")} )} {needToAdd || needToEdit ? ( ) : ( )} diff --git a/src/components/Dashboard/Notetaker/NoteItem.jsx b/src/components/Dashboard/Notetaker/NoteItem.jsx index 0f458f95..b66e5919 100644 --- a/src/components/Dashboard/Notetaker/NoteItem.jsx +++ b/src/components/Dashboard/Notetaker/NoteItem.jsx @@ -13,23 +13,23 @@ const shortText = (text, letters) => { return textCleanFromTags?.length > letters ? `${textCleanFromTags.slice(0, letters)}...` : textCleanFromTags; }; -const NoteItem = ({ id, title, description, pinned, onPick, onPin }) => { +const NoteItem = ({ _id, title, content, pinned, onPick, onPin }) => { const [shortTitle, setShortTitle] = useState(""); const [shortDescr, setShortDescr] = useState(""); useEffect(() => { - setShortTitle(() => (title ? shortText(title, 30) : `UntitledNote #${id.substr(0, 5)}`)); - setShortDescr(() => (description ? shortText(description, 60) : "undescribedNote")); - }, [title, description]); + setShortTitle(() => (title ? shortText(title, 30) : `UntitledNote #${_id.substr(0, 5)}`)); + setShortDescr(() => (content ? shortText(content, 60) : "undescribedNote")); + }, [title, content]); return ( - onPick(id)}> + onPick(_id)}> {shortTitle} {shortDescr} - + ); diff --git a/src/components/Dashboard/Notetaker/NoteList.jsx b/src/components/Dashboard/Notetaker/NoteList.jsx index c7bff4a8..d5c61180 100644 --- a/src/components/Dashboard/Notetaker/NoteList.jsx +++ b/src/components/Dashboard/Notetaker/NoteList.jsx @@ -7,7 +7,7 @@ const NoteList = ({ children, onPick, onPin }) => { {!children.length && There Are No Notes} {children.map((note) => ( - + ))} ); diff --git a/src/features/notes/notesService.js b/src/features/notes/notesService.js index 5fc87a40..f9cef1e1 100644 --- a/src/features/notes/notesService.js +++ b/src/features/notes/notesService.js @@ -1 +1,65 @@ -// we will add here the services after finising with the back-end +import axios from "axios"; +import { getApiUrl } from "../apiUrl"; + +const API_URL = getApiUrl("api/notes/"); + +// Create new note +const createNote = async (noteData, token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.post(API_URL, noteData, config); + + return response.data; +}; + +// Update note +const updateNote = async (id, noteData, token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.put(API_URL + id, noteData, config); + + return response.data; +}; + +// Get user notes +const getNotes = async (token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.get(API_URL, config); + + return response.data; +}; + +// Delete user note +const deleteNote = async (noteId, token) => { + const config = { + headers: { + Authorization: `Bearer ${token}`, + }, + }; + + const response = await axios.delete(API_URL + noteId, config); + + return response.data; +}; + +const notesService = { + createNote, + updateNote, + getNotes, + deleteNote, +}; + +export default notesService; diff --git a/src/features/notes/notesSlice.js b/src/features/notes/notesSlice.js index 9d10765e..8df3237d 100644 --- a/src/features/notes/notesSlice.js +++ b/src/features/notes/notesSlice.js @@ -1,46 +1,161 @@ -import { createSlice, nanoid } from "@reduxjs/toolkit"; +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import notesService from "./notesService"; const initialState = { notes: [], + isNoteError: false, + isNoteSuccess: false, + isNoteLoading: false, + noteMessage: "", }; -export const notesSlice = createSlice({ +// Create new note +export const createNote = createAsyncThunk("notes/create", async (noteData, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.createNote(noteData, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Update existing note +export const updateNote = createAsyncThunk("notes/update", async ({ id, noteData }, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.updateNote(id, noteData, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Update note pinned status +export const pinNote = createAsyncThunk("notes/pin", async ({ id, noteData }, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.updateNote(id, noteData, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Get user notes +export const getNotes = createAsyncThunk("notes/getNotes", async (_, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.getNotes(token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +// Delete user note +export const deleteNote = createAsyncThunk("notes/delete", async (id, thunkAPI) => { + try { + const token = thunkAPI.getState().auth.user.token; + return await notesService.deleteNote(id, token); + } catch (error) { + const message = + (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); + return thunkAPI.rejectWithValue(message); + } +}); + +export const noteSlice = createSlice({ name: "notes", initialState, reducers: { - noteAdd: { - reducer: (state, action) => { - state.notes = [...state.notes, action.payload]; - }, - prepare: (note) => { - const id = nanoid(); - return { payload: { ...note, id } }; - }, - }, - noteRemove: (state, action) => { - state.notes = state.notes.filter((note) => note.id !== action.payload); - }, - noteEdit: (state, action) => { - const editedNote = action.payload; - const indexOfEditedNote = state.notes.findIndex((note) => note.id === editedNote.id); - if (indexOfEditedNote < 0) state.notes = [...state.notes, editedNote]; - state.notes[indexOfEditedNote] = { ...state.notes[indexOfEditedNote], ...editedNote }; - }, - notePin: (state, action) => { - const pinnedNoteIndex = state.notes.findIndex((note) => note.id === action.payload); - if (pinnedNoteIndex > -1) { - state.notes[pinnedNoteIndex].pinned = !state.notes[pinnedNoteIndex].pinned; - } - state.notes.sort((a, b) => { - if (a.pinned !== b.pinned) { - if (a.pinned === true) return -1; - return 1; - } - return 0; + noteReset: () => initialState, + }, + extraReducers: (builder) => { + builder + .addCase(createNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(createNote.fulfilled, (state, action) => { + state.isNoteSuccess = true; + state.isNoteLoading = false; + state.isNoteError = false; + state.notes.push(action.payload); + }) + .addCase(createNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.isNoteSuccess = false; + state.noteMessage = action.payload; + }) + .addCase(updateNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(updateNote.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = state.notes.map((note) => + note._id === action.payload._id ? { ...note, ...action.payload } : note, + ); + }) + .addCase(updateNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; + }) + .addCase(pinNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(pinNote.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = state.notes + .map((note) => (note._id === action.payload._id ? { ...note, ...action.payload } : note)) + .toSorted((a, b) => { + if (a.pinned !== b.pinned) { + if (a.pinned === true) return -1; + return 1; + } + return 0; + }); + }) + .addCase(pinNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; + }) + .addCase(getNotes.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(getNotes.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = action.payload; + }) + .addCase(getNotes.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; + }) + .addCase(deleteNote.pending, (state) => { + state.isNoteLoading = true; + }) + .addCase(deleteNote.fulfilled, (state, action) => { + state.isNoteLoading = false; + state.isNoteSuccess = true; + state.notes = state.notes.filter((note) => note._id !== action.payload.id); + }) + .addCase(deleteNote.rejected, (state, action) => { + state.isNoteLoading = false; + state.isNoteError = true; + state.noteMessage = action.payload; }); - }, }, }); -export const { notesReset, noteAdd, noteRemove, noteEdit, notePin } = notesSlice.actions; -export default notesSlice.reducer; +export const { noteReset } = noteSlice.actions; +export default noteSlice.reducer;