Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Note App Add/Edit Notes , Managing State with Redux #531

Merged
merged 5 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import authReducer from "../features/auth/authSlice";
import userDetailReducer from "../features/userDetail/userDetailSlice";
import goalReducer from "../features/goals/goalSlice";
import blogReducer from "../features/blogs/blogSlice";
import notesReducer from "../features/notes/notesSlice";
import forumReducer from "../features/forum/forumSlice";
import viewReducer from "../features/feeds/views/viewSlice";
import feedReducer from "../features/feeds/feedsSlice";
Expand All @@ -26,6 +27,8 @@ export default configureStore({
blogs: blogReducer,
blogComments: blogCommentsReducer,

notes: notesReducer,

forums: forumReducer,

feeds: feedReducer,
Expand Down
9 changes: 7 additions & 2 deletions src/components/Common/InputEditor/InputEditor.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import React, { useEffect, useState } from "react";
import { InputEditorContainer, InputEditorTheInput, InputEditorLabel, InputEditorPreview } from "./InputEditorElements";

const InputEditor = ({ content, label }) => {
const InputEditor = ({ content, label, onCopyChanges }) => {
const [value, setValue] = useState("");

useEffect(() => {
setValue(content);
}, [content, label]);

const handleChange = (e) => {
setValue(e.target.value);
onCopyChanges(label, e.target.value);
};

return (
<InputEditorContainer>
<InputEditorLabel>{label}</InputEditorLabel>
<InputEditorPreview>{value}</InputEditorPreview>
<InputEditorTheInput type="text" onChange={(e) => setValue(e.target.value)} value={value} />
<InputEditorTheInput type="text" onChange={handleChange} value={value} />
</InputEditorContainer>
);
};
Expand Down
16 changes: 12 additions & 4 deletions src/components/Common/MarkdownEditor/MarkdownEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,36 @@ import {
import rehypeSanitize from "rehype-sanitize";
import "./MarkdownEditor.css";

const MarkdownEditor = ({ content, label, previewModeOnly }) => {
const MarkdownEditor = ({ content, label, previewModeOnly, onCopyChanges }) => {
const [value, setValue] = useState();
useEffect(() => {
setValue(content);
}, [content]);
}, [content, label]);

if (previewModeOnly)
return <MDEditor.Markdown source={value} style={{ whiteSpace: "normal", backgroundColor: "#000" }} />;

const handleChange = (value) => {
setValue(value);
onCopyChanges(label, value);
};
return (
<MarkdownContainer>
<MarkdownLabel>{label}</MarkdownLabel>
<MarkdownEditorPreviewContainer>
<MDEditor.Markdown
source={value}
style={{ whiteSpace: "normal", paddingLeft: "5px", paddingRight: "5px" }}
style={{
whiteSpace: "normal",
paddingLeft: "5px",
paddingRight: "5px",
}}
/>
</MarkdownEditorPreviewContainer>
<MarkdownEditorContainer>
<MDEditor
value={value}
onChange={setValue}
onChange={handleChange}
previewOptions={{
rehypePlugins: [[rehypeSanitize]],
}}
Expand Down
83 changes: 7 additions & 76 deletions src/components/Dashboard/Notetaker/NoteApp.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// import NoteListOld from "./NoteListOld";
// import { nanoid } from "nanoid";
import React, { useEffect, useState } from "react";
import { RxHamburgerMenu } from "react-icons/rx";
import { MdNoteAdd } from "react-icons/md";
Expand All @@ -14,54 +12,17 @@ import SearchInputBox from "../../Common/SearchInputBox";
import "./NoteApp.css";
import NoteList from "./NoteList";
import NoteDescription from "./NoteDescription";

const DUMMY_DATA = [
{
id: 1,
title: "Exploring JWT Security and Vulnerabilities",
description:
"JSON Web Tokens (JWT) are widely used for authentication and authorization. However, like any technology, they have potential vulnerabilities that need to be understood and addressed.",
},
{
id: 2,
description:
"We take the security of our website very seriously and appreciate the contributions of security researchers to help keep our website secure. If you discover a security vulnerability, please report it to us using the contact information provided below. We ask that you please do not publicly disclose the vulnerability until we have had a chance to investigate and address the issue.",
pinned: true,
},
{
id: 3,
title: "What is two-factor authentication and how does it work?",
pinned: false,
},
];
import { useDispatch, useSelector } from "react-redux";
import { notePin } from "../../../features/notes/notesSlice";

const NoteApp = () => {
const [notes, setNotes] = useState(DUMMY_DATA);
const dispatch = useDispatch();
const { notes } = useSelector(({ notes }) => notes);
const [searchTerm, setSearchTerm] = useState("");
const [filteredNotes, setFilteredNotes] = useState([]);
const [pickedNote, setPickedNote] = useState({});
const [needToAdd, setNeedToAdd] = useState(false);

useEffect(() => {
const savedNotes = JSON.parse(localStorage.getItem("react-notes-app-data"));
if (savedNotes !== "") {
setNotes((savedNotes) => {
const sortNotes = savedNotes.sort((a, b) => {
if (a.pinned !== b.pinned) {
if (a.pinned === true) return -1;
return 1;
}
return 0;
});
return [...sortNotes];
});
}
}, []);

useEffect(() => {
localStorage.setItem("react-notes-app-data", JSON.stringify(notes));
}, [notes]);

useEffect(() => {
const newFilteredNotes = notes.filter((note) => {
return (
Expand All @@ -80,38 +41,11 @@ const NoteApp = () => {
setPickedNote(pickedNote !== -1 ? pickedNote : {});
};
const handlePinNote = (noteId) => {
let needToHappen = true;
setNotes((prevNotes) => {
const pinnedNoteIndex = prevNotes.findIndex((note) => note.id === noteId);
if (pinnedNoteIndex > -1 && needToHappen) {
prevNotes[pinnedNoteIndex].pinned = !prevNotes[pinnedNoteIndex].pinned;
}
needToHappen = false;
const sortNotes = prevNotes.sort((a, b) => {
if (a.pinned !== b.pinned) {
if (a.pinned === true) return -1;
return 1;
}
return 0;
});
return [...sortNotes];
});
dispatch(notePin(noteId));
};
const handleOpenAddNewNoteMode = () => {
setNeedToAdd(true);
};
// const addNote = (text) => {
// const newNote = {
// text,
// id: nanoid(),
// };
// const newNotes = [...notes, newNote];
// setNotes(newNotes);
// };

const handleDataWhenDeleteNote = (id) => {
const newNotes = notes?.filter((note) => note.id !== id);
setNotes(newNotes);
setPickedNote({});
};
const handleCloseMDEditorMode = () => {
setNeedToAdd(false);
Expand All @@ -132,22 +66,19 @@ const NoteApp = () => {
onChange={handleSearchTermChange}
/>
</SearchContainer>

<NoteList onPin={handlePinNote} onPick={handlePickNote}>
{filteredNotes}
</NoteList>
</NotesSidebarContainer>
<NoteDescription
onPin={handlePinNote}
onDelete={handleDataWhenDeleteNote}
needToAdd={needToAdd}
onCloseAddMode={handleCloseMDEditorMode}
onChangePickedNote={setPickedNote}
>
{pickedNote}
</NoteDescription>
</NotesContainer>

// <NoteList notes={notes} handleAddNote={addNote} handleDeleteNote={deleteNote} />
);
};

Expand Down
46 changes: 39 additions & 7 deletions src/components/Dashboard/Notetaker/NoteDescription.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,47 @@ import NotePinning from "./NotePinning";
import { RiMore2Fill } from "react-icons/ri";
import MarkdownEditor from "../../Common/MarkdownEditor";
import InputEditor from "../../Common/InputEditor";
import { useDispatch } from "react-redux";
import { noteAdd, noteEdit, noteRemove } from "../../../features/notes/notesSlice";

const NoteDescription = ({ children, onPin, onDelete, needToAdd, onCloseAddMode }) => {
const NoteDescription = ({ children, onPin, needToAdd, onCloseAddMode, onChangePickedNote }) => {
const dispatch = useDispatch();
const [showNote, setShowNote] = useState(children);
const [needToEdit, setNeedToEdit] = useState(false);

useEffect(() => {
setShowNote(children);
}, [children]);

const handleDeleteNote = () => {
onDelete(children.id);
dispatch(noteRemove(children.id));
setShowNote({});
};
const handleClose = () => {
if (needToEdit) return setNeedToEdit(false);
onCloseAddMode(false);
setShowNote({});
};
const handleSaveNote = () => {
const handleCopyNoteData = (label, content) => {
setShowNote((prevCopyNote) => {
return {
...prevCopyNote,
[label]: content,
};
});
};
const handleSaveNote = (newNote) => {
if (!newNote.title && !newNote.description) {
dispatch(noteRemove(newNote.id));
onChangePickedNote({});
handleClose();
return;
}
if (needToEdit) {
dispatch(noteEdit({ ...newNote, id: children.id }));
} else if (needToAdd) {
dispatch(noteAdd(newNote));
}
onChangePickedNote(newNote);
handleClose();
};
return (
Expand All @@ -53,7 +75,12 @@ const NoteDescription = ({ children, onPin, onDelete, needToAdd, onCloseAddMode
)}
{(needToAdd || needToEdit) && (
<NotesDescriptionIconsContainer icons={2}>
<BiSolidSave className="icon icon-save" size="24px" title="Save" onClick={handleSaveNote} />
<BiSolidSave
className="icon icon-save"
size="24px"
title="Save"
onClick={() => handleSaveNote(showNote)}
/>
<MdCancel className="icon icon-cancel" size="24px" title="Cancel" onClick={handleClose} />
<RiMore2Fill className="icon" size="24px" title="More" />
</NotesDescriptionIconsContainer>
Expand All @@ -62,10 +89,14 @@ const NoteDescription = ({ children, onPin, onDelete, needToAdd, onCloseAddMode
<NotesDescription>
<DescriptionTitle>
{needToAdd || needToEdit ? (
<InputEditor label="title" content={needToEdit && showNote.title ? showNote.title : ""} />
<InputEditor
label="title"
content={needToEdit && showNote.title ? showNote.title : ""}
onCopyChanges={handleCopyNoteData}
/>
) : (
<DescriptionDisplayTitle>
{showNote.title || (showNote.id ? `UntitledNote #${showNote.id}` : "")}
{showNote.title || (showNote.id ? `UntitledNote #${showNote.id.substr(0, 5)}` : "")}
</DescriptionDisplayTitle>
)}
</DescriptionTitle>
Expand All @@ -74,6 +105,7 @@ const NoteDescription = ({ children, onPin, onDelete, needToAdd, onCloseAddMode
<MarkdownEditor
content={needToEdit && showNote.description ? showNote.description : ""}
label="description"
onCopyChanges={handleCopyNoteData}
/>
) : (
<MarkdownEditor
Expand Down
11 changes: 10 additions & 1 deletion src/components/Dashboard/Notetaker/NoteElements.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export const NotesSidebarHeaderTitle = styled.h3`
margin-bottom: 0;
font-family: "Roboto Mono", monospace;
`;
export const NotesListNoFound = styled.h4`
color: #787878;
text-align: center;
margin-bottom: 0;
font-family: "Roboto Mono", monospace;
`;
export const SearchContainer = styled.div`
display: flex;
border: 2px solid #111111;
Expand Down Expand Up @@ -82,6 +88,7 @@ export const NoteItemElement = styled.li`
`;

export const NoteItemShortTitle = styled.h4`
text-transform: capitalize;
font-weight: 900;
`;

Expand Down Expand Up @@ -129,4 +136,6 @@ export const DescriptionTitle = styled.div``;

export const DescriptionContent = styled.div``;

export const DescriptionDisplayTitle = styled.h1``;
export const DescriptionDisplayTitle = styled.h1`
text-transform: capitalize;
`;
4 changes: 2 additions & 2 deletions src/components/Dashboard/Notetaker/NoteItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import NotePinning from "./NotePinning";

const shortText = (text, letters) => {
const textCleanFromTags = text?.replace(/<[^>]+>/g, "");
const textCleanFromTags = text?.replace(/<[^>]+>|-|\[|\]|#/g, "");
return textCleanFromTags?.length > letters ? `${textCleanFromTags.slice(0, letters)}...` : textCleanFromTags;
};

Expand All @@ -18,7 +18,7 @@ const NoteItem = ({ id, title, description, pinned, onPick, onPin }) => {
const [shortDescr, setShortDescr] = useState("");

useEffect(() => {
setShortTitle(() => (title ? shortText(title, 30) : `UntitledNote #${id}`));
setShortTitle(() => (title ? shortText(title, 30) : `UntitledNote #${id.substr(0, 5)}`));
setShortDescr(() => (description ? shortText(description, 60) : "undescribedNote"));
}, [title, description]);

Expand Down
3 changes: 2 additions & 1 deletion src/components/Dashboard/Notetaker/NoteList.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from "react";
import { NotesListContainer } from "./NoteElements";
import { NotesListContainer, NotesListNoFound } from "./NoteElements";
import NoteItem from "./NoteItem";

const NoteList = ({ children, onPick, onPin }) => {
return (
<NotesListContainer>
{!children.length && <NotesListNoFound>There Are No Notes</NotesListNoFound>}
{children.map((note) => (
<NoteItem key={note.id} {...note} onPick={onPick} onPin={onPin} />
))}
Expand Down
Loading