# 6. Frontend Code Overview

1. 개요
2. `src/App.js`
3. `src/pages`
4. 학습 내용

## 1) 개요 <a href="#id-1-overview" id="id-1-overview"></a>

이 장에서는 프론트엔드를 구축하겠습니다. 본 튜토리얼의 주목적은 컨트랙트와 프론트엔드 코드를 연결하는 방법을 알아보는 것입니다. 따라서 간략히 리액트 코드에 대해 설명하고 Klaytn에 배포된 컨트랙트와 상호작용하는 API 함수를 중점적으로 다루겠습니다.

```
|-- src
    |-- klaytn
      |-- caver.js
      |-- KlaystagramContract.js
    |-- redux
      |-- auth.js
      |-- photos.js
    |-- pages
      |-- AuthPage.js
      |-- FeedPage.js
    |-- components
      |-- UploadPhoto.js
      |-- Feed.js
      |-- TransferOwnership.js
      |-- ...
    |-- App.js
```

`src/klaytn`: Klaytn과의 상호작용을 지원하는 파일을 담고 있습니다.

* `src/klaytn/caver.js`: 환경설정에 따라 caver-js 인스턴스화합니다.

  참고) caver-js는 Klaytn 노드와 연결하여 해당 노드나 Klaytn에 배포된 컨트랙트와의 상호작용을 지원해주는 RPC 라이브러리입니다.
* `src/klaytn/Klaystagram.js`: caver-js API를 사용하여 컨트랙트 인스턴스를 작성합니다. 이 인스턴스를 통해 컨트랙트와 상호작용할 수 있습니다.

`src/redux`: 컨트랙트와 상호작용하여 결과 데이터를 추적하는 API 함수를 작성합니다.

* `redux/actions/auth.js`
* `redux/actions/photos.js`

`src/pages`: Klaystagram 애플리케이션을 구성하는 두 개의 페이지 파일을 담고 있습니다.

* `src/pages/AuthPage.js`: 회원가입 및 로그인 양식이 있습니다. 회원가입 시 개인키를 생성하여 애플리케이션에 로그인할 수 있습니다.
* `src/pages/FeedPage.js`: 스마트 컨트랙트로부터 사진을 읽어와 사용자에게 보여주고 업로드 기능을 제공합니다.

`src/components`: 페이지를 구성하는 컴포넌트 파일들을 담고 있습니다.

* `src/components/Feed.js`: 컨트랙트로부터 데이터를 읽어와 사진을 보여줍니다.
* `src/components/UploadPhoto.js`: 컨트랙트에 트랜잭션을 전송하여 사진을 업로드합니다.
* `src/components/TransferOwnership.js`: 트랜잭션을 전송하여 사진의 소유권을 이전합니다.

`src/App.js`: 본 튜토리얼의 루트 컴포넌트 파일입니다.

## 2) App.js <a href="#id-1-app-js" id="id-1-app-js"></a>

`'App.js'` 전체 컴포넌트 중 루트 컴포넌트입니다. 이 컴포넌트는 사용자의 로그인 상태에 따라 두 페이지를 렌더링합니다. 각 페이지에는 컨트랙트와 상호작용하는 함수가 있습니다. 블록체인에 트랜잭션을 전송하려면 caver에 지갑 인스턴스를 추가해야 합니다. 자 이제 코드를 간략히 살펴볼게요.

cf. caver-js(or `cav` in the code) is a library for interacting with Klaytn blockchain. 이 내용은 다음 장 - [7-1 스마트 컨트랙트를 프론트엔드에 연결](https://archive-ko.docs.klaytn.foundation/content/dapp/tutorials/klaystagram/7.-feedpage/7-1.-connect-contract-to-frontend)에서 자세히 다룰 것입니다.

```javascript
// src/App.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import AuthPage from 'pages/AuthPage'
import FeedPage from 'pages/FeedPage'
import Nav from 'components/Nav'
import Footer from 'components/Footer'
import Modal from 'components/Modal'
import Toast from 'components/Toast'

import * as authActions from 'redux/actions/auth'

import './App.scss'

class App extends Component {
  constructor(props) {
    super(props)
    /**
     * 1. `isLoggedIn` 상태 초기화
     * 참고) sessionStorage는 브라우저 탭이 닫힐 때까지 데이터를 저장하는 인터넷 브라우저의 기능입니다.
     */
    const walletFromSession = sessionStorage.getItem('walletInstance')
    const { integrateWallet, removeWallet } = this.props

    if (walletFromSession) {
      try {
        /**
         * 2-1. 지갑 연동
         * 'walletInstance' 값이 있는 경우
         * intergrateWallet 메서드가 caver 지갑과 리덕스 스토어에 인스턴스를 추가합니다.
         * 참고) redux/actions/auth.js -> integrateWallet()
         */
        integrateWallet(JSON.parse(walletFromSession).privateKey)
      } catch (e) {
        /**
         * 2-2. 지갑 제거
         * sessionStorage의 값이 유효하지 않은 지갑 인스턴스인 경우
         * removeWallet 메서드가 caver 지갑과 리덕스 스토어에서 인스턴스를 제거합니다.
         * 참고) redux/actions/auth.js -> removeWallet()
         */
        removeWallet()
      }
    }
  }
  /**
   * 3. 페이지 렌더링
   * 리덕스가 walletInstance이 세션 스토리지에 존재하는지 여부에 따라 isLoggedIn의 상태를 true 또는 false로 초기화합니다.
   */
  render() {
    const { isLoggedIn } = this.props
    return (
      <div className="App">
        <Modal />
        <Toast />
        {isLoggedIn && <Nav />}
        {isLoggedIn ? <FeedPage /> : <AuthPage />}
        <Footer />
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  isLoggedIn: state.auth.isLoggedIn,
})

const mapDispatchToProps = (dispatch) => ({
  integrateWallet: (privateKey) => dispatch(authActions.integrateWallet(privateKey)),
  removeWallet: () => dispatch(authActions.removeWallet()),
})

export default connect(mapStateToProps, mapDispatchToProps)(App)
```

참고) `walletInstance` 세션이 JSON 문자열로 저장되기 때문에 `JSON.parse`가 필요합니다.

