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 { 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 { useDialog } from '~/src/system/dialog/dialog'
|
||||||
|
|
||||||
|
import { UserProfileDialog } from '../user-profile-dialog'
|
||||||
|
import { EditGroupChatDialog } from './edit-group-chat-dialog'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'dropdown' | 'context'
|
type: 'dropdown' | 'context'
|
||||||
|
@ -13,7 +18,24 @@ export const ChatMenu = (props: Props) => {
|
||||||
|
|
||||||
const Menu = type === 'dropdown' ? DropdownMenu : ContextMenu
|
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 = (
|
const commonMenuItems = (
|
||||||
<>
|
<>
|
||||||
<Menu.TriggerItem label="Mute Chat" icon={<BellIcon />}>
|
<Menu.TriggerItem label="Mute Chat" icon={<BellIcon />}>
|
||||||
|
@ -34,36 +56,51 @@ export const ChatMenu = (props: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (chatType === 'channel') {
|
if (chatType === 'channel') {
|
||||||
return commonMenuItems
|
return <Menu>{commonMenuItems}</Menu>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chatType === 'group-chat') {
|
if (chatType === 'group-chat') {
|
||||||
return (
|
return (
|
||||||
<>
|
<Menu>
|
||||||
<Menu.Item icon={<BellIcon />}>Add / remove from group</Menu.Item>
|
<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 />
|
<Menu.Separator />
|
||||||
{commonMenuItems}
|
{commonMenuItems}
|
||||||
<Menu.Separator />
|
<Menu.Separator />
|
||||||
<Menu.Item icon={<BellIcon />} danger>
|
<Menu.Item
|
||||||
|
icon={<BellIcon />}
|
||||||
|
danger
|
||||||
|
onSelect={() => leaveGroupDialog.open()}
|
||||||
|
>
|
||||||
Leave Chat
|
Leave Chat
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Menu>
|
||||||
<Menu.Item icon={<BellIcon />}>View Profile</Menu.Item>
|
<Menu.Item
|
||||||
|
icon={<BellIcon />}
|
||||||
|
onSelect={() => userProfileDialog.open({ contact: 'Satoshi' })}
|
||||||
|
>
|
||||||
|
View Profile
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Separator />
|
<Menu.Separator />
|
||||||
{commonMenuItems}
|
{commonMenuItems}
|
||||||
<Menu.Separator />
|
<Menu.Separator />
|
||||||
<Menu.Item icon={<BellIcon />} danger>
|
<Menu.Item
|
||||||
|
icon={<BellIcon />}
|
||||||
|
danger
|
||||||
|
onSelect={() => deleteChatDialog.open()}
|
||||||
|
>
|
||||||
Delete Chat
|
Delete Chat
|
||||||
</Menu.Item>
|
</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',
|
margin: '16px 0',
|
||||||
height: 1,
|
height: 1,
|
||||||
background: 'rgba(0, 0, 0, 0.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 React from 'react'
|
||||||
|
|
||||||
|
import { useProfile } from '~/src/protocol/use-profile'
|
||||||
import { styled } from '~/src/styles/config'
|
import { styled } from '~/src/styles/config'
|
||||||
import {
|
import { Avatar, DialogTrigger, EthAddress, Flex, Text } from '~/src/system'
|
||||||
Avatar,
|
|
||||||
Dialog,
|
import { DisconnectDialog } from './disconnect-dialog'
|
||||||
DialogTrigger,
|
|
||||||
EthAddress,
|
|
||||||
Flex,
|
|
||||||
Text,
|
|
||||||
} from '~/src/system'
|
|
||||||
|
|
||||||
export const UserItem = () => {
|
export const UserItem = () => {
|
||||||
|
const profile = useProfile()
|
||||||
|
|
||||||
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
|
<Avatar size={32} src={profile.imageUrl} />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
<div>
|
<div>
|
||||||
<Flex align="center" gap={1}>
|
<Flex align="center" gap={1}>
|
||||||
<Text size="15" color="black-70">
|
<Text size="15" color="accent">
|
||||||
Pavel
|
{profile.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<EthAddress size={10} color="gray">
|
<EthAddress size={10} color="gray">
|
||||||
71C7656EC7ab88b098defB751B7401B5f6d8976F
|
{profile.publicKey}
|
||||||
</EthAddress>
|
</EthAddress>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
<LogoutButton>
|
<DisconnectButton>
|
||||||
<svg
|
<svg
|
||||||
width="20"
|
width="20"
|
||||||
height="18"
|
height="18"
|
||||||
|
@ -48,21 +43,14 @@ export const UserItem = () => {
|
||||||
fill="#4360DF"
|
fill="#4360DF"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</LogoutButton>
|
</DisconnectButton>
|
||||||
<Dialog
|
<DisconnectDialog />
|
||||||
title="Disconnect"
|
|
||||||
cancelLabel="Disconnect"
|
|
||||||
actionLabel="Stay Connected"
|
|
||||||
>
|
|
||||||
<Text>Do you want to disconnect your profile from this browser?</Text>
|
|
||||||
<Avatar size="120" />
|
|
||||||
</Dialog>
|
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogoutButton = styled('button', {
|
const DisconnectButton = styled('button', {
|
||||||
background: 'rgba(67, 96, 223, 0.1)',
|
background: 'rgba(67, 96, 223, 0.1)',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
height: 32,
|
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 { AppProvider } from '~/src/contexts/app-context'
|
||||||
import { ChatStateProvider } from '~/src/contexts/chatStateProvider'
|
import { ChatStateProvider } from '~/src/contexts/chatStateProvider'
|
||||||
|
import { DialogProvider } from '~/src/contexts/dialog-context'
|
||||||
import { IdentityProvider } from '~/src/contexts/identityProvider'
|
import { IdentityProvider } from '~/src/contexts/identityProvider'
|
||||||
import { MessengerProvider } from '~/src/contexts/messengerProvider'
|
import { MessengerProvider } from '~/src/contexts/messengerProvider'
|
||||||
import { ModalProvider } from '~/src/contexts/modalProvider'
|
import { ModalProvider } from '~/src/contexts/modalProvider'
|
||||||
|
@ -32,6 +33,7 @@ export const Community = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<AppProvider config={props}>
|
<AppProvider config={props}>
|
||||||
|
<DialogProvider>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<NarrowProvider myRef={ref}>
|
<NarrowProvider myRef={ref}>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
|
@ -56,6 +58,7 @@ export const Community = (props: Props) => {
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</NarrowProvider>
|
</NarrowProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</DialogProvider>
|
||||||
</AppProvider>
|
</AppProvider>
|
||||||
</Router>
|
</Router>
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,40 +8,6 @@ import { Chat } from '~/src/routes/chat'
|
||||||
import { NewChat } from '~/src/routes/new-chat'
|
import { NewChat } from '~/src/routes/new-chat'
|
||||||
import { styled } from '~/src/styles/config'
|
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() {
|
export function Messenger() {
|
||||||
const { options } = useAppState()
|
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 * as Primitive from '@radix-ui/react-alert-dialog'
|
||||||
|
|
||||||
|
import { useDialogContext } from '~/src/contexts/dialog-context'
|
||||||
import { CrossIcon } from '~/src/icons/cross-icon'
|
import { CrossIcon } from '~/src/icons/cross-icon'
|
||||||
|
|
||||||
import { Button } from '../button'
|
import { Button } from '../button'
|
||||||
|
@ -10,19 +11,25 @@ import { IconButton } from '../icon-button'
|
||||||
import { Text } from '../text'
|
import { Text } from '../text'
|
||||||
import { Actions, Body, Content, Header, Overlay } from './styles'
|
import { Actions, Body, Content, Header, Overlay } from './styles'
|
||||||
|
|
||||||
|
import type { ButtonProps } from '../button'
|
||||||
|
import type { DialogContentProps } from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
interface TriggerProps {
|
interface TriggerProps {
|
||||||
|
open?: boolean
|
||||||
|
onOpenChange?: (open: boolean) => void
|
||||||
children: [React.ReactElement, React.ReactElement]
|
children: [React.ReactElement, React.ReactElement]
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlertDialogTrigger = (props: TriggerProps) => {
|
const AlertDialogTrigger = (props: TriggerProps) => {
|
||||||
const { children } = props
|
const { children, open, onOpenChange, ...triggerProps } = props
|
||||||
|
|
||||||
const [trigger, content] = children
|
const [trigger, content] = children
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Primitive.Root>
|
<Primitive.Root open={open} onOpenChange={onOpenChange}>
|
||||||
<Primitive.Trigger asChild>{trigger}</Primitive.Trigger>
|
<Primitive.Trigger asChild>
|
||||||
|
{cloneElement(trigger, triggerProps)}
|
||||||
|
</Primitive.Trigger>
|
||||||
{content}
|
{content}
|
||||||
</Primitive.Root>
|
</Primitive.Root>
|
||||||
)
|
)
|
||||||
|
@ -32,16 +39,26 @@ interface DialogProps {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
actionLabel: string
|
actionLabel: string
|
||||||
|
actionVariant?: ButtonProps['variant']
|
||||||
cancelLabel?: string
|
cancelLabel?: string
|
||||||
|
onOpenAutoFocus?: DialogContentProps['onOpenAutoFocus']
|
||||||
|
onCloseAutoFocus?: DialogContentProps['onCloseAutoFocus']
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlertDialog = (props: DialogProps) => {
|
const AlertDialog = (props: DialogProps) => {
|
||||||
const { title, description, cancelLabel = 'Cancel', actionLabel } = props
|
const {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
actionLabel,
|
||||||
|
actionVariant,
|
||||||
|
cancelLabel = 'Cancel',
|
||||||
|
...contentProps
|
||||||
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Primitive.Portal>
|
<Primitive.Portal>
|
||||||
<Overlay as={Primitive.Overlay} />
|
<Overlay as={Primitive.Overlay} />
|
||||||
<Content as={Primitive.Content}>
|
<Content as={Primitive.Content} {...contentProps}>
|
||||||
<Header>
|
<Header>
|
||||||
<Heading as={Primitive.Title} weight="600" size="17">
|
<Heading as={Primitive.Title} weight="600" size="17">
|
||||||
{title}
|
{title}
|
||||||
|
@ -60,7 +77,7 @@ const AlertDialog = (props: DialogProps) => {
|
||||||
<Button>{cancelLabel}</Button>
|
<Button>{cancelLabel}</Button>
|
||||||
</Primitive.Cancel>
|
</Primitive.Cancel>
|
||||||
<Primitive.Action asChild>
|
<Primitive.Action asChild>
|
||||||
<Button>{actionLabel}</Button>
|
<Button variant={actionVariant}>{actionLabel}</Button>
|
||||||
</Primitive.Action>
|
</Primitive.Action>
|
||||||
</Actions>
|
</Actions>
|
||||||
</Content>
|
</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 * as Primitive from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
|
import { useDialogContext } from '~/src/contexts/dialog-context'
|
||||||
import { CrossIcon } from '~/src/icons/cross-icon'
|
import { CrossIcon } from '~/src/icons/cross-icon'
|
||||||
|
|
||||||
import { Button } from '../button'
|
import { Button } from '../button'
|
||||||
|
@ -12,6 +13,7 @@ import { Actions, Body, Content, Header, Overlay } from './styles'
|
||||||
|
|
||||||
import type { ButtonProps } from '../button'
|
import type { ButtonProps } from '../button'
|
||||||
import type { Variants } from './styles'
|
import type { Variants } from './styles'
|
||||||
|
import type { DialogContentProps } from '@radix-ui/react-dialog'
|
||||||
|
|
||||||
interface DialogTriggerProps {
|
interface DialogTriggerProps {
|
||||||
children: [React.ReactElement, React.ReactElement]
|
children: [React.ReactElement, React.ReactElement]
|
||||||
|
@ -36,6 +38,8 @@ interface DialogProps {
|
||||||
title: React.ReactNode
|
title: React.ReactNode
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
size?: Variants['size']
|
size?: Variants['size']
|
||||||
|
onOpenAutoFocus?: DialogContentProps['onOpenAutoFocus']
|
||||||
|
onCloseAutoFocus?: DialogContentProps['onCloseAutoFocus']
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dialog = (props: DialogProps) => {
|
const Dialog = (props: DialogProps) => {
|
||||||
|
@ -79,4 +83,26 @@ Dialog.Cancel = Cancel
|
||||||
Dialog.Action = Action
|
Dialog.Action = Action
|
||||||
Dialog.Separator = Separator
|
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 { AlertDialog, AlertDialogTrigger, useAlertDialog } from './alert-dialog'
|
||||||
export { Dialog, DialogTrigger } from './dialog'
|
export { Dialog, DialogTrigger, useDialog } from './dialog'
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { keyframes, styled } from '~/src/styles/config'
|
import { keyframes, styled } from '~/src/styles/config'
|
||||||
|
|
||||||
|
import { Flex } from '../flex'
|
||||||
|
|
||||||
import type { VariantProps } from '~/src/styles/config'
|
import type { VariantProps } from '~/src/styles/config'
|
||||||
|
|
||||||
export type Variants = VariantProps<typeof Content>
|
export type Variants = VariantProps<typeof Content>
|
||||||
|
@ -76,8 +78,12 @@ export const Header = styled('div', {
|
||||||
borderBottom: '1px solid #eee',
|
borderBottom: '1px solid #eee',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Body = styled('div', {
|
export const Body = styled(Flex, {
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
|
|
||||||
|
defaultVariants: {
|
||||||
|
direction: 'column',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Actions = styled('div', {
|
export const Actions = styled('div', {
|
||||||
|
|
|
@ -10,6 +10,8 @@ export {
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
|
useAlertDialog,
|
||||||
|
useDialog,
|
||||||
} from './dialog'
|
} from './dialog'
|
||||||
export { DropdownMenu, DropdownMenuTrigger } from './dropdown-menu'
|
export { DropdownMenu, DropdownMenuTrigger } from './dropdown-menu'
|
||||||
export { EmojiHash } from './emoji-hash'
|
export { EmojiHash } from './emoji-hash'
|
||||||
|
|
Loading…
Reference in New Issue