Skip to content

Commit 85c07d3

Browse files
committed
FRONT-1234: Nicify TG room properties modal
1 parent 881786e commit 85c07d3

File tree

7 files changed

+150
-100
lines changed

7 files changed

+150
-100
lines changed

src/components/TokenLabel.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ComponentPropsWithRef, ElementType } from 'react'
2+
import tw from 'twin.macro'
3+
4+
type OwnProps<T extends ElementType> = {
5+
as?: T
6+
}
7+
8+
type Props<T extends ElementType> = OwnProps<T> & Omit<ComponentPropsWithRef<T>, keyof OwnProps<T>>
9+
10+
export default function TokenLabel<T extends ElementType = 'label'>({ as, ...props }: Props<T>) {
11+
const Component = as || 'label'
12+
13+
return (
14+
<Component
15+
{...props}
16+
css={tw`
17+
bg-[#59799C]
18+
px-1
19+
rounded-sm
20+
select-none
21+
text-[10px]
22+
text-white
23+
appearance-none
24+
tracking-wide
25+
`}
26+
/>
27+
)
28+
}
Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,19 @@
11
import Text from '$/components/Text'
2+
import TokenLabel from '$/components/TokenLabel'
23
import { Flag } from '$/features/flag/types'
34
import { MiscAction } from '$/features/misc'
45
import { TokenStandard } from '$/features/tokenGatedRooms/types'
56
import useTokenStandard from '$/hooks/useTokenStandard'
67
import { OptionalAddress } from '$/types'
78
import i18n from '$/utils/i18n'
9+
import { HTMLAttributes } from 'react'
810
import { useDispatch } from 'react-redux'
9-
import tw from 'twin.macro'
1011

