Skip to content

Commit

Permalink
feat(frontend): fetch habit with https request
Browse files Browse the repository at this point in the history
  • Loading branch information
Sara-Ho99 committed Mar 18, 2024
1 parent c8d9886 commit 92db5ec
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 20 deletions.
4 changes: 2 additions & 2 deletions client/memo-minder-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.24.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.0",
"react-scripts": "^5.0.1",
"web-vitals": "^2.1.4",
"axios": "^0.24.0"
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
Expand Down
132 changes: 118 additions & 14 deletions client/memo-minder-web/src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from 'axios';
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback} from 'react';
import {
createBrowserRouter,
RouterProvider,
Expand All @@ -16,8 +16,8 @@ import ShopArea from "./component/shopArea/ShopArea";
import ChallengeArea from './component/challengeArea/ChallengeArea';
import Popup from './component/popup/Popup';
import MilestonesArea from './component/milestonesArea/MilestonesArea';

import {BASE_URL, STATUS_CODE, SERVER_API} from './utils/constants'

// function to create default items for TaskArea
const createDefaultItem = (content, options = {}) => ({
id: Date.now(),
Expand Down Expand Up @@ -119,25 +119,27 @@ function App() {
Habit, Daily, To-do, reward
*/
// initialize with default tasks and add a new habit, daily, to-do, reward to the task lists
const defaultHabit = createDefaultItem('Your default habit', { positive: true, negative: true });
//const defaultHabit = createDefaultItem('Your default habit', { positive: true, negative: true });
const defaultDaily = createDefaultItem('Your default daily', { completed: false });
const defaultTodo = createDefaultItem('Your default to-do', { completed: false });
const defaultReward = createDefaultItem('Your default reward', { price: 10});

const [habits, setHabits] = useState(() => JSON.parse(localStorage.getItem('habits')) || [defaultHabit]);
//const [habits, setHabits] = useState(() => JSON.parse(localStorage.getItem('habits')) || [defaultHabit]);
const [dailies, setDailies] = useState(() => JSON.parse(localStorage.getItem('dailies')) || [defaultDaily]);
const [todos, setTodos] = useState(() => JSON.parse(localStorage.getItem('todos')) || [defaultTodo]);
const [rewards, setRewards] = useState(() => JSON.parse(localStorage.getItem('rewards')) || [defaultReward]);

const [habits, setHabits] = useState([]);

// TODO: 账号信息如何管理?全局变量?Context?props传 随用随取?
// TODO: task数据从server获取后 增删改的回调函数逻辑是否可以挪回组件内部
// TODO: task数据从server获取后 增删改的回调函数逻辑是否回组件内部
let validToken;

const login = async(username, password) => {
try {
const response = await axios.post(BASE_URL + SERVER_API.LOGIN, {
// TODO: delete the stub username/psw when login is integrated with backend
'username': username ?? 'Yue',
'username': username ?? 'yue',
'password': password ?? 'yue@memominder'
});
console.debug('login success:', response.status);
Expand All @@ -147,8 +149,41 @@ function App() {
}
};

let retryCount = 0;
// 暂时用yue实例的id和token来测试 fetchHabits, updateHabit, 和 deleteHabit 功能

const yueUserId = '65f85c5b9a50e568364f9856'; // 从Postman获取的id
// inside component
const fetchHabits = useCallback(async () => {
try {
const fetchHabitsUrl = `${BASE_URL}${SERVER_API.FETCH_HABIT.replace(':userId', yueUserId)}`;
if (!validToken) {
validToken = await login();
}
const response = await axios.get(fetchHabitsUrl, {
headers: {
'Authorization': validToken,
},
});

console.log('Fetched habits:', response.data);
// Assuming the habits are in response.data.habits
if (response.data && Array.isArray(response.data.habits)) {
setHabits(response.data.habits);
console.log('Habits state after update:', habits);
} else {
console.error('Data fetched is not in the expected format:', response.data);
}

} catch (error) {
console.error('Failed to fetch habits:', error);
}
},[]);

useEffect(() => {
fetchHabits();
}, [fetchHabits]);

let retryCount = 0;
const addHabitToServer = async (habit) => {
try {
console.debug('addHabitToServer:', habit);
Expand Down Expand Up @@ -181,16 +216,87 @@ function App() {
console.warn('post new habit error:', error);
}
};

const addHabit = (habit) => {
setHabits(prev => [...prev, habit])
addHabitToServer(habit);
fetchHabits();
};

const addDaily = (daily) => {setDailies(prev => [...prev, daily])};
const addTodo = (todo) => {setTodos(prev => [...prev, todo]);};
const addReward = (reward) => {setRewards(prev => [...prev, reward]);};

//update habit
const updateHabitToServer = async (habitId, updatedHabit) => {
try {
console.debug('updateHabitToServer:', updatedHabit);
if (!updatedHabit?.content || !updatedHabit?.notes) {
console.warn('invalid habit update, missing content or notes');
return;
}
if (!validToken) {
validToken = await login();
}
const updateHabitsUrl = `${BASE_URL}${SERVER_API.MODIFY_HABIT.replace(':habitId', habitId)}`;
const response = await axios.put(updateHabitsUrl , {
'title': updatedHabit.content,
'type': updatedHabit.positive && updatedHabit.negative ? 'both' : !updatedHabit.positive && !updatedHabit.negative ? 'neutral' : updatedHabit.positive ? 'positive' : 'negative',
'note': updatedHabit.notes
}, {
headers: {
'Authorization': validToken,
'Content-Type': 'application/json'
}
});
console.debug('update habit success:', response.status);
} catch (error) {
if (error.response && error.response.status === STATUS_CODE.UNAUTHORIZED && retryCount < 1) {
validToken = null;
retryCount++;
updateHabitToServer(habitId, updatedHabit); // Retry the update after re-login
} else {
retryCount = 0;
console.error('update habit error:', error);
}
}
};

const updateHabit = async (habitId, updatedHabit) => {
await updateHabitToServer(habitId, updatedHabit);
fetchHabits();
};


//delete habit
const deleteHabitFromServer = async (habitId) => {
try {
console.debug('deleteHabitFromServer:', habitId);
if (!validToken) {
validToken = await login();
}
const deleteHabitsUrl = `${BASE_URL}${SERVER_API.DELETE_HABIT.replace(':habitId', habitId)}`;
const response = await axios.delete(deleteHabitsUrl, {
headers: {
'Authorization': validToken
}
});
console.debug('delete habit success:', response.status);
} catch (error) {
if (error.response && error.response.status === STATUS_CODE.UNAUTHORIZED && retryCount < 1) {
validToken = null;
retryCount++;
deleteHabitFromServer(habitId); // Retry the delete after re-login
} else {
retryCount = 0;
console.error('delete habit error:', error);
}
}
};
const deleteHabit = async (habitId) => {
await deleteHabitFromServer(habitId);
fetchHabits(); // 重新获取习惯列表以更新UI
};

//update an existing habit, daily, to-do, reward
const createUpdater = (setter) => (updatedItem) => {
setter((prevItems) => {
Expand All @@ -202,7 +308,6 @@ function App() {
});
});
};
const updateHabit = createUpdater(setHabits);
const updateDaily = createUpdater(setDailies);
const updateTodo = createUpdater(setTodos);
const updateReward = createUpdater(setRewards);
Expand All @@ -211,24 +316,23 @@ function App() {
const createDeleter = (setter) => (itemId) => {
setter((prevItems) => prevItems.filter((item) => item.id !== itemId));
};
const deleteHabit = createDeleter(setHabits);
const deleteDaily = createDeleter(setDailies);
const deleteTodo = createDeleter(setTodos);
const deleteReward = createDeleter(setRewards);

useEffect(() => {
// save habits, dailies, todos, rewards to local storage whenever it changes
localStorage.setItem('habits', JSON.stringify(habits));
//localStorage.setItem('habits', JSON.stringify(habits));
localStorage.setItem('dailies', JSON.stringify(dailies));
localStorage.setItem('todos', JSON.stringify(todos));
localStorage.setItem('rewards', JSON.stringify(rewards));
}, [habits, dailies, todos, rewards]);
}, [dailies, todos, rewards]);

const clearStorageAndResetStates = () => {
// clear all localStorage
localStorage.clear();
// update to default state
setHabits([defaultHabit]);
setHabits(habits);
setDailies([defaultDaily]);
setTodos([defaultTodo]);
setRewards([defaultReward]);
Expand Down
6 changes: 3 additions & 3 deletions client/memo-minder-web/src/component/taskArea/TaskArea.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,14 @@ const TaskArea = ({
<div className="taskList">
{/* Individual Habit Item */}
{filteredHabits.map(habit => (
<div className="habitItem" key={habit.id}>
<div className="habitItem" key={habit._id}>
{habit.positive && <button onClick={() => {
handlePositiveClick(habit.id);
handlePositiveClick(habit._id);

}}>+</button>}
<p onClick={() => handleItemClick(habit, 'Habit')}>{habit.content}</p>
{habit.negative && <button onClick={() => {
handleNegativeClick(habit.id);
handleNegativeClick(habit._id);

}}>-</button>}
</div>
Expand Down
6 changes: 5 additions & 1 deletion client/memo-minder-web/src/utils/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// constants.js
export const BASE_URL = 'https://memo-minder.onrender.com';

export const STATUS_CODE = {
Expand All @@ -8,5 +9,8 @@ export const STATUS_CODE = {

export const SERVER_API = {
LOGIN: '/api/login',
ADD_HABIT: '/api/habits'
ADD_HABIT: '/api/habits',
FETCH_HABIT: '/api/habits/user/:userId',
MODIFY_HABIT: '/api/habits/:habitId',
DELETE_HABIT: '/api/habits/:habitId',
}

0 comments on commit 92db5ec

Please sign in to comment.