databag/app/client/web/src/settings/Settings.tsx

928 lines
34 KiB
TypeScript

import { useSettings } from './useSettings.hook'
import { Modal, Textarea, TextInput, PasswordInput, ActionIcon, Radio, Group, Select, Switch, Text, PinInput, Image, Button, UnstyledButton } from '@mantine/core'
import classes from './Settings.module.css'
import {
IconLock,
IconUser,
IconClock,
IconIdBadge,
IconCalendar,
IconUsers,
IconVideo,
IconMicrophone,
IconWorld,
IconRestore,
IconBrightness,
IconTicket,
IconCloudLock,
IconBell,
IconEye,
IconBook,
IconMapPin,
IconTrash,
IconCaretDown,
IconCaretRight,
IconLogout,
IconLogin,
IconCopy,
IconCheck,
IconMessage2Cancel,
IconUserCancel,
IconFolderCancel,
} from '@tabler/icons-react'
import { modals } from '@mantine/modals'
import { useDisclosure } from '@mantine/hooks'
import React, { useCallback, useState, useRef } from 'react'
import Cropper from 'react-easy-crop'
import { Area } from 'react-easy-crop/types'
export function Settings({ showLogout }: { showLogout: boolean }) {
const imageFile = useRef(null as null | HTMLInputElement)
const { state, actions } = useSettings()
const [changeOpened, { open: changeOpen, close: changeClose }] = useDisclosure(false)
const [detailsOpened, { open: detailsOpen, close: detailsClose }] = useDisclosure(false)
const [imageOpened, { open: imageOpen, close: imageClose }] = useDisclosure(false)
const [mfaOpened, { open: mfaOpen, close: mfaClose }] = useDisclosure(false)
const [sealOpened, { open: sealOpen, close: sealClose }] = useDisclosure(false)
const [blockedContactOpened, { open: blockedContactOpen, close: blockedContactClose }] = useDisclosure(false)
const [blockedChannelOpened, { open: blockedChannelOpen, close: blockedChannelClose }] = useDisclosure(false)
const [blockedMessageOpened, { open: blockedMessageOpen, close: blockedMessageClose }] = useDisclosure(false)
const [savingLogin, setSavingLogin] = useState(false)
const [savingDetails, setSavingDetails] = useState(false)
const [savingImage, setSavingImage] = useState(false)
const [savingRegistry, setSavingRegistry] = useState(false)
const [savingNotifications, setSavingNotifications] = useState(false)
const [savingSeal, setSavingSeal] = useState(false)
const [savingMfa, setSavingMfa] = useState(false)
const [addingMfa, setAddingMfa] = useState(false)
const [removingMfa, setRemovingMfa] = useState(false)
const [sealDelete, setSealDelete] = useState(false)
const [sealReset, setSealReset] = useState(false)
const [sealConfig, setSealConfig] = useState(false)
const [authMessage, setAuthMessage] = useState('')
const showBlockedCards = async () => {
await actions.loadBlockedCards();
blockedContactOpen();
}
const unblockCard = async (blocked: {cardId: string, timestamp: number}) => {
try {
await actions.unblockCard(blocked.cardId);
} catch (err) {
console.log(err);
showError();
}
}
const blockedCards = state.blockedCards.map((blocked, index) => (
<div key={index} className={classes.blockedItem}>
<Text className={classes.blockedValue}> { actions.getTimestamp(blocked.timestamp) }</Text>
<ActionIcon variant="subtle" size="md" onClick={()=>unblockCard(blocked)}><IconRestore /></ActionIcon>
</div>
));
const showBlockedChannels = async () => {
await actions.loadBlockedChannels();
blockedChannelOpen();
}
const unblockChannel = async (blocked: {cardId: string | null, channelId: string, timestamp: number}) => {
try {
await actions.unblockChannel(blocked.cardId, blocked.channelId);
} catch (err) {
console.log(err);
showError();
}
}
const blockedChannels = state.blockedChannels.map((blocked, index) => (
<div key={index} className={classes.blockedItem}>
<Text className={classes.blockedValue}> { actions.getTimestamp(blocked.timestamp) }</Text>
<ActionIcon variant="subtle" size="md" onClick={()=>unblockChannel(blocked)}><IconRestore /></ActionIcon>
</div>
));
const showBlockedMessages = async () => {
await actions.loadBlockedMessages();
blockedMessageOpen();
}
const unblockMessage = async (blocked: {cardId: string | null, channelId: string, topicId: string, timestamp: number}) => {
try {
await actions.unblockMessage(blocked.cardId, blocked.channelId, blocked.topicId);
} catch (err) {
console.log(err);
showError();
}
}
const blockedMessages = state.blockedMessages.map((blocked, index) => (
<div key={index} className={classes.blockedItem}>
<Text className={classes.blockedValue}> { actions.getTimestamp(blocked.timestamp) }</Text>
<ActionIcon variant="subtle" size="md" onClick={()=>unblockMessage(blocked)}><IconRestore /></ActionIcon>
</div>
));
const logout = () =>
modals.openConfirmModal({
title: state.strings.confirmLogout,
withCloseButton: false,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: <Switch label={state.strings.allDevices} size="md" onChange={(ev) => actions.setAll(ev.currentTarget.checked)} />,
labels: { confirm: state.strings.logout, cancel: state.strings.cancel },
onConfirm: actions.logout,
})
const clearMfa = () => {
modals.openConfirmModal({
title: state.strings.confirmDisable,
withCloseButton: true,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: <Text>{state.strings.disablePrompt}</Text>,
labels: { confirm: state.strings.disable, cancel: state.strings.cancel },
onConfirm: async () => {
if (!removingMfa) {
setRemovingMfa(true)
try {
await actions.disableMFA()
} catch (err) {
console.log(err)
showError()
}
setRemovingMfa(false)
}
},
})
}
const setRegistry = async (checked: boolean) => {
if (!savingRegistry) {
setSavingRegistry(true)
try {
if (checked) {
await actions.enableRegistry()
} else {
await actions.disableRegistry()
}
} catch (err) {
console.log(err)
showError()
}
setSavingRegistry(false)
}
}
const setNotifications = async (checked: boolean) => {
if (!savingNotifications) {
setSavingNotifications(true)
try {
if (checked) {
await actions.enableNotifications()
} else {
await actions.disableNotifications()
}
} catch (err) {
console.log(err)
showError()
}
setSavingNotifications(false)
}
}
const setSeal = async () => {
if (!savingSeal) {
setSealDelete(false)
setSealReset(false)
setSealConfig(false)
actions.setSealPassword('')
actions.setSealConfirm('')
actions.setSealDelete('')
sealOpen()
setSavingSeal(true)
setSavingSeal(false)
}
}
const setMfa = async (checked: boolean) => {
if (!addingMfa) {
setAddingMfa(true)
try {
if (checked) {
actions.setCode('')
setAuthMessage('')
await actions.enableMFA()
mfaOpen()
} else {
clearMfa()
}
} catch (err) {
console.log(err)
showError()
}
setAddingMfa(false)
}
}
const confirmMfa = async () => {
if (!savingMfa) {
setSavingMfa(true)
try {
await actions.confirmMFA()
mfaClose()
} catch (err) {
const { message } = err as { message: string }
if (message === '401') {
setAuthMessage(state.strings.mfaError)
} else if (message === '429') {
setAuthMessage(state.strings.mfaDisabled)
} else {
setAuthMessage(`${state.strings.error}: ${state.strings.tryAgain}`)
}
}
setSavingMfa(false)
}
}
const setDetails = async () => {
if (!savingDetails) {
setSavingDetails(true)
try {
await actions.setDetails()
detailsClose()
} catch (err) {
console.log(err)
showError()
}
setSavingDetails(false)
}
}
const clickSelect = () => {
if (imageFile.current) {
imageFile.current.click()
}
}
const selectImage = (target: HTMLInputElement) => {
if (target.files) {
const reader = new FileReader()
reader.onload = () => {
if (typeof reader.result === 'string') {
actions.setEditImage(reader.result)
}
}
reader.readAsDataURL(target.files[0])
}
}
const setImage = async () => {
if (!savingImage) {
setSavingImage(true)
try {
await actions.setImage()
imageClose()
} catch (err) {
console.log(err)
showError()
}
setSavingImage(false)
}
}
const setLogin = async () => {
if (!savingLogin) {
setSavingLogin(true)
try {
await actions.setLogin()
changeClose()
} catch (err) {
console.log(err)
showError()
}
setSavingLogin(false)
}
}
const sealUnlock = async () => {
if (!savingSeal) {
setSavingSeal(true)
try {
await actions.unlockSeal()
sealClose()
} catch (err) {
console.log(err)
showError()
}
setSavingSeal(false)
}
}
const sealForget = async () => {
if (!savingSeal) {
setSavingSeal(true)
try {
await actions.forgetSeal()
sealClose()
} catch (err) {
console.log(err)
showError()
}
setSavingSeal(false)
}
}
const sealRemove = async () => {
if (!savingSeal) {
setSavingSeal(true)
try {
await actions.clearSeal()
sealClose()
} catch (err) {
console.log(err)
showError()
}
setSavingSeal(false)
}
}
const sealCreate = async () => {
if (!savingSeal) {
setSavingSeal(true)
try {
await new Promise((r) => setTimeout(r, 100))
await actions.setSeal()
sealClose()
} catch (err) {
console.log(err)
showError()
}
setSavingSeal(false)
}
}
const sealUpdate = async () => {
if (!savingSeal) {
setSavingSeal(true)
try {
await actions.updateSeal()
sealClose()
} catch (err) {
console.log(err)
showError()
}
setSavingSeal(false)
}
}
const showError = () => {
modals.openConfirmModal({
title: state.strings.operationFailed,
withCloseButton: true,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: <Text>{state.strings.tryAgain}</Text>,
cancelProps: { display: 'none' },
confirmProps: { display: 'none' },
})
}
const onCropComplete = useCallback((crop: Area) => {
actions.setEditImageCrop(crop)
}, [])
return (
<>
{state.profileSet && (
<div className={classes.settings}>
<div className={classes.header}>
<Text className={classes.headerLabel}>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text>
</div>
<div className={classes.detail}>
<div className={classes.image}>
{state.profile.imageSet && (
<div className={classes.imageSet}>
<Image radius="md" src={state.imageUrl} />
<div className={classes.edit}>
<UnstyledButton className={classes.imageEdit} size="compact-md" onClick={imageOpen}>
<span className={classes.editLabel}>{state.strings.edit}</span>
</UnstyledButton>
</div>
</div>
)}
{!state.profile.imageSet && (
<div className={classes.imageUnset} onClick={imageOpen}>
<Image radius="md" src={state.imageUrl} />
<Text className={classes.unsetEdit}>{state.strings.edit}</Text>
</div>
)}
</div>
<div className={classes.section}>
<div className={classes.divider} />
<UnstyledButton className={classes.sectionEdit} onClick={detailsOpen}>
{state.strings.edit}
</UnstyledButton>
</div>
{!state.profile.name && <Text className={classes.nameUnset}>{state.strings.name}</Text>}
{state.profile.name && <Text className={classes.nameSet}>{state.profile.name}</Text>}
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconMapPin />
</div>
{!state.profile.location && <Text className={classes.entryUnset}>{state.strings.location}</Text>}
{state.profile.location && <Text className={classes.entrySet}>{state.profile.location}</Text>}
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconBook />
</div>
{!state.profile.description && <Text className={classes.entryUnset}>{state.strings.description}</Text>}
{state.profile.description && <Text className={classes.entrySet}>{state.profile.description}</Text>}
</div>
<div className={classes.divider} />
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconEye />
</div>
<Text className={classes.entryLabel}>{state.strings.registry}</Text>
<Switch className={classes.entryControl} checked={state.config.searchable} onChange={(ev) => setRegistry(ev.currentTarget.checked)} />
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconCloudLock />
</div>
<Text className={classes.entryLabel} onClick={setSeal}>
{state.strings.manageTopics}
</Text>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconBell />
</div>
<Text className={classes.entryLabel}>{state.strings.enableNotifications}</Text>
<Switch className={classes.entryControl} checked={state.config.pushEnabled} onChange={(ev) => setNotifications(ev.currentTarget.checked)} />
</div>
<div className={classes.divider} />
{showLogout && (
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconLogout />
</div>
<Text className={classes.entryLabel} onClick={logout}>
{state.strings.logout}
</Text>
</div>
)}
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconTicket />
</div>
<Text className={classes.entryLabel}>{state.strings.mfaTitle}</Text>
<Switch className={classes.entryControl} checked={state.config.mfaEnabled} onChange={(ev) => setMfa(ev.currentTarget.checked)} />
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconLogin />
</div>
<Text className={classes.entryLabel} onClick={changeOpen}>
{state.strings.changeLogin}
</Text>
</div>
<div className={classes.divider} />
<div className={classes.entry} onClick={showBlockedCards}>
<div className={classes.entryIcon}>
<IconUserCancel />
</div>
<Text className={classes.entryLabel}>{state.strings.blockedContacts}</Text>
</div>
<div className={classes.entry} onClick={showBlockedChannels}>
<div className={classes.entryIcon}>
<IconFolderCancel />
</div>
<Text className={classes.entryLabel}>{state.strings.blockedTopics}</Text>
</div>
<div className={classes.entry} onClick={showBlockedMessages}>
<div className={classes.entryIcon}>
<IconMessage2Cancel />
</div>
<Text className={classes.entryLabel}>{state.strings.blockedMessages}</Text>
</div>
<div className={classes.divider} />
<div className={classes.selects}>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconClock />
</div>
<Text className={classes.controlLabel}>{state.strings.timeFormat}</Text>
<Radio.Group name="timeFormat" className={classes.radio} value={state.timeFormat} onChange={actions.setTimeFormat}>
<Group mt="xs">
<Radio value="12h" label={state.strings.timeUs} />
<Radio value="24h" label={state.strings.timeEu} />
</Group>
</Radio.Group>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconCalendar />
</div>
<Text className={classes.controlLabel}>{state.strings.dateFormat}</Text>
<Radio.Group name="dateFormat" className={classes.radio} value={state.dateFormat} onChange={actions.setDateFormat}>
<Group mt="xs">
<Radio value="mm/dd" label={state.strings.dateUs} />
<Radio value="dd/mm" label={state.strings.dateEu} />
</Group>
</Radio.Group>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconBrightness />
</div>
<Text className={classes.controlLabel}>{state.strings.theme}</Text>
<Select className={classes.entryControl} size="xs" data={state.themes} value={state.scheme} onChange={(theme) => actions.setTheme(theme as string)} />
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconWorld />
</div>
<Text className={classes.controlLabel}>{state.strings.language}</Text>
<Select className={classes.entryControl} size="xs" data={state.languages} value={state.language} onChange={(language) => actions.setLanguage(language as string)} />
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconMicrophone />
</div>
<Text className={classes.controlLabel}>{state.strings.microphone}</Text>
<Select
className={classes.entryControl}
size="xs"
data={[{ value: '', label: state.strings.default }, ...state.audioInputs]}
value={state.audioId ? state.audioId : ''}
onChange={actions.setAudio}
/>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconVideo />
</div>
<Text className={classes.controlLabel}>{state.strings.camera}</Text>
<Select
className={classes.entryControl}
size="xs"
data={[{ value: '', label: state.strings.default }, ...state.videoInputs]}
value={state.videoId ? state.videoId : ''}
onChange={actions.setVideo}
/>
</div>
</div>
</div>
</div>
)}
<Modal title={state.strings.changeLogin} opened={changeOpened} onClose={changeClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
<div className={classes.change}>
<TextInput
className={classes.input}
size="md"
value={state.handle}
leftSectionPointerEvents="none"
leftSection={<IconUser />}
rightSection={state.taken ? <IconUsers /> : null}
placeholder={state.strings.username}
onChange={(event) => actions.setHandle(event.currentTarget.value)}
error={state.taken}
/>
<PasswordInput
className={classes.input}
size="md"
value={state.password}
leftSection={<IconLock />}
placeholder={state.strings.password}
onChange={(event) => actions.setPassword(event.currentTarget.value)}
/>
<PasswordInput
className={classes.input}
size="md"
value={state.confirm}
leftSection={<IconLock />}
placeholder={state.strings.confirmPassword}
onChange={(event) => actions.setConfirm(event.currentTarget.value)}
/>
<div className={classes.control}>
<Button variant="default" onClick={changeClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={setLogin} loading={savingLogin} disabled={state.taken || !state.checked || !state.handle || !state.password || state.confirm !== state.password}>
{state.strings.save}
</Button>
</div>
</div>
</Modal>
<Modal title={state.strings.profileDetails} opened={detailsOpened} onClose={detailsClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
<div className={classes.change}>
<TextInput
className={classes.input}
size="md"
value={state.name}
leftSectionPointerEvents="none"
leftSection={<IconIdBadge />}
placeholder={state.strings.name}
onChange={(event) => actions.setName(event.currentTarget.value)}
/>
<TextInput
className={classes.input}
size="md"
value={state.location}
leftSectionPointerEvents="none"
leftSection={<IconMapPin />}
placeholder={state.strings.location}
onChange={(event) => actions.setLocation(event.currentTarget.value)}
/>
<Textarea
className={classes.input}
size="md"
minRows={1}
maxRows={4}
autosize={true}
value={state.description}
leftSectionPointerEvents="none"
leftSection={<IconBook />}
placeholder={state.strings.description}
onChange={(event) => actions.setDescription(event.currentTarget.value)}
/>
<div className={classes.control}>
<Button variant="default" onClick={detailsClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={setDetails} loading={savingDetails}>
{state.strings.save}
</Button>
</div>
</div>
</Modal>
<Modal title={state.strings.profileImage} opened={imageOpened} onClose={imageClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
<div className={classes.change}>
<div className={classes.cropper}>
<Cropper
image={state.editImage}
crop={state.crop}
zoom={state.zoom}
aspect={1}
onCropChange={actions.setCrop}
onCropComplete={(area, crop) => onCropComplete(crop)}
onZoomChange={actions.setZoom}
/>
</div>
<div className={classes.imageSelect}>
<input type="file" id="file" accept="image/*" ref={imageFile} onChange={(e) => selectImage(e.target)} style={{ display: 'none' }} />
<Button variant="default" className={classes.select} onClick={clickSelect}>
{state.strings.selectImage}
</Button>
<div className={classes.control}>
<Button variant="default" onClick={imageClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={setImage} loading={savingImage}>
{state.strings.save}
</Button>
</div>
</div>
</div>
</Modal>
<Modal title={state.strings.mfaTitle} opened={mfaOpened} onClose={mfaClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
<div className={classes.mfa}>
<div className={classes.secret}>
<Text>{state.strings.mfaSteps}</Text>
<Image radius="md" className={classes.secretImage} src={state.secretImage} />
<div className={classes.secretText}>
<Text>{state.secretText}</Text>
{state.secretCopied && <IconCheck />}
{!state.secretCopied && <IconCopy className={classes.copyIcon} onClick={actions.copySecret} />}
</div>
<PinInput value={state.code} length={6} className={classes.mfaPin} onChange={(event) => actions.setCode(event)} />
<Text className={classes.authMessage}>{authMessage}</Text>
</div>
<div className={classes.control}>
<Button variant="default" onClick={mfaClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={confirmMfa} disabled={state.code.length != 6} loading={savingMfa}>
{state.strings.save}
</Button>
</div>
</div>
</Modal>
<Modal title={state.strings.sealedTopics} opened={sealOpened} onClose={sealClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} size="lg" centered>
<>
{!sealDelete && !sealReset && state.config.sealSet && state.config.sealUnlocked && (
<div className={classes.seal}>
<span>{state.strings.sealForget}</span>
<div className={classes.buttons}>
{!sealConfig && <IconCaretDown className={classes.sealConfig} onClick={() => setSealConfig(true)} />}
{sealConfig && <IconCaretRight className={classes.sealConfig} onClick={() => setSealConfig(false)} />}
{sealConfig && (
<Button className={classes.delete} onClick={() => setSealDelete(true)}>
{state.strings.remove}
</Button>
)}
{sealConfig && (
<Button variant="filled" onClick={() => setSealReset(true)}>
{state.strings.resave}
</Button>
)}
<div className={classes.controls}>
<Button variant="default" onClick={sealClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={sealForget} loading={savingSeal}>
{state.strings.forget}
</Button>
</div>
</div>
</div>
)}
{!sealDelete && sealReset && state.config.sealSet && state.config.sealUnlocked && (
<div className={classes.seal}>
<span>{state.strings.sealUpdate}</span>
<TextInput
className={classes.input}
size="md"
value={state.sealPassword}
leftSectionPointerEvents="none"
leftSection={<IconLock />}
placeholder={state.strings.password}
onChange={(event) => actions.setSealPassword(event.currentTarget.value)}
/>
<TextInput
className={classes.input}
size="md"
value={state.sealConfirm}
leftSectionPointerEvents="none"
leftSection={<IconLock />}
placeholder={state.strings.confirmPassword}
onChange={(event) => actions.setSealConfirm(event.currentTarget.value)}
/>
<div className={classes.controls}>
<Button variant="default" onClick={sealClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={sealUpdate} loading={savingSeal} disabled={state.sealPassword === '' || state.sealPassword !== state.sealConfirm}>
{state.strings.save}
</Button>
</div>
</div>
)}
{!sealDelete && state.config.sealSet && !state.config.sealUnlocked && (
<div className={classes.seal}>
<span>{state.strings.sealUnlock}</span>
<PasswordInput
className={classes.input}
size="md"
value={state.sealPassword}
leftSection={<IconLock />}
placeholder={state.strings.password}
onChange={(event) => actions.setSealPassword(event.currentTarget.value)}
/>
<div className={classes.buttons}>
{!sealConfig && <IconCaretDown className={classes.sealConfig} onClick={() => setSealConfig(true)} />}
{sealConfig && <IconCaretRight className={classes.sealConfig} onClick={() => setSealConfig(false)} />}
{sealConfig && (
<Button className={classes.delete} onClick={() => setSealDelete(true)}>
{state.strings.remove}
</Button>
)}
<div className={classes.controls}>
<Button variant="default" onClick={sealClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={sealUnlock} disabled={state.sealPassword === ''} loading={savingSeal}>
{state.strings.unlock}
</Button>
</div>
</div>
</div>
)}
{sealDelete && state.config.sealSet && (
<div className={classes.seal}>
<span>{state.strings.sealDelete}</span>
<TextInput
className={classes.input}
size="md"
value={state.sealDelete}
leftSectionPointerEvents="none"
leftSection={<IconTrash />}
placeholder={state.strings.deleteKey}
onChange={(event) => actions.setSealDelete(event.currentTarget.value)}
/>
<div className={classes.controls}>
<Button variant="default" onClick={sealClose}>
{state.strings.cancel}
</Button>
<Button
variant="filled"
className={state.sealDelete === state.strings.delete ? classes.delete : ''}
onClick={sealRemove}
disabled={state.sealDelete !== state.strings.delete}
loading={savingSeal}
>
{state.strings.remove}
</Button>
</div>
</div>
)}
{!state.config.sealSet && (
<div className={classes.seal}>
<span>{state.strings.sealCreate}</span>
<TextInput
className={classes.input}
size="md"
value={state.sealPassword}
leftSectionPointerEvents="none"
leftSection={<IconLock />}
placeholder={state.strings.password}
onChange={(event) => actions.setSealPassword(event.currentTarget.value)}
/>
<TextInput
className={classes.input}
size="md"
value={state.sealConfirm}
leftSectionPointerEvents="none"
leftSection={<IconLock />}
placeholder={state.strings.confirmPassword}
onChange={(event) => actions.setSealConfirm(event.currentTarget.value)}
/>
<div className={classes.controls}>
<Button variant="default" onClick={sealClose}>
{state.strings.cancel}
</Button>
<Button variant="filled" onClick={sealCreate} disabled={state.sealPassword == '' || state.sealPassword !== state.sealConfirm} loading={savingSeal}>
{state.strings.save}
</Button>
</div>
</div>
)}
</>
</Modal>
<Modal title={state.strings.blockedContacts} size="auto" opened={blockedContactOpened} onClose={blockedContactClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
{ blockedCards.length > 0 && (
<div className={classes.blocked}>
{ blockedCards }
</div>
)}
{ blockedCards.length === 0 && (
<div className={classes.empty}>
<Text className={classes.emptyLabel}>{ state.strings.noContacts }</Text>
</div>
)}
<div className={classes.controls}>
<Button variant="default" onClick={blockedContactClose}>
{state.strings.close}
</Button>
</div>
</Modal>
<Modal title={state.strings.blockedTopics} opened={blockedChannelOpened} onClose={blockedChannelClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
{ blockedChannels.length > 0 && (
<div className={classes.blocked}>
{ blockedChannels }
</div>
)}
{ blockedChannels.length === 0 && (
<div className={classes.empty}>
<Text className={classes.emptyLabel}>{ state.strings.noTopics }</Text>
</div>
)}
<div className={classes.controls}>
<Button variant="default" onClick={blockedChannelClose}>
{state.strings.close}
</Button>
</div>
</Modal>
<Modal title={state.strings.blockedMessages} opened={blockedMessageOpened} onClose={blockedMessageClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
{ blockedMessages.length > 0 && (
<div className={classes.blocked}>
{ blockedMessages }
</div>
)}
{ blockedMessages.length === 0 && (
<div className={classes.empty}>
<Text className={classes.emptyLabel}>{ state.strings.noMessages }</Text>
</div>
)}
<div className={classes.controls}>
<Button variant="default" onClick={blockedMessageClose}>
{state.strings.close}
</Button>
</div>
</Modal>
</>
)
}