Replace confirm and prompt with custom dialogs

This commit is contained in:
Ajay Bura 2022-04-25 20:12:06 +05:30
parent 9a22b25564
commit 1ce6cfd487
14 changed files with 232 additions and 50 deletions

View file

@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ConfirmDialog.scss';
import { openReusableDialog } from '../../../client/action/navigation';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
function ConfirmDialog({
desc, actionTitle, actionType, onComplete,
}) {
return (
<div className="confirm-dialog">
<Text>{desc}</Text>
<div className="confirm-dialog__btn">
<Button variant={actionType} onClick={() => onComplete(true)}>{actionTitle}</Button>
<Button onClick={() => onComplete(false)}>Cancel</Button>
</div>
</div>
);
}
ConfirmDialog.propTypes = {
desc: PropTypes.string.isRequired,
actionTitle: PropTypes.string.isRequired,
actionType: PropTypes.oneOf(['primary', 'positive', 'danger', 'caution']).isRequired,
onComplete: PropTypes.func.isRequired,
};
/**
* @param {string} title title of confirm dialog
* @param {string} desc description of confirm dialog
* @param {string} actionTitle title of main action to take
* @param {'primary' | 'positive' | 'danger' | 'caution'} actionType type of action. default=primary
* @return {Promise<boolean>} does it get's confirmed or not
*/
// eslint-disable-next-line import/prefer-default-export
export const confirmDialog = (title, desc, actionTitle, actionType = 'primary') => new Promise((resolve) => {
let isCompleted = false;
openReusableDialog(
<Text variant="s1" weight="medium">{title}</Text>,
(requestClose) => (
<ConfirmDialog
desc={desc}
actionTitle={actionTitle}
actionType={actionType}
onComplete={(isConfirmed) => {
isCompleted = true;
resolve(isConfirmed);
requestClose();
}}
/>
),
() => {
if (!isCompleted) resolve(false);
},
);
});

View file

@ -0,0 +1,11 @@
.confirm-dialog {
padding: var(--sp-normal);
& > .text {
padding-bottom: var(--sp-normal);
}
&__btn {
display: flex;
gap: var(--sp-normal);
}
}

View file

