From 3f8437c4f7ecccd479da67ff759ef9e7e591c1fc Mon Sep 17 00:00:00 2001 From: taskooh Date: Sun, 25 Aug 2024 05:54:37 +0900 Subject: [PATCH] Show Loyalty Token Name & Amount --- README.md | 4 +- src/app/client.ts | 343 ++++++++++++------------------- src/components/StoreSelector.tsx | 104 +++++++--- src/utils/store/management.ts | 5 + src/utils/type.ts | 4 + 5 files changed, 219 insertions(+), 241 deletions(-) diff --git a/README.md b/README.md index bec0783..d716823 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ OpenBooking addresses several issues in the traditional booking industry: - Ensuring seamless integration between frontend and smart contracts - Optimizing gas fees for various blockchain operations - Designing a flexible system to accommodate different types of services and businesses +- Implementation of a token-based loyalty program ## Technologies I used @@ -50,9 +51,8 @@ OpenBooking addresses several issues in the traditional booking industry: - Mobile app development for increased accessibility - Integration with popular calendar systems - Multi-chain support to offer users more blockchain options -- Implementation of a token-based loyalty program -- AI-powered recommendation system for personalized booking experiences - Expansion into new service sectors beyond current offerings +- Gasless transactions using thirdweb paymaster ## Installation diff --git a/src/app/client.ts b/src/app/client.ts index 6d7291a..2a4dc25 100644 --- a/src/app/client.ts +++ b/src/app/client.ts @@ -381,31 +381,91 @@ export const bookingContract = getContract({ ], }); +export const erc20Abi = [ + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "recipient", type: "address" }], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "spender", type: "address" }], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + export const loyaltyLogicContractAbi = [ { - anonymous: false, inputs: [ { - indexed: true, internalType: "address", - name: "user", + name: "_loyaltyTokenAddress", type: "address", }, { - indexed: false, - internalType: "uint256", - name: "reservationId", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "deposit", - type: "uint256", + internalType: "address", + name: "_bookingContractAddress", + type: "address", }, ], - name: "DepositForfeited", - type: "event", + stateMutability: "nonpayable", + type: "constructor", }, { anonymous: false, @@ -413,7 +473,7 @@ export const loyaltyLogicContractAbi = [ { indexed: true, internalType: "address", - name: "user", + name: "customer", type: "address", }, { @@ -429,7 +489,7 @@ export const loyaltyLogicContractAbi = [ type: "uint256", }, ], - name: "PaymentFinalized", + name: "LoyaltyPointsAwarded", type: "event", }, { @@ -438,152 +498,85 @@ export const loyaltyLogicContractAbi = [ { indexed: true, internalType: "address", - name: "user", + name: "previousOwner", type: "address", }, { - indexed: false, - internalType: "uint256", - name: "reservationId", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "deposit", - type: "uint256", + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", }, ], - name: "ReservationBooked", + name: "OwnershipTransferred", type: "event", }, { - anonymous: false, inputs: [ { - indexed: false, - internalType: "uint256", - name: "reservationId", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "storeId", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "datetime", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "deposit", - type: "uint256", - }, - { - indexed: false, - internalType: "uint256", - name: "serviceFee", - type: "uint256", + internalType: "address", + name: "_user", + type: "address", }, - ], - name: "ReservationSlotAdded", - type: "event", - }, - { - anonymous: false, - inputs: [ { - indexed: false, internalType: "uint256", - name: "storeId", + name: "_amount", type: "uint256", }, - { - indexed: false, - internalType: "string", - name: "storeName", - type: "string", - }, - { - indexed: false, - internalType: "address", - name: "storeAdmin", - type: "address", - }, ], - name: "StoreAdded", - type: "event", + name: "awardPoints", + outputs: [], + stateMutability: "nonpayable", + type: "function", }, { inputs: [ { internalType: "uint256", - name: "storeId", - type: "uint256", - }, - { - internalType: "uint256", - name: "datetime", - type: "uint256", - }, - { - internalType: "uint256", - name: "deposit", + name: "reservationId", type: "uint256", }, { internalType: "uint256", - name: "serviceFee", + name: "consumeLoyaltyPoints", type: "uint256", }, ], - name: "addReservationSlot", + name: "bookReservationDiscount", outputs: [], stateMutability: "nonpayable", type: "function", }, { - inputs: [ + inputs: [], + name: "bookingContract", + outputs: [ { - internalType: "string", - name: "storeName", - type: "string", + internalType: "contract IBookingContract", + name: "", + type: "address", }, ], - name: "addStore", - outputs: [], - stateMutability: "nonpayable", + stateMutability: "view", type: "function", }, { inputs: [ { - internalType: "uint256", - name: "reservationId", - type: "uint256", + internalType: "address", + name: "_user", + type: "address", }, ], - name: "bookReservation", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ + name: "checkBalance", + outputs: [ { internalType: "uint256", - name: "reservationId", + name: "", type: "uint256", }, ], - name: "finalizePayment", - outputs: [], - stateMutability: "payable", + stateMutability: "view", type: "function", }, { @@ -594,135 +587,73 @@ export const loyaltyLogicContractAbi = [ type: "uint256", }, ], - name: "forfeitDeposit", + name: "finalizePaymentAndAwardPoints", outputs: [], - stateMutability: "nonpayable", + stateMutability: "payable", type: "function", }, { - inputs: [ + inputs: [], + name: "loyaltyToken", + outputs: [ { - internalType: "uint256", + internalType: "contract IERC20", name: "", - type: "uint256", + type: "address", }, ], - name: "reservations", + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", outputs: [ - { - internalType: "uint256", - name: "storeId", - type: "uint256", - }, { internalType: "address", - name: "customer", + name: "", type: "address", }, - { - internalType: "uint256", - name: "datetime", - type: "uint256", - }, - { - internalType: "uint256", - name: "requiredDeposit", - type: "uint256", - }, - { - internalType: "uint256", - name: "currentDeposit", - type: "uint256", - }, - { - internalType: "uint256", - name: "serviceFee", - type: "uint256", - }, - { - internalType: "bool", - name: "paid", - type: "bool", - }, ], stateMutability: "view", type: "function", }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - name: "stores", - outputs: [ - { - internalType: "uint256", - name: "storeId", - type: "uint256", - }, - { - internalType: "string", - name: "storeName", - type: "string", - }, { internalType: "address", - name: "storeAdmin", + name: "newOwner", type: "address", }, ], - stateMutability: "view", + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", type: "function", }, { inputs: [ - { - internalType: "uint256", - name: "_reservationId", - type: "uint256", - }, - { - internalType: "uint256", - name: "_storeId", - type: "uint256", - }, { internalType: "address", - name: "_customer", + name: "_user", type: "address", }, { internalType: "uint256", - name: "_datetime", + name: "_amount", type: "uint256", }, - { - internalType: "uint256", - name: "_requiredDeposit", - type: "uint256", - }, - { - internalType: "uint256", - name: "_currentDeposit", - type: "uint256", - }, - { - internalType: "uint256", - name: "_serviceFee", - type: "uint256", - }, - { - internalType: "bool", - name: "_paid", - type: "bool", - }, ], - name: "updateReservation", + name: "usePoints", outputs: [], stateMutability: "nonpayable", type: "function", }, -]; +] as const; diff --git a/src/components/StoreSelector.tsx b/src/components/StoreSelector.tsx index 79561fc..c70354f 100644 --- a/src/components/StoreSelector.tsx +++ b/src/components/StoreSelector.tsx @@ -3,6 +3,8 @@ import Image from "next/image"; import { Store } from "@/utils/type"; import { useActiveAccount } from "thirdweb/react"; import { listReservations, listStores } from "@/utils/store/management"; +import { getContract, readContract } from "thirdweb"; +import { chain, client, erc20Abi, loyaltyLogicContractAbi } from "@/app/client"; // const stores = [ // { @@ -46,24 +48,57 @@ export default function StoreSelector({ useEffect(() => { if (!account) return; - listStores().then((stores) => { - stores.forEach((store) => { - store.storeImage = `/store-a.jpg`; - listReservations(store.storeId).then((reservations) => { - const fees = reservations.map((reservation) => - Number(reservation.serviceFee) - ); - const maxFee = Math.max(...fees); - const minFee = Math.min(...fees); + const fetchStoresAndReservations = async () => { + const fetchedStores = await listStores(); + const updatedStores = await Promise.all( + fetchedStores.map(async (store) => { + store.storeImage = `/store-a.jpg`; + const reservations = await listReservations(store.storeId); + const fees = reservations.map((reservation) => Number(reservation.serviceFee)); + store.maxFee = fees.length ? Math.max(...fees) : 0; + store.minFee = fees.length ? Math.min(...fees) : 0; - store.maxFee = maxFee; - store.minFee = minFee; - }); - }); + // get loyalty logic contract address + const loyaltyLogicContract = getContract({ + client, + address: store.loyaltyLogicContractAddress, + chain: chain, + abi: loyaltyLogicContractAbi, + }); + const loyaltyTokenContractAddress = await readContract({ + contract: loyaltyLogicContract, + method: "loyaltyToken", + }); + store.loyaltyTokenContractAddress = loyaltyTokenContractAddress; - setStores(stores); - }); + const loyaltyTokenContract = getContract({ + client, + address: loyaltyTokenContractAddress, + chain: chain, + abi: erc20Abi, + }); + store.loyaltyTokenContractAddress = loyaltyTokenContractAddress; + + const loyaltyTokenName = await readContract({ + contract: loyaltyTokenContract, + method: "name", + }); + store.loyaltyTokenName = loyaltyTokenName; + + const loyalyTokenAmount = await readContract({ + contract: loyaltyTokenContract, + method: "balanceOf", + params: [account.address], + }); + store.loyaltyTokenAmount = loyalyTokenAmount; + return store; + }) + ); + setStores(updatedStores); + }; + + fetchStoresAndReservations(); }, [account]); const handleStoreSelect = (storeId: number) => { @@ -78,33 +113,36 @@ export default function StoreSelector({
handleStoreSelect(Number(store.storeId))} - className={`cursor-pointer p-6 rounded-xl shadow-lg transition-all flex flex-col h-full ${ - selectedStore === Number(store.storeId) - ? "bg-blue-500 text-white" - : "bg-white text-gray-800 hover:bg-gray-100" - }`} + className={`cursor-pointer p-6 rounded-xl shadow-lg transition-all flex flex-col h-full ${selectedStore === Number(store.storeId) + ? "bg-blue-500 text-white" + : "bg-white text-gray-800 hover:bg-gray-100" + }`} > -
+
{store.storeName}

