Dialogs (#247)
* Add community dialog * feat(react): add welcome dialog * feat(react): add connect wallet dialog * feat(react): add disconnect dialog * feat(react): add create profile dialog * feat(react): add welcome dialog * feat(react): add load throwaway profile dialog * feat(react): add sync status profile dialog * fix(react): disconnect dialog spacing * feat(react): add get started section to main sidebar * feat(react): add user profile dialog * feat(react): add edit group dialog * feat(react): support opening dialogs programmatically * feat(react): delete legacy components
This commit is contained in:
parent
d9a0fb49ae
commit
9c74cf4685
|
@ -1,158 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
|
||||
import { useMessengerContext } from '../../contexts/messengerProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { lightTheme } from '../../styles/themes'
|
||||
import { Logo } from '../CommunityIdentity'
|
||||
import { textMediumStyles } from '../Text'
|
||||
import { Modal } from './Modal'
|
||||
import { Btn, ButtonSection, Heading, Section, Text } from './ModalStyle'
|
||||
|
||||
import type { Theme } from '../../styles/themes'
|
||||
|
||||
export const AgreementModalName = 'AgreementModal'
|
||||
|
||||
export function AgreementModal() {
|
||||
const theme = useTheme() as Theme
|
||||
const { communityData } = useMessengerContext()
|
||||
const { setModal } = useModal(AgreementModalName)
|
||||
|
||||
const [checked, setChecked] = useState(false)
|
||||
const [token, setToken] = useState('')
|
||||
|
||||
return (
|
||||
<Modal name={AgreementModalName} className="wide">
|
||||
<Section>
|
||||
<Heading>Welcome to {communityData?.name}</Heading>
|
||||
</Section>
|
||||
<Section>
|
||||
<LogoWrapper>
|
||||
<CommunityLogo
|
||||
style={{
|
||||
backgroundImage: communityData?.icon
|
||||
? `url(${communityData?.icon}`
|
||||
: '',
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
{communityData?.icon === undefined &&
|
||||
communityData?.name.slice(0, 1).toUpperCase()}
|
||||
</CommunityLogo>
|
||||
</LogoWrapper>
|
||||
<AgreementSection>
|
||||
<Text>{communityData?.description}</Text>
|
||||
</AgreementSection>
|
||||
<Agreements>
|
||||
<Agreement>
|
||||
<AgreementInput
|
||||
type="checkbox"
|
||||
name="agreement"
|
||||
value="user agreement"
|
||||
checked={checked}
|
||||
onChange={e => setChecked(e.target.checked)}
|
||||
required
|
||||
/>
|
||||
<Checkmark />I agree with the above
|
||||
</Agreement>
|
||||
|
||||
<form>
|
||||
<HCaptcha
|
||||
sitekey="64702fa3-7f57-41bb-bd43-7afeae54227e"
|
||||
theme={theme === lightTheme ? 'light' : 'dark'}
|
||||
onVerify={setToken}
|
||||
/>
|
||||
</form>
|
||||
</Agreements>
|
||||
</Section>
|
||||
<ButtonSection>
|
||||
<Btn
|
||||
onClick={() => {
|
||||
setModal(false)
|
||||
}}
|
||||
disabled={!token || !checked}
|
||||
>
|
||||
Join {communityData?.name}
|
||||
</Btn>
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const LogoWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
|
||||
const CommunityLogo = styled(Logo)`
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
`
|
||||
|
||||
const AgreementSection = styled.div`
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
|
||||
const Agreements = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Agreement = styled.label`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
color: ${({ theme }) => theme.primary};
|
||||
padding-left: 26px;
|
||||
margin-right: 48px;
|
||||
|
||||
${textMediumStyles}
|
||||
|
||||
& input:checked ~ span {
|
||||
background-color: ${({ theme }) => theme.tertiary};
|
||||
border: 1px solid ${({ theme }) => theme.tertiary};
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
& input:checked ~ span:after {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
|
||||
const AgreementInput = styled.input`
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
`
|
||||
|
||||
const Checkmark = styled.span`
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
||||
background-color: ${({ theme }) => theme.inputColor};
|
||||
border: 1px solid ${({ theme }) => theme.inputColor};
|
||||
border-radius: 2px;
|
||||
margin: 0 8px 0 0;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: none;
|
||||
|
||||
left: 5px;
|
||||
top: 1px;
|
||||
width: 4px;
|
||||
height: 9px;
|
||||
border: solid ${({ theme }) => theme.bodyBackgroundColor};
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
`
|
|
@ -1,18 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import { ConnectModal } from './ConnectModal'
|
||||
import { Modal } from './Modal'
|
||||
|
||||
export const CoinbaseModalName = 'CoinbaseModal'
|
||||
|
||||
export function CoinbaseModal() {
|
||||
return (
|
||||
<Modal name={CoinbaseModalName}>
|
||||
<ConnectModal
|
||||
name="Coinbase Wallet"
|
||||
text="Scan QR code or copy and pase it in your Coinbase Wallet."
|
||||
address="https://www.coinbase.com/wallet"
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useMessengerContext } from '../../contexts/messengerProvider'
|
||||
import { useNarrow } from '../../contexts/narrowProvider'
|
||||
import { DownloadButton } from '../Buttons/DownloadButton'
|
||||
import { CommunityIdentity } from '../CommunityIdentity'
|
||||
import { CopyInput } from '../Form/CopyInput'
|
||||
import { StatusLogo } from '../Icons/StatusLogo'
|
||||
import { textSmallStyles } from '../Text'
|
||||
import { Modal } from './Modal'
|
||||
import { Section, Text } from './ModalStyle'
|
||||
|
||||
import type { CommunityIdentityProps } from '../CommunityIdentity'
|
||||
|
||||
export const CommunityModalName = 'CommunityModal'
|
||||
|
||||
type CommunityModalProps = CommunityIdentityProps
|
||||
|
||||
export const CommunityModal = ({ subtitle }: CommunityModalProps) => {
|
||||
const narrow = useNarrow()
|
||||
const { communityData } = useMessengerContext()
|
||||
return (
|
||||
<Modal name={CommunityModalName}>
|
||||
<Section>
|
||||
<CommunityIdentity subtitle={subtitle} />
|
||||
</Section>
|
||||
<Section>
|
||||
<Text>{communityData?.description}</Text>
|
||||
</Section>
|
||||
<Section>
|
||||
<CopyInput
|
||||
value={communityData?.id ?? ''}
|
||||
label="Community public key"
|
||||
/>
|
||||
<Hint>
|
||||
To access this community, paste community public key in Status desktop
|
||||
or mobile app.
|
||||
{narrow && <StyledDownloadButton />}
|
||||
</Hint>
|
||||
</Section>
|
||||
{!narrow && (
|
||||
<BottomSection>
|
||||
<StatusLogo />
|
||||
<DownloadButton />
|
||||
</BottomSection>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const BottomSection = styled(Section)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const StyledDownloadButton = styled(DownloadButton)`
|
||||
display: inline;
|
||||
padding: 0;
|
||||
margin-left: 4px;
|
||||
background: none;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
text-decoration: underline;
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
`
|
||||
|
||||
const Hint = styled.p`
|
||||
margin-top: 16px;
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
|
||||
${textSmallStyles}
|
||||
`
|
|
@ -1,32 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import QRCode from 'qrcode.react'
|
||||
|
||||
import { CopyInput } from '../Form/CopyInput'
|
||||
import { Heading, MiddleSection, QRWrapper, Section, Text } from './ModalStyle'
|
||||
|
||||
export const ConnectModalName = 'ConnectModal'
|
||||
|
||||
interface ConnectModalProps {
|
||||
name: string
|
||||
address: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export function ConnectModal({ name, address, text }: ConnectModalProps) {
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<Heading>Connect with {name}</Heading>
|
||||
</Section>
|
||||
<MiddleSection>
|
||||
<Text>{text}</Text>
|
||||
<QRWrapper>
|
||||
{' '}
|
||||
<QRCode value={address} size={224} />
|
||||
</QRWrapper>
|
||||
<CopyInput value="2Ef1907d50926...6dt4cEbd975aC5E0Ba" />
|
||||
</MiddleSection>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useMessengerContext } from '../../contexts/messengerProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { buttonStyles } from '../Buttons/buttonStyle'
|
||||
import { ChannelLogo } from '../Channels/ChannelIcon'
|
||||
import { inputStyles } from '../Form/inputStyles'
|
||||
import { AddIcon } from '../Icons/AddIcon'
|
||||
import { textMediumStyles } from '../Text'
|
||||
import { Modal } from './Modal'
|
||||
import { AddWrapper, ButtonSection, Heading, Hint, Section } from './ModalStyle'
|
||||
|
||||
export const EditModalName = 'editModal'
|
||||
|
||||
export const EditModal = () => {
|
||||
const { activeChannel, changeGroupChatName } = useMessengerContext()
|
||||
const { setModal } = useModal(EditModalName)
|
||||
|
||||
const [groupName, setGroupName] = useState('')
|
||||
const [image, setImage] = useState('')
|
||||
|
||||
const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
if (e.currentTarget?.files?.length) {
|
||||
setImage(URL.createObjectURL(e.currentTarget.files[0]))
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpload = () => {
|
||||
if (activeChannel) {
|
||||
if (image) {
|
||||
activeChannel.icon = image // Need function to send image to waku
|
||||
setImage('')
|
||||
}
|
||||
if (groupName) {
|
||||
changeGroupChatName(groupName, activeChannel.id)
|
||||
setGroupName('')
|
||||
}
|
||||
setModal(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal name={EditModalName}>
|
||||
<Section>
|
||||
<Heading>Edit group name and image</Heading>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<NameSection>
|
||||
<LabelGroup>
|
||||
<Label>Name the group</Label>
|
||||
<Hint>{groupName.length}/30</Hint>
|
||||
</LabelGroup>
|
||||
|
||||
<NameInput
|
||||
value={groupName}
|
||||
type="text"
|
||||
placeholder="A catchy name"
|
||||
maxLength={30}
|
||||
onInput={e => setGroupName(e.currentTarget.value)}
|
||||
/>
|
||||
</NameSection>
|
||||
<LogoSection>
|
||||
<Label>Group image</Label>
|
||||
<GroupLogo icon={activeChannel?.icon}>
|
||||
{!activeChannel?.icon &&
|
||||
!image &&
|
||||
activeChannel?.name?.slice(0, 1)?.toUpperCase()}
|
||||
{image && <LogoPreview src={image} />}
|
||||
<AddPictureInputWrapper>
|
||||
<AddIcon />
|
||||
<AddPictureInput
|
||||
type="file"
|
||||
accept="image/png, image/jpeg"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</AddPictureInputWrapper>
|
||||
</GroupLogo>
|
||||
</LogoSection>
|
||||
</Section>
|
||||
<ButtonSection>
|
||||
<SaveBtn onClick={handleUpload}>Save changes</SaveBtn>
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const NameSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 16px;
|
||||
`
|
||||
|
||||
const LabelGroup = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Label = styled.p`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
padding: 10px 0;
|
||||
|
||||
${textMediumStyles}
|
||||
`
|
||||
|
||||
const NameInput = styled.input`
|
||||
padding: 14px 70px 14px 8px;
|
||||
|
||||
${inputStyles}
|
||||
`
|
||||
|
||||
const LogoSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const GroupLogo = styled(ChannelLogo)`
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
font-weight: bold;
|
||||
font-size: 80px;
|
||||
position: relative;
|
||||
align-self: center;
|
||||
margin-right: 0;
|
||||
`
|
||||
|
||||
const LogoPreview = styled.img`
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
const AddPictureInputWrapper = styled(AddWrapper)`
|
||||
top: 0;
|
||||
right: 8px;
|
||||
`
|
||||
|
||||
const AddPictureInput = styled.input`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const SaveBtn = styled.button`
|
||||
padding: 11px 24px;
|
||||
|
||||
${buttonStyles}
|
||||
`
|
|
@ -1,47 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import { useMessengerContext } from '../../contexts/messengerProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { ButtonNo } from '../Buttons/buttonStyle'
|
||||
import { Modal } from './Modal'
|
||||
import { ButtonSection, Heading, Section, Text } from './ModalStyle'
|
||||
|
||||
export const LeavingModalName = 'LeavingModal'
|
||||
|
||||
export const LeavingModal = () => {
|
||||
const { setModal } = useModal(LeavingModalName)
|
||||
const { activeChannel, removeChannel } = useMessengerContext()
|
||||
|
||||
if (activeChannel)
|
||||
return (
|
||||
<Modal name={LeavingModalName}>
|
||||
<Section>
|
||||
<Heading>
|
||||
{activeChannel.type === 'dm' ? 'Delete chat' : 'Leave group'}
|
||||
</Heading>
|
||||
</Section>
|
||||
<Section>
|
||||
<Text>
|
||||
Are you sure you want to{' '}
|
||||
{activeChannel.type === 'dm'
|
||||
? 'delete this chat'
|
||||
: 'leave this group'}
|
||||
?
|
||||
</Text>
|
||||
</Section>
|
||||
<ButtonSection>
|
||||
<ButtonNo
|
||||
onClick={() => {
|
||||
removeChannel(activeChannel.id)
|
||||
setModal(false)
|
||||
}}
|
||||
>
|
||||
{activeChannel.type === 'dm' ? 'Delete' : 'Leave'}
|
||||
</ButtonNo>
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
else {
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { ButtonNo, ButtonYes } from '../Buttons/buttonStyle'
|
||||
import { textMediumStyles } from '../Text'
|
||||
import { Modal } from './Modal'
|
||||
import { ButtonSection, Heading, Section } from './ModalStyle'
|
||||
|
||||
export const LinkModalName = 'LinkModal'
|
||||
|
||||
interface LinkModalProps {
|
||||
link: string
|
||||
}
|
||||
|
||||
export const LinkModal = ({ link }: LinkModalProps) => {
|
||||
const { setModal } = useModal(LinkModalName)
|
||||
return (
|
||||
<Modal name={LinkModalName}>
|
||||
<Section>
|
||||
<Heading>Are you sure you want to visit this website?</Heading>
|
||||
</Section>
|
||||
<Section>
|
||||
<Link>{link}</Link>
|
||||
</Section>
|
||||
<ButtonSection>
|
||||
<ButtonNo onClick={() => setModal(false)}>No</ButtonNo>
|
||||
<ButtonYes
|
||||
onClick={() => {
|
||||
window?.open(link, '_blank', 'noopener')?.focus()
|
||||
setModal(false)
|
||||
}}
|
||||
>
|
||||
Yes, take me there
|
||||
</ButtonYes>
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const Link = styled.a`
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
color: ${({ theme }) => theme.primary};
|
||||
|
||||
${textMediumStyles}
|
||||
`
|
|
@ -1,100 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import {
|
||||
useSetIdentity,
|
||||
useSetNikcname,
|
||||
useUserPublicKey,
|
||||
} from '../../contexts/identityProvider'
|
||||
import { useMessengerContext } from '../../contexts/messengerProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { ButtonNo, ButtonYes } from '../Buttons/buttonStyle'
|
||||
import { UserLogo } from '../Members/UserLogo'
|
||||
import { Modal } from './Modal'
|
||||
import { ButtonSection, Heading, Section, Text } from './ModalStyle'
|
||||
import {
|
||||
EmojiKey,
|
||||
UserAddress,
|
||||
UserAddressWrapper,
|
||||
UserName,
|
||||
UserNameWrapper,
|
||||
} from './ProfileModal'
|
||||
|
||||
export const LogoutModalName = 'LogoutModal'
|
||||
|
||||
export const LogoutModal = () => {
|
||||
const { setModal } = useModal(LogoutModalName)
|
||||
const logout = useSetIdentity()
|
||||
const setNickname = useSetNikcname()
|
||||
const userPK = useUserPublicKey()
|
||||
const { nickname } = useMessengerContext()
|
||||
|
||||
if (userPK) {
|
||||
return (
|
||||
<Modal name={LogoutModalName}>
|
||||
<Section>
|
||||
<Heading>Disconnect</Heading>
|
||||
</Section>
|
||||
<Section>
|
||||
<Text>Do you want to disconnect your profile?</Text>
|
||||
<UserSection>
|
||||
<UserLogo
|
||||
contact={{
|
||||
id: userPK,
|
||||
customName: nickname,
|
||||
trueName: userPK,
|
||||
}}
|
||||
radius={80}
|
||||
colorWheel={[
|
||||
['red', 150],
|
||||
['blue', 250],
|
||||
['green', 360],
|
||||
]}
|
||||
/>
|
||||
<UserNameWrapper className="logout">
|
||||
{' '}
|
||||
<UserName className="small">{nickname}</UserName>
|
||||
</UserNameWrapper>
|
||||
|
||||
<UserAddressWrapper className="small">
|
||||
<UserAddress className="small">
|
||||
{' '}
|
||||
Chatkey: {userPK.slice(0, 10)}...
|
||||
{userPK.slice(-3)}{' '}
|
||||
</UserAddress>
|
||||
</UserAddressWrapper>
|
||||
<EmojiKey className="small">🎩🍞🥑🦍🌈📡💅🏻♣️🔔⛸👵🅱</EmojiKey>
|
||||
</UserSection>
|
||||
</Section>
|
||||
<ButtonSection>
|
||||
<ButtonNo
|
||||
onClick={() => {
|
||||
setModal(false)
|
||||
logout(undefined)
|
||||
setNickname(undefined)
|
||||
}}
|
||||
>
|
||||
Disconnect
|
||||
</ButtonNo>
|
||||
<ButtonYes
|
||||
onClick={() => {
|
||||
setModal(false)
|
||||
}}
|
||||
>
|
||||
Stay Connected
|
||||
</ButtonYes>
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const UserSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 8px 0;
|
||||
`
|
|
@ -1,115 +0,0 @@
|
|||
import React, { useCallback, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { CrossIcon } from '../Icons/CrossIcon'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export interface BasicModalProps {
|
||||
name: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface ModalProps extends BasicModalProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const Modal = ({ name, children, className }: ModalProps) => {
|
||||
const { isVisible, setModal } = useModal(name)
|
||||
|
||||
const listenKeyboard = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' || event.keyCode === 27) {
|
||||
setModal(false)
|
||||
}
|
||||
},
|
||||
[setModal]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
window.addEventListener('keydown', listenKeyboard, true)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', listenKeyboard, true)
|
||||
}
|
||||
}
|
||||
}, [isVisible, listenKeyboard])
|
||||
|
||||
if (!isVisible) return null
|
||||
|
||||
const element = document.getElementById('modal-root')
|
||||
|
||||
if (element) {
|
||||
return createPortal(
|
||||
<ModalView>
|
||||
<ModalOverlay onClick={() => setModal(false)} />
|
||||
<ModalBody className={className}>
|
||||
<CloseButton onClick={() => setModal(false)} className={className}>
|
||||
<CrossIcon />
|
||||
</CloseButton>
|
||||
{children}
|
||||
</ModalBody>
|
||||
</ModalView>,
|
||||
element
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const ModalView = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
`
|
||||
|
||||
const ModalBody = styled.div`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: ${({ theme }) => theme.bodyBackgroundColor};
|
||||
border-radius: 8px;
|
||||
overflow-y: auto;
|
||||
|
||||
&.picture {
|
||||
max-width: 820px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&.wide {
|
||||
max-width: 640px;
|
||||
}
|
||||
`
|
||||
|
||||
const ModalOverlay = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: ${({ theme }) => theme.primary};
|
||||
opacity: 0.4;
|
||||
`
|
||||
|
||||
const CloseButton = styled.button`
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
padding: 10px;
|
||||
|
||||
&.picture {
|
||||
display: none;
|
||||
}
|
||||
`
|
|
@ -1,85 +0,0 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
import { buttonStyles } from '../Buttons/buttonStyle'
|
||||
import { textMediumStyles } from '../Text'
|
||||
|
||||
export const Section = styled.div`
|
||||
padding: 16px;
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid ${({ theme }) => theme.border};
|
||||
}
|
||||
`
|
||||
|
||||
export const MiddleSection = styled(Section)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export const Heading = styled.p`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
font-weight: bold;
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
`
|
||||
|
||||
export const Text = styled.p`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
|
||||
${textMediumStyles}
|
||||
`
|
||||
|
||||
export const Btn = styled.button`
|
||||
padding: 11px 24px;
|
||||
margin-left: 8px;
|
||||
${buttonStyles}
|
||||
|
||||
&:disabled {
|
||||
background: ${({ theme }) => theme.border};
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
}
|
||||
`
|
||||
|
||||
export const BackBtn = styled(Btn)`
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
padding: 8px;
|
||||
margin-left: 0;
|
||||
|
||||
& > svg {
|
||||
fill: ${({ theme }) => theme.tertiary};
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonSection = styled(Section)`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
export const Hint = styled.p`
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
`
|
||||
|
||||
export const AddWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: ${({ theme }) => theme.tertiary};
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
export const QRWrapper = styled.div`
|
||||
margin: 30px 0;
|
||||
`
|
|
@ -1,32 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { Modal } from './Modal'
|
||||
|
||||
export const PictureModalName = 'PictureModal' as const
|
||||
|
||||
export interface PictureModalProps {
|
||||
image: string
|
||||
}
|
||||
|
||||
export const PictureModal = ({ image }: PictureModalProps) => {
|
||||
return (
|
||||
<Modal name={PictureModalName} className="picture">
|
||||
<ModalImageWrapper>
|
||||
<ModalImage src={image}></ModalImage>
|
||||
</ModalImageWrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const ModalImageWrapper = styled.div`
|
||||
display: flex;
|
||||
max-width: 820px;
|
||||
max-height: 820px;
|
||||
`
|
||||
|
||||
const ModalImage = styled.img`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`
|
|
@ -1,131 +0,0 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { bufToHex } from '@status-im/core'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useNickname, useSetIdentity } from '../../contexts/identityProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { decryptIdentity, loadEncryptedIdentity } from '../../utils'
|
||||
import { buttonTransparentStyles } from '../Buttons/buttonStyle'
|
||||
import { UserLogo } from '../Members/UserLogo'
|
||||
import { textMediumStyles } from '../Text'
|
||||
import { Modal } from './Modal'
|
||||
import {
|
||||
Btn,
|
||||
ButtonSection,
|
||||
Heading,
|
||||
MiddleSection,
|
||||
Section,
|
||||
Text,
|
||||
} from './ModalStyle'
|
||||
import {
|
||||
EmojiKey,
|
||||
UserAddress,
|
||||
UserAddressWrapper,
|
||||
UserName,
|
||||
} from './ProfileModal'
|
||||
import { UserCreationModalName } from './UserCreationModal'
|
||||
|
||||
import type { Identity } from '@status-im/core'
|
||||
|
||||
export const ProfileFoundModalName = 'ProfileFoundModal'
|
||||
|
||||
export function ProfileFoundModal() {
|
||||
const { setModal } = useModal(ProfileFoundModalName)
|
||||
const { setModal: setCreationModal } = useModal(UserCreationModalName)
|
||||
|
||||
const setIdentity = useSetIdentity()
|
||||
const encryptedIdentity = useMemo(() => loadEncryptedIdentity(), [])
|
||||
const nickname = useNickname()
|
||||
|
||||
const [decryptedIdentity, setDecryptedIdentity] = useState<
|
||||
Identity | undefined
|
||||
>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (encryptedIdentity)
|
||||
(async () => {
|
||||
setDecryptedIdentity(
|
||||
await decryptIdentity(encryptedIdentity, 'noPassword')
|
||||
)
|
||||
})()
|
||||
}, [encryptedIdentity])
|
||||
|
||||
if (decryptedIdentity) {
|
||||
return (
|
||||
<Modal name={ProfileFoundModalName}>
|
||||
<Section>
|
||||
<Heading>Throwaway Profile found</Heading>
|
||||
</Section>
|
||||
<MiddleSection>
|
||||
<Logo
|
||||
contact={{
|
||||
id: bufToHex(decryptedIdentity.publicKey),
|
||||
customName: nickname,
|
||||
trueName: bufToHex(decryptedIdentity.publicKey),
|
||||
}}
|
||||
radius={80}
|
||||
colorWheel={[
|
||||
['red', 150],
|
||||
['blue', 250],
|
||||
['green', 360],
|
||||
]}
|
||||
/>
|
||||
|
||||
<Name className="small">{nickname}</Name>
|
||||
|
||||
<UserAddressWrapper>
|
||||
<UserAddress className="small">
|
||||
{' '}
|
||||
Chatkey: {decryptedIdentity.privateKey.slice(0, 10)}...
|
||||
{decryptedIdentity.privateKey.slice(-3)}{' '}
|
||||
</UserAddress>
|
||||
</UserAddressWrapper>
|
||||
<EmojiKeyBlock>🎩🍞🥑🦍🌈📡💅🏻♣️🔔⛸👵🅱</EmojiKeyBlock>
|
||||
|
||||
<Text>
|
||||
Throwaway Profile is found in your local browser’s cache. Would you
|
||||
like to load it and use it?{' '}
|
||||
</Text>
|
||||
</MiddleSection>
|
||||
<ButtonSection>
|
||||
<SkipBtn
|
||||
onClick={() => {
|
||||
setCreationModal(true)
|
||||
setModal(false)
|
||||
}}
|
||||
>
|
||||
Skip
|
||||
</SkipBtn>
|
||||
<Btn
|
||||
onClick={() => {
|
||||
setIdentity(decryptedIdentity)
|
||||
setModal(false)
|
||||
}}
|
||||
>
|
||||
Load Throwaway Profile
|
||||
</Btn>
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const Logo = styled(UserLogo)`
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const Name = styled(UserName)`
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const EmojiKeyBlock = styled(EmojiKey)`
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
|
||||
const SkipBtn = styled.button`
|
||||
${buttonTransparentStyles}
|
||||
${textMediumStyles}
|
||||
`
|
|
@ -1,415 +0,0 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useUserPublicKey } from '../../contexts/identityProvider'
|
||||
import { useMessengerContext } from '../../contexts/messengerProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { useToasts } from '../../contexts/toastProvider'
|
||||
import { copy } from '../../utils'
|
||||
import { buttonStyles } from '../Buttons/buttonStyle'
|
||||
import {
|
||||
ClearBtn,
|
||||
inputStyles,
|
||||
NameInput,
|
||||
NameInputWrapper,
|
||||
} from '../Form/inputStyles'
|
||||
import { ClearSvgFull } from '../Icons/ClearIconFull'
|
||||
import { CopyIcon } from '../Icons/CopyIcon'
|
||||
import { EditIcon } from '../Icons/EditIcon'
|
||||
import { LeftIcon } from '../Icons/LeftIcon'
|
||||
import { UntrustworthIcon } from '../Icons/UntrustworthIcon'
|
||||
import { UserIcon } from '../Icons/UserIcon'
|
||||
import { textMediumStyles, textSmallStyles } from '../Text'
|
||||
import { Modal } from './Modal'
|
||||
import {
|
||||
BackBtn,
|
||||
Btn,
|
||||
ButtonSection,
|
||||
Heading,
|
||||
Hint,
|
||||
Section,
|
||||
} from './ModalStyle'
|
||||
|
||||
export const ProfileModalName = 'profileModal' as const
|
||||
|
||||
export type ProfileModalProps = {
|
||||
id: string
|
||||
image?: string
|
||||
renamingState?: boolean
|
||||
requestState?: boolean
|
||||
}
|
||||
|
||||
export const ProfileModal = () => {
|
||||
const { props } = useModal(ProfileModalName)
|
||||
const { id, image, renamingState, requestState } = useMemo(
|
||||
() => (props ? props : { id: '' }),
|
||||
[props]
|
||||
)
|
||||
|
||||
const { setToasts } = useToasts()
|
||||
const { setModal } = useModal(ProfileModalName)
|
||||
|
||||
const userPK = useUserPublicKey()
|
||||
const isUser = useMemo(() => {
|
||||
if (userPK) {
|
||||
return id === userPK
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, [id, userPK])
|
||||
|
||||
const [renaming, setRenaming] = useState(renamingState ?? false)
|
||||
|
||||
useEffect(() => {
|
||||
setRenaming(renamingState ?? false)
|
||||
}, [renamingState])
|
||||
|
||||
const [request, setRequest] = useState('')
|
||||
const [requestCreation, setRequestCreation] = useState(requestState ?? false)
|
||||
|
||||
useEffect(() => {
|
||||
setRequestCreation(requestState ?? false)
|
||||
}, [requestState])
|
||||
|
||||
const { contacts, contactsDispatch } = useMessengerContext()
|
||||
const contact = useMemo(() => contacts[id], [id, contacts])
|
||||
const [customNameInput, setCustomNameInput] = useState('')
|
||||
|
||||
if (!contact) return null
|
||||
return (
|
||||
<Modal name={ProfileModalName} className={`${!requestCreation && 'wide'}`}>
|
||||
<Section>
|
||||
<Heading>{contact.trueName}’s Profile</Heading>
|
||||
</Section>
|
||||
|
||||
<ProfileSection>
|
||||
<NameSection className={`${requestCreation && 'small'}`}>
|
||||
{image ? (
|
||||
<ProfileIcon
|
||||
style={{
|
||||
backgroundImage: `url(${image}`,
|
||||
}}
|
||||
className={`${requestCreation && 'small'}`}
|
||||
/>
|
||||
) : (
|
||||
<UserIcon modalView={!requestCreation} />
|
||||
)}
|
||||
<UserNameWrapper>
|
||||
<UserName className={`${requestCreation && 'small'}`}>
|
||||
{contact?.customName ?? contact.trueName}
|
||||
</UserName>
|
||||
{contact.isUntrustworthy && <UntrustworthIcon />}
|
||||
{!renaming && (
|
||||
<button onClick={() => setRenaming(true)}>
|
||||
{' '}
|
||||
{!requestCreation && <EditIcon width={24} height={24} />}
|
||||
</button>
|
||||
)}
|
||||
</UserNameWrapper>
|
||||
{contact?.customName && (
|
||||
<UserTrueName>{contact.trueName}</UserTrueName>
|
||||
)}
|
||||
</NameSection>
|
||||
{renaming ? (
|
||||
<NameInputWrapper>
|
||||
<NameInput
|
||||
placeholder="Only you will see this nickname"
|
||||
value={customNameInput}
|
||||
onChange={e => setCustomNameInput(e.currentTarget.value)}
|
||||
/>
|
||||
{customNameInput && (
|
||||
<ClearBtn
|
||||
onClick={() => {
|
||||
contactsDispatch({
|
||||
type: 'setCustomName',
|
||||
payload: { id, customName: undefined },
|
||||
})
|
||||
setCustomNameInput('')
|
||||
}}
|
||||
>
|
||||
<ClearSvgFull width={16} height={16} />
|
||||
</ClearBtn>
|
||||
)}
|
||||
</NameInputWrapper>
|
||||
) : (
|
||||
<>
|
||||
<UserAddressWrapper className={`${requestCreation && 'small'}`}>
|
||||
{requestCreation ? (
|
||||
<UserAddress>
|
||||
{id.slice(0, 10)}...{id.slice(-3)}
|
||||
</UserAddress>
|
||||
) : (
|
||||
<>
|
||||
<UserAddress className={`${requestCreation && 'small'}`}>
|
||||
Chatkey: {id.slice(0, 30)}
|
||||
</UserAddress>
|
||||
|
||||
<CopyButton onClick={() => copy(id)}>
|
||||
<CopyIcon width={24} height={24} />
|
||||
</CopyButton>
|
||||
</>
|
||||
)}
|
||||
</UserAddressWrapper>
|
||||
<EmojiKey className={`${requestCreation && 'small'}`}>
|
||||
🎩🍞🥑🦍🌈📡💅🏻♣️🔔⛸👵🅱
|
||||
</EmojiKey>{' '}
|
||||
</>
|
||||
)}
|
||||
{requestCreation && (
|
||||
<RequestSection>
|
||||
<Hint>{request.length}/280</Hint>
|
||||
<RequestInput
|
||||
value={request}
|
||||
placeholder="Say who you are / why you want to became a contact..."
|
||||
maxLength={280}
|
||||
onInput={e => setRequest(e.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
</RequestSection>
|
||||
)}
|
||||
</ProfileSection>
|
||||
<ButtonSection>
|
||||
{renaming ? (
|
||||
<>
|
||||
<BackBtn onClick={() => setRenaming(false)}>
|
||||
<LeftIcon width={28} height={28} />
|
||||
</BackBtn>
|
||||
<Btn
|
||||
disabled={!customNameInput}
|
||||
onClick={() => {
|
||||
contactsDispatch({
|
||||
type: 'setCustomName',
|
||||
payload: { id, customName: customNameInput },
|
||||
})
|
||||
setRenaming(false)
|
||||
}}
|
||||
>
|
||||
Apply nickname
|
||||
</Btn>
|
||||
</>
|
||||
) : requestCreation ? (
|
||||
<>
|
||||
<BackBtn onClick={() => setRequestCreation(false)}>
|
||||
<LeftIcon width={28} height={28} />
|
||||
</BackBtn>
|
||||
<Btn
|
||||
disabled={!request}
|
||||
onClick={() => {
|
||||
setToasts(prev => [
|
||||
...prev,
|
||||
{
|
||||
id: id + request,
|
||||
type: 'confirmation',
|
||||
text: 'Contact Request Sent',
|
||||
},
|
||||
]),
|
||||
setRequestCreation(false),
|
||||
setModal(false),
|
||||
setRequest('')
|
||||
}}
|
||||
>
|
||||
Send Contact Request
|
||||
</Btn>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{!contact.isFriend && !isUser && (
|
||||
<ProfileBtn
|
||||
className={contact.blocked ? '' : 'red'}
|
||||
onClick={() => {
|
||||
contactsDispatch({ type: 'toggleBlocked', payload: { id } })
|
||||
}}
|
||||
>
|
||||
{contact.blocked ? 'Unblock' : 'Block'}
|
||||
</ProfileBtn>
|
||||
)}
|
||||
{contact.isFriend && (
|
||||
<ProfileBtn
|
||||
className="red"
|
||||
onClick={() =>
|
||||
contactsDispatch({
|
||||
type: 'setIsFriend',
|
||||
payload: { id, isFriend: false },
|
||||
})
|
||||
}
|
||||
>
|
||||
Remove Contact
|
||||
</ProfileBtn>
|
||||
)}
|
||||
<ProfileBtn
|
||||
className={contact.isUntrustworthy ? '' : 'red'}
|
||||
onClick={() =>
|
||||
contactsDispatch({ type: 'toggleTrustworthy', payload: { id } })
|
||||
}
|
||||
>
|
||||
{contact.isUntrustworthy
|
||||
? 'Remove Untrustworthy Mark'
|
||||
: 'Mark as Untrustworthy'}
|
||||
</ProfileBtn>
|
||||
{!contact.isFriend && (
|
||||
<Btn onClick={() => setRequestCreation(true)}>
|
||||
Send Contact Request
|
||||
</Btn>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const ProfileSection = styled(Section)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const NameSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.small {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const ProfileIcon = styled.div`
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: end;
|
||||
border-radius: 50%;
|
||||
background-color: #bcbdff;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&.small {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
`
|
||||
|
||||
export const UserNameWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 4px;
|
||||
|
||||
& > svg {
|
||||
fill: ${({ theme }) => theme.tertiary};
|
||||
}
|
||||
|
||||
&.logout {
|
||||
margin: 8px 0;
|
||||
}
|
||||
`
|
||||
|
||||
export const UserName = styled.p`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
letter-spacing: -0.2px;
|
||||
margin-right: 8px;
|
||||
|
||||
&.small {
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
margin-right: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const UserTrueName = styled.p`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.1px;
|
||||
margin-top: 8px;
|
||||
`
|
||||
|
||||
export const UserAddressWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&.small {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
export const UserAddress = styled.p`
|
||||
display: flex;
|
||||
letter-spacing: 1px;
|
||||
margin-right: 8px;
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
|
||||
${textMediumStyles}
|
||||
|
||||
&.small {
|
||||
margin-right: 0;
|
||||
|
||||
${textSmallStyles}
|
||||
}
|
||||
`
|
||||
export const EmojiKey = styled.div`
|
||||
width: 116px;
|
||||
gap: 8px;
|
||||
font-size: 15px;
|
||||
line-height: 14px;
|
||||
letter-spacing: 0.2px;
|
||||
|
||||
&.small {
|
||||
width: 83px;
|
||||
${textSmallStyles}
|
||||
}
|
||||
`
|
||||
|
||||
const ProfileBtn = styled.button`
|
||||
padding: 11px 24px;
|
||||
${buttonStyles}
|
||||
background: ${({ theme }) => theme.bodyBackgroundColor};
|
||||
border: 1px solid ${({ theme }) => theme.border};
|
||||
margin-left: 8px;
|
||||
|
||||
&.red {
|
||||
color: ${({ theme }) => theme.redColor};
|
||||
}
|
||||
|
||||
&.red:hover {
|
||||
background: ${({ theme }) => theme.buttonNoBgHover};
|
||||
}
|
||||
`
|
||||
|
||||
const CopyButton = styled.button`
|
||||
& > svg {
|
||||
fill: ${({ theme }) => theme.tertiary};
|
||||
}
|
||||
`
|
||||
|
||||
const RequestSection = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
margin: 16px 0;
|
||||
`
|
||||
|
||||
const RequestInput = styled.textarea`
|
||||
width: 100%;
|
||||
height: 152px;
|
||||
padding: 10px 16px;
|
||||
resize: none;
|
||||
margin-top: 16px;
|
||||
font-family: 'Inter';
|
||||
|
||||
${inputStyles}
|
||||
`
|
|
@ -1,21 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { Modal } from './Modal'
|
||||
|
||||
export const SizeLimitModalName = 'SizeLimitModal'
|
||||
|
||||
export function SizeLimitModal() {
|
||||
const { setModal } = useModal(SizeLimitModalName)
|
||||
|
||||
return (
|
||||
<Modal name={SizeLimitModalName}>
|
||||
<button
|
||||
onClick={() => setModal(false)}
|
||||
style={{ padding: '20px', display: 'block' }}
|
||||
>
|
||||
File size must be less than 1MB
|
||||
</button>
|
||||
</Modal>
|
||||
)
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import QRCode from 'qrcode.react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { buttonStyles } from '../Buttons/buttonStyle'
|
||||
import { LoginInstructions } from '../Form/LoginInstructions'
|
||||
import { PasteInput } from '../Form/PasteInput'
|
||||
import { Modal } from './Modal'
|
||||
import { Heading, MiddleSection, Section } from './ModalStyle'
|
||||
|
||||
export const StatusModalName = 'StatusModal'
|
||||
|
||||
export enum StatusModalState {
|
||||
Mobile,
|
||||
Desktop,
|
||||
}
|
||||
|
||||
export function StatusModal() {
|
||||
const [modalState, setModalState] = useState<StatusModalState>(
|
||||
StatusModalState.Mobile
|
||||
)
|
||||
|
||||
const mobileFlow = modalState === StatusModalState.Mobile
|
||||
const desktopFlow = modalState === StatusModalState.Desktop
|
||||
|
||||
const switchModalState = (state: StatusModalState) => {
|
||||
setModalState(prev => (prev === state ? StatusModalState.Mobile : state))
|
||||
}
|
||||
return (
|
||||
<Modal name={StatusModalName}>
|
||||
<Section>
|
||||
<Heading>Sync with Status profile</Heading>
|
||||
</Section>
|
||||
<MiddleSectionStatus>
|
||||
<Switch>
|
||||
<SwitchBtn
|
||||
className={`${modalState === StatusModalState.Mobile && 'active'}`}
|
||||
onClick={() => switchModalState(StatusModalState.Mobile)}
|
||||
>
|
||||
From mobile
|
||||
</SwitchBtn>
|
||||
<SwitchBtnMobile
|
||||
className={`${modalState === StatusModalState.Desktop && 'active'}`}
|
||||
onClick={() => switchModalState(StatusModalState.Desktop)}
|
||||
>
|
||||
From desktop
|
||||
</SwitchBtnMobile>
|
||||
</Switch>
|
||||
|
||||
{mobileFlow && <QRCode value="https://status.im/get/" size={158} />}
|
||||
|
||||
{desktopFlow && <PasteInput label="Paste sync code" />}
|
||||
|
||||
<LoginInstructions mobileFlow={mobileFlow} />
|
||||
</MiddleSectionStatus>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const MiddleSectionStatus = styled(MiddleSection)`
|
||||
height: 514px;
|
||||
`
|
||||
|
||||
const Switch = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
`
|
||||
|
||||
const SwitchBtn = styled.button`
|
||||
${buttonStyles}
|
||||
width: 159px;
|
||||
padding: 7px 0;
|
||||
text-align: center;
|
||||
color: ${({ theme }) => theme.tertiary};
|
||||
background: ${({ theme }) => theme.buttonBg};
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
background: ${({ theme }) => theme.tertiary};
|
||||
color: ${({ theme }) => theme.bodyBackgroundColor};
|
||||
z-index: 10000;
|
||||
}
|
||||
`
|
||||
|
||||
const SwitchBtnMobile = styled(SwitchBtn)`
|
||||
margin-left: -8px;
|
||||
`
|
|
@ -1,214 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import { Identity } from '@status-im/core'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import {
|
||||
useSetIdentity,
|
||||
useSetNikcname,
|
||||
useUserPublicKey,
|
||||
useWalletIdentity,
|
||||
} from '../../contexts/identityProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { useNameError } from '../../hooks/useNameError'
|
||||
import { saveIdentity } from '../../utils'
|
||||
import { ClearBtn, NameInput, NameInputWrapper } from '../Form/inputStyles'
|
||||
import { NameError } from '../Form/NameError'
|
||||
import { AddIcon } from '../Icons/AddIcon'
|
||||
import { ChainIcon } from '../Icons/ChainIcon'
|
||||
import { ClearSvgFull } from '../Icons/ClearIconFull'
|
||||
import { LeftIcon } from '../Icons/LeftIcon'
|
||||
import { UserLogo } from '../Members/UserLogo'
|
||||
import { AgreementModalName } from './AgreementModal'
|
||||
import { Modal } from './Modal'
|
||||
import {
|
||||
AddWrapper,
|
||||
BackBtn,
|
||||
Btn,
|
||||
ButtonSection,
|
||||
Heading,
|
||||
Hint,
|
||||
Section,
|
||||
Text,
|
||||
} from './ModalStyle'
|
||||
import { EmojiKey, UserAddress } from './ProfileModal'
|
||||
|
||||
import type { Contact } from '../../models/Contact'
|
||||
|
||||
export const UserCreationModalName = 'UserCreationModal'
|
||||
|
||||
export function UserCreationModal() {
|
||||
const walletIdentity = useWalletIdentity()
|
||||
const userPK = useUserPublicKey()
|
||||
const setIdentity = useSetIdentity()
|
||||
const setNickname = useSetNikcname()
|
||||
|
||||
const [customNameInput, setCustomNameInput] = useState('')
|
||||
const error = useNameError(customNameInput)
|
||||
const [nextStep, setNextStep] = useState(false)
|
||||
const { setModal } = useModal(UserCreationModalName)
|
||||
const { setModal: setAgreementModal } = useModal(AgreementModalName)
|
||||
|
||||
return (
|
||||
<Modal name={UserCreationModalName}>
|
||||
<Section>
|
||||
<Heading>Create a Status Profile</Heading>
|
||||
</Section>
|
||||
<MiddleSection className={`${!nextStep && 'initial'}`}>
|
||||
{nextStep ? (
|
||||
<Title>Your emojihash and identicon ring</Title>
|
||||
) : (
|
||||
<Title>Your profile</Title>
|
||||
)}
|
||||
{nextStep ? (
|
||||
<StyledHint>
|
||||
{' '}
|
||||
This set of emojis and coloured ring around your avatar are unique
|
||||
and represent your chat key, so your friends can easily distinguish
|
||||
you from potential impersonators.
|
||||
</StyledHint>
|
||||
) : (
|
||||
<StyledHint>
|
||||
Longer and unusual names are better as they are <br /> less likely
|
||||
to be used by someone else.
|
||||
</StyledHint>
|
||||
)}
|
||||
|
||||
<LogoWrapper>
|
||||
<UserLogo
|
||||
contact={{ trueName: customNameInput } as Contact}
|
||||
radius={80}
|
||||
colorWheel={[
|
||||
['red', 150],
|
||||
['blue', 250],
|
||||
['green', 360],
|
||||
]}
|
||||
/>
|
||||
{!nextStep && (
|
||||
<AddIconWrapper>
|
||||
<AddIcon />
|
||||
</AddIconWrapper>
|
||||
)}
|
||||
</LogoWrapper>
|
||||
{!nextStep && (
|
||||
<NameInputWrapper>
|
||||
<NameInput
|
||||
placeholder="Display name"
|
||||
value={customNameInput}
|
||||
onChange={e => setCustomNameInput(e.currentTarget.value)}
|
||||
maxLength={24}
|
||||
/>
|
||||
{customNameInput && (
|
||||
<ClearBtn onClick={() => setCustomNameInput('')}>
|
||||
<ClearSvgFull width={16} height={16} />
|
||||
</ClearBtn>
|
||||
)}
|
||||
</NameInputWrapper>
|
||||
)}
|
||||
|
||||
<NameError error={error} />
|
||||
|
||||
{nextStep && userPK && (
|
||||
<>
|
||||
<UserAddress>
|
||||
{' '}
|
||||
Chatkey: {userPK.slice(0, 10)}...
|
||||
{userPK.slice(-3)}{' '}
|
||||
</UserAddress>
|
||||
<ChainIcons>
|
||||
<ChainIcon className="transformed" />
|
||||
<ChainIcon />
|
||||
</ChainIcons>
|
||||
<UserAttributes>
|
||||
<EmojiKey>🎩🍞🥑🦍🌈📡💅🏻♣️🔔⛸👵🅱</EmojiKey>
|
||||
<UserLogo
|
||||
radius={40}
|
||||
colorWheel={[
|
||||
['red', 150],
|
||||
['blue', 250],
|
||||
['green', 360],
|
||||
]}
|
||||
/>
|
||||
</UserAttributes>
|
||||
</>
|
||||
)}
|
||||
</MiddleSection>
|
||||
<ButtonSection>
|
||||
<BackBtn onClick={() => setModal(false)}>
|
||||
<LeftIcon width={28} height={28} />
|
||||
</BackBtn>
|
||||
<Btn
|
||||
onClick={() => {
|
||||
if (nextStep) {
|
||||
setModal(false)
|
||||
setAgreementModal(true)
|
||||
} else {
|
||||
const identity = walletIdentity || Identity.generate()
|
||||
setNickname(customNameInput)
|
||||
setIdentity(identity)
|
||||
!walletIdentity && saveIdentity(identity, 'noPassword')
|
||||
setNextStep(true)
|
||||
}
|
||||
}}
|
||||
disabled={!customNameInput || error !== 0}
|
||||
>
|
||||
Next
|
||||
</Btn>
|
||||
</ButtonSection>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const MiddleSection = styled(Section)`
|
||||
height: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&.initial {
|
||||
padding: 32px;
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled(Text)`
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
letter-spacing: -0.2px;
|
||||
margin-bottom: 16px;
|
||||
`
|
||||
|
||||
const StyledHint = styled(Hint)`
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const LogoWrapper = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin-bottom: 32px;
|
||||
`
|
||||
|
||||
const AddIconWrapper = styled(AddWrapper)`
|
||||
top: 0;
|
||||
right: -50%;
|
||||
transform: translateX(-50%);
|
||||
`
|
||||
|
||||
const ChainIcons = styled.div`
|
||||
width: 104px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 16px 0;
|
||||
`
|
||||
|
||||
const UserAttributes = styled.div`
|
||||
width: 200px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
`
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { UserCreationButtons } from '../UserCreation/UserCreationButtons'
|
||||
import { Modal } from './Modal'
|
||||
import { Heading, Section } from './ModalStyle'
|
||||
|
||||
export const UserCreationStartModalName = 'UserCreationStartModal'
|
||||
|
||||
export const UserCreationStartModal = () => {
|
||||
return (
|
||||
<Modal name={UserCreationStartModalName}>
|
||||
<Section>
|
||||
<Heading>Jump into the discussion</Heading>
|
||||
</Section>
|
||||
<MiddleSection>
|
||||
<UserCreationButtons permission={true} />
|
||||
</MiddleSection>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const MiddleSection = styled(Section)`
|
||||
padding: 48px 0;
|
||||
`
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
import { ConnectModal } from './ConnectModal'
|
||||
import { Modal } from './Modal'
|
||||
|
||||
export const WalletConnectModalName = 'WalletConnectModal'
|
||||
|
||||
export function WalletConnectModal() {
|
||||
return (
|
||||
<Modal name={WalletConnectModalName}>
|
||||
<ConnectModal
|
||||
name="WalletConnect"
|
||||
text="Scan QR code with a WallectConnect-compatible wallet or copy code and
|
||||
paste it in your hardware wallet."
|
||||
address="https://walletconnect.com/"
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
import React, { useCallback } from 'react'
|
||||
|
||||
import { genPrivateKeyWithEntropy, Identity } from '@status-im/core'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import {
|
||||
useSetIdentity,
|
||||
useSetWalletIdentity,
|
||||
} from '../../contexts/identityProvider'
|
||||
import { useMessengerContext } from '../../contexts/messengerProvider'
|
||||
import { useModal } from '../../contexts/modalProvider'
|
||||
import { CoinbaseLogo } from '../Icons/CoinbaseLogo'
|
||||
import { MetamaskLogo } from '../Icons/MetamaskLogo'
|
||||
import { WalletConnectLogo } from '../Icons/WalletConnectLogo'
|
||||
import { CoinbaseModalName } from './CoinbaseModal'
|
||||
import { Modal } from './Modal'
|
||||
import { Heading, MiddleSection, Section, Text } from './ModalStyle'
|
||||
import { UserCreationModalName } from './UserCreationModal'
|
||||
import { WalletConnectModalName } from './WalletConnectModal'
|
||||
|
||||
export const WalletModalName = 'WalletModal'
|
||||
|
||||
export function WalletModal() {
|
||||
const { setModal } = useModal(WalletModalName)
|
||||
const setIdentity = useSetIdentity()
|
||||
const setWalletIdentity = useSetWalletIdentity()
|
||||
const { setModal: setUserCreationModal } = useModal(UserCreationModalName)
|
||||
const { setModal: setWalleConnectModal } = useModal(WalletConnectModalName)
|
||||
const { setModal: setCoinbaseModal } = useModal(CoinbaseModalName)
|
||||
const { messenger } = useMessengerContext()
|
||||
|
||||
const handleMetamaskClick = useCallback(async () => {
|
||||
// TODO: Add types for global Ethereum object
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const dappUrl = window.location.hostname
|
||||
|
||||
const ethereum = (window as any)?.ethereum as any | undefined
|
||||
if (document.location.origin !== dappUrl) {
|
||||
alert('You are not signing in from correct url!')
|
||||
return
|
||||
}
|
||||
if (ethereum && messenger) {
|
||||
try {
|
||||
if (ethereum?.isMetaMask) {
|
||||
const [account] = await ethereum.request({
|
||||
method: 'eth_requestAccounts',
|
||||
})
|
||||
|
||||
const msgParams = JSON.stringify({
|
||||
domain: {
|
||||
chainId: 1,
|
||||
name: window.location.origin,
|
||||
version: '1',
|
||||
},
|
||||
message: {
|
||||
action: 'Status CommunityChatRoom Key',
|
||||
onlySignOn: dappUrl,
|
||||
message:
|
||||
"This signature will be used to decrypt chat communications; check that the 'onlySignOn' property of this message matches the current website address.",
|
||||
},
|
||||
primaryType: 'Mail',
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'version', type: 'string' },
|
||||
{ name: 'chainId', type: 'uint256' },
|
||||
],
|
||||
Mail: [
|
||||
{ name: 'action', type: 'string' },
|
||||
{ name: 'onlySignOn', type: 'string' },
|
||||
{ name: 'message', type: 'string' },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const params = [account, msgParams]
|
||||
const method = 'eth_signTypedData_v4'
|
||||
|
||||
const signature = await ethereum.request({
|
||||
method,
|
||||
params,
|
||||
from: account,
|
||||
})
|
||||
const privateKey = genPrivateKeyWithEntropy(signature)
|
||||
|
||||
const loadedIdentity = new Identity(privateKey)
|
||||
|
||||
const userInNetwork = await messenger.checkIfUserInWakuNetwork(
|
||||
loadedIdentity.publicKey
|
||||
)
|
||||
|
||||
if (userInNetwork) {
|
||||
setIdentity(loadedIdentity)
|
||||
} else {
|
||||
setWalletIdentity(loadedIdentity)
|
||||
setUserCreationModal(true)
|
||||
}
|
||||
setModal(false)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
alert('Error')
|
||||
}
|
||||
}
|
||||
alert('Metamask not found')
|
||||
}, [
|
||||
messenger,
|
||||
setIdentity,
|
||||
setModal,
|
||||
setWalletIdentity,
|
||||
setUserCreationModal,
|
||||
])
|
||||
|
||||
return (
|
||||
<Modal name={WalletModalName}>
|
||||
<Section>
|
||||
<Heading>Connect an Ethereum Wallet</Heading>
|
||||
</Section>
|
||||
<MiddleSectionWallet>
|
||||
<Text>Choose a way to chat using your Ethereum address.</Text>
|
||||
<Wallets>
|
||||
<Wallet onClick={() => (setModal(false), setWalleConnectModal(true))}>
|
||||
<Heading>WalletConnect</Heading>
|
||||
<WalletConnectLogo />
|
||||
</Wallet>
|
||||
<Wallet onClick={() => (setModal(false), setCoinbaseModal(true))}>
|
||||
<Heading>Coinbase Wallet</Heading>
|
||||
<CoinbaseLogo />
|
||||
</Wallet>
|
||||
<Wallet onClick={handleMetamaskClick}>
|
||||
<Heading>MetaMask</Heading>
|
||||
<MetamaskLogo />
|
||||
</Wallet>
|
||||
</Wallets>
|
||||
</MiddleSectionWallet>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const MiddleSectionWallet = styled(MiddleSection)`
|
||||
align-items: stretch;
|
||||
`
|
||||
|
||||
const Wallets = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
`
|
||||
|
||||
const Wallet = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid ${({ theme }) => theme.skeletonDark};
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.buttonBgHover};
|
||||
}
|
||||
`
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react'
|
||||
|
||||
import { Avatar, Dialog, TextInput } from '~/src/system'
|
||||
|
||||
export const EditGroupChatDialog = () => {
|
||||
return (
|
||||
<Dialog title="Edit Group">
|
||||
<Dialog.Body align="center">
|
||||
<TextInput
|
||||
label="Group name"
|
||||
placeholder="A catchy name"
|
||||
maxLength={30}
|
||||
/>
|
||||
<Avatar size="120" />
|
||||
</Dialog.Body>
|
||||
<Dialog.Actions>
|
||||
<Dialog.Action>Save changes</Dialog.Action>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
|
@ -2,6 +2,11 @@ import React from 'react'
|
|||
|
||||
import { BellIcon } from '~/src/icons/bell-icon'
|
||||
import { ContextMenu, DropdownMenu } from '~/src/system'
|
||||
import { useAlertDialog } from '~/src/system/dialog/alert-dialog'
|
||||
import { useDialog } from '~/src/system/dialog/dialog'
|
||||
|
||||
import { UserProfileDialog } from '../user-profile-dialog'
|
||||
import { EditGroupChatDialog } from './edit-group-chat-dialog'
|
||||
|
||||
interface Props {
|
||||
type: 'dropdown' | 'context'
|
||||
|
@ -13,7 +18,24 @@ export const ChatMenu = (props: Props) => {
|
|||
|
||||
const Menu = type === 'dropdown' ? DropdownMenu : ContextMenu
|
||||
|
||||
const renderMenuItems = () => {
|
||||
const userProfileDialog = useDialog(UserProfileDialog)
|
||||
const editGroupChatDialog = useDialog(EditGroupChatDialog)
|
||||
|
||||
const deleteChatDialog = useAlertDialog({
|
||||
title: 'Delete Chat',
|
||||
description: 'Are you sure you want to delete this chat?',
|
||||
actionLabel: 'Delete',
|
||||
actionVariant: 'danger',
|
||||
cancelLabel: 'Keep',
|
||||
})
|
||||
const leaveGroupDialog = useAlertDialog({
|
||||
title: 'Leave Group',
|
||||
description: 'Are you sure you want to leave this group chat?',
|
||||
actionLabel: 'Leave',
|
||||
actionVariant: 'danger',
|
||||
cancelLabel: 'Stay',
|
||||
})
|
||||
|
||||
const commonMenuItems = (
|
||||
<>
|
||||
<Menu.TriggerItem label="Mute Chat" icon={<BellIcon />}>
|
||||
|
@ -34,36 +56,51 @@ export const ChatMenu = (props: Props) => {
|
|||
)
|
||||
|
||||
if (chatType === 'channel') {
|
||||
return commonMenuItems
|
||||
return <Menu>{commonMenuItems}</Menu>
|
||||
}
|
||||
|
||||
if (chatType === 'group-chat') {
|
||||
return (
|
||||
<>
|
||||
<Menu>
|
||||
<Menu.Item icon={<BellIcon />}>Add / remove from group</Menu.Item>
|
||||
<Menu.Item icon={<BellIcon />}>Edit name and image</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>
|
||||
<Menu.Item
|
||||
icon={<BellIcon />}
|
||||
danger
|
||||
onSelect={() => leaveGroupDialog.open()}
|
||||
>
|
||||
Leave Chat
|
||||
</Menu.Item>
|
||||
</>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu.Item icon={<BellIcon />}>View Profile</Menu.Item>
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
icon={<BellIcon />}
|
||||
onSelect={() => userProfileDialog.open({ contact: 'Satoshi' })}
|
||||
>
|
||||
View Profile
|
||||
</Menu.Item>
|
||||
<Menu.Separator />
|
||||
{commonMenuItems}
|
||||
<Menu.Separator />
|
||||
<Menu.Item icon={<BellIcon />} danger>
|
||||
<Menu.Item
|
||||
icon={<BellIcon />}
|
||||
danger
|
||||
onSelect={() => deleteChatDialog.open()}
|
||||
>
|
||||
Delete Chat
|
||||
</Menu.Item>
|
||||
</>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
return <Menu>{renderMenuItems()}</Menu>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import React from 'react'
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
Dialog,
|
||||
// EmojiHash,
|
||||
Flex,
|
||||
Heading,
|
||||
Text,
|
||||
TextInput,
|
||||
} from '~/src/system'
|
||||
|
||||
export const CreateProfileDialog = () => {
|
||||
return (
|
||||
<Dialog title="Create Status Profile">
|
||||
{/* <Dialog.Body align="stretch" css={{ paddingTop: 32 }}>
|
||||
<Heading
|
||||
size="22"
|
||||
weight="600"
|
||||
align="center"
|
||||
css={{ marginBottom: 16 }}
|
||||
>
|
||||
Your emojihash and identicon ring
|
||||
</Heading>
|
||||
<Text color="gray" align="center" size="15" css={{ marginBottom: 32 }}>
|
||||
This set of emojis and coloured ring around your avatar are unique and
|
||||
represent your chat key, so your friends can easily distinguish you
|
||||
from potential impersonators.
|
||||
</Text>
|
||||
<Flex justify="center" css={{ marginBottom: 38 }}>
|
||||
<Avatar size="80" />
|
||||
</Flex>
|
||||
<Text color="gray" align="center">
|
||||
Chatkey: 0x63FaC920149...fae4d52fe3BD377
|
||||
</Text>
|
||||
<EmojiHash />
|
||||
</Dialog.Body> */}
|
||||
|
||||
<Dialog.Body align="stretch" css={{ paddingTop: 32 }}>
|
||||
<Heading
|
||||
size="22"
|
||||
weight="600"
|
||||
align="center"
|
||||
css={{ marginBottom: 16 }}
|
||||
>
|
||||
Your profile
|
||||
</Heading>
|
||||
<Text color="gray" align="center" css={{ marginBottom: 32 }}>
|
||||
Longer and unusual names are better as they
|
||||
<br />
|
||||
are less likely to be used by someone else.
|
||||
</Text>
|
||||
<Flex justify="center" css={{ marginBottom: 38 }}>
|
||||
<Avatar size="80" />
|
||||
</Flex>
|
||||
<TextInput placeholder="Display name" />
|
||||
</Dialog.Body>
|
||||
<Dialog.Actions>
|
||||
<Dialog.Action>Next</Dialog.Action>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,68 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import { styled } from '~/src/styles/config'
|
||||
import { Dialog, Grid, Text } from '~/src/system'
|
||||
|
||||
// TODO: Add wallet integration
|
||||
export const ConnectWalletDialog = () => {
|
||||
const [wallet, setWallet] = useState<'coinbase' | undefined>()
|
||||
|
||||
console.log(wallet)
|
||||
|
||||
// TODO: Add wallet logos
|
||||
return (
|
||||
<Dialog title="Connect Ethereum Wallet">
|
||||
<Dialog.Body>
|
||||
<Text css={{ marginBottom: '$3' }}>
|
||||
Choose a way to chat using your Ethereum address.
|
||||
</Text>
|
||||
<Grid gap={2} css={{ paddingBottom: '$2' }}>
|
||||
<ButtonItem>WalletConnect</ButtonItem>
|
||||
<ButtonItem onClick={() => setWallet('coinbase')}>
|
||||
Coinbase Wallet
|
||||
</ButtonItem>
|
||||
<ButtonItem>MetaMask</ButtonItem>
|
||||
</Grid>
|
||||
</Dialog.Body>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
// const CoinbaseWalletDialog = () => {
|
||||
// return (
|
||||
// <Dialog title="Connect with Coinbase Wallet">
|
||||
// <Dialog.Body>
|
||||
// <Text>Scan QR code or copy and pase it in your Coinbase Wallet.</Text>
|
||||
// </Dialog.Body>
|
||||
// </Dialog>
|
||||
// )
|
||||
// }
|
||||
|
||||
// const WalletConnectDialog = () => {
|
||||
// return (
|
||||
// <Dialog title="Connect with WalletConnect">
|
||||
// <Dialog.Body>
|
||||
// <Text>
|
||||
// Scan QR code with a WallectConnect-compatible wallet or copy code and
|
||||
// paste it in your hardware wallet.
|
||||
// </Text>
|
||||
// </Dialog.Body>
|
||||
// </Dialog>
|
||||
// )
|
||||
// }
|
||||
|
||||
const ButtonItem = styled('button', {
|
||||
width: '100%',
|
||||
padding: '12px 16px',
|
||||
textAlign: 'left',
|
||||
border: '1px solid $gray-3',
|
||||
borderRadius: '$2',
|
||||
color: '$accent-1',
|
||||
fontSize: 17,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: '$600',
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: '$primary-3',
|
||||
},
|
||||
})
|
|
@ -0,0 +1,166 @@
|
|||
import React from 'react'
|
||||
|
||||
import { CreateProfileDialog } from '~/src/components/create-profile-dialog'
|
||||
import { useLocalStorage } from '~/src/hooks/use-local-storage'
|
||||
import { Button, Flex } from '~/src/system'
|
||||
import { DialogTrigger } from '~/src/system/dialog'
|
||||
import { Grid } from '~/src/system/grid'
|
||||
import { Heading } from '~/src/system/heading'
|
||||
|
||||
import { ConnectWalletDialog } from './connect-wallet-dialog'
|
||||
import { SyncStatusProfileDialog } from './sync-status-profile-dialog'
|
||||
import { ThrowawayProfileFoundDialog } from './throwaway-profile-found-dialog'
|
||||
|
||||
export const GetStarted = () => {
|
||||
const [throwawayProfile] = useLocalStorage('cipherIdentityt', null)
|
||||
|
||||
const handleSkip = () => {
|
||||
// TODO: Add skip logic
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex direction="column" align="center" gap={5}>
|
||||
<svg
|
||||
width={65}
|
||||
height={64}
|
||||
viewBox="0 0 65 64"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_3_411236)">
|
||||
<path
|
||||
d="M43.045 20.516a21.054 21.054 0 0121.45 20.704 21.217 21.217 0 01-.491 4.988c-.598 2.712-.954 5.472-.954 8.249v5.368a1.49 1.49 0 01-1.49 1.49h-5.368c-2.777 0-5.537.355-8.249.954a21.225 21.225 0 01-4.987.491 21.054 21.054 0 01-20.704-21.45c.173-11.406 9.387-20.62 20.793-20.794z"
|
||||
fill="url(#paint0_linear_3_411236)"
|
||||
/>
|
||||
<path
|
||||
d="M43.045 20.516a21.054 21.054 0 0121.45 20.704 21.217 21.217 0 01-.491 4.988c-.598 2.712-.954 5.472-.954 8.249v5.368a1.49 1.49 0 01-1.49 1.49h-5.368c-2.777 0-5.537.355-8.249.954a21.225 21.225 0 01-4.987.491 21.054 21.054 0 01-20.704-21.45c.173-11.406 9.387-20.62 20.793-20.794z"
|
||||
fill="url(#paint1_linear_3_411236)"
|
||||
/>
|
||||
<path
|
||||
d="M26.637 1.237A25.65 25.65 0 00.505 26.461c-.04 2.09.168 4.124.599 6.076.729 3.304 1.162 6.666 1.162 10.05v6.54c0 1.002.812 1.814 1.814 1.814h6.54c3.384 0 6.746.433 10.05 1.162 1.952.43 3.986.64 6.076.599A25.65 25.65 0 0051.97 26.57C51.758 12.675 40.533 1.45 26.637 1.237z"
|
||||
fill="url(#paint2_linear_3_411236)"
|
||||
/>
|
||||
<path
|
||||
d="M17.024 25.592a3.971 3.971 0 00-2.9-1.258 3.986 3.986 0 00-3.987 3.986c0 1.145.485 2.174 1.257 2.901l8.6 8.6a3.972 3.972 0 002.9 1.257 3.986 3.986 0 003.987-3.986 3.972 3.972 0 00-1.258-2.901l-8.6-8.6z"
|
||||
fill="url(#paint3_linear_3_411236)"
|
||||
/>
|
||||
<path
|
||||
d="M14.123 32.308a3.986 3.986 0 100-7.973 3.986 3.986 0 000 7.973z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M28.297 25.592a3.971 3.971 0 00-2.9-1.258 3.986 3.986 0 00-3.987 3.986c0 1.145.485 2.174 1.258 2.901l8.599 8.6a3.972 3.972 0 002.9 1.257 3.986 3.986 0 003.987-3.986 3.972 3.972 0 00-1.258-2.901l-8.599-8.6z"
|
||||
fill="url(#paint4_linear_3_411236)"
|
||||
/>
|
||||
<path
|
||||
d="M25.398 32.308a3.986 3.986 0 100-7.973 3.986 3.986 0 000 7.973z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M39.572 25.592a3.971 3.971 0 00-2.901-1.258 3.986 3.986 0 00-3.986 3.986c0 1.145.485 2.174 1.257 2.901l8.6 8.6a3.972 3.972 0 002.9 1.257 3.986 3.986 0 003.986-3.986 3.972 3.972 0 00-1.257-2.901l-8.6-8.6z"
|
||||
fill="url(#paint5_linear_3_411236)"
|
||||
/>
|
||||
<path
|
||||
d="M36.672 32.308a3.986 3.986 0 100-7.973 3.986 3.986 0 000 7.973z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_3_411236"
|
||||
x1={39.1099}
|
||||
y1={37.3759}
|
||||
x2={70.1253}
|
||||
y2={68.3913}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#A7F3CE" />
|
||||
<stop offset={1} stopColor="#61DB99" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_3_411236"
|
||||
x1={49.2627}
|
||||
y1={47.5281}
|
||||
x2={36.0891}
|
||||
y2={34.3557}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#61DB99" stopOpacity={0} />
|
||||
<stop offset={1} stopColor="#009E74" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_3_411236"
|
||||
x1={16.8336}
|
||||
y1={22.8099}
|
||||
x2={49.5796}
|
||||
y2={55.5558}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#62E1FB" />
|
||||
<stop offset={1} stopColor="#00A2F3" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_3_411236"
|
||||
x1={22.9908}
|
||||
y1={37.1889}
|
||||
x2={5.74961}
|
||||
y2={19.9482}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#00A2F3" stopOpacity={0} />
|
||||
<stop offset={1} stopColor="#0075CD" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_3_411236"
|
||||
x1={34.2635}
|
||||
y1={37.1884}
|
||||
x2={17.0228}
|
||||
y2={19.9478}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#00A2F3" stopOpacity={0} />
|
||||
<stop offset={1} stopColor="#2A353D" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_3_411236"
|
||||
x1={45.5379}
|
||||
y1={37.1886}
|
||||
x2={28.2972}
|
||||
y2={19.9479}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#00A2F3" stopOpacity={0} />
|
||||
<stop offset={1} stopColor="#0075CD" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_3_411236">
|
||||
<path fill="#fff" transform="translate(.5)" d="M0 0H64V64H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<Heading align="center" size="17" weight="600">
|
||||
Want to jump into the discussion?
|
||||
</Heading>
|
||||
<Grid gap={3} align="center" justify="center">
|
||||
<DialogTrigger>
|
||||
<Button>Sync with Status profile</Button>
|
||||
<SyncStatusProfileDialog />
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Connect Wallet</Button>
|
||||
<ConnectWalletDialog />
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button variant="outline">Use Throwaway Profile</Button>
|
||||
{throwawayProfile ? (
|
||||
<ThrowawayProfileFoundDialog onSkip={handleSkip} />
|
||||
) : (
|
||||
<CreateProfileDialog />
|
||||
)}
|
||||
</DialogTrigger>
|
||||
</Grid>
|
||||
</Flex>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
|
||||
import { styled } from '~/src/styles/config'
|
||||
import { Box, ButtonGroup, Dialog, Text, TextInput } from '~/src/system'
|
||||
|
||||
export const SyncStatusProfileDialog = () => {
|
||||
const [platform, setPlatform] = useState<'mobile' | 'desktop'>('mobile')
|
||||
|
||||
return (
|
||||
<Dialog title="Sync with Status profile">
|
||||
<Dialog.Body
|
||||
align="center"
|
||||
gap="6"
|
||||
css={{ paddingTop: 24, paddingBottom: 48 }}
|
||||
>
|
||||
<ButtonGroup value={platform} onChange={setPlatform}>
|
||||
<ButtonGroup.Item value="mobile">From Mobile</ButtonGroup.Item>
|
||||
<ButtonGroup.Item value="desktop">From Desktop</ButtonGroup.Item>
|
||||
</ButtonGroup>
|
||||
|
||||
{platform === 'mobile' && (
|
||||
<>
|
||||
{/* TODO: Add mobile QR code */}
|
||||
<QRCodeSVG value="https://status.im/get/" size={158} />
|
||||
<Box>
|
||||
<List>
|
||||
{/* // TODO: Add icons to instructions */}
|
||||
<ListItem>1. Open Status App on your mobile</ListItem>
|
||||
<ListItem>2. Navigate yourself to tab</ListItem>
|
||||
<ListItem>3. Select</ListItem>
|
||||
<ListItem>4. Tap</ListItem>
|
||||
<ListItem>5. Scan the sync code from this screen ↑</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{platform === 'desktop' && (
|
||||
<>
|
||||
<Box css={{ width: '100%' }}>
|
||||
<TextInput label="Sync Code" placeholder="0x000000" />
|
||||
</Box>
|
||||
<List>
|
||||
{/* TODO: Add icons to instructions */}
|
||||
<ListItem>1. Open Status App on your desktop</ListItem>
|
||||
<ListItem>2. Navigate yourself to tab</ListItem>
|
||||
<ListItem>3. Select</ListItem>
|
||||
<ListItem>4. Tap</ListItem>
|
||||
<ListItem>5. Scan the sync code from this screen ↑</ListItem>
|
||||
</List>
|
||||
</>
|
||||
)}
|
||||
</Dialog.Body>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
const List = styled('ul', {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 20,
|
||||
})
|
||||
|
||||
const ListItem = styled('li', Text, {
|
||||
defaultVariants: {
|
||||
color: 'gray',
|
||||
},
|
||||
})
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react'
|
||||
|
||||
import { useProfile } from '~/src/protocol/use-profile'
|
||||
import { Avatar, Dialog, EmojiHash, Flex, Heading, Text } from '~/src/system'
|
||||
|
||||
interface Props {
|
||||
onSkip: () => void
|
||||
}
|
||||
|
||||
export const ThrowawayProfileFoundDialog = (props: Props) => {
|
||||
const { onSkip } = props
|
||||
|
||||
const profile = useProfile()
|
||||
|
||||
const handleLoadThrowawayProfile = () => {
|
||||
// TODO: load throwaway profile
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog title="Throwaway Profile Found">
|
||||
<Dialog.Body gap="5">
|
||||
<Flex direction="column" align="center" gap="2">
|
||||
<Avatar size={64} src={profile.imageUrl} />
|
||||
<Heading weight="600">{profile.name}</Heading>
|
||||
<Text color="gray">
|
||||
Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
|
||||
</Text>
|
||||
<EmojiHash />
|
||||
</Flex>
|
||||
<Text>
|
||||
Throwaway profile is found in your local {"browser's"} storage.
|
||||
<br />
|
||||
Would you like to load it and use it?
|
||||
</Text>
|
||||
</Dialog.Body>
|
||||
|
||||
<Dialog.Actions>
|
||||
<Dialog.Action variant="outline" onClick={onSkip}>
|
||||
Skip
|
||||
</Dialog.Action>
|
||||
<Dialog.Action onClick={handleLoadThrowawayProfile}>
|
||||
Load Throwaway Profile
|
||||
</Dialog.Action>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
|
@ -38,4 +38,5 @@ const Separator = styled('div', {
|
|||
margin: '16px 0',
|
||||
height: 1,
|
||||
background: 'rgba(0, 0, 0, 0.1)',
|
||||
flexShrink: 0,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react'
|
||||
|
||||
import { useProfile } from '~/src/protocol/use-profile'
|
||||
import { Avatar, Dialog, EmojiHash, Flex, Heading, Text } from '~/src/system'
|
||||
|
||||
export const DisconnectDialog = () => {
|
||||
const profile = useProfile()
|
||||
|
||||
return (
|
||||
<Dialog title="Disconnect">
|
||||
<Dialog.Body gap="5">
|
||||
<Text>Do you want to disconnect your profile from this browser?</Text>
|
||||
<Flex direction="column" align="center" gap="2">
|
||||
<Avatar size={64} src={profile.imageUrl} />
|
||||
<Heading weight="600">{profile.name}</Heading>
|
||||
<Text color="gray">
|
||||
Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
|
||||
</Text>
|
||||
<EmojiHash />
|
||||
</Flex>
|
||||
</Dialog.Body>
|
||||
<Dialog.Actions>
|
||||
<Dialog.Cancel>Stay Connected</Dialog.Cancel>
|
||||
<Dialog.Action variant="danger">Disconnect</Dialog.Action>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
|
@ -1,37 +1,32 @@
|
|||
import React from 'react'
|
||||
|
||||
import { useProfile } from '~/src/protocol/use-profile'
|
||||
import { styled } from '~/src/styles/config'
|
||||
import {
|
||||
Avatar,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
EthAddress,
|
||||
Flex,
|
||||
Text,
|
||||
} from '~/src/system'
|
||||
import { Avatar, DialogTrigger, EthAddress, Flex, Text } from '~/src/system'
|
||||
|
||||
import { DisconnectDialog } from './disconnect-dialog'
|
||||
|
||||
export const UserItem = () => {
|
||||
const profile = useProfile()
|
||||
|
||||
return (
|
||||
<Flex align="center" justify="between">
|
||||
<Flex gap="2" align="center" css={{ height: 56 }}>
|
||||
<Avatar
|
||||
size={32}
|
||||
src="https://images.unsplash.com/photo-1546776310-eef45dd6d63c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1620&q=80"
|
||||
/>
|
||||
<Avatar size={32} src={profile.imageUrl} />
|
||||
<div>
|
||||
<Flex align="center" gap={1}>
|
||||
<Text size="15" color="black-70">
|
||||
Pavel
|
||||
<Text size="15" color="accent">
|
||||
{profile.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
<EthAddress size={10} color="gray">
|
||||
71C7656EC7ab88b098defB751B7401B5f6d8976F
|
||||
{profile.publicKey}
|
||||
</EthAddress>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<DialogTrigger>
|
||||
<LogoutButton>
|
||||
<DisconnectButton>
|
||||
<svg
|
||||
width="20"
|
||||
height="18"
|
||||
|
@ -48,21 +43,14 @@ export const UserItem = () => {
|
|||
fill="#4360DF"
|
||||
/>
|
||||
</svg>
|
||||
</LogoutButton>
|
||||
<Dialog
|
||||
title="Disconnect"
|
||||
cancelLabel="Disconnect"
|
||||
actionLabel="Stay Connected"
|
||||
>
|
||||
<Text>Do you want to disconnect your profile from this browser?</Text>
|
||||
<Avatar size="120" />
|
||||
</Dialog>
|
||||
</DisconnectButton>
|
||||
<DisconnectDialog />
|
||||
</DialogTrigger>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
const LogoutButton = styled('button', {
|
||||
const DisconnectButton = styled('button', {
|
||||
background: 'rgba(67, 96, 223, 0.1)',
|
||||
borderRadius: '50%',
|
||||
height: 32,
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react'
|
||||
|
||||
import { Avatar, Dialog, EmojiHash, Heading, Text } from '~/src/system'
|
||||
|
||||
interface Props {
|
||||
contact: string
|
||||
}
|
||||
|
||||
// TODO: Add all states of contact, wait for desktop release
|
||||
export const UserProfileDialog = (props: Props) => {
|
||||
const { contact, ...dialogProps } = props
|
||||
|
||||
return (
|
||||
<Dialog title={`${contact}'s Profile`} size="640" {...dialogProps}>
|
||||
<Dialog.Body align="center">
|
||||
<Avatar size="80" />
|
||||
<Heading size="22">{contact}</Heading>
|
||||
<Text>Chatkey:0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377</Text>
|
||||
<EmojiHash />
|
||||
</Dialog.Body>
|
||||
<Dialog.Actions>
|
||||
<Dialog.Action variant="danger">Remove Contact</Dialog.Action>
|
||||
<Dialog.Action variant="danger">Mark Untrustworthy</Dialog.Action>
|
||||
<Dialog.Action>Send Contact Request</Dialog.Action>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import { useCommunity } from '~/src/protocol/use-community'
|
||||
import { Avatar, Checkbox, Dialog, Flex, Text } from '~/src/system'
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { name, imageUrl, requestNeeded } = useCommunity()
|
||||
|
||||
const [agreed, setAgreed] = useState(false)
|
||||
|
||||
return (
|
||||
<Dialog title={`Welcome to ${name}`} size={640}>
|
||||
<Dialog.Body gap="4">
|
||||
<Flex justify="center">
|
||||
<Avatar size="64" src={imageUrl} />
|
||||
</Flex>
|
||||
<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>
|
||||
<Checkbox checked={agreed} onChange={setAgreed}>
|
||||
I agree with the above
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
</Dialog.Body>
|
||||
<Dialog.Actions>
|
||||
<Dialog.Action disabled={agreed === false}>
|
||||
{requestNeeded ? 'Request to Join' : `Join ${name}`}
|
||||
</Dialog.Action>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import React, { cloneElement, createContext, useContext, useState } from 'react'
|
||||
|
||||
const DialogContext = createContext<
|
||||
((dialog: React.ReactElement) => void) | null
|
||||
>(null)
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const DialogProvider = (props: Props) => {
|
||||
const { children } = props
|
||||
|
||||
const [dialog, setDialog] = useState<React.ReactElement | null>(null)
|
||||
|
||||
return (
|
||||
<DialogContext.Provider value={setDialog}>
|
||||
{children}
|
||||
{dialog &&
|
||||
cloneElement(dialog, {
|
||||
defaultOpen: true,
|
||||
onOpenChange: () => setDialog(null),
|
||||
})}
|
||||
</DialogContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useDialogContext = () => {
|
||||
const context = useContext(DialogContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useDialogContext must be used within a DialogProvider')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
|
@ -5,6 +5,7 @@ import styled, { ThemeProvider } from 'styled-components'
|
|||
|
||||
import { AppProvider } from '~/src/contexts/app-context'
|
||||
import { ChatStateProvider } from '~/src/contexts/chatStateProvider'
|
||||
import { DialogProvider } from '~/src/contexts/dialog-context'
|
||||
import { IdentityProvider } from '~/src/contexts/identityProvider'
|
||||
import { MessengerProvider } from '~/src/contexts/messengerProvider'
|
||||
import { ModalProvider } from '~/src/contexts/modalProvider'
|
||||
|
@ -32,6 +33,7 @@ export const Community = (props: Props) => {
|
|||
return (
|
||||
<Router>
|
||||
<AppProvider config={props}>
|
||||
<DialogProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
<NarrowProvider myRef={ref}>
|
||||
<ModalProvider>
|
||||
|
@ -56,6 +58,7 @@ export const Community = (props: Props) => {
|
|||
</ModalProvider>
|
||||
</NarrowProvider>
|
||||
</ThemeProvider>
|
||||
</DialogProvider>
|
||||
</AppProvider>
|
||||
</Router>
|
||||
)
|
||||
|
|
|
@ -8,40 +8,6 @@ import { Chat } from '~/src/routes/chat'
|
|||
import { NewChat } from '~/src/routes/new-chat'
|
||||
import { styled } from '~/src/styles/config'
|
||||
|
||||
// import { AgreementModal } from '~/src/components/Modals/AgreementModal'
|
||||
// import { CoinbaseModal } from '~/src/components/Modals/CoinbaseModal'
|
||||
// import { CommunityModal } from '~/src/components/Modals/CommunityModal'
|
||||
// import { EditModal } from '~/src/components/Modals/EditModal'
|
||||
// import { LeavingModal } from '~/src/components/Modals/LeavingModal'
|
||||
// import { LogoutModal } from '~/src/components/Modals/LogoutModal'
|
||||
// import { ProfileFoundModal } from '~/src/components/Modals/ProfileFoundModal'
|
||||
// import { ProfileModal } from '~/src/components/Modals/ProfileModal'
|
||||
// import { StatusModal } from '~/src/components/Modals/StatusModal'
|
||||
// import { UserCreationModal } from '~/src/components/Modals/UserCreationModal'
|
||||
// import { UserCreationStartModal } from '~/src/components/Modals/UserCreationStartModal'
|
||||
// import { WalletConnectModal } from '~/src/components/Modals/WalletConnectModal'
|
||||
// import { WalletModal } from '~/src/components/Modals/WalletModal'
|
||||
|
||||
// function Modals() {
|
||||
// return (
|
||||
// <>
|
||||
// <CommunityModal subtitle="Public Community" />
|
||||
// <UserCreationModal />
|
||||
// <EditModal />
|
||||
// <ProfileModal />
|
||||
// <StatusModal />
|
||||
// <WalletModal />
|
||||
// <WalletConnectModal />
|
||||
// <CoinbaseModal />
|
||||
// <LogoutModal />
|
||||
// <AgreementModal />
|
||||
// <ProfileFoundModal />
|
||||
// <UserCreationStartModal />
|
||||
// <LeavingModal />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
export function Messenger() {
|
||||
const { options } = useAppState()
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react'
|
||||
import React, { cloneElement, useCallback, useRef } from 'react'
|
||||
|
||||
import * as Primitive from '@radix-ui/react-alert-dialog'
|
||||
|
||||
import { useDialogContext } from '~/src/contexts/dialog-context'
|
||||
import { CrossIcon } from '~/src/icons/cross-icon'
|
||||
|
||||
import { Button } from '../button'
|
||||
|
@ -10,19 +11,25 @@ import { IconButton } from '../icon-button'
|
|||
import { Text } from '../text'
|
||||
import { Actions, Body, Content, Header, Overlay } from './styles'
|
||||
|
||||
import type { ButtonProps } from '../button'
|
||||
import type { DialogContentProps } from '@radix-ui/react-dialog'
|
||||
|
||||
interface TriggerProps {
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
children: [React.ReactElement, React.ReactElement]
|
||||
}
|
||||
|
||||
const AlertDialogTrigger = (props: TriggerProps) => {
|
||||
const { children } = props
|
||||
const { children, open, onOpenChange, ...triggerProps } = props
|
||||
|
||||
const [trigger, content] = children
|
||||
|
||||
return (
|
||||
<Primitive.Root>
|
||||
<Primitive.Trigger asChild>{trigger}</Primitive.Trigger>
|
||||
|
||||
<Primitive.Root open={open} onOpenChange={onOpenChange}>
|
||||
<Primitive.Trigger asChild>
|
||||
{cloneElement(trigger, triggerProps)}
|
||||
</Primitive.Trigger>
|
||||
{content}
|
||||
</Primitive.Root>
|
||||
)
|
||||
|
@ -32,16 +39,26 @@ interface DialogProps {
|
|||
title: string
|
||||
description: string
|
||||
actionLabel: string
|
||||
actionVariant?: ButtonProps['variant']
|
||||
cancelLabel?: string
|
||||
onOpenAutoFocus?: DialogContentProps['onOpenAutoFocus']
|
||||
onCloseAutoFocus?: DialogContentProps['onCloseAutoFocus']
|
||||
}
|
||||
|
||||
const AlertDialog = (props: DialogProps) => {
|
||||
const { title, description, cancelLabel = 'Cancel', actionLabel } = props
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
actionLabel,
|
||||
actionVariant,
|
||||
cancelLabel = 'Cancel',
|
||||
...contentProps
|
||||
} = props
|
||||
|
||||
return (
|
||||
<Primitive.Portal>
|
||||
<Overlay as={Primitive.Overlay} />
|
||||
<Content as={Primitive.Content}>
|
||||
<Content as={Primitive.Content} {...contentProps}>
|
||||
<Header>
|
||||
<Heading as={Primitive.Title} weight="600" size="17">
|
||||
{title}
|
||||
|
@ -60,7 +77,7 @@ const AlertDialog = (props: DialogProps) => {
|
|||
<Button>{cancelLabel}</Button>
|
||||
</Primitive.Cancel>
|
||||
<Primitive.Action asChild>
|
||||
<Button>{actionLabel}</Button>
|
||||
<Button variant={actionVariant}>{actionLabel}</Button>
|
||||
</Primitive.Action>
|
||||
</Actions>
|
||||
</Content>
|
||||
|
@ -68,4 +85,23 @@ const AlertDialog = (props: DialogProps) => {
|
|||
)
|
||||
}
|
||||
|
||||
export { AlertDialog, AlertDialogTrigger }
|
||||
const useAlertDialog = (props: DialogProps) => {
|
||||
const render = useDialogContext()
|
||||
const triggerRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const handleCloseAutoFocus = () => {
|
||||
triggerRef.current?.focus()
|
||||
}
|
||||
|
||||
const open = useCallback(() => {
|
||||
render(
|
||||
<Primitive.Root>
|
||||
<AlertDialog {...props} onCloseAutoFocus={handleCloseAutoFocus} />
|
||||
</Primitive.Root>
|
||||
)
|
||||
}, [props, render])
|
||||
|
||||
return { open, triggerRef }
|
||||
}
|
||||
|
||||
export { AlertDialog, AlertDialogTrigger, useAlertDialog }
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
|
||||
import * as Primitive from '@radix-ui/react-dialog'
|
||||
|
||||
import { useDialogContext } from '~/src/contexts/dialog-context'
|
||||
import { CrossIcon } from '~/src/icons/cross-icon'
|
||||
|
||||
import { Button } from '../button'
|
||||
|
@ -12,6 +13,7 @@ import { Actions, Body, Content, Header, Overlay } from './styles'
|
|||
|
||||
import type { ButtonProps } from '../button'
|
||||
import type { Variants } from './styles'
|
||||
import type { DialogContentProps } from '@radix-ui/react-dialog'
|
||||
|
||||
interface DialogTriggerProps {
|
||||
children: [React.ReactElement, React.ReactElement]
|
||||
|
@ -36,6 +38,8 @@ interface DialogProps {
|
|||
title: React.ReactNode
|
||||
children: React.ReactNode
|
||||
size?: Variants['size']
|
||||
onOpenAutoFocus?: DialogContentProps['onOpenAutoFocus']
|
||||
onCloseAutoFocus?: DialogContentProps['onCloseAutoFocus']
|
||||
}
|
||||
|
||||
const Dialog = (props: DialogProps) => {
|
||||
|
@ -79,4 +83,26 @@ Dialog.Cancel = Cancel
|
|||
Dialog.Action = Action
|
||||
Dialog.Separator = Separator
|
||||
|
||||
export { Dialog, DialogTrigger }
|
||||
const useDialog = <Props,>(Component: React.ComponentType<Props>) => {
|
||||
const render = useDialogContext()
|
||||
const triggerRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const handleCloseAutoFocus = () => {
|
||||
triggerRef.current?.focus()
|
||||
}
|
||||
|
||||
const open = useCallback(
|
||||
(props: Props) => {
|
||||
render(
|
||||
<Primitive.Root>
|
||||
<Component {...props} onCloseAutoFocus={handleCloseAutoFocus} />
|
||||
</Primitive.Root>
|
||||
)
|
||||
},
|
||||
[render, Component]
|
||||
)
|
||||
|
||||
return { open, triggerRef }
|
||||
}
|
||||
|
||||
export { Dialog, DialogTrigger, useDialog }
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export { AlertDialog, AlertDialogTrigger } from './alert-dialog'
|
||||
export { Dialog, DialogTrigger } from './dialog'
|
||||
export { AlertDialog, AlertDialogTrigger, useAlertDialog } from './alert-dialog'
|
||||
export { Dialog, DialogTrigger, useDialog } from './dialog'
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { keyframes, styled } from '~/src/styles/config'
|
||||
|
||||
import { Flex } from '../flex'
|
||||
|
||||
import type { VariantProps } from '~/src/styles/config'
|
||||
|
||||
export type Variants = VariantProps<typeof Content>
|
||||
|
@ -76,8 +78,12 @@ export const Header = styled('div', {
|
|||
borderBottom: '1px solid #eee',
|
||||
})
|
||||
|
||||
export const Body = styled('div', {
|
||||
export const Body = styled(Flex, {
|
||||
padding: '16px',
|
||||
|
||||
defaultVariants: {
|
||||
direction: 'column',
|
||||
},
|
||||
})
|
||||
|
||||
export const Actions = styled('div', {
|
||||
|
|
|
@ -10,6 +10,8 @@ export {
|
|||
AlertDialogTrigger,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
useAlertDialog,
|
||||
useDialog,
|
||||
} from './dialog'
|
||||
export { DropdownMenu, DropdownMenuTrigger } from './dropdown-menu'
|
||||
export { EmojiHash } from './emoji-hash'
|
||||
|
|
Loading…
Reference in New Issue