From 16cbb357b216e2e6c580496329c6fd0f6627ffb0 Mon Sep 17 00:00:00 2001
From: "Luiz R. Rodrigues" <contato@luizrrodrigues.com.br>
Date: Mon, 13 Dec 2021 09:09:54 -0300
Subject: [PATCH 1/3] Revert "Merge pull request #325 from
 topcoder-platform/revert-322-feat/traits-v5-upgrade"

This reverts commit 83a7c5c575250beac24792320eb8c8ad676dab5d, reversing
changes made to 4a658562739b7821008f48e6c1498011dc64208e.
---
 __tests__/actions/profile.js |  4 --
 docs/services.members.md     | 40 +++---------------
 package.json                 |  2 +-
 src/actions/profile.js       |  4 +-
 src/services/members.js      | 78 +++++++-----------------------------
 src/services/user-traits.js  | 44 +++++++++-----------
 src/utils/tc.js              |  3 +-
 7 files changed, 44 insertions(+), 131 deletions(-)

diff --git a/__tests__/actions/profile.js b/__tests__/actions/profile.js
index b4d1848b..4ea366a3 100644
--- a/__tests__/actions/profile.js
+++ b/__tests__/actions/profile.js
@@ -18,8 +18,6 @@ const linkedAccounts = [{
 
 // 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] })),
@@ -47,8 +45,6 @@ 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();
 });
 
