feat(client): convert class components to functional components (#43226)

This commit is contained in:
awu43
2021-10-26 22:47:47 -07:00
committed by GitHub
parent fbc0ea8742
commit fa9fb61f6a
10 changed files with 632 additions and 747 deletions

View File

@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
@@ -52,48 +52,46 @@ type LearnLayoutProps = {
children?: React.ReactNode;
};
class LearnLayout extends Component<LearnLayoutProps> {
static displayName = 'LearnLayout';
function LearnLayout({
isSignedIn,
fetchState,
user,
tryToShowDonationModal,
children
}: LearnLayoutProps): JSX.Element {
useEffect(() => {
tryToShowDonationModal();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
componentDidMount() {
this.props.tryToShowDonationModal();
useEffect(() => {
return () => {
const metaTag = document.querySelector(`meta[name="robots"]`);
if (metaTag) {
metaTag.remove();
}
};
}, []);
if (fetchState.pending && !fetchState.complete) {
return <Loader fullScreen={true} />;
}
componentWillUnmount() {
const metaTag = document.querySelector(`meta[name="robots"]`);
if (metaTag) {
metaTag.remove();
}
if (isSignedIn && !user.acceptedPrivacyTerms) {
return <RedirectEmailSignUp />;
}
render() {
const {
fetchState: { pending, complete },
isSignedIn,
user: { acceptedPrivacyTerms },
children
} = this.props;
if (pending && !complete) {
return <Loader fullScreen={true} />;
}
if (isSignedIn && !acceptedPrivacyTerms) {
return <RedirectEmailSignUp />;
}
return (
<>
<Helmet>
<meta content='noindex' name='robots' />
</Helmet>
<main id='learn-app-wrapper'>{children}</main>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
/* @ts-ignore */}
<DonateModal />
</>
);
}
return (
<>
<Helmet>
<meta content='noindex' name='robots' />
</Helmet>
<main id='learn-app-wrapper'>{children}</main>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
/* @ts-ignore */}
<DonateModal />
</>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(LearnLayout);

View File

@@ -9,7 +9,7 @@ import {
import Loadable from '@loadable/component';
import { useStaticQuery, graphql } from 'gatsby';
import { reverse, sortBy } from 'lodash-es';
import React, { Component, useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import { TFunction, withTranslation } from 'react-i18next';
import envData from '../../../../../config/env.json';
@@ -66,43 +66,58 @@ interface TimelineInnerProps extends TimelineProps {
totalPages: number;
}
interface TimeLineInnerState {
solutionToView: string | null;
solutionOpen: boolean;
pageNo: number;
solution: string | null;
challengeFiles: ChallengeFiles;
}
function TimelineInner({
idToNameMap,
sortedTimeline,
totalPages,
class TimelineInner extends Component<TimelineInnerProps, TimeLineInnerState> {
constructor(props: TimelineInnerProps) {
super(props);
completedMap,
t,
username
}: TimelineInnerProps) {
const [solutionToView, setSolutionToView] = useState<string | null>(null);
const [solutionOpen, setSolutionOpen] = useState(false);
const [pageNo, setPageNo] = useState(1);
const [solution, setSolution] = useState<string | null>(null);
const [challengeFiles, setChallengeFiles] = useState<ChallengeFiles>(null);
this.state = {
solutionToView: null,
solutionOpen: false,
pageNo: 1,
solution: null,
challengeFiles: null
};
this.closeSolution = this.closeSolution.bind(this);
this.renderCompletion = this.renderCompletion.bind(this);
this.viewSolution = this.viewSolution.bind(this);
this.firstPage = this.firstPage.bind(this);
this.prevPage = this.prevPage.bind(this);
this.nextPage = this.nextPage.bind(this);
this.lastPage = this.lastPage.bind(this);
this.renderViewButton = this.renderViewButton.bind(this);
function viewSolution(
id: string,
solution_: string,
challengeFiles_: ChallengeFiles
): void {
setSolutionToView(id);
setSolutionOpen(true);
setSolution(solution_);
setChallengeFiles(challengeFiles_);
}
renderViewButton(
function closeSolution(): void {
setSolutionToView(null);
setSolutionOpen(false);
setSolution(null);
setChallengeFiles(null);
}
function firstPage(): void {
setPageNo(1);
}
function nextPage(): void {
setPageNo(prev => prev + 1);
}
function prevPage(): void {
setPageNo(prev => prev - 1);
}
function lastPage(): void {
setPageNo(totalPages);
}
function renderViewButton(
id: string,
challengeFiles: ChallengeFiles,
githubLink: string,
solution: string
): React.ReactNode {
const { t } = this.props;
if (challengeFiles?.length) {
return (
<Button
@@ -110,7 +125,7 @@ class TimelineInner extends Component<TimelineInnerProps, TimeLineInnerState> {
bsStyle='primary'
className='btn-invert'
id={`btn-for-${id}`}
onClick={() => this.viewSolution(id, solution, challengeFiles)}
onClick={() => viewSolution(id, solution, challengeFiles)}
>
{t('buttons.show-code')}
</Button>
@@ -163,8 +178,7 @@ class TimelineInner extends Component<TimelineInnerProps, TimeLineInnerState> {
}
}
renderCompletion(completed: SortedTimeline): JSX.Element {
const { idToNameMap, username } = this.props;
function renderCompletion(completed: SortedTimeline): JSX.Element {
const { id, challengeFiles, githubLink, solution } = completed;
const completedDate = new Date(completed.completedDate);
// @ts-expect-error idToNameMap is not a <string, string> Map...
@@ -184,9 +198,7 @@ class TimelineInner extends Component<TimelineInnerProps, TimeLineInnerState> {
<Link to={challengePath as string}>{challengeTitle}</Link>
)}
</td>
<td>
{this.renderViewButton(id, challengeFiles, githubLink, solution)}
</td>
<td>{renderViewButton(id, challengeFiles, githubLink, solution)}</td>
<td className='text-center'>
<time dateTime={completedDate.toISOString()}>
{completedDate.toLocaleString([localeCode, 'en-US'], {
@@ -199,127 +211,72 @@ class TimelineInner extends Component<TimelineInnerProps, TimeLineInnerState> {
</tr>
);
}
viewSolution(
id: string,
solution: string,
challengeFiles: ChallengeFiles
): void {
this.setState(state => ({
...state,
solutionToView: id,
solutionOpen: true,
solution,
challengeFiles
}));
}
closeSolution() {
this.setState(state => ({
...state,
solutionToView: null,
solutionOpen: false,
solution: null,
challengeFiles: null
}));
}
const id = solutionToView;
const startIndex = (pageNo - 1) * ITEMS_PER_PAGE;
const endIndex = pageNo * ITEMS_PER_PAGE;
firstPage() {
this.setState({
pageNo: 1
});
}
nextPage() {
this.setState(state => ({
pageNo: state.pageNo + 1
}));
}
prevPage() {
this.setState(state => ({
pageNo: state.pageNo - 1
}));
}
lastPage() {
this.setState((_, props) => ({
pageNo: props.totalPages
}));
}
render() {
const {
completedMap,
idToNameMap,
username,
sortedTimeline,
t,
totalPages = 1
} = this.props;
const { solutionToView: id, solutionOpen, pageNo = 1 } = this.state;
const startIndex = (pageNo - 1) * ITEMS_PER_PAGE;
const endIndex = pageNo * ITEMS_PER_PAGE;
return (
<FullWidthRow>
<h2 className='text-center'>{t('profile.timeline')}</h2>
{completedMap.length === 0 ? (
<p className='text-center'>
{t('profile.none-completed')}&nbsp;
<Link to='/learn'>{t('profile.get-started')}</Link>
</p>
) : (
<Table condensed={true} striped={true}>
<thead>
<tr>
<th>{t('profile.challenge')}</th>
<th>{t('settings.labels.solution')}</th>
<th className='text-center'>{t('profile.completed')}</th>
</tr>
</thead>
<tbody>
{sortedTimeline
.slice(startIndex, endIndex)
.map(this.renderCompletion)}
</tbody>
</Table>
)}
{id && (
<Modal
aria-labelledby='contained-modal-title'
onHide={this.closeSolution}
show={solutionOpen}
>
<Modal.Header closeButton={true}>
<Modal.Title id='contained-modal-title'>
{`${username}'s Solution to ${
// @ts-expect-error Need better TypeDef for this
idToNameMap.get(id).challengeTitle as string
}`}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<SolutionViewer
challengeFiles={this.state.challengeFiles}
solution={this.state.solution ?? ''}
/>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.closeSolution}>{t('buttons.close')}</Button>
</Modal.Footer>
</Modal>
)}
{totalPages > 1 && (
<TimelinePagination
firstPage={this.firstPage}
lastPage={this.lastPage}
nextPage={this.nextPage}
pageNo={pageNo}
prevPage={this.prevPage}
totalPages={totalPages}
/>
)}
</FullWidthRow>
);
}
return (
<FullWidthRow>
<h2 className='text-center'>{t('profile.timeline')}</h2>
{completedMap.length === 0 ? (
<p className='text-center'>
{t('profile.none-completed')}&nbsp;
<Link to='/learn'>{t('profile.get-started')}</Link>
</p>
) : (
<Table condensed={true} striped={true}>
<thead>
<tr>
<th>{t('profile.challenge')}</th>
<th>{t('settings.labels.solution')}</th>
<th className='text-center'>{t('profile.completed')}</th>
</tr>
</thead>
<tbody>
{sortedTimeline.slice(startIndex, endIndex).map(renderCompletion)}
</tbody>
</Table>
)}
{id && (
<Modal
aria-labelledby='contained-modal-title'
onHide={closeSolution}
show={solutionOpen}
>
<Modal.Header closeButton={true}>
<Modal.Title id='contained-modal-title'>
{`${username}'s Solution to ${
// @ts-expect-error Need better TypeDef for this
idToNameMap.get(id).challengeTitle as string
}`}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<SolutionViewer
challengeFiles={challengeFiles}
solution={solution ?? ''}
/>
</Modal.Body>
<Modal.Footer>
<Button onClick={closeSolution}>{t('buttons.close')}</Button>
</Modal.Footer>
</Modal>
)}
{totalPages > 1 && (
<TimelinePagination
firstPage={firstPage}
lastPage={lastPage}
nextPage={nextPage}
pageNo={pageNo}
prevPage={prevPage}
totalPages={totalPages}
/>
)}
</FullWidthRow>
);
}
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call*/
function useIdToNameMap(): Map<string, string> {
const {

View File

@@ -1,5 +1,5 @@
import { Button, Panel } from '@freecodecamp/react-bootstrap';
import React, { Component } from 'react';
import React, { useState } from 'react';
import { TFunction, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@@ -18,11 +18,6 @@ type DangerZoneProps = {
t: TFunction;
};
type DangerZoneState = {
reset: boolean;
delete: boolean;
};
const mapStateToProps = () => ({});
const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
@@ -33,79 +28,65 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
dispatch
);
class DangerZone extends Component<DangerZoneProps, DangerZoneState> {
static displayName: string;
constructor(props: DangerZoneProps) {
super(props);
this.state = {
reset: false,
delete: false
};
function DangerZone({ deleteAccount, resetProgress, t }: DangerZoneProps) {
const [reset, setReset] = useState(false);
const [delete_, setDelete] = useState(false);
// delete is reserved
function toggleResetModal(): void {
setReset(prev => !prev);
}
toggleResetModal = () => {
return this.setState(state => ({
...state,
reset: !state.reset
}));
};
function toggleDeleteModal(): void {
setDelete(prev => !prev);
}
toggleDeleteModal = () => {
return this.setState(state => ({
...state,
delete: !state.delete
}));
};
render() {
const { deleteAccount, resetProgress, t } = this.props;
return (
<div className='danger-zone text-center'>
<FullWidthRow>
<Panel bsStyle='danger'>
<Panel.Heading>{t('settings.danger.heading')}</Panel.Heading>
return (
<div className='danger-zone text-center'>
<FullWidthRow>
<Panel bsStyle='danger'>
<Panel.Heading>{t('settings.danger.heading')}</Panel.Heading>
<Spacer />
<p>{t('settings.danger.be-careful')}</p>
<FullWidthRow>
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={toggleResetModal}
type='button'
>
{t('settings.danger.reset')}
</Button>
<ButtonSpacer />
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={toggleDeleteModal}
type='button'
>
{t('settings.danger.delete')}
</Button>
<Spacer />
<p>{t('settings.danger.be-careful')}</p>
<FullWidthRow>
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={() => this.toggleResetModal()}
type='button'
>
{t('settings.danger.reset')}
</Button>
<ButtonSpacer />
<Button
block={true}
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={() => this.toggleDeleteModal()}
type='button'
>
{t('settings.danger.delete')}
</Button>
<Spacer />
</FullWidthRow>
</Panel>
</FullWidthRow>
</Panel>
<ResetModal
onHide={() => this.toggleResetModal()}
reset={resetProgress}
show={this.state.reset}
/>
<DeleteModal
delete={deleteAccount}
onHide={() => this.toggleDeleteModal()}
show={this.state.delete}
/>
</FullWidthRow>
</div>
);
}
<ResetModal
onHide={toggleResetModal}
reset={resetProgress}
show={reset}
/>
<DeleteModal
delete={deleteAccount}
onHide={toggleDeleteModal}
show={delete_}
/>
</FullWidthRow>
</div>
);
}
DangerZone.displayName = 'DangerZone';

View File

@@ -7,7 +7,7 @@ import {
Button
} from '@freecodecamp/react-bootstrap';
import { Link } from 'gatsby';
import React, { Component } from 'react';
import React, { useState } from 'react';
import { TFunction, Trans, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@@ -36,70 +36,49 @@ type EmailProps = {
updateQuincyEmail: (sendQuincyEmail: boolean) => void;
};
type EmailState = {
emailForm: {
currentEmail: string;
newEmail: string;
confirmNewEmail: string;
isPristine: boolean;
};
};
export function UpdateEmailButton(this: EmailSettings): JSX.Element {
const { t } = this.props;
return (
<Link style={{ textDecoration: 'none' }} to='/update-email'>
<Button block={true} bsSize='lg' bsStyle='primary'>
{t('buttons.edit')}
</Button>
</Link>
);
interface EmailForm {
currentEmail: string;
newEmail: string;
confirmNewEmail: string;
isPristine: boolean;
}
class EmailSettings extends Component<EmailProps, EmailState> {
static displayName: string;
constructor(props: EmailProps) {
super(props);
function EmailSettings({
email,
isEmailVerified,
sendQuincyEmail,
t,
updateMyEmail,
updateQuincyEmail
}: EmailProps): JSX.Element {
const [emailForm, setEmailForm] = useState<EmailForm>({
currentEmail: email,
newEmail: '',
confirmNewEmail: '',
isPristine: true
});
this.state = {
emailForm: {
currentEmail: props.email,
newEmail: '',
confirmNewEmail: '',
isPristine: true
}
function handleSubmit(e: React.FormEvent): void {
e.preventDefault();
updateMyEmail(emailForm.newEmail);
}
function createHandleEmailFormChange(
key: 'newEmail' | 'confirmNewEmail'
): (e: React.ChangeEvent<HTMLInputElement>) => void {
return e => {
e.preventDefault();
const userInput = e.target.value.slice();
setEmailForm(prev => ({
...prev,
[key]: userInput,
isPristine: userInput === prev.currentEmail
}));
};
}
handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const {
emailForm: { newEmail }
} = this.state;
const { updateMyEmail } = this.props;
return updateMyEmail(newEmail);
};
createHandleEmailFormChange =
(key: 'newEmail' | 'confirmNewEmail') =>
(e: React.FormEvent<HTMLInputElement>) => {
e.preventDefault();
const userInput = (e.target as HTMLInputElement).value.slice();
return this.setState(state => ({
emailForm: {
...state.emailForm,
[key]: userInput,
isPristine: userInput === state.emailForm.currentEmail
}
}));
};
getValidationForNewEmail = () => {
const {
emailForm: { newEmail, currentEmail }
} = this.state;
const { t } = this.props;
function getValidationForNewEmail() {
const { newEmail, currentEmail } = emailForm;
if (!maybeEmailRE.test(newEmail)) {
return {
state: null,
@@ -120,14 +99,10 @@ class EmailSettings extends Component<EmailProps, EmailState> {
message: t('validation.invalid-email')
};
}
};
getValidationForConfirmEmail = () => {
const {
emailForm: { confirmNewEmail, newEmail }
} = this.state;
const { t } = this.props;
}
function getValidationForConfirmEmail() {
const { confirmNewEmail, newEmail } = emailForm;
if (!maybeEmailRE.test(newEmail)) {
return {
state: null,
@@ -146,115 +121,109 @@ class EmailSettings extends Component<EmailProps, EmailState> {
message: ''
};
}
};
}
render() {
const {
emailForm: { newEmail, confirmNewEmail, currentEmail, isPristine }
} = this.state;
const { newEmail, confirmNewEmail, currentEmail, isPristine } = emailForm;
const { isEmailVerified, updateQuincyEmail, sendQuincyEmail, t } =
this.props;
const { state: newEmailValidation, message: newEmailValidationMessage } =
getValidationForNewEmail();
const { state: newEmailValidation, message: newEmailValidationMessage } =
this.getValidationForNewEmail();
const {
state: confirmEmailValidation,
message: confirmEmailValidationMessage
} = getValidationForConfirmEmail();
const {
state: confirmEmailValidation,
message: confirmEmailValidationMessage
} = this.getValidationForConfirmEmail();
if (!currentEmail) {
return (
<div>
<FullWidthRow>
<p className='large-p text-center'>{t('settings.email.missing')}</p>
</FullWidthRow>
<FullWidthRow>
<UpdateEmailButton />
</FullWidthRow>
</div>
);
}
if (!currentEmail) {
return (
<div className='email-settings'>
<SectionHeader>{t('settings.email.heading')}</SectionHeader>
{isEmailVerified ? null : (
<FullWidthRow>
<HelpBlock>
<Alert
bsStyle='info'
className='text-center'
closeLabel={t('buttons.close')}
>
{t('settings.email.not-verified')}
<br />
<Trans i18nKey='settings.email.check'>
<Link to='/update-email' />
</Trans>
</Alert>
</HelpBlock>
</FullWidthRow>
)}
<div>
<FullWidthRow>
<form id='form-update-email' onSubmit={this.handleSubmit}>
<FormGroup controlId='current-email'>
<ControlLabel>{t('settings.email.current')}</ControlLabel>
<FormControl.Static>{currentEmail}</FormControl.Static>
</FormGroup>
<FormGroup
controlId='new-email'
validationState={newEmailValidation}
>
<ControlLabel>{t('settings.email.new')}</ControlLabel>
<FormControl
onChange={this.createHandleEmailFormChange('newEmail')}
type='email'
value={newEmail}
/>
{newEmailValidationMessage ? (
<HelpBlock>{newEmailValidationMessage}</HelpBlock>
) : null}
</FormGroup>
<FormGroup
controlId='confirm-email'
validationState={confirmEmailValidation}
>
<ControlLabel>{t('settings.email.confirm')}</ControlLabel>
<FormControl
onChange={this.createHandleEmailFormChange('confirmNewEmail')}
type='email'
value={confirmNewEmail}
/>
{confirmEmailValidationMessage ? (
<HelpBlock>{confirmEmailValidationMessage}</HelpBlock>
) : null}
</FormGroup>
<BlockSaveButton
disabled={
newEmailValidation !== 'success' ||
confirmEmailValidation !== 'success' ||
isPristine
}
/>
</form>
<p className='large-p text-center'>{t('settings.email.missing')}</p>
</FullWidthRow>
<Spacer />
<FullWidthRow>
<form id='form-quincy-email' onSubmit={this.handleSubmit}>
<ToggleSetting
action={t('settings.email.weekly')}
flag={sendQuincyEmail}
flagName='sendQuincyEmail'
offLabel={t('buttons.no-thanks')}
onLabel={t('buttons.yes-please')}
toggleFlag={() => updateQuincyEmail(!sendQuincyEmail)}
/>
</form>
<Link style={{ textDecoration: 'none' }} to='/update-email'>
<Button block={true} bsSize='lg' bsStyle='primary'>
{t('buttons.edit')}
</Button>
</Link>
</FullWidthRow>
</div>
);
}
return (
<div className='email-settings'>
<SectionHeader>{t('settings.email.heading')}</SectionHeader>
{isEmailVerified ? null : (
<FullWidthRow>
<HelpBlock>
<Alert
bsStyle='info'
className='text-center'
closeLabel={t('buttons.close')}
>
{t('settings.email.not-verified')}
<br />
<Trans i18nKey='settings.email.check'>
<Link to='/update-email' />
</Trans>
</Alert>
</HelpBlock>
</FullWidthRow>
)}
<FullWidthRow>
<form id='form-update-email' onSubmit={handleSubmit}>
<FormGroup controlId='current-email'>
<ControlLabel>{t('settings.email.current')}</ControlLabel>
<FormControl.Static>{currentEmail}</FormControl.Static>
</FormGroup>
<FormGroup controlId='new-email' validationState={newEmailValidation}>
<ControlLabel>{t('settings.email.new')}</ControlLabel>
<FormControl
onChange={createHandleEmailFormChange('newEmail')}
type='email'
value={newEmail}
/>
{newEmailValidationMessage ? (
<HelpBlock>{newEmailValidationMessage}</HelpBlock>
) : null}
</FormGroup>
<FormGroup
controlId='confirm-email'
validationState={confirmEmailValidation}
>
<ControlLabel>{t('settings.email.confirm')}</ControlLabel>
<FormControl
onChange={createHandleEmailFormChange('confirmNewEmail')}
type='email'
value={confirmNewEmail}
/>
{confirmEmailValidationMessage ? (
<HelpBlock>{confirmEmailValidationMessage}</HelpBlock>
) : null}
</FormGroup>
<BlockSaveButton
disabled={
newEmailValidation !== 'success' ||
confirmEmailValidation !== 'success' ||
isPristine
}
/>
</form>
</FullWidthRow>
<Spacer />
<FullWidthRow>
<form id='form-quincy-email' onSubmit={handleSubmit}>
<ToggleSetting
action={t('settings.email.weekly')}
flag={sendQuincyEmail}
flagName='sendQuincyEmail'
offLabel={t('buttons.no-thanks')}
onLabel={t('buttons.yes-please')}
toggleFlag={() => updateQuincyEmail(!sendQuincyEmail)}
/>
</form>
</FullWidthRow>
</div>
);
}
EmailSettings.displayName = 'EmailSettings';

View File

@@ -1,5 +1,5 @@
import { Button, Form } from '@freecodecamp/react-bootstrap';
import React, { Component } from 'react';
import React from 'react';
import { TFunction, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@@ -44,142 +44,145 @@ type PrivacyProps = {
};
};
class PrivacySettings extends Component<PrivacyProps> {
static displayName: string;
handleSubmit = (e: React.FormEvent) => e.preventDefault();
toggleFlag = (flag: string) => () => {
const privacyValues = { ...this.props.user.profileUI };
privacyValues[flag as keyof ProfileUIType] =
!privacyValues[flag as keyof ProfileUIType];
this.props.submitProfileUI(privacyValues);
};
render() {
const { t, user } = this.props;
const {
isLocked = true,
showAbout = false,
showCerts = false,
showDonation = false,
showHeatMap = false,
showLocation = false,
showName = false,
showPoints = false,
showPortfolio = false,
showTimeLine = false
} = user.profileUI;
return (
<div className='privacy-settings' id='privacy-settings'>
<SectionHeader>{t('settings.headings.privacy')}</SectionHeader>
<FullWidthRow>
<p>{t('settings.privacy')}</p>
<Form inline={true} onSubmit={this.handleSubmit}>
<ToggleSetting
action={t('settings.labels.my-profile')}
explain={t('settings.disabled')}
flag={isLocked}
flagName='isLocked'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('isLocked')}
/>
<ToggleSetting
action={t('settings.labels.my-name')}
explain={t('settings.private-name')}
flag={!showName}
flagName='name'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showName')}
/>
<ToggleSetting
action={t('settings.labels.my-location')}
flag={!showLocation}
flagName='showLocation'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showLocation')}
/>
<ToggleSetting
action={t('settings.labels.my-about')}
flag={!showAbout}
flagName='showAbout'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showAbout')}
/>
<ToggleSetting
action={t('settings.labels.my-points')}
flag={!showPoints}
flagName='showPoints'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showPoints')}
/>
<ToggleSetting
action={t('settings.labels.my-heatmap')}
flag={!showHeatMap}
flagName='showHeatMap'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showHeatMap')}
/>
<ToggleSetting
action={t('settings.labels.my-certs')}
explain={t('settings.disabled')}
flag={!showCerts}
flagName='showCerts'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showCerts')}
/>
<ToggleSetting
action={t('settings.labels.my-portfolio')}
flag={!showPortfolio}
flagName='showPortfolio'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showPortfolio')}
/>
<ToggleSetting
action={t('settings.labels.my-timeline')}
flag={!showTimeLine}
flagName='showTimeLine'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showTimeLine')}
/>
<ToggleSetting
action={t('settings.labels.my-donations')}
flag={!showDonation}
flagName='showPortfolio'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={this.toggleFlag('showDonation')}
/>
</Form>
</FullWidthRow>
<FullWidthRow>
<Spacer />
<p>{t('settings.data')}</p>
<Button
block={true}
bsSize='lg'
bsStyle='primary'
download={`${user.username}.json`}
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(user)
)}`}
>
{t('buttons.download-data')}
</Button>
</FullWidthRow>
</div>
);
function PrivacySettings({
submitProfileUI,
t,
user
}: PrivacyProps): JSX.Element {
function handleSubmit(e: React.FormEvent): void {
e.preventDefault();
}
function toggleFlag(flag: string): () => void {
return () => {
const privacyValues = { ...user.profileUI };
privacyValues[flag as keyof ProfileUIType] =
!privacyValues[flag as keyof ProfileUIType];
submitProfileUI(privacyValues);
};
}
const {
isLocked = true,
showAbout = false,
showCerts = false,
showDonation = false,
showHeatMap = false,
showLocation = false,
showName = false,
showPoints = false,
showPortfolio = false,
showTimeLine = false
} = user.profileUI;
return (
<div className='privacy-settings' id='privacy-settings'>
<SectionHeader>{t('settings.headings.privacy')}</SectionHeader>
<FullWidthRow>
<p>{t('settings.privacy')}</p>
<Form inline={true} onSubmit={handleSubmit}>
<ToggleSetting
action={t('settings.labels.my-profile')}
explain={t('settings.disabled')}
flag={isLocked}
flagName='isLocked'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('isLocked')}
/>
<ToggleSetting
action={t('settings.labels.my-name')}
explain={t('settings.private-name')}
flag={!showName}
flagName='name'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showName')}
/>
<ToggleSetting
action={t('settings.labels.my-location')}
flag={!showLocation}
flagName='showLocation'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showLocation')}
/>
<ToggleSetting
action={t('settings.labels.my-about')}
flag={!showAbout}
flagName='showAbout'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showAbout')}
/>
<ToggleSetting
action={t('settings.labels.my-points')}
flag={!showPoints}
flagName='showPoints'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showPoints')}
/>
<ToggleSetting
action={t('settings.labels.my-heatmap')}
flag={!showHeatMap}
flagName='showHeatMap'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showHeatMap')}
/>
<ToggleSetting
action={t('settings.labels.my-certs')}
explain={t('settings.disabled')}
flag={!showCerts}
flagName='showCerts'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showCerts')}
/>
<ToggleSetting
action={t('settings.labels.my-portfolio')}
flag={!showPortfolio}
flagName='showPortfolio'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showPortfolio')}
/>
<ToggleSetting
action={t('settings.labels.my-timeline')}
flag={!showTimeLine}
flagName='showTimeLine'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showTimeLine')}
/>
<ToggleSetting
action={t('settings.labels.my-donations')}
flag={!showDonation}
flagName='showPortfolio'
offLabel={t('buttons.public')}
onLabel={t('buttons.private')}
toggleFlag={toggleFlag('showDonation')}
/>
</Form>
</FullWidthRow>
<FullWidthRow>
<Spacer />
<p>{t('settings.data')}</p>
<Button
block={true}
bsSize='lg'
bsStyle='primary'
download={`${user.username}.json`}
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(user)
)}`}
>
{t('buttons.download-data')}
</Button>
</FullWidthRow>
</div>
);
}
PrivacySettings.displayName = 'PrivacySettings';