diff --git a/src/api/api.js b/src/api/api.js index 503ecc4ef..b0227d485 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -518,11 +518,17 @@ class API { } const headers = { Authorization: `Bearer ${authToken}`, - requestOrigin + requestOrigin, + newVersion: 'true' }; const { data } = await this.axiosInstance.get(`/subscriber/${userId}`, { headers }); + + if (data && !data.success) { + throw data; + } + return data; } diff --git a/src/components/Board/Board.actions.js b/src/components/Board/Board.actions.js index cee628515..2602b092b 100644 --- a/src/components/Board/Board.actions.js +++ b/src/components/Board/Board.actions.js @@ -758,8 +758,20 @@ export function updateApiObjects( .then(res => { const updatedChildBoardId = res.id; //create - update parent board + const updateTilesParentBoard = () => + parentBoard.tiles.map(tile => { + if (tile.loadBoard === childBoard.id) + return { ...tile, loadBoard: updatedChildBoardId }; + return tile; + }); + const updatedParentBoard = { + ...parentBoard, + tiles: createParentBoard + ? updateTilesParentBoard() + : parentBoard.tiles + }; const action = createParentBoard ? createApiBoard : updateApiBoard; - return dispatch(action(parentBoard, parentBoard.id)) + return dispatch(action(updatedParentBoard, parentBoard.id)) .then(res => { const updatedParentBoardId = res.id; //add new boards to the active communicator diff --git a/src/components/Board/Board.container.js b/src/components/Board/Board.container.js index ebee0820f..14afd000e 100644 --- a/src/components/Board/Board.container.js +++ b/src/components/Board/Board.container.js @@ -644,7 +644,7 @@ export class BoardContainer extends Component { // Loggedin user? if ('name' in userData && 'email' in userData) { - await this.handleApiUpdates(tile); + await this.handleApiUpdates(tile); // this function could mutate tthe tile return; } @@ -1125,6 +1125,7 @@ export class BoardContainer extends Component { //update the parent updateBoard(parentBoardData); } + // Untill here all is with shorts ids //api updates if (tile && tile.type === 'board') { //child becomes parent @@ -1166,6 +1167,9 @@ export class BoardContainer extends Component { ) .then(parentBoardId => { if (createParentBoard) { + /* Here the parentBoardData is not updated with the values + that updatedApiObjects store on the API. Inside the boards are already updated + an the value is not replaced because the oldboard Id was replaced on the updateApiObjects inside createApiBoardSuccess */ replaceBoard( { ...parentBoardData }, { ...parentBoardData, id: parentBoardId } diff --git a/src/components/Board/Board.reducer.js b/src/components/Board/Board.reducer.js index 03b5fd327..b7db1bd49 100644 --- a/src/components/Board/Board.reducer.js +++ b/src/components/Board/Board.reducer.js @@ -71,6 +71,8 @@ function tileReducer(board, action) { return { ...board, tiles: [...board.tiles, { ...action.tile }] + /* some times when a tile folder is created here the last tile change loadBoard to a long Id with no reason + action tile before this copy has a short ID*/ }; case DELETE_TILES: return { @@ -165,6 +167,8 @@ function boardReducer(state = initialState, action) { if (prev.id !== current.id) { const boardIndex = boards.findIndex(b => b.id === prev.id); + /* On create a parent board the prev board doesn't exist with a short Id + because is already replaced by a long one */ if (boardIndex >= 0) { boards[boardIndex] = current; } diff --git a/src/components/Settings/Settings.component.js b/src/components/Settings/Settings.component.js index de13fce71..bbfa9ce4c 100644 --- a/src/components/Settings/Settings.component.js +++ b/src/components/Settings/Settings.component.js @@ -25,7 +25,7 @@ import FullScreenDialog from '../UI/FullScreenDialog'; import Paper from '@material-ui/core/Paper'; import UserIcon from '../UI/UserIcon'; import SettingsTour from './SettingsTour.component'; -import { isCordova, isAndroid, isIOS } from '../../cordova-util'; +import { isCordova, isAndroid } from '../../cordova-util'; import './Settings.css'; import { CircularProgress } from '@material-ui/core'; @@ -96,7 +96,7 @@ export class Settings extends PureComponent { } ]; - if (!isIOS() && !isInFreeCountry) { + if (!isInFreeCountry) { const subscribeSection = { icon: , text: messages.subscribe, diff --git a/src/components/Settings/Subscribe/Subscribe.component.js b/src/components/Settings/Subscribe/Subscribe.component.js index 1188b7721..0675b22f0 100644 --- a/src/components/Settings/Subscribe/Subscribe.component.js +++ b/src/components/Settings/Subscribe/Subscribe.component.js @@ -9,6 +9,7 @@ import './Subscribe.css'; import SubscriptionInfo from './SubscriptionInfo'; import SubscriptionPlans from './SubscriptionPlans'; +import { CircularProgress } from '@material-ui/core'; const propTypes = { /** @@ -29,7 +30,8 @@ const propTypes = { onRefreshSubscription: PropTypes.func, onSubscribeCancel: PropTypes.func.isRequired, onCancelSubscription: PropTypes.func.isRequired, - cancelSubscriptionStatus: PropTypes.string.isRequired + cancelSubscriptionStatus: PropTypes.string.isRequired, + updatingStatus: PropTypes.bool.isRequired }; const defaultProps = { @@ -46,7 +48,8 @@ const Subscribe = ({ onSubscribeCancel, onPaypalApprove, onCancelSubscription, - cancelSubscriptionStatus + cancelSubscriptionStatus, + updatingStatus }) => { return (
@@ -56,7 +59,11 @@ const Subscribe = ({ onClose={onClose} // fullWidth > - {!subscription.isSubscribed ? ( + {updatingStatus ? ( +
+ +
+ ) : !subscription.isSubscribed ? ( { return p.id === product.subscriptionId; @@ -207,7 +210,10 @@ export class SubscribeContainer extends PureComponent { try { await window.CdvPurchase.store.update(); offers = prod.offers; - offer = offers.find(offer => offer.tags[0] === product.tag); + const findCallback = isAndroid() + ? offer => offer.tags[0] === product.tag + : offer => offer.productId === product.subscriptionId; + offer = offers.find(findCallback); } catch (err) { console.error('Cannot subscribe product. Error: ', err.message); this.handleError(err); @@ -215,6 +221,11 @@ export class SubscribeContainer extends PureComponent { } } + const filterInAppPurchaseIOSTransactions = uniqueReceipt => + uniqueReceipt.transactions.filter( + transaction => transaction.transactionId !== 'appstore.application' + ); + try { // update the api const requestOrigin = @@ -225,40 +236,80 @@ export class SubscribeContainer extends PureComponent { // check if current subscriber already bought in this device if (localReceipts.length) { const lastReceipt = localReceipts.slice(-1)[0]; - if ( - lastReceipt && - lastReceipt?.transactions[0]?.nativePurchase?.orderId !== - subscriber.transaction?.transactionId - ) { - this.handleError({ - code: '0001', - message: intl.formatMessage(messages.googleAccountAlreadyOwns) - }); - return; + if (isAndroid()) { + if ( + lastReceipt && + lastReceipt?.transactions[0]?.nativePurchase?.orderId !== + subscriber.transaction?.transactionId + ) { + this.handleError({ + code: '0001', + message: intl.formatMessage(messages.googleAccountAlreadyOwns) + }); + return; + } + } + if (isIOS()) { + //IOS have a unique receipt here => 'lastReceipt' + const inAppPurchaseTransactions = filterInAppPurchaseIOSTransactions( + localReceipts[0] + ); + + const lastTransaction = inAppPurchaseTransactions.slice(-1)[0]; + if ( + inAppPurchaseTransactions.length > 0 && + lastTransaction?.transactionId !== + subscriber.transaction?.transactionId + ) { + this.handleError({ + code: '0001', + message: intl.formatMessage(messages.appleAccountAlreadyOwns) + }); + return; + } } } + await API.updateSubscriber(apiProduct); // proceed with the purchase - if (isAndroid()) { + if (isAndroid() || isIOS()) { const order = await window.CdvPurchase.store.order(offer); if (order && order.isError) throw order; updateSubscription({ ownedProduct: { ...product, - platform: 'android-playstore' + platform: isAndroid() ? 'android-playstore' : 'app-store' } }); } } catch (err) { - if (err.response?.data.error === 'subscriber not found') { + const isSubscriberNotFound = + (err.response?.data?.error || err.error) === 'subscriber not found'; + + if (isSubscriberNotFound) { // check if current subscriber already bought in this device - if (localReceipts.length) { - this.handleError({ - code: '0001', - message: intl.formatMessage(messages.googleAccountAlreadyOwns) - }); - return; + if (isAndroid()) { + if (localReceipts.length) { + this.handleError({ + code: '0001', + message: intl.formatMessage(messages.googleAccountAlreadyOwns) + }); + return; + } + } + if (isIOS()) { + const localInAppPurchaseTransactions = filterInAppPurchaseIOSTransactions( + localReceipts[0] + ); + + if (localInAppPurchaseTransactions.length) { + this.handleError({ + code: '0001', + message: intl.formatMessage(messages.appleAccountAlreadyOwns) + }); + return; + } } try { const newSubscriber = { @@ -269,13 +320,13 @@ export class SubscribeContainer extends PureComponent { }; const res = await API.createSubscriber(newSubscriber); updateSubscriberId(res._id); - if (isAndroid()) { + if (isAndroid() || isIOS()) { const order = await window.CdvPurchase.store.order(offer); if (order && order.isError) throw order; updateSubscription({ ownedProduct: { ...product, - platform: 'android-playstore' + platform: isAndroid() ? 'android-playstore' : 'app-store' } }); } @@ -306,6 +357,7 @@ export class SubscribeContainer extends PureComponent { onPaypalApprove={this.handlePaypalApprove} onCancelSubscription={this.handleCancelSubscription} cancelSubscriptionStatus={this.state.cancelSubscriptionStatus} + updatingStatus={this.state.updatingStatus} /> ); } diff --git a/src/components/Settings/Subscribe/Subscribe.css b/src/components/Settings/Subscribe/Subscribe.css index 687df3158..97b778ad0 100644 --- a/src/components/Settings/Subscribe/Subscribe.css +++ b/src/components/Settings/Subscribe/Subscribe.css @@ -1,3 +1,10 @@ +.Subscribe__Loading__Container { + padding: 1em; + height: 92vh; + display: flex; + justify-content: center; + align-items: center; +} .Subscribe__Alert { margin: 8px 0; } diff --git a/src/components/Settings/Subscribe/Subscribe.messages.js b/src/components/Settings/Subscribe/Subscribe.messages.js index 37d4e4c6d..08fa05288 100644 --- a/src/components/Settings/Subscribe/Subscribe.messages.js +++ b/src/components/Settings/Subscribe/Subscribe.messages.js @@ -89,6 +89,11 @@ export default defineMessages({ id: 'cboard.components.Settings.Subscribe.not_subscribed', defaultMessage: 'You are not subscribed. ' }, + unverified: { + id: 'cboard.components.Settings.Subscribe.unverified', + defaultMessage: + 'An error occurred during validation of your purchase. Please refresh' + }, refresh: { id: 'cboard.components.Settings.Subscribe.refresh', defaultMessage: 'Refresh' @@ -148,6 +153,11 @@ export default defineMessages({ defaultMessage: 'It looks like your Google account has already purchased a product. Try restarting the app.' }, + appleAccountAlreadyOwns: { + id: 'cboard.components.Settings.Subscribe.appleAccountAlreadyOwns', + defaultMessage: + 'It looks like your Apple account has already purchased a product. Try restarting the app.' + }, fallback: { id: 'cboard.components.Settings.Subscribe.fallback', defaultMessage: 'Wait please...' diff --git a/src/components/Settings/Subscribe/SubscriptionInfo.js b/src/components/Settings/Subscribe/SubscriptionInfo.js index 40589f125..f90e62fdd 100644 --- a/src/components/Settings/Subscribe/SubscriptionInfo.js +++ b/src/components/Settings/Subscribe/SubscriptionInfo.js @@ -29,8 +29,8 @@ import { import RefreshIcon from '@material-ui/icons/Refresh'; import IconButton from '../../UI/IconButton'; -import { isAndroid, isElectron } from '../../../cordova-util'; -import { GOOGLE_PLAY_STORE_URL } from './Subscribe.constants'; +import { isAndroid, isElectron, isIOS } from '../../../cordova-util'; +import { APP_STORE_URL, GOOGLE_PLAY_STORE_URL } from './Subscribe.constants'; const propTypes = { ownedProduct: PropTypes.object.isRequired, @@ -131,13 +131,15 @@ const SubscriptionInfo = ({