Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Next.js와 MetaMask로 구축하는 DApp 사용자 환경설정 저장소 완벽 가이드

DAO, DApp과 같은 Web3 애플리케이션이 점점 더 많은 인기를 얻고 있습니다. Web3의 기본 전제에 따르면, 이러한 플랫폼은 사용자의 신원을 타인에게 비공개로 유지하거나 심지어 본인조차 알 수 없게 하면서도, 더 개인화되고 맞춤화된 경험을 제공해야 합니다.

이 글에서는 이러한 상황에서 사용자 경험을 한층 향상시키는 방법을 살펴보겠습니다.

지갑을 통한 인증 및 신원 확인

많은 애플리케이션에서 MetaMask와 같은 블록체인 지갑은 ETH 등 암호화폐 자산을 관리하는 데 사용됩니다. 이러한 지갑을 통해 사용자는 여러 체인에서 계정을 생성할 수 있습니다. 계정은 개인 키(private key)로 생성되지만, 각 계정에는 이를 대표하는 공개 주소(public address)가 존재합니다.

공개 주소는 누구나 열람할 수 있지만, 해당 주소와 관련된 트랜잭션은 오직 소유자만 관리할 수 있습니다. 따라서 지갑 연결은 이런 애플리케이션에 매우 견고한 인증 메커니즘을 제공합니다.

프로젝트 소개

이 프로젝트에서는 Next.js를 활용해 사용자가 자신에게 제공되는 인터페이스를 직접 커스터마이징할 수 있는 환경설정(Preference) 저장 시스템을 구현합니다. 즉, 사용자가 사이트를 다시 방문하면 언제나 자신이 의도한 대로의 UI를 보게 됩니다.

더 나아가 여러 회사의 하위·크로스 플랫폼 간에 사용자 인터페이스를 정규화하는 것까지 확장할 수 있습니다. 이렇게 하면 사용자가 다른 곳에서 설정해 둔 환경설정을 지원하는 사이트에 방문할 때마다 일관된 경험을 누릴 수 있습니다.

또한 이 프로젝트는 커스터마이징 파라미터의 개수를 자유롭게 조정할 수 있어, 원하는 어떤 구조로든 응용이 가능합니다.

이 샘플 프로젝트에서는 단 두 가지 파라미터만 사용합니다:

  • 웹사이트 테마
  • 사용자를 부를 때 사용할 이름(인사말)

시작하기

Upstash Redis 환경 변수 설정

  • Upstash에서 무료 Redis 데이터베이스를 생성합니다.
  • .env.local.example 파일을 .env.local(Git에서 무시됨)로 복사합니다.
  • UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN은 Upstash 콘솔의 데이터베이스 상세 페이지에서 확인할 수 있습니다.

의존성 설치

다음 명령어로 필요한 패키지를 설치합니다:

npm i @upstash/redis @metamask/detect-provider @mui/material @emotion/react @emotion/styled

api/store.js 파일 생성

Redis SDK를 설정하고 핸들러 함수를 export합니다.

import { Redis } from "@upstash/redis"
const redis = Redis.fromEnv()

export default async function handler(req, res) {
  const accountID = await JSON.parse(req.body).accountID
  const body = req.body
  const setResult = await redis.set(accountID, body);
  res.status(200).json({ result: setResult })
}

api/[accountAddress].js 파일 생성

마찬가지로 Redis SDK를 설정하고 핸들러를 export합니다.

import { Redis } from "@upstash/redis"
const redis = Redis.fromEnv()

export default async function handler(req, res) {
    const accountID = req.query.accountAddress
    const getResult = await redis.get(accountID)
    res.status(200).json({ result: getResult })
}

index.js 파일 설정

프로젝트 변수 설정

const [accountAddress, setAccountAddress] = useState(null)
const [themePreference, setThemePreference] = useState("light")
const [greetingMessage, setGreetingMessage] = useState("Anonymous Person")
const [userSettings, setUserSettings] = useState(null)

// 이미 연결이 설정되어 있는지 확인하고, 연결되어 있다면 데이터를 가져옵니다.
useEffect(() => {
    checkConnection()
    getPreferences()
}, [accountAddress])

MetaMask 연결하기

async function connect() {
    const provider = await detectEthereumProvider()
    console.log("provider:", provider)

    if (provider) {
      console.log('Ethereum successfully detected!')
      provider.request({ method: "eth_requestAccounts" }).then((accounts) => {
        if (!accountAddress) {
          setAccountAddress(accounts[0])
        }
      }).catch((err) => console.log(err))
      console.log("window.ethereum:", window.ethereum)
      getPreferences()
    } else {
      alert('Please install MetaMask!')
    }
  }

새로고침 또는 일정 시간 후 재방문 시 연결 상태 확인

async function checkConnection() {
    const provider = await detectEthereumProvider()
    if (provider) {
      provider
        .request({ method: 'eth_accounts' })
        .then(accounts => {
          setAccountAddress(accounts[0])
        })
        .catch(console.log)
    } else {
      console.log("Not connected, window.ethereum not found")
    }
}

환경설정 저장하기

async function setPreferences(themePreference, greetingMessage) {
    if (accountAddress) {
      const res = await fetch(`/api/store`, {
        method: "POST",
        body: JSON.stringify({
          accountID: accountAddress,
          themePreference: themePreference,
          greetingMessage: greetingMessage,
        })
      })
      const data = await res.json()
    }
    else {
      alert("No account address detected")
    }
}

환경설정 불러오기

async function getPreferences(e) {
    if (accountAddress) {
      console.log("Fetching user preferences...")
      const res = await fetch(`/api/${accountAddress}`, { method: "GET" })
      const data = await res.json()

      setUserSettings(data.result)
      if (data.result) {
        setThemePreference(data.result.themePreference)
        setGreetingMessage(data.result.greetingMessage)
      }
    }
    else {
      console.log("No account connected yet!")
    }
}

