Skip to content

Commit

Permalink
feat(unlock-app): Make renewal details consistent with keychain on da…
Browse files Browse the repository at this point in the history
…shboard (#10572)

* feat(unlock-app): Make renewal details consistent with keychain on dashboard

* refactor

* remove unused variable
  • Loading branch information
searchableguy authored Dec 12, 2022
1 parent 1c04e90 commit c223812
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 67 deletions.
10 changes: 2 additions & 8 deletions unlock-app/src/components/interface/keychain/KeyInfoDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { MAX_UINT, UNLIMITED_RENEWAL_LIMIT } from '~/constants'
import relative from 'dayjs/plugin/relativeTime'
import duration from 'dayjs/plugin/duration'
import custom from 'dayjs/plugin/customParseFormat'
import { durationAsText } from '~/utils/durations'
dayjs.extend(relative)
dayjs.extend(duration)
dayjs.extend(custom)
Expand Down Expand Up @@ -251,14 +252,7 @@ export const KeyInfo = ({
)}
{lock.expirationDuration !== MAX_UINT && (
<KeyItem label="Renewal Duration">
{dayjs
.duration(
ethers.BigNumber.from(lock.expirationDuration)
.div(86400)
.toNumber(),
'day'
)
.humanize()}
{durationAsText(lock.expirationDuration)}
</KeyItem>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface MemberCardProps {
metadata: any
lockAddress: string
network: number
expirationDuration: string
}

interface DetailProps {
Expand All @@ -44,6 +45,7 @@ export const MemberCard = ({
metadata,
lockAddress,
network,
expirationDuration,
}: MemberCardProps) => {
const [isOpen, setIsOpen] = useState(false)
const [expireAndRefundOpen, setExpireAndRefundOpen] = useState(false)
Expand Down Expand Up @@ -169,7 +171,12 @@ export const MemberCard = ({
disabled={!isManager}
content={<MemberInfo />}
>
<MetadataCard metadata={metadata} owner={owner} network={network} />
<MetadataCard
expirationDuration={expirationDuration}
metadata={metadata}
owner={owner}
network={network}
/>
</Collapse>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { useAuth } from '~/contexts/AuthenticationContext'
import { useStorageService } from '~/utils/withStorageService'
import { useWalletService } from '~/utils/withWalletService'
import { ToastHelper } from '~/components/helpers/toast.helper'
import { useWeb3Service } from '~/utils/withWeb3Service'
import { ImageBar } from './ImageBar'
import { MemberCard } from './MemberCard'
import { paginate } from '~/utils/pagination'
import { PaginationBar } from './PaginationBar'
import React from 'react'
import { ExpirationStatus } from './FilterBar'
import Link from 'next/link'
import { subgraph } from '~/config/subgraph'

interface MembersProps {
lockAddress: string
Expand Down Expand Up @@ -59,7 +59,6 @@ export const Members = ({
}: MembersProps) => {
const { account } = useAuth()
const walletService = useWalletService()
const web3Service = useWeb3Service()
const storageService = useStorageService()

const getMembers = async () => {
Expand All @@ -75,14 +74,9 @@ export const Members = ({
})
}

const getLockVersion = async (): Promise<number> => {
if (!network) return 0
return web3Service.publicLockVersion(lockAddress, network)
}

const [
{ isLoading, data: members = [] },
{ isLoading: isLoadingVersion, data: lockVersion = 0 },
{ isLoading: isLockLoading, data: lock },
] = useQueries({
queries: [
{
Expand All @@ -92,17 +86,27 @@ export const Members = ({
ToastHelper.error(`Can't load members, please try again`)
},
},

{
queryFn: getLockVersion,
queryKey: ['getLockVersion', lockAddress, network],
queryFn: () => {
return subgraph.lock(
{
where: {
address: lockAddress,
},
},
{ network }
)
},
queryKey: ['getSubgraphLock', lockAddress, network],
onError: () => {
ToastHelper.error('Cant get lock version, please try again')
ToastHelper.error('Unable to fetch lock from subgraph')
},
},
],
})

const loading = isLoadingVersion || isLoading || loadingFilters
const loading = isLockLoading || isLoading || loadingFilters
const noItems = members?.length === 0 && !loading

const hasActiveFilter =
Expand Down Expand Up @@ -172,10 +176,11 @@ export const Members = ({
token={token}
owner={owner}
expiration={expiration}
version={lockVersion}
version={lock?.version}
metadata={metadata}
lockAddress={lockAddress!}
network={network}
expirationDuration={lock?.expirationDuration}
/>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@ import { useLockManager } from '~/hooks/useLockManager'
import { useStorageService } from '~/utils/withStorageService'
import { useWalletService } from '~/utils/withWalletService'
import { FiExternalLink as ExternalLinkIcon } from 'react-icons/fi'
import dayjs from 'dayjs'
import { LoadingIcon } from '../../../Loading'
import { ethers } from 'ethers'
import { MAX_UINT, UNLIMITED_RENEWAL_LIMIT } from '~/constants'
import { durationAsText } from '~/utils/durations'

interface DetailProps {
title: string
value: React.ReactNode
label: string
children?: React.ReactNode
append?: React.ReactNode
}

interface MetadataCardProps {
metadata: any
owner: string
network: number
expirationDuration?: string
}

const keysToIgnore = [
Expand All @@ -32,14 +36,14 @@ const keysToIgnore = [
'checkedInAt',
]

const MetadataDetail = ({ title, value, append }: DetailProps) => {
const MetadataDetail = ({ label, children, append }: DetailProps) => {
return (
<div className="gap-1 pb-2 border-b border-gray-400 last-of-type:border-none">
<div className="flex items-center gap-2">
<span className="text-base">{title}: </span>
<span className="text-base">{label}: </span>
<div className="flex items-center gap-2">
<span className="block text-base font-bold break-words md:inline-block">
{value}
{children}
</span>
{append && <div>{append}</div>}
</div>
Expand All @@ -48,10 +52,56 @@ const MetadataDetail = ({ title, value, append }: DetailProps) => {
)
}

interface KeyRenewalProps {
possibleRenewals: string
approvedRenewals: string
balance: Record<'amount' | 'symbol', string>
}

const MembershipRenewal = ({
possibleRenewals,
approvedRenewals,
balance,
}: KeyRenewalProps) => {
const possible = ethers.BigNumber.from(possibleRenewals)
const approved = ethers.BigNumber.from(approvedRenewals)

if (possible.lte(0)) {
return (
<MetadataDetail label="Renewals">
User balance of {balance.amount} {balance.symbol} is too low to renew
</MetadataDetail>
)
}

if (approved.lte(0)) {
return (
<MetadataDetail label="Renewals">No renewals approved</MetadataDetail>
)
}

if (approved.gt(0) && approved.lte(UNLIMITED_RENEWAL_LIMIT)) {
return (
<MetadataDetail label="Renewals">
{approved.toString()} times
</MetadataDetail>
)
}

if (approved.gt(UNLIMITED_RENEWAL_LIMIT)) {
return (
<MetadataDetail label="Renewals">Renews unlimited times</MetadataDetail>
)
}

return <MetadataDetail label="Renewals">-</MetadataDetail>
}

export const MetadataCard = ({
metadata,
owner,
network,
expirationDuration,
}: MetadataCardProps) => {
const { account } = useAuth()
const storageService = useStorageService()
Expand Down Expand Up @@ -234,21 +284,20 @@ export const MetadataCard = ({
)}
<div className="flex flex-col gap-4">
{isCheckedIn && (
<MetadataDetail title="Checked-in at" value={getCheckInTime()!} />
<MetadataDetail label="Checked-in at">
{getCheckInTime()}
</MetadataDetail>
)}

{items?.map(([key, value], index) => {
return (
<MetadataDetail
key={`${key}-${index}`}
title={`${key}`}
value={value as any}
/>
<MetadataDetail key={`${key}-${index}`} label={`${key}`}>
{value}
</MetadataDetail>
)
})}
<MetadataDetail
title="Key Holder"
value={owner}
label="Key Holder"
append={
<>
<Button
Expand All @@ -266,36 +315,24 @@ export const MetadataCard = ({
</Button>
</>
}
/>
>
{owner}
</MetadataDetail>
{isSubscriptionLoading && <LoadingIcon />}
{!isSubscriptionLoading && subscription && (
<>
{subscription.balance && (
<MetadataDetail
title="User Balance"
value={`${subscription.balance.amount} ${subscription.balance.symbol}`}
/>
)}

{subscription.approvedTime && (
<MetadataDetail
title="Renew cycle"
value={subscription.approvedTime}
/>
)}
{subscription.next && (
<MetadataDetail
title="Next Renewal"
value={dayjs
.unix(subscription.next)
.format('D MMM YYYY, h:mm A')}
/>
)}
{subscription.type && (
<MetadataDetail
title="Payment type"
value={subscription.type}
/>
<MetadataDetail label="User Balance">
{subscription.balance.amount} {subscription.balance.symbol}
</MetadataDetail>
<MembershipRenewal
possibleRenewals={subscription.possibleRenewals}
approvedRenewals={subscription.approvedRenewals}
balance={subscription.balance}
/>
{expirationDuration && expirationDuration !== MAX_UINT && (
<MetadataDetail label="Renewal duration">
{durationAsText(expirationDuration)}
</MetadataDetail>
)}
</>
)}
Expand Down
4 changes: 4 additions & 0 deletions unlock-app/src/config/subgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { SubgraphService } from '@unlock-protocol/unlock-js'
import { config } from './app'

export const subgraph = new SubgraphService(config.networks)
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { MAX_UINT, MONTH_NAMES, UNLIMITED_KEYS_DURATION } from '../constants'
import dayjs from 'dayjs'
import relative from 'dayjs/plugin/relativeTime'
import duration from 'dayjs/plugin/duration'
import custom from 'dayjs/plugin/customParseFormat'
import { ethers } from 'ethers'
dayjs.extend(relative)
dayjs.extend(duration)
dayjs.extend(custom)

/**
* Function which computes days, hours, minutes and seconds based on seconds
* We limit ourselves to days because months and years can becomes messy (variable duration!)
* @param {*} seconds
* @param {*} intervals
*/
export function durations(seconds, intervals) {
export function durations(
seconds: number,
intervals: Record<string, number>
): Record<string, number> {
if (!seconds) {
return intervals
}
Expand Down Expand Up @@ -41,7 +52,7 @@ export function durations(seconds, intervals) {
* Given a number of seconds, returns the durations as text (10 days, 3 hours and 40 minutes)
* @param {number} seconds
*/
export function durationsAsTextFromSeconds(seconds) {
export function durationsAsTextFromSeconds(seconds: number) {
if (seconds === UNLIMITED_KEYS_DURATION) {
return 'Forever'
}
Expand Down Expand Up @@ -75,7 +86,7 @@ export function durationsAsTextFromSeconds(seconds) {
* @param seconds
* @returns {number}
*/
export function secondsAsDays(seconds) {
export function secondsAsDays(seconds: number) {
return Math.ceil(seconds / 86400).toString()
}

Expand All @@ -84,7 +95,7 @@ export function secondsAsDays(seconds) {
* @param timestamp
* @returns {string}
*/
export function expirationAsDate(timestamp) {
export function expirationAsDate(timestamp: any) {
if (!timestamp || timestamp === MAX_UINT || timestamp === -1) {
return 'Never'
}
Expand All @@ -108,3 +119,9 @@ export function expirationAsDate(timestamp) {

return `${MONTH_NAMES[month]} ${day}, ${year}`
}

export const durationAsText = (duration: string) => {
return dayjs
.duration(ethers.BigNumber.from(duration).div(86400).toNumber(), 'day')
.humanize()
}

0 comments on commit c223812

Please sign in to comment.