diff --git a/.gitignore b/.gitignore index 2af11b2e..983ad299 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __coverage__ dist node_modules _auto_doc_ +.vscode \ No newline at end of file diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap index 67d964da..34e6fac0 100644 --- a/__tests__/__snapshots__/index.js.snap +++ b/__tests__/__snapshots__/index.js.snap @@ -13,6 +13,8 @@ Object { "dropResults": [Function], "fetchCheckpointsDone": [Function], "fetchCheckpointsInit": [Function], + "getActiveChallengesCountDone": [Function], + "getActiveChallengesCountInit": [Function], "getDetailsDone": [Function], "getDetailsInit": [Function], "getSubmissionsDone": [Function], @@ -48,6 +50,10 @@ Object { "getGroupsDone": [Function], "getGroupsInit": [Function], }, + "lookup": Object { + "getSkillTagsDone": [Function], + "getSkillTagsInit": [Function], + }, "memberTasks": Object { "dropAll": [Function], "getDone": [Function], @@ -58,25 +64,59 @@ Object { "dropAll": [Function], "getAchievementsDone": [Function], "getAchievementsInit": [Function], + "getActiveChallengesDone": [Function], + "getActiveChallengesInit": [Function], "getFinancesDone": [Function], "getFinancesInit": [Function], + "getStatsDistributionDone": [Function], + "getStatsDistributionInit": [Function], "getStatsDone": [Function], + "getStatsHistoryDone": [Function], + "getStatsHistoryInit": [Function], "getStatsInit": [Function], }, "profile": Object { + "addSkillDone": [Function], + "addSkillInit": [Function], + "addWebLinkDone": [Function], + "addWebLinkInit": [Function], + "deletePhotoDone": [Function], + "deletePhotoInit": [Function], + "deleteWebLinkDone": [Function], + "deleteWebLinkInit": [Function], "getAchievementsDone": [Function], "getAchievementsInit": [Function], + "getCredentialDone": [Function], + "getCredentialInit": [Function], + "getEmailPreferencesDone": [Function], + "getEmailPreferencesInit": [Function], "getExternalAccountsDone": [Function], "getExternalAccountsInit": [Function], "getExternalLinksDone": [Function], "getExternalLinksInit": [Function], "getInfoDone": [Function], "getInfoInit": [Function], + "getLinkedAccountsDone": [Function], + "getLinkedAccountsInit": [Function], "getSkillsDone": [Function], "getSkillsInit": [Function], "getStatsDone": [Function], "getStatsInit": [Function], + "hideSkillDone": [Function], + "hideSkillInit": [Function], + "linkExternalAccountDone": [Function], + "linkExternalAccountInit": [Function], "loadProfile": [Function], + "saveEmailPreferencesDone": [Function], + "saveEmailPreferencesInit": [Function], + "unlinkExternalAccountDone": [Function], + "unlinkExternalAccountInit": [Function], + "updatePasswordDone": [Function], + "updatePasswordInit": [Function], + "updateProfileDone": [Function], + "updateProfileInit": [Function], + "uploadPhotoDone": [Function], + "uploadPhotoInit": [Function], }, "reviewOpportunity": Object { "cancelApplicationsDone": [Function], @@ -166,6 +206,7 @@ Object { "direct": [Function], "errors": [Function], "groups": [Function], + "lookup": [Function], "memberTasks": [Function], "members": [Function], "mySubmissionsManagement": [Function], @@ -209,6 +250,10 @@ Object { "default": undefined, "getService": [Function], }, + "lookup": Object { + "default": undefined, + "getService": [Function], + }, "members": Object { "default": undefined, "getService": [Function], diff --git a/__tests__/actions/__snapshots__/challenge.js.snap b/__tests__/actions/__snapshots__/challenge.js.snap new file mode 100644 index 00000000..809430ac --- /dev/null +++ b/__tests__/actions/__snapshots__/challenge.js.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`challenge.getActiveChallengesCountDone 1`] = ` +Object { + "payload": 10, + "type": "CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE", +} +`; + +exports[`challenge.getActiveChallengesCountInit 1`] = ` +Object { + "type": "CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_INIT", +} +`; diff --git a/__tests__/actions/__snapshots__/lookup.js.snap b/__tests__/actions/__snapshots__/lookup.js.snap new file mode 100644 index 00000000..c29d03c4 --- /dev/null +++ b/__tests__/actions/__snapshots__/lookup.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Module exports 1`] = ` +Object { + "lookup": Object { + "getSkillTagsDone": [Function], + "getSkillTagsInit": [Function], + }, +} +`; + +exports[`lookup.getSkillTagsDone 1`] = ` +Object { + "payload": Array [ + Object { + "domain": "SKILLS", + "id": 251, + "name": "Jekyll", + "status": "APPROVED", + }, + ], + "type": "LOOKUP/GET_SKILL_TAGS_DONE", +} +`; + +exports[`lookup.getSkillTagsInit 1`] = ` +Object { + "type": "LOOKUP/GET_SKILL_TAGS_INIT", +} +`; diff --git a/__tests__/actions/__snapshots__/profile.js.snap b/__tests__/actions/__snapshots__/profile.js.snap new file mode 100644 index 00000000..17e184aa --- /dev/null +++ b/__tests__/actions/__snapshots__/profile.js.snap @@ -0,0 +1,209 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Module exports 1`] = ` +Object { + "profile": Object { + "addSkillDone": [Function], + "addSkillInit": [Function], + "addWebLinkDone": [Function], + "addWebLinkInit": [Function], + "deletePhotoDone": [Function], + "deletePhotoInit": [Function], + "deleteWebLinkDone": [Function], + "deleteWebLinkInit": [Function], + "getAchievementsDone": [Function], + "getAchievementsInit": [Function], + "getCredentialDone": [Function], + "getCredentialInit": [Function], + "getEmailPreferencesDone": [Function], + "getEmailPreferencesInit": [Function], + "getExternalAccountsDone": [Function], + "getExternalAccountsInit": [Function], + "getExternalLinksDone": [Function], + "getExternalLinksInit": [Function], + "getInfoDone": [Function], + "getInfoInit": [Function], + "getLinkedAccountsDone": [Function], + "getLinkedAccountsInit": [Function], + "getSkillsDone": [Function], + "getSkillsInit": [Function], + "getStatsDone": [Function], + "getStatsInit": [Function], + "hideSkillDone": [Function], + "hideSkillInit": [Function], + "linkExternalAccountDone": [Function], + "linkExternalAccountInit": [Function], + "loadProfile": [Function], + "saveEmailPreferencesDone": [Function], + "saveEmailPreferencesInit": [Function], + "unlinkExternalAccountDone": [Function], + "unlinkExternalAccountInit": [Function], + "updatePasswordDone": [Function], + "updatePasswordInit": [Function], + "updateProfileDone": [Function], + "updateProfileInit": [Function], + "uploadPhotoDone": [Function], + "uploadPhotoInit": [Function], + }, +} +`; + +exports[`profile.addSkillDone 1`] = ` +Object { + "payload": Object { + "handle": "tcscoder", + "skill": Object { + "tagId": 123, + "tagName": "Node.js", + }, + "skills": Array [ + Object { + "tagId": 123, + "tagName": "Node.js", + }, + ], + }, + "type": "PROFILE/ADD_SKILL_DONE", +} +`; + +exports[`profile.addWebLinkDone 1`] = ` +Object { + "payload": Object { + "data": "https://www.google.com", + "handle": "tcscoder", + }, + "type": "PROFILE/ADD_WEB_LINK_DONE", +} +`; + +exports[`profile.deleteWebLinkDone 1`] = ` +Object { + "payload": Object { + "data": "https://www.google.com", + "handle": "tcscoder", + }, + "type": "PROFILE/DELETE_WEB_LINK_DONE", +} +`; + +exports[`profile.getCredentialDone 1`] = ` +Object { + "payload": Object { + "credential": Object { + "hasPassword": true, + }, + }, + "type": "PROFILE/GET_CREDENTIAL_DONE", +} +`; + +exports[`profile.getEmailPreferencesDone 1`] = ` +Object { + "payload": Object { + "subscriptions": Object { + "TOPCODER_NL_DATA": true, + }, + }, + "type": "PROFILE/GET_EMAIL_PREFERENCES_DONE", +} +`; + +exports[`profile.getLinkedAccountsDone 1`] = ` +Object { + "payload": Object { + "profiles": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + }, + "type": "PROFILE/GET_LINKED_ACCOUNTS_DONE", +} +`; + +exports[`profile.hideSkillDone 1`] = ` +Object { + "payload": Object { + "handle": "tcscoder", + "skill": Object { + "tagId": 123, + "tagName": "Node.js", + }, + "skills": Array [], + }, + "type": "PROFILE/HIDE_SKILL_DONE", +} +`; + +exports[`profile.linkExternalAccountDone 1`] = ` +Object { + "payload": Object { + "data": Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + "handle": "tcscoder", + }, + "type": "PROFILE/LINK_EXTERNAL_ACCOUNT_DONE", +} +`; + +exports[`profile.saveEmailPreferencesDone 1`] = ` +Object { + "payload": Object { + "data": Object { + "subscriptions": Object { + "TOPCODER_NL_DATA": true, + }, + }, + "handle": "tcscoder", + }, + "type": "PROFILE/SAVE_EMAIL_PREFERENCES_DONE", +} +`; + +exports[`profile.unlinkExternalAccountDone 1`] = ` +Object { + "payload": Object { + "handle": "tcscoder", + "providerType": "github", + }, + "type": "PROFILE/UNLINK_EXTERNAL_ACCOUNT_DONE", +} +`; + +exports[`profile.updatePasswordDone 1`] = ` +Object { + "payload": Object { + "data": Object { + "update": true, + }, + "handle": "tcscoder", + }, + "type": "PROFILE/UPDATE_PASSWORD_DONE", +} +`; + +exports[`profile.updateProfileDone 1`] = ` +Object { + "payload": Object { + "handle": "tcscoder", + "userId": 12345, + }, + "type": "PROFILE/UPDATE_PROFILE_DONE", +} +`; + +exports[`profile.uploadPhotoDone 1`] = ` +Object { + "payload": Object { + "handle": "tcscoder", + "photoURL": "url-of-photo", + }, + "type": "PROFILE/UPLOAD_PHOTO_DONE", +} +`; diff --git a/__tests__/actions/challenge.js b/__tests__/actions/challenge.js index 1210c1ee..2ea9878c 100644 --- a/__tests__/actions/challenge.js +++ b/__tests__/actions/challenge.js @@ -1,3 +1,4 @@ +import { redux } from 'topcoder-react-utils'; import { actions } from '../../src'; jest.mock('../../src/services/challenges'); @@ -74,3 +75,14 @@ describe('challenge.fetchSubmissionsDone', () => { submissions: 'DUMMY DATA', }))); }); + +test('challenge.getActiveChallengesCountInit', () => { + const actionResult = actions.challenge.getActiveChallengesCountInit(); + expect(actionResult).toMatchSnapshot(); +}); + +test('challenge.getActiveChallengesCountDone', async () => { + const actionResult = + await redux.resolveAction(actions.challenge.getActiveChallengesCountDone('handle', 'tokenV3')); + expect(actionResult).toMatchSnapshot(); +}); diff --git a/__tests__/actions/lookup.js b/__tests__/actions/lookup.js new file mode 100644 index 00000000..16afe8e0 --- /dev/null +++ b/__tests__/actions/lookup.js @@ -0,0 +1,31 @@ +import { redux } from 'topcoder-react-utils'; + +import * as LookupService from 'services/lookup'; +import actions from 'actions/lookup'; + +const tag = { + domain: 'SKILLS', + id: 251, + name: 'Jekyll', + status: 'APPROVED', +}; + +// Mock services +const mockLookupService = { + getTags: jest.fn().mockReturnValue(Promise.resolve([tag])), +}; +LookupService.getService = jest.fn().mockReturnValue(mockLookupService); + +test('Module exports', () => expect(actions).toMatchSnapshot()); + +test('lookup.getSkillTagsInit', async () => { + const actionResult = actions.lookup.getSkillTagsInit(); + expect(actionResult).toMatchSnapshot(); +}); + +test('lookup.getSkillTagsDone', async () => { + const actionResult = + await redux.resolveAction(actions.lookup.getSkillTagsDone()); + expect(actionResult).toMatchSnapshot(); + expect(mockLookupService.getTags).toBeCalled(); +}); diff --git a/__tests__/actions/profile.js b/__tests__/actions/profile.js new file mode 100644 index 00000000..99f814e3 --- /dev/null +++ b/__tests__/actions/profile.js @@ -0,0 +1,139 @@ +import { redux } from 'topcoder-react-utils'; + +import * as MembersService from 'services/members'; +import * as UserService from 'services/user'; + +import actions from 'actions/profile'; + +const handle = 'tcscoder'; +const tokenV3 = 'tokenV3'; +const profile = { userId: 12345, handle }; +const skill = { tagId: 123, tagName: 'Node.js' }; +const weblink = 'https://www.google.com'; +const linkedAccounts = [{ + providerType: 'github', + social: true, + userId: '623633', +}]; + +// Mock services +const mockMembersService = { + getPresignedUrl: jest.fn().mockReturnValue(Promise.resolve()), + uploadFileToS3: jest.fn().mockReturnValue(Promise.resolve()), + updateMemberPhoto: jest.fn().mockReturnValue(Promise.resolve('url-of-photo')), + updateMemberProfile: jest.fn().mockReturnValue(Promise.resolve(profile)), + addSkill: jest.fn().mockReturnValue(Promise.resolve({ skills: [skill] })), + hideSkill: jest.fn().mockReturnValue(Promise.resolve({ skills: [] })), + addWebLink: jest.fn().mockReturnValue(Promise.resolve(weblink)), + deleteWebLink: jest.fn().mockReturnValue(Promise.resolve(weblink)), +}; +MembersService.getService = jest.fn().mockReturnValue(mockMembersService); + +const mockUserService = { + linkExternalAccount: jest.fn().mockReturnValue(Promise.resolve(linkedAccounts[0])), + unlinkExternalAccount: jest.fn().mockReturnValue(Promise.resolve('unlinked')), + getLinkedAccounts: jest.fn().mockReturnValue(Promise.resolve({ profiles: linkedAccounts })), + getCredential: jest.fn().mockReturnValue(Promise.resolve({ credential: { hasPassword: true } })), + getEmailPreferences: + jest.fn().mockReturnValue(Promise.resolve({ subscriptions: { TOPCODER_NL_DATA: true } })), + saveEmailPreferences: + jest.fn().mockReturnValue(Promise.resolve({ subscriptions: { TOPCODER_NL_DATA: true } })), + updatePassword: jest.fn().mockReturnValue(Promise.resolve({ update: true })), +}; +UserService.getService = jest.fn().mockReturnValue(mockUserService); + +test('Module exports', () => expect(actions).toMatchSnapshot()); + +test('profile.uploadPhotoDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.uploadPhotoDone(handle, tokenV3)); + expect(actionResult).toMatchSnapshot(); + expect(mockMembersService.getPresignedUrl).toBeCalled(); + expect(mockMembersService.uploadFileToS3).toBeCalled(); + expect(mockMembersService.updateMemberPhoto).toBeCalled(); +}); + +test('profile.updateProfileDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.updateProfileDone(profile, tokenV3)); + expect(actionResult).toMatchSnapshot(); + expect(mockMembersService.updateMemberProfile).toBeCalled(); +}); + +test('profile.addSkillDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.addSkillDone(handle, tokenV3, skill)); + expect(actionResult).toMatchSnapshot(); + expect(mockMembersService.addSkill).toBeCalled(); +}); + +test('profile.hideSkillDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.hideSkillDone(handle, tokenV3, skill)); + expect(actionResult).toMatchSnapshot(); + expect(mockMembersService.hideSkill).toBeCalled(); +}); + +test('profile.addWebLinkDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.addWebLinkDone(handle, tokenV3, weblink)); + expect(actionResult).toMatchSnapshot(); + expect(mockMembersService.addWebLink).toBeCalled(); +}); + +test('profile.deleteWebLinkDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.deleteWebLinkDone(handle, tokenV3, weblink)); + expect(actionResult).toMatchSnapshot(); + expect(mockMembersService.deleteWebLink).toBeCalled(); +}); + +test('profile.linkExternalAccountDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.linkExternalAccountDone(profile, tokenV3, 'github')); + expect(actionResult).toMatchSnapshot(); + expect(mockUserService.linkExternalAccount).toBeCalled(); +}); + +test('profile.unlinkExternalAccountDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.unlinkExternalAccountDone(profile, tokenV3, 'github')); + expect(actionResult).toMatchSnapshot(); + expect(mockUserService.unlinkExternalAccount).toBeCalled(); +}); + +test('profile.getLinkedAccountsDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.getLinkedAccountsDone(profile, tokenV3)); + expect(actionResult).toMatchSnapshot(); + expect(mockUserService.getLinkedAccounts).toBeCalled(); +}); + +test('profile.getCredentialDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.getCredentialDone(profile, tokenV3)); + expect(actionResult).toMatchSnapshot(); + expect(mockUserService.getCredential).toBeCalled(); +}); + +test('profile.getEmailPreferencesDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.getEmailPreferencesDone(profile, tokenV3)); + expect(actionResult).toMatchSnapshot(); + expect(mockUserService.getEmailPreferences).toBeCalled(); +}); + +test('profile.saveEmailPreferencesDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.saveEmailPreferencesDone(profile, tokenV3, {})); + expect(actionResult).toMatchSnapshot(); + expect(mockUserService.saveEmailPreferences).toBeCalled(); +}); + +test('profile.updatePasswordDone', async () => { + const actionResult = + await redux.resolveAction(actions.profile.updatePasswordDone(profile, tokenV3, 'newPassword', 'oldPassword')); + expect(actionResult).toMatchSnapshot(); + expect(mockUserService.updatePassword).toBeCalled(); +}); + diff --git a/__tests__/reducers/__snapshots__/challenge.js.snap b/__tests__/reducers/__snapshots__/challenge.js.snap new file mode 100644 index 00000000..9db7c531 --- /dev/null +++ b/__tests__/reducers/__snapshots__/challenge.js.snap @@ -0,0 +1,892 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Default reducer Creates expected intial state 1`] = ` +Object { + "checkpoints": null, + "details": null, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles CHALLENGE/GET_DETAILS_DONE as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles CHALLENGE/GET_DETAILS_DONE with error as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles CHALLENGE/GET_DETAILS_INIT as expected 1`] = ` +Object { + "checkpoints": null, + "details": null, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "12345", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles deleteSubmissionDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [], + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles fetchSubmissionsDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles fetchSubmissionsDoneError as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles fetchSubmissionsInit as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "12345", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Default reducer Handles getActiveChallengesCountDone as expected 1`] = ` +Object { + "activeChallengesCount": 5, + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Creates expected intial state 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles CHALLENGE/GET_DETAILS_DONE as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles CHALLENGE/GET_DETAILS_DONE with error as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles CHALLENGE/GET_DETAILS_INIT as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "12345", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles deleteSubmissionDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [], + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles fetchSubmissionsDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles fetchSubmissionsDoneError as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles fetchSubmissionsInit as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "12345", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory with server-side rendering Handles getActiveChallengesCountDone as expected 1`] = ` +Object { + "activeChallengesCount": 5, + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Creates expected intial state 1`] = ` +Object { + "checkpoints": null, + "details": null, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles CHALLENGE/GET_DETAILS_DONE as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles CHALLENGE/GET_DETAILS_DONE with error as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles CHALLENGE/GET_DETAILS_INIT as expected 1`] = ` +Object { + "checkpoints": null, + "details": null, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "12345", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles deleteSubmissionDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [], + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles fetchSubmissionsDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles fetchSubmissionsDoneError as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles fetchSubmissionsInit as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "12345", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without http request Handles getActiveChallengesCountDone as expected 1`] = ` +Object { + "activeChallengesCount": 5, + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Creates expected intial state 1`] = ` +Object { + "checkpoints": null, + "details": null, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles CHALLENGE/GET_DETAILS_DONE as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles CHALLENGE/GET_DETAILS_DONE with error as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles CHALLENGE/GET_DETAILS_INIT as expected 1`] = ` +Object { + "checkpoints": null, + "details": null, + "fetchChallengeFailure": false, + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "12345", + "loadingResultsForChallengeId": "", + "mySubmissions": Object {}, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles deleteSubmissionDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [], + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles fetchSubmissionsDone as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "12345", + "v2": Array [ + Object { + "submissionId": "1", + }, + ], + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles fetchSubmissionsDoneError as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles fetchSubmissionsInit as expected 1`] = ` +Object { + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "12345", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object {}, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; + +exports[`Factory without server-side rendering Handles getActiveChallengesCountDone as expected 1`] = ` +Object { + "activeChallengesCount": 5, + "checkpoints": null, + "details": Object { + "id": 12345, + "tag": "v3-normalized-details", + }, + "fetchChallengeFailure": "Unknown error", + "loadingCheckpoints": false, + "loadingDetailsForChallengeId": "", + "loadingResultsForChallengeId": "", + "loadingSubmissionsForChallengeId": "", + "mySubmissions": Object { + "challengeId": "", + "v2": null, + }, + "mySubmissionsManagement": Object { + "deletingSubmission": false, + }, + "registering": false, + "results": null, + "resultsLoadedForChallengeId": "", + "unregistering": false, + "updatingChallengeUuid": "", +} +`; diff --git a/__tests__/reducers/__snapshots__/lookup.js.snap b/__tests__/reducers/__snapshots__/lookup.js.snap new file mode 100644 index 00000000..3f923ead --- /dev/null +++ b/__tests__/reducers/__snapshots__/lookup.js.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Default reducer Get skill tags 1`] = ` +Object { + "loadingSkillTagsError": false, + "skillTags": Array [ + Object { + "domain": "SKILLS", + "id": 251, + "name": "Jekyll", + "status": "APPROVED", + }, + ], +} +`; + +exports[`Default reducer Get skill tags error 1`] = ` +Object { + "loadingSkillTagsError": true, + "skillTags": Array [ + Object { + "domain": "SKILLS", + "id": 251, + "name": "Jekyll", + "status": "APPROVED", + }, + ], +} +`; + +exports[`Default reducer Initial state 1`] = ` +Object { + "skillTags": Array [], +} +`; + +exports[`Factory without server side rendering Get skill tags 1`] = ` +Object { + "loadingSkillTagsError": false, + "skillTags": Array [ + Object { + "domain": "SKILLS", + "id": 251, + "name": "Jekyll", + "status": "APPROVED", + }, + ], +} +`; + +exports[`Factory without server side rendering Get skill tags error 1`] = ` +Object { + "loadingSkillTagsError": true, + "skillTags": Array [ + Object { + "domain": "SKILLS", + "id": 251, + "name": "Jekyll", + "status": "APPROVED", + }, + ], +} +`; + +exports[`Factory without server side rendering Initial state 1`] = ` +Object { + "skillTags": Array [], +} +`; diff --git a/__tests__/reducers/__snapshots__/profile.js.snap b/__tests__/reducers/__snapshots__/profile.js.snap new file mode 100644 index 00000000..fcd97829 --- /dev/null +++ b/__tests__/reducers/__snapshots__/profile.js.snap @@ -0,0 +1,2257 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Default reducer Add skill done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [ + Object { + "tagId": 123, + "tagName": "Node.js", + }, + ], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Add skill init 1`] = ` +Object { + "achievements": null, + "addingSkill": true, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Add web link done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + Object { + "URL": "http://www.google.com", + "key": "2222", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Add web link init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": true, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Delete photo done 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Delete photo init 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": true, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": "http://url", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Delete web link done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Delete web link init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": true, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + Object { + "URL": "http://www.google.com", + "key": "2222", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Get credential 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Default reducer Get email preferences 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Default reducer Get external links 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Default reducer Get linked account 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Default reducer Get member info 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "info": Object { + "handle": "tcscoder", + }, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Default reducer Hide skill done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Hide skill init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": true, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [ + Object { + "tagId": 123, + "tagName": "Node.js", + }, + ], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Initial state 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "info": null, + "loadingError": false, + "skills": null, + "stats": null, +} +`; + +exports[`Default reducer Link external account done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + Object { + "providerType": "stackoverlow", + "social": true, + "userId": "343523", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Link external account init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": true, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Load profile 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "info": null, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Default reducer Save email preferences done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": false, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Save email preferences init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": true, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Unlink external account done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Unlink external account init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + Object { + "providerType": "stackoverlow", + "social": true, + "userId": "343523", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": true, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Update password done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": false, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingPassword": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Update password init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": false, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingPassword": true, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Update profile done 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Update profile init 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "updatingProfile": true, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Upload photo done 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": "http://url", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": false, +} +`; + +exports[`Default reducer Upload photo init 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": true, +} +`; + +exports[`Factory without server side rendering Add skill done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [ + Object { + "tagId": 123, + "tagName": "Node.js", + }, + ], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Add skill init 1`] = ` +Object { + "achievements": null, + "addingSkill": true, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Add web link done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + Object { + "URL": "http://www.google.com", + "key": "2222", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Add web link init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": true, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Delete photo done 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Delete photo init 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": true, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": "http://url", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Delete web link done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Delete web link init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": true, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + Object { + "URL": "http://www.google.com", + "key": "2222", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Get credential 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Factory without server side rendering Get email preferences 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Factory without server side rendering Get external links 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Factory without server side rendering Get linked account 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Factory without server side rendering Get member info 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "info": Object { + "handle": "tcscoder", + }, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Factory without server side rendering Hide skill done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Hide skill init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": true, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [ + Object { + "tagId": 123, + "tagName": "Node.js", + }, + ], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Initial state 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "info": null, + "loadingError": false, + "skills": null, + "stats": null, +} +`; + +exports[`Factory without server side rendering Link external account done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + Object { + "providerType": "stackoverlow", + "social": true, + "userId": "343523", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Link external account init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": true, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Load profile 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "info": null, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, +} +`; + +exports[`Factory without server side rendering Save email preferences done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": false, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Save email preferences init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": true, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Unlink external account done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Unlink external account init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + Object { + "providerType": "stackoverlow", + "social": true, + "userId": "343523", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": true, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Update password done 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": false, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingPassword": false, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Update password init 1`] = ` +Object { + "achievements": null, + "addingSkill": false, + "addingWebLink": false, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "deletingWebLink": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "hidingSkill": false, + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "linkingExternalAccount": false, + "loadingError": false, + "profileForHandle": "tcscoder", + "savingEmailPreferences": false, + "skills": Array [], + "stats": null, + "unlinkingExternalAccount": false, + "updatingPassword": true, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Update profile done 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "description": "bio desc", + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "updatingProfile": false, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Update profile init 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "deletingPhoto": false, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": null, + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "updatingProfile": true, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Upload photo done 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + "photoURL": "http://url", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": false, +} +`; + +exports[`Factory without server side rendering Upload photo init 1`] = ` +Object { + "achievements": null, + "copilot": false, + "country": "", + "credential": Object { + "hasPassword": true, + }, + "emailPreferences": Object { + "TOPCODER_NL_DATA": true, + }, + "externalLinks": Array [ + Object { + "URL": "http://www.github.com", + "key": "1111", + "providerType": "weblink", + }, + ], + "info": Object { + "handle": "tcscoder", + }, + "linkedAccounts": Array [ + Object { + "providerType": "github", + "social": true, + "userId": "623633", + }, + ], + "loadingError": false, + "profileForHandle": "tcscoder", + "skills": null, + "stats": null, + "uploadingPhoto": true, +} +`; diff --git a/__tests__/reducers/auth.js b/__tests__/reducers/auth.js index a86137f7..f89ea226 100644 --- a/__tests__/reducers/auth.js +++ b/__tests__/reducers/auth.js @@ -2,6 +2,8 @@ import { mockAction } from 'utils/mock'; import { redux } from 'topcoder-react-utils'; const dummy = 'DUMMY'; +const handle = 'tcscoder'; +const photoURL = 'http://url'; const mockActions = { auth: { @@ -9,8 +11,14 @@ const mockActions = { setTcTokenV2: mockAction('SET_TC_TOKEN_V2', 'Token V2'), setTcTokenV3: mockAction('SET_TC_TOKEN_V3', 'Token V3'), }, + profile: { + uploadPhotoDone: mockAction('UPLOAD_PHOTO_DONE', Promise.resolve({ handle, photoURL })), + deletePhotoDone: mockAction('DELETE_PHOTO_DONE', Promise.resolve({ handle })), + updateProfileDone: mockAction('UPDATE_PROFILE_DONE', Promise.resolve({ handle, photoURL: 'http://newurl' })), + }, }; jest.setMock(require.resolve('actions/auth'), mockActions); +jest.setMock(require.resolve('actions/profile'), mockActions); jest.setMock('tc-accounts', { decodeToken: () => 'User object', @@ -19,7 +27,9 @@ jest.setMock('tc-accounts', { const reducers = require('reducers/auth'); -function testReducer(reducer, istate) { +let reducer; + +function testReducer(istate) { test('Initial state', () => { const state = reducer(undefined, {}); expect(state).toEqual(istate); @@ -62,10 +72,61 @@ function testReducer(reducer, istate) { }); mockActions.auth.setTcTokenV3 = mockAction('SET_TC_TOKEN_V3', 'Token V3'); }); + + test('Upload photo', () => + redux.resolveAction(mockActions.profile.uploadPhotoDone()).then((action) => { + const state = reducer({ profile: { handle } }, action); + expect(state).toEqual({ + profile: { + handle, + photoURL, + }, + }); + })); + + test('Delete photo', () => + redux.resolveAction(mockActions.profile.deletePhotoDone()).then((action) => { + const state = reducer({ profile: { handle, photoURL } }, action); + expect(state).toEqual({ + profile: { + handle, + photoURL: null, + }, + }); + })); + + test('Update profile', () => + redux.resolveAction(mockActions.profile.updateProfileDone()).then((action) => { + const state = reducer({ profile: { handle, photoURL } }, action); + expect(state).toEqual({ + profile: { + handle, + photoURL: 'http://newurl', + }, + }); + })); } describe('Default reducer', () => { - testReducer(reducers.default, { + reducer = reducers.default; + testReducer({ + authenticating: true, + profile: null, + tokenV2: '', + tokenV3: '', + user: null, + }); +}); + +describe('Factory without server side rendering', () => { + beforeAll((done) => { + reducers.factory().then((res) => { + reducer = res; + done(); + }); + }); + + testReducer({ authenticating: true, profile: null, tokenV2: '', @@ -74,15 +135,20 @@ describe('Default reducer', () => { }); }); -describe('Factory without server side rendering', () => - reducers.factory().then(res => - testReducer(res, {}))); - -describe('Factory with server side rendering', () => - reducers.factory({ - cookies: { - tcjwt: 'Token V2', - v3jwt: 'Token V3', - }, - }).then(res => - testReducer(res, {}))); +describe('Factory with server side rendering', () => { + beforeAll((done) => { + reducers.factory({ + auth: { + tokenV2: 'Token V2', + tokenV3: 'Token V3', + }, + }).then((res) => { + reducer = res; + done(); + }); + }); + + testReducer({ + authenticating: false, user: 'User object', profile: 'Profile', tokenV2: 'Token V2', tokenV3: 'Token V3', + }); +}); diff --git a/__tests__/reducers/challenge.js b/__tests__/reducers/challenge.js index 0a157ef0..5177aaff 100644 --- a/__tests__/reducers/challenge.js +++ b/__tests__/reducers/challenge.js @@ -25,6 +25,7 @@ const mockChallengeActions = { { challengeId: '12345' }, 'Unknown error', ), + getActiveChallengesCountDone: mockAction('CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE', 5), }, DETAIL_TABS: { DETAILS: 'details', @@ -52,188 +53,58 @@ const reducers = require('../../src/reducers/challenge'); beforeEach(() => jest.clearAllMocks()); -const defaultState = { - details: null, - checkpoints: null, - loadingCheckpoints: false, - loadingDetailsForChallengeId: '', - loadingResultsForChallengeId: '', - mySubmissions: {}, - mySubmissionsManagement: {}, - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', -}; - let reducer; -function testReducer(istate) { +function testReducer() { let state; test('Creates expected intial state', () => { state = reducer(undefined, {}); - expect(state).toEqual(istate); - state = _.clone(defaultState); + expect(state).toMatchSnapshot(); + // state = _.clone(defaultState); }); test('Handles CHALLENGE/GET_DETAILS_INIT as expected', () => { state = reducer(state, mockChallengeActions.challenge.getDetailsInit(12345)); - expect(state).toEqual({ - mySubmissions: {}, - mySubmissionsManagement: {}, - loadingCheckpoints: false, - loadingDetailsForChallengeId: '12345', - loadingResultsForChallengeId: '', - fetchChallengeFailure: false, - details: null, - checkpoints: null, - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + expect(state).toMatchSnapshot(); }); test('Handles CHALLENGE/GET_DETAILS_DONE as expected', (done) => { redux.resolveAction(mockChallengeActions.challenge.getDetailsDone()).then((action) => { state = reducer(state, action); - expect(state).toEqual({ - fetchChallengeFailure: false, - mySubmissions: {}, - mySubmissionsManagement: {}, - loadingCheckpoints: false, - loadingDetailsForChallengeId: '', - details: { - id: 12345, - tag: 'v3-normalized-details', - }, - checkpoints: null, - loadingResultsForChallengeId: '', - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + expect(state).toMatchSnapshot(); }).then(done).catch(done); }); test('Handles CHALLENGE/GET_DETAILS_DONE with error as expected', () => { state = reducer(state, mockChallengeActions.challenge.getDetailsDoneError()); - expect(state).toEqual({ - fetchChallengeFailure: 'Unknown error', - mySubmissions: {}, - mySubmissionsManagement: {}, - loadingCheckpoints: false, - loadingDetailsForChallengeId: '', - details: { - id: 12345, - tag: 'v3-normalized-details', - }, - checkpoints: null, - loadingResultsForChallengeId: '', - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + expect(state).toMatchSnapshot(); }); test('Handles fetchSubmissionsInit as expected', () => { state = reducer(state, mockChallengeActions.challenge.getSubmissionsInit()); - expect(state).toEqual({ - fetchChallengeFailure: 'Unknown error', - loadingSubmissionsForChallengeId: '12345', - mySubmissions: { challengeId: '', v2: null }, - mySubmissionsManagement: {}, - loadingDetailsForChallengeId: '', - details: { - id: 12345, - tag: 'v3-normalized-details', - }, - checkpoints: null, - loadingCheckpoints: false, - loadingResultsForChallengeId: '', - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + expect(state).toMatchSnapshot(); }); test('Handles fetchSubmissionsDone as expected', (done) => { redux.resolveAction(mockChallengeActions.challenge.getSubmissionsDone()).then((action) => { state = reducer(state, action); - expect(state).toEqual({ - fetchChallengeFailure: 'Unknown error', - loadingSubmissionsForChallengeId: '', - mySubmissions: { challengeId: '12345', v2: [{ submissionId: '1' }] }, - mySubmissionsManagement: {}, - loadingDetailsForChallengeId: '', - details: { - id: 12345, - tag: 'v3-normalized-details', - }, - checkpoints: null, - loadingCheckpoints: false, - loadingResultsForChallengeId: '', - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + expect(state).toMatchSnapshot(); }).then(done).catch(done); }); test('Handles deleteSubmissionDone as expected', () => { state = reducer(state, mockSmpActions.smp.deleteSubmissionDone()); - expect(state).toEqual({ - fetchChallengeFailure: 'Unknown error', - loadingSubmissionsForChallengeId: '', - mySubmissions: { challengeId: '12345', v2: [] }, - mySubmissionsManagement: { deletingSubmission: false }, - loadingDetailsForChallengeId: '', - details: { - id: 12345, - tag: 'v3-normalized-details', - }, - checkpoints: null, - loadingCheckpoints: false, - loadingResultsForChallengeId: '', - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + expect(state).toMatchSnapshot(); }); test('Handles fetchSubmissionsDoneError as expected', () => { state = reducer(state, mockChallengeActions.challenge.getSubmissionsDoneError()); - expect(state).toEqual({ - fetchChallengeFailure: 'Unknown error', - loadingSubmissionsForChallengeId: '', - mySubmissions: { challengeId: '', v2: null }, - mySubmissionsManagement: { deletingSubmission: false }, - loadingDetailsForChallengeId: '', - details: { - id: 12345, - tag: 'v3-normalized-details', - }, - checkpoints: null, - loadingCheckpoints: false, - loadingResultsForChallengeId: '', - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + expect(state).toMatchSnapshot(); + }); + + test('Handles getActiveChallengesCountDone as expected', () => { + state = reducer(state, mockChallengeActions.challenge.getActiveChallengesCountDone()); + expect(state).toMatchSnapshot(); }); } @@ -242,7 +113,7 @@ describe('Default reducer', () => { reducer = reducers.default; }); - testReducer(defaultState); + testReducer(); }); jest.clearAllMocks(); @@ -256,7 +127,7 @@ describe('Factory without http request', () => { }); }); - testReducer(defaultState); + testReducer(); }); describe('Factory with server-side rendering', () => { @@ -278,25 +149,7 @@ describe('Factory with server-side rendering', () => { }); }); - testReducer({ - details: { - id: 12345, - tag: 'v3-normalized-details', - }, - checkpoints: null, - loadingCheckpoints: false, - loadingDetailsForChallengeId: '', - loadingResultsForChallengeId: '', - loadingSubmissionsForChallengeId: '', - fetchChallengeFailure: false, - mySubmissions: { challengeId: '12345', v2: [{ submissionId: '1' }] }, - mySubmissionsManagement: {}, - registering: false, - results: null, - resultsLoadedForChallengeId: '', - unregistering: false, - updatingChallengeUuid: '', - }); + testReducer(); }); describe('Factory without server-side rendering', () => { @@ -309,5 +162,5 @@ describe('Factory without server-side rendering', () => { }); }); - testReducer(defaultState); + testReducer(); }); diff --git a/__tests__/reducers/lookup.js b/__tests__/reducers/lookup.js new file mode 100644 index 00000000..c7baeab9 --- /dev/null +++ b/__tests__/reducers/lookup.js @@ -0,0 +1,56 @@ +import { mockAction } from 'utils/mock'; + +const tag = { + domain: 'SKILLS', + id: 251, + name: 'Jekyll', + status: 'APPROVED', +}; + +const mockActions = { + lookup: { + getSkillTagsInit: mockAction('LOOKUP/GET_SKILL_TAGS_INIT'), + getSkillTagsDone: mockAction('LOOKUP/GET_SKILL_TAGS_DONE', [tag]), + getSkillTagsDoneError: mockAction('LOOKUP/GET_SKILL_TAGS_DONE', null, 'Unknown error'), + }, +}; +jest.setMock(require.resolve('actions/lookup'), mockActions); + +const reducers = require('reducers/lookup'); + +let reducer; + +function testReducer() { + let state; + + test('Initial state', () => { + state = reducer(undefined, {}); + expect(state).toMatchSnapshot(); + }); + + test('Get skill tags', () => { + state = reducer(state, mockActions.lookup.getSkillTagsInit()); + state = reducer(state, mockActions.lookup.getSkillTagsDone()); + expect(state).toMatchSnapshot(); + }); + + test('Get skill tags error', () => { + state = reducer(state, mockActions.lookup.getSkillTagsDoneError()); + expect(state).toMatchSnapshot(); + }); +} + +describe('Default reducer', () => { + reducer = reducers.default; + testReducer(); +}); + +describe('Factory without server side rendering', () => { + beforeAll((done) => { + reducers.factory().then((res) => { + reducer = res; + done(); + }); + }); + testReducer(); +}); diff --git a/__tests__/reducers/profile.js b/__tests__/reducers/profile.js new file mode 100644 index 00000000..95d6c705 --- /dev/null +++ b/__tests__/reducers/profile.js @@ -0,0 +1,213 @@ +import { mockAction } from 'utils/mock'; + +const handle = 'tcscoder'; +const photoURL = 'http://url'; +const skill = { tagId: 123, tagName: 'Node.js' }; +const externalLink = { providerType: 'weblink', key: '1111', URL: 'http://www.github.com' }; +const webLink = { providerType: 'weblink', key: '2222', URL: 'http://www.google.com' }; +const linkedAccount = { providerType: 'github', social: true, userId: '623633' }; +const linkedAccount2 = { providerType: 'stackoverlow', social: true, userId: '343523' }; + +const mockActions = { + profile: { + loadProfile: mockAction('LOAD_PROFILE', handle), + getInfoDone: mockAction('GET_INFO_DONE', { handle }), + getExternalLinksDone: mockAction('GET_EXTERNAL_LINKS_DONE', [externalLink]), + getLinkedAccountsDone: mockAction('GET_LINKED_ACCOUNTS_DONE', { profiles: [linkedAccount] }), + getCredentialDone: mockAction('GET_CREDENTIAL_DONE', { credential: { hasPassword: true } }), + getEmailPreferencesDone: mockAction('GET_EMAIL_PREFERENCES_DONE', { subscriptions: { TOPCODER_NL_DATA: true } }), + uploadPhotoInit: mockAction('UPLOAD_PHOTO_INIT'), + uploadPhotoDone: mockAction('UPLOAD_PHOTO_DONE', { handle, photoURL }), + deletePhotoInit: mockAction('DELETE_PHOTO_INIT'), + deletePhotoDone: mockAction('DELETE_PHOTO_DONE', { handle }), + updatePasswordInit: mockAction('UPDATE_PASSWORD_INIT'), + updatePasswordDone: mockAction('UPDATE_PASSWORD_DONE'), + updateProfileInit: mockAction('UPDATE_PROFILE_INIT'), + updateProfileDone: mockAction('UPDATE_PROFILE_DONE', { handle, description: 'bio desc' }), + addSkillInit: mockAction('ADD_SKILL_INIT'), + addSkillDone: mockAction('ADD_SKILL_DONE', { handle, skills: [skill] }), + hideSkillInit: mockAction('HIDE_SKILL_INIT'), + hideSkillDone: mockAction('HIDE_SKILL_DONE', { handle, skills: [] }), + addWebLinkInit: mockAction('ADD_WEB_LINK_INIT'), + addWebLinkDone: mockAction('ADD_WEB_LINK_DONE', { handle, data: webLink }), + deleteWebLinkInit: mockAction('DELETE_WEB_LINK_INIT'), + deleteWebLinkDone: mockAction('DELETE_WEB_LINK_DONE', { handle, data: webLink }), + saveEmailPreferencesInit: mockAction('SAVE_EMAIL_PREFERENCES_INIT'), + saveEmailPreferencesDone: mockAction('SAVE_EMAIL_PREFERENCES_DONE', { handle, data: { subscriptions: { TOPCODER_NL_DATA: true } } }), + linkExternalAccountInit: mockAction('LINK_EXTERNAL_ACCOUNT_INIT'), + linkExternalAccountDone: mockAction('LINK_EXTERNAL_ACCOUNT_DONE', { handle, data: linkedAccount2 }), + unlinkExternalAccountInit: mockAction('UNLINK_EXTERNAL_ACCOUNT_INIT'), + unlinkExternalAccountDone: mockAction('UNLINK_EXTERNAL_ACCOUNT_DONE', { handle, providerType: linkedAccount2.providerType }), + }, +}; +jest.setMock(require.resolve('actions/profile'), mockActions); + +const reducers = require('reducers/profile'); + +let reducer; + +function testReducer() { + let state; + + test('Initial state', () => { + state = reducer(undefined, {}); + expect(state).toMatchSnapshot(); + }); + + test('Load profile', () => { + state = reducer(state, mockActions.profile.loadProfile()); + expect(state).toMatchSnapshot(); + }); + + test('Get member info', () => { + state = reducer(state, mockActions.profile.getInfoDone()); + expect(state).toMatchSnapshot(); + }); + + test('Get external links', () => { + state = reducer(state, mockActions.profile.getExternalLinksDone()); + expect(state).toMatchSnapshot(); + }); + + test('Get linked account', () => { + state = reducer(state, mockActions.profile.getLinkedAccountsDone()); + expect(state).toMatchSnapshot(); + }); + + test('Get credential', () => { + state = reducer(state, mockActions.profile.getCredentialDone()); + expect(state).toMatchSnapshot(); + }); + + test('Get email preferences', () => { + state = reducer(state, mockActions.profile.getEmailPreferencesDone()); + expect(state).toMatchSnapshot(); + }); + + test('Upload photo init', () => { + state = reducer(state, mockActions.profile.uploadPhotoInit()); + expect(state).toMatchSnapshot(); + }); + + test('Upload photo done', () => { + state = reducer(state, mockActions.profile.uploadPhotoDone()); + expect(state).toMatchSnapshot(); + }); + + test('Delete photo init', () => { + state = reducer(state, mockActions.profile.deletePhotoInit()); + expect(state).toMatchSnapshot(); + }); + + test('Delete photo done', () => { + state = reducer(state, mockActions.profile.deletePhotoDone()); + expect(state).toMatchSnapshot(); + }); + + test('Update profile init', () => { + state = reducer(state, mockActions.profile.updateProfileInit()); + expect(state).toMatchSnapshot(); + }); + + test('Update profile done', () => { + state = reducer(state, mockActions.profile.updateProfileDone()); + expect(state).toMatchSnapshot(); + }); + + test('Add skill init', () => { + state = reducer(state, mockActions.profile.addSkillInit()); + expect(state).toMatchSnapshot(); + }); + + test('Add skill done', () => { + state = reducer(state, mockActions.profile.addSkillDone()); + expect(state).toMatchSnapshot(); + }); + + test('Hide skill init', () => { + state = reducer(state, mockActions.profile.hideSkillInit()); + expect(state).toMatchSnapshot(); + }); + + test('Hide skill done', () => { + state = reducer(state, mockActions.profile.hideSkillDone()); + expect(state).toMatchSnapshot(); + }); + + test('Add web link init', () => { + state = reducer(state, mockActions.profile.addWebLinkInit()); + expect(state).toMatchSnapshot(); + }); + + test('Add web link done', () => { + state = reducer(state, mockActions.profile.addWebLinkDone()); + expect(state).toMatchSnapshot(); + }); + + test('Delete web link init', () => { + state = reducer(state, mockActions.profile.deleteWebLinkInit()); + expect(state).toMatchSnapshot(); + }); + + test('Delete web link done', () => { + state = reducer(state, mockActions.profile.deleteWebLinkDone()); + expect(state).toMatchSnapshot(); + }); + + test('Link external account init', () => { + state = reducer(state, mockActions.profile.linkExternalAccountInit()); + expect(state).toMatchSnapshot(); + }); + + test('Link external account done', () => { + state = reducer(state, mockActions.profile.linkExternalAccountDone()); + expect(state).toMatchSnapshot(); + }); + + test('Unlink external account init', () => { + state = reducer(state, mockActions.profile.unlinkExternalAccountInit()); + expect(state).toMatchSnapshot(); + }); + + test('Unlink external account done', () => { + state = reducer(state, mockActions.profile.unlinkExternalAccountDone()); + expect(state).toMatchSnapshot(); + }); + + test('Save email preferences init', () => { + state = reducer(state, mockActions.profile.saveEmailPreferencesInit()); + expect(state).toMatchSnapshot(); + }); + + test('Save email preferences done', () => { + state = reducer(state, mockActions.profile.saveEmailPreferencesDone()); + expect(state).toMatchSnapshot(); + }); + + test('Update password init', () => { + state = reducer(state, mockActions.profile.updatePasswordInit()); + expect(state).toMatchSnapshot(); + }); + + test('Update password done', () => { + state = reducer(state, mockActions.profile.updatePasswordDone()); + expect(state).toMatchSnapshot(); + }); +} + +describe('Default reducer', () => { + reducer = reducers.default; + testReducer(); +}); + +describe('Factory without server side rendering', () => { + beforeAll((done) => { + reducers.factory().then((res) => { + reducer = res; + done(); + }); + }); + + testReducer(); +}); + diff --git a/docs/actions.challenge.md b/docs/actions.challenge.md index 9d34a964..23d16fb5 100644 --- a/docs/actions.challenge.md +++ b/docs/actions.challenge.md @@ -22,6 +22,8 @@ Actions related to Topcoder challenges APIs. * [.toggleCheckpointFeedback(id, open)](#module_actions.challenge.toggleCheckpointFeedback) ⇒ Action * [.updateChallengeInit(uuid)](#module_actions.challenge.updateChallengeInit) ⇒ Action * [.updateChallengeDone(uuid, challenge, tokenV3)](#module_actions.challenge.updateChallengeDone) ⇒ Action + * [.getActiveChallengesCountInit()](#module_actions.challenge.getActiveChallengesCountInit) ⇒ Action + * [.getActiveChallengesCountDone(handle, tokenV3)](#module_actions.challenge.getActiveChallengesCountDone) ⇒ Action @@ -226,3 +228,21 @@ Creates an action that updates challenge details. | challenge | Object | Challenge data. | | tokenV3 | String | Topcoder v3 auth token. | + + +### actions.challenge.getActiveChallengesCountInit() ⇒ Action +Creates an action that signals beginning of getting count of user's active challenges. + +**Kind**: static method of [actions.challenge](#module_actions.challenge) + + +### actions.challenge.getActiveChallengesCountDone(handle, tokenV3) ⇒ Action +Creates an action that gets count of user's active challenges from the backend. + +**Kind**: static method of [actions.challenge](#module_actions.challenge) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle. | +| tokenV3 | String | Optional. Topcoder auth token v3. Without token only public challenges will be counted. With the token provided, the action will also count private challenges related to this user. | + diff --git a/docs/actions.lookup.md b/docs/actions.lookup.md new file mode 100644 index 00000000..1db62d1e --- /dev/null +++ b/docs/actions.lookup.md @@ -0,0 +1,22 @@ + + +## actions.lookup +Actions related to lookup data. + + +* [actions.lookup](#module_actions.lookup) + * [.getSkillTagsInit()](#module_actions.lookup.getSkillTagsInit) ⇒ Action + * [.getSkillTagsDone()](#module_actions.lookup.getSkillTagsDone) ⇒ Action + + + +### actions.lookup.getSkillTagsInit() ⇒ Action +Creates an action that signals beginning of getting all skill tags. + +**Kind**: static method of [actions.lookup](#module_actions.lookup) + + +### actions.lookup.getSkillTagsDone() ⇒ Action +Creates an action that gets all skill tags. + +**Kind**: static method of [actions.lookup](#module_actions.lookup) diff --git a/docs/actions.members.md b/docs/actions.members.md index d5a54c27..7a04f558 100644 --- a/docs/actions.members.md +++ b/docs/actions.members.md @@ -13,6 +13,12 @@ Actions related to members data. * [.getFinancesDone(handle, uuid, tokenV3)](#module_actions.members.getFinancesDone) ⇒ Action * [.getStatsInit(handle, uuid)](#module_actions.members.getStatsInit) ⇒ Action * [.getStatsDone(handle, uuid, tokenV3)](#module_actions.members.getStatsDone) ⇒ Action + * [.getActiveChallengesInit(handle, uuid)](#module_actions.members.getActiveChallengesInit) ⇒ Object + * [.getActiveChallengesDone(handle, uuid, tokenV3)](#module_actions.members.getActiveChallengesDone) ⇒ Object + * [.getStatsHistoryInit(handle, uuid)](#module_actions.members.getStatsHistoryInit) ⇒ Action + * [.getStatsHistoryDone(handle, uuid, tokenV3)](#module_actions.members.getStatsHistoryDone) ⇒ Action + * [.getStatsDistributionInit(handle, uuid)](#module_actions.members.getStatsDistributionInit) ⇒ Action + * [.getStatsDistributionDone(handle, track, subTrack, uuid, tokenV3)](#module_actions.members.getStatsDistributionDone) ⇒ Action @@ -109,3 +115,82 @@ Create an action that loads member statistics. | uuid | String | Operation UUID. | | tokenV3 | String | v3 auth token. | + + +### actions.members.getActiveChallengesInit(handle, uuid) ⇒ Object +Payload creator for the action that inits the loading of member active challenges. + +**Kind**: static method of [actions.members](#module_actions.members) +**Returns**: Object - Payload + +| Param | Type | +| --- | --- | +| handle | String | +| uuid | String | + + + +### actions.members.getActiveChallengesDone(handle, uuid, tokenV3) ⇒ Object +Payload creator for the action that loads the member active challenges. + +**Kind**: static method of [actions.members](#module_actions.members) +**Returns**: Object - Payload + +| Param | Type | +| --- | --- | +| handle | String | +| uuid | String | +| tokenV3 | String | + + + +### actions.members.getStatsHistoryInit(handle, uuid) ⇒ Action +Create an action that signals beginning of member stats distribution history. + +**Kind**: static method of [actions.members](#module_actions.members) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Member handle. | +| uuid | String | Operation UUID. | + + + +### actions.members.getStatsHistoryDone(handle, uuid, tokenV3) ⇒ Action +Create an action that loads the member stats history. + +**Kind**: static method of [actions.members](#module_actions.members) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Member handle. | +| uuid | String | Operation UUID. | +| tokenV3 | String | v3 auth token. | + + + +### actions.members.getStatsDistributionInit(handle, uuid) ⇒ Action +Create an action that signals beginning of member stats distribution loading. + +**Kind**: static method of [actions.members](#module_actions.members) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Member handle. | +| uuid | String | Operation UUID. | + + + +### actions.members.getStatsDistributionDone(handle, track, subTrack, uuid, tokenV3) ⇒ Action +Create an action that loads the member stats distribution. + +**Kind**: static method of [actions.members](#module_actions.members) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Member handle. | +| track | String | Main track name. | +| subTrack | String | Subtrack name. | +| uuid | String | Operation UUID. | +| tokenV3 | String | v3 auth token. | + diff --git a/docs/actions.profile.md b/docs/actions.profile.md index 14e5b1fd..e84440c2 100644 --- a/docs/actions.profile.md +++ b/docs/actions.profile.md @@ -23,6 +23,33 @@ Actions for interactions with profile details API. * [.getSkillsDone(handle)](#module_actions.profile.getSkillsDone) ⇒ Action * [.getStatsInit()](#module_actions.profile.getStatsInit) ⇒ Action * [.getStatsDone(handle)](#module_actions.profile.getStatsDone) ⇒ Action + * [.getLinkedAccountsInit()](#module_actions.profile.getLinkedAccountsInit) ⇒ Action + * [.getLinkedAccountsDone(profile, tokenV3)](#module_actions.profile.getLinkedAccountsDone) ⇒ Action + * [.getCredentialInit()](#module_actions.profile.getCredentialInit) ⇒ Action + * [.getCredentialDone(profile, tokenV3)](#module_actions.profile.getCredentialDone) ⇒ Action + * [.getEmailPreferencesInit()](#module_actions.profile.getEmailPreferencesInit) ⇒ Action + * [.getEmailPreferencesDone(profile, tokenV3)](#module_actions.profile.getEmailPreferencesDone) ⇒ Action + * [.uploadPhotoInit()](#module_actions.profile.uploadPhotoInit) ⇒ Action + * [.uploadPhotoDone(handle, tokenV3, file)](#module_actions.profile.uploadPhotoDone) ⇒ Action + * [.deletePhotoInit()](#module_actions.profile.deletePhotoInit) ⇒ Action + * [.updateProfileInit()](#module_actions.profile.updateProfileInit) ⇒ Action + * [.updateProfileDone(profile, tokenV3)](#module_actions.profile.updateProfileDone) ⇒ Action + * [.addSkillInit()](#module_actions.profile.addSkillInit) ⇒ Action + * [.addSkillDone(handle, tokenV3, skill)](#module_actions.profile.addSkillDone) ⇒ Action + * [.hideSkillInit()](#module_actions.profile.hideSkillInit) ⇒ Action + * [.hideSkillDone(handle, tokenV3, skill)](#module_actions.profile.hideSkillDone) ⇒ Action + * [.addWebLinkInit()](#module_actions.profile.addWebLinkInit) ⇒ Action + * [.addWebLinkDone(handle, tokenV3, webLink)](#module_actions.profile.addWebLinkDone) ⇒ Action + * [.deleteWebLinkInit(key)](#module_actions.profile.deleteWebLinkInit) ⇒ Action + * [.deleteWebLinkDone(handle, tokenV3, webLink)](#module_actions.profile.deleteWebLinkDone) ⇒ Action + * [.linkExternalAccountInit()](#module_actions.profile.linkExternalAccountInit) ⇒ Action + * [.linkExternalAccountDone(profile, tokenV3, providerType, callbackUrl)](#module_actions.profile.linkExternalAccountDone) ⇒ Action + * [.unlinkExternalAccountInit(providerType)](#module_actions.profile.unlinkExternalAccountInit) ⇒ Action + * [.unlinkExternalAccountDone(profile, tokenV3, providerType)](#module_actions.profile.unlinkExternalAccountDone) ⇒ Action + * [.saveEmailPreferencesInit()](#module_actions.profile.saveEmailPreferencesInit) ⇒ Action + * [.saveEmailPreferencesDone(profile, tokenV3, preferences)](#module_actions.profile.saveEmailPreferencesDone) ⇒ Action + * [.updatePasswordInit()](#module_actions.profile.updatePasswordInit) ⇒ Action + * [.updatePasswordDone(profile, tokenV3, newPassword, oldPassword)](#module_actions.profile.updatePasswordDone) ⇒ Action @@ -167,3 +194,264 @@ Creates an action that loads member's stats. | --- | --- | --- | | handle | String | Member handle. | + + +### actions.profile.getLinkedAccountsInit() ⇒ Action +Creates an action that signals beginning of getting linked accounts. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.getLinkedAccountsDone(profile, tokenV3) ⇒ Action +Creates an action that gets linked accounts. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | Topcoder member profile. | +| tokenV3 | String | Topcoder auth token v3. | + + + +### actions.profile.getCredentialInit() ⇒ Action +Creates an action that signals beginning of getting credential. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.getCredentialDone(profile, tokenV3) ⇒ Action +Creates an action that gets credential. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | Topcoder member profile. | +| tokenV3 | String | Topcoder auth token v3. | + + + +### actions.profile.getEmailPreferencesInit() ⇒ Action +Creates an action that signals beginning of getting email preferences. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.getEmailPreferencesDone(profile, tokenV3) ⇒ Action +Creates an action that gets email preferences. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | Topcoder member profile. | +| tokenV3 | String | Topcoder auth token v3. | + + + +### actions.profile.uploadPhotoInit() ⇒ Action +Creates an action that signals beginning of uploading user's photo. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.uploadPhotoDone(handle, tokenV3, file) ⇒ Action +Creates an action that uploads user's photo. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle. | +| tokenV3 | String | Topcoder auth token v3. | +| file | String | The photo file. | + + + +### actions.profile.deletePhotoInit() ⇒ Action +Creates an action that signals beginning of deleting user's photo. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.updateProfileInit() ⇒ Action +Creates an action that signals beginning of updating user's profile. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.updateProfileDone(profile, tokenV3) ⇒ Action +Creates an action that updates user's profile. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | String | Topcoder user profile. | +| tokenV3 | String | Topcoder auth token v3. | + + + +### actions.profile.addSkillInit() ⇒ Action +Creates an action that signals beginning of adding user's skill. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.addSkillDone(handle, tokenV3, skill) ⇒ Action +Creates an action that adds user's skill. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle. | +| tokenV3 | String | Topcoder auth token v3. | +| skill | Object | Skill to add. | + + + +### actions.profile.hideSkillInit() ⇒ Action +Creates an action that signals beginning of hiding user's skill. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.hideSkillDone(handle, tokenV3, skill) ⇒ Action +Creates an action that hides user's skill. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle. | +| tokenV3 | String | Topcoder auth token v3. | +| skill | Object | Skill to hide. | + + + +### actions.profile.addWebLinkInit() ⇒ Action +Creates an action that signals beginning of adding user's web link. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.addWebLinkDone(handle, tokenV3, webLink) ⇒ Action +Creates an action that adds user's web link. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle. | +| tokenV3 | String | Topcoder auth token v3. | +| webLink | String | Web link to add. | + + + +### actions.profile.deleteWebLinkInit(key) ⇒ Action +Creates an action that signals beginning of deleting user's web link. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| key | Object | Web link key to delete. | + + + +### actions.profile.deleteWebLinkDone(handle, tokenV3, webLink) ⇒ Action +Creates an action that deletes user's web link. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle. | +| tokenV3 | String | Topcoder auth token v3. | +| webLink | String | Web link to delete. | + + + +### actions.profile.linkExternalAccountInit() ⇒ Action +Creates an action that signals beginning of linking external account. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.linkExternalAccountDone(profile, tokenV3, providerType, callbackUrl) ⇒ Action +Creates an action that links external account. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | Topcoder member handle. | +| tokenV3 | String | Topcoder auth token v3. | +| providerType | String | The external account service provider | +| callbackUrl | String | Optional. The callback url | + + + +### actions.profile.unlinkExternalAccountInit(providerType) ⇒ Action +Creates an action that signals beginning of unlinking external account. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| providerType | Object | External account provider type to delete. | + + + +### actions.profile.unlinkExternalAccountDone(profile, tokenV3, providerType) ⇒ Action +Creates an action that unlinks external account. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | Topcoder member profile. | +| tokenV3 | String | Topcoder auth token v3. | +| providerType | String | The external account service provider | + + + +### actions.profile.saveEmailPreferencesInit() ⇒ Action +Creates an action that signals beginning of saving email preferences. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.saveEmailPreferencesDone(profile, tokenV3, preferences) ⇒ Action +Creates an action that saves email preferences. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | Topcoder member profile. | +| tokenV3 | String | Topcoder auth token v3. | +| preferences | Object | The email preferences | + + + +### actions.profile.updatePasswordInit() ⇒ Action +Creates an action that signals beginning of updating user password. + +**Kind**: static method of [actions.profile](#module_actions.profile) + + +### actions.profile.updatePasswordDone(profile, tokenV3, newPassword, oldPassword) ⇒ Action +Creates an action that updates user password. + +**Kind**: static method of [actions.profile](#module_actions.profile) + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | Topcoder member profile. | +| tokenV3 | String | Topcoder auth token v3. | +| newPassword | String | The new password | +| oldPassword | String | The old password | + diff --git a/docs/index.md b/docs/index.md index 70f1bb72..ec065107 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,6 +25,10 @@ messaging.

