diff --git a/app/skill-picker/skill-picker.controller.js b/app/skill-picker/skill-picker.controller.js
index e34eb5f2b..4933534e7 100644
--- a/app/skill-picker/skill-picker.controller.js
+++ b/app/skill-picker/skill-picker.controller.js
@@ -3,26 +3,123 @@
angular.module('tc.skill-picker').controller('SkillPickerController', SkillPickerController);
- SkillPickerController.$inject = ['CONSTANTS', 'ProfileService', '$state', 'userProfile', 'featuredSkills', '$log', 'toaster'];
+ SkillPickerController.$inject = ['$scope', 'CONSTANTS', 'ProfileService', '$state', 'userProfile', 'featuredSkills', '$log', 'toaster', 'MemberCertService', '$q'];
- function SkillPickerController(CONSTANTS, ProfileService, $state, userProfile, featuredSkills, $log, toaster) {
+ function SkillPickerController($scope, CONSTANTS, ProfileService, $state, userProfile, featuredSkills, $log, toaster, MemberCertService, $q) {
var vm = this;
$log = $log.getInstance("SkillPickerController");
vm.ASSET_PREFIX = CONSTANTS.ASSET_PREFIX;
+ vm.IOS_PROGRAM_ID = CONSTANTS.SWIFT_PROGRAM_ID;
vm.submitSkills = submitSkills;
vm.featuredSkills = featuredSkills;
+ vm.userId = userProfile.userId;
vm.username = userProfile.handle;
vm.toggleSkill = toggleSkill;
vm.tracks = {};
vm.mySkills = [];
vm.disableDoneButton = false;
+ vm.showCommunity = false;
+ vm.loadingCommunities = false;
+ vm.communities = {};
+ vm.isPageDirty = isPageDirty;
///////
activate();
+ /**
+ * Activates the controller.
+ */
function activate() {
- $log.debug("init")
+ $log.debug("init");
+ initCommunities();
+ checkCommunityStatus();
}
+ /**
+ * Verfies if the page state has been modified by the user in any way.
+ */
+ function isPageDirty() {
+ return isTracksDirty() || isCommunitiesDirty();
+ }
+
+ /**
+ * Verfies if the tracks section state has been modified by the user in any way.
+ */
+ function isTracksDirty() {
+ return vm.tracks.DESIGN || vm.tracks.DEVELOP || vm.tracks.DATA_SCIENCE;
+ }
+
+ /**
+ * Verfies if the communities section state has been modified by the user in any way.
+ */
+ function isCommunitiesDirty() {
+ var community = _.findWhere(vm.communities, {dirty: true});
+ return !!community;
+ }
+
+ /**
+ * Initializes the communities to show in the communities section.
+ */
+ function initCommunities() {
+ vm.communities['ios'] = { displayName: 'iOS', programId: vm.IOS_PROGRAM_ID, status: false, dirty: false, display: true};
+ _addWatchToCommunity(vm.communities['ios']);
+ }
+
+ /**
+ * Helper method to add watch to given object.
+ */
+ function _addWatchToCommunity(community) {
+ community.unregister = $scope.$watch(
+ function() { return community; },
+ function(newValue, oldValue) {
+ if (oldValue && newValue.status !== oldValue.status) {
+ newValue.dirty = oldValue.dirty ? false : true;
+ }
+ },
+ true
+ );
+ }
+
+ /**
+ * Checks registration status of each community and updates the state of each community.
+ */
+ function checkCommunityStatus() {
+ var promises = [];
+ for (var name in vm.communities) {
+ var community = vm.communities[name];
+ promises.push(MemberCertService.getMemberRegistration(vm.userId, community.programId));
+ }
+ vm.loadingCommunities = true;
+ $q.all(promises).then(function(responses) {
+ vm.loadingCommunities = false;
+ responses.forEach(function(program) {
+ if (program) {
+ var community = _.findWhere(vm.communities, {programId: program.eventId});
+ if (community) {
+ // set display false to avoid showing already enabled/registered program
+ // we expect display property to be modified after first load of the page
+ community.display = false;
+ community.status = true;
+ if (community.unregister){
+ community.unregister();
+ _addWatchToCommunity(community);
+ }
+ }
+ }
+ });
+ // if there exists at least 1 community which can be displayed, set showCommunity flag to true
+ var community = _.findWhere(vm.communities, {display: true});
+ if (community) {
+ vm.showCommunity = true;
+ }
+ })
+ .catch(function(error) {
+ vm.loadingCommunities = false;
+ });
+ }
+
+ /**
+ * Toggles the given skill for the user. If it is not added, adds it and if already added, removes it.
+ */
function toggleSkill(tagId) {
var _idx = vm.mySkills.indexOf(tagId.toString());
if (_idx > -1) {
@@ -34,50 +131,56 @@
}
}
+ /**
+ * Persists the user's altered information.
+ */
function submitSkills() {
vm.saving = true;
- // save tracks
- userProfile.tracks = _.reduce(vm.tracks, function(result, isInterested, trackName) {
- if (isInterested) {
- result.push(trackName);
- }
- return result;
- }, []);
- userProfile.save().then(function(data) {
- if (vm.mySkills.length > 0) {
- // save skills
- var data = {};
- for (var i = 0; i < vm.mySkills.length; i++) {
- data[vm.mySkills[i]] = {
- hidden: false
- };
+ var promises = [];
+ if (isTracksDirty()) {
+ // save tracks
+ userProfile.tracks = _.reduce(vm.tracks, function(result, isInterested, trackName) {
+ if (isInterested) {
+ result.push(trackName);
+ }
+ return result;
+ }, []);
+ promises.push(userProfile.save());
+ }
+ if (vm.mySkills.length > 0) {
+ // save skills
+ var data = {};
+ for (var i = 0; i < vm.mySkills.length; i++) {
+ data[vm.mySkills[i]] = {
+ hidden: false
+ };
+ }
+ promises.push(ProfileService.updateUserSkills(vm.username, data));
+ }
+ $log.debug('isCommunitiesDirty: ' + isCommunitiesDirty());
+ if (isCommunitiesDirty()) {
+ for(var communityName in vm.communities) {
+ var community = vm.communities[communityName];
+ if (community.dirty === true) {
+ if (community.status === true) {
+ promises.push(MemberCertService.registerMember(vm.userId, community.programId));
}
- ProfileService.updateUserSkills(vm.username, data)
- .then(function(resp) {
- vm.saving = false;
- toaster.pop('success', "Success!", "Your skills have been updated.");
- vm.disableDoneButton = true;
- $state.go('dashboard');
- })
- .catch(function(data) {
- vm.saving = false;
- toaster.pop('error', "Whoops", "Something went wrong. Please try again later.");
- })
- } else {
- vm.saving = false;
- toaster.pop('success', "Success!", "Your skills have been updated.");
- vm.disableDoneButton = true;
- $state.go('dashboard');
}
+ }
+ }
- })
- .catch(function(resp) {
- vm.saving = false;
- toaster.pop('error', "Whoops", "Something went wrong. Please try again later.");
- })
-
+ $q.all(promises).then(function(responses) {
+ vm.saving = false;
+ toaster.pop('success', "Success!", "Your skills have been updated.");
+ vm.disableDoneButton = true;
+ //$state.go('dashboard');
+ })
+ .catch(function(resp) {
+ vm.saving = false;
+ toaster.pop('error', "Whoops!", "Something went wrong. Please try again later.");
+ });
}
}
})();
diff --git a/app/skill-picker/skill-picker.jade b/app/skill-picker/skill-picker.jade
index 63f450525..812f79bc5 100644
--- a/app/skill-picker/skill-picker.jade
+++ b/app/skill-picker/skill-picker.jade
@@ -3,6 +3,25 @@
p.instruction Hi {{vm.username}}! Your account is now active. To help other members get to know you, select the tracks in which you're interested, and specify some of your skills. You can edit this information later on your Profile.
+ .communities(ng-show="!vm.loadingCommunities && vm.showCommunity")
+ .communities__title Communities
+ .communities__description Topcoder regularly establishes exclusive communities to help members develop expertise and earn money in particular technologies. Join a featured community to be notified of events and challenges.
+ .communities__list
+ .community(ng-repeat="(communityKey, community) in vm.communities", ng-class="{'community--disabled': !community.status}", ng-if="community.display")
+ .community__details
+ .community__icon(ng-class="{'community__icon--disabled': !community.status}")
+ img(ng-if="communityKey == 'ios' && community.status", src="/images/ico-ios-community.svg")
+ img(ng-if="communityKey == 'ios' && !community.status", src="/images/ico-ios-community-grey.svg")
+
+ .community__text
+ span.community__title(class="{{!community.status && 'disabled'}}") {{community.displayName}}
+ .community__description
+ span(ng-if="communityKey == 'ios'") Mobile app design and development for iOS, with Swift emphasis
+
+ onoff-switch(model="community.status", unique-id="'community-' + communityKey")
+
+
+
.tracks-container
.title tracks
.description Topcoder's three categories of challenges… please pick at least one based on your skills and interests.
@@ -51,4 +70,4 @@
type="button",
tc-busy-button, tc-busy-when="vm.saving",
ng-click="vm.submitSkills()",
- ng-disabled="vm.disableDoneButton || (!vm.tracks.DESIGN && !vm.tracks.DEVELOP && !vm.tracks.DATA_SCIENCE)") Done
+ ng-disabled="vm.disableDoneButton || !vm.isPageDirty()") Done
diff --git a/app/skill-picker/skill-picker.spec.js b/app/skill-picker/skill-picker.spec.js
index f4e938b68..ba831d011 100644
--- a/app/skill-picker/skill-picker.spec.js
+++ b/app/skill-picker/skill-picker.spec.js
@@ -1,19 +1,224 @@
/* jshint -W117, -W030 */
describe('Skill Picker Controller', function() {
var vm;
+ var toasterSvc, memberCertService, profileService;
+ var mockProfile = mockData.getMockProfile();
- // beforeEach(function() {
- // bard.appModule('tc.skill-picker');
- // bard.inject(this, '$controller', '$rootScope', '$q', 'userProfile');
+ beforeEach(function() {
+ bard.appModule('tc.skill-picker');
+ bard.inject(this, '$controller', '$rootScope', '$q', 'MemberCertService', 'ProfileService', 'toaster', 'CONSTANTS');
- // vm = $controller('SkillPickerController', {
- // });
- // });
+ memberCertService = MemberCertService;
+ profileService = ProfileService;
+ var swiftProgramId = CONSTANTS.SWIFT_PROGRAM_ID;
- // bard.verifyNoOutstandingHttpRequests();
+ // mock member cert api
+ sinon.stub(memberCertService, 'getMemberRegistration', function(userId, programId) {
+ var deferred = $q.defer();
+ if (programId === swiftProgramId) {
+ var resp = {eventId: swiftProgramId, userId: 12345};
+ deferred.resolve(resp);
+ } else {
+ deferred.resolve();
+ }
+ return deferred.promise;
+ });
+
+ sinon.stub(memberCertService, 'registerMember', function(userId, programId) {
+ var deferred = $q.defer();
+ var resp = {eventId: programId, userId: 12345};
+ if (programId === 100) {
+ deferred.reject();
+ } else {
+ deferred.resolve(resp);
+ }
+ return deferred.promise;
+ });
- // it('should be created successfully', function() {
- // expect(vm).to.exist;
- // });
+ // adds spy method to mocked profile object for saving the profile
+ mockProfile.save = sinon.spy();
+
+ // mock profile service
+ sinon.stub(profileService, 'updateUserSkills', function(username, skills) {
+ var deferred = $q.defer();
+ var resp = {};
+ if (skills[412]) {
+ deferred.reject();
+ } else {
+ deferred.resolve(resp);
+ }
+ return deferred.promise;
+ });
+
+ // mocks the toaster service
+ toasterSvc = toaster;
+ bard.mockService(toaster, {
+ pop: $q.when(true),
+ default: $q.when(true)
+ });
+
+ var scope = $rootScope.$new();
+ vm = $controller('SkillPickerController', {
+ $scope: scope,
+ userProfile: mockProfile,
+ featuredSkills: []
+ });
+ $rootScope.$digest();
+ });
+
+ bard.verifyNoOutstandingHttpRequests();
+
+ it('should be created successfully', function() {
+ expect(vm).to.exist;
+ expect(vm.showCommunity).to.exist.to.false;
+ });
+
+ it('should have empty tracks object ', function() {
+ expect(vm.tracks).to.exist;
+ expect(vm.tracks.DESIGN).not.to.exist;
+ expect(vm.tracks.DEVELOP).not.to.exist;
+ expect(vm.tracks.DATA_SCIENCE).not.to.exist;
+ });
+
+ it('should have correct userIdentity ', function() {
+ expect(vm.userId).to.exist.to.equal(mockProfile.userId);
+ expect(vm.username).to.exist.to.equal(mockProfile.handle);
+ });
+
+ it('should not have page dirty ', function() {
+ var dirty = vm.isPageDirty();
+ expect(dirty).to.equal(false);
+ });
+
+ it('should be created successfully with showCommunity being true', function() {
+ // backup original swift program id, to restore after test execution
+ var origSwiftProgId = CONSTANTS.SWIFT_PROGRAM_ID;
+ // update swift program id to something which says, member is not registered
+ CONSTANTS.SWIFT_PROGRAM_ID = 3446;
+ vm = $controller('SkillPickerController', {
+ $scope: $rootScope.$new(),
+ userProfile: mockProfile,
+ featuredSkills: []
+ });
+ $rootScope.$digest();
+ expect(vm).to.exist;
+ // showCommunity flag should be set to true because we have at least one unregistered community
+ expect(vm.showCommunity).to.exist.to.true;
+ // restores the original swift program id
+ CONSTANTS.SWIFT_PROGRAM_ID = origSwiftProgId;
+ });
+
+ it('should add skill ', function() {
+ vm.toggleSkill(409);
+ expect(vm.mySkills).to.exist.have.length(1);
+ });
+
+ it('should remove added skill ', function() {
+ vm.toggleSkill(409);
+ // should add the skill
+ expect(vm.mySkills).to.exist.have.length(1);
+ // next call to toggleSkill with same argument, should remove that skill
+ vm.toggleSkill(409);
+ // should remove the skill
+ expect(vm.mySkills).to.exist.have.length(0);
+ });
+
+ it('should not make any update call without any change ', function() {
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).not.to.be.called;
+ expect(profileService.updateUserSkills).not.to.be.called;
+ expect(memberCertService.registerMember).not.to.be.called;
+ });
+
+ it('should update tracks for the member ', function() {
+ vm.tracks.DEVELOP = true;
+ vm.tracks.DESIGN = false;
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).to.be.calledOnce;
+ expect(profileService.updateUserSkills).not.to.be.called;
+ expect(memberCertService.registerMember).not.to.be.called;
+ });
+
+ it('should show error popup for updating tracks ', function() {
+ vm.tracks.DEVELOP = true;
+ // stubs the save method to reject the promise
+ mockProfile.save = function() {};
+ sinon.stub(mockProfile, 'save', function() {
+ var deferred = $q.defer();
+ deferred.reject();
+ return deferred.promise;
+ });
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).to.be.calledOnce;
+ expect(toasterSvc.pop).to.have.been.calledWith('error', "Whoops!", sinon.match('wrong')).calledOnce;
+ expect(profileService.updateUserSkills).not.to.be.called;
+ expect(memberCertService.registerMember).not.to.be.called;
+ });
+
+ it('should update skills for the member ', function() {
+ vm.mySkills = [409, 410];
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).not.to.be.called;
+ expect(profileService.updateUserSkills).to.be.calledOnce;
+ expect(memberCertService.registerMember).not.to.be.called;
+ });
+
+ it('should show error popup for error in updating skills ', function() {
+ vm.mySkills = [409, 410, 412];
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).not.to.be.called;
+ expect(profileService.updateUserSkills).to.be.calledOnce;
+ expect(toasterSvc.pop).to.have.been.calledWith('error', "Whoops!", sinon.match('wrong')).calledOnce;
+ expect(memberCertService.registerMember).not.to.be.called;
+ });
+
+ it('should update communities for the member ', function() {
+ vm.communities['ios'].status = true;
+ vm.communities['ios'].dirty = true;
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).not.to.be.called;
+ expect(profileService.updateUserSkills).not.to.be.called;
+ expect(memberCertService.registerMember).to.be.calledOnce;
+ });
+
+ // we may need to update this test case when we want to call unregister endpoint
+ it('should NOT update communities for the member for disabled community ', function() {
+ vm.communities['ios'].status = false;
+ vm.communities['ios'].dirty = true;
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).not.to.be.called;
+ expect(profileService.updateUserSkills).not.to.be.called;
+ expect(memberCertService.registerMember).not.to.be.called;
+ });
+
+ it('should NOT update communities for the member for enabled but non dirty community ', function() {
+ // TODO need one more community, with dirty true, to test out the non dirty community update
+ vm.communities['ios'].status = true;
+ vm.communities['ios'].dirty = false;
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).not.to.be.called;
+ expect(profileService.updateUserSkills).not.to.be.called;
+ expect(memberCertService.registerMember).not.to.be.called;
+ });
+
+ it('should show error popup for error in updating communities ', function() {
+ vm.communities['ios'].programId = 100;// to cause rejction of the promise
+ vm.communities['ios'].status = true;
+ vm.communities['ios'].dirty = true;
+ vm.submitSkills();
+ $rootScope.$digest();
+ expect(mockProfile.save).not.to.be.called;
+ expect(profileService.updateUserSkills).not.to.be.called;
+ expect(memberCertService.registerMember).to.be.calledOnce;
+ expect(toasterSvc.pop).to.have.been.calledWith('error', "Whoops!", sinon.match('wrong')).calledOnce;
+ });
});
diff --git a/assets/css/skill-picker/skill-picker.scss b/assets/css/skill-picker/skill-picker.scss
index 4b18ed13e..6432cbb22 100644
--- a/assets/css/skill-picker/skill-picker.scss
+++ b/assets/css/skill-picker/skill-picker.scss
@@ -30,6 +30,67 @@
color: $accent-gray;
}
+ .communities {
+ width: 100%;
+ margin-bottom: 30px;
+ .communities__title {
+ @include sofia-pro-regular;
+ text-transform: uppercase;
+ font-size: 18px;
+ padding-bottom: 10px;
+ }
+ .communities__description {
+ @include font-with-weight('Merriweather Sans');
+ color: #A3A3AE;
+ font-size: 13px;
+ line-height: 18px;
+ }
+ .communities__list {
+ margin-top: 10px;
+ .community {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 15px 0;
+ border-bottom: 1px solid $gray-light;
+
+ &.disabled {
+ background-color: $gray-lightest;
+ }
+
+ .community__details {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding-left: 10px;
+ }
+
+ .community__text {
+ margin-left: 10px;
+ }
+
+ .community__title {
+ font-size: 16px;
+ line-height: 28px;
+ @include font-with-weight('Sofia Pro', 500);
+ transition: .1s color;
+
+ &.disabled {
+ color: $gray-dark;
+ }
+ }
+
+ .community__description {
+ @include font-with-weight('Merriweather Sans', 400);
+ font-size: 13px;
+ margin-top: 4px;
+ color: $accent-gray;
+ }
+ }
+ }
+ }
+
.tracks-container {
width: 100%;
>.title {
diff --git a/assets/images/ico-ios-community-grey.svg b/assets/images/ico-ios-community-grey.svg
new file mode 100755
index 000000000..7dfad082d
--- /dev/null
+++ b/assets/images/ico-ios-community-grey.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/assets/images/ico-ios-community.svg b/assets/images/ico-ios-community.svg
new file mode 100755
index 000000000..3fa0795ca
--- /dev/null
+++ b/assets/images/ico-ios-community.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file