diff --git a/.firebase/hosting.YnVpbGQ.cache b/.firebase/hosting.YnVpbGQ.cache new file mode 100644 index 000000000..d32d77f6b --- /dev/null +++ b/.firebase/hosting.YnVpbGQ.cache @@ -0,0 +1,24 @@ +favicon.ico,1701324781159,eae62e993eb980ec8a25058c39d5a51feab118bd2100c4deebb2a9c158ec11f9 +logo192.png,1701324782749,3ee59515172ee198f3be375979df15ac5345183e656720a381b8872b2a39dc8b +logo512.png,1701324782914,ee7e2f3fdb8209c4b6fd7bef6ba50d1b9dba30a25bb5c3126df057e1cb6f5331 +manifest.json,1701324781392,aff3449bdc238776f5d6d967f19ec491b36aed5fb7f23ccff6500736fd58494a +robots.txt,1701324783694,bfe106a3fb878dc83461c86818bf74fc1bdc7f28538ba613cd3e775516ce8b49 +bg/bg.png,1701347069276,f3da082aa49cee668dac1d4f8edf150c36f7c9f485bf4a5d4b42b6b27e7cad99 +homePage/user.png,1701857344122,3bacf05181c4a286087afee612ab2971cd3c46b4ec45cde9147ee2dc076067f2 +logo/image1.png,1701347048566,a8e687d0d310e78081f90752f3f81ca2b53ee1785378c9babd9b1995ea075d68 +submitCard/close.svg,1701844216163,1fc165e347762212e40c3de9a81b678cef0b393509f1588b8e1b738bced2f96d +index.html,1704175810131,f9cc5c8c688a002d6baed5b2d731220db3dbaa3b2a8bf59e16aa2da525fb5a9b +asset-manifest.json,1704175810131,d8af1f61df0f364263bfc2eb6d4cb919ee36dad19d0d7c50af2d5f3b8d99e5c8 +static/css/main.37bf2c02.css,1704175810150,7dc36c6606b5b9e05e1a1db815bf4589004a387cc93791b2f107ad0ea355243a +static/js/787.97a755d5.chunk.js,1704175810148,cc114f23b43e5415100680b4a7ceb5aa62d0c5bb5f54f160a9808319fd91935c +static/css/main.37bf2c02.css.map,1704175810148,7f599aeebb0bdde30826e9d5a093a0e7819dbc76178bee4dc73dd3032b89b0ae +static/js/787.97a755d5.chunk.js.map,1704175810149,20b6b43ba98871fc71423361cfcc46397a0e31efa2dc284e97cbcfce4333dafb +static/js/main.5dee8f23.js.LICENSE.txt,1704175810148,ac5a220873d7f6d9ea7abb16648904d28aaff03529a143225133761dc8b64cd9 +static/media/public-sans-latin-400-normal.395632508f028112812e.woff2,1704175810147,561c4f5d59db36f529a794d5948346d81f245744120c8959354e942b8e21ed3a +static/media/public-sans-latin-ext-400-normal.ec6f5fe4a39d95207b1f.woff,1704175810147,3d786a89fa8a91c4ce85f1b7bfde31b0853d535f1635d5918d127b272cf9c150 +static/media/public-sans-vietnamese-400-normal.529cb4480800870e4c21.woff,1704175810147,e1a3002976a355cecb3b9970347d25c7d7222b72ec7dad04999445350bacf567 +static/media/public-sans-vietnamese-400-normal.cd61d3bcc45fcd8389b9.woff2,1704175810147,a736201581a86c25eeb0a4e33d4941e0827fe034f7efb50d1cd7f93b772875b0 +static/media/public-sans-latin-ext-400-normal.fb7d8869799c6eec9401.woff2,1704175810147,9ab844a64857f2c1a12a9b8a9efa25d47e3ddd60e4c9cc676da7c0a67bdadd69 +static/media/public-sans-latin-400-normal.c49c54e2dace7c6c0d24.woff,1704175810147,d54a1ec84ddd47afd2661a3c9cdb879b818c1ddbba14295e34e61b5c9e919376 +static/js/main.5dee8f23.js,1704175810148,f88f8000c03e9f2276eb97a08febe457f0611ae9244a24f2dbcad64bba6c6a6d +static/js/main.5dee8f23.js.map,1704175810153,d1f0f47ff087ecefc3e36605a54994b7e20363b01701661688982c479adafb4f diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 000000000..2c8b78102 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "dear-diary-emailusername-6a3d7" + } +} diff --git a/firebase.json b/firebase.json new file mode 100644 index 000000000..f47555498 --- /dev/null +++ b/firebase.json @@ -0,0 +1,10 @@ +{ + "hosting": { + "public": "build", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + } +} diff --git a/public/bg.png b/public/bg/bg.png similarity index 100% rename from public/bg.png rename to public/bg/bg.png diff --git a/public/user.png b/public/homePage/user.png similarity index 100% rename from public/user.png rename to public/homePage/user.png diff --git a/public/image1.png b/public/logo/image1.png similarity index 100% rename from public/image1.png rename to public/logo/image1.png diff --git a/public/close.svg b/public/submitCard/close.svg similarity index 100% rename from public/close.svg rename to public/submitCard/close.svg diff --git a/src/.env b/src/.env index f2b28fc63..798af47f8 100644 --- a/src/.env +++ b/src/.env @@ -1 +1,7 @@ -PUBLIC_URL = "./public/" \ No newline at end of file +PUBLIC_URL = "./public/" +REACT_APP_API_KEY = AIzaSyCduh8Bd6T3lqaMxFxihVPZQSRH7_vPODY +REACT_APP_AUTH_DOMAIN = dear-diary-emailusername-6a3d7.firebaseapp.com +REACT_APP_PROJECT_ID = dear-diary-emailusername-6a3d7 +REACT_APP_STORAGE_BUCKET = dear-diary-emailusername-6a3d7.appspot.com +REACT_APP_MESSAGING_SENDER_ID = 107317734441 +REACT_APP_APP_ID = 1:107317734441:web:2b5d34185dce4a01564038 \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 0dbb2c77e..01490dc0b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import './App.css'; -import AppRoutes from './utility/appRoutes'; +import AppRoutes from './config/appRoutes/appRoutes'; function App() { return ( diff --git a/src/components/header.tsx b/src/components/Header/Header.tsx similarity index 92% rename from src/components/header.tsx rename to src/components/Header/Header.tsx index b48a2b18a..ae9e71a4d 100644 --- a/src/components/header.tsx +++ b/src/components/Header/Header.tsx @@ -34,7 +34,7 @@ const Header = () => {
- + Dear Diary diff --git a/src/config/appRoutes/appRoutes.tsx b/src/config/appRoutes/appRoutes.tsx new file mode 100644 index 000000000..cd01725f7 --- /dev/null +++ b/src/config/appRoutes/appRoutes.tsx @@ -0,0 +1,19 @@ +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import SignInPage from "../../containers/SignInPage/SignInPage"; +import HomePage from "../../containers/HomePage/HomePage"; +import { SIGN_IN_PATH, HOME_PATH } from "../../utility/routeConstants"; + +const AppRoutes = () => { + return ( + + + }/> + }/> + + + + + ); +}; + +export default AppRoutes; \ No newline at end of file diff --git a/src/utility/firebaseConfig.ts b/src/config/firebaseConfig/firebaseConfig.ts similarity index 85% rename from src/utility/firebaseConfig.ts rename to src/config/firebaseConfig/firebaseConfig.ts index 2aebc22b7..8d3403bec 100644 --- a/src/utility/firebaseConfig.ts +++ b/src/config/firebaseConfig/firebaseConfig.ts @@ -4,8 +4,8 @@ import { } from "firebase/firestore"; const firebaseConfig = { - apiKey: "AIzaSyCduh8Bd6T3lqaMxFxihVPZQSRH7_vPODY", - authDomain: "dear-diary-emailusername-6a3d7.firebaseapp.com", + apiKey: process.env.REACT_APP_API_KEY, + authDomain: process.env.REACT_APP_AUTH_DOMAIN, projectId: "dear-diary-emailusername-6a3d7", storageBucket: "dear-diary-emailusername-6a3d7.appspot.com", messagingSenderId: "107317734441", diff --git a/src/containers/HomePage/DiaryCard/DiaryCard.tsx b/src/containers/HomePage/DiaryCard/DiaryCard.tsx index 36cd6abed..1b91415a3 100644 --- a/src/containers/HomePage/DiaryCard/DiaryCard.tsx +++ b/src/containers/HomePage/DiaryCard/DiaryCard.tsx @@ -16,23 +16,32 @@ interface DiaryCardProps { const StyledCard = styled(Card)` width: 271px; + min-height: 10px; height: auto; - padding: 20px; + padding: 0px 0px 6px 0px; border-radius: 6px; align-items: flex-start; `; const StyledCardContent = styled(CardContent)` align-self: flex-start; + + `; const StyledBox = styled(Box)` display: flex; - height: auto; margin-bottom: 6px; align-items: flex-start; `; +const StyledBoxDesc = styled(Box)` + display: flex; + + align-items: flex-start; + padding-bottom: -16; +`; + const TypographyTitle = styled(Typography)` color: #4B465C; font-family: public sans; @@ -68,19 +77,22 @@ const StyledButton = styled(Button)` letter-spacing: 0.43px; width: 271px; display: flex; - padding: 20px; + padding: 0px; + padding-left: 0px; flex-direction: column; align-items: flex-start; border-radius: 6px; background-color: #ffffff; - box-shadow: 0 4px 18px 0pc rgba(75, 70, 92, 0.10); + box-shadow: 0 4px 18px 0pc rgba(75, 70, 92, 0.0); + margin-left: 6px; } `; const DiaryCard: React.FC = ({ title, description }) => { const [showMore, setShowMore] = useState(false); // state to track whether to show more or not - const truncatedDescription = showMore ? description : `${description.slice(0, 100)}...`; // truncate description if showMore is false + const truncatedDescription = showMore ? description : `${description.slice(0, 100)}`; // truncate description if showMore is false + const showMoreButton = description.length > 100; // only show showMore button if description is longer than 100 characters const handleToggleShowMore = () => { setShowMore(!showMore); @@ -88,23 +100,26 @@ const DiaryCard: React.FC = ({ title, description }) => { return ( - + {title} - + {truncatedDescription} - - - - - {showMore ? 'Hide' : 'Show More'} - - + + + {showMoreButton && ( // conditionally render the button based on description length + + + {showMore ? 'Hide' : 'Show More'} + + + )} + ); }; diff --git a/src/containers/HomePage/HomePage.tsx b/src/containers/HomePage/HomePage.tsx index a56c9a0eb..87d00e2fe 100644 --- a/src/containers/HomePage/HomePage.tsx +++ b/src/containers/HomePage/HomePage.tsx @@ -1,12 +1,10 @@ import React, { useState } from 'react'; -import Box from '@mui/material/Box'; -import Container from '@mui/material/Container'; import TextField from '@mui/material/TextField'; import SearchIcon from '@mui/icons-material/Search'; import InputAdornment from '@mui/material/InputAdornment'; import Button from '@mui/material/Button'; import { IconButton } from '@mui/material'; -import Header from '../../components/Header'; +import Header from '../../components/Header/Header'; import Grid from '@mui/material/Grid'; import Modal from '@mui/material/Modal'; import DiaryCard from './DiaryCard/DiaryCard'; @@ -15,11 +13,17 @@ import "@fontsource/public-sans/"; import { useLocation } from 'react-router-dom'; import styled from 'styled-components'; +import Alert from '@mui/material/Alert'; +import DoneIcon from '@mui/icons-material/Done'; + import { useEffect } from 'react'; import { RootState, useAppDispatch } from '../../redux/store'; import { useSelector, useDispatch } from 'react-redux'; -import { diaryCardActions } from '../../redux/Diary/diarySlice'; +import { diaryCardActions } from '../../redux/diary/slice'; +import { useNavigate } from 'react-router-dom'; +import CalculateTimeElapsed from '../../utility/calculateTimeElapsed'; +import { SIGN_IN_PATH } from '../../utility/routeConstants'; const StyledMainDiv = styled.div` display: flex; @@ -27,7 +31,7 @@ const StyledMainDiv = styled.div` height: 100vh; width: 100%; position: relative; - background-image: url(${process.env.PUBLIC_URL}/bg.png); + background-image: url(/bg/bg.png); background-size: cover; background-repeat: no-repeat; overflow-x: hidden; @@ -42,7 +46,7 @@ const StyledHeaderGrid = styled(Grid)` display: flex; align-items: center; width: 100%; - margin-left: 0px; + margin-left: 16px; margin-top: 60px; margin-right: 60px; justify-content: space-between; @@ -107,12 +111,15 @@ const StyledSearchGrid = styled(Grid)` margin-top: 0px; margin-right: 60px; justify-content: space-between; + gap: 5px; `; const StyledTextField = styled(TextField)` + width: 530px; border-radius: 8px; background-color: #ffffff; + `; const StyledSubmitButton = styled(Button)` @@ -137,13 +144,42 @@ const StyledSubmitButton = styled(Button)` const DiaryCardContainer = styled.div` display: flex; flex-wrap: wrap; - gap: 30px; + gap: 14px; margin-top: 20px; margin-left: 60px; - margin-right: 60px; + + margin-bottom: 10px; + `; -interface DiaryCardProps { +const ToastContainerMsg = styled.span + ` + color: #4B465C; + font-family: public sans; + + font-size: 15px; + + font-weight: 600; + line-height: 22px; + + +`; + +const ToastContainerTime = styled.span` +&&&{ + color: rgba(75, 70, 92, 0.55); + font-family: public sans; + font-weight: 400; + font-size: 13px; + text-align: right; + margin-left: 50px; + line-height: 20px; + +}`; + + + +interface IDiaryCardProps { title: string; description: string; username: string; @@ -152,40 +188,62 @@ interface DiaryCardProps { const HomePage = () => { const [showSubmitCard, setShowSubmitCard] = useState(false); // state to track if submit card is open or not - const [diaryEntries, setDiaryEntries] = useState([]); // state to track diary entries + const [diaryEntries, setDiaryEntries] = useState([]); // state to track diary entries + const [showAlert, setShowAlert] = useState(false); // sate to track alert + const [submitTime, setSubmitTime] = useState(null); // state to track submit time + const [elapsedTime, setElapsedTime] = useState(''); // state to track elapsed time + const [searchQuery, setSearchQuery] = useState(''); // state to track search query - const dispatch = useDispatch(); + const dispatch = useDispatch(); + const history = useNavigate(); // get history from react router dom const location = useLocation(); // get location from react router dom - const nickName = location.state.name || {}; // from that location get name - + const nickName = location.state?.name || null; // from that location get name + + // checking the diary card list array useEffect(() => { console.log("Diary Card List:", diaryEntries); }, [diaryEntries]); - + + // fetching the diarycard according to the username useEffect(() => { - console.log("fetching diary card list"); - dispatch(diaryCardActions.fetchDiaryCardList(nickName)); - }, [dispatch, nickName]); + if (!nickName) { + history(SIGN_IN_PATH); // Redirect to the signing page if nickname is empty + } else { + + dispatch(diaryCardActions.fetchDiaryCardList(nickName)); + } + }, [dispatch, history, nickName]); + // function to handle submit card const handleOnSubmitCard = (title: string, description: string) => { // create new diary card - console.log('Handling submit card:', { title, description, username: nickName }); - dispatch(diaryCardActions.addDiaryCard({title, description, username: nickName})) + + + // Add the new card to the beginning of the diaryEntries array + setDiaryEntries([{ title, description, username: nickName }, ...diaryEntries]); + + dispatch(diaryCardActions.addDiaryCard({ title, description, username: nickName })) // addding a new diary card to db // add new diary card to diary entries - // setDiaryEntries([newDiaryCard, ...diaryEntries]); + + setSubmitTime(new Date()); // Capture the time when the user submits the card + handleCloseSubmitCard(); + // show alert + setShowAlert(true); }; - + // function to handle submit button const handleSubmit = () => { setShowSubmitCard(true); + }; // function to handle close submit card const handleCloseSubmitCard = () => { setShowSubmitCard(false); + }; const entries = useSelector((state: RootState) => state.diaryCardList.diaryCardList); @@ -194,6 +252,35 @@ const HomePage = () => { setDiaryEntries(entries); }, [entries]); + + + // Function to filter diary entries based on search query + const filteredDiaryEntries = diaryEntries.filter((entry) => { + const searchTerms = `${entry.title} ${entry.description} ${entry.username}`.toLowerCase(); + return searchTerms.includes(searchQuery.toLowerCase()); + }); + + + + + // Function to calculate and update elapsed time + const updateElapsedTime = () => { + const timeElapsed = CalculateTimeElapsed(submitTime); + + // Check if there is any time elapsed + if (timeElapsed) { + setElapsedTime(`${timeElapsed}`); + } + }; + + // Effect to update elapsed time every second + useEffect(() => { + const intervalId = setInterval(updateElapsedTime, 1000); + + // Clear interval on component unmount + return () => clearInterval(intervalId); + }, [submitTime]); + return ( @@ -221,7 +308,7 @@ const HomePage = () => { - User Icon + User Icon @@ -245,12 +332,15 @@ const HomePage = () => { variant="outlined" size="small" placeholder="Placeholder" + value={searchQuery} + onChange={(e) => setSearchQuery(e.target.value)} InputProps={{ startAdornment: ( ), + }} /> {
+ {/* if submit card is available using Modal to visbible submit card */} { {/* Rendering Diary Cards */} - {diaryEntries.map((diaryCard, index) => ( - + {filteredDiaryEntries.length === 0 && ( +
+

No Diary Entries Found

+
+ )} + {filteredDiaryEntries.map((diaryCard, index) => ( +
+ +
))}
+ + {/* Render Alert */} + setShowAlert(false)} + icon={} + sx={{ + position: 'fixed', + top: 148, + right: 60, + zIndex: 1000, + width: 'auto', + height: 'auto', + display: showAlert ? 'flex' : 'none', + backgroundColor: 'white', + '& .MuiIconButton-root': { + color: 'rgba(75, 70, 92, 0.55)', + }, + + }} + > + Record Saved Successfully. + {elapsedTime && `${elapsedTime} ago`} + + ); }; + + export default HomePage; diff --git a/src/containers/HomePage/SubmitCard/SubmitCard.tsx b/src/containers/HomePage/SubmitCard/SubmitCard.tsx index 96bde632a..fb44eb27a 100644 --- a/src/containers/HomePage/SubmitCard/SubmitCard.tsx +++ b/src/containers/HomePage/SubmitCard/SubmitCard.tsx @@ -43,7 +43,7 @@ const StyledCloseButton = styled(IconButton)` `; const StyledSpan = styled.span` - margin-bottom: 8px; + margin-bottom: 10px; margin-right: 330px; font-family: public sans; font-feature-settings: 'clig' off, 'liga' off; @@ -144,10 +144,7 @@ const SubmitCard: React.FC = ({ onClose, onsubmit }) => { toast.error('Please enter title and description'); return; } - // if title and description are not empty, show toast success message - if (title !== '' && description !== '') { - toast.success('Submitted successfully'); - } + // call onsubmit function with title and description onsubmit(title, description); onClose(); @@ -158,17 +155,18 @@ const SubmitCard: React.FC = ({ onClose, onsubmit }) => { Submit New - Close Icon + Close Icon
Title @@ -176,7 +174,8 @@ const SubmitCard: React.FC = ({ onClose, onsubmit }) => { Description { - + Dear Diary Sign In - + { Random - + Continue diff --git a/src/redux/Diary/diarySaga.ts b/src/redux/Diary/diarySaga.ts deleted file mode 100644 index 6afd57346..000000000 --- a/src/redux/Diary/diarySaga.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { take, call, put, takeEvery, all} from 'redux-saga/effects'; -import { eventChannel } from 'redux-saga'; -import { - diaryCardActions -} from './diarySlice'; -import { db } from '../../utility/firebaseConfig'; -import { collection, addDoc, query, onSnapshot, where } from "firebase/firestore"; -import { PayloadAction } from '@reduxjs/toolkit'; - -interface DiaryCardProps { - title: string; - description: string; - username: string; -} - -function snapshotChannel(db: any, username: string) { - return eventChannel((emitter) => { - const ref = collection(db, "DiaryCards"); - const query1 = query(ref, where("username", "==", username)); - - const unsubscribe = onSnapshot(query1, (querySnapshot) => { - const updatedDiaryCards: DiaryCardProps[] = []; - querySnapshot.forEach((doc) => { - const entry = doc.data() as DiaryCardProps; - updatedDiaryCards.push(entry); - }); - emitter(updatedDiaryCards); - }); - - return unsubscribe; - }); -} - - -function* addDiaryCard(action: PayloadAction): Generator { - try { - console.log('Adding diary card', action.payload) - const { title, description, username } = action.payload; - const diaryCard: DiaryCardProps = { - title, - description, - username - }; - yield call(addDoc, collection(db, "DiaryCards"), diaryCard); - } catch (err) { - console.log('Adding card error', err); - alert(err); - } -} - -function* fetchDiaryCardList(action: PayloadAction): Generator { - try { - const username = action.payload; - const channel = yield call(snapshotChannel, db, username); - while (true) { - const diaryCardList: DiaryCardProps[] = yield take(channel); - console.log('Diary card list', diaryCardList); - yield put(diaryCardActions.setDiaryCardList(diaryCardList)); - } - } catch (err) { - console.log('Fetching diary card list error', err); - alert(err); - } -} - -function* addDiaryCardSaga() { - yield takeEvery(diaryCardActions.addDiaryCard, addDiaryCard); -} - -function* fetchDiaryCardListSaga(): Generator { - yield takeEvery(diaryCardActions.fetchDiaryCardList.type, fetchDiaryCardList); -} - -export default function* diaryCardSaga() { - yield all([addDiaryCardSaga(), fetchDiaryCardListSaga()]); -}; \ No newline at end of file diff --git a/src/redux/Diary/diarySlice.ts b/src/redux/Diary/diarySlice.ts deleted file mode 100644 index cc6c79a16..000000000 --- a/src/redux/Diary/diarySlice.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {createSlice, PayloadAction} from '@reduxjs/toolkit'; - -interface DiaryCardProps { - title: string; - description: string; - username: string; -} - -interface DiaryCardState { - diaryCardList: DiaryCardProps[]; -} - -const initialState: DiaryCardState = { - diaryCardList: [], -}; - -export const diaryCardSlice = createSlice({ - name: 'diaryCard', - initialState, - reducers: { - addDiaryCard: (state, action: PayloadAction) => { - console.log('Adding diary card:', action.payload); - state.diaryCardList.push(action.payload); - console.log('Diary card list:', state.diaryCardList); - }, - setDiaryCardList: (state, action: PayloadAction) => { - state.diaryCardList = action.payload; - }, - fetchDiaryCardList: () => {}, - }, -}); - -const diaryCardActions = diaryCardSlice.actions; -const diaryCardReducer = diaryCardSlice.reducer; -export {diaryCardActions, diaryCardReducer}; \ No newline at end of file diff --git a/src/redux/User/signInSlice.ts b/src/redux/User/signInSlice.ts deleted file mode 100644 index afabd2614..000000000 --- a/src/redux/User/signInSlice.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {createSlice} from '@reduxjs/toolkit'; - -// Define a type for the slice state -export const signInSlice = createSlice({ - name: 'signIn', // a name used in action type - initialState: { - nickname: '', // nickname of the user as initial state - }, - reducers: { - setNickname: (state, action) => { - state.nickname = action.payload; // set nickname to the payload - }, - }, - - }); - -export const {setNickname} = signInSlice.actions; - -export default signInSlice.reducer; \ No newline at end of file diff --git a/src/redux/diary/saga.ts b/src/redux/diary/saga.ts new file mode 100644 index 000000000..f383f0e7f --- /dev/null +++ b/src/redux/diary/saga.ts @@ -0,0 +1,82 @@ +import { take, call, put, takeEvery, all} from 'redux-saga/effects'; +import { eventChannel } from 'redux-saga'; +import { + diaryCardActions +} from './slice'; +import { db } from '../../config/firebaseConfig/firebaseConfig'; +import { collection, addDoc, query, onSnapshot, where } from "firebase/firestore"; +import { PayloadAction } from '@reduxjs/toolkit'; + + +// props for DiaryCard component +interface IDiaryCardProps { + title: string; + description: string; + username: string; +} + +function snapshotChannel(db: any) { + return eventChannel((emitter) => { + const ref = collection(db, "DiaryCards"); + + const unsubscribe = onSnapshot(ref, (querySnapshot) => { + const updatedDiaryCards: IDiaryCardProps[] = []; + querySnapshot.forEach((doc) => { + const entry = doc.data() as IDiaryCardProps; + updatedDiaryCards.push(entry); + }); + emitter(updatedDiaryCards); + }); + + return unsubscribe; + }); +} + + +// add a diary card to the database +function* addDiaryCard(action: PayloadAction): Generator { + try { + // console.log('Adding diary card', action.payload) + const { title, description, username } = action.payload; + const diaryCard: IDiaryCardProps = { + title, + description, + username + }; + yield call(addDoc, collection(db, "DiaryCards"), diaryCard); // add the diary card to the database + } catch (err) { + console.error('Adding card error', err); + alert(err); + } +} + +// fetch the diary card list from the database according to the username +function* fetchDiaryCardList(action: PayloadAction): Generator { + try { + const username = action.payload; + const channel = yield call(snapshotChannel, db); // create a channel to listen to changes in the database + while (true) { + const diaryCardList: IDiaryCardProps[] = yield take(channel); // get the updated diary card list + const filteredDiaryCardList = diaryCardList.filter(card => card.username === username); + yield put(diaryCardActions.setDiaryCardList(filteredDiaryCardList)); // update the diary card list in the redux store + + } + } catch (err) { + console.error('Fetching diary card list error', err); + alert(err); + } +} + + +function* addDiaryCardSaga() { + yield takeEvery(diaryCardActions.addDiaryCard, addDiaryCard); + +} + +function* fetchDiaryCardListSaga(): Generator { + yield takeEvery(diaryCardActions.fetchDiaryCardList.type, fetchDiaryCardList); +} + +export default function* diaryCardSaga() { + yield all([addDiaryCardSaga(), fetchDiaryCardListSaga()]); // run the sagas in parallel +}; \ No newline at end of file diff --git a/src/redux/diary/slice.ts b/src/redux/diary/slice.ts new file mode 100644 index 000000000..1e18792fd --- /dev/null +++ b/src/redux/diary/slice.ts @@ -0,0 +1,36 @@ +import {createSlice, PayloadAction} from '@reduxjs/toolkit'; + +interface IDiaryCardProps { + title: string; + description: string; + username: string; +} + + +interface IDiaryCardState { + diaryCardList: IDiaryCardProps[]; +} + +const initialState: IDiaryCardState = { + diaryCardList: [], +}; + +export const diaryCardSlice = createSlice({ + name: 'diaryCard', + initialState, + reducers: { + addDiaryCard: (state, action: PayloadAction) => { + + state.diaryCardList.push(action.payload); // add the diary card to the diary card list + + }, + setDiaryCardList: (state, action: PayloadAction) => { + state.diaryCardList = action.payload; // set the diary card list to the updated diary card list + }, + fetchDiaryCardList: () => {}, // fetch the diary card list from the database + }, +}); + +const diaryCardActions = diaryCardSlice.actions; +const diaryCardReducer = diaryCardSlice.reducer; +export {diaryCardActions, diaryCardReducer}; \ No newline at end of file diff --git a/src/redux/store.ts b/src/redux/store.ts index 0cc7dba3e..7320bf2b3 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -2,9 +2,8 @@ import {configureStore, Tuple } from '@reduxjs/toolkit'; // import logger from 'redux-logger'; import createSagaMiddleware from 'redux-saga'; -import {diaryCardReducer} from './Diary/diarySlice'; -import signInSliceReducer from './User/signInSlice'; -import diaryCardSaga from './Diary/diarySaga'; +import {diaryCardReducer} from './diary/slice'; +import diaryCardSaga from './diary/saga'; import { useDispatch } from 'react-redux'; diff --git a/src/utility/appRoutes.tsx b/src/utility/appRoutes.tsx deleted file mode 100644 index 751c58e11..000000000 --- a/src/utility/appRoutes.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import SignInPage from "../containers/SignInPage/SignInPage"; -import HomePage from "../containers/HomePage/HomePage"; - -const AppRoutes = () => { - return ( - - - }/> - }/> - - - - - ); -}; - -export default AppRoutes; \ No newline at end of file diff --git a/src/utility/calculateTimeElapsed.ts b/src/utility/calculateTimeElapsed.ts new file mode 100644 index 000000000..af2b67206 --- /dev/null +++ b/src/utility/calculateTimeElapsed.ts @@ -0,0 +1,24 @@ +const CalculateTimeElapsed = (submitTime: Date | null) => { + if (!submitTime) return ''; + + const currentTime = new Date(); + const elapsedMilliseconds = currentTime.getTime() - submitTime.getTime(); + const elapsedSeconds = Math.floor(elapsedMilliseconds / 1000); + + // Calculate minutes and remaining seconds + const minutes = Math.floor(elapsedSeconds / 60); + const seconds = elapsedSeconds % 60; + + // Construct the time elapsed string + let timeElapsedString = ''; + + if (minutes > 0) { + timeElapsedString += `${minutes} ${minutes === 1 ? 'min' : 'mins'}`; + } else { + timeElapsedString += '0 mins'; + } + + return timeElapsedString.trim(); +}; + +export default CalculateTimeElapsed; \ No newline at end of file diff --git a/src/utility/routeConstants.ts b/src/utility/routeConstants.ts new file mode 100644 index 000000000..e99d4c190 --- /dev/null +++ b/src/utility/routeConstants.ts @@ -0,0 +1,4 @@ +// Purpose: To store the route constants for the application. + +export const SIGN_IN_PATH = '/'; +export const HOME_PATH = '/home'; \ No newline at end of file