Merge pull request #742 from filecoin-project/@jason-leyser/confirmation-modals

Confirmation popup components
This commit is contained in:
martinalong 2021-05-13 14:48:56 -07:00 committed by GitHub
commit a01c262f6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 392 additions and 153 deletions

View File

@ -69,11 +69,6 @@ export const signOut = async ({ viewer }) => {
// NOTE(jim): Permanently deletes you, forever. // NOTE(jim): Permanently deletes you, forever.
export const deleteMe = async ({ viewer }) => { export const deleteMe = async ({ viewer }) => {
const message = "Do you really want to delete your account? It will be permanently removed";
if (!window.confirm(message)) {
return false;
}
await Actions.updateSearch("delete-user"); await Actions.updateSearch("delete-user");
let response = await Actions.deleteViewer(); let response = await Actions.deleteViewer();

View File

@ -51,13 +51,11 @@ const STYLES_CONTENT = css`
} }
`; `;
const STYLES_SIDEBAR = css` const STYLES_SIDEBAR_ELEMENTS = css`
z-index: ${Constants.zindex.sidebar};
height: 100vh; height: 100vh;
width: ${Constants.sizes.sidebar}px; width: ${Constants.sizes.sidebar}px;
padding: 0; padding: 0;
flex-shrink: 0; flex-shrink: 0;
position: fixed;
background-color: rgba(195, 195, 196, 1); background-color: rgba(195, 195, 196, 1);
top: 0; top: 0;
right: 0; right: 0;
@ -66,12 +64,20 @@ const STYLES_SIDEBAR = css`
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
width: 100%; width: 100%;
} }
/*
@supports ((-webkit-backdrop-filter: blur(25px)) or (backdrop-filter: blur(25px))) { @supports ((-webkit-backdrop-filter: blur(25px)) or (backdrop-filter: blur(25px))) {
-webkit-backdrop-filter: blur(25px); -webkit-backdrop-filter: blur(25px);
backdrop-filter: blur(25px); backdrop-filter: blur(25px);
background-color: rgba(195, 195, 196, 0.6); background-color: rgba(195, 195, 196, 0.6);
} }
*/
`;
const STYLES_SIDEBAR = css`
position: fixed;
top: 0; right: 0;
margin: auto;
z-index: ${Constants.zindex.sidebar};
`; `;
const STYLES_SIDEBAR_HEADER = css` const STYLES_SIDEBAR_HEADER = css`
@ -202,14 +208,16 @@ export default class ApplicationLayout extends React.Component {
enabled enabled
onOutsideRectEvent={this._handleDismiss} onOutsideRectEvent={this._handleDismiss}
> >
<div css={STYLES_SIDEBAR}>
<div <div
css={STYLES_SIDEBAR} css={STYLES_SIDEBAR_ELEMENTS}
ref={(c) => { ref={(c) => {
this._sidebar = c; this._sidebar = c;
}} }}
> >
{sidebarElements} {sidebarElements}
</div> </div>
</div>
</Boundary> </Boundary>
) : null} ) : null}
</React.Fragment> </React.Fragment>

View File

