
Bài 10: Xây Dựng Frontend DApp (React ) – The Grand Finale
Chào mừng bạn đến với Bài 10 — trạm dừng chân cuối cùng và cũng là vinh quang nhất của Chapter 3!
Xuyên suốt 4 bài học vừa qua, chúng ta đã đi từ những dòng code logic khô khan dưới đáy blockchain (Aiken), bay lên không gian mạng phân tán (IPFS), chạm đến trí tuệ nhân tạo (MediaPipe) và đóng gói tất cả thành một cỗ máy ngầm API (FastAPI).
Nhưng một ứng dụng Web3 (DApp) thực thụ không thể bắt người dùng phải dùng Terminal! Hôm nay, chúng ta sẽ xây dựng Giao diện người dùng (Frontend) bằng React & Vite, biến mọi thứ trở nên trực quan, sống động và dễ dàng thao tác chỉ bằng những cú click chuột.
Mục Tiêu Bài Học
Trong bài viết cuối cùng này, bạn sẽ nắm được:
-
Kiến trúc Frontend chuẩn cho một DApp sử dụng React, TypeScript và Vite.
-
Tuyệt chiêu sử dụng Vite Proxy để kết nối Frontend và Backend mà không lo bị chặn bởi lỗi CORS.
-
Viết API Client an toàn với kiểu dữ liệu chặt chẽ (Type-safe) để tải ảnh (Multipart form data).
-
Xây dựng giao diện State Machine thay đổi tự động theo trạng thái của DID trên Blockchain.
-
Vận hành toàn bộ pipeline End-to-End từ AI nhận diện đến giao dịch thành công trên Cardano.
Kiến Trúc Frontend & Tuyệt Chiêu Vite Proxy
Dự án Frontend của chúng ta được thiết kế tinh gọn nhưng đầy đủ chức năng:
React App (Vite - Port: 5173)
├── App.tsx → Bố cục chính (Main layout) & Tab điều hướng
├── FaceDetector.tsx → Tab 1: Upload ảnh + AI Detect + Đẩy lên IPFS
├── DIDManager.tsx → Tab 2: Quản lý vòng đời DID (Register, Verify, Revoke)
├── api/client.ts → API client (Axios) tương tác với Backend
└── index.css → CSS thuần: Giao diện Dark theme, Glassmorphism
Cạm Bẫy Lớn (CORS Error): Khi bạn chạy Frontend ở port 5173 và cố gắng gọi API ở Backend port 8000, trình duyệt sẽ ngay lập tức chặn lại vì vi phạm chính sách bảo mật cùng nguồn gốc (CORS).
Cách giải quyết (Vite Proxy): Thay vì cấu hình CORS phức tạp trên Backend, chúng ta dùng mẹo Proxy của Vite trong file vite.config.ts:
export default defineConfig({
server: {
proxy: {
'/api': 'http://localhost:8000', // Mọi request /api đều được chuyển ngầm tới port 8000
'/health': 'http://localhost:8000',
}
}
})
Giờ đây, Frontend chỉ cần gọi API tới /api/v1/..., Vite sẽ tự động “đẩy” (forward) request đó sang Backend một cách trong suốt. Trình duyệt sẽ hoàn toàn bị “đánh lừa” và cho phép request đi qua!
Xây Dựng API Client (Type-Safe)
Việc định nghĩa các hàm gọi API được đặt trong client.ts. Nhờ sức mạnh của TypeScript, nếu cấu trúc Backend thay đổi, Frontend sẽ báo lỗi ngay từ lúc viết code thay vì lúc chạy.
export interface FaceVerifyResponse {
did_id: string;
match: boolean;
similarity: number;
message: string;
tx_hash: string | null;
}
// API Phát hiện khuôn mặt — Gửi ảnh qua FormData
export async function detectFaces(file: File): Promise<FaceDetectResponse> {
const form = new FormData();
form.append('file', file);
const { data } = await api.post('/api/v1/face/detect', form);
return data;
}
// API Xác minh danh tính (Verify) — Cũng cần gửi ảnh để so sánh
export async function verifyDID(didId: string, faceFile: File): Promise<FaceVerifyResponse> {
const form = new FormData();
form.append('file', faceFile);
const { data } = await api.post(`/api/v1/did/${didId}/verify`, form);
return data;
}
Lưu ý rằng chúng ta sử dụng đối tượng FormData để đóng gói file ảnh thay vì gửi JSON thông thường, vì Backend cần đọc trực tiếp các byte của file ảnh.
Tab 1: Phát Hiện Khuôn Mặt (Face Detection)
Trong tab này (FaceDetector.tsx), người dùng có thể nhấn hoặc kéo thả ảnh khuôn mặt của họ vào khung upload.
Khi có ảnh, React sẽ gọi detectFaces. Kết quả trả về sẽ hiển thị đầy đủ:
-
Số lượng khuôn mặt nhận diện được.
-
Độ tin cậy của AI (Ví dụ: 98.5%).
-
Vector đặc trưng:
512D. -
Mã IPFS CID (ví dụ:
QmXLaBY...).
Khi nhận diện thành công, nút “Create DID from this face” sẽ sáng lên. Khi người dùng bấm nút này, Backend sẽ thực hiện giao dịch Lock 2 ADA lên mạng Cardano Testnet!
Tab 2: Trình Quản Lý Vòng Đời DID (DID Manager)
Đây là nơi hiển thị mọi DID của bạn dưới dạng các thẻ (Card) tuyệt đẹp.
Giao diện ở đây là một State Machine (Cỗ máy trạng thái) thu nhỏ, phản chiếu chính xác logic CKV của Smart Contract (Bài 6):
-
🟡 Trạng thái LOCKED: Chỉ hiện nút
Register. -
🟣 Trạng thái REGISTERED: Hiện nút
VerifyvàRevoke. -
🟢 Trạng thái VERIFIED: Chỉ còn nút
Revoke. -
🔴 Trạng thái REVOKED: DID đã bị hủy, ADA đã được rút về, không thể thao tác thêm.
Điểm Nhấn Đặc Biệt: Nút “Verify with Face”
Khác với các ứng dụng bình thường, nút Verify không xác nhận bằng cú click chuột. Khi bấm “📸 Verify with Face”, hệ thống sẽ yêu cầu bạn tải lên ảnh khuôn mặt hiện tại để kiểm tra.
const handleVerifyFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file || !verifyDIDId) return;
// Gửi ảnh mới lên backend để so sánh AI
const result = await verifyDID(verifyDIDId, file);
setVerifyResult(result);
};
Kịch bản 1 (Match): Bạn tải đúng ảnh của bạn. Backend so sánh ra Cosine Similarity $100\%$. Một thanh trượt màu xanh lá (Gradient bar) hiện lên báo hiệu ✅ Face Verified! Kèm theo đó là link Hash của giao dịch on-chain.
Kịch bản 2 (Mismatch): Bạn thử tải ảnh của người khác. Backend báo Similarity chỉ đạt $23.5\%$ (dưới ngưỡng chuẩn $70\%$). Giao diện lập tức hiện màu đỏ: ❌ Face Mismatch – Cannot verify.
Đây chính là sự kết hợp hoàn hảo của Sinh trắc học (Biometric) và Blockchain!
Demo End-to-End Thực Tế
Đã đến lúc khởi động toàn bộ động cơ.
1. Khởi chạy Backend FastAPI:
cd lesson9_deploy_dapp
python -m uvicorn app.main:app --reload --port 8000
2. Khởi chạy Frontend React/Vite:
cd lesson10_demo_dapp
npm install
npm run dev
Mở trình duyệt ở http://localhost:5173. Bạn sẽ được trải nghiệm một luồng làm việc mượt mà:
-
Upload ảnh khuôn mặt $\rightarrow$ Phát hiện thành công!
-
Click tạo DID $\rightarrow$ Chờ 15s $\rightarrow$ DID xuất hiện ở Tab 2.
-
Bấm Register $\rightarrow$ Kích hoạt CKV thành công.
-
Tải lại ảnh khuôn mặt $\rightarrow$ Verify thành công!
-
Mở link giao dịch trên CardanoScan: Cả 4 giao dịch đều được ghi nhận vĩnh viễn trên mạng lưới Preprod!
Tổng Kết Chương 3 (Chapter 3 Wrap-up)
Xin chúc mừng! Bạn vừa hoàn thành một trong những dự án phức tạp và hiện đại nhất: DApp Nhận diện khuôn mặt phi tập trung.
Hãy nhìn lại hệ thống khổng lồ mà chúng ta đã xây dựng bằng một đường ống duy nhất:
📸 Camera → 🤖 MediaPipe → 📊 Embedding → ☁️ IPFS (CID)
(Bài 7) (Bài 7) (512D vector) (Bài 7)
↓
🌐 React UI ← 🖥️ FastAPI ← 🔗 PyCardano ← ⛓️ Aiken CKV
(Bài 10) (Bài 9) (Bài 8) (Bài 6)
Những Bài Học Xương Máu Đã Trải Qua:
-
Mô hình CKV (Continuing Key Validation): Bí quyết để xây dựng State machine trên Cardano.
-
Off-chain Storage: Blockchain không sinh ra để lưu dữ liệu lớn. Hãy dùng IPFS và chỉ lưu mã CID on-chain.
-
Đồng Bộ Kiểu Dữ Liệu: Đừng bao giờ dùng
Booltrong Aiken nếu bạn muốn tương tác mượt mà vớiPython/CBOR, hãy dùngInt. -
Phí Giao Dịch UTxO: Luôn nhớ nạp UTxO từ ví (
add_input_address) khi muốn chi tiêu từ Smart Contract.
Kiến trúc này không chỉ dừng lại ở ứng dụng nhận diện khuôn mặt. Bạn hoàn toàn có thể dùng nó làm bộ khung chuẩn (Architecture Template) cho bất kỳ DApp nào cần: Lưu trữ dữ liệu lớn off-chain, yêu cầu xác thực qua AI, và quản lý vòng đời trên Blockchain.
Cảm ơn các bạn đã đồng hành trong suốt hành trình này. Chúc các bạn code thật vui và sớm xây dựng được những siêu phẩm Web3 của riêng mình! 🚀
Hẹn gặp lại các bạn trong bài viết tiếp theo! Chúc các bạn code vui vẻ!
Chi tiết về source code toàn bộ bài học các bạn có thể tham khảo tại!!!
Pycardano integration with AI Implementation Example
