feat: implement UI for logging out (#638)

* feat: implement UI for logging out

* feat: use old style dialogs for logout confirmation

* feat: implement manage sessions

* feat: implement session logout success dialog

* feat: use snjs alert for revoking sessions confirmation

* fix: make OtherSessionsLogout easier to read
This commit is contained in:
Gorjan Petrovski
2021-09-21 19:01:32 +02:00
committed by GitHub
parent a9610fdbc6
commit 77525a56cd
19 changed files with 342 additions and 70 deletions

View File

@@ -2,19 +2,22 @@ import { FunctionComponent } from 'preact';
const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content`;
const normalClass = `${baseClass} bg-default color-text border-solid border-gray-300 border-1 \
focus:bg-contrast hover:bg-contrast`;
const primaryClass = `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 \
focus:brightness-130`;
type ButtonType = 'normal' | 'primary' | 'danger';
const buttonClasses: { [type in ButtonType]: string } = {
normal: `${baseClass} bg-default color-text border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`,
primary: `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 focus:brightness-130`,
danger: `${baseClass} bg-default color-danger border-solid border-gray-300 border-1 focus:bg-contrast hover:bg-contrast`,
};
export const Button: FunctionComponent<{
className?: string;
type: 'normal' | 'primary';
type: ButtonType;
label: string;
onClick: () => void;
disabled?: boolean;
}> = ({ type, label, className = '', onClick, disabled = false }) => {
const buttonClass = type === 'primary' ? primaryClass : normalClass;
const buttonClass = buttonClasses[type];
const cursorClass = disabled ? 'cursor-default' : 'cursor-pointer';
return (

View File

@@ -26,7 +26,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
const [deleteLocalBackups, setDeleteLocalBackups] = useState(false);
const cancelRef = useRef<HTMLButtonElement>();
function close() {
function closeDialog() {
appState.accountMenu.setSigningOut(false);
}
@@ -37,7 +37,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
}, [appState.accountMenu.signingOut, application.bridge]);
return (
<AlertDialog onDismiss={close} leastDestructiveRef={cancelRef}>
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef}>
<div className="sk-modal-content">
<div className="sn-component">
<div className="sk-panel">
@@ -83,7 +83,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
<button
className="sn-button small neutral"
ref={cancelRef}
onClick={close}
onClick={closeDialog}
>
Cancel
</button>
@@ -95,7 +95,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
} else {
application.signOut();
}
close();
closeDialog();
}}
>
{application.hasAccount()

View File

@@ -0,0 +1,35 @@
import { ComponentChildren, FunctionComponent } from 'preact';
import {
AlertDialog,
AlertDialogDescription,
AlertDialogLabel,
} from '@reach/alert-dialog';
import { useRef } from 'preact/hooks';
export const ConfirmationDialog: FunctionComponent<{
title: string | ComponentChildren;
}> = ({ title, children }) => {
const ldRef = useRef<HTMLButtonElement>();
return (
<AlertDialog leastDestructiveRef={ldRef}>
{/* sn-component is focusable by default, but doesn't stretch to child width
resulting in a badly focused dialog. Utility classes are not available
at the sn-component level, only below it. tabIndex -1 disables focus
and enables it on the child component */}
<div tabIndex={-1} className="sn-component">
<div
tabIndex={0}
className="max-w-89 bg-default rounded shadow-overlay focus:padded-ring-info px-9 py-9 flex flex-col items-center"
>
<AlertDialogLabel>{title}</AlertDialogLabel>
<div className="min-h-2" />
<AlertDialogDescription className="flex flex-col items-center">
{children}
</AlertDialogDescription>
</div>
</div>
</AlertDialog>
);
};

View File

@@ -0,0 +1,80 @@
import { useRef, useState } from 'preact/hooks';
import {
AlertDialog,
AlertDialogDescription,
AlertDialogLabel,
} from '@reach/alert-dialog';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
type Props = {
application: WebApplication;
appState: AppState;
};
export const OtherSessionsLogoutContainer = observer((props: Props) => {
if (!props.appState.accountMenu.otherSessionsLogOut) {
return null;
}
return <ConfirmOtherSessionsLogout {...props} />;
});
const ConfirmOtherSessionsLogout = observer(
({ application, appState }: Props) => {
const cancelRef = useRef<HTMLButtonElement>();
function closeDialog() {
appState.accountMenu.setOtherSessionsLogout(false);
}
return (
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef}>
<div className="sk-modal-content">
<div className="sn-component">
<div className="sk-panel">
<div className="sk-panel-content">
<div className="sk-panel-section">
<AlertDialogLabel className="sk-h3 sk-panel-section-title capitalize">
End all other sessions?
</AlertDialogLabel>
<AlertDialogDescription className="sk-panel-row">
<p className="color-foreground">
This action will sign out all other devices signed into your account,
and remove your data from those devices when they next regain connection
to the internet. You may sign back in on those devices at any time.
</p>
</AlertDialogDescription>
<div className="flex my-1 mt-4">
<button
className="sn-button small neutral"
ref={cancelRef}
onClick={closeDialog}
>
Cancel
</button>
<button
className="sn-button small danger ml-2"
onClick={() => {
application.revokeAllOtherSessions();
closeDialog();
application.alertService.alert(
"You have successfully revoked your sessions from other devices.",
undefined,
"Finish"
);
}}
>
End Sessions
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</AlertDialog>
);
}
);

View File

@@ -26,12 +26,12 @@ type Session = RemoteSession & {
function useSessions(
application: SNApplication
): [
Session[],
() => void,
boolean,
(uuid: UuidString) => Promise<void>,
string
] {
Session[],
() => void,
boolean,
(uuid: UuidString) => Promise<void>,
string
] {
const [sessions, setSessions] = useState<Session[]>([]);
const [lastRefreshDate, setLastRefreshDate] = useState(Date.now());
const [refreshing, setRefreshing] = useState(true);
@@ -240,7 +240,7 @@ const SessionsModal: FunctionComponent<{
);
};
const Sessions: FunctionComponent<{
export const Sessions: FunctionComponent<{
appState: AppState;
application: WebApplication;
}> = observer(({ appState, application }) => {