diff --git a/docs/services.members.md b/docs/services.members.md
index 856b90ce..8fe477f2 100644
--- a/docs/services.members.md
+++ b/docs/services.members.md
@@ -25,9 +25,7 @@ members via API V3.
             * [.addSkill(handle, skillTagId)](#module_services.members..MembersService+addSkill) ⇒ <code>Promise</code>
             * [.hideSkill(handle, skillTagId)](#module_services.members..MembersService+hideSkill) ⇒ <code>Promise</code>
             * [.updateMemberProfile(profile)](#module_services.members..MembersService+updateMemberProfile) ⇒ <code>Promise</code>
-            * [.getPresignedUrl(userHandle, file)](#module_services.members..MembersService+getPresignedUrl) ⇒ <code>Promise</code>
-            * [.updateMemberPhoto(S3Response)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
-            * [.uploadFileToS3(presignedUrlResponse)](#module_services.members..MembersService+uploadFileToS3) ⇒ <code>Promise</code>
+            * [.updateMemberPhoto(userHandle, file)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
            * [.verifyMemberNewEmail(handle, emailVerifyToken)](#module_services.members..MembersService+verifyMemberNewEmail) ⇒ <code>Promise</code>
 
 <a name="module_services.members.getService"></a>
@@ -65,9 +63,7 @@ Service class.
     * [.addSkill(handle, skillTagId)](#module_services.members..MembersService+addSkill) ⇒ <code>Promise</code>
     * [.hideSkill(handle, skillTagId)](#module_services.members..MembersService+hideSkill) ⇒ <code>Promise</code>
     * [.updateMemberProfile(profile)](#module_services.members..MembersService+updateMemberProfile) ⇒ <code>Promise</code>
-    * [.getPresignedUrl(userHandle, file)](#module_services.members..MembersService+getPresignedUrl) ⇒ <code>Promise</code>
-    * [.updateMemberPhoto(S3Response)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
-    * [.uploadFileToS3(presignedUrlResponse)](#module_services.members..MembersService+uploadFileToS3) ⇒ <code>Promise</code>
+    * [.updateMemberPhoto(userHandle, file)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
     * [.verifyMemberNewEmail(handle, emailVerifyToken)](#module_services.members..MembersService+verifyMemberNewEmail) ⇒ <code>Promise</code>
 
 <a name="new_module_services.members..MembersService_new"></a>
@@ -256,42 +252,18 @@ Updates member profile.
 | --- | --- | --- |
 | profile | <code>Object</code> | The profile to update. |
 
-<a name="module_services.members..MembersService+getPresignedUrl"></a>
-
-#### membersService.getPresignedUrl(userHandle, file) ⇒ <code>Promise</code>
-Gets presigned url for member photo file.
-
-**Kind**: instance method of [<code>MembersService</code>](#module_services.members..MembersService)
-**Returns**: <code>Promise</code> - Resolves to the api response content
-
-| Param | Type | Description |
-| --- | --- | --- |
-| userHandle | <code>String</code> | The user handle |
-| file | <code>File</code> | The file to get its presigned url |
-
 <a name="module_services.members..MembersService+updateMemberPhoto"></a>
 
-#### membersService.updateMemberPhoto(S3Response) ⇒ <code>Promise</code>
-Updates member photo.
+#### membersService.updateMemberPhoto(userHandle, file) ⇒ <code>Promise</code>
+Uploads and updates member photo.
 
 **Kind**: instance method of [<code>MembersService</code>](#module_services.members..MembersService)
 **Returns**: <code>Promise</code> - Resolves to the api response content
 
 | Param | Type | Description |
 | --- | --- | --- |
-| S3Response | <code>Object</code> | The response from uploadFileToS3() function. |
-
-<a name="module_services.members..MembersService+uploadFileToS3"></a>
-
-#### membersService.uploadFileToS3(presignedUrlResponse) ⇒ <code>Promise</code>
-Uploads file to S3.
-
-**Kind**: instance method of [<code>MembersService</code>](#module_services.members..MembersService)
-**Returns**: <code>Promise</code> - Resolves to the api response content
-
-| Param | Type | Description |
-| --- | --- | --- |
-| presignedUrlResponse | <code>Object</code> | The presigned url response from                                      getPresignedUrl() function. |
+| userHandle | <code>String</code> | The user handle |
+| file | <code>File</code> | The file to be uploaded |
 
 <a name="module_services.members..MembersService+verifyMemberNewEmail"></a>
 
diff --git a/package.json b/package.json
index 442f898e..2df0f751 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .",
     "test": "npm run lint && npm run jest"
   },
-  "version": "1.2.4",
+  "version": "1000.28.8",
   "dependencies": {
     "auth0-js": "^6.8.4",
     "config": "^3.2.0",
diff --git a/src/actions/profile.js b/src/actions/profile.js
index bcd668cf..96a9ef5e 100644
--- a/src/actions/profile.js
+++ b/src/actions/profile.js
@@ -216,9 +216,7 @@ function uploadPhotoInit() {}
  */
 function uploadPhotoDone(handle, tokenV3, file) {
   const service = getMembersService(tokenV3);
-  return service.getPresignedUrl(handle, file)
-    .then(res => service.uploadFileToS3(res))
-    .then(res => service.updateMemberPhoto(res))
+  return service.updateMemberPhoto(handle, file)
     .then(photoURL => ({ handle, photoURL }));
 }
 
diff --git a/src/services/members.js b/src/services/members.js
index 7b6648b0..61f3e9ab 100644
--- a/src/services/members.js
+++ b/src/services/members.js
@@ -4,11 +4,10 @@
  * members via API V3.
  */
 
-/* global XMLHttpRequest */
+/* global FormData */
 import _ from 'lodash';
 import qs from 'qs';
 import { decodeToken } from '@topcoder-platform/tc-auth-lib';
-import logger from '../utils/logger';
 import { getApiResponsePayload, handleApiResponse } from '../utils/tc';
 import { getApi } from './api';
 
@@ -238,73 +237,24 @@ class MembersService {
     return getApiResponsePayload(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 getApiResponsePayload(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 getApiResponsePayload(res);
-  }
-
-  /**
-   * Uploads file to S3.
-   * @param {Object} presignedUrlResponse The presigned url response from
-   *                                      getPresignedUrl() function.
+   * @param {String} userHandle The user handle
+   * @param {File} file The photo to upload
    * @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);
+  async updateMemberPhoto(userHandle, file) {
+    const formData = new FormData();
+    formData.append('photo', file);
+    const res = await this.private.apiV5.fetch(`/members/${userHandle}/photo`, {
+      method: 'POST',
+      headers: {
+        'Content-Type': null,
+      },
+      body: formData,
     });
+    return handleApiResponse(res)
+      .then(({ photoURL }) => photoURL);
   }
 
   /**
diff --git a/src/services/user-traits.js b/src/services/user-traits.js
index c1d71b71..34d25a26 100644
--- a/src/services/user-traits.js
+++ b/src/services/user-traits.js
@@ -4,7 +4,7 @@
  * via API V3.
  */
 import toCapitalCase from 'to-capital-case';
-import { getApiResponsePayload } from '../utils/tc';
+import { handleApiResponse } from '../utils/tc';
 import { getApi } from './api';
 
 /**
@@ -16,7 +16,7 @@ class UserTraitsService {
    */
   constructor(tokenV3) {
     this.private = {
-      api: getApi('V3', tokenV3),
+      api: getApi('V5', tokenV3),
       tokenV3,
     };
   }
@@ -29,7 +29,7 @@ class UserTraitsService {
   async getAllUserTraits(handle) {
     // FIXME: Remove the .toLowerCase() when the API is fixed to ignore the case in the route params
     const res = await this.private.api.get(`/members/${handle.toLowerCase()}/traits`);
-    return getApiResponsePayload(res);
+    return handleApiResponse(res);
   }
 
   /**
@@ -40,18 +40,16 @@ class UserTraitsService {
    * @return {Promise} Resolves to the member traits.
    */
   async addUserTrait(handle, traitId, data) {
-    const body = {
-      param: [{
-        traitId,
-        categoryName: toCapitalCase(traitId),
-        traits: {
-          data,
-        },
-      }],
-    };
+    const body = [{
+      traitId,
+      categoryName: toCapitalCase(traitId),
+      traits: {
+        data,
+      },
+    }];
 
     const res = await this.private.api.postJson(`/members/${handle}/traits`, body);
-    return getApiResponsePayload(res);
+    return handleApiResponse(res);
   }
 
   /**
@@ -62,18 +60,16 @@ class UserTraitsService {
    * @return {Promise} Resolves to the member traits.
    */
   async updateUserTrait(handle, traitId, data) {
-    const body = {
-      param: [{
-        traitId,
-        categoryName: toCapitalCase(traitId),
-        traits: {
-          data,
-        },
-      }],
-    };
+    const body = [{
+      traitId,
+      categoryName: toCapitalCase(traitId),
+      traits: {
+        data,
+      },
+    }];
 
     const res = await this.private.api.putJson(`/members/${handle}/traits`, body);
-    return getApiResponsePayload(res);
+    return handleApiResponse(res);
   }
 
   /**
@@ -84,7 +80,7 @@ class UserTraitsService {
    */
   async deleteUserTrait(handle, traitId) {
     const res = await this.private.api.delete(`/members/${handle}/traits?traitIds=${traitId}`);
-    return getApiResponsePayload(res);
+    return handleApiResponse(res);
   }
 }
 
diff --git a/src/utils/tc.js b/src/utils/tc.js
index 2941de37..ff6d46f1 100644
--- a/src/utils/tc.js
+++ b/src/utils/tc.js
@@ -87,7 +87,8 @@ export async function getApiResponsePayload(res, shouldThrowError = true) {
  */
 export function handleApiResponse(response) {
   if (!response.ok) throw new Error(response.statusText);
-  return response.json();
+  return response.json()
+    .catch(() => null);
 }
 
 /**

From 2a01da82ff62829099b3a49be08e8e07db211b88 Mon Sep 17 00:00:00 2001
From: Luiz Ricardo Rodrigues <contato@luizrrodrigues.com.br>
Date: Mon, 13 Dec 2021 09:18:45 -0300
Subject: [PATCH 2/3] ci: added tag test-release

---
 .circleci/config.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 0e161844..e8eac28e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -28,7 +28,7 @@ jobs:
       - attach_workspace:
           at: .
       - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
-      - run: npm publish
+      - run: npm publish --tag test-release
 # dont change anything
 workflows:
   version: 2

From e04f229fc34847e1b18e8c8bde8468c0a170cab6 Mon Sep 17 00:00:00 2001
From: Ubuntu <ubuntu@ip-172-31-19-225.ap-south-1.compute.internal>
Date: Tue, 21 Dec 2021 10:51:40 +0000
Subject: [PATCH 3/3] update profile and skills to v5

---
 __tests__/__snapshots__/index.js.snap         |  2 +
 .../actions/__snapshots__/profile.js.snap     |  2 +
 __tests__/actions/auth.js                     |  4 +-
 src/actions/auth.js                           |  8 ++--
 src/actions/profile.js                        | 21 +++++++++
 src/reducers/auth.js                          | 15 ++++++
 src/reducers/profile.js                       | 45 ++++++++++++++++++
 src/services/members.js                       | 46 +++++++++++--------
 8 files changed, 117 insertions(+), 26 deletions(-)

diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap
index 283e8d01..351b4e3c 100644
--- a/__tests__/__snapshots__/index.js.snap
+++ b/__tests__/__snapshots__/index.js.snap
@@ -174,7 +174,9 @@ Object {
       "updatePasswordDone": [Function],
       "updatePasswordInit": [Function],
       "updateProfileDone": [Function],
+      "updateProfileDoneV5": [Function],
       "updateProfileInit": [Function],
+      "updateProfileInitV5": [Function],
       "uploadPhotoDone": [Function],
       "uploadPhotoInit": [Function],
       "verifyMemberNewEmailDone": [Function],
diff --git a/__tests__/actions/__snapshots__/profile.js.snap b/__tests__/actions/__snapshots__/profile.js.snap
index b784f238..0649581b 100644
--- a/__tests__/actions/__snapshots__/profile.js.snap
+++ b/__tests__/actions/__snapshots__/profile.js.snap
@@ -44,7 +44,9 @@ Object {
     "updatePasswordDone": [Function],
     "updatePasswordInit": [Function],
     "updateProfileDone": [Function],
+    "updateProfileDoneV5": [Function],
     "updateProfileInit": [Function],
+    "updateProfileInitV5": [Function],
     "uploadPhotoDone": [Function],
     "uploadPhotoInit": [Function],
     "verifyMemberNewEmailDone": [Function],
diff --git a/__tests__/actions/auth.js b/__tests__/actions/auth.js
index a36ef7e3..19cc26d5 100644
--- a/__tests__/actions/auth.js
+++ b/__tests__/actions/auth.js
@@ -1,5 +1,5 @@
 const MOCK_GROUPS_REQ_URL = 'https://api.topcoder-dev.com/v5/groups?memberId=12345&membershipType=user';
-const MOCK_PROFILE_REQ_URL = 'https://api.topcoder-dev.com/v3/members/username12345';
+const MOCK_PROFILE_REQ_URL = 'https://api.topcoder-dev.com/v5/members/username12345';
 
 jest.mock('isomorphic-fetch', () => jest.fn(url => Promise.resolve({
   ok: true,
@@ -10,7 +10,7 @@ jest.mock('isomorphic-fetch', () => jest.fn(url => Promise.resolve({
         content = ['Group1', 'Group2'];
         break;
       case MOCK_PROFILE_REQ_URL:
-        content = { result: { content: { userId: 12345 }, status: 200 } };
+        content = Promise.resolve({ userId: 12345 });
         break;
       default: throw new Error('Unexpected URL!');
     }
diff --git a/src/actions/auth.js b/src/actions/auth.js
index 3cec762a..c8a760b0 100644
--- a/src/actions/auth.js
+++ b/src/actions/auth.js
@@ -5,9 +5,10 @@
 
 import { createActions } from 'redux-actions';
 import { decodeToken } from '@topcoder-platform/tc-auth-lib';
-import { getApiV3, getApiV5 } from '../services/api';
+import { getApiV5 } from '../services/api';
 import { setErrorIcon, ERROR_ICON_TYPES } from '../utils/errors';
 import { getService } from '../services/groups';
+import { handleApiResponse } from '../utils/tc';
 
 /**
  * Helper method that checks for HTTP error response v5 and throws Error in this case.
@@ -41,11 +42,10 @@ async function checkErrorV5(res) {
 function loadProfileDone(userTokenV3) {
   if (!userTokenV3) return Promise.resolve(null);
   const user = decodeToken(userTokenV3);
-  const apiV3 = getApiV3(userTokenV3);
   const apiV5 = getApiV5(userTokenV3);
   return Promise.all([
-    apiV3.get(`/members/${user.handle}`)
-      .then(res => res.json()).then(res => (res.result.status === 200 ? res.result.content : {})),
+    apiV5.get(`/members/${user.handle}`)
+      .then(handleApiResponse),
     apiV5.get(`/groups?memberId=${user.userId}&membershipType=user`)
       .then(checkErrorV5).then(res => res.result || []),
   ]).then(([profile, groups]) => ({ ...profile, groups }));
diff --git a/src/actions/profile.js b/src/actions/profile.js
index 96a9ef5e..e9368d0a 100644
--- a/src/actions/profile.js
+++ b/src/actions/profile.js
@@ -246,6 +246,25 @@ function updateProfileDone(profile, tokenV3) {
   return service.updateMemberProfile(profile);
 }
 
+/**
+ * @static
+ * @desc Creates an action that signals beginning of updating user's profile.
+ * @return {Action}
+ */
+function updateProfileInitV5() {}
+
+/**
+ * @static
+ * @desc Creates an action that updates user's profile.
+ * @param {String} profile Topcoder user profile.
+ * @param {String} tokenV5 Topcoder auth token v5.
+ * @return {Action}
+ */
+function updateProfileDoneV5(profile, handle, tokenV3) {
+  const service = getMembersService(tokenV3);
+  return service.updateMemberProfileV5(profile, handle);
+}
+
 /**
  * @static
  * @desc Creates an action that signals beginning of adding user's skill.
@@ -483,6 +502,8 @@ export default createActions({
     DELETE_PHOTO_DONE: updateProfileDone,
     UPDATE_PROFILE_INIT: updateProfileInit,
     UPDATE_PROFILE_DONE: updateProfileDone,
+    UPDATE_PROFILE_INIT_V5: updateProfileInitV5,
+    UPDATE_PROFILE_DONE_V5: updateProfileDoneV5,
     ADD_SKILL_INIT: addSkillInit,
     ADD_SKILL_DONE: addSkillDone,
     HIDE_SKILL_INIT: hideSkillInit,
diff --git a/src/reducers/auth.js b/src/reducers/auth.js
index b20e7851..a957d6d8 100644
--- a/src/reducers/auth.js
+++ b/src/reducers/auth.js
@@ -106,6 +106,21 @@ function create(initialState) {
         },
       };
     },
+    [profileActions.profile.updateProfileDoneV5]: (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/profile.js b/src/reducers/profile.js
index 8ca03fdf..e060f7d4 100644
--- a/src/reducers/profile.js
+++ b/src/reducers/profile.js
@@ -266,6 +266,49 @@ function onUpdateProfileDone(state, { payload, error }) {
   };
 }
 
+/**
+ * Handles PROFILE/UPDATE_PROFILE_DONE_V5 action.
+ * @param {Object} state
+ * @param {Object} action Payload will be JSON from api call
+ * @return {Object} New state
+ */
+function onUpdateProfileDoneV5(state, { payload, error }) {
+  const newState = { ...state, updatingProfile: false };
+
+  if (payload.isEmailConflict) {
+    return {
+      ...newState,
+      isEmailConflict: true,
+      updateProfileSuccess: false,
+    };
+  }
+
+  if (error) {
+    logger.error('Failed to update user profile', payload);
+    fireErrorMessage('ERROR: Failed to update user profile!');
+    return {
+      ...newState,
+      updateProfileSuccess: false,
+    };
+  }
+
+  if (!newState.info || newState.info.handle !== payload.handle) {
+    return {
+      ...newState,
+      updateProfileSuccess: true,
+    };
+  }
+
+  return {
+    ...newState,
+    info: {
+      ...newState.info,
+      ...payload,
+    },
+    updateProfileSuccess: true,
+  };
+}
+
 /**
  * Handles PROFILE/ADD_SKILL_DONE action.
  * @param {Object} state
@@ -530,6 +573,8 @@ function create(initialState) {
     [a.deletePhotoDone]: onDeletePhotoDone,
     [a.updateProfileInit]: state => ({ ...state, updatingProfile: true }),
     [a.updateProfileDone]: onUpdateProfileDone,
+    [a.updateProfileInitV5]: state => ({ ...state, updatingProfile: true }),
+    [a.updateProfileDoneV5]: onUpdateProfileDoneV5,
     [a.addSkillInit]: state => ({ ...state, addingSkill: true }),
     [a.addSkillDone]: onAddSkillDone,
     [a.hideSkillInit]: state => ({ ...state, hidingSkill: true }),
diff --git a/src/services/members.js b/src/services/members.js
index 61f3e9ab..5022b362 100644
--- a/src/services/members.js
+++ b/src/services/members.js
@@ -45,8 +45,8 @@ class MembersService {
    * @return {Promise} Resolves to the data object.
    */
   async getMemberInfo(handle) {
-    const res = await this.private.api.get(`/members/${handle}`);
-    return getApiResponsePayload(res);
+    const res = await this.private.apiV5.get(`/members/${handle}`);
+    return handleApiResponse(res);
   }
 
   /**
@@ -75,8 +75,8 @@ class MembersService {
    * @return {Promise} Resolves to the stats object.
    */
   async getSkills(handle) {
-    const res = await this.private.api.get(`/members/${handle}/skills`);
-    return getApiResponsePayload(res);
+    const res = await this.private.apiV5.get(`/members/${handle}/skills`);
+    return handleApiResponse(res);
   }
 
   /**
@@ -188,16 +188,12 @@ class MembersService {
    */
   async addSkill(handle, skillTagId) {
     const body = {
-      param: {
-        skills: {
-          [skillTagId]: {
-            hidden: false,
-          },
-        },
+      [skillTagId]: {
+        hidden: false,
       },
     };
-    const res = await this.private.api.patchJson(`/members/${handle}/skills`, body);
-    return getApiResponsePayload(res);
+    const res = await this.private.apiV5.patchJson(`/members/${handle}/skills`, body);
+    return handleApiResponse(res);
   }
 
   /**
@@ -208,19 +204,15 @@ class MembersService {
    */
   async hideSkill(handle, skillTagId) {
     const body = {
-      param: {
-        skills: {
-          [skillTagId]: {
-            hidden: true,
-          },
-        },
+      [skillTagId]: {
+        hidden: true,
       },
     };
-    const res = await this.private.api.fetch(`/members/${handle}/skills`, {
+    const res = await this.private.apiV5.fetch(`/members/${handle}/skills`, {
       body: JSON.stringify(body),
       method: 'PATCH',
     });
-    return getApiResponsePayload(res);
+    return handleApiResponse(res);
   }
 
   /**
@@ -237,6 +229,20 @@ class MembersService {
     return getApiResponsePayload(res);
   }
 
+  /**
+   * Updates member profile.
+   * @param {Object} profile The profile to update.
+   * @return {Promise} Resolves to the api response content
+   */
+  async updateMemberProfileV5(profile, handle) {
+    const url = profile.verifyUrl ? `/members/${handle}?verifyUrl=${profile.verifyUrl}` : `/members/${handle}`;
+    const res = await this.private.apiV5.putJson(url, profile.verifyUrl ? _.omit(profile, ['verifyUrl']) : profile);
+    if (profile.verifyUrl && res.status === 409) {
+      return Promise.resolve(Object.assign({}, profile, { isEmailConflict: true }));
+    }
+    return handleApiResponse(res);
+  }
+
   /**
    * Updates member photo.
    * @param {String} userHandle The user handle