@ -21,6 +21,7 @@ import { Tag } from "~/components/system/components/Tag";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
import cloneDeep from "lodash/cloneDeep"; import cloneDeep from "lodash/cloneDeep";
import ProcessedText from "~/components/core/ProcessedText"; import ProcessedText from "~/components/core/ProcessedText";
import { ConfirmationModal } from "~/components/core/ConfirmationModal";
const DEFAULT_BOOK = const DEFAULT_BOOK =
"https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi"; "https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi";
@ -38,7 +39,6 @@ const STYLES_NO_VISIBLE_SCROLL = css`
scrollbar-width: none; scrollbar-width: none;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar; -ms-overflow-style: -ms-autohiding-scrollbar;
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0px; width: 0px;
display: none; display: none;
@ -77,13 +77,11 @@ const STYLES_SIDEBAR = css`
justify-content: space-between; justify-content: space-between;
background-color: rgba(20, 20, 20, 0.8); background-color: rgba(20, 20, 20, 0.8);
${STYLES_NO_VISIBLE_SCROLL} ${STYLES_NO_VISIBLE_SCROLL}
@supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) { @supports ((-webkit-backdrop-filter: blur(75px)) or (backdrop-filter: blur(75px))) {
-webkit-backdrop-filter: blur(75px); -webkit-backdrop-filter: blur(75px);
backdrop-filter: blur(75px); backdrop-filter: blur(75px);
background-color: rgba(150, 150, 150, 0.2); background-color: rgba(150, 150, 150, 0.2);
} }
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
display: none; display: none;
} }
@ -95,7 +93,6 @@ const STYLES_DISMISS_BOX = css`
right: 16px; right: 16px;
color: ${Constants.system.darkGray}; color: ${Constants.system.darkGray};
cursor: pointer; cursor: pointer;
:hover { :hover {
color: ${Constants.system.white}; color: ${Constants.system.white};
} }
@ -123,7 +120,6 @@ const STYLES_META_TITLE = css`
text-decoration: none; text-decoration: none;
word-break: break-all; word-break: break-all;
overflow-wrap: anywhere; overflow-wrap: anywhere;
:hover { :hover {
color: ${Constants.system.blue}; color: ${Constants.system.blue};
} }
@ -173,11 +169,9 @@ const STYLES_ACTION = css`
border-bottom: 1px solid #3c3c3c; border-bottom: 1px solid #3c3c3c;
display: flex; display: flex;
align-items: center; align-items: center;
:hover { :hover {
color: ${Constants.system.brand}; color: ${Constants.system.brand};
} }
:last-child { :last-child {
border: none; border: none;
} }
@ -243,7 +237,6 @@ const STYLES_AUTOSAVE = css`
position: absolute; position: absolute;
top: 24px; top: 24px;
left: 16px; left: 16px;
@keyframes slate-animations-autosave { @keyframes slate-animations-autosave {
0% { 0% {
opacity: 0; opacity: 0;
@ -300,6 +293,7 @@ class CarouselSidebar extends React.Component {
showSavedMessage: false, showSavedMessage: false,
showConnectedSection: false, showConnectedSection: false,
showFileSection: true, showFileSection: true,
modalShow: false,
}; };
componentDidMount = () => { componentDidMount = () => {
@ -449,11 +443,11 @@ class CarouselSidebar extends React.Component {
}); });
}; };
_handleDelete = () => { _handleDelete = (res) => {
if (this.props.external || !this.props.isOwner) return; if (this.props.external || !this.props.isOwner) return;
const message =
"Are you sure you want to delete this? It will be removed from your collections as well"; if (!res) {
if (!window.confirm(message)) { this.setState({ modalShow: false });
return; return;
} }
const id = this.props.data.id; const id = this.props.data.id;
@ -745,7 +739,7 @@ class CarouselSidebar extends React.Component {
if (editingAllowed) { if (editingAllowed) {
actions.push( actions.push(
<div key="delete" css={STYLES_ACTION} onClick={this._handleDelete}> <div key="delete" css={STYLES_ACTION} onClick={() => this.setState({ modalShow: true })}>
<SVG.Trash height="24px" /> <SVG.Trash height="24px" />
<span style={{ marginLeft: 16 }}>Delete</span> <span style={{ marginLeft: 16 }}>Delete</span>
</div> </div>
@ -847,6 +841,16 @@ class CarouselSidebar extends React.Component {
} }
return ( return (
<>
{this.state.modalShow && (
<ConfirmationModal
type={"DELETE"}
withValidation={false}
callback={this._handleDelete}
header={`Are you sure you want to delete the file “${this.state.name}”?`}
subHeader={`This file will be deleted from all connected collections and your file library. You cant undo this action.`}
/>
)}
<div css={STYLES_SIDEBAR} style={{ display: this.props.display }}> <div css={STYLES_SIDEBAR} style={{ display: this.props.display }}>
{this.state.showSavedMessage && ( {this.state.showSavedMessage && (
<div css={STYLES_AUTOSAVE}> <div css={STYLES_AUTOSAVE}>
@ -911,6 +915,7 @@ class CarouselSidebar extends React.Component {
) : null} ) : null}
</div> </div>
</div> </div>
</>
); );
} }
} }

View File

@ -0,0 +1,128 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
import { useState } from "react";
import { ButtonPrimaryFull, ButtonSecondaryFull, ButtonWarningFull } from "~/components/system/components/Buttons.js";
import { Input } from "~/components/system/components/Input.js";
import { Boundary } from "~/components/system/components/fragments/Boundary.js";
const STYLES_TRANSPARENT_BG = css `
background-color: ${Constants.system.bgBlurGrayBlack};
z-index: ${Constants.zindex.modal};
width: 100vw;
height: 100vh;
position: fixed;
left: 0;
top: 0;
`;
const STYLES_MAIN_MODAL = css `
background-color: ${Constants.system.white};
width: 380px;
height: auto;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 8px;
padding: 24px;
text-align: left;
`;
const STYLES_HEADER = css `
color: ${Constants.system.black};
font-size: ${Constants.typescale.lvl1};
font-family: ${Constants.font.semiBold};
`;
const STYLES_SUB_HEADER = css `
color: ${Constants.system.textGray};
font-size: ${Constants.typescale.lvl0};
font-family: ${Constants.font.text};
margin-top: 16px;
`;
const STYLES_INPUT_HEADER = css `
color: ${Constants.system.black};
font-size: ${Constants.typescale.lvlN1};
font-family: ${Constants.font.semiBold};
font-weight: bold;
margin-top: 24px;
margin-bottom: 8px;
`;
export const ConfirmationModal = (props) => {
const [isEnabled, setIsEnabled] = useState(false);
const lang = {
deleteText: 'Delete',
confirmText: 'Confirm',
cancelText: 'Cancel'
}
const _handleChange = (e) => {
if (e.target.value === props.matchValue) {
setIsEnabled(true);
return;
}
setIsEnabled(false);
}
let deleteButton = <ButtonWarningFull disabled={true}>{props.buttonText || lang.deleteText}</ButtonWarningFull>;
if (isEnabled) {
deleteButton = <ButtonWarningFull onClick={() => props.callback(true)}>{props.buttonText || lang.deleteText}</ButtonWarningFull>;
}
let confirmButton = <ButtonPrimaryFull disabled={true}>{props.buttonText || lang.confirmText}</ButtonPrimaryFull>;
if (isEnabled) {
confirmButton = <ButtonPrimaryFull onClick={() => props.callback(true)}>{props.buttonText || lang.confirmText}</ButtonPrimaryFull>;
}
return (
<div css={STYLES_TRANSPARENT_BG}>
<Boundary enabled={true} onOutsideRectEvent={() => props.callback(false)}>
<div css={STYLES_MAIN_MODAL}>
<div css={STYLES_HEADER}>{props.header}</div>
<div css={STYLES_SUB_HEADER}>{props.subHeader}</div>
{props.type === "DELETE" &&
<>
{props.withValidation ? (
<>
<div css={STYLES_INPUT_HEADER}>{props.inputHeader}</div>
<Input placeholder={props.inputPlaceholder} onChange={_handleChange} />
<ButtonSecondaryFull onClick={() => props.callback(false)} style={{margin: '24px 0px 8px'}}>{lang.cancelText}</ButtonSecondaryFull>
{deleteButton}
</>
) : (
<>
<ButtonSecondaryFull onClick={() => props.callback(false)} style={{ margin: '24px 0px 8px' }}>{lang.cancelText}</ButtonSecondaryFull>
<ButtonWarningFull onClick={() => props.callback(true)}>{props.buttonText || lang.deleteText}</ButtonWarningFull>
</>
)}
</>
}
{props.type === "CONFIRM" &&
<>
{props.withValidation ? (
<>
<div css={STYLES_INPUT_HEADER}>{props.inputHeader}</div>
<Input placeholder={props.inputPlaceholder} onChange={_handleChange} />
<ButtonSecondaryFull onClick={() => props.callback(false)} style={{ margin: '24px 0px 8px' }}>{lang.cancelText}</ButtonSecondaryFull>
{confirmButton}
</>
) : (
<>
<ButtonSecondaryFull onClick={() => props.callback(false)} style={{ margin: '24px 0px 8px' }}>{lang.cancelText}</ButtonSecondaryFull>
<ButtonPrimaryFull onClick={() => props.callback(true)}>{props.buttonText || lang.confirmText}</ButtonPrimaryFull>
</>
)}
</>
}
</div>
</Boundary>
</div>
);
};

View File

@ -20,10 +20,10 @@ import { GroupSelectable, Selectable } from "~/components/core/Selectable/";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import FilePreviewBubble from "~/components/core/FilePreviewBubble"; import FilePreviewBubble from "~/components/core/FilePreviewBubble";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
import { ConfirmationModal } from "~/components/core/ConfirmationModal";
const STYLES_CONTAINER_HOVER = css` const STYLES_CONTAINER_HOVER = css`
display: flex; display: flex;
:hover { :hover {
color: ${Constants.system.brand}; color: ${Constants.system.brand};
} }
@ -67,7 +67,6 @@ const STYLES_LINK = css`
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
max-width: 320px; max-width: 320px;
@media (max-width: ${Constants.sizes.tablet}px) { @media (max-width: ${Constants.sizes.tablet}px) {
max-width: 120px; max-width: 120px;
} }
@ -86,7 +85,6 @@ const STYLES_ICON_BOX_HOVER = css`
align-items: center; align-items: center;
padding: 8px; padding: 8px;
cursor: pointer; cursor: pointer;
:hover { :hover {
color: ${Constants.system.brand}; color: ${Constants.system.brand};
} }
@ -117,7 +115,6 @@ const STYLES_ACTION_BAR = css`
width: 90vw; width: 90vw;
max-width: 878px; max-width: 878px;
height: 48px; height: 48px;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
display: none; display: none;
} }
@ -131,7 +128,6 @@ const STYLES_ACTION_BAR_CONTAINER = css`
display: flex; display: flex;
justify-content: center; justify-content: center;
z-index: ${Constants.zindex.header}; z-index: ${Constants.zindex.header};
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
display: none; display: none;
} }
@ -153,7 +149,6 @@ const STYLES_LEFT = css`
const STYLES_FILES_SELECTED = css` const STYLES_FILES_SELECTED = css`
font-family: ${Constants.font.semiBold}; font-family: ${Constants.font.semiBold};
color: ${Constants.system.white}; color: ${Constants.system.white};
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
display: none; display: none;
} }
@ -171,7 +166,6 @@ const STYLES_IMAGE_GRID = css`
grid-column-gap: 20px; grid-column-gap: 20px;
grid-row-gap: 20px; grid-row-gap: 20px;
width: 100%; width: 100%;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
@ -185,11 +179,9 @@ const STYLES_IMAGE_BOX = css`
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
@media (max-width: ${Constants.sizes.mobile}px) { @media (max-width: ${Constants.sizes.mobile}px) {
margin: 12px auto; margin: 12px auto;
} }
:hover { :hover {
box-shadow: 0px 0px 0px 1px ${Constants.system.lightBorder} inset, box-shadow: 0px 0px 0px 1px ${Constants.system.lightBorder} inset,
0 0 40px 0 ${Constants.system.shadow}; 0 0 40px 0 ${Constants.system.shadow};
@ -223,12 +215,10 @@ const STYLES_TAG = css`
font-family: ${Constants.font.text}; font-family: ${Constants.font.text};
padding: 2px 8px; padding: 2px 8px;
margin: 8px 8px 0 0; margin: 8px 8px 0 0;
span { span {
line-height: 1.5; line-height: 1.5;
font-size: 14px; font-size: 14px;
} }
&:hover { &:hover {
background: ${Constants.system.gray30}; background: ${Constants.system.gray30};
} }
@ -307,6 +297,7 @@ export default class DataView extends React.Component {
viewLimit: 40, viewLimit: 40,
scrollDebounce: false, scrollDebounce: false,
imageSize: 100, imageSize: 100,
modalShow: false,
}; };
isShiftDown = false; isShiftDown = false;
@ -474,9 +465,9 @@ export default class DataView extends React.Component {
this.setState({ checked: {} }); this.setState({ checked: {} });
}; };
_handleDelete = (id) => { _handleDelete = (res, id) => {
const message = `Are you sure you want to delete these files? They will be deleted from your collections as well`; if (!res) {
if (!window.confirm(message)) { this.setState({ modalShow: false });
return; return;
} }
@ -495,7 +486,7 @@ export default class DataView extends React.Component {
this.props.onUpdateViewer({ library }); this.props.onUpdateViewer({ library });
UserBehaviors.deleteFiles(ids); UserBehaviors.deleteFiles(ids);
this.setState({ checked: {} }); this.setState({ checked: {}, modalShow: false });
}; };
_handleSelect = (index) => { _handleSelect = (index) => {
@ -600,7 +591,6 @@ export default class DataView extends React.Component {
return commonTags; return commonTags;
}; };
render() { render() {
let numChecked = Object.keys(this.state.checked).length || 0; let numChecked = Object.keys(this.state.checked).length || 0;
// const header = ( // const header = (
@ -637,6 +627,7 @@ export default class DataView extends React.Component {
// </span> // </span>
// </div> // </div>
// ); // );
const footer = ( const footer = (
<React.Fragment> <React.Fragment>
{numChecked ? ( {numChecked ? (
@ -684,11 +675,20 @@ export default class DataView extends React.Component {
<ButtonWarning <ButtonWarning
transparent transparent
style={{ marginLeft: 8, color: Constants.system.white }} style={{ marginLeft: 8, color: Constants.system.white }}
onClick={() => this._handleDelete()} onClick={() => this.setState({ modalShow: true })}
> >
{Strings.pluralize("Delete file", numChecked)} {Strings.pluralize("Delete file", numChecked)}
</ButtonWarning> </ButtonWarning>
)} )}
{this.state.modalShow && (
<ConfirmationModal
type={"DELETE"}
withValidation={false}
callback={this._handleDelete}
header={`Are you sure you want to delete the selected files?`}
subHeader={`These files will be deleted from all connected collections and your file library. You cant undo this action.`}
/>
)}
<div <div
css={STYLES_ICON_BOX} css={STYLES_ICON_BOX}
onClick={() => { onClick={() => {
@ -969,7 +969,7 @@ export default class DataView extends React.Component {
text: "Delete", text: "Delete",
onClick: (e) => { onClick: (e) => {
e.stopPropagation(); e.stopPropagation();
this.setState({ menu: null }, () => this._handleDelete(each.id)); this.setState({ menu: null, modalShow: true });
}, },
}, },
]} ]}

View File

@ -24,6 +24,7 @@ import {
ButtonWarning, ButtonWarning,
} from "~/components/system/components/Buttons"; } from "~/components/system/components/Buttons";
import { GroupSelectable, Selectable } from "~/components/core/Selectable/"; import { GroupSelectable, Selectable } from "~/components/core/Selectable/";
import { ConfirmationModal } from "~/components/core/ConfirmationModal";
//NOTE(martina): sets 200px as the standard width for a 1080px wide layout with 20px margin btwn images. //NOTE(martina): sets 200px as the standard width for a 1080px wide layout with 20px margin btwn images.
//If the container is larger or smaller, it scales accordingly by that factor //If the container is larger or smaller, it scales accordingly by that factor
@ -328,6 +329,7 @@ export class SlateLayout extends React.Component {
tooltip: null, tooltip: null,
keyboardTooltip: false, keyboardTooltip: false,
signInModal: false, signInModal: false,
modalShowDeleteFiles: false,
}; };
componentDidMount = async () => { componentDidMount = async () => {
@ -887,10 +889,9 @@ export class SlateLayout extends React.Component {
this.setState(state); this.setState(state);
}; };
_handleResetLayout = async () => { _handleResetLayout = async (res) => {
if ( if (!res) {
!window.confirm("Are you sure you want to reset your layout to the default column layout?") this.setState({ modalShowResetLayout: false });
) {
return; return;
} }
let prevLayout = this.cloneLayout(this.state.layout); let prevLayout = this.cloneLayout(this.state.layout);
@ -907,6 +908,7 @@ export class SlateLayout extends React.Component {
], ],
layout, layout,
zIndexMax: 1, zIndexMax: 1,
modalShowResetLayout: false,
}); });
}; };
@ -1049,14 +1051,15 @@ export class SlateLayout extends React.Component {
e.dataTransfer.setData("DownloadURL", `${type}:${title}:${url}`); e.dataTransfer.setData("DownloadURL", `${type}:${title}:${url}`);
}; };
_handleDeleteFiles = async (e, i) => { _handleDeleteModal = () => {
const message = `Are you sure you want to delete these files? They will be deleted from your data and collections.`; this.setState({ modalShowDeleteFiles: true })
if (!window.confirm(message)) {
return;
} }
e.stopPropagation(); _handleDeleteFiles = async (res, i) => {
e.preventDefault(); if (!res) {
this.setState({ modalShowDeleteFiles: false });
return;
}
let ids = []; let ids = [];
if (i !== undefined) { if (i !== undefined) {
ids = [this.state.items[i].id.replace("data-", "")]; ids = [this.state.items[i].id.replace("data-", "")];
@ -1078,7 +1081,7 @@ export class SlateLayout extends React.Component {
} }
await UserBehaviors.deleteFiles(ids); await UserBehaviors.deleteFiles(ids);
this.setState({ checked: {} }); this.setState({ checked: {}, modalShowDeleteFiles: false });
}; };
_stopProp = (e) => { _stopProp = (e) => {
@ -1134,10 +1137,19 @@ export class SlateLayout extends React.Component {
Reset layout Reset layout
</ButtonDisabled> </ButtonDisabled>
) : ( ) : (
<ButtonSecondary onClick={this._handleResetLayout} style={{ marginRight: 16 }}> <ButtonSecondary onClick={() => { this.setState({ modalShowResetLayout: true }) }} style={{ marginRight: 16 }}>
Reset layout Reset layout
</ButtonSecondary> </ButtonSecondary>
)} )}
{this.state.modalShowResetLayout && (
<ConfirmationModal
type={"CONFIRM"}
withValidation={false}
callback={this._handleResetLayout}
header={`Are you sure you want to reset your layout to the default column layout?`}
subHeader={`You cant undo this action.`}
/>
)}
{this.state.prevLayouts.length ? ( {this.state.prevLayouts.length ? (
<ButtonSecondary style={{ marginRight: 16 }} onClick={this._handleUndo}> <ButtonSecondary style={{ marginRight: 16 }} onClick={this._handleUndo}>
Undo Undo
@ -1453,8 +1465,8 @@ export class SlateLayout extends React.Component {
onMouseLeave={() => this.setState({ tooltip: null })} onMouseLeave={() => this.setState({ tooltip: null })}
onClick={ onClick={
this.state.items[i].ownerId === this.props.viewer.id this.state.items[i].ownerId === this.props.viewer.id
? (e) => { ? () => {
this._handleDeleteFiles(e, i); this.setState({ modalShowDeleteFiles: true })
} }
: () => {} : () => {}
} }
@ -1475,6 +1487,7 @@ export class SlateLayout extends React.Component {
}} }}
/> />
</div> </div>
</div> </div>
</React.Fragment> </React.Fragment>
) : ( ) : (
@ -1674,6 +1687,15 @@ export class SlateLayout extends React.Component {
)} )}
</div> </div>
</div> </div>
{this.state.modalShowDeleteFiles && (
<ConfirmationModal
type={"DELETE"}
withValidation={false}
callback={this._handleDeleteFiles}
header={`Are you sure you want to delete the selected files?`}
subHeader={`These files will be deleted from all connected collections and your file library. You cant undo this action.`}
/>
)}
{numChecked ? ( {numChecked ? (
<div css={STYLES_ACTION_BAR_CONTAINER}> <div css={STYLES_ACTION_BAR_CONTAINER}>
<div css={STYLES_ACTION_BAR}> <div css={STYLES_ACTION_BAR}>
@ -1710,7 +1732,7 @@ export class SlateLayout extends React.Component {
<ButtonWarning <ButtonWarning
transparent transparent
style={{ marginLeft: 8, color: Constants.system.white }} style={{ marginLeft: 8, color: Constants.system.white }}
onClick={this._handleDeleteFiles} onClick={this._handleDeleteModal}
> >
{Strings.pluralize("Delete file", numChecked)} {Strings.pluralize("Delete file", numChecked)}
</ButtonWarning> </ButtonWarning>

View File

@ -10,6 +10,7 @@ import * as UserBehaviors from "~/common/user-behaviors";
import { RadioGroup } from "~/components/system/components/RadioGroup"; import { RadioGroup } from "~/components/system/components/RadioGroup";
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { ConfirmationModal } from "~/components/core/ConfirmationModal";
const SIZE_LIMIT = 1000000; const SIZE_LIMIT = 1000000;
const DEFAULT_IMAGE = const DEFAULT_IMAGE =
@ -52,6 +53,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
name: this.props.data.data.name, name: this.props.data.data.name,
tags: this.props.data.data?.tags || [], tags: this.props.data.data?.tags || [],
suggestions: this.props.viewer?.tags || [], suggestions: this.props.viewer?.tags || [],
modalShow: false,
}; };
componentDidMount = () => { componentDidMount = () => {
@ -107,12 +109,9 @@ export default class SidebarSingleSlateSettings extends React.Component {
}); });
}; };
_handleDelete = async (e) => { _handleDelete = async (res) => {
if ( if (!res) {
!window.confirm( this.setState({ modalShow: false })
"Are you sure you want to delete this Collection? This action is irreversible."
)
) {
return; return;
} }
@ -130,6 +129,8 @@ export default class SidebarSingleSlateSettings extends React.Component {
if (Events.hasError(response)) { if (Events.hasError(response)) {
return; return;
} }
this.setState({ modalShow: false })
}; };
render() { render() {
@ -303,11 +304,20 @@ export default class SidebarSingleSlateSettings extends React.Component {
</System.ButtonPrimary> </System.ButtonPrimary>
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<System.ButtonWarning full onClick={this._handleDelete} style={{ overflow: "hidden" }}> <System.ButtonWarning full onClick={() => this.setState({ modalShow: true })} style={{ overflow: "hidden" }}>
Delete collection Delete collection
</System.ButtonWarning> </System.ButtonWarning>
</div> </div>
</div> </div>
{this.state.modalShow && (
<ConfirmationModal
type={"DELETE"}
withValidation={false}
callback={this._handleDelete}
header={`Are you sure you want to delete the collection “${this.state.slatename}”?`}
subHeader={`This collection will be deleted but all your files will remain in your file library. You cant undo this action.`}
/>
)}
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -39,6 +39,13 @@ const STYLES_BUTTON_PRIMARY = css`
} }
`; `;
const STYLES_BUTTON_PRIMARY_DISABLED = css`
${STYLES_BUTTON}
cursor: not-allowed;
background-color: ${Constants.system.bgBlue};
color: ${Constants.system.white};
`;
const STYLES_BUTTON_PRIMARY_TRANSPARENT = css` const STYLES_BUTTON_PRIMARY_TRANSPARENT = css`
${STYLES_BUTTON} ${STYLES_BUTTON}
cursor: pointer; cursor: pointer;
@ -71,6 +78,17 @@ export const ButtonPrimary = (props) => {
); );
} }
if (props.disabled) {
return (
<button
css={STYLES_BUTTON_PRIMARY_DISABLED}
style={{ width: props.full ? "100%" : "auto", ...props.style }}
onClick={props.onClick}
children={props.children}
/>
);
}
return ( return (
<button <button
css={props.transparent ? STYLES_BUTTON_PRIMARY_TRANSPARENT : STYLES_BUTTON_PRIMARY} css={props.transparent ? STYLES_BUTTON_PRIMARY_TRANSPARENT : STYLES_BUTTON_PRIMARY}
@ -88,12 +106,12 @@ export const ButtonPrimaryFull = (props) => {
const STYLES_BUTTON_SECONDARY = css` const STYLES_BUTTON_SECONDARY = css`
${STYLES_BUTTON} ${STYLES_BUTTON}
cursor: pointer; cursor: pointer;
color: ${Constants.system.brand}; color: ${Constants.system.black};
background-color: ${Constants.system.white}; background-color: ${Constants.system.gray20};
box-shadow: 0 0 0 1px ${Constants.system.bgGray} inset; box-shadow: 0 0 0 1px ${Constants.system.bgGray} inset;
:hover { :hover {
background-color: #fcfcfc; background-color: ${Constants.system.gray30};
} }
:focus { :focus {
@ -251,12 +269,11 @@ export const ButtonDisabledFull = (props) => {
const STYLES_BUTTON_WARNING = css` const STYLES_BUTTON_WARNING = css`
${STYLES_BUTTON} ${STYLES_BUTTON}
cursor: pointer; cursor: pointer;
color: ${Constants.system.red}; color: ${Constants.system.white};
background-color: ${Constants.system.white}; background-color: ${Constants.system.red};
box-shadow: 0 0 0 1px ${Constants.system.bgGray} inset;
:hover { :hover {
background-color: #fcfcfc; background-color: #b51111;
} }
:focus { :focus {
@ -265,6 +282,14 @@ const STYLES_BUTTON_WARNING = css`
} }
`; `;
const STYLES_BUTTON_WARNING_DISABLED = css`
${STYLES_BUTTON}
cursor: not-allowed;
color: ${Constants.system.white};
background-color: ${Constants.system.bgRed};
box-shadow: 0 0 0 1px ${Constants.system.bgGray} inset;
`;
const STYLES_BUTTON_WARNING_TRANSPARENT = css` const STYLES_BUTTON_WARNING_TRANSPARENT = css`
${STYLES_BUTTON} ${STYLES_BUTTON}
cursor: pointer; cursor: pointer;
@ -297,6 +322,17 @@ export const ButtonWarning = (props) => {
); );
} }
if (props.disabled) {
return (
<button
css={STYLES_BUTTON_WARNING_DISABLED}
style={{ width: props.full ? "100%" : "auto", ...props.style }}
onClick={props.onClick}
children={props.children}
/>
);
}
return ( return (
<button <button
css={props.transparent ? STYLES_BUTTON_WARNING_TRANSPARENT : STYLES_BUTTON_WARNING} css={props.transparent ? STYLES_BUTTON_WARNING_TRANSPARENT : STYLES_BUTTON_WARNING}
@ -306,3 +342,7 @@ export const ButtonWarning = (props) => {
/> />
); );
}; };
export const ButtonWarningFull = (props) => {
return <ButtonWarning full {...props} />;
};