@ -36,6 +36,8 @@ import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
import CmdIC from '../../../../public/res/ic/outlined/cmd.svg'; import CmdIC from '../../../../public/res/ic/outlined/cmd.svg';
import BinIC from '../../../../public/res/ic/outlined/bin.svg'; import BinIC from '../../../../public/res/ic/outlined/bin.svg';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
function PlaceholderMessage() { function PlaceholderMessage() {
return ( return (
<div className="ph-msg"> <div className="ph-msg">
@ -546,10 +548,15 @@ const MessageOptions = React.memo(({
<MenuItem <MenuItem
variant="danger" variant="danger"
iconSrc={BinIC} iconSrc={BinIC}
onClick={() => { onClick={async () => {
if (window.confirm('Are you sure that you want to delete this event?')) { const isConfirmed = await confirmDialog(
redactEvent(roomId, mEvent.getId()); 'Delete message',
} 'Are you sure that you want to delete this message?',
'Delete',
'danger',
);
if (!isConfirmed) return;
redactEvent(roomId, mEvent.getId());
}} }}
> >
Delete Delete

View file

@ -8,6 +8,8 @@ import Text from '../../atoms/text/Text';
import Toggle from '../../atoms/button/Toggle'; import Toggle from '../../atoms/button/Toggle';
import SettingTile from '../setting-tile/SettingTile'; import SettingTile from '../setting-tile/SettingTile';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
function RoomEncryption({ roomId }) { function RoomEncryption({ roomId }) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
@ -15,17 +17,20 @@ function RoomEncryption({ roomId }) {
const [isEncrypted, setIsEncrypted] = useState(encryptionEvents.length > 0); const [isEncrypted, setIsEncrypted] = useState(encryptionEvents.length > 0);
const canEnableEncryption = room.currentState.maySendStateEvent('m.room.encryption', mx.getUserId()); const canEnableEncryption = room.currentState.maySendStateEvent('m.room.encryption', mx.getUserId());
const handleEncryptionEnable = () => { const handleEncryptionEnable = async () => {
const joinRule = room.getJoinRule(); const joinRule = room.getJoinRule();
const confirmMsg1 = 'It is not recommended to add encryption in public room. Anyone can find and join public rooms, so anyone can read messages in them.'; const confirmMsg1 = 'It is not recommended to add encryption in public room. Anyone can find and join public rooms, so anyone can read messages in them.';
const confirmMsg2 = 'Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly'; const confirmMsg2 = 'Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly';
if (joinRule === 'public' ? confirm(confirmMsg1) : true) {
if (confirm(confirmMsg2)) { const isConfirmed1 = (joinRule === 'public')
setIsEncrypted(true); ? await confirmDialog('Enable encryption', confirmMsg1, 'Continue', 'caution')
mx.sendStateEvent(roomId, 'm.room.encryption', { : true;
algorithm: 'm.megolm.v1.aes-sha2', if (!isConfirmed1) return;
}); if (await confirmDialog('Enable encryption', confirmMsg2, 'Enable', 'caution')) {
} setIsEncrypted(true);
mx.sendStateEvent(roomId, 'm.room.encryption', {
algorithm: 'm.megolm.v1.aes-sha2',
});
} }
}; };

View file

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { twemojify } from '../../../util/twemojify'; import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import { openInviteUser, openNavigation } from '../../../client/action/navigation'; import { openInviteUser } from '../../../client/action/navigation';
import * as roomActions from '../../../client/action/room'; import * as roomActions from '../../../client/action/room';
import { markAsRead } from '../../../client/action/notifications'; import { markAsRead } from '../../../client/action/notifications';
@ -15,6 +15,8 @@ import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg'; import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg';
import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg'; import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
function RoomOptions({ roomId, afterOptionSelect }) { function RoomOptions({ roomId, afterOptionSelect }) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
@ -29,12 +31,16 @@ function RoomOptions({ roomId, afterOptionSelect }) {
openInviteUser(roomId); openInviteUser(roomId);
afterOptionSelect(); afterOptionSelect();
}; };
const handleLeaveClick = () => { const handleLeaveClick = async () => {
if (confirm('Are you sure that you want to leave this room?')) { afterOptionSelect();
roomActions.leave(roomId); const isConfirmed = await confirmDialog(
afterOptionSelect(); 'Leave room',
openNavigation(); `Are you sure that you want to leave "${room.name}" room?`,
} 'Leave',
'danger',
);
if (!isConfirmed) return;
roomActions.leave(roomId);
}; };
return ( return (

View file

@ -19,6 +19,7 @@ import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useForceUpdate } from '../../hooks/useForceUpdate'; import { useForceUpdate } from '../../hooks/useForceUpdate';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
function RoomProfile({ roomId }) { function RoomProfile({ roomId }) {
const isMountStore = useStore(); const isMountStore = useStore();
@ -117,7 +118,13 @@ function RoomProfile({ roomId }) {
const handleAvatarUpload = async (url) => { const handleAvatarUpload = async (url) => {
if (url === null) { if (url === null) {
if (confirm('Are you sure that you want to remove room avatar?')) { const isConfirmed = await confirmDialog(
'Remove avatar',
'Are you sure that you want to remove room avatar?',
'Remove',
'caution',
);
if (isConfirmed) {
await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, ''); await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');
} }
} else await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, ''); } else await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');

View file

@ -24,6 +24,8 @@ import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
import PinIC from '../../../../public/res/ic/outlined/pin.svg'; import PinIC from '../../../../public/res/ic/outlined/pin.svg';
import PinFilledIC from '../../../../public/res/ic/filled/pin.svg'; import PinFilledIC from '../../../../public/res/ic/filled/pin.svg';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
function SpaceOptions({ roomId, afterOptionSelect }) { function SpaceOptions({ roomId, afterOptionSelect }) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
@ -54,11 +56,16 @@ function SpaceOptions({ roomId, afterOptionSelect }) {
afterOptionSelect(); afterOptionSelect();
}; };
const handleLeaveClick = () => { const handleLeaveClick = async () => {
if (confirm('Are you sure that you want to leave this space?')) { afterOptionSelect();
leave(roomId); const isConfirmed = await confirmDialog(
afterOptionSelect(); 'Leave space',
} `Are you sure that you want to leave "${room.name}" space?`,
'Leave',
'danger',
);
if (!isConfirmed) return;
leave(roomId);
}; };
return ( return (

View file

@ -12,6 +12,7 @@ import ImageUpload from '../../molecules/image-upload/ImageUpload';
import Input from '../../atoms/input/Input'; import Input from '../../atoms/input/Input';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg'; import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import './ProfileEditor.scss'; import './ProfileEditor.scss';
@ -38,9 +39,15 @@ function ProfileEditor({ userId }) {
}; };
}, [userId]); }, [userId]);
const handleAvatarUpload = (url) => { const handleAvatarUpload = async (url) => {
if (url === null) { if (url === null) {
if (confirm('Are you sure that you want to remove avatar?')) { const isConfirmed = await confirmDialog(
'Remove avatar',
'Are you sure that you want to remove avatar?',
'Remove',
'caution',
);
if (isConfirmed) {
mx.setAvatarUrl(''); mx.setAvatarUrl('');
setAvatarSrc(null); setAvatarSrc(null);
} }

View file

@ -32,6 +32,7 @@ import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.s
import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useForceUpdate } from '../../hooks/useForceUpdate'; import { useForceUpdate } from '../../hooks/useForceUpdate';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
function ModerationTools({ function ModerationTools({
roomId, userId, roomId, userId,
@ -362,7 +363,7 @@ function ProfileViewer() {
&& (powerLevel < myPowerLevel || userId === mx.getUserId()) && (powerLevel < myPowerLevel || userId === mx.getUserId())
); );
const handleChangePowerLevel = (newPowerLevel) => { const handleChangePowerLevel = async (newPowerLevel) => {
if (newPowerLevel === powerLevel) return; if (newPowerLevel === powerLevel) return;
const SHARED_POWER_MSG = 'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?'; const SHARED_POWER_MSG = 'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?';
const DEMOTING_MYSELF_MSG = 'You will not be able to undo this change as you are demoting yourself. Are you sure?'; const DEMOTING_MYSELF_MSG = 'You will not be able to undo this change as you are demoting yourself. Are you sure?';
@ -370,9 +371,14 @@ function ProfileViewer() {
const isSharedPower = newPowerLevel === myPowerLevel; const isSharedPower = newPowerLevel === myPowerLevel;
const isDemotingMyself = userId === mx.getUserId(); const isDemotingMyself = userId === mx.getUserId();
if (isSharedPower || isDemotingMyself) { if (isSharedPower || isDemotingMyself) {
if (confirm(isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG)) { const isConfirmed = await confirmDialog(
roomActions.setPowerLevel(roomId, userId, newPowerLevel); 'Change power level',
} isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG,
'Change',
'caution',
);
if (!isConfirmed) return;
roomActions.setPowerLevel(roomId, userId, newPowerLevel);
} else { } else {
roomActions.setPowerLevel(roomId, userId, newPowerLevel); roomActions.setPowerLevel(roomId, userId, newPowerLevel);
} }

View file

@ -36,6 +36,7 @@ import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
import ChevronTopIC from '../../../../public/res/ic/outlined/chevron-top.svg'; import ChevronTopIC from '../../../../public/res/ic/outlined/chevron-top.svg';
import { useForceUpdate } from '../../hooks/useForceUpdate'; import { useForceUpdate } from '../../hooks/useForceUpdate';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
const tabText = { const tabText = {
GENERAL: 'General', GENERAL: 'General',
@ -85,10 +86,15 @@ function GeneralSettings({ roomId }) {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
variant="danger" variant="danger"
onClick={() => { onClick={async () => {
if (confirm('Are you sure that you want to leave this room?')) { const isConfirmed = await confirmDialog(
roomActions.leave(roomId); 'Leave room',
} `Are you sure that you want to leave "${room.name}" room?`,
'Leave',
'danger',
);
if (!isConfirmed) return;
roomActions.leave(roomId);
}} }}
iconSrc={LeaveArrowIC} iconSrc={LeaveArrowIC}
> >

View file

@ -4,10 +4,12 @@ import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import { isCrossVerified } from '../../../util/matrixUtil'; import { isCrossVerified } from '../../../util/matrixUtil';
import { openReusableDialog } from '../../../client/action/navigation';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button'; import Button from '../../atoms/button/Button';
import IconButton from '../../atoms/button/IconButton'; import IconButton from '../../atoms/button/IconButton';
import Input from '../../atoms/input/Input';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import InfoCard from '../../atoms/card/InfoCard'; import InfoCard from '../../atoms/card/InfoCard';
import Spinner from '../../atoms/spinner/Spinner'; import Spinner from '../../atoms/spinner/Spinner';
@ -18,11 +20,46 @@ import BinIC from '../../../../public/res/ic/outlined/bin.svg';
import InfoIC from '../../../../public/res/ic/outlined/info.svg'; import InfoIC from '../../../../public/res/ic/outlined/info.svg';
import { authRequest } from './AuthRequest'; import { authRequest } from './AuthRequest';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useDeviceList } from '../../hooks/useDeviceList'; import { useDeviceList } from '../../hooks/useDeviceList';
import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
const promptDeviceName = async (deviceName) => new Promise((resolve) => {
let isCompleted = false;
const renderContent = (onComplete) => {
const handleSubmit = (e) => {
e.preventDefault();
const name = e.target.session.value;
if (typeof name !== 'string') onComplete(null);
onComplete(name);
};
return (
<form className="device-manage__rename" onSubmit={handleSubmit}>
<Input value={deviceName} label="Session name" name="session" />
<div className="device-manage__rename-btn">
<Button variant="primary" type="submit">Save</Button>
<Button onClick={() => onComplete(null)}>Cancel</Button>
</div>
</form>
);
};
openReusableDialog(
<Text variant="s1" weight="medium">Edit session name</Text>,
(requestClose) => renderContent((name) => {
isCompleted = true;
resolve(name);
requestClose();
}),
() => {
if (!isCompleted) resolve(null);
},
);
});
function DeviceManage() { function DeviceManage() {
const TRUNCATED_COUNT = 4; const TRUNCATED_COUNT = 4;
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
@ -59,7 +96,7 @@ function DeviceManage() {
} }
const handleRename = async (device) => { const handleRename = async (device) => {
const newName = window.prompt('Edit session name', device.display_name); const newName = await promptDeviceName(device.display_name);
if (newName === null || newName.trim() === '') return; if (newName === null || newName.trim() === '') return;
if (newName.trim() === device.display_name) return; if (newName.trim() === device.display_name) return;
addToProcessing(device); addToProcessing(device);
@ -74,15 +111,20 @@ function DeviceManage() {
}; };
const handleRemove = async (device) => { const handleRemove = async (device) => {
if (window.confirm(`You are about to logout "${device.display_name}" session.`)) { const isConfirmed = await confirmDialog(
addToProcessing(device); `Logout ${device.display_name}`,
await authRequest(`Logout "${device.display_name}"`, async (auth) => { `You are about to logout "${device.display_name}" session.`,
await mx.deleteDevice(device.device_id, auth); 'Logout',
}); 'danger',
);
if (!isConfirmed) return;
addToProcessing(device);
await authRequest(`Logout "${device.display_name}"`, async (auth) => {
await mx.deleteDevice(device.device_id, auth);
});
if (!mountStore.getItem()) return; if (!mountStore.getItem()) return;
removeFromProcessing(device); removeFromProcessing(device);
}
}; };
const renderDevice = (device, isVerified) => { const renderDevice = (device, isVerified) => {

View file

@ -15,4 +15,15 @@
& .setting-tile:last-of-type { & .setting-tile:last-of-type {
border-bottom: none; border-bottom: none;
} }
&__rename {
padding: var(--sp-normal);
& > *:not(:last-child) {
margin-bottom: var(--sp-normal);
}
&-btn {
display: flex;
gap: var(--sp-normal);
}
}
} }

View file

@ -38,6 +38,7 @@ import PowerIC from '../../../../public/res/ic/outlined/power.svg';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import CinnySVG from '../../../../public/res/svg/cinny.svg'; import CinnySVG from '../../../../public/res/svg/cinny.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
function AppearanceSection() { function AppearanceSection() {
const [, updateState] = useState({}); const [, updateState] = useState({});
@ -297,8 +298,10 @@ function Settings() {
const [isOpen, requestClose] = useWindowToggle(setSelectedTab); const [isOpen, requestClose] = useWindowToggle(setSelectedTab);
const handleTabChange = (tabItem) => setSelectedTab(tabItem); const handleTabChange = (tabItem) => setSelectedTab(tabItem);
const handleLogout = () => { const handleLogout = async () => {
if (confirm('Confirm logout')) logout(); if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) {
logout();
}
}; };
return ( return (

View file

@ -36,6 +36,7 @@ import PinFilledIC from '../../../../public/res/ic/filled/pin.svg';
import CategoryIC from '../../../../public/res/ic/outlined/category.svg'; import CategoryIC from '../../../../public/res/ic/outlined/category.svg';
import CategoryFilledIC from '../../../../public/res/ic/filled/category.svg'; import CategoryFilledIC from '../../../../public/res/ic/filled/category.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import { useForceUpdate } from '../../hooks/useForceUpdate'; import { useForceUpdate } from '../../hooks/useForceUpdate';
const tabText = { const tabText = {
@ -61,6 +62,7 @@ const tabItems = [{
function GeneralSettings({ roomId }) { function GeneralSettings({ roomId }) {
const isPinned = initMatrix.accountData.spaceShortcut.has(roomId); const isPinned = initMatrix.accountData.spaceShortcut.has(roomId);
const isCategorized = initMatrix.accountData.categorizedSpaces.has(roomId); const isCategorized = initMatrix.accountData.categorizedSpaces.has(roomId);
const roomName = initMatrix.matrixClient.getRoom(roomId)?.name;
const [, forceUpdate] = useForceUpdate(); const [, forceUpdate] = useForceUpdate();
return ( return (
@ -89,10 +91,14 @@ function GeneralSettings({ roomId }) {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
variant="danger" variant="danger"
onClick={() => { onClick={async () => {
if (confirm('Are you sure that you want to leave this space?')) { const isConfirmed = await confirmDialog(
leave(roomId); 'Leave space',
} `Are you sure that you want to leave "${roomName}" space?`,
'Leave',
'danger',
);
if (isConfirmed) leave(roomId);
}} }}
iconSrc={LeaveArrowIC} iconSrc={LeaveArrowIC}
> >