Actions related to user groups.

+actions.lookup
+

Actions related to lookup data.

+
+
actions.member-tasks

Actions for management of member tasks and payments. Under the hood it is very similar to the challenge listing management, as these tasks are in @@ -94,6 +98,11 @@ not look really necessary at the moment, thus we do not provide an action to really cancel group loading.

+reducers.lookup
+

Reducer for actions.lookup actions.

+

State segment managed by this reducer has the following structure:

+
+
reduces.member-tasks

Member tasks reducer.

@@ -162,6 +171,11 @@ Also each group in the group map is timestamped to keep caching of the loaded data.

+services.lookup
+

This module provides a service to get lookup data from Topcoder +via API V3.

+
+
services.members

This module provides a service for searching for Topcoder members via API V3.

diff --git a/docs/reducers.challenge.md b/docs/reducers.challenge.md index 5d53f539..d2f2bd0e 100644 --- a/docs/reducers.challenge.md +++ b/docs/reducers.challenge.md @@ -26,6 +26,7 @@ State segment managed by this reducer has the following strcuture: * [~onUnregisterDone(state, action)](#module_reducers.challenge..onUnregisterDone) ⇒ Object * [~onUpdateChallengeInit(state, actions)](#module_reducers.challenge..onUpdateChallengeInit) ⇒ Object * [~onUpdateChallengeDone(state, actions)](#module_reducers.challenge..onUpdateChallengeDone) ⇒ Object + * [~onGetActiveChallengesCountDone(state, action)](#module_reducers.challenge..onGetActiveChallengesCountDone) ⇒ Object * [~create(initialState)](#module_reducers.challenge..create) ⇒ function @@ -192,6 +193,19 @@ Handles CHALLENGE/UPDATE_CHALLENGE_DONE. | state | Object | Old state. | | actions | Object | Action. | + + +### reducers.challenge~onGetActiveChallengesCountDone(state, action) ⇒ Object +Handles CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE action. + +**Kind**: inner method of [reducers.challenge](#module_reducers.challenge) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | Old state. | +| action | Object | Action payload/error | + ### reducers.challenge~create(initialState) ⇒ function diff --git a/docs/reducers.lookup.md b/docs/reducers.lookup.md new file mode 100644 index 00000000..340e2d25 --- /dev/null +++ b/docs/reducers.lookup.md @@ -0,0 +1,59 @@ + + +## reducers.lookup +Reducer for [actions.lookup](#module_actions.lookup) actions. + +State segment managed by this reducer has the following structure: + + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| skillTags | Array | '' | skill tags. | + + +* [reducers.lookup](#module_reducers.lookup) + * _static_ + * [.default](#module_reducers.lookup.default) + * [.factory()](#module_reducers.lookup.factory) ⇒ Promise + * _inner_ + * [~onGetSkillTagsDone(state, action)](#module_reducers.lookup..onGetSkillTagsDone) ⇒ Object + * [~create(initialState)](#module_reducers.lookup..create) ⇒ function + + + +### reducers.lookup.default +Reducer with default initial state. + +**Kind**: static property of [reducers.lookup](#module_reducers.lookup) + + +### reducers.lookup.factory() ⇒ Promise +Factory which creates a new reducer. + +**Kind**: static method of [reducers.lookup](#module_reducers.lookup) +**Resolves**: Function(state, action): state New reducer. + + +### reducers.lookup~onGetSkillTagsDone(state, action) ⇒ Object +Handles LOOKUP/GET_SKILL_TAGS_DONE action. + +**Kind**: inner method of [reducers.lookup](#module_reducers.lookup) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.lookup~create(initialState) ⇒ function +Creates a new Lookup reducer with the specified initial state. + +**Kind**: inner method of [reducers.lookup](#module_reducers.lookup) +**Returns**: function - Lookup reducer. + +| Param | Type | Description | +| --- | --- | --- | +| initialState | Object | Optional. Initial state. | + diff --git a/docs/reducers.members.md b/docs/reducers.members.md index ed4f5064..4d75fcfc 100644 --- a/docs/reducers.members.md +++ b/docs/reducers.members.md @@ -21,6 +21,12 @@ Reducer for the Redux store segment that holds members data. * [~onGetFinancesDone(state, action)](#module_reducers.members..onGetFinancesDone) ⇒ Object * [~onGetStatsInit(state, action)](#module_reducers.members..onGetStatsInit) ⇒ Object * [~onGetStatsDone(state, action)](#module_reducers.members..onGetStatsDone) ⇒ Object + * [~onGetStatsHistoryInit(state, action)](#module_reducers.members..onGetStatsHistoryInit) ⇒ Object + * [~onGetStatsHistoryDone(state, action)](#module_reducers.members..onGetStatsHistoryDone) ⇒ Object + * [~onGetStatsDistributionInit(state, action)](#module_reducers.members..onGetStatsDistributionInit) ⇒ Object + * [~onGetStatsDistributionDone(state, action)](#module_reducers.members..onGetStatsDistributionDone) ⇒ Object + * [~onGetActiveChallengesInit(state, action)](#module_reducers.members..onGetActiveChallengesInit) ⇒ Object + * [~onGetActiveChallengesDone(state, action)](#module_reducers.members..onGetActiveChallengesDone) ⇒ Object * [~create(initialState)](#module_reducers.members..create) ⇒ function @@ -141,6 +147,84 @@ Finalizes the loading of member stats. | state | Object | | action | Object | + + +### reducers.members~onGetStatsHistoryInit(state, action) ⇒ Object +Inits the loading of member stats history. + +**Kind**: inner method of [reducers.members](#module_reducers.members) +**Returns**: Object - New state. + +| Param | Type | +| --- | --- | +| state | Object | +| action | Object | + + + +### reducers.members~onGetStatsHistoryDone(state, action) ⇒ Object +Finalizes the loading of member stats history. + +**Kind**: inner method of [reducers.members](#module_reducers.members) +**Returns**: Object - New state. + +| Param | Type | +| --- | --- | +| state | Object | +| action | Object | + + + +### reducers.members~onGetStatsDistributionInit(state, action) ⇒ Object +Inits the loading of member stats distribution. + +**Kind**: inner method of [reducers.members](#module_reducers.members) +**Returns**: Object - New state. + +| Param | Type | +| --- | --- | +| state | Object | +| action | Object | + + + +### reducers.members~onGetStatsDistributionDone(state, action) ⇒ Object +Finalizes the loading of member stats distribution. + +**Kind**: inner method of [reducers.members](#module_reducers.members) +**Returns**: Object - New state. + +| Param | Type | +| --- | --- | +| state | Object | +| action | Object | + + + +### reducers.members~onGetActiveChallengesInit(state, action) ⇒ Object +Inits the loading of member active challenges. + +**Kind**: inner method of [reducers.members](#module_reducers.members) +**Returns**: Object - New state. + +| Param | Type | +| --- | --- | +| state | Object | +| action | Object | + + + +### reducers.members~onGetActiveChallengesDone(state, action) ⇒ Object +Finalizes the loading of member active challenges. + +**Kind**: inner method of [reducers.members](#module_reducers.members) +**Returns**: Object - New state. + +| Param | Type | +| --- | --- | +| state | Object | +| action | Object | + ### reducers.members~create(initialState) ⇒ function diff --git a/docs/reducers.profile.md b/docs/reducers.profile.md index 5492081a..979675c0 100644 --- a/docs/reducers.profile.md +++ b/docs/reducers.profile.md @@ -18,6 +18,20 @@ Reducer for Profile API data * [~onGetInfoDone(state, action)](#module_reducers.profile..onGetInfoDone) ⇒ Object * [~onGetSkillsDone(state, action)](#module_reducers.profile..onGetSkillsDone) ⇒ Object * [~onGetStatsDone(state, action)](#module_reducers.profile..onGetStatsDone) ⇒ Object + * [~onGetLinkedAccountsDone(state, action)](#module_reducers.profile..onGetLinkedAccountsDone) ⇒ Object + * [~onGetCredentialDone(state, action)](#module_reducers.profile..onGetCredentialDone) ⇒ Object + * [~onGetEmailPreferencesDone(state, action)](#module_reducers.profile..onGetEmailPreferencesDone) ⇒ Object + * [~onUploadPhotoDone(state, action)](#module_reducers.profile..onUploadPhotoDone) ⇒ Object + * [~onDeletePhotoDone(state, action)](#module_reducers.profile..onDeletePhotoDone) ⇒ Object + * [~onUpdateProfileDone(state, action)](#module_reducers.profile..onUpdateProfileDone) ⇒ Object + * [~onAddSkillDone(state, action)](#module_reducers.profile..onAddSkillDone) ⇒ Object + * [~onHideSkillDone(state, action)](#module_reducers.profile..onHideSkillDone) ⇒ Object + * [~onAddWebLinkDone(state, action)](#module_reducers.profile..onAddWebLinkDone) ⇒ Object + * [~onDeleteWebLinkDone(state, action)](#module_reducers.profile..onDeleteWebLinkDone) ⇒ Object + * [~onLinkExternalAccountDone(state, action)](#module_reducers.profile..onLinkExternalAccountDone) ⇒ Object + * [~onUnlinkExternalAccountDone(state, action)](#module_reducers.profile..onUnlinkExternalAccountDone) ⇒ Object + * [~onSaveEmailPreferencesDone(state, action)](#module_reducers.profile..onSaveEmailPreferencesDone) ⇒ Object + * [~onUpdatePasswordDone(state, action)](#module_reducers.profile..onUpdatePasswordDone) ⇒ Object * [~create(initialState)](#module_reducers.profile..create) ⇒ function @@ -107,6 +121,188 @@ Handles PROFILE/GET_STATS_DONE action. | state | Object | | | action | Object | Payload will be JSON from api call | + + +### reducers.profile~onGetLinkedAccountsDone(state, action) ⇒ Object +Handles PROFILE/GET_LINKED_ACCOUNTS_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onGetCredentialDone(state, action) ⇒ Object +Handles PROFILE/GET_CREDENTIAL_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onGetEmailPreferencesDone(state, action) ⇒ Object +Handles PROFILE/GET_EMAIL_PREFERENCES_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onUploadPhotoDone(state, action) ⇒ Object +Handles PROFILE/UPLOAD_PHOTO_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onDeletePhotoDone(state, action) ⇒ Object +Handles PROFILE/DELETE_PHOTO_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onUpdateProfileDone(state, action) ⇒ Object +Handles PROFILE/UPDATE_PROFILE_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onAddSkillDone(state, action) ⇒ Object +Handles PROFILE/ADD_SKILL_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onHideSkillDone(state, action) ⇒ Object +Handles PROFILE/HIDE_SKILL_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onAddWebLinkDone(state, action) ⇒ Object +Handles PROFILE/ADD_WEB_LINK_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onDeleteWebLinkDone(state, action) ⇒ Object +Handles PROFILE/DELETE_WEB_LINK_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onLinkExternalAccountDone(state, action) ⇒ Object +Handles PROFILE/LINK_EXTERNAL_ACCOUNT_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onUnlinkExternalAccountDone(state, action) ⇒ Object +Handles PROFILE/UNLINK_EXTERNAL_ACCOUNT_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onSaveEmailPreferencesDone(state, action) ⇒ Object +Handles PROFILE/SAVE_EMAIL_PREFERENCES_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + + + +### reducers.profile~onUpdatePasswordDone(state, action) ⇒ Object +Handles PROFILE/UPDATE_PASSWORD_DONE action. + +**Kind**: inner method of [reducers.profile](#module_reducers.profile) +**Returns**: Object - New state + +| Param | Type | Description | +| --- | --- | --- | +| state | Object | | +| action | Object | Payload will be JSON from api call | + ### reducers.profile~create(initialState) ⇒ function diff --git a/docs/services.api.md b/docs/services.api.md index 015b5986..eb787a8e 100644 --- a/docs/services.api.md +++ b/docs/services.api.md @@ -19,6 +19,8 @@ This module provides a service for conventient access to Topcoder APIs. * [.postJson(endpoint, json)](#module_services.api..Api+postJson) ⇒ Promise * [.put(endpoint, body)](#module_services.api..Api+put) ⇒ Promise * [.putJson(endpoint, json)](#module_services.api..Api+putJson) ⇒ Promise + * [.patch(endpoint, body)](#module_services.api..Api+patch) ⇒ Promise + * [.patchJson(endpoint, json)](#module_services.api..Api+patchJson) ⇒ Promise * [.upload(endpoint, body, callback)](#module_services.api..Api+upload) ⇒ Promise @@ -70,6 +72,8 @@ thing we need to be different is the base URL and auth token to use. * [.postJson(endpoint, json)](#module_services.api..Api+postJson) ⇒ Promise * [.put(endpoint, body)](#module_services.api..Api+put) ⇒ Promise * [.putJson(endpoint, json)](#module_services.api..Api+putJson) ⇒ Promise + * [.patch(endpoint, body)](#module_services.api..Api+patch) ⇒ Promise + * [.patchJson(endpoint, json)](#module_services.api..Api+patchJson) ⇒ Promise * [.upload(endpoint, body, callback)](#module_services.api..Api+upload) ⇒ Promise @@ -182,6 +186,30 @@ Sends PUT request to the specified endpoint. | endpoint | String | | json | JSON | + + +#### api.patch(endpoint, body) ⇒ Promise +Sends PATCH request to the specified endpoint. + +**Kind**: instance method of [Api](#module_services.api..Api) + +| Param | Type | +| --- | --- | +| endpoint | String | +| body | Blob \| BufferSource \| FormData \| String | + + + +#### api.patchJson(endpoint, json) ⇒ Promise +Sends PATCH request to the specified endpoint. + +**Kind**: instance method of [Api](#module_services.api..Api) + +| Param | Type | +| --- | --- | +| endpoint | String | +| json | JSON | + #### api.upload(endpoint, body, callback) ⇒ Promise diff --git a/docs/services.challenges.md b/docs/services.challenges.md index c33d9fff..bf0c48d9 100644 --- a/docs/services.challenges.md +++ b/docs/services.challenges.md @@ -28,6 +28,7 @@ This module provides a service for convenient manipulation with * [.register(challengeId)](#module_services.challenges..ChallengesService+register) ⇒ Promise * [.unregister(challengeId)](#module_services.challenges..ChallengesService+unregister) ⇒ Promise * [.getUserMarathonMatches(username, filters, params)](#module_services.challenges..ChallengesService+getUserMarathonMatches) ⇒ Promise + * [.getActiveChallengesCount(handle)](#module_services.challenges..ChallengesService+getActiveChallengesCount) ⇒ Action * [.submit(body, challengeId, track)](#module_services.challenges..ChallengesService+submit) ⇒ Promise * [.updateChallenge(challenge, tokenV3)](#module_services.challenges..ChallengesService+updateChallenge) ⇒ Promise * [~normalizeNameConventionForSubtrack(subTrack)](#module_services.challenges..normalizeNameConventionForSubtrack) ⇒ String @@ -128,6 +129,7 @@ Challenge service. * [.register(challengeId)](#module_services.challenges..ChallengesService+register) ⇒ Promise * [.unregister(challengeId)](#module_services.challenges..ChallengesService+unregister) ⇒ Promise * [.getUserMarathonMatches(username, filters, params)](#module_services.challenges..ChallengesService+getUserMarathonMatches) ⇒ Promise + * [.getActiveChallengesCount(handle)](#module_services.challenges..ChallengesService+getActiveChallengesCount) ⇒ Action * [.submit(body, challengeId, track)](#module_services.challenges..ChallengesService+submit) ⇒ Promise * [.updateChallenge(challenge, tokenV3)](#module_services.challenges..ChallengesService+updateChallenge) ⇒ Promise @@ -314,6 +316,18 @@ Gets marathon matches of the specified user. | filters | Object | Optional. | | params | Number | Optional. | + + +#### challengesService.getActiveChallengesCount(handle) ⇒ Action +Gets count of user's active challenges. + +**Kind**: instance method of [ChallengesService](#module_services.challenges..ChallengesService) +**Returns**: Action - Resolves to the api response. + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle. | + #### challengesService.submit(body, challengeId, track) ⇒ Promise diff --git a/docs/services.lookup.md b/docs/services.lookup.md new file mode 100644 index 00000000..a0088232 --- /dev/null +++ b/docs/services.lookup.md @@ -0,0 +1,56 @@ + + +## services.lookup +This module provides a service to get lookup data from Topcoder +via API V3. + + +* [services.lookup](#module_services.lookup) + * _static_ + * [.getService(tokenV3)](#module_services.lookup.getService) ⇒ LookupService + * _inner_ + * [~LookupService](#module_services.lookup..LookupService) + * [new LookupService(tokenV3)](#new_module_services.lookup..LookupService_new) + * [.getTags(params)](#module_services.lookup..LookupService+getTags) ⇒ Promise + + + +### services.lookup.getService(tokenV3) ⇒ LookupService +Returns a new or existing lookup service. + +**Kind**: static method of [services.lookup](#module_services.lookup) +**Returns**: LookupService - Lookup service object + +| Param | Type | Description | +| --- | --- | --- | +| tokenV3 | String | Optional. Auth token for Topcoder API v3. | + + + +### services.lookup~LookupService +**Kind**: inner class of [services.lookup](#module_services.lookup) + +* [~LookupService](#module_services.lookup..LookupService) + * [new LookupService(tokenV3)](#new_module_services.lookup..LookupService_new) + * [.getTags(params)](#module_services.lookup..LookupService+getTags) ⇒ Promise + + + +#### new LookupService(tokenV3) + +| Param | Type | Description | +| --- | --- | --- | +| tokenV3 | String | Optional. Auth token for Topcoder API v3. | + + + +#### lookupService.getTags(params) ⇒ Promise +Gets tags. + +**Kind**: instance method of [LookupService](#module_services.lookup..LookupService) +**Returns**: Promise - Resolves to the tags. + +| Param | Type | Description | +| --- | --- | --- | +| params | Object | Parameters | + diff --git a/docs/services.members.md b/docs/services.members.md index 2af2f272..3ecc99b0 100644 --- a/docs/services.members.md +++ b/docs/services.members.md @@ -17,7 +17,17 @@ members via API V3. * [.getExternalLinks(handle)](#module_services.members..MembersService+getExternalLinks) ⇒ Promise * [.getSkills(handle)](#module_services.members..MembersService+getSkills) ⇒ Promise * [.getStats(handle)](#module_services.members..MembersService+getStats) ⇒ Promise + * [.getStatsHistory(handle)](#module_services.members..MembersService+getStatsHistory) ⇒ Promise + * [.getStatsDistribution(handle, track, subTrack)](#module_services.members..MembersService+getStatsDistribution) ⇒ Promise * [.getMemberSuggestions(keyword)](#module_services.members..MembersService+getMemberSuggestions) ⇒ Promise + * [.addWebLink(userHandle, webLink)](#module_services.members..MembersService+addWebLink) ⇒ Promise + * [.deleteWebLink(userHandle, webLinkHandle)](#module_services.members..MembersService+deleteWebLink) ⇒ Promise + * [.addSkill(handle, skillTagId)](#module_services.members..MembersService+addSkill) ⇒ Promise + * [.hideSkill(handle, skillTagId)](#module_services.members..MembersService+hideSkill) ⇒ Promise + * [.updateMemberProfile(profile)](#module_services.members..MembersService+updateMemberProfile) ⇒ Promise + * [.getPresignedUrl(userHandle, file)](#module_services.members..MembersService+getPresignedUrl) ⇒ Promise + * [.updateMemberPhoto(S3Response)](#module_services.members..MembersService+updateMemberPhoto) ⇒ Promise + * [.uploadFileToS3(presignedUrlResponse)](#module_services.members..MembersService+uploadFileToS3) ⇒ Promise @@ -46,7 +56,17 @@ Service class. * [.getExternalLinks(handle)](#module_services.members..MembersService+getExternalLinks) ⇒ Promise * [.getSkills(handle)](#module_services.members..MembersService+getSkills) ⇒ Promise * [.getStats(handle)](#module_services.members..MembersService+getStats) ⇒ Promise + * [.getStatsHistory(handle)](#module_services.members..MembersService+getStatsHistory) ⇒ Promise + * [.getStatsDistribution(handle, track, subTrack)](#module_services.members..MembersService+getStatsDistribution) ⇒ Promise * [.getMemberSuggestions(keyword)](#module_services.members..MembersService+getMemberSuggestions) ⇒ Promise + * [.addWebLink(userHandle, webLink)](#module_services.members..MembersService+addWebLink) ⇒ Promise + * [.deleteWebLink(userHandle, webLinkHandle)](#module_services.members..MembersService+deleteWebLink) ⇒ Promise + * [.addSkill(handle, skillTagId)](#module_services.members..MembersService+addSkill) ⇒ Promise + * [.hideSkill(handle, skillTagId)](#module_services.members..MembersService+hideSkill) ⇒ Promise + * [.updateMemberProfile(profile)](#module_services.members..MembersService+updateMemberProfile) ⇒ Promise + * [.getPresignedUrl(userHandle, file)](#module_services.members..MembersService+getPresignedUrl) ⇒ Promise + * [.updateMemberPhoto(S3Response)](#module_services.members..MembersService+updateMemberPhoto) ⇒ Promise + * [.uploadFileToS3(presignedUrlResponse)](#module_services.members..MembersService+uploadFileToS3) ⇒ Promise @@ -130,6 +150,32 @@ Gets member statistics. | --- | --- | | handle | String | + + +#### membersService.getStatsHistory(handle) ⇒ Promise +Gets member statistics history + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the stats object. + +| Param | Type | +| --- | --- | +| handle | String | + + + +#### membersService.getStatsDistribution(handle, track, subTrack) ⇒ Promise +Gets member statistics distribution + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the stats object. + +| Param | Type | +| --- | --- | +| handle | String | +| track | String | +| subTrack | String | + #### membersService.getMemberSuggestions(keyword) ⇒ Promise @@ -144,3 +190,104 @@ WARNING: This method requires v3 authorization. | --- | --- | --- | | keyword | String | Partial string to find suggestions for | + + +#### membersService.addWebLink(userHandle, webLink) ⇒ Promise +Adds external web link for member. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the api response content + +| Param | Type | Description | +| --- | --- | --- | +| userHandle | String | The user handle | +| webLink | String | The external web link | + + + +#### membersService.deleteWebLink(userHandle, webLinkHandle) ⇒ Promise +Deletes external web link for member. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the api response content + +| Param | Type | Description | +| --- | --- | --- | +| userHandle | String | The user handle | +| webLinkHandle | String | The external web link handle | + + + +#### membersService.addSkill(handle, skillTagId) ⇒ Promise +Adds user skill. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to operation result + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle | +| skillTagId | Number | Skill tag id | + + + +#### membersService.hideSkill(handle, skillTagId) ⇒ Promise +Hides user skill. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to operation result + +| Param | Type | Description | +| --- | --- | --- | +| handle | String | Topcoder user handle | +| skillTagId | Number | Skill tag id | + + + +#### membersService.updateMemberProfile(profile) ⇒ Promise +Updates member profile. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the api response content + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | The profile to update. | + + + +#### membersService.getPresignedUrl(userHandle, file) ⇒ Promise +Gets presigned url for member photo file. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the api response content + +| Param | Type | Description | +| --- | --- | --- | +| userHandle | String | The user handle | +| file | File | The file to get its presigned url | + + + +#### membersService.updateMemberPhoto(S3Response) ⇒ Promise +Updates member photo. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the api response content + +| Param | Type | Description | +| --- | --- | --- | +| S3Response | Object | The response from uploadFileToS3() function. | + + + +#### membersService.uploadFileToS3(presignedUrlResponse) ⇒ Promise +Uploads file to S3. + +**Kind**: instance method of [MembersService](#module_services.members..MembersService) +**Returns**: Promise - Resolves to the api response content + +| Param | Type | Description | +| --- | --- | --- | +| presignedUrlResponse | Object | The presigned url response from getPresignedUrl() function. | + diff --git a/docs/services.user.md b/docs/services.user.md index 0daaf23a..de282529 100644 --- a/docs/services.user.md +++ b/docs/services.user.md @@ -15,6 +15,14 @@ The User service provides functionality related to Topcoder user * [.getAchievements(username)](#module_services.user..User+getAchievements) ⇒ Object * [.getUserPublic(username)](#module_services.user..User+getUserPublic) ⇒ Object * [.getUser(username)](#module_services.user..User+getUser) ⇒ Promise + * [.getEmailPreferences(userId)](#module_services.user..User+getEmailPreferences) ⇒ Promise + * [.saveEmailPreferences(user, preferences)](#module_services.user..User+saveEmailPreferences) ⇒ Promise + * [.getCredential(userId)](#module_services.user..User+getCredential) ⇒ Promise + * [.updatePassword(userId, newPassword, oldPassword)](#module_services.user..User+updatePassword) ⇒ Promise + * [.getLinkedAccounts(userId)](#module_services.user..User+getLinkedAccounts) ⇒ Promise + * [.unlinkExternalAccount(userId, provider)](#module_services.user..User+unlinkExternalAccount) ⇒ Promise + * [.linkExternalAccount(userId, provider, callbackUrl)](#module_services.user..User+linkExternalAccount) ⇒ Promise + * [~getSocialUserData(profile, accessToken)](#module_services.user..getSocialUserData) ⇒ Object @@ -47,6 +55,13 @@ Service class. * [.getAchievements(username)](#module_services.user..User+getAchievements) ⇒ Object * [.getUserPublic(username)](#module_services.user..User+getUserPublic) ⇒ Object * [.getUser(username)](#module_services.user..User+getUser) ⇒ Promise + * [.getEmailPreferences(userId)](#module_services.user..User+getEmailPreferences) ⇒ Promise + * [.saveEmailPreferences(user, preferences)](#module_services.user..User+saveEmailPreferences) ⇒ Promise + * [.getCredential(userId)](#module_services.user..User+getCredential) ⇒ Promise + * [.updatePassword(userId, newPassword, oldPassword)](#module_services.user..User+updatePassword) ⇒ Promise + * [.getLinkedAccounts(userId)](#module_services.user..User+getLinkedAccounts) ⇒ Promise + * [.unlinkExternalAccount(userId, provider)](#module_services.user..User+unlinkExternalAccount) ⇒ Promise + * [.linkExternalAccount(userId, provider, callbackUrl)](#module_services.user..User+linkExternalAccount) ⇒ Promise @@ -95,3 +110,116 @@ NOTE: Only admins are authorized to use the underlying endpoint. | --- | --- | | username | String | + + +#### user.getEmailPreferences(userId) ⇒ Promise +Gets email preferences. + +NOTE: Only admins are authorized to use the underlying endpoint. + +**Kind**: instance method of [User](#module_services.user..User) +**Returns**: Promise - Resolves to the email preferences result + +| Param | Type | Description | +| --- | --- | --- | +| userId | Number | The TopCoder user id | + + + +#### user.saveEmailPreferences(user, preferences) ⇒ Promise +Saves email preferences. + +NOTE: Only admins are authorized to use the underlying endpoint. + +**Kind**: instance method of [User](#module_services.user..User) +**Returns**: Promise - Resolves to the email preferences result + +| Param | Type | Description | +| --- | --- | --- | +| user | Object | The TopCoder user | +| preferences | Object | The email preferences | + + + +#### user.getCredential(userId) ⇒ Promise +Gets credential for the specified user id. + +NOTE: Only admins are authorized to use the underlying endpoint. + +**Kind**: instance method of [User](#module_services.user..User) +**Returns**: Promise - Resolves to the linked accounts array. + +| Param | Type | Description | +| --- | --- | --- | +| userId | Number | The user id | + + + +#### user.updatePassword(userId, newPassword, oldPassword) ⇒ Promise +Updates user password. + +NOTE: Only admins are authorized to use the underlying endpoint. + +**Kind**: instance method of [User](#module_services.user..User) +**Returns**: Promise - Resolves to the update result. + +| Param | Type | Description | +| --- | --- | --- | +| userId | Number | The user id | +| newPassword | String | The new password | +| oldPassword | String | The old password | + + + +#### user.getLinkedAccounts(userId) ⇒ Promise +Gets linked accounts for the specified user id. + +NOTE: Only admins are authorized to use the underlying endpoint. + +**Kind**: instance method of [User](#module_services.user..User) +**Returns**: Promise - Resolves to the linked accounts array. + +| Param | Type | Description | +| --- | --- | --- | +| userId | Number | The user id | + + + +#### user.unlinkExternalAccount(userId, provider) ⇒ Promise +Unlinks external account. + +**Kind**: instance method of [User](#module_services.user..User) +**Returns**: Promise - Resolves to the unlink result + +| Param | Type | Description | +| --- | --- | --- | +| userId | Number | The TopCoder user id | +| provider | String | The external account service provider | + + + +#### user.linkExternalAccount(userId, provider, callbackUrl) ⇒ Promise +Links external account. + +**Kind**: instance method of [User](#module_services.user..User) +**Returns**: Promise - Resolves to the linked account result + +| Param | Type | Description | +| --- | --- | --- | +| userId | Number | The TopCoder user id | +| provider | String | The external account service provider | +| callbackUrl | String | Optional. The callback url | + + + +### services.user~getSocialUserData(profile, accessToken) ⇒ Object +Gets social user data. + +**Kind**: inner method of [services.user](#module_services.user) +**Returns**: Object - Social user data + +| Param | Type | Description | +| --- | --- | --- | +| profile | Object | The user social profile | +| accessToken | \* | The access token | + diff --git a/package-lock.json b/package-lock.json index a1b97f2d..a91f0eed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "topcoder-react-lib", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -176,6 +176,11 @@ "integrity": "sha512-LAQ1d4OPfSJ/BMbI2DuizmYrrkD9JMaTdi2hQTlI53lQ4kRQPyZQRS4CYQ7O66bnBBnP/oYdRxbk++X0xuFU6A==", "dev": true }, + "Base64": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.1.4.tgz", + "integrity": "sha1-6fbGvvVn/WNepBYqsU3TKedKpt4=" + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -643,6 +648,39 @@ "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", "dev": true }, + "auth0-js": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-6.8.4.tgz", + "integrity": "sha1-Qw3Uystk2NFdabHmIRhPmipkCmE=", + "requires": { + "Base64": "0.1.4", + "json-fallback": "0.0.1", + "jsonp": "0.0.4", + "qs": "git+https://github.com/jfromaniello/node-querystring.git#5d96513991635e3e22d7aa54a8584d6ce97cace8", + "reqwest": "1.1.6", + "trim": "0.0.1", + "winchan": "0.1.4", + "xtend": "2.1.2" + }, + "dependencies": { + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "qs": { + "version": "git+https://github.com/jfromaniello/node-querystring.git#5d96513991635e3e22d7aa54a8584d6ce97cace8" + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "requires": { + "object-keys": "0.4.0" + } + } + } + }, "autoprefixer": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.6.0.tgz", @@ -10784,6 +10822,11 @@ "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, + "json-fallback": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-fallback/-/json-fallback-0.0.1.tgz", + "integrity": "sha1-6OMIPD/drQ+bXwnTMSB0RCWA14E=" + }, "json-loader": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", @@ -10839,6 +10882,14 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, + "jsonp": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.0.4.tgz", + "integrity": "sha1-lGZaS3caq+y4qshBNbmVlHVpGL0=", + "requires": { + "debug": "2.6.9" + } + }, "jsonpointer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", @@ -15864,6 +15915,11 @@ } } }, + "reqwest": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/reqwest/-/reqwest-1.1.6.tgz", + "integrity": "sha1-S2iU0pWWv46CSiXzSXXfFVYu6BM=" + }, "reselect": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz", @@ -25301,8 +25357,7 @@ "trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", - "dev": true + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" }, "trim-newlines": { "version": "1.0.0", @@ -26614,6 +26669,11 @@ "string-width": "2.1.1" } }, + "winchan": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.1.4.tgz", + "integrity": "sha1-iPoSQRzVQutiYBjDihlry7F5k7s=" + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", diff --git a/package.json b/package.json index dfa5ad98..bd6c605f 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,9 @@ "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .", "test": "npm run lint && npm run jest" }, - "version": "0.1.2", + "version": "0.1.3", "dependencies": { + "auth0-js": "^6.8.4", "isomorphic-fetch": "^2.2.1", "le_node": "^1.7.0", "lodash": "^4.17.10", diff --git a/src/actions/challenge.js b/src/actions/challenge.js index 2eadca38..eb3c36a3 100644 --- a/src/actions/challenge.js +++ b/src/actions/challenge.js @@ -251,6 +251,26 @@ function updateChallengeDone(uuid, challenge, tokenV3) { .then(res => ({ uuid, res })); } +/** + * @static + * @desc Creates an action that signals beginning of getting count of user's active challenges. + * @return {Action} + */ +function getActiveChallengesCountInit() {} + +/** + * @static + * @desc Creates an action that gets count of user's active challenges from the backend. + * @param {String} handle Topcoder user handle. + * @param {String} tokenV3 Optional. Topcoder auth token v3. Without token only + * public challenges will be counted. With the token provided, the action will + * also count private challenges related to this user. + * @return {Action} + */ +function getActiveChallengesCountDone(handle, tokenV3) { + return getChallengesService(tokenV3).getActiveChallengesCount(handle); +} + export default createActions({ CHALLENGE: { DROP_CHECKPOINTS: dropCheckpoints, @@ -270,5 +290,7 @@ export default createActions({ UNREGISTER_DONE: unregisterDone, UPDATE_CHALLENGE_INIT: updateChallengeInit, UPDATE_CHALLENGE_DONE: updateChallengeDone, + GET_ACTIVE_CHALLENGES_COUNT_INIT: getActiveChallengesCountInit, + GET_ACTIVE_CHALLENGES_COUNT_DONE: getActiveChallengesCountDone, }, }); diff --git a/src/actions/index.js b/src/actions/index.js index dff22096..333494c1 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -10,6 +10,7 @@ import profileActions from './profile'; import memberActions from './members'; import memberTaskActions from './member-tasks'; import reviewOpportunityActions from './reviewOpportunity'; +import lookupActions from './lookup'; export const actions = { auth: authActions.auth, @@ -24,6 +25,7 @@ export const actions = { members: memberActions.members, memberTasks: memberTaskActions.memberTasks, reviewOpportunity: reviewOpportunityActions.reviewOpportunity, + lookup: lookupActions.lookup, }; export default undefined; diff --git a/src/actions/lookup.js b/src/actions/lookup.js new file mode 100644 index 00000000..b37ed92e --- /dev/null +++ b/src/actions/lookup.js @@ -0,0 +1,34 @@ +/** + * @module "actions.lookup" + * @desc Actions related to lookup data. + */ + +import { createActions } from 'redux-actions'; +import { getService } from '../services/lookup'; + +/** + * @static + * @desc Creates an action that signals beginning of getting all skill tags. + * @return {Action} + */ +function getSkillTagsInit() {} + +/** + * @static + * @desc Creates an action that gets all skill tags. + * @return {Action} + */ +function getSkillTagsDone() { + const params = { + domain: 'SKILLS', + status: 'APPROVED', + }; + return getService().getTags(params); +} + +export default createActions({ + LOOKUP: { + GET_SKILL_TAGS_INIT: getSkillTagsInit, + GET_SKILL_TAGS_DONE: getSkillTagsDone, + }, +}); diff --git a/src/actions/members.js b/src/actions/members.js index 2fb8dfe8..7e7f6c16 100644 --- a/src/actions/members.js +++ b/src/actions/members.js @@ -106,7 +106,8 @@ async function getStatsDone(handle, uuid, tokenV3) { } /** - * Payload creator for the action that inits the loading of member active challenges. + * @static + * @desc Payload creator for the action that inits the loading of member active challenges. * @param {String} handle * @param {String} uuid * @returns {Object} Payload @@ -116,7 +117,8 @@ async function getActiveChallengesInit(handle, uuid) { } /** - * Payload creator for the action that loads the member active challenges. + * @static + * @desc Payload creator for the action that loads the member active challenges. * @param {String} handle * @param {String} uuid * @param {String} tokenV3 diff --git a/src/actions/profile.js b/src/actions/profile.js index 9a5d8b65..69c4cb99 100644 --- a/src/actions/profile.js +++ b/src/actions/profile.js @@ -127,6 +127,291 @@ function getStatsDone(handle) { return getMembersService().getStats(handle); } +/** + * @static + * @desc Creates an action that signals beginning of getting linked accounts. + * @return {Action} + */ +function getLinkedAccountsInit() {} + +/** + * @static + * @desc Creates an action that gets linked accounts. + * + * @param {Object} profile Topcoder member profile. + * @param {String} tokenV3 Topcoder auth token v3. + * @return {Action} + */ +function getLinkedAccountsDone(profile, tokenV3) { + const service = getUserService(tokenV3); + return service.getLinkedAccounts(profile.userId); +} + +/** + * @static + * @desc Creates an action that signals beginning of getting credential. + * @return {Action} + */ +function getCredentialInit() {} + +/** + * @static + * @desc Creates an action that gets credential. + * + * @param {Object} profile Topcoder member profile. + * @param {String} tokenV3 Topcoder auth token v3. + * @return {Action} + */ +function getCredentialDone(profile, tokenV3) { + const service = getUserService(tokenV3); + return service.getCredential(profile.userId); +} + +/** + * @static + * @desc Creates an action that signals beginning of getting email preferences. + * @return {Action} + */ +function getEmailPreferencesInit() {} + +/** + * @static + * @desc Creates an action that gets email preferences. + * + * @param {Object} profile Topcoder member profile. + * @param {String} tokenV3 Topcoder auth token v3. + * @return {Action} + */ +function getEmailPreferencesDone(profile, tokenV3) { + const service = getUserService(tokenV3); + return service.getEmailPreferences(profile.userId); +} + +/** + * @static + * @desc Creates an action that signals beginning of uploading user's photo. + * @return {Action} + */ +function uploadPhotoInit() {} + +/** + * @static + * @desc Creates an action that uploads user's photo. + * @param {String} handle Topcoder user handle. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {String} file The photo file. + * @return {Action} + */ +function uploadPhotoDone(handle, tokenV3, file) { + const service = getMembersService(tokenV3); + return service.getPresignedUrl(handle, file) + .then(res => service.uploadFileToS3(res)) + .then(res => service.updateMemberPhoto(res)) + .then(photoURL => ({ handle, photoURL })); +} + +/** + * @static + * @desc Creates an action that signals beginning of deleting user's photo. + * @return {Action} + */ +function deletePhotoInit() {} + +/** + * @static + * @desc Creates an action that signals beginning of updating user's profile. + * @return {Action} + */ +function updateProfileInit() {} + +/** + * @static + * @desc Creates an action that updates user's profile. + * @param {String} profile Topcoder user profile. + * @param {String} tokenV3 Topcoder auth token v3. + * @return {Action} + */ +function updateProfileDone(profile, tokenV3) { + const service = getMembersService(tokenV3); + return service.updateMemberProfile(profile); +} + +/** + * @static + * @desc Creates an action that signals beginning of adding user's skill. + * @return {Action} + */ +function addSkillInit() {} + +/** + * @static + * @desc Creates an action that adds user's skill. + * @param {String} handle Topcoder user handle. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {Object} skill Skill to add. + * @return {Action} + */ +function addSkillDone(handle, tokenV3, skill) { + const service = getMembersService(tokenV3); + return service.addSkill(handle, skill.tagId) + .then(res => ({ skills: res.skills, handle, skill })); +} + +/** + * @static + * @desc Creates an action that signals beginning of hiding user's skill. + * @return {Action} + */ +function hideSkillInit() {} + +/** + * @static + * @desc Creates an action that hides user's skill. + * @param {String} handle Topcoder user handle. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {Object} skill Skill to hide. + * @return {Action} + */ +function hideSkillDone(handle, tokenV3, skill) { + const service = getMembersService(tokenV3); + return service.hideSkill(handle, skill.tagId) + .then(res => ({ skills: res.skills, handle, skill })); +} + +/** + * @static + * @desc Creates an action that signals beginning of adding user's web link. + * @return {Action} + */ +function addWebLinkInit() {} + +/** + * @static + * @desc Creates an action that adds user's web link. + * @param {String} handle Topcoder user handle. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {String} webLink Web link to add. + * @return {Action} + */ +function addWebLinkDone(handle, tokenV3, webLink) { + const service = getMembersService(tokenV3); + return service.addWebLink(handle, webLink).then(res => ({ data: res, handle })); +} + +/** + * @static + * @desc Creates an action that signals beginning of deleting user's web link. + * @param {Object} key Web link key to delete. + * @return {Action} + */ +function deleteWebLinkInit({ key }) { + return { key }; +} + +/** + * @static + * @desc Creates an action that deletes user's web link. + * @param {String} handle Topcoder user handle. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {String} webLink Web link to delete. + * @return {Action} + */ +function deleteWebLinkDone(handle, tokenV3, webLink) { + const service = getMembersService(tokenV3); + return service.deleteWebLink(handle, webLink.key).then(res => ({ data: res, handle })); +} + +/** + * @static + * @desc Creates an action that signals beginning of linking external account. + * @return {Action} + */ +function linkExternalAccountInit() {} + +/** + * @static + * @desc Creates an action that links external account. + * @param {Object} profile Topcoder member handle. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {String} providerType The external account service provider + * @param {String} callbackUrl Optional. The callback url + * @return {Action} + */ +function linkExternalAccountDone(profile, tokenV3, providerType, callbackUrl) { + const service = getUserService(tokenV3); + return service.linkExternalAccount(profile.userId, providerType, callbackUrl) + .then(res => ({ data: res, handle: profile.handle })); +} + +/** + * @static + * @desc Creates an action that signals beginning of unlinking external account. + * @param {Object} providerType External account provider type to delete. + * @return {Action} + */ +function unlinkExternalAccountInit({ providerType }) { + return { providerType }; +} + +/** + * @static + * @desc Creates an action that unlinks external account. + * @param {Object} profile Topcoder member profile. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {String} providerType The external account service provider + * @return {Action} + */ +function unlinkExternalAccountDone(profile, tokenV3, providerType) { + const service = getUserService(tokenV3); + return service.unlinkExternalAccount(profile.userId, providerType) + .then(() => ({ providerType, handle: profile.handle })); +} + +/** + * @static + * @desc Creates an action that signals beginning of saving email preferences. + * @return {Action} + */ +function saveEmailPreferencesInit() {} + +/** + * @static + * @desc Creates an action that saves email preferences. + * + * @param {Object} profile Topcoder member profile. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {Object} preferences The email preferences + * @return {Action} + */ +function saveEmailPreferencesDone(profile, tokenV3, preferences) { + const service = getUserService(tokenV3); + return service.saveEmailPreferences(profile, preferences) + .then(res => ({ data: res, handle: profile.handle })); +} + +/** + * @static + * @desc Creates an action that signals beginning of updating user password. + * @return {Action} + */ +function updatePasswordInit() {} + +/** + * @static + * @desc Creates an action that updates user password. + * + * @param {Object} profile Topcoder member profile. + * @param {String} tokenV3 Topcoder auth token v3. + * @param {String} newPassword The new password + * @param {String} oldPassword The old password + * @return {Action} + */ +function updatePasswordDone(profile, tokenV3, newPassword, oldPassword) { + const service = getUserService(tokenV3); + return service.updatePassword(profile.userId, newPassword, oldPassword) + .then(res => ({ data: res, handle: profile.handle })); +} + export default createActions({ PROFILE: { LOAD_PROFILE: loadProfile, @@ -142,5 +427,33 @@ export default createActions({ GET_SKILLS_DONE: getSkillsDone, GET_STATS_INIT: getStatsInit, GET_STATS_DONE: getStatsDone, + GET_LINKED_ACCOUNTS_INIT: getLinkedAccountsInit, + GET_LINKED_ACCOUNTS_DONE: getLinkedAccountsDone, + GET_EMAIL_PREFERENCES_INIT: getEmailPreferencesInit, + GET_EMAIL_PREFERENCES_DONE: getEmailPreferencesDone, + GET_CREDENTIAL_INIT: getCredentialInit, + GET_CREDENTIAL_DONE: getCredentialDone, + UPLOAD_PHOTO_INIT: uploadPhotoInit, + UPLOAD_PHOTO_DONE: uploadPhotoDone, + DELETE_PHOTO_INIT: deletePhotoInit, + DELETE_PHOTO_DONE: updateProfileDone, + UPDATE_PROFILE_INIT: updateProfileInit, + UPDATE_PROFILE_DONE: updateProfileDone, + ADD_SKILL_INIT: addSkillInit, + ADD_SKILL_DONE: addSkillDone, + HIDE_SKILL_INIT: hideSkillInit, + HIDE_SKILL_DONE: hideSkillDone, + ADD_WEB_LINK_INIT: addWebLinkInit, + ADD_WEB_LINK_DONE: addWebLinkDone, + DELETE_WEB_LINK_INIT: deleteWebLinkInit, + DELETE_WEB_LINK_DONE: deleteWebLinkDone, + LINK_EXTERNAL_ACCOUNT_INIT: linkExternalAccountInit, + LINK_EXTERNAL_ACCOUNT_DONE: linkExternalAccountDone, + UNLINK_EXTERNAL_ACCOUNT_INIT: unlinkExternalAccountInit, + UNLINK_EXTERNAL_ACCOUNT_DONE: unlinkExternalAccountDone, + SAVE_EMAIL_PREFERENCES_INIT: saveEmailPreferencesInit, + SAVE_EMAIL_PREFERENCES_DONE: saveEmailPreferencesDone, + UPDATE_PASSWORD_INIT: updatePasswordInit, + UPDATE_PASSWORD_DONE: updatePasswordDone, }, }); diff --git a/src/reducers/auth.js b/src/reducers/auth.js index 93da1e49..9a5adee5 100644 --- a/src/reducers/auth.js +++ b/src/reducers/auth.js @@ -16,6 +16,7 @@ import _ from 'lodash'; import { decodeToken } from 'tc-accounts'; import { redux } from 'topcoder-react-utils'; import actions from '../actions/auth'; +import profileActions from '../actions/profile'; /** * Handles actions.auth.loadProfile action. @@ -55,6 +56,51 @@ function create(initialState) { groups: state.profile.groups.concat({ id: payload.groupId.toString() }), }, }), + [profileActions.profile.uploadPhotoDone]: (state, { payload, error }) => { + if (error) { + return state; + } + if (!state.profile || state.profile.handle !== payload.handle) { + return state; + } + return { + ...state, + profile: { + ...state.profile, + photoURL: payload.photoURL, + }, + }; + }, + [profileActions.profile.deletePhotoDone]: (state, { payload, error }) => { + if (error) { + return state; + } + if (!state.profile || state.profile.handle !== payload.handle) { + return state; + } + return { + ...state, + profile: { + ...state.profile, + photoURL: null, + }, + }; + }, + [profileActions.profile.updateProfileDone]: (state, { payload, error }) => { + if (error) { + return state; + } + if (!state.profile || state.profile.handle !== payload.handle) { + return state; + } + return { + ...state, + profile: { + ...state.profile, + ...payload, + }, + }; + }, }, _.defaults(initialState, { authenticating: true, profile: null, diff --git a/src/reducers/challenge.js b/src/reducers/challenge.js index 35e839d4..889623e1 100644 --- a/src/reducers/challenge.js +++ b/src/reducers/challenge.js @@ -264,6 +264,22 @@ function onUpdateChallengeDone(state, { error, payload }) { }; } +/** + * Handles CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE action. + * @param {Object} state Old state. + * @param {Object} action Action payload/error + * @return {Object} New state + */ +function onGetActiveChallengesCountDone(state, { payload, error }) { + if (error) { + fireErrorMessage('Failed to get active challenges count!', ''); + logger.error('Failed to get active challenges count', payload); + return state; + } + + return ({ ...state, activeChallengesCount: payload }); +} + /** * Creates a new Challenge reducer with the specified initial state. * @param {Object} initialState Optional. Initial state. @@ -301,6 +317,8 @@ function create(initialState) { [a.fetchCheckpointsDone]: onFetchCheckpointsDone, [a.updateChallengeInit]: onUpdateChallengeInit, [a.updateChallengeDone]: onUpdateChallengeDone, + [a.getActiveChallengesCountInit]: state => state, + [a.getActiveChallengesCountDone]: onGetActiveChallengesCountDone, }, _.defaults(initialState, { details: null, loadingCheckpoints: false, diff --git a/src/reducers/index.js b/src/reducers/index.js index ed0d4829..6be38551 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -12,6 +12,7 @@ import errors, { factory as errorsFactory } from './errors'; import challenge, { factory as challengeFactory } from './challenge'; import profile, { factory as profileFactory } from './profile'; import members, { factory as membersFactory } from './members'; +import lookup, { factory as lookupFactory } from './lookup'; import memberTasks, { factory as memberTasksFactory } from './member-tasks'; import reviewOpportunity, { factory as reviewOpportunityFactory } from './reviewOpportunity'; @@ -29,6 +30,7 @@ export function factory(options) { errors: errorsFactory(options), challenge: challengeFactory(options), profile: profileFactory(options), + lookup: lookupFactory(options), members: membersFactory(options), memberTasks: memberTasksFactory(options), reviewOpportunity: reviewOpportunityFactory(options), @@ -45,6 +47,7 @@ export default ({ errors, challenge, profile, + lookup, members, memberTasks, reviewOpportunity, diff --git a/src/reducers/lookup.js b/src/reducers/lookup.js new file mode 100644 index 00000000..a2cdff2a --- /dev/null +++ b/src/reducers/lookup.js @@ -0,0 +1,61 @@ +/** + * @module "reducers.lookup" + * @desc Reducer for {@link module:actions.lookup} actions. + * + * State segment managed by this reducer has the following structure: + * @param {Array} skillTags='' skill tags. + */ +import _ from 'lodash'; +import { handleActions } from 'redux-actions'; +import logger from '../utils/logger'; +import actions from '../actions/lookup'; + +/** + * Handles LOOKUP/GET_SKILL_TAGS_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onGetSkillTagsDone(state, { payload, error }) { + if (error) { + logger.error('Failed to get skill tags', payload); + return { ...state, loadingSkillTagsError: true }; + } + + return ({ + ...state, + loadingSkillTagsError: false, + skillTags: payload, + }); +} + +/** + * Creates a new Lookup reducer with the specified initial state. + * @param {Object} initialState Optional. Initial state. + * @return {Function} Lookup reducer. + */ +function create(initialState = {}) { + const a = actions.lookup; + return handleActions({ + [a.getSkillTagsInit]: state => state, + [a.getSkillTagsDone]: onGetSkillTagsDone, + }, _.defaults(initialState, { + skillTags: [], + })); +} + +/** + * Factory which creates a new reducer. + * @return {Promise} + * @resolves {Function(state, action): state} New reducer. + */ +export function factory() { + return Promise.resolve(create()); +} + +/** + * @static + * @member default + * @desc Reducer with default initial state. + */ +export default create(); diff --git a/src/reducers/profile.js b/src/reducers/profile.js index 51970fa3..1fb3bddc 100644 --- a/src/reducers/profile.js +++ b/src/reducers/profile.js @@ -6,6 +6,8 @@ import _ from 'lodash'; import { handleActions } from 'redux-actions'; import actions from '../actions/profile'; +import logger from '../utils/logger'; +import { fireErrorMessage } from '../utils/errors'; /** * Handles PROFILE/GET_ACHIEVEMENTS_DONE action. @@ -103,6 +105,325 @@ function onGetStatsDone(state, { payload, error }) { return ({ ...state, stats: payload, loadingError: false }); } +/** + * Handles PROFILE/GET_LINKED_ACCOUNTS_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onGetLinkedAccountsDone(state, { payload, error }) { + if (error) { + return { ...state, loadingError: true }; + } + + return { ...state, linkedAccounts: payload.profiles, loadingError: false }; +} + +/** + * Handles PROFILE/GET_CREDENTIAL_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onGetCredentialDone(state, { payload, error }) { + if (error) { + return { ...state, loadingError: true }; + } + + return { ...state, credential: payload.credential, loadingError: false }; +} + +/** + * Handles PROFILE/GET_EMAIL_PREFERENCES_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onGetEmailPreferencesDone(state, { payload, error }) { + if (error) { + return { ...state, loadingError: true }; + } + + return { ...state, emailPreferences: payload.subscriptions, loadingError: false }; +} + +/** + * Handles PROFILE/UPLOAD_PHOTO_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onUploadPhotoDone(state, { payload, error }) { + const newState = { ...state, uploadingPhoto: false }; + + if (error) { + logger.error('Failed to upload user photo', payload); + fireErrorMessage('ERROR: Failed to upload photo!'); + return newState; + } + + if (!newState.info || newState.info.handle !== payload.handle) { + return newState; + } + + return { + ...newState, + info: { + ...newState.info, + photoURL: payload.photoURL, + }, + }; +} + +/** + * Handles PROFILE/DELETE_PHOTO_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onDeletePhotoDone(state, { payload, error }) { + const newState = { ...state, deletingPhoto: false }; + + if (error) { + logger.error('Failed to delete user photo', payload); + fireErrorMessage('ERROR: Failed to delete photo!'); + return newState; + } + + if (!newState.info || newState.info.handle !== payload.handle) { + return newState; + } + + return { + ...newState, + info: { + ...newState.info, + photoURL: null, + }, + }; +} + +/** + * Handles PROFILE/UPDATE_PROFILE_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onUpdateProfileDone(state, { payload, error }) { + const newState = { ...state, updatingProfile: false }; + + if (error) { + logger.error('Failed to update user profile', payload); + fireErrorMessage('ERROR: Failed to update user profile!'); + return newState; + } + + if (!newState.info || newState.info.handle !== payload.handle) { + return newState; + } + + return { + ...newState, + info: { + ...newState.info, + ...payload, + }, + }; +} + +/** + * Handles PROFILE/ADD_SKILL_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onAddSkillDone(state, { payload, error }) { + const newState = { ...state, addingSkill: false }; + + if (error) { + logger.error('Failed to add user skill', payload); + fireErrorMessage('ERROR: Failed to add user skill!'); + return newState; + } + + if (newState.profileForHandle !== payload.handle) { + return newState; + } + + return { + ...newState, + skills: payload.skills, + }; +} + +/** + * Handles PROFILE/HIDE_SKILL_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onHideSkillDone(state, { payload, error }) { + const newState = { ...state, hidingSkill: false }; + + if (error) { + logger.error('Failed to remove user skill', payload); + fireErrorMessage('ERROR: Failed to remove user skill!'); + return newState; + } + + if (newState.profileForHandle !== payload.handle) { + return newState; + } + + return { + ...newState, + skills: payload.skills, + }; +} + +/** + * Handles PROFILE/ADD_WEB_LINK_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onAddWebLinkDone(state, { payload, error }) { + const newState = { ...state, addingWebLink: false }; + + if (error) { + logger.error('Failed to add web link', payload); + fireErrorMessage('ERROR: Failed to add web link!'); + return newState; + } + + if (newState.profileForHandle !== payload.handle || !payload.data) { + return newState; + } + + return { + ...newState, + externalLinks: [...newState.externalLinks, payload.data], + }; +} + +/** + * Handles PROFILE/DELETE_WEB_LINK_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onDeleteWebLinkDone(state, { payload, error }) { + const newState = { ...state, deletingWebLink: false }; + + if (error) { + logger.error('Failed to delete web link', payload); + fireErrorMessage('ERROR: Failed to delete web link!'); + return newState; + } + + if (newState.profileForHandle !== payload.handle || !payload.data) { + return newState; + } + + return { + ...newState, + externalLinks: _.filter(newState.externalLinks, el => el.key !== payload.data.key), + }; +} + +/** + * Handles PROFILE/LINK_EXTERNAL_ACCOUNT_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onLinkExternalAccountDone(state, { payload, error }) { + const newState = { ...state, linkingExternalAccount: false }; + + if (error) { + logger.error('Failed to link external account', payload); + fireErrorMessage('ERROR: Failed to link external account!'); + return newState; + } + + if (newState.profileForHandle !== payload.handle || !payload.data) { + return newState; + } + + return { + ...newState, + linkedAccounts: [...newState.linkedAccounts, payload.data], + }; +} + +/** + * Handles PROFILE/UNLINK_EXTERNAL_ACCOUNT_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onUnlinkExternalAccountDone(state, { payload, error }) { + const newState = { ...state, unlinkingExternalAccount: false }; + + if (error) { + logger.error('Failed to unlink external account', payload); + fireErrorMessage('ERROR: Failed to unlink external account!'); + return newState; + } + + if (newState.profileForHandle !== payload.handle) { + return newState; + } + + return { + ...newState, + linkedAccounts: _.filter( + newState.linkedAccounts, + el => el.providerType !== payload.providerType, + ), + }; +} + +/** + * Handles PROFILE/SAVE_EMAIL_PREFERENCES_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onSaveEmailPreferencesDone(state, { payload, error }) { + const newState = { ...state, savingEmailPreferences: false }; + + if (error) { + logger.error('Failed to save email preferences', payload); + fireErrorMessage('ERROR: Failed to save email preferences!'); + return newState; + } + + if (newState.profileForHandle !== payload.handle || !payload.data) { + return newState; + } + + return { + ...newState, + emailPreferences: payload.data.subscriptions, + }; +} + +/** + * Handles PROFILE/UPDATE_PASSWORD_DONE action. + * @param {Object} state + * @param {Object} action Payload will be JSON from api call + * @return {Object} New state + */ +function onUpdatePasswordDone(state, { payload, error }) { + const newState = { ...state, updatingPassword: false }; + + if (error) { + logger.error('Failed to update password', payload); + } + return newState; +} + /** * Creates a new Profile reducer with the specified initial state. * @param {Object} initialState Optional. Initial state. @@ -124,6 +445,34 @@ function create(initialState) { [a.getSkillsDone]: onGetSkillsDone, [a.getStatsInit]: state => state, [a.getStatsDone]: onGetStatsDone, + [a.getLinkedAccountsInit]: state => state, + [a.getLinkedAccountsDone]: onGetLinkedAccountsDone, + [a.uploadPhotoInit]: state => ({ ...state, uploadingPhoto: true }), + [a.uploadPhotoDone]: onUploadPhotoDone, + [a.deletePhotoInit]: state => ({ ...state, deletingPhoto: true }), + [a.deletePhotoDone]: onDeletePhotoDone, + [a.updateProfileInit]: state => ({ ...state, updatingProfile: true }), + [a.updateProfileDone]: onUpdateProfileDone, + [a.addSkillInit]: state => ({ ...state, addingSkill: true }), + [a.addSkillDone]: onAddSkillDone, + [a.hideSkillInit]: state => ({ ...state, hidingSkill: true }), + [a.hideSkillDone]: onHideSkillDone, + [a.addWebLinkInit]: state => ({ ...state, addingWebLink: true }), + [a.addWebLinkDone]: onAddWebLinkDone, + [a.deleteWebLinkInit]: state => ({ ...state, deletingWebLink: true }), + [a.deleteWebLinkDone]: onDeleteWebLinkDone, + [a.linkExternalAccountInit]: state => ({ ...state, linkingExternalAccount: true }), + [a.linkExternalAccountDone]: onLinkExternalAccountDone, + [a.unlinkExternalAccountInit]: state => ({ ...state, unlinkingExternalAccount: true }), + [a.unlinkExternalAccountDone]: onUnlinkExternalAccountDone, + [a.getCredentialInit]: state => state, + [a.getCredentialDone]: onGetCredentialDone, + [a.getEmailPreferencesInit]: state => state, + [a.getEmailPreferencesDone]: onGetEmailPreferencesDone, + [a.saveEmailPreferencesInit]: state => ({ ...state, savingEmailPreferences: true }), + [a.saveEmailPreferencesDone]: onSaveEmailPreferencesDone, + [a.updatePasswordInit]: state => ({ ...state, updatingPassword: true }), + [a.updatePasswordDone]: onUpdatePasswordDone, }, _.defaults(initialState, { achievements: null, copilot: false, diff --git a/src/services/__mocks__/challenges.js b/src/services/__mocks__/challenges.js index 7670037a..05465af3 100644 --- a/src/services/__mocks__/challenges.js +++ b/src/services/__mocks__/challenges.js @@ -329,6 +329,16 @@ class ChallengesService { return this.private.apiV2.post(endpoint) .then(res => (res.ok ? res.json() : new Error(res.statusText))); } + + /** + * Gets count of user's active challenges. + * @param {String} handle Topcoder user handle. + * @return {Action} Resolves to the api response. + */ + getActiveChallengesCount(handle) { + _.noop(this, handle); + return Promise.resolve(10); + } } /** diff --git a/src/services/api.js b/src/services/api.js index df363b7d..3d98c3dd 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -170,6 +170,29 @@ class Api { return this.put(endpoint, JSON.stringify(json)); } + /** + * Sends PATCH request to the specified endpoint. + * @param {String} endpoint + * @param {Blob|BufferSource|FormData|String} body + * @return {Promise} + */ + patch(endpoint, body) { + return this.fetch(endpoint, { + body, + method: 'PATCH', + }); + } + + /** + * Sends PATCH request to the specified endpoint. + * @param {String} endpoint + * @param {JSON} json + * @return {Promise} + */ + patchJson(endpoint, json) { + return this.patch(endpoint, JSON.stringify(json)); + } + /** * Upload with progress * @param {String} endpoint diff --git a/src/services/challenges.js b/src/services/challenges.js index 1e3b8095..c599fbf5 100644 --- a/src/services/challenges.js +++ b/src/services/challenges.js @@ -628,6 +628,22 @@ class ChallengesService { return this.private.getChallenges(endpoint, filters, params); } + /** + * Gets count of user's active challenges. + * @param {String} handle Topcoder user handle. + * @return {Action} Resolves to the api response. + */ + getActiveChallengesCount(handle) { + const filter = { status: 'ACTIVE' }; + const params = { limit: 1, offset: 0 }; + + const calls = []; + calls.push(this.getUserChallenges(handle, filter, params)); + calls.push(this.getUserMarathonMatches(handle, filter, params)); + + return Promise.all(calls).then(([uch, umm]) => uch.totalCount + umm.totalCount); + } + /** * Submits a challenge submission. Uses APIV2 for Development submission * and APIV3 for Design submisisons. diff --git a/src/services/index.js b/src/services/index.js index af222abd..a71a3306 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -12,6 +12,7 @@ import * as communities from './communities'; import * as reviewOpportunities from './reviewOpportunities'; import * as userSetting from './user-settings'; import * as user from './user'; +import * as lookup from './lookup'; export const services = { api, @@ -25,6 +26,7 @@ export const services = { user, userSetting, reviewOpportunities, + lookup, }; export default undefined; diff --git a/src/services/lookup.js b/src/services/lookup.js new file mode 100644 index 00000000..60a7e480 --- /dev/null +++ b/src/services/lookup.js @@ -0,0 +1,47 @@ +/** + * @module "services.lookup" + * @desc This module provides a service to get lookup data from Topcoder + * via API V3. + */ +import qs from 'qs'; +import { getApiResponsePayloadV3 } from '../utils/tc'; +import { getApiV3 } from './api'; + +class LookupService { + /** + * @param {String} tokenV3 Optional. Auth token for Topcoder API v3. + */ + constructor(tokenV3) { + this.private = { + api: getApiV3(tokenV3), + tokenV3, + }; + } + + /** + * Gets tags. + * @param {Object} params Parameters + * @return {Promise} Resolves to the tags. + */ + async getTags(params) { + const res = await this.private.api.get(`/tags/?${qs.stringify(params)}`); + return getApiResponsePayloadV3(res); + } +} + +let lastInstance = null; +/** + * Returns a new or existing lookup service. + * @param {String} tokenV3 Optional. Auth token for Topcoder API v3. + * @return {LookupService} Lookup service object + */ +export function getService(tokenV3) { + if (!lastInstance || tokenV3 !== lastInstance.private.tokenV3) { + lastInstance = new LookupService(tokenV3); + } + return lastInstance; +} + +/* Using default export would be confusing in this case. */ +export default undefined; + diff --git a/src/services/members.js b/src/services/members.js index 13b59d0f..12d85400 100644 --- a/src/services/members.js +++ b/src/services/members.js @@ -4,7 +4,10 @@ * members via API V3. */ +/* global XMLHttpRequest */ +import _ from 'lodash'; import qs from 'qs'; +import logger from '../utils/logger'; import { getApiResponsePayloadV3 } from '../utils/tc'; import { getApiV3 } from './api'; @@ -122,6 +125,155 @@ class MembersService { const res = await this.private.api.get(`/members/_suggest/${keyword}`); return getApiResponsePayloadV3(res); } + + /** + * Adds external web link for member. + * @param {String} userHandle The user handle + * @param {String} webLink The external web link + * @return {Promise} Resolves to the api response content + */ + async addWebLink(userHandle, webLink) { + const res = await this.private.api.postJson(`/members/${userHandle}/externalLinks`, { param: { url: webLink } }); + return getApiResponsePayloadV3(res); + } + + /** + * Deletes external web link for member. + * @param {String} userHandle The user handle + * @param {String} webLinkHandle The external web link handle + * @return {Promise} Resolves to the api response content + */ + async deleteWebLink(userHandle, webLinkHandle) { + const body = { + param: { + handle: webLinkHandle, + }, + }; + const res = await this.private.api.delete(`/members/${userHandle}/externalLinks/${webLinkHandle}`, JSON.stringify(body)); + return getApiResponsePayloadV3(res); + } + + /** + * Adds user skill. + * @param {String} handle Topcoder user handle + * @param {Number} skillTagId Skill tag id + * @return {Promise} Resolves to operation result + */ + async addSkill(handle, skillTagId) { + const body = { + param: { + skills: { + [skillTagId]: { + hidden: false, + }, + }, + }, + }; + const res = await this.private.api.patchJson(`/members/${handle}/skills`, body); + return getApiResponsePayloadV3(res); + } + + /** + * Hides user skill. + * @param {String} handle Topcoder user handle + * @param {Number} skillTagId Skill tag id + * @return {Promise} Resolves to operation result + */ + async hideSkill(handle, skillTagId) { + const body = { + param: { + skills: { + [skillTagId]: { + hidden: true, + }, + }, + }, + }; + const res = await this.private.api.fetch(`/members/${handle}/skills`, { + body: JSON.stringify(body), + method: 'PATCH', + }); + return getApiResponsePayloadV3(res); + } + + /** + * Updates member profile. + * @param {Object} profile The profile to update. + * @return {Promise} Resolves to the api response content + */ + async updateMemberProfile(profile) { + const res = await this.private.api.putJson(`/members/${profile.handle}`, { param: profile }); + return getApiResponsePayloadV3(res); + } + + /** + * Gets presigned url for member photo file. + * @param {String} userHandle The user handle + * @param {File} file The file to get its presigned url + * @return {Promise} Resolves to the api response content + */ + async getPresignedUrl(userHandle, file) { + const res = await this.private.api.postJson(`/members/${userHandle}/photoUploadUrl`, { param: { contentType: file.type } }); + const payload = await getApiResponsePayloadV3(res); + + return { + preSignedURL: payload.preSignedURL, + token: payload.token, + file, + userHandle, + }; + } + + /** + * Updates member photo. + * @param {Object} S3Response The response from uploadFileToS3() function. + * @return {Promise} Resolves to the api response content + */ + async updateMemberPhoto(S3Response) { + const res = await this.private.api.putJson(`/members/${S3Response.userHandle}/photo`, { param: S3Response.body }); + return getApiResponsePayloadV3(res); + } + + /** + * Uploads file to S3. + * @param {Object} presignedUrlResponse The presigned url response from + * getPresignedUrl() function. + * @return {Promise} Resolves to the api response content + */ + uploadFileToS3(presignedUrlResponse) { + _.noop(this); + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + + xhr.open('PUT', presignedUrlResponse.preSignedURL, true); + xhr.setRequestHeader('Content-Type', presignedUrlResponse.file.type); + + xhr.onreadystatechange = () => { + const { status } = xhr; + if (((status >= 200 && status < 300) || status === 304) && xhr.readyState === 4) { + resolve({ + userHandle: presignedUrlResponse.userHandle, + body: { + token: presignedUrlResponse.token, + contentType: presignedUrlResponse.file.type, + }, + }); + } else if (status >= 400) { + const err = new Error('Could not upload image to S3'); + err.status = status; + reject(err); + } + }; + + xhr.onerror = (err) => { + logger.error('Could not upload image to S3', err); + + reject(err); + }; + + xhr.send(presignedUrlResponse.file); + }); + } } let lastInstance = null; diff --git a/src/services/user.js b/src/services/user.js index 88718a3a..3eddb2c5 100644 --- a/src/services/user.js +++ b/src/services/user.js @@ -3,10 +3,96 @@ * @desc The User service provides functionality related to Topcoder user * accounts. */ +import { config, isomorphy } from 'topcoder-react-utils'; +import logger from '../utils/logger'; import { getApiResponsePayloadV3 } from '../utils/tc'; import { getApiV2, getApiV3 } from './api'; +let auth0; +if (isomorphy.isClientSide()) { + const Auth0 = require('auth0-js'); /* eslint-disable-line global-require */ + auth0 = new Auth0({ + domain: config.AUTH0.DOMAIN, + clientID: config.AUTH0.CLIENT_ID, + callbackOnLocationHash: true, + sso: false, + }); +} + +/** + * Gets social user data. + * @param {Object} profile The user social profile + * @param {*} accessToken The access token + * @returns {Object} Social user data + */ +function getSocialUserData(profile, accessToken) { + const socialProvider = profile.identities[0].connection; + let firstName = ''; + let lastName = ''; + let handle = ''; + const email = profile.email || ''; + + const socialUserId = profile.user_id.substring(profile.user_id.lastIndexOf('|') + 1); + let splitName; + + if (socialProvider === 'google-oauth2') { + firstName = profile.given_name; + lastName = profile.family_name; + handle = profile.nickname; + } else if (socialProvider === 'facebook') { + firstName = profile.given_name; + lastName = profile.family_name; + handle = `${firstName}.${lastName}`; + } else if (socialProvider === 'twitter') { + splitName = profile.name.split(' '); + [firstName] = splitName; + if (splitName.length > 1) { + [, lastName] = splitName; + } + handle = profile.screen_name; + } else if (socialProvider === 'github') { + splitName = profile.name.split(' '); + [firstName] = splitName; + if (splitName.length > 1) { + [, lastName] = splitName; + } + handle = profile.nickname; + } else if (socialProvider === 'bitbucket') { + firstName = profile.first_name; + lastName = profile.last_name; + handle = profile.username; + } else if (socialProvider === 'stackoverflow') { + firstName = profile.first_name; + lastName = profile.last_name; + handle = socialUserId; + } else if (socialProvider === 'dribbble') { + firstName = profile.first_name; + lastName = profile.last_name; + handle = socialUserId; + } + + let token = accessToken; + let tokenSecret = null; + if (profile.identities[0].access_token) { + token = profile.identities[0].access_token; + } + if (profile.identities[0].access_token_secret) { + tokenSecret = profile.identities[0].access_token_secret; + } + return { + socialUserId, + username: handle, + firstname: firstName, + lastname: lastName, + email, + socialProfile: profile, + socialProvider, + accessToken: token, + accessTokenSecret: tokenSecret, + }; +} + /** * Service class. */ @@ -60,6 +146,163 @@ class User { const res = await this.private.api.get(url); return (await getApiResponsePayloadV3(res))[0]; } + + /** + * Gets email preferences. + * + * NOTE: Only admins are authorized to use the underlying endpoint. + * + * @param {Number} userId The TopCoder user id + * @returns {Promise} Resolves to the email preferences result + */ + async getEmailPreferences(userId) { + const url = `/users/${userId}/preferences/email`; + const res = await this.private.api.get(url); + return getApiResponsePayloadV3(res); + } + + /** + * Saves email preferences. + * + * NOTE: Only admins are authorized to use the underlying endpoint. + * + * @param {Object} user The TopCoder user + * @param {Object} preferences The email preferences + * @returns {Promise} Resolves to the email preferences result + */ + async saveEmailPreferences({ firstName, lastName, userId }, preferences) { + const settings = { + firstName, + lastName, + subscriptions: {}, + }; + + if (!preferences) { + settings.subscriptions.TOPCODER_NL_GEN = true; + } else { + settings.subscriptions = preferences; + } + const url = `/users/${userId}/preferences/email`; + + const res = await this.private.api.putJson(url, { param: settings }); + return getApiResponsePayloadV3(res); + } + + /** + * Gets credential for the specified user id. + * + * NOTE: Only admins are authorized to use the underlying endpoint. + * + * @param {Number} userId The user id + * @return {Promise} Resolves to the linked accounts array. + */ + async getCredential(userId) { + const url = `/users/${userId}?fields=credential`; + const res = await this.private.api.get(url); + return getApiResponsePayloadV3(res); + } + + /** + * Updates user password. + * + * NOTE: Only admins are authorized to use the underlying endpoint. + * + * @param {Number} userId The user id + * @param {String} newPassword The new password + * @param {String} oldPassword The old password + * @return {Promise} Resolves to the update result. + */ + async updatePassword(userId, newPassword, oldPassword) { + const credential = { + password: newPassword, + currentPassword: oldPassword, + }; + + const url = `/users/${userId}`; + const res = await this.private.api.patchJson(url, { param: { credential } }); + return getApiResponsePayloadV3(res); + } + + /** + * Gets linked accounts for the specified user id. + * + * NOTE: Only admins are authorized to use the underlying endpoint. + * + * @param {Number} userId The user id + * @return {Promise} Resolves to the linked accounts array. + */ + async getLinkedAccounts(userId) { + const url = `/users/${userId}?fields=profiles`; + const res = await this.private.api.get(url); + return getApiResponsePayloadV3(res); + } + + /** + * Unlinks external account. + * @param {Number} userId The TopCoder user id + * @param {String} provider The external account service provider + * @returns {Promise} Resolves to the unlink result + */ + async unlinkExternalAccount(userId, provider) { + const url = `/users/${userId}/profiles/${provider}`; + const res = await this.private.api.delete(url); + return getApiResponsePayloadV3(res); + } + + /** + * Links external account. + * @param {Number} userId The TopCoder user id + * @param {String} provider The external account service provider + * @param {String} callbackUrl Optional. The callback url + * @returns {Promise} Resolves to the linked account result + */ + async linkExternalAccount(userId, provider, callbackUrl) { + return new Promise((resolve, reject) => { + auth0.signin( + { + popup: true, + connection: provider, + scope: 'openid profile offline_access', + state: callbackUrl, + }, + (authError, profile, idToken, accessToken) => { + if (authError) { + logger.error('Error signing in - onSocialLoginFailure', authError); + reject(authError); + return; + } + + const socialData = getSocialUserData(profile, accessToken); + + const postData = { + userId: socialData.socialUserId, + name: socialData.username, + email: socialData.email, + emailVerified: false, + providerType: socialData.socialProvider, + context: { + handle: socialData.username, + accessToken: socialData.accessToken, + auth0UserId: profile.user_id, + }, + }; + if (socialData.accessTokenSecret) { + postData.context.accessTokenSecret = socialData.accessTokenSecret; + } + logger.debug(`link API postdata: ${JSON.stringify(postData)}`); + this.private.api.postJson(`/users/${userId}/profiles`, { param: postData }) + .then(resp => getApiResponsePayloadV3(resp).then((result) => { + logger.debug(`Succesfully linked account: ${JSON.stringify(result)}`); + resolve(postData); + })) + .catch((err) => { + logger.error('Error linking account', err); + reject(err); + }); + }, + ); + }); + } } let lastInstance = null;