View File

@ -15,6 +15,7 @@ import { SecondaryTabGroup } from "~/components/core/TabGroup";
import ScenePage from "~/components/core/ScenePage"; import ScenePage from "~/components/core/ScenePage";
import ScenePageHeader from "~/components/core/ScenePageHeader"; import ScenePageHeader from "~/components/core/ScenePageHeader";
import Avatar from "~/components/core/Avatar"; import Avatar from "~/components/core/Avatar";
import { ConfirmationModal } from "~/components/core/ConfirmationModal";
const STYLES_FILE_HIDDEN = css` const STYLES_FILE_HIDDEN = css`
height: 1px; height: 1px;
@ -56,6 +57,7 @@ export default class SceneEditAccount extends React.Component {
savingNameBio: false, savingNameBio: false,
changingFilecoin: false, changingFilecoin: false,
tab: 0, tab: 0,
modalShow: false,
}; };
_handleUpload = async (e) => { _handleUpload = async (e) => {
@ -151,13 +153,19 @@ export default class SceneEditAccount extends React.Component {
this.setState({ changingPassword: false, password: "", confirm: "" }); this.setState({ changingPassword: false, password: "", confirm: "" });
}; };
_handleDelete = async (e) => { _handleDelete = async (res) => {
if (!res) {
this.setState({ modalShow: false });
return;
}
this.setState({ deleting: true }); this.setState({ deleting: true });
this.setState({ modalShow: false });
await Window.delay(100); await Window.delay(100);
await UserBehaviors.deleteMe({ viewer: this.props.viewer }); await UserBehaviors.deleteMe({ viewer: this.props.viewer });
this.setState({ deleting: false }); this.setState({ deleting: false });
}; };
_handleChange = (e) => { _handleChange = (e) => {
@ -333,7 +341,7 @@ export default class SceneEditAccount extends React.Component {
<div style={{ marginTop: 24 }}> <div style={{ marginTop: 24 }}>
<System.ButtonWarning <System.ButtonWarning
onClick={this._handleDelete} onClick={() => this.setState({ modalShow: true })}
loading={this.state.deleting} loading={this.state.deleting}
style={{ width: "200px" }} style={{ width: "200px" }}
> >
@ -351,6 +359,19 @@ export default class SceneEditAccount extends React.Component {
tabIndex="-1" tabIndex="-1"
css={STYLES_COPY_INPUT} css={STYLES_COPY_INPUT}
/>{" "} />{" "}
{this.state.modalShow && (
<ConfirmationModal
type={"DELETE"}
withValidation={true}
matchValue={this.state.username}
callback={this._handleDelete}
header={`Are you sure you want to delete your account @${this.state.username}?`}
subHeader={`You will lose all your files and collections. You cant undo this action.`}
inputHeader={`Please type your username to confirm`}
inputPlaceholder={`username`}
/>
)}
</ScenePage> </ScenePage>
); );
} }

View File

@ -25,6 +25,8 @@ import APIDocsUpdateSlateV2 from "~/components/api-docs/v2/update-slate.js";
import APIDocsUpdateFileV2 from "~/components/api-docs/v2/update-file.js"; import APIDocsUpdateFileV2 from "~/components/api-docs/v2/update-file.js";
import APIDocsUploadToSlateV2 from "~/components/api-docs/v2/upload.js"; import APIDocsUploadToSlateV2 from "~/components/api-docs/v2/upload.js";
import { ConfirmationModal } from "~/components/core/ConfirmationModal";
const STYLES_API_KEY = css` const STYLES_API_KEY = css`
height: 40px; height: 40px;
border-radius: 4px; border-radius: 4px;
@ -49,10 +51,15 @@ const STYLES_KEY_CONTAINER = css`
class Key extends React.Component { class Key extends React.Component {
_input; _input;
state = { visible: false, copying: false }; state = { visible: false, copying: false, modalShow: false };
_handleDelete = async (id) => { _handleDelete = async (res, id) => {
if (!res) {
this.setState({ modalShow: false });
return;
}
await this.props.onDelete(id); await this.props.onDelete(id);
this.setState({ modalShow: false });
}; };
_handleCopy = async () => { _handleCopy = async () => {
@ -79,13 +86,24 @@ class Key extends React.Component {
onMouseLeave={() => this.setState({ visible: false })} onMouseLeave={() => this.setState({ visible: false })}
/> />
<SquareButtonGray <SquareButtonGray
onClick={() => this._handleDelete(this.props.data.id)} onClick={() => this.setState({ modalShow: true })}
style={{ style={{
marginLeft: 8, marginLeft: 8,
}} }}
> >
<SVG.Trash height="16px" /> <SVG.Trash height="16px" />
</SquareButtonGray> </SquareButtonGray>
{this.state.modalShow && (
<ConfirmationModal
type={"DELETE"}
withValidation={false}
callback={(e) => this._handleDelete(e, this.props.data.id)}
header={`Are you sure you want to revoke this API key?`}
subHeader={`Any services using it will no longer be able to access your Slate account.`}
/>
)}
</div> </div>
); );
} }
@ -100,6 +118,7 @@ export default class SceneSettingsDeveloper extends React.Component {
docs: "GET", docs: "GET",
copying: false, copying: false,
tab: 0, tab: 0,
modalShow: false,
}; };
_handleCopy = async () => { _handleCopy = async () => {
@ -121,16 +140,7 @@ export default class SceneSettingsDeveloper extends React.Component {
}; };
_handleDelete = async (id) => { _handleDelete = async (id) => {
this.setState({ loading: true }); this.setState({ loading: true, modalShow: false });
if (
!window.confirm(
"Are you sure you want to revoke this API key? Any services using it will no longer be able to access your Slate account"
)
) {
this.setState({ loading: false });
return;
}
const response = await Actions.deleteAPIKey({ id }); const response = await Actions.deleteAPIKey({ id });