11-
interface Props {
12+
interface Props extends HTMLAttributes<HTMLDivElement> {
1213
tokenAddress: OptionalAddress
1314
}
1415

15-
const commonCss = tw`
16-
bg-[#59799C]
17-
ml-6
18-
px-1
19-
rounded-sm
20-
select-none
21-
text-[10px]
22-
text-white
23-
`
24-
25-
export default function TokenStandardLabel({ tokenAddress }: Props) {
16+
export default function TokenStandardLabel({ tokenAddress, ...props }: Props) {
2617
const dispatch = useDispatch()
2718

2819
const tokenStandard = useTokenStandard(tokenAddress)
@@ -32,10 +23,10 @@ export default function TokenStandardLabel({ tokenAddress }: Props) {
3223
}
3324

3425
return (
35-
<>
26+
<div {...props}>
3627
{tokenStandard === TokenStandard.Unknown ? (
37-
<button
38-
type="button"
28+
<TokenLabel
29+
as="button"
3930
onClick={() => {
4031
dispatch(
4132
MiscAction.setTokenStandard({
@@ -52,15 +43,14 @@ export default function TokenStandardLabel({ tokenAddress }: Props) {
5243
})
5344
)
5445
}}
55-
css={[commonCss, tw`appearance-none`]}
5646
>
5747
<Text>{i18n('common.tokenStandardLabel', TokenStandard.Unknown)}</Text>
58-
</button>
48+
</TokenLabel>
5949
) : (
60-
<div css={commonCss}>
50+
<TokenLabel as="div">
6151
<Text>{i18n('common.tokenStandardLabel', tokenStandard)}</Text>
62-
</div>
52+
</TokenLabel>
6353
)}
64-
</>
54+
</div>
6555
)
6656
}

src/components/modals/AddTokenGatedRoomModal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import PrimaryButton from '$/components/PrimaryButton'
55
import SecondaryButton from '$/components/SecondaryButton'
66
import Spinner from '$/components/Spinner'
77
import Text from '$/components/Text'
8-
import Avatar from '$/components/Avatar'
98
import { Flag } from '$/features/flag/types'
109
import { MiscAction } from '$/features/misc'
1110
import { TokenInfo } from '$/features/misc/types'

src/components/modals/RoomPropertiesModal.tsx

Lines changed: 83 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@ import Toggle from '../Toggle'
1919
import Modal, { Props as ModalProps } from './Modal'
2020
import { useWalletAccount, useWalletClient } from '$/features/wallet/hooks'
2121
import { Flag } from '$/features/flag/types'
22-
import useTokenMetadata from '$/hooks/useTokenMetadata'
2322
import TextField from '$/components/TextField'
2423
import i18n from '$/utils/i18n'
24+
import useRoomEntryRequirements from '$/hooks/useRoomEntryRequirements'
25+
import TokenLogo from '$/components/TokenLogo'
26+
import TokenStandardLabel from '$/components/TokenStandardLabel'
27+
import { RoomId } from '$/features/room/types'
28+
import useCachedTokenGate from '$/hooks/useCachedTokenGate'
29+
import trunc from '$/utils/trunc'
30+
import TokenLabel from '$/components/TokenLabel'
31+
import useFetchingTokenMetadataForAnyTokenId from '$/hooks/useFetchingTokenMetadataForAnyTokenId'
2532

2633
export default function RoomPropertiesModal({
2734
title = i18n('roomPropertiesModal.title'),
@@ -31,14 +38,7 @@ export default function RoomPropertiesModal({
3138
}: ModalProps) {
3239
const selectedRoomId = useSelectedRoomId()
3340

34-
const {
35-
name: roomName = '',
36-
tokenAddress,
37-
minRequiredBalance,
38-
tokenType,
39-
tokenIds = [],
40-
stakingEnabled = false,
41-
} = useSelectedRoom() || {}
41+
const { name: roomName = '' } = useSelectedRoom() || {}
4242

4343
const isStorageEnabled = useStorageNodeState(selectedRoomId, STREAMR_STORAGE_NODE_GERMANY)
4444

@@ -91,84 +91,16 @@ export default function RoomPropertiesModal({
9191
)
9292
}, [open, selectedRoomId])
9393

94-
const tokenMetadata = useTokenMetadata(tokenAddress, tokenIds)
95-
9694
return (
9795
<Modal {...props} onAbort={onAbort} title={title} subtitle={roomName || subtitle}>
98-
{tokenMetadata ? (
99-
<>
100-
{tokenType && (
101-
<Label>
102-
<b>Token Standard:</b>
103-
{tokenType.standard}
104-
</Label>
105-
)}
106-
{tokenAddress && (
107-
<Label>
108-
<b>Address:</b>
109-
{tokenAddress}
110-
</Label>
111-
)}
112-
{'name' in tokenMetadata && tokenMetadata.name && (
113-
<Label>
114-
<b>Token Name:</b>
115-
{tokenMetadata.name}
116-
</Label>
117-
)}
118-
{'symbol' in tokenMetadata && tokenMetadata.symbol && (
119-
<Label>
120-
<b>Symbol:</b>
121-
{tokenMetadata.symbol}
122-
</Label>
123-
)}
124-
{'decimals' in tokenMetadata && tokenMetadata.decimals && (
125-
<Label>
126-
<b>Decimals:</b>
127-
{tokenMetadata.decimals}
128-
</Label>
129-
)}
130-
{'granularity' in tokenMetadata && tokenMetadata.granularity && (
131-
<Label>
132-
<b>Granularity:</b>
133-
{tokenMetadata.granularity}
134-
</Label>
135-
)}
136-
{'uris' in tokenMetadata && tokenMetadata.uris && (
137-
<>
138-
{Object.entries(tokenMetadata.uris).map(([tokenId, uri]) => (
139-
<Label key={tokenId}>
140-
<b>URI:</b>
141-
{uri}
142-
</Label>
143-
))}
144-
</>
145-
)}
146-
{minRequiredBalance !== undefined && (
147-
<Label>
148-
<b>Min Token Amount:</b>
149-
{minRequiredBalance.toString()}
150-
</Label>
151-
)}
152-
<Label>{i18n('roomPropertiesModal.stakingLabel')}</Label>
153-
<div css={tw`flex`}>
154-
<div css={tw`grow`}>
155-
<Hint css={tw`pr-16`}>
156-
<Text>{i18n('addTokenGatedRoomModal.stakingDesc')}</Text>
157-
</Hint>
158-
</div>
159-
<div css={tw`mt-2`}>
160-
<Toggle value={stakingEnabled} />
161-
</div>
162-
</div>
163-
</>
164-
) : null}
16596
<Form onSubmit={() => void onAbort?.()}>
16697
{!!selectedRoomId && (
16798
<>
16899
<Label>{i18n('roomPropertiesModal.roomIdLabel')}</Label>
169100
<TextField defaultValue={selectedRoomId} readOnly />
170101
</>
171102
)}
103+
<TokenGateSummary roomId={selectedRoomId} />
172104
<>
173105
<Label>{i18n('addRoomModal.storageFieldLabel')}</Label>
174106
<div css={tw`flex`}>
@@ -195,3 +127,76 @@ export default function RoomPropertiesModal({
195127
}
196128

197129
RoomPropertiesModal.displayName = 'RoomPropertiesModal'
130+
131+
function TokenGateSummary({ roomId }: { roomId: RoomId | undefined }) {
132+
const tokenGate = useCachedTokenGate(roomId)
133+
134+
const entryReq = useRoomEntryRequirements(roomId)
135+
136+
const isFetching = useFetchingTokenMetadataForAnyTokenId(tokenGate?.tokenAddress)
137+
138+
if (!tokenGate) {
139+
return null
140+
}
141+
142+
const { tokenAddress, stakingEnabled } = tokenGate
143+
144+
return (
145+
<>
146+
<Label>{i18n('roomPropertiesModal.tokenGateLabel')}</Label>
147+
<div
148+
css={tw`
149+
[img]:mr-3
150+
border
151+
border-[#F1F4F7]
152+
flex
153+
h-[64px]
154+
items-center
155+
px-4
156+
rounded-lg
157+
text-[#36404E]
158+
text-[14px]
159+
`}
160+
>
161+
<TokenLogo tokenAddress={tokenAddress} />
162+
<div css={tw`mr-6 grow`}>
163+
<div css={tw`font-semibold`}>
164+
<Text
165+
truncate
166+
css={[
167+
tw`leading-normal`,
168+
isFetching &&
169+
tw`
170+
animate-pulse
171+
[animation-duration: 0.5s]
172+
`,
173+
]}
174+
>
175+
{isFetching ? (
176+
i18n('common.load', true)
177+
) : !entryReq ? (
178+
<>Failed to load</>
179+
) : (
180+
<>
181+
{typeof entryReq.quantity === 'string' && (
182+
<>{entryReq.quantity} </>
183+
)}
184+
{entryReq.unit}
185+
</>
186+
)}
187+
</Text>
188+
</div>
189+
<div css={tw`text-[#59799C]`}>
190+
<Text>{trunc(tokenAddress)}</Text>
191+
</div>
192+
</div>
193+
<TokenStandardLabel tokenAddress={tokenAddress} css={tw`mr-1.5`} />
194+
{stakingEnabled && (
195+
<TokenLabel as="div" css={tw`uppercase`}>
196+
<Text>{i18n('roomPropertiesModal.stakingLabel')}</Text>
197+
</TokenLabel>
198+
)}
199+
</div>
200+
</>
201+
)
202+
}

src/features/room/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ export interface RoomState {
1010
privacy?: PrivacySetting
1111
storageNodes: Record<Address, boolean>
1212
temporaryName?: string
13+
/**
14+
* `tokenGate` is:
15+
* - null if the room isn't token gated,
16+
* - an object if it is, and
17+
* - undefined if we don't know if it is.
18+
*/
1319
tokenGate?: CachedTokenGate | null
1420
}
1521
>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { OptionalAddress, State } from '$/types'
2+
import { useMemo } from 'react'
3+
import { useSelector } from 'react-redux'
4+
5+
function selectFlags({ flag }: State) {
6+
return flag
7+
}
8+
9+
export default function useFetchingTokenMetadataForAnyTokenId(tokenAddress: OptionalAddress) {
10+
const flags = useSelector(selectFlags)
11+
12+
return useMemo(() => {
13+
if (!tokenAddress) {
14+
return false
15+
}
16+
17+
return Object.keys(flags).some((flag) =>
18+
flag.startsWith(`["isFetchingTokenMetadata","${tokenAddress.toLowerCase()}"`)
19+
)
20+
}, [flags, tokenAddress])
21+
}

src/utils/i18n.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ const I18n = {
343343
'roomPropertiesModal.stakingLabel': 'Staking',
344344
'roomPropertiesModal.roomIdLabel': 'Room id',
345345
'roomPropertiesModal.dismissButtonLabel': 'Close',
346+
'roomPropertiesModal.tokenGateLabel': 'Entry requirement',
346347
'walletModal.title': 'Select a wallet',
347348
'conversationHeader.roomNamePlaceholder': 'e.g. random-giggly-bear',
348349
'conversationHeader.renamingInProgress'(from: string, to: string) {

0 commit comments

Comments
 (0)