diff --git a/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.css b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.css
new file mode 100644
index 0000000..e99f514
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.css
@@ -0,0 +1,24 @@
+.container {
+ text-align: center;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.container strong {
+ font-size: 20px;
+ line-height: 26px;
+}
+
+.container p {
+ font-size: 16px;
+ line-height: 22px;
+ color: #8c8c8c;
+ margin: 0;
+}
+
+.container a {
+ text-decoration: none;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.tsx b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.tsx
new file mode 100644
index 0000000..7f14436
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.tsx
@@ -0,0 +1,21 @@
+import './ExploreContainer.css';
+
+const ExploreContainer = (): React.JSX.Element => {
+ return (
+
+ );
+};
+
+export default ExploreContainer;
diff --git a/03_source/mobile/src/pages/DemoReactNotes/index.tsx b/03_source/mobile/src/pages/DemoReactNotes/index.tsx
index ad6dd84..8891e53 100644
--- a/03_source/mobile/src/pages/DemoReactNotes/index.tsx
+++ b/03_source/mobile/src/pages/DemoReactNotes/index.tsx
@@ -1,38 +1,27 @@
-import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
+import { IonRouterOutlet, IonTabs } from '@ionic/react';
-import { cloudOutline, searchOutline } from 'ionicons/icons';
import { Route, Redirect } from 'react-router';
-import Tab1 from './AppPages/Tab1';
-import Tab2 from './AppPages/Tab2';
+// import Tab1 from './AppPages/Tab1';
+// import Tab2 from './AppPages/Tab2';
-import './style.scss';
+import './theme/variables.scss';
+import Home from './pages/Home';
+import Add from './pages/Add';
function DemoReactNotes() {
return (
-
+
-
-
+
+
-
-
+
+
-
+
-
- {/* */}
-
-
-
- Dashboard
-
-
-
- Search
-
-
);
}
diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Add.module.scss b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.module.scss
new file mode 100644
index 0000000..490a0ea
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.module.scss
@@ -0,0 +1,29 @@
+.title {
+
+ margin-top: 0.35rem;
+}
+
+.customInput {
+
+ border-radius: 22px !important;
+ --padding-bottom: 1rem;
+ --padding-top: 1rem;
+
+ box-shadow: 0 0.7px 0.9px rgba(0, 0, 0, 0.101),
+ 0 0.9px 2.5px rgba(0, 0, 0, 0.145),
+ 0 2.5px 6px rgba(0, 0, 0, 0.189),
+ 0 8px 20px rgba(0, 0, 0, 0.29);
+}
+
+.customInput {
+
+ margin-bottom: 1rem !important;
+}
+
+.saveButton {
+
+ --border-radius: 12px !important;
+
+ --padding-top: 1.75rem !important;
+ --padding-bottom: 1.75rem !important;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Add.tsx b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.tsx
new file mode 100644
index 0000000..4d58927
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.tsx
@@ -0,0 +1,115 @@
+import {
+ IonBackButton,
+ IonButton,
+ IonButtons,
+ IonCol,
+ IonContent,
+ IonGrid,
+ IonHeader,
+ IonIcon,
+ IonItem,
+ IonLabel,
+ IonPage,
+ IonRow,
+ IonSelect,
+ IonSelectOption,
+ IonTextarea,
+ IonTitle,
+ IonToolbar,
+ useIonRouter,
+} from '@ionic/react';
+import styles from './Add.module.scss';
+
+import { checkmarkOutline } from 'ionicons/icons';
+import { getCategories, getNotes } from '../store/Selectors';
+import { CategoryStore, NoteStore } from '../store';
+import { addNote } from '../store/NoteStore';
+import { useState } from 'react';
+
+const Add = (): React.JSX.Element => {
+ const categories = CategoryStore.useState(getCategories);
+ const notes = NoteStore.useState(getNotes);
+ const [noteCategory, setNoteCategory] = useState(false);
+ const [noteContent, setNoteContent] = useState('');
+ const router = useIonRouter();
+
+ const add = () => {
+ const note = {
+ id: notes.length + 1,
+ category_id: noteCategory,
+ note: noteContent,
+ complete: false,
+ };
+
+ addNote(note);
+ router.goBack();
+ };
+
+ return (
+
+
+
+
+
+
+
+ Add note
+
+
+
+
+
+
+ Add a note
+
+
+
+
+
+
+ Category
+ setNoteCategory(e.target.value)}
+ >
+ {categories.map((category) => {
+ return (
+
+ {category.name}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+ Note
+ setNoteContent(e.target.value)}
+ placeholder="Enter note text here..."
+ />
+
+
+
+
+
+
+
+
+ Save note
+
+
+
+
+
+
+ );
+};
+
+export default Add;
diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Home.module.scss b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.module.scss
new file mode 100644
index 0000000..ec7c818
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.module.scss
@@ -0,0 +1,130 @@
+.heading {
+
+ color: var(--lighter-blue-color);
+ font-size: 1rem;
+}
+
+.mainTitle {
+
+ font-size: 2rem;
+ font-weight: 700;
+}
+
+.slideHeader {
+
+ margin: 0 !important;
+ padding: 0;
+ padding: 1.5rem !important;
+ padding-top: 0 !important;
+}
+
+.slideCount {
+
+ margin: 0 !important;
+ padding: 0 !important;
+ padding-left: 1.5rem !important;
+ padding-top: 1rem !important;
+}
+
+.slideCount h6 {
+
+ color: var(--light-blue-color);
+}
+
+.slideHeader h4 {
+
+ color: white;
+ margin: 0 !important;
+ padding: 0 !important;
+ text-align: left;
+ font-size: 1.75rem;
+}
+
+.categorySlider {
+
+ margin-top: -1.6rem;
+
+ ion-slide {
+
+ width: 60%;
+ margin-right: 30px;
+ }
+
+ ion-col {
+
+ padding-left: 0;
+
+ ion-card {
+
+ width: 100%;
+ border-radius: 22px;
+ // box-shadow: 0px 4px 12px 2px rgba(0, 0, 0, 0.16);
+
+ box-shadow: 0 0.7px 0.9px rgba(0, 0, 0, 0.101),
+ 0 1.9px 2.5px rgba(0, 0, 0, 0.145),
+ 0 4.5px 6px rgba(0, 0, 0, 0.189),
+ 0 15px 20px rgba(0, 0, 0, 0.29);
+
+ ion-card-header {
+
+ text-align: left !important;
+ }
+
+ ion-card-content {
+
+ padding: 0;
+ padding: 0.5rem;
+ }
+ }
+ }
+
+ .lastUsed {
+
+ display: flex;
+ flex-direction: row;
+ align-content: flex-start;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.9rem;
+ color: white;
+ }
+
+ .categoryColor {
+
+ border-bottom: 3px solid var(--pink-color);
+ margin-left: 1rem;
+ margin-right: 1rem;
+ margin-bottom: 0.5rem;
+ margin-top: -1rem;
+ }
+}
+
+.recentNotes {
+
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ max-height: 18.8rem;
+ overflow: scroll;
+
+ ion-item {
+
+ --padding-start: 2rem;
+ --padding-end: 2rem;
+ --border-style: none;
+ --border-radius: 22px;
+ --min-height: 4rem;
+
+ h4 {
+
+ padding-left: 1rem;
+ margin-top: 0.6rem;
+ font-size: 1.2rem;
+ font-weight: 400;
+ }
+ }
+}
+
+.bottomContainer {
+
+ margin-top: -1rem;
+}
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Home.tsx b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.tsx
new file mode 100644
index 0000000..337108b
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.tsx
@@ -0,0 +1,166 @@
+import {
+ IonButton,
+ IonButtons,
+ IonCard,
+ IonCardContent,
+ IonCardHeader,
+ IonCardSubtitle,
+ IonCheckbox,
+ IonCol,
+ IonContent,
+ IonFab,
+ IonFabButton,
+ IonGrid,
+ IonHeader,
+ IonIcon,
+ IonItem,
+ IonPage,
+ IonRow,
+ // IonSlide,
+ // IonSlides,
+ IonToolbar,
+} from '@ionic/react';
+import styles from './Home.module.scss';
+
+import { addOutline, menuOutline, notificationsOutline, searchOutline } from 'ionicons/icons';
+import { getCategories, getNotes } from '../store/Selectors';
+import { CategoryStore, NoteStore } from '../store';
+import { markNote } from '../store/NoteStore';
+
+const Home = (): React.JSX.Element => {
+ return <>TODO: need update IonSlide>;
+
+ const categories = CategoryStore.useState(getCategories);
+ const notes = NoteStore.useState(getNotes);
+
+ const getNoteStyle = (categoryID, isComplete = false) => {
+ const categoryColor = categories.filter((category) => category.id === categoryID)[0].color;
+
+ return {
+ '--background': categoryColor,
+ '--background-checked': categoryColor,
+ '--border-style': 'none',
+ opacity: isComplete ? '0.6' : '1',
+ };
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hello, Alan!
+
+
+
+
+
+ Categories
+
+
+
+
+
+ {categories.map((category, index) => {
+ const noteCount = notes.filter((n) => n.category_id === category.id).length;
+
+ return (
+
+
+
+
+
+
+ {noteCount} {noteCount === 1 ? 'note' : 'notes'}{' '}
+
+
+
+
{category.name}
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
+
+
+ Recent Notes
+
+
+
+
+ {notes.map((note, index) => {
+ return (
+
+
+
+ markNote(note.id)}
+ />
+
+ {note.note}
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/CategoryStore.ts b/03_source/mobile/src/pages/DemoReactNotes/store/CategoryStore.ts
new file mode 100644
index 0000000..148b5b7
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/store/CategoryStore.ts
@@ -0,0 +1,33 @@
+import { Store } from 'pullstate';
+
+interface Category {
+ id: number;
+ name: string;
+ count: string;
+ color: string;
+}
+
+const CategoryStore = new Store({
+ categories: [
+ {
+ id: 1,
+ name: 'Business',
+ count: '34',
+ color: '#60b660',
+ },
+ {
+ id: 2,
+ name: 'Personal',
+ count: '12',
+ color: '#1D68DF',
+ },
+ {
+ id: 3,
+ name: 'Leisure',
+ count: '23',
+ color: '#EB06FF',
+ },
+ ] as Category[],
+});
+
+export default CategoryStore;
diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/NoteStore.ts b/03_source/mobile/src/pages/DemoReactNotes/store/NoteStore.ts
new file mode 100644
index 0000000..b60c9ef
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/store/NoteStore.ts
@@ -0,0 +1,58 @@
+import { Store } from 'pullstate';
+
+interface Note {
+ id: number;
+ category_id: number;
+ note: string;
+ complete: boolean;
+}
+
+const NoteStore = new Store({
+ notes: [
+ {
+ id: 1,
+ category_id: 1,
+ note: 'Daily meeting with team',
+ complete: false,
+ },
+ {
+ id: 2,
+ category_id: 2,
+ note: 'Pay monthly rent',
+ complete: true,
+ },
+ {
+ id: 3,
+ category_id: 3,
+ note: 'Workout in the gym',
+ complete: false,
+ },
+ {
+ id: 4,
+ category_id: 1,
+ note: 'Make progress on project',
+ complete: false,
+ },
+ ] as Note[],
+});
+
+export const markNote = (noteID: number) => {
+ const noteIndex = NoteStore.currentState.notes.findIndex((n) => n.id === noteID);
+ NoteStore.update((s) => {
+ s.notes[noteIndex].complete = !s.notes[noteIndex].complete;
+ });
+
+ document.getElementById(`noteRow_${noteID}`).classList.add('animate__pulse');
+
+ setTimeout(() => {
+ document.getElementById(`noteRow_${noteID}`).classList.remove('animate__pulse');
+ }, 500);
+};
+
+export const addNote = (note: Note) => {
+ NoteStore.update((s) => {
+ s.notes = [note, ...s.notes];
+ });
+};
+
+export default NoteStore;
diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/Selectors.ts b/03_source/mobile/src/pages/DemoReactNotes/store/Selectors.ts
new file mode 100644
index 0000000..e997fd6
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/store/Selectors.ts
@@ -0,0 +1,16 @@
+import { createSelector } from 'reselect';
+import { Category, Note } from './NoteStore';
+
+interface State {
+ categories: Category[];
+ notes: Note[];
+}
+
+const getState = (state: State) => state;
+
+// General getters
+export const getCategories = createSelector(getState, (state) => state.categories);
+export const getNotes = createSelector(getState, (state) => state.notes);
+
+// More specific getters
+// export const getCoffee = (id: number) => createSelector(getState, state => state.coffees.filter(c => parseInt(c.id) === parseInt(id))[0]);
diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/index.js b/03_source/mobile/src/pages/DemoReactNotes/store/index.js
new file mode 100644
index 0000000..3ede6a0
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoReactNotes/store/index.js
@@ -0,0 +1,2 @@
+export { default as CategoryStore } from "./CategoryStore";
+export { default as NoteStore } from "./NoteStore";
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoReactNotes/style.scss b/03_source/mobile/src/pages/DemoReactNotes/style.scss
deleted file mode 100644
index 37c1e1a..0000000
--- a/03_source/mobile/src/pages/DemoReactNotes/style.scss
+++ /dev/null
@@ -1,103 +0,0 @@
-#about-page {
- ion-toolbar {
- position: absolute;
-
- top: 0;
- left: 0;
- right: 0;
-
- --background: transparent;
- --color: white;
- }
-
- ion-toolbar ion-back-button,
- ion-toolbar ion-button,
- ion-toolbar ion-menu-button {
- --color: white;
- }
-
- .about-header {
- position: relative;
-
- width: 100%;
- height: 30%;
- }
-
- .about-header .about-image {
- position: absolute;
-
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
-
- background-position: center;
- background-size: cover;
- background-repeat: no-repeat;
-
- opacity: 0;
-
- transition: opacity 500ms ease-in-out;
- }
-
- .about-header .madison {
- background-image: url('/assets/WeatherDemo/img/about/madison.jpg');
- }
-
- .about-header .austin {
- background-image: url('/assets/WeatherDemo/img/about/austin.jpg');
- }
-
- .about-header .chicago {
- background-image: url('/assets/WeatherDemo/img/about/chicago.jpg');
- }
-
- .about-header .seattle {
- background-image: url('/assets/WeatherDemo/img/about/seattle.jpg');
- }
-
- .about-info {
- position: relative;
- margin-top: -10px;
- border-radius: 10px;
- background: var(--ion-background-color, #fff);
- z-index: 2; // display rounded border above header image
- }
-
- .about-info h3 {
- margin-top: 0;
- }
-
- .about-info ion-list {
- padding-top: 0;
- }
-
- .about-info p {
- line-height: 130%;
-
- color: var(--ion-color-dark);
- }
-
- .about-info ion-icon {
- margin-inline-end: 32px;
- }
-
- /*
- * iOS Only
- */
-
- .ios .about-info {
- --ion-padding: 19px;
- }
-
- .ios .about-info h3 {
- font-weight: 700;
- }
-}
-
-#date-input-popover {
- --offset-y: -var(--ion-safe-area-bottom);
-
- --max-width: 90%;
- --width: 336px;
-}
diff --git a/03_source/mobile/src/pages/DemoReactNotes/theme/variables.scss b/03_source/mobile/src/pages/DemoReactNotes/theme/variables.scss
new file mode 100644
index 0000000..e69de29