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 related to lookup data.
+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.
Reducer for actions.lookup actions.
+State segment managed by this reducer has the following structure:
+Member tasks reducer.
This module provides a service to get lookup data from Topcoder +via API V3.
+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;