"Time Log" https://time-log.netlify.app/
시간을 효율적으로 집중하면서 쓰지 못한다는 생각에 유튜브에서 '집중하는 법'을 찾아봤다. 그 중에 눈에 들어왔던 방법이 꼼꼼하게 '시간을 기록하기' 였다. 몇가지 어플이 존재했지만, 마음에 드는 게 없어서 직접 만들어봐야지 생각했다. 나중는 기회가 되면 앱으로도 만들어보고싶다.
구글로그인을 통한 소셜로그인을 만들었다. 직접 로그인 기능을 만드는 것보다 간단하고 편했다.
소셜 로그인의 핵심개념으로, 사용자의 패스워드없이도 권한을 위임받을 수 있는 기술을 말한다.
사용자 정보를 가지고 있는 웹 서비스에서 사용자의 인증을 대신 처리해주고, 접근 권한에 대한 토큰을 발급하면, 이를 이용해서 해당 웹서비스 API를 사용해 사용자 정보를 받아오거나 이를 이용해 session을 부여해 로그인 상태를 유지시킬 수 있다.
0. 그림에는 생략되어있지만 provider 내부에서 resourece server
에서 authorization server
로 인가책임을 위임하는 과정이 있다.
이번 프로젝트는 혼자 진행했기 때문에 serverless 서비스가 필요했다. firebase을 사용하여 CRUD 중 Create, Read를 구현해보았다.
버튼을 눌렀을 때 'handleRecord'함수를 작동시켜 파이어베이스로 데이터를 보내 저장
const handleRecord = () => {
handleRemove(selectedIdx);
const now = new Date();
const endAt = now.getHours() + ':' + now.getMinutes();
const result = {
name: list.name,
img: list.img,
timer: timer,
date: date,
startAt: startAt,
endAt: endAt,
identifier: userEmail,
};
db.collection('logInfo').add(result);
};
const getMyHistory = useCallback(async () => {
const firestoreData = await db
.collection('logInfo')
.where('identifier', '==', userEmail)
.get();
const originData = firestoreData.docs.map(
(doc) => doc.data() as IResultData,
);
setProcessingData(processData(originData));
}, [userEmail]);
useEffect(() => {
getMyHistory();
}, [getMyHistory]);
firebase를 사용하다보니 데이터 형태 때문에 어려움을 겪었다. 내가 원하는 모양으로 db내에서 디테일하게 수정할 수가 없어서 데이터를 받아온 후, 클라이언트 내에서 데이터를 가공해주는 함수를 따로 만들어서 해결하였다.
들어오는 데이터
{
"name": "공부",
"startAt": "22:44",
"identifier": "jolove.dev@gmail.com",
"img": "/static/media/study.bf2b89b3c7dfeb32a0a2.png",
"endAt": "21:44",
"timer": 3600
}
원하는 데이터 형태
{
"date": "2022.1.16",
"infoByDate": [
{
"identifier": "jolove.dev@gmail.com",
"img": "/static/media/study.bf2b89b3c7dfeb32a0a2.png",
"endAt": "21:44",
"timer": 3600,
"name": "공부",
"startAt": "22:44"
},
{
"endAt": "22:48",
"img": "/static/media/reading.1f9650abd36f9d59751b.png",
"startAt": "22:48",
"timer": 11,
"identifier": "jolove.dev@gmail.com",
"name": "독서"
},
]
}```
```js
export const processData = (rawData: IResultData[]) => {
let processingData: IProcessingData[] = [];
const uniqueDate: string[] = [];
// 날짜 가져오기
const getDate = rawData.map((el) => el.date);
// 유니크 날짜와 infodata 객체 만들어 주기
const combination = Array.from(new Set(getDate)).map((date) => {
uniqueDate.push(date || '');
return { date: date, infoByDate: [] };
});
// 객체 틀 배열에 넣어주기
processingData = [...combination] as IProcessingData[];
rawData.map((item) => {
const targetIdx = uniqueDate.indexOf(item.date || '');
delete item['date'];
return processingData[targetIdx].infoByDate.push(item);
});
return processingData;
};
// 적용 이전
<img src="/assets/a.png" />
<img src="/assets/b.png" />
<img src="/assets/c.png" />
//적용 후
//index.js -> 모듈에 모아두기
export { default as a } from './a.png';
export { default as b } from './a.png';
export { default as c } from './a.png';
// content.js
<img src={a} />
<img src={b} />
<img src={c} />
import { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import PrivateRoute from 'routes/PrivateRoute';
import { loading } from 'assets/index';
const Login = lazy(() => import('pages/Login'));
const Record = lazy(() => import('pages/Record'));
const History = lazy(() => import('pages/History'));
const NotFound = lazy(() => import('pages/NotFound'));
function App() {
return (
<BrowserRouter>
<Suspense
fallback={
<div>
<img width={130} src={loading} alt="loading" />
</div>
}
>
<Routes>
<Route path="/" element={<Login />} />
<Route
path="/record"
element={
<PrivateRoute>
<Record />
</PrivateRoute>
}
/>
<Route
path="/history"
element={
<PrivateRoute>
<History />
</PrivateRoute>
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
동적으로 import가 이루어진다.
ex) /record 페이지에 진입하면 Record
리소스만 다운받도록 lazy-loading
시킨다.
컴포넌트가 동적으로 로드될 때 아무것도 로드되지 않는 순간, 에러를 발생시키지않고 suspense에 적용한 컴포넌트를 렌더링해준다.