Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Early merchant UI #1

Merged
merged 9 commits into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions packages/hardhat/contracts/SubscryptoToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -17,6 +17,11 @@ export const menuLinks: HeaderMenuLink[] = [
label: "Home",
href: "/",
},
{
label: "Merchant: Set up tiers",
href: "/merchant",
icon: <BuildingStorefrontIcon className="h-4 w-4" />,
},
{
label: "Debug Contracts",
href: "/debug",
Expand Down
39 changes: 39 additions & 0 deletions packages/nextjs/components/example-ui/TierCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<li className="py-8 px-5 border border-primary rounded-xl flex m-5">
Calls/activities per week maximum:{" "}
{unitsIsLoading ? "Loading..." : typeof unitsPerWeek === "undefined" ? "*" : unitsPerWeek.toString()}
<br />
Price per week in credits (each ≈$1):{" "}
{priceIsLoading ? "Loading..." : typeof pricePerWeek === "undefined" ? "*" : formatEther(pricePerWeek)}
<br />
{props.showOfferedStatus ? activeOfferText : null}
</li>
);
};
24 changes: 24 additions & 0 deletions packages/nextjs/components/example-ui/TierList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col justify-center items-center py-10 px-5 sm:px-0 lg:py-auto max-w-[100vw] ">
<div className="flex justify-between w-full">
<ol>
{indicies.map(index => (
<TierCard key={index.toString()} merchant={merchant} tierIndex={index} showOfferedStatus={true} />
))}
</ol>
</div>
</div>
);
};
66 changes: 66 additions & 0 deletions packages/nextjs/components/example-ui/TierListing.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="w-full">
<div className="topRow">
<h2 className="text-4xl">{tierCountText}</h2>
</div>
<TiersList merchant={address} tiersLength={tiersLength} />
</div>
);
};
57 changes: 57 additions & 0 deletions packages/nextjs/components/example-ui/TierSetup.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex bg-base-300 relative pb-10">
<div className="flex flex-col w-full mx-5 sm:mx-8 2xl:mx-20">
<div className="flex flex-col mt-6 px-7 py-8 bg-base-200 opacity-80 rounded-2xl shadow-lg border-2 border-primary">
<span className="text-xl my-4">Create a new service tier:</span>
<input
type="number"
placeholder="Calls/activities per week maximum"
className="input w-full px-5 border border-primary text-lg my-4"
onChange={e => setUnitsPerWeek(e.target.value)}
/>
<br />
<input
type="number"
placeholder="Price per week in credits (each ≈$1)"
className="input w-full px-5 border border-primary text-lg my-4"
onChange={e => setPricePerWeek(e.target.value)}
/>
<div className="flex rounded-full p-1 flex-shrink-0 place-content-end">
<button
className="btn btn-primary rounded-full capitalize font-normal font-white flex items-center gap-1 hover:gap-2 transition-all tracking-widest"
onClick={() => writeAsync()}
disabled={isLoading}
>
{isLoading ? (
<span className="loading loading-spinner loading-sm"></span>
) : (
<>
Create tier <ArrowSmallRightIcon className="w-3 h-3 mt-0.5" />
</>
)}
</button>
</div>
</div>
</div>
</div>
);
};
21 changes: 21 additions & 0 deletions packages/nextjs/pages/merchant.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<MetaHeader
title="API Merchant: Set up tiers"
description="Allows the maker of an API to set up offerings to sell access for cryptocurrency payments."
/>
<div className="grid lg:grid-cols-2 flex-grow" data-theme="exampleUi">
<TierListing />
<TierSetup />
</div>
</>
);
};

export default MerchantSetup;
Loading