FortuneMon νλ‘μ νΈμ κΈ°μ¬ν΄μ£Όμ
μ κ°μ¬ν©λλ€. μ΄ λ¬Έμλ κ°λ° μ μ§μΌμΌ ν κ·μΉλ€μ μ€λͺ
ν©λλ€. νμ
μ μνν¨μ μν΄ λ°λμ μλ μ½λ© κ°μ΄λ λ° κ·μΉλ€μ λ°λΌμ£ΌμΈμ.
- λ°±μλ API λ¬Έμ(Swagger/OpenAPI)μ νμ λκΈ°νλ μνλ‘ APIλ₯Ό κ°λ° λ° μ¬μ©ν©λλ€.
- μ€μ μ¬μ©νλ API μμ² κ²½λ‘, μλ΅ νμ λ±μ Swaggerμ μΌμΉμμΌ μ μ§ν©λλ€.
- 곡ν΅μ μΈ μμΈ μ²λ¦¬λ₯Ό μν 컀μ€ν μμΈ ν΄λμ€ λ° κ³΅ν΅ Response νμμ μ¬μ©ν©λλ€.
- μμ κ°λ₯ν μμΈλ 미리 μ μλ νμμΌλ‘ νλ‘ νΈμ μ λ¬λλλ‘ μ²λ¦¬ν©λλ€.
- νκ²½ λ³μλ
.envλλ secrets λ±μΌλ‘ κ΄λ¦¬νλ©°, μ λ νλμ½λ©λ ν€/λΉλ°λ²νΈλ κΈμ§ν©λλ€. - μ:
REACT_APP_API_URL,REACT_APP_GOOGLE_CLIENT_IDλ±
- λ°λ³΅μ μΌλ‘ μ¬μ©λλ ν¨μ λλ λ‘μ§μ
common/,utils/λ± κ³΅ν΅ μ νΈ λλ ν 리μ λͺ¨λνν©λλ€. - μ½λ μ€λ³΅μ μ€μ΄κ³ μ μ§λ³΄μλ₯Ό μ©μ΄νκ² ν©λλ€.
- λ°λ³΅ μ¬μ©λλ UI μμλ μ»΄ν¬λνΈνν©λλ€.
- νλμ λͺ©μ λ§ μννλλ‘ μ€κ³νλ©° λΉμ¦λμ€ λ‘μ§μ μ΅λν λΆλ¦¬ν©λλ€.
- νλμ νμΌμ΄ λ무 κΈΈκ±°λ 볡μ‘ν΄μ§λ κ²½μ° κΈ°λ₯ λ¨μλ‘ μ»΄ν¬λνΈλ₯Ό λΆλ¦¬ν©λλ€.
- μν μ΄ λͺ ννκ² λλ¬λλ λ€μ΄λ°μ μ¬μ©ν©λλ€.
ButtonComponent,InputComponentμ κ°μ **λΆνμν μ λ―Έμ¬(Component)**λ μ§μν©λλ€.
src/components/
βββ Common # κ³΅ν΅ μμ (λ²νΌ, μΈν, λ‘λ© λ±)
βββ Layouts # νμ΄μ§ λ μ΄μμ κ΄λ ¨
βββ Chart # λ£¨ν΄ ν΅κ³ μκ°ν μμ (μΊλ¦°λ, λ°μ΄, μΌλ¬μ
λ±)
βββ Fortune # μ΄μΈ κ΄λ ¨ μ»΄ν¬λνΈ
βββ Pokemon # ν¬μΌλͺ¬ κ΄λ ¨ μ»΄ν¬λνΈ
βββ Routines # λ£¨ν΄ κ΄λ ¨ μ»΄ν¬λνΈ
- Redux Toolkit μ¬μ©
src/store/
βββ slices/
β βββ user.js
βββ thunks/
β βββ user.js
βββ store.js
// src/store/store.js
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./slices/user";
// import new slice!!
export const store = configureStore({
reducer: {
user: userReducer,
// <- add here!!!
},
});// 1. thunk νμΌμ thunkλ₯Ό μμ±νλ€.
// src/store/thunks/user.js
...
export const addMyRoutine = createAsyncThunk("addMyRoutine", async (routineId) => {
const {
data: { result },
} = await axiosInstance.post(`${prefix}/routines/${routineId}`);
return result;
});
...
// 2. ν΄λΉ λλ©μΈμ slice μμ import νκ³ , λΉλκΈ° μμ² ν μνν λμ reducerμ μΆκ°
// src/store/slices/user.js
const userSlice = createSlice({
name: "user",
...
extraReducers: (builder) => {
...
builder.addCase(addMyRoutine.fulfilled, (state, action) => {
const routineName = action.payload.routineName;
const routineId = action.meta.arg;
state.myRoutines = [...state.myRoutines, { routineId, name: routineName, isCompeleted: false }];
console.log("addMyRoutine Result:", state.myRoutines);
});
...
},
});
// 3. μ¬μ© μ useDispatch λ° dispatchλ‘ μ¬μ©
// src/components/routines/RoutineCard.jsx
const dispatch = useDispatch();
...
const onClick = useCallback(() => {
if (isRegistered) {
...
} else {
dispatch(addMyRoutine(routineId)); //here!!
}
}, [dispatch, routineId, isRegistered]);// src/store/slices/user.js
export const selectMyInfo = (state) => state.user?.me; //here!!
// src/pages/MyPage.jsx
import { selectMyInfo } from "../store/slices/user"; //here!!
const MyPage = () => {
...
const user = useSelector(selectMyInfo); //here!!
...
}src/apis/
βββ UserApi.js
βββ RoutineApi.js
βββ FortuneApi.js
βββ PokeApi.js
// μ¬μ© μμ
// src/apis/RoutineApi.js
...
/**
* @param {number} year
* @param {number} month
* @returns {Promise<{routineId: number; routineName: string; daysStatistics: {}}[]>}
*/
export async function fetchMyStatistics(year, month) {
try {
const date = dayjs(new Date(year, month - 1)).format("YYYY-MM-DD");
const {
data: {
result: { statistics },
},
} = await axiosInstance.get(`${prefix}/routines/${date}/statistics`);
return statistics;
} catch (error) {
console.error(error);
throw error;
}
}
...
//src/pages/ChartPage.jsx
import { fetchMyStatistics } from "../apis/RoutineApi";
...
useEffect(() => {
if (!isLoading) {
// here!!
fetchMyStatistics(selectedDate.year, selectedDate.month).then((s) => {
setStatistics(s);
});
}
}, [isLoading, selectedDate]);
...β μ§μ
axiosλ₯Ό import νμ§ μκ³ , λ°λμ api λͺ¨λμμ ν¨μλ‘ μΆμΆν΄μ μ¬μ©νμΈμ.
λͺ¨λ κ°λ° μμ μ λ°λμ GitHub μ΄μλ₯Ό μμ±ν λ€μ μμν©λλ€. μ΄μκ° μμ±λλ©΄ μλμΌλ‘ κ³ μ ν λ²νΈκ° λΆμ¬λλ©°, ν΄λΉ λ²νΈλ₯Ό κΈ°μ€μΌλ‘ λΈλμΉλͺ , μ»€λ° λ©μμ§, PR μ λͺ©μ μμ±ν©λλ€.
μμ:
#35 λ£¨ν΄ μνμ¬λΆ κΈ°λ₯ λ²κ·Έ μμ
λΈλμΉ μ΄λ¦μ λ€μ νμμ λ°λ¦ λλ€:
{μ»€λ° λ©μμ§ μ»¨λ²€μ
νμ
}: #{μ΄μλ²νΈ} - {κ°λ¨νμ€λͺ
}
μμ:
feat: #9 λ‘κ·ΈμΈ UI κ°λ°
refactor: #42 μ΄μΈνμ΄μ§_리ν©ν λ§
hotfix: #50 λ°°ν¬ ν https μμ² μ€λ₯ μμ
μ»€λ° λ©μμ§λ λ€μ νμμ μ§μΌμ£ΌμΈμ. Git branch κ·μΉκ³Ό μ μ¬νλ λ΄μ©μ μμΈ μ λμ μ°¨μ΄κ° μμ΅λλ€.
{νμ
}: #{μ΄μλ²νΈ} {λ΄μ©}
| νμ | μ€λͺ |
|---|---|
| feat | μλ‘μ΄ κΈ°λ₯ μΆκ° |
| fix | λ²κ·Έ μμ |
| refactor | μ½λ 리ν©ν λ§ |
| style | μ½λ ν¬λ§·ν , μΈλ―Έμ½λ‘ λλ½, μ½λ λ³κ²½ μμ |
| docs | λ¬Έμ μμ |
| chore | κΈ°ν λ³κ²½μ¬ν (λΉλ μ€ν¬λ¦½νΈ μμ λ±) |
| test | ν μ€νΈ κ΄λ ¨ μ½λ μΆκ°/μμ |
| perf | μ±λ₯ ν₯μ |
feat: #35 λ£¨ν΄ λ―Έμν μ κ²½κ³ λ©μμ§ μΆκ°
fix: #35 λ£¨ν΄ μν μ¬λΆ μ²΄ν¬ λ²κ·Έ μμ
docs: #40 README μ
λ°μ΄νΈ
refactor: #42 ν¬μΌλͺ¬ λ½κΈ°κΈ° κ²°κ³Ό μ»΄ν¬λνΈ κ΅¬μ‘° κ°μ
PR μ λͺ©λ μ΄μ λ²νΈ κΈ°λ°μΌλ‘ μμ±ν©λλ€. Git branch μ λ΅μ λ°λΌμλ€λ©΄ μλμ μΌλ‘ μ λͺ©μ΄ μμ±λ©λλ€.
PR λ³Έλ¬Έμμλ λ³΄λ€ μμΈν μ€λͺ μ μμ±ν΄μ£ΌμΈμ. μ΄λ―Έμ§λ μ½λμ λν μ€λͺ μ μ μ΄μ£Όμλ©΄ μνν μν΅μ΄ κ°λ₯ν©λλ€.
#{μ΄μλ²νΈ} {λ³κ²½ μμ½}
μμ:
fix: #35 λ£¨ν΄ λ―Έμν κΈ°λ₯ κ΄λ ¨ λ²κ·Έ μμ
refactor: #42 ν¬μΌλͺ¬ λ½κΈ° κ²°κ³Ό νμ΄μ§ UI κ°μ
π‘ μμ
κ°μ
- λ£¨ν΄ μν μ¬λΆ μ²΄ν¬ λ‘μ§μμ null 체ν¬κ° λλ½λμ΄ λ°μνλ λ²κ·Έλ₯Ό μμ νμ΅λλ€.
## β
λ³κ²½ μ¬ν
- λ£¨ν΄ μλ£ μ¬λΆ νλ¨ λ‘μ§ μμ
- κ²½κ³ λ©μμ§ μΆκ°
- κ΄λ ¨ μ λ ν
μ€νΈ μΆκ°
## π κ΄λ ¨ μ΄μ
- Closes #35FortuneMonμ PRμ μΉμΈμ κΈ°λ°μΌλ‘ MERGEκ° κ°λ₯ν©λλ€.
μ΄ 4λͺ μ νμ μ€ 4λͺ μ΄ λͺ¨λ μΉμΈν΄μΌ MERGEκ° νμ±νλ©λλ€.
μ΄λ₯Ό ν΅ν΄ μ½λμ νμ§κ³Ό μμ μ±μ ν보νκ³ , λͺ¨λ νμμ΄ λ³κ²½μ¬νμ μΈμ§ν μ μλλ‘ ν©λλ€.
μμ±μ:
FortuneMonνμ μ κΈ°μ
μ λ°μ΄νΈ: 2025-06-15