Merge branch 'master' into gdpr-email

This commit is contained in:
Wyatt Johnson
2018-05-03 15:18:22 -06:00
51 changed files with 1689 additions and 63 deletions
@@ -0,0 +1,44 @@
.container {
border: 1px solid #f26563;
border-radius: 2px;
color: #3b4a53;
padding: 20px 10px;
background-color: rgba(242, 101, 99, 0.1);
margin: 20px 0;
}
.button {
color: #787D80;
border-radius: 2px;
background-color: transparent;
height: 30px;
font-size: 0.9em;
line-height: normal;
&:hover {
background-color: #eaeaea;
}
&.secondary {
background-color: #787D80;
color: white;
}
}
.title {
margin: 0;
i.icon {
font-size: 1em;
padding: 4px;
}
}
.description {
margin: 0;
padding-left: 22px;
padding-bottom: 15px;
}
.actions {
text-align: center;
}
@@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { t } from 'plugin-api/beta/client/services';
import moment from 'moment';
import { Button, Icon } from 'plugin-api/beta/client/components/ui';
import styles from './AccountDeletionRequestedSign.css';
import { getErrorMessages } from 'coral-framework/utils';
import { scheduledDeletionDelayHours } from '../../config';
class AccountDeletionRequestedSign extends React.Component {
cancelAccountDeletion = async () => {
const { cancelAccountDeletion, notify } = this.props;
try {
await cancelAccountDeletion();
notify('success', t('delete_request.account_deletion_cancelled'));
} catch (err) {
notify('error', getErrorMessages(err));
}
};
render() {
const { me: { scheduledDeletionDate } } = this.props.root;
const deletionScheduledFor = moment(scheduledDeletionDate).format(
'MMM Do YYYY, h:mm a'
);
const deletionScheduledOn = moment(scheduledDeletionDate)
.subtract(scheduledDeletionDelayHours, 'hours')
.format('MMM Do YYYY, h:mm a');
return (
<div className={styles.container}>
<h4 className={styles.title}>
<Icon name="warning" className={styles.icon} />{' '}
{t('delete_request.account_deletion_requested')}
</h4>
<p className={styles.description}>
{t('delete_request.received_on')}
{deletionScheduledFor}.
</p>
<p className={styles.description}>
{t('delete_request.cancel_request_description')}
<b>
{' '}
{t('delete_request.before')} {deletionScheduledOn}
</b>.
</p>
<div className={styles.actions}>
<Button
className={cn(styles.button, styles.secondary)}
onClick={this.cancelAccountDeletion}
>
{t('delete_request.cancel_account_deletion_request')}
</Button>
</div>
</div>
);
}
}
AccountDeletionRequestedSign.propTypes = {
cancelAccountDeletion: PropTypes.func.isRequired,
notify: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
};
export default AccountDeletionRequestedSign;
@@ -0,0 +1,23 @@
.button {
color: #787D80;
border-radius: 2px;
border: 1px solid #787d80;
background-color: transparent;
height: 30px;
font-size: 0.9em;
line-height: normal;
&:hover {
background-color: #eaeaea;
}
&.secondary {
background-color: #787D80;
color: white;
}
> i {
font-size: 1.2em;
vertical-align: middle;
}
}
@@ -0,0 +1,118 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import moment from 'moment';
import styles from './DeleteMyAccount.css';
import { Button } from 'plugin-api/beta/client/components/ui';
import DeleteMyAccountDialog from './DeleteMyAccountDialog';
import { getErrorMessages } from 'coral-framework/utils';
import { t } from 'plugin-api/beta/client/services';
const initialState = { showDialog: false };
class DeleteMyAccount extends React.Component {
state = initialState;
showDialog = () => {
this.setState({
showDialog: true,
});
};
closeDialog = () => {
this.setState({
showDialog: false,
});
};
cancelAccountDeletion = async () => {
const { cancelAccountDeletion, notify } = this.props;
try {
await cancelAccountDeletion();
notify('success', t('delete_request.account_deletion_requested'));
} catch (err) {
notify('error', getErrorMessages(err));
}
};
requestAccountDeletion = async () => {
const { requestAccountDeletion, notify } = this.props;
try {
await requestAccountDeletion();
notify('success', t('delete_request.account_deletion_requested'));
} catch (err) {
notify('error', getErrorMessages(err));
}
};
render() {
const {
me: { scheduledDeletionDate },
settings: { organizationContactEmail },
} = this.props.root;
return (
<div className="talk-plugin-auth--delete-my-account">
<DeleteMyAccountDialog
requestAccountDeletion={this.requestAccountDeletion}
showDialog={this.state.showDialog}
closeDialog={this.closeDialog}
scheduledDeletionDate={scheduledDeletionDate}
organizationContactEmail={organizationContactEmail}
/>
<h3
className={cn(
styles.title,
'talk-plugin-auth--delete-my-account-description'
)}
>
{t('delete_request.delete_my_account')}
</h3>
<p
className={cn(
styles.description,
'talk-plugin-auth--delete-my-account-description'
)}
>
{t('delete_request.delete_my_account_description')}
</p>
<p
className={cn(
styles.description,
'talk-plugin-auth--delete-my-account-description'
)}
>
{scheduledDeletionDate &&
t(
'delete_request.already_submitted_request_description',
moment(scheduledDeletionDate).format('MMM Do YYYY, h:mm:ss a')
)}
</p>
{scheduledDeletionDate ? (
<Button
className={cn(styles.button, styles.secondary)}
onClick={this.cancelAccountDeletion}
>
{t('delete_request.cancel_account_deletion_request')}
</Button>
) : (
<Button
className={cn(styles.button)}
icon="delete"
onClick={this.showDialog}
>
{t('delete_request.delete_my_account')}
</Button>
)}
</div>
);
}
}
DeleteMyAccount.propTypes = {
requestAccountDeletion: PropTypes.func.isRequired,
cancelAccountDeletion: PropTypes.func.isRequired,
notify: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
};
export default DeleteMyAccount;
@@ -0,0 +1,38 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 380px;
top: 10px;
font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif;
font-size: 14px;
border-radius: 4px;
padding: 20px;
}
.close {
font-size: 20px;
line-height: 14px;
top: 10px;
right: 10px;
position: absolute;
display: block;
font-weight: bold;
color: #363636;
cursor: pointer;
&:hover {
color: #6b6b6b;
}
}
.title {
font-size: 1.2em;
margin-top: 0;
margin-bottom: 8px;
}
.description {
font-size: 1em;
line-height: 20px;
margin: 0;
}
@@ -0,0 +1,104 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './DeleteMyAccountDialog.css';
import { Dialog } from 'plugin-api/beta/client/components/ui';
import StepProgress from './StepProgress';
import DeleteMyAccountStep0 from './DeleteMyAccountStep0';
import DeleteMyAccountStep1 from './DeleteMyAccountStep1';
import DeleteMyAccountStep2 from './DeleteMyAccountStep2';
import DeleteMyAccountStep3 from './DeleteMyAccountStep3';
import DeleteMyAccountFinalStep from './DeleteMyAccountFinalStep';
import { t } from 'plugin-api/beta/client/services';
const initialState = { step: 0, formData: {} };
class DeleteMyAccountDialog extends React.Component {
state = initialState;
goToNextStep = () => {
this.setState(state => ({
step: state.step + 1,
}));
};
clear = () => {
this.setState(initialState);
};
cancel = () => {
this.clear();
this.props.closeDialog();
};
onChange = e => {
const { name, value } = e.target;
this.setState(state => ({
formData: {
...state.formData,
[name]: value,
},
}));
};
render() {
const { step } = this.state;
const { scheduledDeletionDate, organizationContactEmail } = this.props;
return (
<Dialog open={this.props.showDialog} className={styles.dialog}>
<span className={styles.close} onClick={this.cancel}>
×
</span>
<h3 className={styles.title}>
{t('delete_request.delete_my_account')}
</h3>
<StepProgress currentStep={this.state.step} totalSteps={4} />
{step === 0 && (
<DeleteMyAccountStep0
goToNextStep={this.goToNextStep}
cancel={this.cancel}
/>
)}
{step === 1 && (
<DeleteMyAccountStep1
goToNextStep={this.goToNextStep}
cancel={this.cancel}
/>
)}
{step === 2 && (
<DeleteMyAccountStep2
goToNextStep={this.goToNextStep}
cancel={this.cancel}
/>
)}
{step === 3 && (
<DeleteMyAccountStep3
formData={this.state.formData}
goToNextStep={this.goToNextStep}
cancel={this.cancel}
requestAccountDeletion={this.props.requestAccountDeletion}
onChange={this.onChange}
/>
)}
{step === 4 && (
<DeleteMyAccountFinalStep
scheduledDeletionDate={scheduledDeletionDate}
organizationContactEmail={organizationContactEmail}
finish={this.cancel}
/>
)}
</Dialog>
);
}
}
DeleteMyAccountDialog.propTypes = {
showDialog: PropTypes.bool.isRequired,
closeDialog: PropTypes.func.isRequired,
requestAccountDeletion: PropTypes.func.isRequired,
scheduledDeletionDate: PropTypes.any.isRequired,
organizationContactEmail: PropTypes.string.isRequired,
};
export default DeleteMyAccountDialog;
@@ -0,0 +1,60 @@
import React from 'react';
import cn from 'classnames';
import PropTypes from 'prop-types';
import { Button, Icon } from 'plugin-api/beta/client/components/ui';
import styles from './DeleteMyAccountStep.css';
import { t } from 'plugin-api/beta/client/services';
import moment from 'moment';
const DeleteMyAccountFinalStep = props => (
<div className={styles.step}>
<p className={styles.description}>
{t('delete_request.your_request_submitted_description')}
</p>
<div className={cn(styles.box, styles.scheduledDeletion)}>
<strong className={styles.block}>
{t('delete_request.your_account_deletion_scheduled')}
</strong>
<strong className={styles.block}>
<Icon name="access_time" className={styles.timeIcon} />
<span>
{moment(props.scheduledDeletionDate).format('MMM Do YYYY, h:mm a')}
</span>
</strong>
</div>
<p className={styles.description}>
<strong> {t('delete_request.changed_your_mind')}</strong>{' '}
{t('delete_request.simply_go_to')} <strong>
{t('delete_request.cancel_account_deletion_request')}.
</strong>
</p>
<p className={styles.description}>
<strong>{t('delete_request.tell_us_why')}.</strong>{' '}
{t('delete_request.feedback_copy')}{' '}
<a href={`mailto:${props.organizationContactEmail}`}>
{props.organizationContactEmail}
</a>.
</p>
<div className={cn(styles.actions, styles.columnView)}>
<Button
className={cn(styles.button, styles.proceed)}
onClick={props.finish}
full
>
{t('delete_request.done')}
</Button>
</div>
</div>
);
DeleteMyAccountFinalStep.propTypes = {
finish: PropTypes.func.isRequired,
scheduledDeletionDate: PropTypes.any.isRequired,
organizationContactEmail: PropTypes.string.isRequired,
};
export default DeleteMyAccountFinalStep;
@@ -0,0 +1,116 @@
.list {
padding: 0;
margin: 20px 0;
list-style: none;
}
.item {
display: flex;
margin-bottom: 20px;
.itemIcon {
flex-grow: 0;
}
.text {
flex-grow: 1;
padding-left: 10px;
}
> i.itemIcon {
font-size: 1.3em;
}
}
.button {
color: #787D80;
border-radius: 2px;
background-color: transparent;
height: 30px;
font-size: 0.9em;
line-height: normal;
&:hover {
background-color: #eaeaea;
}
&.cancel {
background-color: transparent;
color: #787D80;
}
&.proceed {
background-color: #3498DB;
color: white;
}
&.danger {
background-color: #FA4643;
color: white;
}
}
.actions {
text-align: right;
padding-top: 20px;
&.columnView {
display: flex;
flex-direction: column;
align-items: center;
}
}
.title {
font-size: 1.2em;
margin-bottom: 8px;
}
.description {
font-size: 1em;
line-height: 20px;
margin: 0;
margin-bottom: 15px;
}
.box {
display: flex;
flex-direction: column;
margin-bottom: 15px;
}
.note {
margin: 10px 0;
}
.subTitle {
font-size: 1em;
margin-bottom: 8px;
}
.textBox {
background-color: #F1F2F2;
border: none;
width: 100%;
padding: 15px;
box-sizing: border-box;
color: #3B4A53;
font-size: 1em;
margin-bottom: 15px;
}
.block {
display: block;
margin-top: 2px;
}
.step {
padding-top: 20px;
}
.scheduledDeletion {
i.timeIcon {
font-size: 1.2em;
padding: 4px;
}
}
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Button, Icon } from 'plugin-api/beta/client/components/ui';
import styles from './DeleteMyAccountStep.css';
import { t } from 'plugin-api/beta/client/services';
const DeleteMyAccountStep0 = props => (
<div className={styles.step}>
<p className={styles.description}>
{t('delete_request.step_0.you_are_attempting')}
</p>
<ul className={styles.list}>
<li className={styles.item}>
<Icon name="done" className={styles.itemIcon} />
<span className={styles.text}>{t('delete_request.step_0.item_1')}</span>
</li>
<li className={styles.item}>
<Icon name="done" className={styles.itemIcon} />
<span className={styles.text}>{t('delete_request.step_0.item_2')}</span>
</li>
<li className={styles.item}>
<Icon name="done" className={styles.itemIcon} />
<span className={styles.text}>{t('delete_request.step_0.item_3')}</span>
</li>
</ul>
<div className={cn(styles.actions)}>
<Button
className={cn(styles.button, styles.cancel)}
onClick={props.cancel}
>
{t('delete_request.cancel')}
</Button>
<Button
className={cn(styles.button, styles.proceed)}
onClick={props.goToNextStep}
>
{t('delete_request.proceed')}
</Button>
</div>
</div>
);
DeleteMyAccountStep0.propTypes = {
goToNextStep: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
};
export default DeleteMyAccountStep0;
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Button } from 'plugin-api/beta/client/components/ui';
import styles from './DeleteMyAccountStep.css';
import { t } from 'plugin-api/beta/client/services';
import { scheduledDeletionDelayHours } from '../../config';
const DeleteMyAccountStep1 = props => (
<div className={styles.step}>
<h4 className={styles.subTitle}>{t('delete_request.step_1.subtitle')}</h4>
<p className={styles.description}>
{t('delete_request.step_1.description', scheduledDeletionDelayHours)}
</p>
<h4 className={styles.subTitle}>{t('delete_request.step_1.subtitle_2')}</h4>
<p className={styles.description}>
{t('delete_request.step_1.description_2', scheduledDeletionDelayHours)}
</p>
<div className={cn(styles.actions)}>
<Button
className={cn(styles.button, styles.cancel)}
onClick={props.cancel}
>
{t('delete_request.cancel')}
</Button>
<Button
className={cn(styles.button, styles.proceed)}
onClick={props.goToNextStep}
>
{t('delete_request.proceed')}
</Button>
</div>
</div>
);
DeleteMyAccountStep1.propTypes = {
goToNextStep: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
};
export default DeleteMyAccountStep1;
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Button } from 'plugin-api/beta/client/components/ui';
import styles from './DeleteMyAccountStep.css';
import { t } from 'plugin-api/beta/client/services';
const DeleteMyAccountStep2 = props => (
<div className={styles.step}>
<p className={styles.description}>
{t('delete_request.step_2.description')}
</p>
<p className={styles.description}>
{t('delete_request.step_2.to_download')}
<strong className={styles.block}>
{t('delete_request.step_2.path')}
</strong>
</p>
<div className={cn(styles.actions)}>
<Button
className={cn(styles.button, styles.cancel)}
onClick={props.cancel}
>
{t('delete_request.cancel')}
</Button>
<Button
className={cn(styles.button, styles.proceed)}
onClick={props.goToNextStep}
>
{t('delete_request.proceed')}
</Button>
</div>
</div>
);
DeleteMyAccountStep2.propTypes = {
goToNextStep: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
};
export default DeleteMyAccountStep2;
@@ -0,0 +1,96 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Button } from 'plugin-api/beta/client/components/ui';
import styles from './DeleteMyAccountStep.css';
import InputField from './InputField';
import { t } from 'plugin-api/beta/client/services';
const initialState = {
showError: false,
};
class DeleteMyAccountStep3 extends React.Component {
state = initialState;
phrase = 'delete';
showError = () => {
this.setState({
showError: true,
});
};
clear = () => {
this.setState(initialState);
};
deleteAndContinue = async () => {
if (this.formHasError()) {
this.showError();
return;
}
await this.props.requestAccountDeletion();
this.clear();
this.props.goToNextStep();
};
formHasError = () =>
!this.props.formData.confirmPhrase ||
this.props.formData.confirmPhrase !== this.phrase;
render() {
return (
<div className={styles.step}>
<h4 className={styles.subTitle}>
{t('delete_request.step_3.subtitle')}
</h4>
<p className={styles.description}>
{t('delete_request.step_3.description')}
</p>
<input
className={styles.textBox}
disabled={true}
readOnly={true}
value={this.phrase}
/>
<InputField
id="confirmPhrase"
label={t('delete_request.step_3.type_to_confirm')}
name="confirmPhrase"
type="text"
onChange={this.props.onChange}
defaultValue=""
hasError={this.formHasError()}
errorMsg={t('delete_request.input_is_not_correct')}
showError={this.state.showError}
columnDisplay
/>
<div className={cn(styles.actions)}>
<Button
className={cn(styles.button, styles.cancel)}
onClick={this.props.cancel}
>
{t('delete_request.cancel')}
</Button>
<Button
className={cn(styles.button, styles.danger)}
onClick={this.deleteAndContinue}
>
{t('delete_request.delete_my_account')}
</Button>
</div>
</div>
);
}
}
DeleteMyAccountStep3.propTypes = {
goToNextStep: PropTypes.func.isRequired,
requestAccountDeletion: PropTypes.func.isRequired,
cancel: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
formData: PropTypes.object.isRequired,
};
export default DeleteMyAccountStep3;
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
import { t } from 'plugin-api/beta/client/services';
import { Button } from 'plugin-api/beta/client/components/ui';
import styles from './DownloadCommentHistory.css';
import { getErrorMessages } from 'coral-framework/utils';
import { downloadRateLimitDays } from '../../config';
export const readableDuration = durAsHours => {
const durAsDays = Math.ceil(durAsHours / 24);
@@ -19,21 +21,30 @@ export const readableDuration = durAsHours => {
class DownloadCommentHistory extends Component {
static propTypes = {
requestDownloadLink: PropTypes.func.isRequired,
notify: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
};
requestDownloadLink = async () => {
const { requestDownloadLink, notify } = this.props;
try {
await requestDownloadLink();
notify('success', t('download_request.download_preparing'));
} catch (err) {
notify('error', getErrorMessages(err));
}
};
render() {
const {
root: { me: { lastAccountDownload } },
requestDownloadLink,
} = this.props;
const { root: { me: { lastAccountDownload } } } = this.props;
const now = new Date();
const lastAccountDownloadDate =
lastAccountDownload && new Date(lastAccountDownload);
const hoursLeft = lastAccountDownloadDate
? Math.ceil(
7 * 24 - (now.getTime() - lastAccountDownloadDate.getTime()) / 3.6e6
downloadRateLimitDays * 24 -
(now.getTime() - lastAccountDownloadDate.getTime()) / 3.6e6
)
: 0;
const canRequestDownload = !lastAccountDownloadDate || hoursLeft <= 0;
@@ -43,7 +54,7 @@ class DownloadCommentHistory extends Component {
<h3>{t('download_request.section_title')}</h3>
<p>
{t('download_request.you_will_get_a_copy')}{' '}
<b>{t('download_request.download_rate')}</b>.
<b>{t('download_request.download_rate', downloadRateLimitDays)}</b>.
</p>
{lastAccountDownloadDate && (
<p className={styles.most_recent}>
@@ -52,7 +63,7 @@ class DownloadCommentHistory extends Component {
</p>
)}
{canRequestDownload ? (
<Button className={styles.button} onClick={requestDownloadLink}>
<Button className={styles.button} onClick={this.requestDownloadLink}>
<i className="material-icons" aria-hidden={true}>
file_download
</i>{' '}
@@ -0,0 +1,12 @@
.errorMsg {
color: #FA4643;
font-size: 0.9em;
i.warningIcon {
font-size: 17px;
}
}
.warningIcon {
color: #FA4643;
}
@@ -0,0 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './ErrorMessage.css';
import { Icon } from 'plugin-api/beta/client/components/ui';
const ErrorMessage = ({ children }) => (
<div className={styles.errorMsg}>
<Icon className={styles.warningIcon} name="warning" />
<span>{children}</span>
</div>
);
ErrorMessage.propTypes = {
children: PropTypes.node,
};
export default ErrorMessage;
@@ -0,0 +1,85 @@
.detailItem {
margin-bottom: 12px;
}
.detailItemContainer {
display: flex;
flex-direction: column;
}
.columnDisplay {
flex-direction: column;
.detailItemMessage {
padding: 4px 0 0;
}
}
.detailItemContent {
display: flex;
}
.detailInput {
border: solid 1px #787D80;
border-radius: 2px;
background-color: white;
height: 30px;
display: inline-block;
width: 230px;
display: flex;
box-sizing: border-box;
> .detailIcon {
font-size: 1.2em;
padding: 0 5px;
color: #787D80;
line-height: 30px;
}
&.error {
border: solid 2px #FA4643;
}
&.disabled {
background-color: #E0E0E0;
}
}
.detailLabel {
color: #4C4C4D;
font-size: 1em;
display: block;
margin-bottom: 4px;
}
.detailValue {
background: transparent;
border: none;
font-size: 1em;
color: #000;
outline: none;
flex: 1;
height: 100%;
box-sizing: border-box;
padding: 0 6px;
}
.detailItemMessage {
flex-grow: 1;
display: flex;
align-items: center;
padding-left: 6px;
.warningIcon, .checkIcon {
font-size: 17px;
}
}
.checkIcon {
color: #00CD73;
}
.warningIcon {
color: #FA4643;
}
@@ -0,0 +1,98 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './InputField.css';
import ErrorMessage from './ErrorMessage';
import { Icon } from 'plugin-api/beta/client/components/ui';
const InputField = ({
id = '',
label = '',
type = 'text',
name = '',
onChange = () => {},
showError = true,
hasError = false,
errorMsg = '',
children,
columnDisplay = false,
showSuccess = false,
validationType = '',
icon = '',
value = '',
defaultValue = '',
disabled = false,
}) => {
const inputValue = {
...(value ? { value } : {}),
...(defaultValue ? { defaultValue } : {}),
};
return (
<div className={styles.detailItem}>
<div className={cn(styles.detailItemContainer)}>
{label && (
<label className={styles.detailLabel} id={id}>
{label}
</label>
)}
<div
className={cn(styles.detailItemContent, {
[styles.columnDisplay]: columnDisplay,
})}
>
<div
className={cn(
styles.detailInput,
{ [styles.error]: hasError && showError },
{ [styles.disabled]: disabled }
)}
>
{icon && <Icon name={icon} className={styles.detailIcon} />}
<input
id={id}
type={type}
name={name}
className={styles.detailValue}
onChange={onChange}
autoComplete="off"
data-validation-type={validationType}
disabled={disabled}
{...inputValue}
/>
</div>
<div className={styles.detailItemMessage}>
{!hasError &&
showSuccess &&
value && (
<Icon className={styles.checkIcon} name="check_circle" />
)}
{hasError && showError && <ErrorMessage>{errorMsg}</ErrorMessage>}
</div>
</div>
</div>
{children}
</div>
);
};
InputField.propTypes = {
id: PropTypes.string,
disabled: PropTypes.bool,
label: PropTypes.string,
type: PropTypes.string,
name: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.string,
defaultValue: PropTypes.string,
icon: PropTypes.string,
showError: PropTypes.bool,
hasError: PropTypes.bool,
errorMsg: PropTypes.string,
children: PropTypes.node,
columnDisplay: PropTypes.bool,
showSuccess: PropTypes.bool,
validationType: PropTypes.string,
};
export default InputField;
@@ -0,0 +1,36 @@
.step {
color: #BBBEBF;
background-color: white;
padding: 6px;
z-index: 10;
> .icon {
font-size: 25px;
}
&.current {
color: rgba(0, 205, 115, 0.3);
}
&.completed {
color: #00CD73;
}
}
.line {
position: absolute;
border: solid 2px #BBBEBF;
width: 100%;
box-sizing: border-box;
margin: 0;
padding: 0;
}
.container {
display: flex;
position: relative;
justify-content: space-between;
align-items: center;
height: 50px;
margin: 0 20px;
}
@@ -0,0 +1,48 @@
import React from 'react';
import cn from 'classnames';
import PropTypes from 'prop-types';
import styles from './StepProgress.css';
import { Icon } from 'plugin-api/beta/client/components/ui';
const CheckItem = ({ current = false, completed = false }) => (
<span
className={cn(styles.step, {
[styles.current]: current,
[styles.completed]: completed,
})}
>
<Icon name="check_circle" className={styles.icon} />
</span>
);
CheckItem.propTypes = {
current: PropTypes.bool.isRequired,
completed: PropTypes.bool.isRequired,
};
const Line = () => <hr className={styles.line} />;
class StepProgress extends React.Component {
render() {
const { currentStep, totalSteps } = this.props;
return (
<div className={styles.container}>
{Array.from({ length: totalSteps }).map((_, i) => (
<CheckItem
key={`step_${i}`}
completed={i < currentStep}
current={currentStep === i}
/>
))}
<Line />
</div>
);
}
}
StepProgress.propTypes = {
currentStep: PropTypes.number.isRequired,
totalSteps: PropTypes.number.isRequired,
};
export default StepProgress;
@@ -0,0 +1,25 @@
import { compose, gql } from 'react-apollo';
import { bindActionCreators } from 'redux';
import { connect, withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
import AccountDeletionRequestedSign from '../components/AccountDeletionRequestedSign';
import { notify } from 'coral-framework/actions/notification';
import { withCancelAccountDeletion } from '../hocs';
const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch);
const withData = withFragments({
root: gql`
fragment Talk_AccountDeletionRequestedSignIn_root on RootQuery {
me {
scheduledDeletionDate
}
}
`,
});
export default compose(
connect(null, mapDispatchToProps),
withCancelAccountDeletion,
withData,
excludeIf(({ root: { me } }) => !me || !me.scheduledDeletionDate)
)(AccountDeletionRequestedSign);
@@ -0,0 +1,28 @@
import { compose, gql } from 'react-apollo';
import { bindActionCreators } from 'redux';
import { connect, withFragments } from 'plugin-api/beta/client/hocs';
import DeleteMyAccount from '../components/DeleteMyAccount';
import { notify } from 'coral-framework/actions/notification';
import { withRequestAccountDeletion, withCancelAccountDeletion } from '../hocs';
const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch);
const withData = withFragments({
root: gql`
fragment Talk_DeleteMyAccount_root on RootQuery {
me {
scheduledDeletionDate
}
settings {
organizationContactEmail
}
}
`,
});
export default compose(
connect(null, mapDispatchToProps),
withRequestAccountDeletion,
withCancelAccountDeletion,
withData
)(DeleteMyAccount);
@@ -1,27 +1,36 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { compose, gql } from 'react-apollo';
import DownloadCommentHistory from '../components/DownloadCommentHistory';
import { withFragments } from 'plugin-api/beta/client/hocs';
import { withRequestDownloadLink } from '../hocs';
import { connect, withFragments } from 'plugin-api/beta/client/hocs';
import { withRequestDownloadLink } from '../hocs';
import { notify } from 'coral-framework/actions/notification';
class DownloadCommentHistoryContainer extends Component {
static propTypes = {
requestDownloadLink: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
notify: PropTypes.func.isRequired,
};
render() {
return (
<DownloadCommentHistory
root={this.props.root}
notify={this.props.notify}
requestDownloadLink={this.props.requestDownloadLink}
/>
);
}
}
const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch);
const enhance = compose(
connect(null, mapDispatchToProps),
withFragments({
root: gql`
fragment TalkDownloadCommentHistory_DownloadCommentHistorySection_root on RootQuery {
@@ -1,8 +1,16 @@
import update from 'immutability-helper';
import { createDefaultResponseFragments } from 'coral-framework/utils';
export default {
fragments: {
...createDefaultResponseFragments(
'RequestAccountDeletionResponse',
'RequestDownloadLinkResponse',
'CancelAccountDeletionResponse'
),
},
mutations: {
DownloadCommentHistory: () => ({
RequestDownloadLink: () => ({
updateQueries: {
CoralEmbedStream_Profile: previousData =>
update(previousData, {
@@ -1,10 +1,13 @@
import DownloadCommentHistory from './containers/DownloadCommentHistory';
import DeleteMyAccount from './containers/DeleteMyAccount';
import AccountDeletionRequestedSign from './containers/AccountDeletionRequestedSign';
import translations from './translations.yml';
import graphql from './graphql';
export default {
slots: {
profileSettings: [DownloadCommentHistory],
stream: [AccountDeletionRequestedSign],
profileSettings: [DownloadCommentHistory, DeleteMyAccount],
},
translations,
...graphql,
@@ -2,7 +2,7 @@ en:
download_request:
section_title: "Download My Comment History"
you_will_get_a_copy: "You will recieve an email with a link to download your comment history. You can make"
download_rate: "one download request every 7 days"
download_rate: "one download request every {0} days"
most_recent_request: "Your most recent request"
request: "Request Comment History"
rate_limit: "You can submit another Comment History request in {0}"
@@ -10,3 +10,42 @@ en:
days: "{0} days"
hour: "{0} hour"
day: "{0} day"
download_preparing: "Account Download Preparing - Check your email shortly for a download link"
delete_request:
account_deletion_cancelled: 'Account Deletion Request Cancelled - Your request to delete your account has been cancelled.'
account_deletion_requested: 'Account Deletion Requested'
received_on: "A request to delete your account was received on "
cancel_request_description: "If you would like to reactivate your account, you may cancel your request to delete your account below"
before: "before"
cancel_account_deletion_request: "Cancel Account Deletion Request"
delete_my_account: "Delete My Account"
delete_my_account_description: "Deleting your account will permanently erase your profile and remove all your comments from this site."
already_submitted_request_description: "You have already submitted a request to delete your account. Your account will be deleted on {0}. You may cancel the request until that time"
your_request_submitted_description: "Your request has been submitted and confirmation has been sent to the email address associated with your account."
your_account_deletion_scheduled: "Your account is scheduled to be deleted at:"
changed_your_mind: "Changed your mind?"
simply_go_to: "Simply go to your account again before this time and click"
tell_us_why: "Tell us why"
feedback_copy: "We would like to know why you chose to delete your account. Send us feedback on our comment system by emailing"
done: "Done"
cancel: "Cancel"
proceed: "Proceed"
input_is_not_correct: "The input is not correct"
step_0:
you_are_attempting: "You are attempting to delete your account. This means:"
item_1: "All of your comments are removed from this site"
item_2: "All of your comments are deleted from our database"
item_3: "Your username and email address are removed from our system"
step_1:
subtitle: "When will my account be deleted?"
description: "Your account will be deleted {0} hours after your request has been submitted."
subtitle_2: "Can I still write comments until my account is deleted?"
description_2: "Yes, you can still comment, reply, and react to comments until the {0} hours expires."
step_2:
description: "Before your account is deleted, we recommend you download your comment history for your records. After your account is deleted, you will be unable to request your comment history."
to_download: "To download your comment history go to:"
path: "My Profile > Download My Comment History"
step_3:
subtitle: "Are you sure you want to delete your account?"
description: "To confirm you would like to delete your account please type in the following phrase into the text box below:"
type_to_confirm: "Type phrase below to confirm"