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

2 note page #55

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
146 changes: 81 additions & 65 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,79 +20,95 @@ import ToDoList from './pages/todolist';
import Calendar from './pages/calendar';
import Settings from './pages/settings';
import Login from './pages/login';
import Modal from './components/widgets/ModalWidget'

import { NavbarModuleContext, initialNavbarModuleContext } from './contexts/NavbarModuleContext';
import {
NavbarModuleContext,
initialNavbarModuleContext,
} from './contexts/NavbarModuleContext';
import { GoogleOAuthProvider } from '@react-oauth/google';
import Notes from './pages/notes/Notes';

const IS_GOOGLE_AVAILABLE =
import.meta.env.VITE_GOOGLE_CLIENT_ID !== "" &&
const IS_GOOGLE_AVAILABLE =
import.meta.env.VITE_GOOGLE_CLIENT_ID !== '' &&
import.meta.env.VITE_GOOGLE_CLIENT_ID !== undefined;
// TODO: pack it to different component
// TODO: pack it to different component

const App = () => {
const [navbarModule, setNavbarModule] = useState(initialNavbarModuleContext);
const [navbarModule, setNavbarModule] = useState(
initialNavbarModuleContext,
);

return (
<main className='Main' themestyle='default' thememode='light'>
<ToastContainer {...toastConfig} theme='light' />
<GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID ?? 'defaultnotvalid'}>
<Provider store={store}>
<NavbarModuleContext.Provider value={{ navbarModule, setNavbarModule }}>
<Router>
<Routes>
<Route
path='/'
element={<Navigate to='/home' replace={true} />}
/>
<Route
path='/home'
element={Layout({
Component: Home,
props: {},
})}
/>
<Route
path='/calendar'
element={Layout({
Component: Calendar,
props: {},
})}
/>
<Route
path='/todolist'
element={Layout({
Component: ToDoList,
props: {},
})}
/>
<Route
path='/categories'
element={Layout({
Component: Home,
props: {},
})}
/>
<Route
path='/notes'
element={Layout({
Component: Home,
props: {},
})}
/>
<Route
path='/settings/*'
element={Layout({
Component: Settings,
props: {},
})}
/>
<Route path='*' element={<div>Not found</div>} />
<Route path='/login' element={<Login />} />
</Routes>
</Router>
</NavbarModuleContext.Provider>
</Provider>
<main className="Main" themestyle="default" thememode="light">
<ToastContainer {...toastConfig} theme="light" />
<GoogleOAuthProvider
clientId={
import.meta.env.VITE_GOOGLE_CLIENT_ID ?? 'defaultnotvalid'
}
>
<Provider store={store}>
<NavbarModuleContext.Provider
value={{ navbarModule, setNavbarModule }}
>
<Router>
<Routes>
<Route
path="/"
element={
<Navigate to="/home" replace={true} />
}
/>
<Route
path="/home"
element={Layout({
Component: Home,
props: {},
})}
/>
<Route
path="/calendar"
element={Layout({
Component: Calendar,
props: {},
})}
/>
<Route
path="/todolist"
element={Layout({
Component: ToDoList,
props: {},
})}
/>
<Route
path="/categories"
element={Layout({
Component: Home,
props: {},
})}
/>
<Route
path="/notes"
element={Layout({
Component: Notes,
props: {},
})}
/>
<Route
path="/settings/*"
element={Layout({
Component: Settings,
props: {},
})}
/>
<Route
path="*"
element={<div>Not found</div>}
/>
<Route path="/login" element={<Login />} />
</Routes>
</Router>
</NavbarModuleContext.Provider>
</Provider>
</GoogleOAuthProvider>
</main>
);
Expand Down
33 changes: 33 additions & 0 deletions src/components/notes/AddNote.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import styles from '#src/styles/components/notes/AddNote.module.scss';
import { notesActions } from '#src/store/slices/Notes/Notes.slice';
import { useDispatch, useSelector } from 'react-redux';
import { notesSelector } from '../../store/slices/Notes';

const AddNote = () => {
const dispatch = useDispatch();
const notes = useSelector(notesSelector);

const initNote = {
title: 'Name your note!',
body: 'Write down your thoughts',
createdAt: new Date().toISOString(),
};

const addNoteHandler = () => {
dispatch(notesActions.addNote(initNote));
console.log(notes.notes);
};
return (
<div role="button" className={styles.main} onClick={addNoteHandler}>
<div className={styles.wrapper}>
<div className={`${styles.lt} ${styles.corner}`}></div>
<div className={`${styles.rt} ${styles.corner}`}></div>
<div className={`${styles.lb} ${styles.corner}`}></div>
<div className={`${styles.rb} ${styles.corner}`}></div>
</div>
<div className={styles.plus}></div>
</div>
);
};

