Skip to content

Commit

Permalink
feat: update subscription module with email validation (#356)
Browse files Browse the repository at this point in the history
* feat: update subscription module with email validation

* chore: update decentraland-dapps
  • Loading branch information
braianj authored May 16, 2024
1 parent b05e939 commit 90073a7
Show file tree
Hide file tree
Showing 13 changed files with 394 additions and 33 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@sentry/react": "^7.64.0",
"@typechain/ethers-v5": "^10.0.0",
"connected-react-router": "^6.9.1",
"decentraland-dapps": "^19.10.0",
"decentraland-dapps": "^19.11.0",
"decentraland-transactions": "^2.6.0",
"decentraland-ui": "^5.14.0",
"decentraland-ui2": "^0.0.2",
Expand Down
1 change: 0 additions & 1 deletion src/lib/environment.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/modules/locations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const locations = {
root: () => '/',
confirmEmail: () => '/confirm-email',
signIn: () => '/sign-in',
settings: () => '/settings'
}
3 changes: 1 addition & 2 deletions src/modules/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { applyMiddleware, compose, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import createSagasMiddleware from 'redux-saga'
import { config } from '../config'
import { isTest } from '../lib/environment'
import { SET_DEPOSIT_STATUS, SET_WITHDRAWAL_STATUS, WATCH_DEPOSIT_STATUS_SUCCESS, WATCH_WITHDRAWAL_STATUS_SUCCESS } from './mana/actions'
import migrations from './migrations'
import { createRootReducer } from './reducer'
Expand All @@ -27,7 +26,7 @@ const rootReducer = storageReducerWrapper(createRootReducer(history))
const sagasMiddleware = createSagasMiddleware()
const loggerMiddleware = createLogger({
collapsed: () => true,
predicate: (_: any, action) => !isTest && (config.is(Env.DEVELOPMENT) || action.type.includes('Failure'))
predicate: (_: any, action) => config.is(Env.DEVELOPMENT) || action.type.includes('Failure')
})

const transactionMiddleware = createTransactionMiddleware()
Expand Down
30 changes: 28 additions & 2 deletions src/modules/subscription/actions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Subscription, SubscriptionDetails } from '@dcl/schemas'
import { SubscriptionDetails } from '@dcl/schemas'
import { action } from 'typesafe-actions'
import { SubscriptionFromClient } from './types'

export const GET_SUBSCRIPTIONS_REQUEST = '[Request] Get Subscriptions'
export const GET_SUBSCRIPTIONS_SUCCESS = '[Success] Get Subscriptions'
export const GET_SUBSCRIPTIONS_FAILURE = '[Failure] Get Subscriptions'

export const getSubscriptionsRequest = () => action(GET_SUBSCRIPTIONS_REQUEST)
export const getSubscriptionsSuccess = (subscription: Subscription) =>
export const getSubscriptionsSuccess = (subscription: SubscriptionFromClient) =>
action(GET_SUBSCRIPTIONS_SUCCESS, {
...subscription
})
Expand Down Expand Up @@ -34,3 +35,28 @@ export const CLEAR_SUBSCRIPTIONS_ERROR = 'Clear Subscriptions Error'
export const clearSaveSubscriptionError = () => action(CLEAR_SUBSCRIPTIONS_ERROR)

export type ClearSaveSubscriptionErrorAction = ReturnType<typeof clearSaveSubscriptionError>

export const SAVE_SUBSCRIPTION_EMAIL_REQUEST = '[Request] Save Subscription Email'
export const SAVE_SUBSCRIPTION_EMAIL_SUCCESS = '[Success] Save Subscription Email'
export const SAVE_SUBSCRIPTION_EMAIL_FAILURE = '[Failure] Save Subscription Email'

export const saveSubscriptionEmailRequest = (email: string) => action(SAVE_SUBSCRIPTION_EMAIL_REQUEST, { email })
export const saveSubscriptionEmailSuccess = (email: string) => action(SAVE_SUBSCRIPTION_EMAIL_SUCCESS, { email })
export const saveSubscriptionEmailFailure = (error: string) => action(SAVE_SUBSCRIPTION_EMAIL_FAILURE, { error })

export type SaveSubscriptionEmailRequestAction = ReturnType<typeof saveSubscriptionEmailRequest>
export type SaveSubscriptionEmailSuccessAction = ReturnType<typeof saveSubscriptionEmailSuccess>
export type SaveSubscriptionEmailFailureAction = ReturnType<typeof saveSubscriptionEmailFailure>

export const VALIDATE_SUBSCRIPTION_EMAIL_REQUEST = '[Request] Validate Subscription Email'
export const VALIDATE_SUBSCRIPTION_EMAIL_SUCCESS = '[Success] Validate Subscription Email'
export const VALIDATE_SUBSCRIPTION_EMAIL_FAILURE = '[Failure] Validate Subscription Email'

export const validateSubscriptionEmailRequest = (validationBody: { address: string; code: string }) =>
action(VALIDATE_SUBSCRIPTION_EMAIL_REQUEST, validationBody)
export const validateSubscriptionEmailSuccess = () => action(VALIDATE_SUBSCRIPTION_EMAIL_SUCCESS)
export const validateSubscriptionEmailFailure = (error: string) => action(VALIDATE_SUBSCRIPTION_EMAIL_FAILURE, { error })

export type ValidateSubscriptionEmailRequestAction = ReturnType<typeof validateSubscriptionEmailRequest>
export type ValidateSubscriptionEmailSuccessAction = ReturnType<typeof validateSubscriptionEmailSuccess>
export type ValidateSubscriptionEmailFailureAction = ReturnType<typeof validateSubscriptionEmailFailure>
183 changes: 172 additions & 11 deletions src/modules/subscription/reducer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import { Subscription } from '@dcl/schemas'
import { objectToCamel, objectToSnake } from 'ts-case-convert'
import {
clearSaveSubscriptionError,
getSubscriptionsFailure,
getSubscriptionsRequest,
getSubscriptionsSuccess,
saveSubscriptionEmailFailure,
saveSubscriptionEmailRequest,
saveSubscriptionEmailSuccess,
saveSubscriptionsFailure,
saveSubscriptionsRequest,
saveSubscriptionsSuccess
saveSubscriptionsSuccess,
validateSubscriptionEmailFailure,
validateSubscriptionEmailRequest,
validateSubscriptionEmailSuccess
} from './actions'
import { buildInitialState, subscriptionReducer } from './reducer'
import { SubscriptionState } from './types'
import { SubscriptionFromClient, SubscriptionState } from './types'

let state: SubscriptionState
let initialState: SubscriptionState
let subscription: Subscription
let subscription: SubscriptionFromClient
let unconfirmedEmail: string
let validationBody: {
address: string
code: string
}

beforeEach(() => {
state = buildInitialState()
unconfirmedEmail = 'example@decentraland.org'
subscription = {
address: '0x13a088C9ae5028C55F8E1cd5A13dc8134b062d50',
email: 'email@example.org',
Expand Down Expand Up @@ -47,14 +58,52 @@ describe('when reducing the get subscription success action', () => {
subscriptionDetails: { ...objectToCamel(subscription.details), ignoreAllInApp: true }
}
})
describe('and there is no email nor unconfirmedEmail', () => {
it('should return a state with the subscription details set without the email and the loading state cleared', () => {
expect(subscriptionReducer(initialState, getSubscriptionsSuccess({ ...subscription, email: '' }))).toEqual({
...state,
email: '',
subscriptionDetails: objectToCamel(subscription.details),
loading: [],
error: null
})
})
})
describe('and there is an unconfirmedEmail pending', () => {
it('should return a state with the subscription details set with the unconfirmed email set and the loading state cleared', () => {
expect(subscriptionReducer(initialState, getSubscriptionsSuccess({ ...subscription, email: '', unconfirmedEmail }))).toEqual({
...state,
email: '',
unconfirmedEmail,
subscriptionDetails: objectToCamel(subscription.details),
loading: [],
error: null
})
})
})

it('should return a state with the subscription details set, the email set and the loading state cleared', () => {
expect(subscriptionReducer(initialState, getSubscriptionsSuccess(subscription))).toEqual({
...state,
email: subscription.email,
subscriptionDetails: objectToCamel(subscription.details),
loading: [],
error: null
describe('and there is an email but no unconfirmedEmail', () => {
it('should return a state with the subscription details set, the email set and the loading state cleared', () => {
expect(subscriptionReducer(initialState, getSubscriptionsSuccess(subscription))).toEqual({
...state,
email: subscription.email,
subscriptionDetails: objectToCamel(subscription.details),
loading: [],
error: null
})
})
})

describe('and there is an email and an unconfirmedEmail', () => {
it('should return a state with the subscription details set, the email and the unconfirmed email set and the loading state cleared', () => {
expect(subscriptionReducer(initialState, getSubscriptionsSuccess({ ...subscription, unconfirmedEmail }))).toEqual({
...state,
email: subscription.email,
unconfirmedEmail,
subscriptionDetails: objectToCamel(subscription.details),
loading: [],
error: null
})
})
})
})
Expand Down Expand Up @@ -138,3 +187,115 @@ describe('when reducing update of the subscription error clear action', () => {
})
})
})

describe('when reducing the update of the email subscription request action', () => {
beforeEach(() => {
initialState = { ...state, error: 'some error' }
})

it('should return a state with the loading state set and the error cleared', () => {
expect(subscriptionReducer(initialState, saveSubscriptionEmailRequest(unconfirmedEmail))).toEqual({
...state,
loading: [saveSubscriptionEmailRequest(unconfirmedEmail)],
error: null
})
})
})

describe('when reducing the update of the email subscription success action', () => {
beforeEach(() => {
initialState = {
...state,
loading: [saveSubscriptionEmailRequest(unconfirmedEmail)]
}
})

it('should return a state with the unconfirmed email set and the loading state cleared', () => {
expect(subscriptionReducer(initialState, saveSubscriptionEmailSuccess(unconfirmedEmail))).toEqual({
...state,
unconfirmedEmail: unconfirmedEmail,
loading: [],
error: null
})
})
})

describe('when reducing update of the email subscription failure action', () => {
let error: string

beforeEach(() => {
initialState = { ...state, loading: [saveSubscriptionEmailRequest(unconfirmedEmail)] }
error = 'some error'
})

it('should return a state with the error set and the loading state cleared', () => {
expect(subscriptionReducer(initialState, saveSubscriptionEmailFailure(error))).toEqual({
...state,
loading: [],
error: error
})
})
})

describe('when reducing the validation of the email subscription request action', () => {
beforeEach(() => {
initialState = { ...state, error: 'some error' }
validationBody = {
address: unconfirmedEmail,
code: '123456'
}
})

it('should return a state with the loading state set and the error cleared', () => {
expect(subscriptionReducer(initialState, validateSubscriptionEmailRequest(validationBody))).toEqual({
...state,
loading: [validateSubscriptionEmailRequest(validationBody)],
error: null
})
})
})

describe('when reducing the validation of the email subscription success action', () => {
beforeEach(() => {
unconfirmedEmail = 'example@decentraland.org'
validationBody = {
address: unconfirmedEmail,
code: '123456'
}
initialState = {
...state,
loading: [validateSubscriptionEmailRequest(validationBody)]
}
})

it('should return a state with the new email set, the unconfirmedEmail undefined and the loading state cleared', () => {
expect(subscriptionReducer({ ...initialState, unconfirmedEmail }, validateSubscriptionEmailSuccess())).toEqual({
...state,
email: unconfirmedEmail,
unconfirmedEmail: undefined,
loading: [],
error: null
})
})
})

describe('when reducing the validation of the email subscription failure action', () => {
let error: string

beforeEach(() => {
validationBody = {
address: unconfirmedEmail,
code: '123456'
}
initialState = { ...state, loading: [validateSubscriptionEmailRequest(validationBody)] }
error = 'some error'
})

it('should return a state with the error set and the loading state cleared', () => {
expect(subscriptionReducer(initialState, validateSubscriptionEmailFailure(error))).toEqual({
...state,
loading: [],
error: error
})
})
})
Loading

0 comments on commit 90073a7

Please sign in to comment.