connect UI to protocol

This commit is contained in:
Pavel Prichodko 2022-06-10 16:11:32 +02:00 committed by Felicio Mununga
parent 65d9c33bb8
commit d0f4104b53
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
28 changed files with 319 additions and 306 deletions

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React, { useMemo, useRef, useState } from 'react' import React, { useMemo, useRef, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React, { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React, { useEffect, useMemo, useRef, useState } from 'react' import React, { useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'

View File

@ -2,42 +2,42 @@ import React from 'react'
import { BellIcon } from '~/src/icons/bell-icon' import { BellIcon } from '~/src/icons/bell-icon'
import { ContextMenu, DropdownMenu } from '~/src/system' import { ContextMenu, DropdownMenu } from '~/src/system'
import { useAlertDialog } from '~/src/system/dialog/alert-dialog' // import { useAlertDialog } from '~/src/system/dialog/alert-dialog'
import { useDialog } from '~/src/system/dialog/dialog' // import { useDialog } from '~/src/system/dialog/dialog'
import { UserProfileDialog } from '../user-profile-dialog' // import { UserProfileDialog } from '../user-profile-dialog'
import { EditGroupChatDialog } from './edit-group-chat-dialog' // import { EditGroupChatDialog } from './edit-group-chat-dialog'
interface Props { interface Props {
type: 'dropdown' | 'context' type: 'dropdown' | 'context'
chatType: 'channel' | 'chat' | 'group-chat' // chatType: 'channel' | 'chat' | 'group-chat'
} }
export const ChatMenu = (props: Props) => { export const ChatMenu = (props: Props) => {
const { type, chatType } = props const { type } = props
const Menu = type === 'dropdown' ? DropdownMenu : ContextMenu const Menu = type === 'dropdown' ? DropdownMenu : ContextMenu
const userProfileDialog = useDialog(UserProfileDialog) // const userProfileDialog = useDialog(UserProfileDialog)
const editGroupChatDialog = useDialog(EditGroupChatDialog) // const editGroupChatDialog = useDialog(EditGroupChatDialog)
const deleteChatDialog = useAlertDialog({ // const deleteChatDialog = useAlertDialog({
title: 'Delete Chat', // title: 'Delete Chat',
description: 'Are you sure you want to delete this chat?', // description: 'Are you sure you want to delete this chat?',
actionLabel: 'Delete', // actionLabel: 'Delete',
actionVariant: 'danger', // actionVariant: 'danger',
cancelLabel: 'Keep', // cancelLabel: 'Keep',
}) // })
const leaveGroupDialog = useAlertDialog({ // const leaveGroupDialog = useAlertDialog({
title: 'Leave Group', // title: 'Leave Group',
description: 'Are you sure you want to leave this group chat?', // description: 'Are you sure you want to leave this group chat?',
actionLabel: 'Leave', // actionLabel: 'Leave',
actionVariant: 'danger', // actionVariant: 'danger',
cancelLabel: 'Stay', // cancelLabel: 'Stay',
}) // })
const commonMenuItems = ( return (
<> <Menu>
<Menu.TriggerItem label="Mute Chat" icon={<BellIcon />}> <Menu.TriggerItem label="Mute Chat" icon={<BellIcon />}>
<Menu.Item>For 15 min</Menu.Item> <Menu.Item>For 15 min</Menu.Item>
<Menu.Item>For 1 hour</Menu.Item> <Menu.Item>For 1 hour</Menu.Item>
@ -52,55 +52,55 @@ export const ChatMenu = (props: Props) => {
<Menu.Item>Last 3 days</Menu.Item> <Menu.Item>Last 3 days</Menu.Item>
<Menu.Item>Last 7 days</Menu.Item> <Menu.Item>Last 7 days</Menu.Item>
</Menu.TriggerItem> </Menu.TriggerItem>
</>
)
if (chatType === 'channel') {
return <Menu>{commonMenuItems}</Menu>
}
if (chatType === 'group-chat') {
return (
<Menu>
<Menu.Item icon={<BellIcon />}>Add / remove from group</Menu.Item>
<Menu.Item
icon={<BellIcon />}
onSelect={() => editGroupChatDialog.open({})}
>
Edit name and image
</Menu.Item>
<Menu.Separator />
{commonMenuItems}
<Menu.Separator />
<Menu.Item
icon={<BellIcon />}
danger
onSelect={() => leaveGroupDialog.open()}
>
Leave Chat
</Menu.Item>
</Menu> </Menu>
) )
}
return ( // if (chatType === 'channel') {
<Menu> // return <Menu>{commonMenuItems}</Menu>
<Menu.Item // }
icon={<BellIcon />}
onSelect={() => userProfileDialog.open({ name: 'Satoshi' })} // if (chatType === 'group-chat') {
> // return (
View Profile // <Menu>
</Menu.Item> // <Menu.Item icon={<BellIcon />}>Add / remove from group</Menu.Item>
<Menu.Separator /> // <Menu.Item
{commonMenuItems} // icon={<BellIcon />}
<Menu.Separator /> // onSelect={() => editGroupChatDialog.open({})}
<Menu.Item // >
icon={<BellIcon />} // Edit name and image
danger // </Menu.Item>
onSelect={() => deleteChatDialog.open()} // <Menu.Separator />
> // {commonMenuItems}
Delete Chat // <Menu.Separator />
</Menu.Item> // <Menu.Item
</Menu> // icon={<BellIcon />}
) // danger
// onSelect={() => leaveGroupDialog.open()}
// >
// Leave Chat
// </Menu.Item>
// </Menu>
// )
// }
// return (
// <Menu>
// <Menu.Item
// icon={<BellIcon />}
// onSelect={() => userProfileDialog.open({ name: 'Satoshi' })}
// >
// View Profile
// </Menu.Item>
// <Menu.Separator />
// {commonMenuItems}
// <Menu.Separator />
// <Menu.Item
// icon={<BellIcon />}
// danger
// onSelect={() => deleteChatDialog.open()}
// >
// Delete Chat
// </Menu.Item>
// </Menu>
// )
} }

View File

@ -17,7 +17,7 @@ export const ChannelItem = (props: Props) => {
return ( return (
<ContextMenuTrigger> <ContextMenuTrigger>
<SidebarItem {...sidebarItemProps}>#{children}</SidebarItem> <SidebarItem {...sidebarItemProps}>#{children}</SidebarItem>
<ChatMenu type="context" chatType="channel" /> <ChatMenu type="context" />
</ContextMenuTrigger> </ContextMenuTrigger>
) )
} }

View File

@ -1,43 +1,28 @@
import React from 'react' import React from 'react'
import { Box } from '~/src/system'
import { useChats } from '~/src/protocol' import { useChats } from '~/src/protocol'
import { Box } from '~/src/system'
import { ChannelGroup } from './channel-group' // import { ChannelGroup } from './channel-group'
import { ChannelItem } from './channel-item' import { ChannelItem } from './channel-item'
export const Channels = () => { export const Channels = () => {
const chats = useChats() const chats = useChats()
return ( return (
<Box css={{padding:'8px 0'}}> <Box css={{ padding: '24px 0', overflow: 'auto' }}>
{chats.map((chat) => ( {chats.map(chat => (
<ChannelItem <ChannelItem
key={chat.id} key={chat.id}
to={`/${chat.id}`} to={`/${chat.id}`}
unread={false} unread={false}
muted={false} muted={false}
name={chat.identity?.displayName}
color={chat.identity?.color}
> >
{chat.identity!.displayName} {chat.identity!.displayName}
</ChannelItem> </ChannelItem>
))} ))}
{/* {Object.entries(community.chats).map(([group, channels]) => (
<ChannelGroup key={group} name={group}>
{channels.map(channel => (
<ChannelItem
key={group + channel}
to={`/${channel}`}
unread={channel === 'general'}
muted={channel === 'random'}
>
{channel}
</ChannelItem>
))}
</ChannelGroup>
))} */}
</Box> </Box>
) )
} }

View File

@ -1,12 +1,11 @@
import React from 'react' import React from 'react'
import { useCommunity } from '~/src/protocol' import { useProtocol } from '~/src/protocol'
import { Button, CopyInput, Dialog, Flex, Grid, Text } from '~/src/system' import { Button, CopyInput, Dialog, Flex, Grid, Text } from '~/src/system'
export const CommunityDialog = () => { export const CommunityDialog = () => {
const { identity, publicKey='0xTODO' } = useCommunity() const { community } = useProtocol()
const { displayName, description} = identity const { displayName, description } = community.identity!
return ( return (
<Dialog title={displayName}> <Dialog title={displayName}>
@ -16,7 +15,7 @@ export const CommunityDialog = () => {
<Dialog.Separator /> <Dialog.Separator />
<Dialog.Body> <Dialog.Body>
<Grid gap={3}> <Grid gap={3}>
<CopyInput label="Community Public Key" value={publicKey} /> <CopyInput label="Community Public Key" value="0xTODO" />
<Text size="13" color="gray"> <Text size="13" color="gray">
To access this community, paste community public key in Status To access this community, paste community public key in Status
desktop or mobile app. desktop or mobile app.
@ -54,7 +53,7 @@ export const CommunityDialog = () => {
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
<Button href="https://status.im/get">Download Status for Mac</Button> <Button href="https://status.im/get">Download Status</Button>
</Flex> </Flex>
</Dialog.Body> </Dialog.Body>
</Dialog> </Dialog>

View File

@ -1,22 +1,23 @@
import React from 'react' import React from 'react'
import { useCommunity, useMembers } from '~/src/protocol' import { useMembers, useProtocol } from '~/src/protocol'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { Avatar, DialogTrigger, Text } from '~/src/system' import { Avatar, DialogTrigger, Text } from '~/src/system'
import { CommunityDialog } from './community-dialog' import { CommunityDialog } from './community-dialog'
export const CommunityInfo = () => { export const CommunityInfo = () => {
const community = useCommunity() const { community } = useProtocol()
const members = useMembers() const members = useMembers()
console.log("file: index.tsx > line 11 > CommunityInfo > community", community)
const { displayName, color } = community.identity!
return ( return (
<DialogTrigger> <DialogTrigger>
<Button> <Button>
<Avatar size={36} /> <Avatar size={36} name={displayName} color={color} />
<div> <div>
<Text>{community.identity?.displayName}</Text> <Text>{displayName}</Text>
<Text color="gray" size={12}> <Text color="gray" size={12}>
{members.length} members {members.length} members
</Text> </Text>

View File

@ -19,10 +19,10 @@ export const GetStarted = () => {
// TODO: Add skip logic // TODO: Add skip logic
} }
const [account, { createAccount }] = useAccount() const { account, createAccount } = useAccount()
return ( return (
<Flex direction="column" align="center" gap={5}> <Flex direction="column" align="center" gap={5} css={{ padding: '30px 0' }}>
<svg <svg
width={65} width={65}
height={64} height={64}

View File

@ -10,22 +10,22 @@ interface Props {
export const ThrowawayProfileFoundDialog = (props: Props) => { export const ThrowawayProfileFoundDialog = (props: Props) => {
const { onSkip } = props const { onSkip } = props
const [account] = useAccount() const { account } = useAccount()
const handleLoadThrowawayProfile = () => { const handleLoadThrowawayProfile = () => {
// TODO: load throwaway profile // TODO: load throwaway profile
} }
if (!account) { if (!account) {
return null return null
} }
return ( return (
<Dialog title="Throwaway Profile Found"> <Dialog title="Throwaway Profile Found">
<Dialog.Body gap="5"> <Dialog.Body gap="5">
<Flex direction="column" align="center" gap="2"> <Flex direction="column" align="center" gap="2">
<Avatar size={64} src={account.imageUrl} /> <Avatar size={64} />
<Heading weight="600">{account.name}</Heading> <Heading weight="600">{account.username}</Heading>
<Text color="gray"> <Text color="gray">
Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377 Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
</Text> </Text>

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import { ChatMenu } from '~/src/components/chat-menu' import { ChatMenu } from '~/src/components/chat-menu'
import { useChannel } from '~/src/protocol'
import { ContextMenuTrigger } from '~/src/system' import { ContextMenuTrigger } from '~/src/system'
import { SidebarItem } from '../sidebar-item' import { SidebarItem } from '../sidebar-item'
@ -15,12 +14,10 @@ interface Props extends SidebarItemProps {
export const ChatItem = (props: Props) => { export const ChatItem = (props: Props) => {
const { children, ...sidebarItemProps } = props const { children, ...sidebarItemProps } = props
const chat = useChannel(children)
return ( return (
<ContextMenuTrigger> <ContextMenuTrigger>
<SidebarItem {...sidebarItemProps}>{children}</SidebarItem> <SidebarItem {...sidebarItemProps}>{children}</SidebarItem>
<ChatMenu type="context" chatType={chat.type} /> <ChatMenu type="context" />
</ContextMenuTrigger> </ContextMenuTrigger>
) )
} }

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import React from 'react' import React from 'react'
import { EditIcon } from '~/src/icons/edit-icon' import { EditIcon } from '~/src/icons/edit-icon'

View File

@ -12,10 +12,12 @@ interface Props {
muted: boolean muted: boolean
unread: boolean unread: boolean
children: React.ReactNode children: React.ReactNode
name?: string
color?: string
} }
const SidebarItem = (props: Props, ref: Ref<HTMLAnchorElement>) => { const SidebarItem = (props: Props, ref: Ref<HTMLAnchorElement>) => {
const { muted, unread, children, ...buttonProps } = props const { muted, unread, children, name, color, ...buttonProps } = props
return ( return (
<Link <Link
@ -23,7 +25,7 @@ const SidebarItem = (props: Props, ref: Ref<HTMLAnchorElement>) => {
state={muted ? 'muted' : unread ? 'unread' : undefined} state={muted ? 'muted' : unread ? 'unread' : undefined}
{...buttonProps} {...buttonProps}
> >
<Avatar size={24} /> <Avatar size={24} name={name} color={color} />
{children} {children}
</Link> </Link>
) )

View File

@ -1,34 +1,32 @@
import React from 'react' import React from 'react'
import { useAppState } from '~/src/contexts/app-context' import { useAppState } from '~/src/contexts/app-context'
import { useAccount } from '~/src/protocol'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { Separator } from '~/src/system' import { Separator } from '~/src/system'
import { Channels } from './components/channels' import { Channels } from './components/channels'
import { CommunityInfo } from './components/community-info' import { CommunityInfo } from './components/community-info'
import { GetStarted } from './components/get-started' import { GetStarted } from './components/get-started'
import { useAccount } from '~/src/protocol'
// import { Messages } from './components/messages' // import { Messages } from './components/messages'
export const MainSidebar = () => { export const MainSidebar = () => {
const { options } = useAppState() const { options } = useAppState()
const { account } = useAccount()
if (options.enableSidebar === false) { if (options.enableSidebar === false) {
return null return null
} }
const [account] = useAccount()
return ( return (
<Wrapper> <Wrapper>
<CommunityInfo /> <CommunityInfo />
<Channels /> <Channels />
{/* <Separator css={{ margin: '16px 0' }} /> {/* <Separator css={{ margin: '16px 0' }} />
<Messages /> */} <Messages /> */}
{ !account && ( {!account && (
<> <>
<Separator css={{ margin: '16px 0' }} /> <Separator />
<GetStarted /> <GetStarted />
</> </>
)} )}

View File

@ -3,25 +3,32 @@ import React from 'react'
import { useAccount } from '~/src/protocol' import { useAccount } from '~/src/protocol'
import { Avatar, Dialog, EmojiHash, Flex, Heading, Text } from '~/src/system' import { Avatar, Dialog, EmojiHash, Flex, Heading, Text } from '~/src/system'
export const DisconnectDialog = () => { import type { Account } from '~/src/protocol'
const [account] = useAccount()
interface Props {
account: Account
}
export const DisconnectDialog = (props: Props) => {
const { deleteAccount } = useAccount()
const { account } = props
return ( return (
<Dialog title="Disconnect"> <Dialog title="Disconnect">
<Dialog.Body gap="5"> <Dialog.Body gap="5">
<Text>Do you want to disconnect your profile from this browser?</Text> <Text>Do you want to disconnect your profile from this browser?</Text>
<Flex direction="column" align="center" gap="2"> <Flex direction="column" align="center" gap="2">
<Avatar size={64} src={account.imageUrl} /> <Avatar size={64} />
<Heading weight="600">{account.name}</Heading> <Heading weight="600">{account.username}</Heading>
<Text color="gray"> <Text color="gray">Chatkey: {account.chatKey}</Text>
Chatkey: {account.chatKey}
</Text>
<EmojiHash /> <EmojiHash />
</Flex> </Flex>
</Dialog.Body> </Dialog.Body>
<Dialog.Actions> <Dialog.Actions>
<Dialog.Cancel>Stay Connected</Dialog.Cancel> <Dialog.Cancel>Stay Connected</Dialog.Cancel>
<Dialog.Action variant="danger">Disconnect</Dialog.Action> <Dialog.Action variant="danger" onClick={deleteAccount}>
Disconnect
</Dialog.Action>
</Dialog.Actions> </Dialog.Actions>
</Dialog> </Dialog>
) )

View File

@ -1,15 +1,16 @@
import React from 'react' import React from 'react'
import { useAccount, useMembers } from '~/src/protocol'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { Grid, Heading } from '~/src/system' import { Grid, Heading } from '~/src/system'
import { MemberGroup } from './member-group' import { MemberGroup } from './member-group'
import { MemberItem } from './member-item' import { MemberItem } from './member-item'
import { UserItem } from './user-item' import { UserItem } from './user-item'
import { useMembers } from '~/src/protocol'
export function MemberSidebar() { export function MemberSidebar() {
const members = useMembers() const members = useMembers()
const { account } = useAccount()
return ( return (
<Wrapper> <Wrapper>
@ -17,9 +18,11 @@ export function MemberSidebar() {
Members Members
</Heading> </Heading>
<Grid gap="2"> <Grid gap="2">
{account && (
<MemberGroup label="You"> <MemberGroup label="You">
<UserItem /> <UserItem account={account} />
</MemberGroup> </MemberGroup>
)}
<MemberGroup label="Online"> <MemberGroup label="Online">
{members.map(member => ( {members.map(member => (
<MemberItem <MemberItem

View File

@ -17,10 +17,7 @@ export const MemberItem = (props: Props) => {
return ( return (
<Flex gap="2" align="center" css={{ height: 56 }}> <Flex gap="2" align="center" css={{ height: 56 }}>
<Avatar <Avatar size={32} indicator={indicator} />
size={32}
indicator={indicator}
/>
<div> <div>
<Flex align="center" gap={1}> <Flex align="center" gap={1}>
<Text size="15" color="accent" truncate> <Text size="15" color="accent" truncate>

View File

@ -1,27 +1,27 @@
import React from 'react' import React from 'react'
import { useAccount } from '~/src/protocol'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { Avatar, DialogTrigger, EthAddress, Flex, Text } from '~/src/system' import { Avatar, DialogTrigger, EthAddress, Flex, Text } from '~/src/system'
import { DisconnectDialog } from './disconnect-dialog' import { DisconnectDialog } from './disconnect-dialog'
export const UserItem = () => { import type { Account } from '~/src/protocol'
const [account] = useAccount()
console.log("file: user-item.tsx > line 11 > UserItem > account", account)
if (!account) { interface Props {
return null account: Account
} }
export const UserItem = (props: Props) => {
const { account } = props
return ( return (
<Flex align="center" justify="between"> <Flex align="center" justify="between">
<Flex gap="2" align="center" css={{ height: 56 }}> <Flex gap="2" align="center" css={{ height: 56 }}>
<Avatar size={32} src={account.imageUrl} /> <Avatar size={32} />
<div> <div>
<Flex align="center" gap={1}> <Flex align="center" gap={1}>
<Text size="15" color="accent"> <Text size="15" color="accent">
{account.name} {account.username}
</Text> </Text>
</Flex> </Flex>
<EthAddress size={10} color="gray"> <EthAddress size={10} color="gray">
@ -30,7 +30,6 @@ export const UserItem = () => {
</div> </div>
</Flex> </Flex>
<DialogTrigger> <DialogTrigger>
<DisconnectButton> <DisconnectButton>
<svg <svg
@ -50,10 +49,7 @@ export const UserItem = () => {
/> />
</svg> </svg>
</DisconnectButton> </DisconnectButton>
{account && ( <DisconnectDialog account={account} />
<DisconnectDialog />
)}
</DialogTrigger> </DialogTrigger>
</Flex> </Flex>
) )

View File

@ -14,27 +14,27 @@ interface Props {
} }
export const emojis: Record<Reaction, { url: string; symbol: string }> = { export const emojis: Record<Reaction, { url: string; symbol: string }> = {
heart: { LOVE: {
symbol: '❤️', symbol: '❤️',
url: 'https://twemoji.maxcdn.com/v/latest/svg/2764.svg', url: 'https://twemoji.maxcdn.com/v/latest/svg/2764.svg',
}, },
'thumbs-up': { THUMBS_UP: {
symbol: '👍️', symbol: '👍️',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f44d.svg', url: 'https://twemoji.maxcdn.com/v/latest/svg/1f44d.svg',
}, },
'thumbs-down': { THUMBS_DOWN: {
symbol: '👎️', symbol: '👎️',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f44e.svg', url: 'https://twemoji.maxcdn.com/v/latest/svg/1f44e.svg',
}, },
smile: { LAUGH: {
symbol: '😆', symbol: '😆',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f606.svg', url: 'https://twemoji.maxcdn.com/v/latest/svg/1f606.svg',
}, },
sad: { SAD: {
symbol: '😭', symbol: '😭',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f62d.svg', url: 'https://twemoji.maxcdn.com/v/latest/svg/1f62d.svg',
}, },
angry: { ANGRY: {
symbol: '😡', symbol: '😡',
url: 'https://twemoji.maxcdn.com/v/latest/svg/1f621.svg', url: 'https://twemoji.maxcdn.com/v/latest/svg/1f621.svg',
}, },

View File

@ -15,7 +15,7 @@ export const UserProfileDialog = (props: Props) => {
<Dialog.Body align="center"> <Dialog.Body align="center">
<Avatar size="80" /> <Avatar size="80" />
<Heading size="22">{name}</Heading> <Heading size="22">{name}</Heading>
<Text>Chatkey:0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377</Text> <Text>Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377</Text>
<EmojiHash /> <EmojiHash />
</Dialog.Body> </Dialog.Body>
<Dialog.Actions> <Dialog.Actions>

View File

@ -1,35 +1,25 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { useCommunity } from '~/src/protocol/use-community' import { useProtocol } from '~/src/protocol'
import { Avatar, Checkbox, Dialog, Flex, Text } from '~/src/system' import { Avatar, Checkbox, Dialog, Flex, Text } from '~/src/system'
export const WelcomeDialog = () => { export const WelcomeDialog = () => {
const { name, imageUrl, requestNeeded } = useCommunity() const { community } = useProtocol()
const { identity } = community
const [agreed, setAgreed] = useState(false) const [agreed, setAgreed] = useState(false)
return ( return (
<Dialog title={`Welcome to ${name}`} size={640}> <Dialog title={`Welcome to ${identity?.displayName}`} size={640}>
<Dialog.Body gap="4"> <Dialog.Body gap="4">
<Flex justify="center"> <Flex justify="center">
<Avatar size="64" src={imageUrl} /> <Avatar
size="64"
src={identity?.displayName}
color={identity?.color}
/>
</Flex> </Flex>
<Text> <Text>{identity?.description}</Text>
CryptoKitties sed ut perspiciatis unde omnis iste natus error sit
voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque
ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo.
<br />
<br />
Ut enim ad minim veniam Excepteur sint occaecat cupidatat non proident
Duis aute irure Dolore eu fugiat nulla pariatur 🚗 consectetur
adipiscing elit.
<br />
<br />
Nemo enim 😋 ipsam voluptatem quia voluptas sit aspernatur aut odit
aut fugit, sed quia consequuntur magni dolores eos qui ratione
voluptatem sequi nesciunt.
</Text>
<Flex> <Flex>
<Checkbox checked={agreed} onChange={setAgreed}> <Checkbox checked={agreed} onChange={setAgreed}>
I agree with the above I agree with the above
@ -38,7 +28,8 @@ export const WelcomeDialog = () => {
</Dialog.Body> </Dialog.Body>
<Dialog.Actions> <Dialog.Actions>
<Dialog.Action disabled={agreed === false}> <Dialog.Action disabled={agreed === false}>
{requestNeeded ? 'Request to Join' : `Join ${name}`} Request to Join
{/* {requestNeeded ? 'Request to Join' : `Join ${name}`} */}
</Dialog.Action> </Dialog.Action>
</Dialog.Actions> </Dialog.Actions>
</Dialog> </Dialog>

View File

@ -1,27 +1,26 @@
import React from 'react' import React from 'react'
import { useMatch } from 'react-router-dom' // import { PinIcon } from '~/src/icons/pin-icon'
import { Avatar, Flex, Text } from '~/src/system'
import { PinIcon } from '~/src/icons/pin-icon' // import { PinnedMessagesDialog } from './pinned-messages-dialog'
import { Avatar, DialogTrigger, Flex, Text } from '~/src/system' import type { Chat } from '~/src/protocol'
import { PinnedMessagesDialog } from './pinned-messages-dialog'
import type { Channel } from '~/src/protocol'
interface Props { interface Props {
chat: Channel chat: Chat
} }
export const ChatInfo = (props: Props) => { export const ChatInfo = (props: Props) => {
const { chat } = props const { chat } = props
console.log("file: index.tsx > line 18 > ChatInfo > chat", chat)
// if (chat.type == 'channel') { // if (chat.type == 'channel') {
return ( return (
<Flex align="center" gap="2"> <Flex align="center" gap="2">
<Avatar size={36} /> <Avatar
size={36}
name={chat.identity?.displayName}
color={chat.identity?.color}
/>
<div> <div>
<Text>#{chat.identity?.displayName}</Text> <Text>#{chat.identity?.displayName}</Text>
<Flex align="center"> <Flex align="center">

View File

@ -14,10 +14,11 @@ interface Props {
mode?: 'normal' | 'editing' mode?: 'normal' | 'editing'
value?: string value?: string
editing?: boolean editing?: boolean
onSubmit: (value: string) => void
} }
export const ChatInput = (props: Props) => { export const ChatInput = (props: Props) => {
const { value, editing } = props const { value, editing, onSubmit } = props
const [inputValue, setInputValue] = useState(value ?? '') const [inputValue, setInputValue] = useState(value ?? '')
const { state } = useChatContext() const { state } = useChatContext()
@ -32,6 +33,13 @@ export const ChatInput = (props: Props) => {
setInputValue(event.target.value) setInputValue(event.target.value)
} }
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && event.shiftKey === false) {
onSubmit(inputValue)
setInputValue('')
}
}
return ( return (
<Wrapper> <Wrapper>
<Box css={{ paddingBottom: 6 }}> <Box css={{ paddingBottom: 6 }}>
@ -47,6 +55,7 @@ export const ChatInput = (props: Props) => {
placeholder="Message" placeholder="Message"
value={inputValue} value={inputValue}
onChange={handleChange} onChange={handleChange}
onKeyDown={handleKeyDown}
/> />
<Flex> <Flex>
<IconButton label="Pick emoji"> <IconButton label="Pick emoji">

View File

@ -25,7 +25,7 @@ export const InputReply = (props: Props) => {
<ReplyIcon /> <ReplyIcon />
</Icon> </Icon>
<Text size="13" weight="500" truncate={false}> <Text size="13" weight="500" truncate={false}>
{message.contact.name} TODO: Add name
</Text> </Text>
</Flex> </Flex>
@ -36,14 +36,14 @@ export const InputReply = (props: Props) => {
<CrossIcon /> <CrossIcon />
</IconButton> </IconButton>
</Flex> </Flex>
{message.type === 'text' && ( {message.contentType === 'TEXT_PLAIN' && (
<Flex> <Flex>
<Text size="13" truncate> <Text size="13" truncate>
{message.text} {message.text}
</Text> </Text>
</Flex> </Flex>
)} )}
{message.type === 'image' && ( {message.contentType === 'IMAGE' && (
<Image <Image
src={message.imageUrl} src={message.imageUrl}
width={56} width={56}
@ -53,23 +53,6 @@ export const InputReply = (props: Props) => {
alt="message" alt="message"
/> />
)} )}
{message.type === 'image-text' && (
<Box>
<Flex>
<Text size="13" truncate>
{message.text}
</Text>
</Flex>
<Image
src={message.imageUrl}
width={56}
height={56}
fit="cover"
radius="bubble"
alt="message"
/>
</Box>
)}
</Wrapper> </Wrapper>
) )
} }

View File

@ -2,11 +2,11 @@ import React from 'react'
import { ReactionPopover } from '~/src/components/reaction-popover' import { ReactionPopover } from '~/src/components/reaction-popover'
import { PencilIcon } from '~/src/icons/pencil-icon' import { PencilIcon } from '~/src/icons/pencil-icon'
import { PinIcon } from '~/src/icons/pin-icon' // import { PinIcon } from '~/src/icons/pin-icon'
import { ReactionIcon } from '~/src/icons/reaction-icon' import { ReactionIcon } from '~/src/icons/reaction-icon'
import { ReplyIcon } from '~/src/icons/reply-icon' import { ReplyIcon } from '~/src/icons/reply-icon'
import { TrashIcon } from '~/src/icons/trash-icon' import { TrashIcon } from '~/src/icons/trash-icon'
import { UnpinIcon } from '~/src/icons/unpin-icon' // import { UnpinIcon } from '~/src/icons/unpin-icon'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { import {
AlertDialog, AlertDialog,
@ -15,7 +15,7 @@ import {
Tooltip, Tooltip,
} from '~/src/system' } from '~/src/system'
import type { Reactions } from '~/src/protocol/use-messages' import type { Reaction, Reactions } from '~/src/protocol/use-messages'
interface Props { interface Props {
owner: boolean owner: boolean
@ -23,6 +23,7 @@ interface Props {
onReplyClick: () => void onReplyClick: () => void
onEditClick: () => void onEditClick: () => void
onPinClick: () => void onPinClick: () => void
onReactionClick: (reaction: Reaction) => void
reacting: boolean reacting: boolean
onReactingChange: (reacting: boolean) => void onReactingChange: (reacting: boolean) => void
reactions: Reactions reactions: Reactions
@ -31,10 +32,11 @@ interface Props {
export const Actions = (props: Props) => { export const Actions = (props: Props) => {
const { const {
owner, owner,
pinned, // pinned,
onReplyClick, onReplyClick,
onEditClick, onEditClick,
onPinClick, // onPinClick,
onReactionClick,
reacting, reacting,
onReactingChange, onReactingChange,
reactions, reactions,
@ -47,7 +49,7 @@ export const Actions = (props: Props) => {
open={reacting} open={reacting}
onOpenChange={onReactingChange} onOpenChange={onReactingChange}
onClick={emoji => { onClick={emoji => {
console.log(emoji) onReactionClick(emoji)
onReactingChange(false) onReactingChange(false)
}} }}
> >
@ -79,7 +81,7 @@ export const Actions = (props: Props) => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
<Tooltip label={pinned ? 'Unpin' : 'Pin'}> {/* <Tooltip label={pinned ? 'Unpin' : 'Pin'}>
<IconButton <IconButton
label={pinned ? 'Unpin message' : 'Pin message'} label={pinned ? 'Unpin message' : 'Pin message'}
intent="info" intent="info"
@ -88,7 +90,7 @@ export const Actions = (props: Props) => {
> >
{pinned ? <UnpinIcon /> : <PinIcon />} {pinned ? <UnpinIcon /> : <PinIcon />}
</IconButton> </IconButton>
</Tooltip> </Tooltip> */}
{owner && ( {owner && (
<AlertDialogTrigger> <AlertDialogTrigger>
<Tooltip label="Delete"> <Tooltip label="Delete">

View File

@ -1,9 +1,12 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import snarkdown from 'snarkdown'
import { UserProfileDialog } from '~/src/components/user-profile-dialog' import { UserProfileDialog } from '~/src/components/user-profile-dialog'
import { useChatContext } from '~/src/contexts/chat-context' import { useChatContext } from '~/src/contexts/chat-context'
import { BellIcon } from '~/src/icons/bell-icon' import { BellIcon } from '~/src/icons/bell-icon'
import { PinIcon } from '~/src/icons/pin-icon' import { PinIcon } from '~/src/icons/pin-icon'
import { useProtocol } from '~/src/protocol/provider'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { import {
Avatar, Avatar,
@ -25,10 +28,11 @@ import { Actions } from './actions'
import { MessageReply } from './message-reply' import { MessageReply } from './message-reply'
import { MessageReactions } from './reactions' import { MessageReactions } from './reactions'
import type { MessageType } from '~/src/protocol/use-messages' import type { Message, Reaction } from '~/src/protocol/use-messages'
interface Props { interface Props {
message: MessageType message: Message
previousMessage?: Message
} }
// const MessageLink = forwardRef(function MessageLink( // const MessageLink = forwardRef(function MessageLink(
@ -53,15 +57,17 @@ interface Props {
// }) // })
export const ChatMessage = (props: Props) => { export const ChatMessage = (props: Props) => {
const { message } = props const { client } = useProtocol()
console.log("🚀 > message", message)
// const { type, contact, owner, mention, pinned, reply, reactions } = message const { message } = props
const owner=false
const mention=false const owner = false
const mention = false
const pinned = false const pinned = false
const reply = false const reply = false
const { contentType, text, displayName, reactions } = message
const { messageId, chatId, contentType, clock, displayName, reactions } =
message
const [editing, setEditing] = useState(false) const [editing, setEditing] = useState(false)
const [reacting, setReacting] = useState(false) const [reacting, setReacting] = useState(false)
@ -70,26 +76,34 @@ export const ChatMessage = (props: Props) => {
const userProfileDialog = useDialog(UserProfileDialog) const userProfileDialog = useDialog(UserProfileDialog)
const handleReplyClick = () => { const handleMessageSubmit = (message: string) => {
dispatch({ client.community.sendTextMessage(
type: 'SET_REPLY', chatId,
message, message,
}) '0x0fa999097568d1fdcc39108a08d75340bd2cee5ec59c36799007150d0a9fc896'
)
}
const handleReaction = (reaction: Reaction) => {
client.community.sendReaction(chatId, messageId, reaction)
}
const handleReplyClick = () => {
dispatch({ type: 'SET_REPLY', message })
} }
const handlePinClick = () => { const handlePinClick = () => {
// console.log(pinned) // TODO: pin message
}
const handleReaction = (reaction: string) => {
console.log(reaction)
} }
const renderMessage = () => { const renderMessage = () => {
if (editing) { if (editing) {
return ( return (
<Box> <Box>
<ChatInput value={message?.text ?? ''} /> <ChatInput
value={message?.text ?? ''}
onSubmit={handleMessageSubmit}
/>
<Flex gap={2}> <Flex gap={2}>
<Button <Button
variant="outline" variant="outline"
@ -98,7 +112,9 @@ export const ChatMessage = (props: Props) => {
> >
Cancel Cancel
</Button> </Button>
<Button size="small">Save</Button> <Button size="small" onClick={handleMessageSubmit}>
Save
</Button>
</Flex> </Flex>
</Box> </Box>
) )
@ -118,37 +134,30 @@ export const ChatMessage = (props: Props) => {
// </AlertDialogTrigger>{' '} // </AlertDialogTrigger>{' '}
return <Text>{message.text}</Text> return <Text>{message.text}</Text>
} }
case 'image': { case 'EMOJI': {
return (
<Text css={{ fontSize: '3rem', lineHeight: 1.1, letterSpacing: -2 }}>
{message.text}
</Text>
)
}
case 'IMAGE': {
const blob = new Blob([message.image.payload], { type: 'image/jpeg' })
// TODO?: call URL.revokeObjectURL()
return ( return (
<Flex gap={1} css={{ paddingTop: '$2' }}> <Flex gap={1} css={{ paddingTop: '$2' }}>
<Image <Image
width={147} width={150}
alt="message" alt="message"
height={196} height={150}
src={message.imageUrl} src={URL.createObjectURL(blob)}
radius="bubble" radius="bubble"
fit="cover"
/> />
</Flex> </Flex>
) )
} }
case 'image-text': {
const { text, imageUrl } = message
return (
<>
<Text>{text}</Text>
<Flex gap={1} css={{ paddingTop: '$1' }}>
<Image
width={147}
alt="message"
height={196}
src={imageUrl}
radius="bubble"
/>
</Flex>
</>
)
}
} }
} }
@ -161,12 +170,11 @@ export const ChatMessage = (props: Props) => {
<Box> <Box>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<button type="button"> <button type="button">
{/* <Avatar size={44} src={contact.imageUrl} /> */} <Avatar size={44} />
</button> </button>
<DropdownMenu> <DropdownMenu>
<Flex direction="column" align="center" gap="1"> <Flex direction="column" align="center" gap="1">
{/* <Avatar size="36" src={contact.imageUrl} /> */} <Avatar size="36" />
{/* <Text>{contact.name}</Text> */}
<Text>{displayName}</Text> <Text>{displayName}</Text>
<EmojiHash /> <EmojiHash />
</Flex> </Flex>
@ -197,20 +205,22 @@ export const ChatMessage = (props: Props) => {
</Box> </Box>
<Box css={{ flex: 1 }}> <Box css={{ flex: 1 }}>
{pinned && ( {/* {pinned && (
<Flex gap={1}> <Flex gap={1}>
<PinIcon width={8} /> <PinIcon width={8} />
<Text size="13">Pinned by carmen.eth</Text> <Text size="13">Pinned by {contact.name}</Text>
</Flex> </Flex>
)} )} */}
<Flex gap="1" align="center"> <Flex gap="1" align="center">
<Text color="primary" weight="500" size="15"> <Text color="primary" weight="500" size="15">
{displayName} {displayName}
{/* {contact.name} */}
</Text> </Text>
<Text size="10" color="gray"> <Text size="10" color="gray">
10:00 AM {new Date(Number(clock)).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}
</Text> </Text>
</Flex> </Flex>
@ -229,6 +239,7 @@ export const ChatMessage = (props: Props) => {
onEditClick={() => setEditing(true)} onEditClick={() => setEditing(true)}
onReplyClick={handleReplyClick} onReplyClick={handleReplyClick}
onPinClick={handlePinClick} onPinClick={handlePinClick}
onReactionClick={handleReaction}
reacting={reacting} reacting={reacting}
onReactingChange={setReacting} onReactingChange={setReacting}
reactions={reactions} reactions={reactions}
@ -236,7 +247,7 @@ export const ChatMessage = (props: Props) => {
</Wrapper> </Wrapper>
<ContextMenu> <ContextMenu>
<ContextMenu.Item onSelect={handleReplyClick}>Reply</ContextMenu.Item> <ContextMenu.Item onSelect={handleReplyClick}>Reply</ContextMenu.Item>
<ContextMenu.Item onSelect={handlePinClick}>Pin</ContextMenu.Item> {/* <ContextMenu.Item onSelect={handlePinClick}>Pin</ContextMenu.Item> */}
</ContextMenu> </ContextMenu>
</ContextMenuTrigger> </ContextMenuTrigger>
</> </>

View File

@ -7,7 +7,7 @@ import { useAppState } from '~/src/contexts/app-context'
import { BellIcon } from '~/src/icons/bell-icon' import { BellIcon } from '~/src/icons/bell-icon'
import { DotsIcon } from '~/src/icons/dots-icon' import { DotsIcon } from '~/src/icons/dots-icon'
import { GroupIcon } from '~/src/icons/group-icon' import { GroupIcon } from '~/src/icons/group-icon'
import { useChannel } from '~/src/protocol' import { useChat } from '~/src/protocol'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { DropdownMenuTrigger, Flex, IconButton, Separator } from '~/src/system' import { DropdownMenuTrigger, Flex, IconButton, Separator } from '~/src/system'
@ -23,7 +23,7 @@ export const Navbar = (props: Props) => {
const { state, dispatch } = useAppState() const { state, dispatch } = useAppState()
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion
const chat = useChannel(params.id!) const chat = useChat(params.id!)
return ( return (
<NavbarWrapper> <NavbarWrapper>
@ -44,7 +44,7 @@ export const Navbar = (props: Props) => {
<IconButton label="Options"> <IconButton label="Options">
<DotsIcon /> <DotsIcon />
</IconButton> </IconButton>
<ChatMenu type="dropdown" chatType="channel" /> <ChatMenu type="dropdown" />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<Separator orientation="vertical" css={{ height: 24 }} /> <Separator orientation="vertical" css={{ height: 24 }} />

View File

@ -1,3 +1,5 @@
// TODO: handle non-existing chat ID
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { useMatch } from 'react-router-dom' import { useMatch } from 'react-router-dom'
@ -5,7 +7,8 @@ import { useMatch } from 'react-router-dom'
import { MemberSidebar } from '~/src/components/member-sidebar' import { MemberSidebar } from '~/src/components/member-sidebar'
import { useAppState } from '~/src/contexts/app-context' import { useAppState } from '~/src/contexts/app-context'
import { ChatProvider } from '~/src/contexts/chat-context' import { ChatProvider } from '~/src/contexts/chat-context'
import { useChannel } from '~/src/protocol' import { useChat } from '~/src/protocol'
import { useProtocol } from '~/src/protocol/provider'
import { useMessages } from '~/src/protocol/use-messages' import { useMessages } from '~/src/protocol/use-messages'
import { styled } from '~/src/styles/config' import { styled } from '~/src/styles/config'
import { Avatar, Flex, Heading, Text } from '~/src/system' import { Avatar, Flex, Heading, Text } from '~/src/system'
@ -14,59 +17,85 @@ import { ChatInput } from './components/chat-input'
import { ChatMessage } from './components/chat-message' import { ChatMessage } from './components/chat-message'
import { Navbar } from './components/navbar' import { Navbar } from './components/navbar'
const ChatStart = () => { interface ChatStartProps {
// TODO: unify this with the useChat hook chatId: string
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion }
const chat = useChannel(params.id!) const ChatStart = (props: ChatStartProps) => {
const { chatId } = props
const { identity } = useChat(chatId)
return ( return (
<Flex direction="column" gap="3" align="center" css={{ marginBottom: 50 }}> <Flex direction="column" gap="3" align="center" css={{ marginBottom: 50 }}>
{/* <Avatar size={120} src={chat.imageUrl} /> */} <Avatar size={120} name={identity?.displayName} color={identity?.color} />
<Heading>{chat.identity?.displayName}</Heading> <Heading>{identity?.displayName}</Heading>
<Text> <Text>
Welcome to the beginning of the #{chat.identity?.displayName} channel! Welcome to the beginning of the #{identity?.displayName} channel!
</Text> </Text>
</Flex> </Flex>
) )
} }
const Content = () => { interface ContentProps {
const contentRef = useRef<HTMLDivElement>(null) chatId: string
}
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion const Content = (props: ContentProps) => {
const { chatId } = props
const contentRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
contentRef.current!.scrollTop = contentRef.current!.scrollHeight ?? 0 contentRef.current!.scrollTop = contentRef.current!.scrollHeight ?? 0
}, []) }, [chatId])
const messages = useMessages(params.id!) const messages = useMessages(chatId)
return ( return (
<ContentWrapper ref={contentRef}> <ContentWrapper ref={contentRef}>
<ChatStart /> {/* <Button onClick={messages.fetchMore}>Fetch more</Button> */}
{messages.data.map(message => ( <ChatStart chatId={chatId} />
<ChatMessage key={message.messageId} message={message} /> {messages.data.map((message, index) => (
<ChatMessage
key={message.messageId}
message={message}
previousMessage={messages.data[index - 1]}
/>
))} ))}
</ContentWrapper> </ContentWrapper>
) )
} }
export const Chat = () => { export const Chat = () => {
const { client } = useProtocol()
const { state, options } = useAppState() const { state, options } = useAppState()
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion
const chatId = params.id!
const chat = useChat(chatId)
// TODO: Update condition based on a chat type // TODO: Update condition based on a chat type
const enableMembers = options.enableMembers // && (chat.type === 'group' || chat.type === 'channel') const enableMembers = options.enableMembers ?? false // && (chat.type === 'group' || chat.type === 'channel')
const showMembers = enableMembers && state.showMembers const showMembers = enableMembers && state.showMembers
const handleMessageSubmit = (message: string) => {
client.community.sendTextMessage(
chatId,
message
// '0x0fa999097568d1fdcc39108a08d75340bd2cee5ec59c36799007150d0a9fc896'
)
}
return ( return (
<ChatProvider> <ChatProvider>
<Wrapper> <Wrapper>
<Main> <Main>
<Navbar enableMembers={enableMembers} /> <Navbar enableMembers={enableMembers} />
<Content /> <Content chatId={chatId} />
<ChatInput /> <ChatInput onSubmit={handleMessageSubmit} />
</Main> </Main>
{showMembers && <MemberSidebar />} {showMembers && <MemberSidebar />}
</Wrapper> </Wrapper>