Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

จัดเก็บ User Preferences ใน Web3 Apps: โซลูชัน Next.js รองรับ Metamask

Web3 Applications เช่น DAOs และ DApps กำลังเป็นที่นิยมมากขึ้น ตามหลักการของ Web3 แพลตฟอร์มเหล่านี้ควรให้ประสบการณ์ที่ส่วนตัวและปรับแต่งได้มากขึ้นสำหรับผู้ใช้ พร้อมรักษาความเป็นส่วนตัวของตัวตน ไม่ให้ผู้อื่นรู้หรือแม้แต่ตัวเองก็ไม่รู้

ในโปรเจกต์นี้ เราสำรวจวิธีปรับปรุง User Experience สำหรับกรณีเหล่านี้

การยืนยันตัวตน / ตรวจสอบ Identity ผ่าน Wallet

สำหรับแอปพลิเคชันหลายอย่าง Blockchain Wallets เช่น Metamask ใช้จัดการ Crypto Assets เช่น ETH Wallet เหล่านี้ให้ผู้ใช้สร้าง Accounts บน Chain ต่างๆ Accounts สร้างด้วย Private Keys แต่แต่ละ Account มี Public Address แทน

เนื่องจาก Public Keys แสดงได้สาธารณะ แต่ Transactions ที่เกี่ยวข้องจัดการได้เฉพาะเจ้าของ การเชื่อมต่อ Wallet จึงให้กลไก Authentication ที่มั่นคงสำหรับแอปพลิเคชันแบบนี้

โปรเจกต์

ในโปรเจกต์นี้ เราจะสร้างระบบจัดเก็บ Preferences ด้วย Next.js โดยผู้ใช้ปรับแต่ง Interface ที่ให้มา หมายความว่าเมื่อพวกเขากลับมาที่เว็บไซต์ จะเห็น UI ตามที่ตั้งใจไว้

แนวคิดที่ทรงพลังกว่านั้นคือการ Normalize User Interfaces ข้าม Sub-platforms และ Cross-platforms ของบริษัท ทำให้เมื่อผู้ใช้ไปยังเว็บไซต์ที่รองรับ Preferences ที่ตั้งค่าไว้ที่เว็บอื่น พวกเขาจะได้ประสบการณ์ที่คล้ายกัน

โปรเจกต์นี้ยังปรับเปลี่ยนได้ เพื่อเปลี่ยนจำนวน Parameters สำหรับการปรับแต่ง ให้เหมาะกับโครงสร้างที่คุณต้องการ

สำหรับตัวอย่างโปรเจกต์นี้ มี 2 Parameters คือ:

  • Theme ของเว็บไซต์
  • Greeting Name สำหรับผู้ใช้

เริ่มต้น

ตั้งค่า Environment Variables สำหรับ Upstash Redis

  • สร้าง Redis Database ฟรีที่ Upstash
  • คัดลอกไฟล์ .env.local.example เป็น .env.local (จะถูก Git Ignore)
  • UPSTASH_REDIS_REST_URL และ UPSTASH_REDIS_REST_TOKEN หาได้ที่หน้ารายละเอียด Database ใน Upstash Console

Dependencies

ติดตั้ง Dependencies: npm i @upstash/redis @metamask/detect-provider @mui/material @emotion/react @emotion/styled

สร้างไฟล์ api/store.js

ตั้งค่า Redis SDK และ Export Handler Function

// api/store.js
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 Handler เช่นเดียวกัน

// api/[accountAddress].js
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

ตั้งค่าตัวแปรสำหรับโปรเจกต์

// 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!');
    }
}

ตรวจสอบการเชื่อมต่อเมื่อ Refresh หรือ Navigate หลังจากผ่านไปสักพัก

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");
    }
}

ตั้งค่า Preferences

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");
    }
}

ดึง Preferences

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!");
    }
}

เปลี่ยน State ของตัวแปร

ผูกฟังก์ชันต่อไปนี้กับ Input Fields / Buttons ฯลฯ

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) {
    // Submit ด้วย Enter/Return Key
    if (e.keyCode == 13) {
        const message = e.target.value;
        setGreetingMessage(message);
        console.log(message);
        await setPreferences(themePreference, message);
        await getPreferences();
        e.target.value = "";
    }
}

สร้าง UI เรียบง่ายด้วยฟังก์ชันข้างต้น

ตรวจสอบว่า Import Material-UI Dependencies ที่เกี่ยวข้องแล้ว

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>
);

สร้าง sampleComponent.jsx ใน Directory components

นี่คือ Component หลักที่แสดง Web Interface / Application เป็นตัวอย่างการทำงานของระบบและเป้าหมายหลัก ที่นี่ Parse Parameters ตามที่ต้องการและสร้าง Sample Interface

ในกรณีนี้ โปรเจกต์ตั้งค่าดังนี้:

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>
    );
}

Workflow

ผู้ใช้ยังไม่เชื่อมต่อ Metamask - Default Template

จัดเก็บ User Preferences ใน Web3 Apps: โซลูชัน Next.js รองรับ Metamask

ผู้ใช้เชื่อมต่อ Metamask ครั้งแรก - Default Template

จัดเก็บ User Preferences ใน Web3 Apps: โซลูชัน Next.js รองรับ Metamask

  • ผู้ใช้กรอกชื่อที่ต้องการให้เรียกเมื่อเข้าสู่แพลตฟอร์ม
  • ผู้ใช้เลือก Theme ระหว่าง Light และ Dark

ตั้งค่า Preferences แล้ว - ตั้งแต่ตอนนี้ Interface เดียวกันจะแสดงได้ใน Platform ใดๆ ที่รองรับ Preference Interface - User-Customized Template

จัดเก็บ User Preferences ใน Web3 Apps: โซลูชัน Next.js รองรับ Metamask

Component แสดง Preferences ที่ผู้ใช้ตั้งค่าในรูปแบบข้อความ คุณสามารถ Parse ตามต้องการได้

จากตัวอย่างง่ายๆ นี้ คุณเห็นได้ว่าสามารถ Parametrize Component ใดๆ ที่แสดงให้ผู้ใช้ได้ คุณสามารถปรับปรุง Design และเพิ่ม Parameters ได้ แต่ Logic พื้นฐานยังคงเดิม

ข้อสรุป

ดู Demo ของโปรเจกต์ ที่นี่

ดูโปรเจกต์เสร็จสมบูรณ์ GitHub Repo

ที่นั่นมี Quick Deploy Button สำหรับ Vercel Deployment ให้ Deploy โปรเจกต์เร็วๆ และสร้าง Upstash Redis Integration อัตโนมัติ

เราคาดหวังไอเดียและความคิดเห็นของคุณ สามารถติดต่อเราได้ทาง Twitter หรือ Discord