diff --git a/packages/hardhat/contracts/SubscryptoToken.sol b/packages/hardhat/contracts/SubscryptoToken.sol
index 06c62c5..70d7759 100644
--- a/packages/hardhat/contracts/SubscryptoToken.sol
+++ b/packages/hardhat/contracts/SubscryptoToken.sol
@@ -149,6 +149,33 @@ contract SubscryptoToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {
_mint(to, amount);
}
+ function getTiersCount(
+ address merchant
+ ) public view returns(uint) {
+ return tiersOffered[merchant].length;
+ }
+
+ function getTierUnitsPerWeek(
+ address merchant,
+ uint tierIndex
+ ) public view returns(uint) {
+ return tiersOffered[merchant][tierIndex].unitsPerWeek;
+ }
+
+ function getTierPricePerWeek(
+ address merchant,
+ uint tierIndex
+ ) public view returns(uint) {
+ return tiersOffered[merchant][tierIndex].pricePerWeek;
+ }
+
+ function getTierisActivelyOffered(
+ address merchant,
+ uint tierIndex
+ ) public view returns(bool) {
+ return tiersOffered[merchant][tierIndex].isActivelyOffered;
+ }
+
function addTier(
uint unitsPerWeek,
uint pricePerWeek
@@ -165,7 +192,7 @@ contract SubscryptoToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {
uint pricePerWeek,
bool isActivelyOffered
) public {
- if (tiersOffered[msg.sender]).length == 0 {
+ if (tiersOffered[msg.sender].length == 0) {
//Set index 0 to the non-subscription:
tiersOffered[msg.sender].push(Tier({
unitsPerWeek: 0,
@@ -279,7 +306,8 @@ contract SubscryptoToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {
address customer,
uint tierIndex
) private {
- require(tierIndex < tiers[merchant].length, 'No such tier offered by this merchant.');
+ require(tierIndex < tiersOffered[merchant].length, 'No such tier offered by this merchant.');
+ require(tiersOffered[merchant][tierIndex].isActivelyOffered, 'Tier is not actively offered at present.');
accountAtSubscriptionEnd(merchant, customer);
subscriptions[merchant][customer] = Subscription({
tier: tierIndex,
diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx
index 52a7889..24217f1 100644
--- a/packages/nextjs/components/Header.tsx
+++ b/packages/nextjs/components/Header.tsx
@@ -2,7 +2,7 @@ import React, { useCallback, useRef, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
-import { Bars3Icon, BugAntIcon, SparklesIcon } from "@heroicons/react/24/outline";
+import { Bars3Icon, BugAntIcon, BuildingStorefrontIcon, SparklesIcon } from "@heroicons/react/24/outline";
import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { useOutsideClick } from "~~/hooks/scaffold-eth";
@@ -17,6 +17,11 @@ export const menuLinks: HeaderMenuLink[] = [
label: "Home",
href: "/",
},
+ {
+ label: "Merchant: Set up tiers",
+ href: "/merchant",
+ icon: ,
+ },
{
label: "Debug Contracts",
href: "/debug",
diff --git a/packages/nextjs/components/example-ui/TierCard.tsx b/packages/nextjs/components/example-ui/TierCard.tsx
new file mode 100644
index 0000000..f2aec89
--- /dev/null
+++ b/packages/nextjs/components/example-ui/TierCard.tsx
@@ -0,0 +1,39 @@
+import { formatEther } from "viem";
+import { useScaffoldContractRead } from "~~/hooks/scaffold-eth";
+
+export const TierCard = (props: { merchant: string; tierIndex: bigint; showOfferedStatus: boolean }) => {
+ const { data: unitsPerWeek, isLoading: unitsIsLoading } = useScaffoldContractRead({
+ contractName: "SubscryptoToken",
+ functionName: "getTierUnitsPerWeek",
+ args: [props.merchant, props.tierIndex],
+ });
+ const { data: pricePerWeek, isLoading: priceIsLoading } = useScaffoldContractRead({
+ contractName: "SubscryptoToken",
+ functionName: "getTierPricePerWeek",
+ args: [props.merchant, props.tierIndex],
+ });
+ const { data: isActivelyOffered, isLoading: activeStatusIsLoading } = useScaffoldContractRead({
+ contractName: "SubscryptoToken",
+ functionName: "getTierisActivelyOffered",
+ args: [props.merchant, props.tierIndex],
+ });
+
+ let activeOfferText = "Actively offered";
+ if (activeStatusIsLoading) {
+ activeOfferText = "Checking to see if this is actively offered.";
+ } else if (!isActivelyOffered) {
+ activeOfferText = "Not actively offered.";
+ }
+
+ return (
+
+ Calls/activities per week maximum:{" "}
+ {unitsIsLoading ? "Loading..." : typeof unitsPerWeek === "undefined" ? "*" : unitsPerWeek.toString()}
+
+ Price per week in credits (each ≈$1):{" "}
+ {priceIsLoading ? "Loading..." : typeof pricePerWeek === "undefined" ? "*" : formatEther(pricePerWeek)}
+
+ {props.showOfferedStatus ? activeOfferText : null}
+
+ );
+};
diff --git a/packages/nextjs/components/example-ui/TierList.tsx b/packages/nextjs/components/example-ui/TierList.tsx
new file mode 100644
index 0000000..f875f24
--- /dev/null
+++ b/packages/nextjs/components/example-ui/TierList.tsx
@@ -0,0 +1,24 @@
+import { TierCard } from "./TierCard";
+
+export const TiersList = (props: { merchant?: string; tiersLength?: bigint }) => {
+ const tiersLength = props.tiersLength;
+ const merchant = props.merchant;
+ if (typeof tiersLength == "undefined" || typeof merchant == "undefined") {
+ return null;
+ }
+ const indicies = [];
+ for (let i = 1n; i < tiersLength; i++) {
+ indicies.push(i);
+ }
+ return (
+
+
+
+ {indicies.map(index => (
+
+ ))}
+
+
+
+ );
+};
diff --git a/packages/nextjs/components/example-ui/TierListing.tsx b/packages/nextjs/components/example-ui/TierListing.tsx
new file mode 100644
index 0000000..f4d2c83
--- /dev/null
+++ b/packages/nextjs/components/example-ui/TierListing.tsx
@@ -0,0 +1,66 @@
+import { TiersList } from "./TierList";
+import { useAccount } from "wagmi";
+import {
+ useScaffoldContract,
+ useScaffoldContractRead,
+ useScaffoldEventHistory,
+ useScaffoldEventSubscriber,
+} from "~~/hooks/scaffold-eth";
+
+export const TierListing = () => {
+ const { address } = useAccount();
+
+ const { data: tiersLength, isLoading: isTierCountLoading } = useScaffoldContractRead({
+ contractName: "SubscryptoToken",
+ functionName: "getTiersCount",
+ args: [address],
+ });
+
+ useScaffoldEventSubscriber({
+ contractName: "SubscryptoToken",
+ eventName: "TierAdded",
+ listener: logs => {
+ logs.map(log => {
+ const { merchant, tierIndex, unitsPerWeek, pricePerWeek, isActivelyOffered } = log.args;
+ console.log("📡 TierAdded event", merchant, tierIndex, unitsPerWeek, pricePerWeek, isActivelyOffered);
+ });
+ },
+ });
+
+ const {
+ data: myGreetingChangeEvents,
+ isLoading: isLoadingEvents,
+ error: errorReadingEvents,
+ } = useScaffoldEventHistory({
+ contractName: "SubscryptoToken",
+ eventName: "TierAdded",
+ fromBlock: process.env.NEXT_PUBLIC_DEPLOY_BLOCK ? BigInt(process.env.NEXT_PUBLIC_DEPLOY_BLOCK) : 0n,
+ filters: { merchant: address },
+ blockData: true,
+ });
+
+ console.log("Events:", isLoadingEvents, errorReadingEvents, myGreetingChangeEvents);
+
+ const { data: yourContract } = useScaffoldContract({ contractName: "SubscryptoToken" });
+ console.log("subscryptoToken: ", yourContract);
+
+ let tierCountText = "No defined tiers for this merchant account yet! Use the tool to the right to create one.";
+ if (isTierCountLoading) {
+ tierCountText = "Loading...";
+ } else if (typeof tiersLength === "undefined") {
+ tierCountText = "Error loading defined tiers.";
+ } else if (tiersLength === 1n) {
+ tierCountText = (tiersLength - 1n).toString() + " defined tier:";
+ } else if (tiersLength > 1n) {
+ tierCountText = (tiersLength - 1n).toString() + " defined tiers:";
+ }
+
+ return (
+
+
+
{tierCountText}
+
+
+
+ );
+};
diff --git a/packages/nextjs/components/example-ui/TierSetup.tsx b/packages/nextjs/components/example-ui/TierSetup.tsx
new file mode 100644
index 0000000..7bda575
--- /dev/null
+++ b/packages/nextjs/components/example-ui/TierSetup.tsx
@@ -0,0 +1,57 @@
+import { useState } from "react";
+import { parseEther } from "viem";
+import { ArrowSmallRightIcon } from "@heroicons/react/24/outline";
+import { useScaffoldContractWrite } from "~~/hooks/scaffold-eth";
+
+export const TierSetup = () => {
+ const [unitsPerWeek, setUnitsPerWeek] = useState("");
+ const [pricePerWeek, setPricePerWeek] = useState("");
+
+ const { writeAsync, isLoading } = useScaffoldContractWrite({
+ contractName: "SubscryptoToken",
+ functionName: "addTier",
+ //@ts-ignore: not sure why it's not picking up that this function exists on this contract
+ args: [parseInt(unitsPerWeek), parseEther(pricePerWeek)],
+ onBlockConfirmation: txnReceipt => {
+ console.log("📦 Transaction blockHash", txnReceipt.blockHash);
+ },
+ });
+
+ return (
+
+
+
+
Create a new service tier:
+
setUnitsPerWeek(e.target.value)}
+ />
+
+
setPricePerWeek(e.target.value)}
+ />
+
+
+
+
+
+
+ );
+};
diff --git a/packages/nextjs/pages/merchant.tsx b/packages/nextjs/pages/merchant.tsx
new file mode 100644
index 0000000..f8e6d08
--- /dev/null
+++ b/packages/nextjs/pages/merchant.tsx
@@ -0,0 +1,21 @@
+import type { NextPage } from "next";
+import { MetaHeader } from "~~/components/MetaHeader";
+import { TierListing } from "~~/components/example-ui/TierListing";
+import { TierSetup } from "~~/components/example-ui/TierSetup";
+
+const MerchantSetup: NextPage = () => {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
+
+export default MerchantSetup;