import { useCallback } from 'react';
import { sprintf } from 'sprintf-js';
import styled from 'styled-components';
import { colors } from '../../config.json';
import { IDevice } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
import log from '../../shared/logging';
import { capitalizeEveryWord } from '../../shared/string-helpers';
import { useAppContext } from '../context';
import { transitions, useHistory } from '../lib/history';
import { formatHtml } from '../lib/html-formatter';
import { RoutePath } from '../lib/routes';
import { useBoolean } from '../lib/utilityHooks';
import { useSelector } from '../redux/store';
import * as AppButton from './AppButton';
import * as Cell from './cell';
import { bigText, measurements, normalText, tinyText } from './common-styles';
import CustomScrollbars from './CustomScrollbars';
import { Brand, HeaderBarSettingsButton } from './HeaderBar';
import ImageView from './ImageView';
import { Footer, Header, Layout, SettingsContainer } from './Layout';
import List from './List';
import { ModalAlert, ModalAlertType, ModalContainer, ModalMessage } from './Modal';
const StyledCustomScrollbars = styled(CustomScrollbars)({
flex: 1,
});
const StyledContainer = styled(SettingsContainer)({
paddingTop: '14px',
minHeight: '100%',
});
const StyledBody = styled.div({
display: 'flex',
flexDirection: 'column',
flex: 1,
paddingBottom: 'auto',
});
const StyledStatusIcon = styled.div({
alignSelf: 'center',
width: '60px',
height: '60px',
marginBottom: '18px',
});
const StyledTitle = styled.span(bigText, {
lineHeight: '38px',
margin: `0 ${measurements.viewMargin} 8px`,
color: colors.white,
});
const StyledLabel = styled.span({
fontFamily: 'Open Sans',
fontSize: '12px',
fontWeight: 600,
lineHeight: '20px',
color: colors.white,
margin: `0 ${measurements.viewMargin} 18px`,
});
const StyledSpacer = styled.div({
flex: '1',
});
const StyledDeviceInfo = styled(Cell.Label)({
display: 'flex',
flexDirection: 'column',
marginTop: '9px',
marginBottom: '9px',
});
const StyledDeviceName = styled.span(normalText, {
fontWeight: 'normal',
lineHeight: '20px',
textTransform: 'capitalize',
});
const StyledDeviceDate = styled.span(tinyText, {
fontSize: '10px',
lineHeight: '10px',
color: colors.white60,
});
const StyledRemoveDeviceButton = styled.button({
cursor: 'default',
padding: 0,
marginLeft: 8,
backgroundColor: 'transparent',
border: 'none',
});
export default function TooManyDevices() {
const history = useHistory();
const { removeDevice, login, cancelLogin } = useAppContext();
const accountToken = useSelector((state) => state.account.accountToken)!;
const devices = useSelector((state) => state.account.devices);
const loginState = useSelector((state) => state.account.status);
const onRemoveDevice = useCallback(
async (deviceId: string) => {
await removeDevice({ accountToken, deviceId });
},
[removeDevice, accountToken],
);
const continueLogin = useCallback(() => {
void login(accountToken);
history.reset(RoutePath.login, { transition: transitions.pop });
}, [login, accountToken]);
const cancel = useCallback(() => {
cancelLogin();
history.reset(RoutePath.login, { transition: transitions.pop });
}, [history.reset, cancelLogin]);
const iconSource = getIconSource(devices);
const title = getTitle(devices);
const subtitle = getSubtitle(devices);
const continueButtonDisabled = devices.length === 5 || loginState.type !== 'too many devices';
return (
{devices !== undefined && (
<>
{title}
{subtitle}
>
)}
{devices !== undefined && (
)}
);
}
interface IDeviceListProps {
devices: Array;
onRemoveDevice: (deviceId: string) => Promise;
}
function DeviceList(props: IDeviceListProps) {
return (
{(device) => }
);
}
const getDeviceKey = (device: IDevice): string => device.id;
interface IDeviceProps {
device: IDevice;
onRemove: (deviceId: string) => Promise;
}
function Device(props: IDeviceProps) {
const { fetchDevices } = useAppContext();
const accountToken = useSelector((state) => state.account.accountToken)!;
const [confirmationVisible, showConfirmation, hideConfirmation] = useBoolean(false);
const [deleting, setDeleting, unsetDeleting] = useBoolean(false);
const [error, setError, resetError] = useBoolean(false);
const handleError = useCallback(
async (error: Error) => {
log.error(`Failede to remove device: ${error.message}`);
let devices: Array | undefined = undefined;
try {
devices = await fetchDevices(accountToken);
} catch {
/* no-op */
}
if (devices === undefined || devices.find((device) => device.id === props.device.id)) {
hideConfirmation();
unsetDeleting();
setError();
}
},
[fetchDevices, accountToken, hideConfirmation, setError],
);
const onRemove = useCallback(async () => {
setDeleting();
hideConfirmation();
try {
await props.onRemove(props.device.id);
} catch (e) {
await handleError(e as Error);
}
}, [props.onRemove, props.device.id, hideConfirmation, setDeleting, handleError]);
const capitalizedDeviceName = capitalizeEveryWord(props.device.name);
const createdDate = props.device.created.toISOString().split('T')[0];
return (
<>
{props.device.name}
{sprintf(
// TRANSLATORS: Label informing the user when a device was created.
// TRANSLATORS: Available placeholders:
// TRANSLATORS: %(createdDate)s - The creation date of the device.
messages.pgettext('device-management', 'Created: %(createdDate)s'),
{
createdDate,
},
)}
{deleting ? (
) : (
)}
{
// TRANSLATORS: Confirmation button when logging out other device.
messages.pgettext('device-management', 'Yes, log out device')
}
,
{messages.gettext('Back')}
,
]}
close={hideConfirmation}>
{formatHtml(
sprintf(
// TRANSLATORS: Text displayed above button which logs out another device.
// TRANSLATORS: The text enclosed in "" will appear bold.
// TRANSLATORS: Available placeholders:
// TRANSLATORS: %(deviceName)s - The name of the device to log out.
messages.pgettext(
'device-management',
'Are you sure you want to log %(deviceName)s out?',
),
{ deviceName: capitalizedDeviceName },
),
)}
{messages.gettext('Close')}
,
]}
close={resetError}
message={messages.pgettext('device-management', 'Failed to remove device')}
/>
>
);
}
function getIconSource(devices?: Array): string {
if (devices) {
if (devices.length === 5) {
return 'icon-fail';
} else {
return 'icon-success';
}
} else {
return 'icon-spinner';
}
}
function getTitle(devices?: Array): string | undefined {
if (devices) {
if (devices.length === 5) {
// TRANSLATORS: Page title informing user that the login failed due to too many registered
// TRANSLATORS: devices on account.
return messages.pgettext('device-management', 'Too many devices');
} else {
// TRANSLATORS: Page title informing user that enough devices has been removed to continue
// TRANSLATORS: login process.
return messages.pgettext('device-management', 'Super!');
}
} else {
return undefined;
}
}
function getSubtitle(devices?: Array): string | undefined {
if (devices) {
if (devices.length === 5) {
return messages.pgettext(
'device-management',
'Please log out of at least one by removing it from the list below. You can find the corresponding device name under the device’s Account settings.',
);
} else {
return messages.pgettext(
'device-management',
'You can now continue logging in on this device.',
);
}
} else {
return undefined;
}
}