개인 프로젝트⏰<Time Log>

조연정·2022년 2월 9일
0

"Time Log" https://time-log.netlify.app/

프로젝트 소개

시간을 효율적으로 집중하면서 쓰지 못한다는 생각에 유튜브에서 '집중하는 법'을 찾아봤다. 그 중에 눈에 들어왔던 방법이 꼼꼼하게 '시간을 기록하기' 였다. 몇가지 어플이 존재했지만, 마음에 드는 게 없어서 직접 만들어봐야지 생각했다. 나중는 기회가 되면 앱으로도 만들어보고싶다.

소셜 로그인

구글로그인을 통한 소셜로그인을 만들었다. 직접 로그인 기능을 만드는 것보다 간단하고 편했다.

Oauth

소셜 로그인의 핵심개념으로, 사용자의 패스워드없이도 권한을 위임받을 수 있는 기술을 말한다.
사용자 정보를 가지고 있는 웹 서비스에서 사용자의 인증을 대신 처리해주고, 접근 권한에 대한 토큰을 발급하면, 이를 이용해서 해당 웹서비스 API를 사용해 사용자 정보를 받아오거나 이를 이용해 session을 부여해 로그인 상태를 유지시킬 수 있다.

0. 그림에는 생략되어있지만 provider 내부에서 resourece server에서 authorization server로 인가책임을 위임하는 과정이 있다.

serverless로 구현

이번 프로젝트는 혼자 진행했기 때문에 serverless 서비스가 필요했다. firebase을 사용하여 CRUD 중 Create, Read를 구현해보았다.

cloud Firestore에 데이터 추가하기(add)

버튼을 눌렀을 때 '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 깔끔하게 정리하기

    기존에 img태그 src에 변수를 할당하는 방법을 적용시켰다. 수정도 쉽고, 관리하기 더 편한 것 같다.
// 적용 이전
<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} />
  • code-splitting

    맨처음 페이지에 진입하면 웹팩에서 압축한 번들파일을 다운받는다.spa의 특징으로 눈에 보이지 않는 페이지의 전체 리소스까지 전부 다운받게 되는데 이때문에 초기 페이지가 보이기까지 시간이 지체된다. code-splitting을 사용하면 덩치가 큰 번들파일을 분할하여, 작은 사이즈의 파일로 분할하여 로딩시간을 줄일 수 있다.
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>
  );
}

lazy

동적으로 import가 이루어진다.
ex) /record 페이지에 진입하면 Record 리소스만 다운받도록 lazy-loading시킨다.

suspense

컴포넌트가 동적으로 로드될 때 아무것도 로드되지 않는 순간, 에러를 발생시키지않고 suspense에 적용한 컴포넌트를 렌더링해준다.

profile
Lv.1🌷

0개의 댓글