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:
@@ -2,19 +2,22 @@ import { FunctionComponent } from 'preact';
|
|||||||
|
|
||||||
const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content`;
|
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 \
|
type ButtonType = 'normal' | 'primary' | 'danger';
|
||||||
focus:bg-contrast hover:bg-contrast`;
|
|
||||||
const primaryClass = `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 \
|
const buttonClasses: { [type in ButtonType]: string } = {
|
||||||
focus:brightness-130`;
|
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<{
|
export const Button: FunctionComponent<{
|
||||||
className?: string;
|
className?: string;
|
||||||
type: 'normal' | 'primary';
|
type: ButtonType;
|
||||||
label: string;
|
label: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}> = ({ type, label, className = '', onClick, disabled = false }) => {
|
}> = ({ type, label, className = '', onClick, disabled = false }) => {
|
||||||
const buttonClass = type === 'primary' ? primaryClass : normalClass;
|
const buttonClass = buttonClasses[type];
|
||||||
const cursorClass = disabled ? 'cursor-default' : 'cursor-pointer';
|
const cursorClass = disabled ? 'cursor-default' : 'cursor-pointer';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
|
|||||||
const [deleteLocalBackups, setDeleteLocalBackups] = useState(false);
|
const [deleteLocalBackups, setDeleteLocalBackups] = useState(false);
|
||||||
|
|
||||||
const cancelRef = useRef<HTMLButtonElement>();
|
const cancelRef = useRef<HTMLButtonElement>();
|
||||||
function close() {
|
function closeDialog() {
|
||||||
appState.accountMenu.setSigningOut(false);
|
appState.accountMenu.setSigningOut(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
|
|||||||
}, [appState.accountMenu.signingOut, application.bridge]);
|
}, [appState.accountMenu.signingOut, application.bridge]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog onDismiss={close} leastDestructiveRef={cancelRef}>
|
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef}>
|
||||||
<div className="sk-modal-content">
|
<div className="sk-modal-content">
|
||||||
<div className="sn-component">
|
<div className="sn-component">
|
||||||
<div className="sk-panel">
|
<div className="sk-panel">
|
||||||
@@ -83,7 +83,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
|
|||||||
<button
|
<button
|
||||||
className="sn-button small neutral"
|
className="sn-button small neutral"
|
||||||
ref={cancelRef}
|
ref={cancelRef}
|
||||||
onClick={close}
|
onClick={closeDialog}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -95,7 +95,7 @@ const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
|
|||||||
} else {
|
} else {
|
||||||
application.signOut();
|
application.signOut();
|
||||||
}
|
}
|
||||||
close();
|
closeDialog();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{application.hasAccount()
|
{application.hasAccount()
|
||||||
|
|||||||
35
app/assets/javascripts/components/ConfirmationDialog.tsx
Normal file
35
app/assets/javascripts/components/ConfirmationDialog.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
80
app/assets/javascripts/components/OtherSessionsLogout.tsx
Normal file
80
app/assets/javascripts/components/OtherSessionsLogout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -26,12 +26,12 @@ type Session = RemoteSession & {
|
|||||||
function useSessions(
|
function useSessions(
|
||||||
application: SNApplication
|
application: SNApplication
|
||||||
): [
|
): [
|
||||||
Session[],
|
Session[],
|
||||||
() => void,
|
() => void,
|
||||||
boolean,
|
boolean,
|
||||||
(uuid: UuidString) => Promise<void>,
|
(uuid: UuidString) => Promise<void>,
|
||||||
string
|
string
|
||||||
] {
|
] {
|
||||||
const [sessions, setSessions] = useState<Session[]>([]);
|
const [sessions, setSessions] = useState<Session[]>([]);
|
||||||
const [lastRefreshDate, setLastRefreshDate] = useState(Date.now());
|
const [lastRefreshDate, setLastRefreshDate] = useState(Date.now());
|
||||||
const [refreshing, setRefreshing] = useState(true);
|
const [refreshing, setRefreshing] = useState(true);
|
||||||
@@ -240,7 +240,7 @@ const SessionsModal: FunctionComponent<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Sessions: FunctionComponent<{
|
export const Sessions: FunctionComponent<{
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
}> = observer(({ appState, application }) => {
|
}> = observer(({ appState, application }) => {
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import { PreferencesMenu } from './PreferencesMenu';
|
|||||||
import { PreferencesMenuView } from './PreferencesMenuView';
|
import { PreferencesMenuView } from './PreferencesMenuView';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { MfaProps } from './panes/two-factor-auth/MfaProps';
|
import { MfaProps } from './panes/two-factor-auth/MfaProps';
|
||||||
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
|
||||||
interface PreferencesProps extends MfaProps {
|
interface PreferencesProps extends MfaProps {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
|
appState: AppState;
|
||||||
closePreferences: () => void;
|
closePreferences: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +22,12 @@ const PaneSelector: FunctionComponent<
|
|||||||
case 'general':
|
case 'general':
|
||||||
return null;
|
return null;
|
||||||
case 'account':
|
case 'account':
|
||||||
return <AccountPreferences application={props.application} />;
|
return (
|
||||||
|
<AccountPreferences
|
||||||
|
application={props.application}
|
||||||
|
appState={props.appState}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 'appearance':
|
case 'appearance':
|
||||||
return null;
|
return null;
|
||||||
case 'security':
|
case 'security':
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { FunctionComponent } from 'preact';
|
|||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { PreferencesView } from './PreferencesView';
|
import { PreferencesView } from './PreferencesView';
|
||||||
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
|
||||||
export interface PreferencesViewWrapperProps {
|
export interface PreferencesViewWrapperProps {
|
||||||
appState: { preferences: { isOpen: boolean; closePreferences: () => void } };
|
appState: AppState;
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ export const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperPro
|
|||||||
<PreferencesView
|
<PreferencesView
|
||||||
closePreferences={() => appState.preferences.closePreferences()}
|
closePreferences={() => appState.preferences.closePreferences()}
|
||||||
application={application}
|
application={application}
|
||||||
|
appState={appState}
|
||||||
mfaProvider={application}
|
mfaProvider={application}
|
||||||
userProvider={application}
|
userProvider={application}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
|
||||||
|
|
||||||
|
const HorizontalLine: FunctionComponent<{ index: number; length: number }> = ({
|
||||||
|
index,
|
||||||
|
length,
|
||||||
|
}) => (index < length - 1 ? <HorizontalSeparator classes="my-4" /> : null);
|
||||||
|
|
||||||
|
export const PreferencesGroup: FunctionComponent = ({ children }) => (
|
||||||
|
<div className="bg-default border-1 border-solid rounded border-gray-300 px-6 py-6 flex flex-col">
|
||||||
|
{Array.isArray(children)
|
||||||
|
? children
|
||||||
|
.filter(
|
||||||
|
(child) => child != undefined && child !== '' && child !== false
|
||||||
|
)
|
||||||
|
.map((child, i, arr) => (
|
||||||
|
<>
|
||||||
|
{child}
|
||||||
|
<HorizontalLine index={i} length={arr.length} />
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
: children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -1,43 +1,18 @@
|
|||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
|
|
||||||
|
|
||||||
const HorizontalLine: FunctionComponent<{ index: number; length: number }> = ({
|
|
||||||
index,
|
|
||||||
length,
|
|
||||||
}) => (index < length - 1 ? <HorizontalSeparator classes="my-4" /> : null);
|
|
||||||
|
|
||||||
export const PreferencesSegment: FunctionComponent = ({ children }) => (
|
|
||||||
<div className="flex flex-col">{children}</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const PreferencesGroup: FunctionComponent = ({ children }) => (
|
|
||||||
<div className="bg-default border-1 border-solid rounded border-gray-300 px-6 py-6 flex flex-col">
|
|
||||||
{Array.isArray(children)
|
|
||||||
? children
|
|
||||||
.filter(
|
|
||||||
(child) => child != undefined && child !== '' && child !== false
|
|
||||||
)
|
|
||||||
.map((child, i, arr) => (
|
|
||||||
<>
|
|
||||||
{child}
|
|
||||||
<HorizontalLine index={i} length={arr.length} />
|
|
||||||
</>
|
|
||||||
))
|
|
||||||
: children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const PreferencesPane: FunctionComponent = ({ children }) => (
|
export const PreferencesPane: FunctionComponent = ({ children }) => (
|
||||||
<div className="color-black flex-grow flex flex-row overflow-y-auto min-h-0">
|
<div className="color-black flex-grow flex flex-row overflow-y-auto min-h-0">
|
||||||
<div className="flex-grow flex flex-col py-6 items-center">
|
<div className="flex-grow flex flex-col py-6 items-center">
|
||||||
<div className="w-125 max-w-125 flex flex-col">
|
<div className="w-125 max-w-125 flex flex-col">
|
||||||
{children != undefined && Array.isArray(children)
|
{children != undefined && Array.isArray(children)
|
||||||
? children.map((child, idx, arr) => (
|
? children
|
||||||
<>
|
.filter((child) => child != undefined)
|
||||||
{child}
|
.map((child) => (
|
||||||
{idx < arr.length - 1 ? <div className="min-h-3" /> : undefined}
|
<>
|
||||||
</>
|
{child}
|
||||||
))
|
<div className="min-h-3" />
|
||||||
|
</>
|
||||||
|
))
|
||||||
: children}
|
: children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { FunctionComponent } from 'preact';
|
||||||
|
|
||||||
|
export const PreferencesSegment: FunctionComponent = ({ children }) => (
|
||||||
|
<div className="flex flex-col">{children}</div>
|
||||||
|
);
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './Content';
|
export * from './Content';
|
||||||
export * from './MenuItem';
|
export * from './MenuItem';
|
||||||
export * from './PreferencesPane';
|
export * from './PreferencesPane';
|
||||||
|
export * from './PreferencesGroup';
|
||||||
|
export * from './PreferencesSegment';
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
import { Sync, SubscriptionWrapper, Credentials } from '@/preferences/panes/account';
|
import {
|
||||||
|
Sync,
|
||||||
|
SubscriptionWrapper,
|
||||||
|
Credentials,
|
||||||
|
LogOutWrapper,
|
||||||
|
} from '@/preferences/panes/account';
|
||||||
import { PreferencesPane } from '@/preferences/components';
|
import { PreferencesPane } from '@/preferences/components';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
}
|
appState: AppState;
|
||||||
export const AccountPreferences = observer(({application}: Props) => {
|
};
|
||||||
return (
|
|
||||||
<PreferencesPane>
|
export const AccountPreferences = observer(
|
||||||
<Credentials application={application} />
|
({ application, appState }: Props) => {
|
||||||
<Sync application={application} />
|
return (
|
||||||
<SubscriptionWrapper application={application} />
|
<PreferencesPane>
|
||||||
</PreferencesPane>
|
<Credentials application={application} />
|
||||||
);
|
<Sync application={application} />
|
||||||
});
|
<SubscriptionWrapper application={application} />
|
||||||
|
<LogOutWrapper application={application} appState={appState} />
|
||||||
|
</PreferencesPane>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
103
app/assets/javascripts/preferences/panes/account/LogOutView.tsx
Normal file
103
app/assets/javascripts/preferences/panes/account/LogOutView.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { Button } from '@/components/Button';
|
||||||
|
import { ConfirmSignoutContainer } from '@/components/ConfirmSignoutModal';
|
||||||
|
import { OtherSessionsLogoutContainer } from '@/components/OtherSessionsLogout';
|
||||||
|
import {
|
||||||
|
PreferencesGroup,
|
||||||
|
PreferencesSegment,
|
||||||
|
Subtitle,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@/preferences/components';
|
||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { AppState } from '@/ui_models/app_state';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { FunctionComponent } from 'preact';
|
||||||
|
|
||||||
|
const LogOutView: FunctionComponent<{
|
||||||
|
application: WebApplication;
|
||||||
|
appState: AppState;
|
||||||
|
}> = observer(({ application, appState }) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Log out</Title>
|
||||||
|
<div className="min-h-2" />
|
||||||
|
<Subtitle>Other devices</Subtitle>
|
||||||
|
<Text>Want to log out on all devices except this one?</Text>
|
||||||
|
<div className="min-h-3" />
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Button
|
||||||
|
className="mr-3"
|
||||||
|
type="normal"
|
||||||
|
label="Log out other sessions"
|
||||||
|
onClick={() => {
|
||||||
|
appState.accountMenu.setOtherSessionsLogout(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button type="normal" label="Manage sessions" onClick={() => appState.openSessionsModal()} />
|
||||||
|
</div>
|
||||||
|
</PreferencesSegment>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Subtitle>This device</Subtitle>
|
||||||
|
<Text>This will delete all local items and preferences.</Text>
|
||||||
|
<div className="min-h-3" />
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
label="Log out and clear local data"
|
||||||
|
onClick={() => {
|
||||||
|
appState.accountMenu.setSigningOut(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
<OtherSessionsLogoutContainer appState={appState} application={application} />
|
||||||
|
|
||||||
|
<ConfirmSignoutContainer
|
||||||
|
appState={appState}
|
||||||
|
application={application}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ClearSessionDataView: FunctionComponent<{
|
||||||
|
application: WebApplication;
|
||||||
|
appState: AppState;
|
||||||
|
}> = observer(({ application, appState }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>Clear Session Data</Title>
|
||||||
|
<div className="min-h-2" />
|
||||||
|
<Text>This will delete all local items and preferences.</Text>
|
||||||
|
<div className="min-h-3" />
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
label="Clear Session Data"
|
||||||
|
onClick={() => {
|
||||||
|
appState.accountMenu.setSigningOut(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
|
||||||
|
<ConfirmSignoutContainer
|
||||||
|
appState={appState}
|
||||||
|
application={application}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</>);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LogOutWrapper: FunctionComponent<{
|
||||||
|
application: WebApplication;
|
||||||
|
appState: AppState;
|
||||||
|
}> = observer(({ application, appState }) => {
|
||||||
|
const isLoggedIn = application.getUser() != undefined;
|
||||||
|
if (!isLoggedIn) return <ClearSessionDataView appState={appState} application={application} />;
|
||||||
|
return <LogOutView appState={appState} application={application} />;
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export { SubscriptionWrapper } from './subscription/SubscriptionWrapper';
|
export { SubscriptionWrapper } from './subscription/SubscriptionWrapper';
|
||||||
export { Sync } from './Sync';
|
export { Sync } from './Sync';
|
||||||
export { Credentials } from './Credentials';
|
export { Credentials } from './Credentials';
|
||||||
|
export { LogOutWrapper } from './LogOutView';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { SNItem } from '@standardnotes/snjs/dist/@types/models/core/item';
|
|||||||
export class AccountMenuState {
|
export class AccountMenuState {
|
||||||
show = false;
|
show = false;
|
||||||
signingOut = false;
|
signingOut = false;
|
||||||
|
otherSessionsLogOut = false;
|
||||||
server: string | undefined = undefined;
|
server: string | undefined = undefined;
|
||||||
notesAndTags: SNItem[] = [];
|
notesAndTags: SNItem[] = [];
|
||||||
isEncryptionEnabled = false;
|
isEncryptionEnabled = false;
|
||||||
@@ -21,6 +22,7 @@ export class AccountMenuState {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
show: observable,
|
show: observable,
|
||||||
signingOut: observable,
|
signingOut: observable,
|
||||||
|
otherSessionsLogOut: observable,
|
||||||
server: observable,
|
server: observable,
|
||||||
notesAndTags: observable,
|
notesAndTags: observable,
|
||||||
isEncryptionEnabled: observable,
|
isEncryptionEnabled: observable,
|
||||||
@@ -35,6 +37,7 @@ export class AccountMenuState {
|
|||||||
setIsEncryptionEnabled: action,
|
setIsEncryptionEnabled: action,
|
||||||
setEncryptionStatusString: action,
|
setEncryptionStatusString: action,
|
||||||
setIsBackupEncrypted: action,
|
setIsBackupEncrypted: action,
|
||||||
|
setOtherSessionsLogout: action,
|
||||||
|
|
||||||
notesAndTagsCount: computed
|
notesAndTagsCount: computed
|
||||||
});
|
});
|
||||||
@@ -106,6 +109,10 @@ export class AccountMenuState {
|
|||||||
this.show = !this.show;
|
this.show = !this.show;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setOtherSessionsLogout = (otherSessionsLogOut: boolean): void => {
|
||||||
|
this.otherSessionsLogOut = otherSessionsLogOut;
|
||||||
|
}
|
||||||
|
|
||||||
get notesAndTagsCount(): number {
|
get notesAndTagsCount(): number {
|
||||||
return this.notesAndTags.length;
|
return this.notesAndTags.length;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,11 +161,11 @@ export class AppState {
|
|||||||
this.onVisibilityChange = undefined;
|
this.onVisibilityChange = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
openSessionsModal() {
|
openSessionsModal(): void {
|
||||||
this.isSessionsModalVisible = true;
|
this.isSessionsModalVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
closeSessionsModal() {
|
closeSessionsModal(): void {
|
||||||
this.isSessionsModalVisible = false;
|
this.isSessionsModalVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,10 @@
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-w-89 {
|
||||||
|
max-width: 22.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-92 {
|
.w-92 {
|
||||||
width: 23rem;
|
width: 23rem;
|
||||||
}
|
}
|
||||||
@@ -206,6 +210,10 @@
|
|||||||
min-height: 0.75rem;
|
min-height: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.min-h-6 {
|
||||||
|
min-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.border-danger {
|
.border-danger {
|
||||||
border-color: var(--sn-stylekit-danger-color);
|
border-color: var(--sn-stylekit-danger-color);
|
||||||
}
|
}
|
||||||
@@ -218,3 +226,12 @@
|
|||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-9 {
|
||||||
|
padding-left: 2.25rem;
|
||||||
|
padding-right: 2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-9 {
|
||||||
|
padding-top: 2.25rem;
|
||||||
|
padding-bottom: 2.25rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"@reach/dialog": "^0.13.0",
|
"@reach/dialog": "^0.13.0",
|
||||||
"@standardnotes/sncrypto-web": "1.5.2",
|
"@standardnotes/sncrypto-web": "1.5.2",
|
||||||
"@standardnotes/features": "1.6.1",
|
"@standardnotes/features": "1.6.1",
|
||||||
"@standardnotes/snjs": "2.14.5",
|
"@standardnotes/snjs": "2.14.6",
|
||||||
"mobx": "^6.3.2",
|
"mobx": "^6.3.2",
|
||||||
"mobx-react-lite": "^3.2.0",
|
"mobx-react-lite": "^3.2.0",
|
||||||
"preact": "^10.5.12",
|
"preact": "^10.5.12",
|
||||||
|
|||||||
@@ -2069,10 +2069,10 @@
|
|||||||
"@standardnotes/sncrypto-common" "^1.5.2"
|
"@standardnotes/sncrypto-common" "^1.5.2"
|
||||||
libsodium-wrappers "^0.7.8"
|
libsodium-wrappers "^0.7.8"
|
||||||
|
|
||||||
"@standardnotes/snjs@2.14.5":
|
"@standardnotes/snjs@2.14.6":
|
||||||
version "2.14.5"
|
version "2.14.6"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.14.5.tgz#1a48b71a4ae5dbdc863cdbbb468e8ebf2113f918"
|
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.14.6.tgz#fb3f625ec6f22bbee543ccb6fc69e177311c1a4b"
|
||||||
integrity sha512-PAjv4VgWs//pzG4aYdnQC+4bY8MmwBgkdbwEfCmWRVXv7o/Mfa4t1ds+FbeiyqoEhwCcdWg3E7RRWPqRH93lQQ==
|
integrity sha512-/PfuyOv2u4Km29yQi2JXYYUteFmHmLYnbQhk96wYHbCwBiNmIyVdSp0VaA4XgVIuZq32QyhhTayjkexq5Huw/Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "3.7.2"
|
"@standardnotes/auth" "3.7.2"
|
||||||
"@standardnotes/common" "1.1.0"
|
"@standardnotes/common" "1.1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user