Skip to content
23 changes: 4 additions & 19 deletions src/components/Account/Login/Login.actions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import API from '../../../api';
import { LOGIN_SUCCESS, LOGOUT } from './Login.constants';
import { addBoards } from '../../Board/Board.actions';
import {
changeVoice,
changePitch,
Expand Down Expand Up @@ -174,24 +173,10 @@ export function login({ email, password, activatedData }, type = 'local') {
? activatedData
: await API[apiMethod](email, password);

const apiBoards = loginData.boards || [];
const hasRemoteCommunicators =
loginData.communicators && loginData.communicators.length > 0;
const discardLocalChanges = hasRemoteCommunicators;

const { communicator } = getState();

const activeCommunicatorId = communicator.activeCommunicatorId;
let currentCommunicator = communicator.communicators.find(
communicator => communicator.id === activeCommunicatorId
);

if (loginData.communicators && loginData.communicators.length) {
const lastRemoteSavedCommunicatorIndex =
loginData.communicators.length - 1;
currentCommunicator =
loginData.communicators[lastRemoteSavedCommunicatorIndex]; //use the latest communicator
}
if (apiBoards.length && currentCommunicator) {
dispatch(addBoards(apiBoards));
}
if (type === 'local') {
dispatch(
disableTour({
Expand All @@ -203,7 +188,7 @@ export function login({ email, password, activatedData }, type = 'local') {
})
);
}
dispatch(loginSuccess(loginData));
dispatch(loginSuccess({ ...loginData, discardLocalChanges }));
await setAVoice({ loginData, dispatch, getState });
} catch (e) {
console.error(e);
Expand Down
39 changes: 37 additions & 2 deletions src/components/Board/Board.container.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ import {
verifyAndUpsertCommunicator
} from '../Communicator/Communicator.actions';
import { disableTour } from '../App/App.actions';
import { isLogged } from '../App/App.selectors';
import { showPremiumRequired } from '../../providers/SubscriptionProvider/SubscriptionProvider.actions';
import { isSubscriptionRequired } from '../../providers/SubscriptionProvider/SubscriptionProvider.selectors';
import UnauthenticatedEditModal from '../LoggedInFeature/UnauthenticatedEditModal';
import TileEditor from './TileEditor';
import messages from './Board.messages';
import Board from './Board.component';
Expand Down Expand Up @@ -208,7 +210,8 @@ export class BoardContainer extends Component {
isCbuilderBoard: false,
pinDialogOpen: false,
pinAttempt: '',
pinError: false
pinError: false,
showUnauthEditModal: false
};
constructor(props) {
super(props);
Expand Down Expand Up @@ -804,6 +807,11 @@ export class BoardContainer extends Component {
return;
}

if (this.state.isLocked && !this.props.isLogged) {
this.setState({ showUnauthEditModal: true });
return;
}

this.setState(
prevState => ({
isLocked: !prevState.isLocked,
Expand Down Expand Up @@ -1814,6 +1822,32 @@ export class BoardContainer extends Component {
isSymbolSearchTourEnabled={this.props.isSymbolSearchTourEnabled}
disableTour={this.props.disableTour}
/>
<UnauthenticatedEditModal
open={this.state.showUnauthEditModal}
onClose={() => this.setState({ showUnauthEditModal: false })}
onContinue={() => {
const {
showPremiumRequired,
isSubscriptionRequired,
setIsSaving
} = this.props;
this.setState(
{
showUnauthEditModal: false,
isLocked: false,
isSaving: false,
isSelecting: false,
selectedTileIds: []
},
() => {
setIsSaving(false);
if (isSubscriptionRequired) {
showPremiumRequired({ showTryPeriodFinishedMessages: true });
}
}
);
}}
/>
<PinDialog
open={this.state.pinDialogOpen}
onClose={this.handlePinDialogClose}
Expand Down Expand Up @@ -1872,7 +1906,8 @@ export const mapStateToProps = state => {
isUnlockedTourEnabled: liveHelp.isUnlockedTourEnabled,
isPremiumRequiredModalOpen: premiumRequiredModalState?.open,
improvedPhrase: board.improvedPhrase,
isSubscriptionRequired: isSubscriptionRequired(state)
isSubscriptionRequired: isSubscriptionRequired(state),
isLogged: isLogged(state)
};
};

Expand Down
41 changes: 40 additions & 1 deletion src/components/Board/Board.reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function tileReducer(board, action) {

function boardReducer(state = initialState, action) {
switch (action.type) {
case LOGIN_SUCCESS:
case LOGIN_SUCCESS: {
let activeBoardId = state.activeBoardId;
const userCommunicators = action.payload.communicators || [];
const activeCommunicator = userCommunicators.length
Expand All @@ -131,11 +131,50 @@ function boardReducer(state = initialState, action) {
activeCommunicator.rootBoard || initialState.activeBoardId;
}

if (action.payload.discardLocalChanges) {
const remoteBoards = (action.payload.boards || []).map(b =>
deepCopy(b)
);
const remoteIds = new Set(remoteBoards.map(b => b.id));
const defaults = deepCopy(initialBoardsState).filter(
b => !remoteIds.has(b.id)
);
const allBoards = [...defaults, ...remoteBoards];
const allIds = new Set(allBoards.map(b => b.id));
const discardActiveBoardId = allIds.has(activeBoardId)
? activeBoardId
: 'root';
return {
...state,
boards: allBoards,
syncMeta: allBoards.reduce((acc, b) => {
acc[b.id] = { status: SYNC_STATUS.SYNCED };
return acc;
}, {}),
activeBoardId: discardActiveBoardId,
navHistory: discardActiveBoardId ? [discardActiveBoardId] : []
};
}

const remoteBoards = (action.payload.boards || []).map(b => deepCopy(b));
const existingIds = new Set(state.boards.map(b => b.id));
const newBoards = remoteBoards.filter(
b => b?.id && !existingIds.has(b.id)
);
const addedSyncMeta = newBoards.reduce((acc, b) => {
acc[b.id] = {
status: isLocalBoard(b) ? SYNC_STATUS.PENDING : SYNC_STATUS.SYNCED
};
return acc;
}, {});
return {
...state,
boards: state.boards.concat(newBoards),
syncMeta: { ...state.syncMeta, ...addedSyncMeta },
activeBoardId,
navHistory: activeBoardId ? [activeBoardId] : []
};
}

case LOGOUT:
return {
Expand Down
10 changes: 8 additions & 2 deletions src/components/Communicator/Communicator.reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,22 @@ function communicatorReducer(state = initialState, action) {
communicator => communicator.id === state.activeCommunicatorId
);
switch (action.type) {
case LOGIN_SUCCESS:
case LOGIN_SUCCESS: {
const userCommunicators = action.payload.communicators || [];
const activeCommunicatorId = userCommunicators.length
? userCommunicators[userCommunicators.length - 1].id
: state.activeCommunicatorId;
const remoteIds = new Set(userCommunicators.map(c => c.id));
const base = (action.payload.discardLocalChanges
? deepCopy(defaultCommunicators)
: state.communicators
).filter(c => !remoteIds.has(c.id));
return {
...state,
activeCommunicatorId,
communicators: state.communicators.concat(userCommunicators)
communicators: base.concat(userCommunicators)
};
}

case LOGOUT:
return {
Expand Down
8 changes: 8 additions & 0 deletions src/components/LoggedInFeature/LoginRequiredModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
padding-bottom: 1em;
}

.buttons {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 0.75em;
}

@media (max-width: 480px), (max-height: 400px) {
.content {
margin: 2em 1em;
Expand Down
54 changes: 54 additions & 0 deletions src/components/LoggedInFeature/UnauthenticatedEditModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogContent from '@material-ui/core/DialogContent';
import { Typography } from '@material-ui/core';
import WarningIcon from '@material-ui/icons/Warning';
import { FormattedMessage } from 'react-intl';

import messages from './UnauthenticatedEditModal.messages';
import style from './LoginRequiredModal.module.css';

function UnauthenticatedEditModal({ open, onClose, onContinue }) {
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="md"
aria-labelledby="dialog"
>
<DialogContent className={style.content}>
<WarningIcon fontSize="large" color="action" />
<Typography variant="h3">
<FormattedMessage {...messages.title} />
</Typography>
<Typography className={style.dialogText} variant="h6">
<FormattedMessage {...messages.text} />
</Typography>
<div className={style.buttons}>
<Button
onClick={onContinue}
color="default"
variant="outlined"
size="large"
>
<FormattedMessage {...messages.continueEditing} />
</Button>
<Button
onClick={onClose}
color="primary"
variant="contained"
size="large"
component={Link}
to="/login-signup"
>
<FormattedMessage {...messages.loginSignup} />
</Button>
</div>
</DialogContent>
</Dialog>
);
}

export default UnauthenticatedEditModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineMessages } from 'react-intl';

export default defineMessages({
title: {
id: 'cboard.components.UnauthenticatedEditModal.title',
defaultMessage: 'You are not logged in'
},
text: {
id: 'cboard.components.UnauthenticatedEditModal.text',
defaultMessage:
'Your changes will be saved locally on this device only. Sign up to keep your boards synced across devices.'
},
continueEditing: {
id: 'cboard.components.UnauthenticatedEditModal.continueEditing',
defaultMessage: 'Continue editing'
},
loginSignup: {
id: 'cboard.components.UnauthenticatedEditModal.loginSignup',
defaultMessage: 'Login or Sign Up'
}
});
4 changes: 4 additions & 0 deletions src/translations/src/cboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
"cboard.components.LoginRequiredModal.featureBlockedTitle": "Login or Sign Up to use this feature",
"cboard.components.LoginRequiredModal.featureBlockedText": "This feature is disabled for anonymous users. To continue using it please sign up or login",
"cboard.components.LoginRequiredModal.loginSignupNow": "Login or Sign Up now",
"cboard.components.UnauthenticatedEditModal.title": "You are not logged in",
"cboard.components.UnauthenticatedEditModal.text": "Your changes will be saved locally on this device only. Sign up to keep your boards synced across devices.",
"cboard.components.UnauthenticatedEditModal.continueEditing": "Continue editing",
"cboard.components.UnauthenticatedEditModal.loginSignup": "Login or Sign Up",
"cboard.components.FormItems.PasswwordTextField.togglePasswordVisibility": "Toggle password visibility",
"cboard.components.SignUp.signUp": "Sign Up",
"cboard.components.SignUp.name": "Name",
Expand Down
Loading