변수 상태 변경

아래 함수들을 입력 필드나 버튼 등에 바인딩하세요.

async function handleDarkMode(e) {
    console.log("themePreference:", themePreference)
    const newPreference = themePreference == "light" ? "dark" : "light"
    setThemePreference(newPreference)
    await setPreferences(newPreference, greetingMessage)
    await getPreferences()
}

async function takeGreetingMessage(e) {
    // Enter 키로 제출
    if (e.keyCode == 13) {
        const message = e.target.value
        setGreetingMessage(message)
        console.log(message)
        await setPreferences(themePreference, message)
        await getPreferences()
        e.target.value = ""
    }
}

위 함수들을 활용한 간단한 UI 만들기

관련 Material-UI 의존성을 import했는지 먼저 확인하세요.

return (
    <div className={styles.container}>

      <h2>Web3 Preferences Holder</h2>
      <Button variant="contained" onClick={connect}>Connect Metamask</Button>
      <p>
        Lets you keep user preferences on cross-websites
      </p>
      <br />
      <p>For example, take a greeter message from user.</p>
      <TextField label="Call me..." variant="outlined" size="small" onKeyDown={takeGreetingMessage} />
      <br />
      <br />

      <Button onClick={handleDarkMode} variant="contained" size="small" style={{ backgroundColor: "#3D3B3B" }}> Switch Dark Mode </Button>

      <p>Sample Component/Page:</p>
      <Showcase userSettings={userSettings} />

    </div>
)

components 디렉터리에 sampleComponent.jsx 생성

이 컴포넌트는 웹 인터페이스나 애플리케이션을 나타내는 메인 컴포넌트입니다. 시스템이 작동하는 방식과 핵심 목표를 시각적으로 보여주는 역할을 하며, 여기서 파라미터를 원하는 방식으로 파싱해 샘플 인터페이스를 만들면 됩니다.

이 예제에서 프로젝트는 다음과 같이 구성되어 있습니다:


import React from "react";
import { useState} from 'react'

import { ThemeProvider, createTheme } from '@mui/material/styles'
import { orange, grey } from '@mui/material/colors'

const lightTheme = createTheme({
  palette: {
    primary: {
      main: grey[400],
    },
  }
})

const darkTheme = createTheme({
  palette: {
    primary: {
      main: orange[400],
    },
  }
})

export default function Showcase(parameters) {

  const userSettings = parameters.userSettings
  const [theme, setTheme] = useState("light")
  const [greetingMessage, setGreetingMessage] = useState("Anonymous Person")

  const items = []
  if (userSettings) {

    const obj = userSettings[0]

    for (const key in userSettings) {
      items.push(<li key={key}> {key}: {userSettings[key]} </li>)
    }

    if (userSettings["themePreference"] != theme) {
      setTheme(userSettings["themePreference"] == "light" ? "light" : "dark")
    }

    if (!greetingMessage || userSettings["greetingMessage"] != greetingMessage) {
      if (userSettings["greetingMessage"]) {
        setGreetingMessage(userSettings["greetingMessage"])
      }
      else {
        setGreetingMessage("not the same message")
      }
    }
  }

  return (
    <div>
      <div style={{
        padding: 10,
        margin: 10,
        backgroundColor: theme == "light" ? "grey" : "orange",
        border: "solid",
        borderWidth: "30px",
        borderColor: theme == "light" ? "#B2B2B2" : "black"
      }}>
        <ThemeProvider
          theme={theme == "light" ? lightTheme : darkTheme}
        >

          <h2>Hi, {greetingMessage}!</h2>
          <p>User and their preferences:</p>
          {items}

        </ThemeProvider>

      </div>
    </div>
  );
}

워크플로우

MetaMask로 아직 연결하지 않은 사용자 — 기본 템플릿

Next.js와 MetaMask로 구축하는 DApp 사용자 환경설정 저장소 완벽 가이드

MetaMask에 처음으로 연결한 사용자 — 기본 템플릿

Next.js와 MetaMask로 구축하는 DApp 사용자 환경설정 저장소 완벽 가이드

  • 사용자가 플랫폼에서 인사받을 때 보고 싶은 이름을 입력합니다.
  • 사용자가 라이트 테마와 다크 테마 중 하나를 선택합니다.

환경설정 저장 완료 — 이후부터는 해당 환경설정 인터페이스를 지원하는 모든 플랫폼에서 동일한 인터페이스 표시 가능 (사용자 맞춤형 템플릿)

Next.js와 MetaMask로 구축하는 DApp 사용자 환경설정 저장소 완벽 가이드

컴포넌트는 사용자가 설정한 환경설정 값을 텍스트 형식으로 보여주며, 원하는 방식으로 자유롭게 파싱해 활용할 수 있습니다.

이 간단한 예제에서 확인할 수 있듯이, 사용자에게 보여주는 모든 컴포넌트를 파라미터화할 수 있습니다. 디자인을 개선하고 파라미터 수를 늘려가며 프로젝트를 계속 발전시킬 수 있지만, 근본적인 로직은 언제나 동일하게 유지됩니다.

마무리

프로젝트 데모는 여기에서 확인할 수 있습니다.

완성된 프로젝트는 GitHub 저장소에서 확인하세요.

저장소에는 Vercel 빠른 배포 버튼이 포함되어 있어, 프로젝트를 손쉽게 배포하면서 Upstash Redis 통합까지 자동으로 생성할 수 있습니다.

여러분의 아이디어와 피드백을 기다립니다. Twitter 또는 Discord를 통해 언제든 문의해 주세요.