**1. Initialize `isLoggedIn` state**\
To initialize state `isLoggedIn`, we use `constructor` life cycle method on App component. 이는 컴포넌트를 마운트하기 전에 브라우저의 sessionStorage 내의 `walletInstance` 세션을 확인합니다.

**2. Inject/Remove wallet**\
If you have never logged in before, `walletInstance` session may not exist. 로그인했었다면 sessionStorage 내에 `walletInstance` 세션이 JSON 문자열의 형태로 존재할 것입니다.

1. 삽입 - 지갑 인스턴스가 sessionStorage에 있으면 해당 지갑 인스턴스를 caver와 리덕스 스토어에 추가하세요.
2. 제거 - sessionStorage에 있는 지갑 인스턴스가 유효하지 않으면 caver 지갑과 리덕스 스토어에서 제거하세요.

```javascript
// redux/actions/auth.js

// 1. 지갑 삽입
export const integrateWallet = (privateKey) => (dispatch) => {
  // caver의 privateKeyToAccount API를 이용하여 지갑 인스턴스를 생성합니다.
  const walletInstance = cav.klay.accounts.privateKeyToAccount(privateKey)

  // 트랜잭션을 보내려면 지갑 인스턴스를 caver에 추가합니다.
  cav.klay.accounts.wallet.add(walletInstance)

  // 로그인 상태를 유지하려면 walletInstance를 sessionStorage에 저장합니다.
  sessionStorage.setItem('walletInstance', JSON.stringify(walletInstance))

  // 애플리케이션을 실행하는 동안 walletInstance 정보에 접근하려면 리덕스 스토어에 저장합니다.
  return dispatch({
    type: INTEGRATE_WALLET,
    payload: {
      privateKey,
      address: walletInstance.address,
    },
  })
}

// 2. 지갑 제거
export const removeWallet = () => (dispatch) => {
  cav.klay.accounts.wallet.clear()
  sessionStorage.removeItem('walletInstance')
  return dispatch({
    type: REMOVE_WALLET,
  })
}
```

cf. For further information about caver's `privateKeyToAccount` API, see [caver.klay.accounts.privateKeyToAccount](https://docs.klaytn.foundation/dapp/sdk/caver-js/v1.4.1/api-references/caver.klay.accounts#privatekeytoaccount).

**3. 페이지 렌더링** 리덕스는 walletInstance가 세션 스토리지에 존재하는지 여부에 따라 `isLoggedIn` 상태를 true 또는 false로 초기화합니다.

## 3) `src/pages` <a href="#id-2-src-pages" id="id-2-src-pages"></a>

[위](#1-overview)에서 설명했듯이 `src/pages`는 두 페이지 파일을 포함합니다. 사용자의 로그인 여부에 따라 두 페이지 중 하나가 애플리케이션에 렌더링됩니다.

* `AuthPage.js`: 회원가입 및 로그인 양식이 있습니다. 회원가입 시 개인키를 생성하여 애플리케이션에 로그인할 수 있습니다.
* `FeedPage.js`: 컨트랙트로부터 사진 데이터를 읽어와 사용자들에게 보여줍니다. 또한 사용자는 사진을 업로드할 수도 있습니다.

## 4) What we are going to learn? <a href="#id-3-what-we-are-going-to-learn" id="id-3-what-we-are-going-to-learn"></a>

블록체인 기반 애플리케이션에서 컨트랙트와 상호작용하는 방법이 두 가지 있습니다.

1\) **Reading** data from contract.\
2\) **Writing** data to contract.

Reading data from contract is cost-free.\
On the otherhand, there is cost for writing data to contract (Sending a transaction). 따라서 데이터를 쓰려면 이 비용을 지불할 KLAY가 있는 Klaytn 계정이 있어야 합니다.

AuthPage에 있는 `SignupForm`을 통해 Klaytn 계정(개인키)을 생성할 수 있습니다. 이후에 생성한 개인키로 로그인하여 트랜잭션 수수료를 지불할 수 있습니다.

If you want to learn more about the two different login methods (private key / keystore),\
please refer to the [5.2. Auth Component](https://archive-ko.docs.klaytn.foundation/content/dapp/tutorials/count-dapp/5.-frontend-code-overview/5-2.-auth-component) page.

본 튜토리얼에서는 `FeedPage`에 중점을 두어 애플리케이션이 어떻게 컨트랙트에서 **데이터를 읽고 쓰는지**를 알아봅니다.
