diff --git a/packages/nextjs/components/PermitModal/Create/Create.tsx b/packages/nextjs/components/PermitModal/Create/Create.tsx index a587552..9586de2 100644 --- a/packages/nextjs/components/PermitModal/Create/Create.tsx +++ b/packages/nextjs/components/PermitModal/Create/Create.tsx @@ -1,5 +1,5 @@ import { useChain, useAccount } from "@account-kit/react"; -import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; +import { ArrowDownTrayIcon, ArrowUpTrayIcon, PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; import React from "react"; import { useState } from "react"; import { zeroAddress, isAddress, getAddress } from "viem"; @@ -60,6 +60,7 @@ const PermitV2ModalCreateButton: React.FC<{ disabled?: boolean }> = ({ disabled const permit = await PermitV2.createAndSign( { + name: createOptions.name.length > 0 ? createOptions.name : "Unnamed Permit", type: createOptions.type === PermitV2CreateType.Using ? "self" : "sharing", issuer: account.address, recipient: createOptions.recipient.length > 0 ? createOptions.recipient : zeroAddress, @@ -131,19 +132,19 @@ export const PermitV2ModalCreate = () => { <> {/* Type */}
-
Type:
+
Purpose:
/
diff --git a/packages/nextjs/components/PermitModal/Select/Select.tsx b/packages/nextjs/components/PermitModal/Select/Select.tsx index 7f5030d..f24925c 100644 --- a/packages/nextjs/components/PermitModal/Select/Select.tsx +++ b/packages/nextjs/components/PermitModal/Select/Select.tsx @@ -1,4 +1,82 @@ -import { useFhenixAllPermits } from "~~/permits/hooks"; +import { ArrowDownTrayIcon, ArrowUpTrayIcon } from "@heroicons/react/24/outline"; +import React from "react"; +import { useFhenixActivePermitHash, useFhenixAllPermits } from "~~/permits/hooks"; +import { PermitV2 } from "~~/permits/permitV2"; +import { usePermitSatisfiesRequirements } from "~~/services/store/permitV2ModalStore"; + +const timeUntilExpiration = (ts: number): string => { + const now = Math.floor(Date.now() / 1000); + let diff = Math.max(0, ts - now); + + const units = [ + { label: "y", value: 365 * 24 * 60 * 60 }, // Years + { label: "m", value: 30 * 24 * 60 * 60 }, // Months (approx) + { label: "w", value: 7 * 24 * 60 * 60 }, // Weeks + { label: "d", value: 24 * 60 * 60 }, // Days + { label: "h", value: 60 * 60 }, // Hours + { label: "m", value: 60 }, // Minutes + ]; + + if (diff > units[0].value) return ">1y"; + + for (const unit of units) { + const unitCount = Math.floor(diff / unit.value); + if (unitCount > 0) { + return `${unitCount}${unit.label}`; + } + } + + return "0m"; +}; + +const PermitRow: React.FC<{ permit: PermitV2; children?: React.ReactNode; className?: string }> = ({ + permit, + children, + className, +}) => { + const timestamp = Math.floor(Date.now() / 1000); + const satisfies = usePermitSatisfiesRequirements(permit); + + return ( + + {permit.name != null && permit.name.length > 0 ? permit.name : "Unnamed Permit"} + + {permit.type === "self" && } + {permit.type === "sharing" && } + {permit.type === "recipient" && } + + + {permit.expiration > timestamp && timeUntilExpiration(permit.expiration)} + {permit.expiration <= timestamp &&
} + + +
+ + +
{children}
+ + + ); +}; + +const SelectedPermitRow: React.FC<{ permit: PermitV2 }> = ({ permit }) => { + return ( + + + + ); +}; + +const SelectPermitRow: React.FC<{ permit: PermitV2 }> = ({ permit }) => { + return ( + + + + + ); +}; export const PermitV2ModalSelect = () => { // List of permits @@ -7,8 +85,41 @@ export const PermitV2ModalSelect = () => { // Expand opens sub page with action buttons const permits = useFhenixAllPermits(); + const activePermitHash = useFhenixActivePermitHash(); + + return ( + <> + + + + + + + + + + + + {/*
+
Selected:
*/} - console.log({ permits }); +
Selected:
+ {activePermitHash != null && permits[activePermitHash] != null && ( + + )} + {/*
+
+
Available:
*/} - return <>List of permits; +
Available:
+ {Object.entries(permits) + .filter(([hash]) => hash !== activePermitHash) + .map(([hash, permit]) => { + return ; + })} + {/*
*/} + +
NameTypeExp.AccessActions
+ + ); }; diff --git a/packages/nextjs/permits/hooks.ts b/packages/nextjs/permits/hooks.ts index c4684b2..808a137 100644 --- a/packages/nextjs/permits/hooks.ts +++ b/packages/nextjs/permits/hooks.ts @@ -19,6 +19,16 @@ export const useFhenixPermit = () => { }); }; +export const useFhenixActivePermitHash = () => { + const { address } = useAccount({ type: "LightAccount" }); + + return useStore(permitsStore, state => { + if (address == null) return undefined; + + return state.activePermitHash[address]; + }); +}; + export const useFhenixAllPermits = () => { const { address } = useAccount({ type: "LightAccount" }); diff --git a/packages/nextjs/permits/permitV2.ts b/packages/nextjs/permits/permitV2.ts index 5efe48a..6c2520a 100644 --- a/packages/nextjs/permits/permitV2.ts +++ b/packages/nextjs/permits/permitV2.ts @@ -219,6 +219,7 @@ export class PermitV2 implements PermitV2Interface { JSON.stringify({ type: this.type, issuer: this.issuer, + expiration: this.expiration, contracts: this.contracts, projects: this.projects, recipient: this.recipient, diff --git a/packages/nextjs/services/store/permitV2ModalStore.ts b/packages/nextjs/services/store/permitV2ModalStore.ts index ea6a634..5b7bcbd 100644 --- a/packages/nextjs/services/store/permitV2ModalStore.ts +++ b/packages/nextjs/services/store/permitV2ModalStore.ts @@ -1,5 +1,6 @@ import { useCallback, useMemo } from "react"; import { create } from "zustand"; +import { PermitV2 } from "~~/permits/permitV2"; export enum PermitV2Tab { Create = "Create", @@ -199,3 +200,29 @@ export const usePermitCreateOptionsAndActions = () => { reset, }; }; + +export const usePermitSatisfiesRequirements = (permit: PermitV2) => { + const accessRequirements = usePermitModalStore(state => state.accessRequirements); + return useMemo(() => { + // Set to true if requirements includes some contracts + let contractsSatisfied = accessRequirements.contracts.length > 0; + for (const contract of accessRequirements.contracts) { + if (!permit.contracts.includes(contract)) { + contractsSatisfied = false; + } + } + + // Set to true if requirements includes some projects + let projectsSatisfied = accessRequirements.projects.length > 0; + for (const project of accessRequirements.projects) { + if (!permit.projects.includes(project)) { + projectsSatisfied = false; + } + } + + // Only need to satisfy one of the options to satisfy the requirements + if (contractsSatisfied || projectsSatisfied) return true; + + return false; + }, [permit, accessRequirements]); +};