{store.storeName}

-

- serviceFeeRange: - {store.minFee} ~ {store.maxFee} -

-

{store.description}

+
+ Service Fee Range: + {store.minFee} ~ {store.maxFee} +
+

{store.description}

+
+

Loyality Token Name:

+

{store.loyaltyTokenName}

+

Amount: {store.loyaltyTokenAmount.toString()}

+
@@ -113,4 +151,4 @@ export default function StoreSelector({
); -} +} \ No newline at end of file diff --git a/src/utils/store/management.ts b/src/utils/store/management.ts index 8bdd857..a075a5e 100644 --- a/src/utils/store/management.ts +++ b/src/utils/store/management.ts @@ -38,6 +38,11 @@ export async function listStores() { storeId: store[0], storeName: store[1], storeAdmin: store[2], + loyaltyLogicContractAddress: store[3], + // fill later + loyaltyTokenContractAddress: "", + loyaltyTokenName: "", + loyaltyTokenAmount: BigInt(0), storeImage: "", description: "", maxFee: 0, diff --git a/src/utils/type.ts b/src/utils/type.ts index 6c0ca1c..bda21bd 100644 --- a/src/utils/type.ts +++ b/src/utils/type.ts @@ -8,6 +8,10 @@ export interface Store { storeId: bigint; storeName: string; storeAdmin: string; + loyaltyLogicContractAddress: string; + loyaltyTokenContractAddress: string; + loyaltyTokenName: string; + loyaltyTokenAmount: bigint; storeImage: string; description: string; maxFee: number;