export default AddNote;
127 changes: 127 additions & 0 deletions src/components/notes/Note.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import styles from '#src/styles/components/notes/Note.module.scss';
import { useState, useRef, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { notesActions } from '../../store/slices/Notes';
import ContentEditable from '../widgets/ContentEditable';

const Note = (props) => {
const dispatch = useDispatch();

const [title, setTitle] = useState(props.title);
const [titleChanged, setTitleChanged] = useState(false);
const [content, setContent] = useState(props.content);
const [contentChanged, setContentChanged] = useState(false);

const defaultNote = {
title: 'Name your note!',
content: 'Write down your thoughts',
};

// Input Counter (accessible for both useEffect and onBlurEvent)
let timeoutId = null;

// PTC commences
useEffect(() => {
timeoutId = setTimeout(() => {
if (titleChanged) {
dispatch(
notesActions.updateNoteTitle({ title, noteId: props.id }),
);
setTitleChanged(false);
}
if (contentChanged) {
dispatch(
notesActions.updateNoteContent({
content,
noteId: props.id,
}),
);
setContentChanged(false);
}
}, 5 * 1000);

// Cleanup executes after every input
return () => {
clearTimeout(timeoutId);
};
}, [title, setTitle, content, setContent]);

const stopEditing = (e) => {
if (!e.shiftKey) {
if (e.key === 'Enter' || e.key === 'Escape') {
e.target.blur();
}
}
};

const editTitle = (e) => {
e.preventDefault();
setTitle(e.target.value);
setTitleChanged(true);
};

const onBlurUpdateTitle = () => {
clearTimeout(timeoutId);
if (titleChanged) {
dispatch(notesActions.updateNoteTitle({ title, noteId: props.id }));
setTitleChanged(false);
}
};

const editContent = (e) => {
setContent(e);
setContentChanged(true);
};

const onBlurUpdateContent = () => {
clearTimeout(timeoutId);
if (contentChanged) {
dispatch(
notesActions.updateNoteContent({ content, noteId: props.id }),
);
setContentChanged(false);
}
};

const deleteNoteHandler = () => {
dispatch(notesActions.deleteNoteById({ noteId: props.id }));
};

return (
<div className={styles.block}>
<div className={styles.header}>
<div className={styles.wrapper}>
<input
className={styles.title}
type="text"
value={title}
onChange={editTitle}
onKeyDown={stopEditing}
onBlur={onBlurUpdateTitle}
/>
<div
className={styles.icon}
onClick={deleteNoteHandler}
></div>
</div>
<div className={styles.line}>
<div className={styles.invisible}></div>
<div className={styles.visible}></div>
</div>
</div>
<div className={styles.content}>
<ContentEditable onChange={editContent}>
<p onKeyDown={stopEditing} onBlur={onBlurUpdateContent}>
{props.content}
</p>
</ContentEditable>
</div>
<div className={styles.footer}>
<div className={styles.time}>{props.time}</div>
<div className={styles.date}>{props.date}</div>
</div>
</div>
);
};

export default Note;
17 changes: 17 additions & 0 deletions src/components/notes/SearchBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import styles from '#src/styles/components/notes/SearchBar.module.scss';

const SearchBar = () => {
return (
<div className={styles.container}>
<input
className={styles.searchBar}
type="text"
placeholder="Search notes..."
/>
<div className={styles.horLine}></div>
<div className={styles.box}></div>
</div>
);
};

export default SearchBar;
40 changes: 40 additions & 0 deletions src/components/widgets/ContentEditable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useRef, useEffect } from 'react';

const ContentEditable = (props) => {
// The onChange must happen on a different hierarchy level to not cause a rerender !!!
const { onChange } = props;
// Tracking cloned element
const element = useRef();

let value = element.current?.value || element.current?.innerHTML;

let elements = React.Children.toArray(props.children);
if (elements.length > 1) {
throw Error("Can't have more than one child");
}

// every time the user types, we update the value
const onMouseUp = () => {
value = element.current?.value || element.current?.innerHTML;
if (onChange) {
onChange(value);
}
};
// Do on the first pass
useEffect(() => {
value = element.current?.value || element.current?.innerHTML;
if (onChange) {
onChange(value);
}
}, []);
// Creating the contentEditable element
elements = React.cloneElement(elements[0], {
contentEditable: true,
suppressContentEditableWarning: true,
ref: element,
onKeyUp: onMouseUp,
});
return elements;
};

export default ContentEditable;
Loading