diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap
index 0542a66c..e7ee51bc 100644
--- a/__tests__/__snapshots__/index.js.snap
+++ b/__tests__/__snapshots__/index.js.snap
@@ -29,6 +29,31 @@ Object {
       "updateChallengeDone": [Function],
       "updateChallengeInit": [Function],
     },
+    "challengeListing": Object {
+      "dropChallenges": [Function],
+      "expandTag": [Function],
+      "getActiveChallengesDone": [Function],
+      "getActiveChallengesInit": [Function],
+      "getAllActiveChallengesDone": [Function],
+      "getAllActiveChallengesInit": [Function],
+      "getChallengeSubtracksDone": [Function],
+      "getChallengeSubtracksInit": [Function],
+      "getChallengeTagsDone": [Function],
+      "getChallengeTagsInit": [Function],
+      "getMoreChallenges": [Function],
+      "getPastChallengesDone": [Function],
+      "getPastChallengesInit": [Function],
+      "getRestActiveChallengesDone": [Function],
+      "getRestActiveChallengesInit": [Function],
+      "getReviewOpportunitiesDone": [Function],
+      "getReviewOpportunitiesInit": [Function],
+      "getSrmsDone": [Function],
+      "getSrmsInit": [Function],
+      "selectCommunity": [Function],
+      "setDatepickerStatus": [Function],
+      "setFilter": [Function],
+      "setSort": [Function],
+    },
     "direct": Object {
       "dropAll": [Function],
       "getProjectDetailsDone": [Function],
@@ -173,13 +198,145 @@ Object {
     },
   },
   "challenge": Object {
+    "buckets": Object {
+      "BUCKETS": Object {
+        "ALL": "all",
+        "MY": "my",
+        "ONGOING": "ongoing",
+        "OPEN_FOR_REGISTRATION": "openForRegistration",
+        "PAST": "past",
+        "REVIEW_OPPORTUNITIES": "reviewOpportunities",
+        "SAVED_FILTER": "saved-filter",
+        "SAVED_REVIEW_OPPORTUNITIES_FILTER": "savedReviewOpportunitiesFilter",
+        "UPCOMING": "upcoming",
+      },
+      "BUCKET_DATA": Object {
+        "all": Object {
+          "filter": Object {
+            "started": true,
+            "status": Array [
+              "ACTIVE",
+            ],
+          },
+          "hideCount": false,
+          "name": "All Challenges",
+          "sorts": Array [],
+        },
+        "my": Object {
+          "filter": Object {
+            "started": true,
+            "status": Array [
+              "ACTIVE",
+            ],
+          },
+          "hideCount": false,
+          "name": "My Challenges",
+          "sorts": Array [
+            "most-recent",
+            "time-to-submit",
+            "num-registrants",
+            "num-submissions",
+            "prize-high-to-low",
+            "title-a-to-z",
+          ],
+        },
+        "ongoing": Object {
+          "filter": Object {
+            "registrationOpen": false,
+            "started": true,
+            "status": Array [
+              "ACTIVE",
+            ],
+          },
+          "hideCount": false,
+          "name": "Ongoing challenges",
+          "sorts": Array [
+            "most-recent",
+            "current-phase",
+            "title-a-to-z",
+            "prize-high-to-low",
+          ],
+        },
+        "openForRegistration": Object {
+          "filter": Object {
+            "registrationOpen": true,
+            "started": true,
+            "status": Array [
+              "ACTIVE",
+            ],
+          },
+          "hideCount": false,
+          "name": "Open for registration",
+          "sorts": Array [
+            "most-recent",
+            "time-to-register",
+            "time-to-submit",
+            "num-registrants",
+            "num-submissions",
+            "prize-high-to-low",
+            "title-a-to-z",
+          ],
+        },
+        "past": Object {
+          "filter": Object {
+            "status": Array [
+              "COMPLETED",
+              "PAST",
+            ],
+          },
+          "hideCount": true,
+          "name": "Past challenges",
+          "sorts": Array [
+            "most-recent",
+            "prize-high-to-low",
+            "title-a-to-z",
+          ],
+        },
+        "reviewOpportunities": Object {
+          "filter": Object {},
+          "hideCount": true,
+          "name": "Open for review",
+          "sorts": Array [
+            "review-opportunities-start-date",
+            "review-opportunities-payment",
+            "review-opportunities-title-a-to-z",
+          ],
+        },
+        "savedReviewOpportunitiesFilter": Object {
+          "filter": Object {},
+          "sorts": Array [
+            "review-opportunities-start-date",
+            "review-opportunities-payment",
+            "review-opportunities-title-a-to-z",
+          ],
+        },
+        "upcoming": Object {
+          "filter": Object {
+            "upcoming": true,
+          },
+          "hideCount": true,
+          "name": "Upcoming challenges",
+          "sorts": Array [
+            "most-recent",
+            "prize-high-to-low",
+            "title-a-to-z",
+          ],
+        },
+      },
+      "default": undefined,
+      "getBuckets": [Function],
+      "isReviewOpportunitiesBucket": [Function],
+      "registerBucket": [Function],
+    },
     "filter": Object {
       "addTrack": [Function],
       "combine": [Function],
       "default": undefined,
+      "filterByDate": [Function],
       "getFilterFunction": [Function],
       "getReviewOpportunitiesFilterFunction": [Function],
       "mapToBackend": [Function],
+      "newMeta": [Function],
       "removeTrack": [Function],
       "setEndDate": [Function],
       "setReviewOpportunityType": [Function],
@@ -188,6 +345,67 @@ Object {
       "setTags": [Function],
       "setText": [Function],
     },
+    "sort": Object {
+      "SORTS": Object {
+        "CURRENT_PHASE": "current-phase",
+        "MOST_RECENT": "most-recent",
+        "NUM_REGISTRANTS": "num-registrants",
+        "NUM_SUBMISSIONS": "num-submissions",
+        "PRIZE_HIGH_TO_LOW": "prize-high-to-low",
+        "REVIEW_OPPORTUNITIES_PAYMENT": "review-opportunities-payment",
+        "REVIEW_OPPORTUNITIES_START_DATE": "review-opportunities-start-date",
+        "REVIEW_OPPORTUNITIES_TITLE_A_TO_Z": "review-opportunities-title-a-to-z",
+        "TIME_TO_REGISTER": "time-to-register",
+        "TIME_TO_SUBMIT": "time-to-submit",
+        "TITLE_A_TO_Z": "title-a-to-z",
+      },
+      "SORTS_DATA": Object {
+        "current-phase": Object {
+          "func": [Function],
+          "name": "Current phase",
+        },
+        "most-recent": Object {
+          "func": [Function],
+          "name": "Most recent",
+        },
+        "num-registrants": Object {
+          "func": [Function],
+          "name": "# of registrants",
+        },
+        "num-submissions": Object {
+          "func": [Function],
+          "name": "# of submissions",
+        },
+        "prize-high-to-low": Object {
+          "func": [Function],
+          "name": "Prize high to low",
+        },
+        "review-opportunities-payment": Object {
+          "func": [Function],
+          "name": "Payment",
+        },
+        "review-opportunities-start-date": Object {
+          "func": [Function],
+          "name": "Review start date",
+        },
+        "review-opportunities-title-a-to-z": Object {
+          "func": [Function],
+          "name": "Title A-Z",
+        },
+        "time-to-register": Object {
+          "func": [Function],
+          "name": "Time to register",
+        },
+        "time-to-submit": Object {
+          "func": [Function],
+          "name": "Time to submit",
+        },
+        "title-a-to-z": Object {
+          "func": [Function],
+          "name": "Title A-Z",
+        },
+      },
+    },
   },
   "errors": Object {
     "ERROR_ICON_TYPES": Object {
@@ -227,6 +445,7 @@ Object {
   "reducers": Object {
     "auth": [Function],
     "challenge": [Function],
+    "challengeListing": [Function],
     "direct": [Function],
     "errors": [Function],
     "groups": [Function],
@@ -321,11 +540,17 @@ Object {
     },
     "getApiResponsePayload": [Function],
     "getLookerApiResponsePayload": [Function],
+    "processSRM": [Function],
   },
   "time": Object {
     "default": undefined,
     "delay": [Function],
     "formatDuration": [Function],
   },
+  "url": Object {
+    "default": undefined,
+    "removeTrailingSlash": [Function],
+    "updateQuery": [Function],
+  },
 }
 `;
diff --git a/docs/actions.challenge-listing.md b/docs/actions.challenge-listing.md
new file mode 100644
index 00000000..85843947
--- /dev/null
+++ b/docs/actions.challenge-listing.md
@@ -0,0 +1,271 @@
+<a name="module_actions.challenge-listing"></a>
+
+## actions.challenge-listing
+Actions related to Topcoder challenge-listing APIs.
+
+
+* [actions.challenge-listing](#module_actions.challenge-listing)
+    * [.dropChallenges(bucket)](#module_actions.challenge-listing.dropChallenges) ⇒ <code>Action</code>
+    * [.getMoreChallenges(bucket)](#module_actions.challenge-listing.getMoreChallenges) ⇒ <code>Action</code>
+    * [.getAllActiveChallengesInit(uuid)](#module_actions.challenge-listing.getAllActiveChallengesInit) ⇒ <code>Action</code>
+    * [.getAllActiveChallengesDone(uuid, tokenV3)](#module_actions.challenge-listing.getAllActiveChallengesDone) ⇒ <code>Action</code>
+    * [.getActiveChallengesInit(uuid, page, frontFilter, sort, bucket)](#module_actions.challenge-listing.getActiveChallengesInit) ⇒ <code>Action</code>
+    * [.getActiveChallengesDone(
+  uuid, page, backendFilter, tokenV3, frontFilter, sort, bucket,
+)](#module_actions.challenge-listing.getActiveChallengesDone) ⇒ <code>Action</code>
+    * [.getRestActiveChallengesInit(uuid)](#module_actions.challenge-listing.getRestActiveChallengesInit) ⇒ <code>Action</code>
+    * [.getRestActiveChallengesDone(
+  uuid, tokenV3, backendFilter, frontFilter, sort, bucket,
+)](#module_actions.challenge-listing.getRestActiveChallengesDone) ⇒ <code>Action</code>
+    * [.getChallengeSubtracksInit()](#module_actions.challenge-listing.getChallengeSubtrackInit) ⇒ <code>Action</code>
+    * [.getChallengeSubtracksDone()](#module_actions.challenge-listing.getChallengeSubtracksDone) ⇒ <code>Action</code>
+    * [.getChallengeTagsInit()](#module_actions.challenge-listing.getChallengeTagsInit) ⇒ <code>Action</code>
+    * [.getChallengeTagsDone()](#module_actions.challenge-listing.getChallengeTagsDone) ⇒ <code>Action</code>
+    * [.getPastChallengesInit(uuid, page, frontFilter, sort)](#module_actions.challenge-listing.getPastChallengesInit) ⇒ <code>Action</code>
+    * [.getPastChallengesDone(uuid, page, filter, tokenV3, frontFilter, sort)](#module_actions.challenge-listing.getPastChallengesDone) ⇒ <code>Action</code>
+    * [.getReviewOpportunitiesInit(uuid, page, sort)](#module_actions.challenge-listing.getReviewOpportunitiesInit) ⇒ <code>Action</code>
+    * [.getReviewOpportunitiesDone(uuid, page, tokenV3, sort, frontFilter)](#module_actions.challenge-listing.getReviewOpportunitiesDone) ⇒ <code>Action</code>
+    * [.getSrmsInit(uuid)](#module_actions.challenge-listing.getSrmsInit) ⇒ <code>Action</code>
+    * [.getSrmsDone(uuid, handle, params, tokenV3)](#module_actions.challenge-listing.getSrmsDone) ⇒ <code>Action</code>
+    * [.expandTag(id)](#module_actions.challenge-listing.expandTag) ⇒ <code>Action</code>
+    * [.selectCommunity()](#module_actions.challenge-listing.selectCommunity) ⇒ <code>Action</code>
+    * [.setFilter()](#module_actions.challenge-listing.setFilter) ⇒ <code>Action</code>
+    * [.setDatepickerStatus(status)](#module_actions.challenge-listing.setDatepickerStatus) ⇒ <code>Action</code>
+    * [.setSort(bucket, sort)](#module_actions.challenge-listing.setSort) ⇒ <code>Action</code>
+
+
+<a name="module_actions.challenge-listing.dropChallenges"></a>
+### actions.challenge-listing.dropChallenges(bucket) ⇒ <code>Action</code>
+Creates an action that drops from Redux store all challenges-list related loaded.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| bucket | <code>String</code> | Bucket name |
+
+
+<a name="module_actions.challenge-listing.getMoreChallenges"></a>
+### actions.challenge-listing.getMoreChallenges(bucket) ⇒ <code>Action</code>
+Creates an action that get more challenges of bucket.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| bucket | <code>String</code> | Bucket name |
+
+
+<a name="module_actions.challenge-listing.getAllActiveChallengesInit"></a>
+### actions.challenge-listing.getAllActiveChallengesInit(uuid) ⇒ <code>Action</code>
+Creates an action that signals beginning of all active challenges loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+
+
+<a name="module_actions.challenge-listing.getAllActiveChallengesInit"></a>
+### actions.challenge-listing.getAllActiveChallengesInit(uuid, tokenV3) ⇒ <code>Action</code>
+Creates an action that loads all active challenges.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| tokenV3 | <code>String</code> | Topcoder v3 auth token. |
+
+<a name="module_actions.challenge-listing.getActiveChallengesInit"></a>
+### actions.challenge-listing.getActiveChallengesInit(uuid, page, frontFilter, sort, bucket) ⇒ <code>Action</code>
+Creates an action that signals beginning of active challenges of bucket loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| page | <code>Number</code> | Page number of fetch data |
+| frontFilter | <code>Object</code> | Filter Object from Client |
+| sort | <code>String</code> | Sort name |
+| bucket | <code>String</code> | Bucket name |
+
+
+<a name="module_actions.challenge-listing.getActiveChallengesDone"></a>
+### actions.challenge-listing.getActiveChallengesDone(
+  uuid, page, backendFilter, tokenV3, frontFilter, sort, bucket,
+) ⇒ <code>Action</code>
+Creates an action that loads active challenges of bucket.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| page | <code>Number</code> | Page number of fetch data |
+| backendFilter | <code>Object</code> | Filter Object from Backend |
+| tokenV3 | <code>String</code> | Topcoder v3 auth token |
+| frontFilter | <code>Object</code> | Filter Object from Client |
+| sort | <code>String</code> | Sort name |
+| bucket | <code>String</code> | Bucket name |
+
+
+<a name="module_actions.challenge-listing.getRestActiveChallengesInit"></a>
+### actions.challenge-listing.getRestActiveChallengesInit(uuid) ⇒ <code>Action</code>
+Creates an action that signals beginning of rest active challenges of bucket loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+
+
+<a name="module_actions.challenge-listing.getRestActiveChallengesDone"></a>
+### actions.challenge-listing.getRestActiveChallengesDone(
+  uuid, tokenV3, backendFilter, frontFilter, sort, bucket,
+) ⇒ <code>Action</code>
+Creates an action that loads rest active challenges of bucket.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| tokenV3 | <code>String</code> | Topcoder v3 auth token |
+| backendFilter | <code>Object</code> | Filter Object from Backend |
+| frontFilter | <code>Object</code> | Filter Object from Client |
+| sort | <code>String</code> | Sort name |
+| bucket | <code>String</code> | Bucket name |
+
+
+<a name="module_actions.challenge-listing.getChallengeSubtracksInit"></a>
+### actions.challenge-listing.getChallengeSubtracksInit() ⇒ <code>Action</code>
+Creates an action that signals beginning of challenge substrcks loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+<a name="module_actions.challenge-listing.getChallengeSubtracksDone"></a>
+### actions.challenge-listing.getChallengeSubtracksDone()⇒ <code>Action</code>
+Creates an action that loads challenge substrcks.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+<a name="module_actions.challenge-listing.getChallengeTagsInit"></a>
+### actions.challenge-listing.getChallengeTagsInit() ⇒ <code>Action</code>
+Creates an action that signals beginning of challenge tags loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+<a name="module_actions.challenge-listing.getChallengeTagsDone"></a>
+### actions.challenge-listing.getChallengeTagsDone()⇒ <code>Action</code>
+Creates an action that loads challenge tags.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+<a name="module_actions.challenge-listing.getPastChallengesInit"></a>
+### actions.challenge-listing.getPastChallengesInit(uuid, page, frontFilter, sort) ⇒ <code>Action</code>
+Creates an action that signals beginning of past challenges loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| page | <code>Number</code> | Page number of fetch data |
+| frontFilter | <code>Object</code> | Filter Object from Client |
+| sort | <code>String</code> | Sort name |
+
+
+<a name="module_actions.challenge-listing.getPastChallengesDone"></a>
+### actions.challenge-listing.getPastChallengesDone(uuid, page, filter, tokenV3, frontFilter, sort) ⇒ <code>Action</code>
+Creates an action that loads past challenges.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| page | <code>Number</code> | Page number of fetch data |
+| filter | <code>Object</code> | Filter Object from Backend |
+| tokenV3 | <code>String</code> | Topcoder v3 auth token |
+| frontFilter | <code>Object</code> | Filter Object from Client |
+| sort | <code>String</code> | Sort name |
+
+
+<a name="module_actions.challenge-listing.getReviewOpportunitiesInit"></a>
+### actions.challenge-listing.getReviewOpportunitiesInit(uuid, page, sort) ⇒ <code>Action</code>
+Creates an action that signals beginning of review opportunities loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| page | <code>Number</code> | Page number of fetch data |
+| sort | <code>String</code> | Sort name |
+
+
+<a name="module_actions.challenge-listing.getReviewOpportunitiesDone"></a>
+### actions.challenge-listing.getReviewOpportunitiesDone(uuid, page, tokenV3, sort, frontFilter) ⇒ <code>Action</code>
+Creates an action that loads review oportunites.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| page | <code>Number</code> | Page number of fetch data |
+| tokenV3 | <code>String</code> | Topcoder v3 auth token |
+| sort | <code>String</code> | Sort name |
+| frontFilter | <code>Object</code> | Filter Object from Client |
+
+
+
+<a name="module_actions.challenge-listing.getSrmsInit"></a>
+### actions.challenge-listing.getSrmsInit(uuid) ⇒ <code>Action</code>
+Creates an action that signals beginning of SRMs loading.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+
+<a name="module_actions.challenge-listing.getSrmsDone"></a>
+### actions.challenge-listing.getSrmsDone(uuid, handle, params, tokenV3) ⇒ <code>Action</code>
+Creates an action that SRMs.
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| uuid | <code>String</code> | UUID of the operation (the same should be passed into  the corresponding |
+| handle | <code>String</code> | Topcoder member handle |
+| params | <code>Object</code> | params of fetch data |
+| tokenV3 | <code>String</code> | Topcoder v3 auth token |
+
+
+<a name="module_actions.challenge-listing.expandTag"></a>
+### actions.challenge-listing.expandTag(id) ⇒ <code>Action</code>
+Creates an action that set tag id
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| id | <code>String</code> | Id of tag |
+
+
+<a name="module_actions.challenge-listing.selectCommunity"></a>
+### actions.challenge-listing.selectCommunity() ⇒ <code>Action</code>
+Creates an action that pass community id
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+<a name="module_actions.challenge-listing.setFilter"></a>
+### actions.challenge-listing.setFilter() ⇒ <code>Action</code>
+Creates an action that pass filter value
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+<a name="module_actions.challenge-listing.setDatepickerStatus"></a>
+### actions.challenge-listing.setDatepickerStatus(status) ⇒ <code>Action</code>
+Creates an action that set Datepicker status
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing) 
+
+| Param | Type | Description |
+| --- | --- | --- |
+| status | <code>Boolean</code> | Status datapicker |
+
+
+<a name="module_actions.challenge-listing.setSort"></a>
+### actions.challenge-listing.setSort(bucket, sort) ⇒ <code>Action</code>
+Creates an action that set sort of bucket
+**Kind**: static method of [<code>actions.challenge-listing</code>](#module_actions.challenge-listing)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| bucket | <code>String</code> | Bucket name |
+| sort | <code>String</code> | Sort name |
diff --git a/docs/buckets.md b/docs/buckets.md
new file mode 100644
index 00000000..80072bd3
--- /dev/null
+++ b/docs/buckets.md
@@ -0,0 +1,50 @@
+<a name="module_challenge_buckets"></a>
+
+## buckets
+Collection of buckets of challenge
+
+* [challenge_buckets](#module_challenge_buckets)
+    * [.BUCKETS](#module_challenge_buckets.BUCKETS)
+    * [.BUCKET_DATA](#module_challenge_buckets.BUCKET_DATA)
+    * [.getBuckets(res)](#module_challenge_buckets.getBuckets) ⇒ <code>Promise</code>
+    * [.isReviewOpportunitiesBucket(res)](#module_challenge_buckets.isReviewOpportunitiesBucket) ⇒ <code>Promise</code>
+    * [.registerBucket](#module_challenge_buckets.registerBucket)
+
+<a name="module_challenge_buckets.BUCKETS"></a>
+### challenge_buckets.BUCKETS
+Bucket types
+**Kind**: static constant of [<code>challenge_buckets</code>](#module_challenge_buckets)  
+
+
+<a name="module_challenge_buckets.BUCKET_DATA"></a>
+### challenge_buckets.BUCKET_DATA
+The data of bucket
+**Kind**: static constant of [<code>challenge_buckets</code>](#module_challenge_buckets)  
+
+
+<a name="module_challenge_buckets.getBuckets"></a>
+### challenge_buckets.getBuckets(userHandle) ⇒ <code>Promise</code>
+Returns configuration of all possible challenge buckets.
+**Kind**: static method of [<code>challenge_buckets</code>](#module_challenge_buckets)
+**Returns**: <code>Promise</code> - Resolves to the payload.  
+
+| Param | Type |
+| --- | --- |
+| res | <code>Object</code> | 
+
+
+<a name="module_challenge_buckets.isReviewOpportunitiesBucket"></a>
+### challenge_buckets.isReviewOpportunitiesBucket(bucket) ⇒ <code>Promise</code>
+Tests if a given bucket is of any of the Review Opportunities types
+**Kind**: static method of [<code>challenge_buckets</code>](#module_challenge_buckets) 
+**Returns**: <code>Promise</code> - Resolves to the payload.  
+
+| Param | Type |
+| --- | --- |
+| res | <code>Boolean</code> | 
+
+<a name="module_challenge_buckets.registerBucket"></a>
+### challenge_buckets.registerBucket(id, bucket) ⇒ <code>Promise</code>
+Registers a new bucket.
+**Kind**: static method of [<code>challenge_buckets</code>](#module_challenge_buckets)  
+ 
diff --git a/docs/index.md b/docs/index.md
index ec065107..185c46d5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -9,6 +9,11 @@
 <a href="actions.challenge.md">actions.challenge</a></dt>
 <dd><p>Actions related to Topcoder challenges APIs.</p>
 </dd>
+<dt>
+ <dt>
+<a href="actions.challenge-listing.md">actions.challenge-listing</a></dt>
+<dd><p>Actions related to Topcoder challenge-listing APIs.</p>
+</dd>
 <dt>
 <a href="actions.direct.md">actions.direct</a></dt>
 <dd><p>Actions related to Direct API: access to projects, billing accounts,
@@ -71,6 +76,11 @@ actions and reducer; thus, this module.</p>
 <dd><p>Reducer for <a href="#module_actions.challenge">actions.challenge</a> actions.</p>
 <p>State segment managed by this reducer has the following strcuture:</p>
 </dd>
+<dt>
+ <dt>
+<a href="reducers.challenge-listing.md">reducers.challenge-listing</a></dt>
+<dd><p>Reducer for <a href="#module_actions.challenge-listing">actions.challenge-listing</a> actions.</p>
+</dd>
 <dt>
 <a href="reducers.direct.md">reducers.direct</a></dt>
 <dd><p>Reducer for handling the results of Direct-related actions.</p>
@@ -275,4 +285,19 @@ the proxy will forward them to the service only if LOG_ENTRIES_TOKEN is set).</p
 <dd><p>Utility functions for time/date related stuff</p>
 </dd>
 </dl>
-
+<dt>
+<a href="sort.md">sort</a></dt>
+<dd><p>Collection of challenge sort.</p>
+</dd>
+<dt>
+ <dt>
+<a href="buckets.md">tc</a></dt>
+<dd><p>Collection of challenge buckets.</p>
+</dd>
+<dt>
+ <dt>
+ <dt>
+<a href="url.md">url</a></dt>
+<dd><p>Collection of url function.</p>
+</dd>
+<dt>
diff --git a/docs/reducers.challenge-listing.md b/docs/reducers.challenge-listing.md
new file mode 100644
index 00000000..ff80e6f4
--- /dev/null
+++ b/docs/reducers.challenge-listing.md
@@ -0,0 +1,310 @@
+<a name="module_reducers.challenge-listing"></a>
+
+## reducers.challenge-listing
+Reducer for [actions.challenge-listing](#module_actions.challenge-listing) actions.
+
+State segment managed by this reducer has the following strcuture:
+
+**Todo**
+
+- [ ] Document the structure.
+
+
+* [reducers.challenge-listing](#module_reducers.challenge-listing)
+    * _static_
+        * [.default](#module_reducers.challenge-listing.default)
+        * [.factory(options)](#module_reducers.challenge-listing.factory) ⇒ <code>Promise</code>
+    * _inner_
+        * [~dropChallenges(state, action)](#module_reducers.challenge-listing..dropChallenges) ⇒ <code>Object</code>
+        * [~getMoreChallenges(state, action)](#module_reducers.challenge-listing..getMoreChallenges) ⇒ <code>Object</code>
+        * [~expandTag(state, action)](#module_reducers.challenge-listing..expandTag) ⇒ <code>Object</code>
+        * [~getAllActiveChallengesInit(state, action)](#module_reducers.challenge-listing..onGetAllActiveChallengesInit) ⇒ <code>Object</code>
+        * [~getAllActiveChallengesDone(state, action)](#module_reducers.challenge-listing..onGetAllActiveChallengesDone) ⇒ <code>Object</code>
+        * [~getActiveChallengesInit(state, action)](#module_reducers.challenge-listing..getActiveChallengesInit) ⇒ <code>Object</code>
+        * [~getActiveChallengesDone(state, action)](#module_reducers.challenge-listing..getActiveChallengesDone) ⇒ <code>Object</code>
+        * [~getRestActiveChallengesInit(state, action)](#module_reducers.challenge-listing..getRestActiveChallengesInit) ⇒ <code>Object</code>
+        * [~getRestActiveChallengesDone(state, action)](#module_reducers.challenge-listing..getRestActiveChallengesDone) ⇒ <code>Object</code>
+        * [~getChallengeSubtracksInit()](#module_reducers.challenge-listing..getChallengeSubtracksInit) ⇒ <code>Object</code>
+        * [~getChallengeSubtracksDone(state, action)](#module_reducers.challenge-listing..getChallengeSubtracksDone) ⇒ <code>Object</code>
+        * [~getChallengeTagsInit()](#module_reducers.challenge-listing..getChallengeTagsInit) ⇒ <code>Object</code>
+        * [~getChallengeTagsDone(state, action)](#module_reducers.challenge-listing..getChallengeTagsDone) ⇒ <code>Object</code>
+        * [~getPastChallengesInit(state, action)](#module_reducers.challenge-listing..getPastChallengesInit) ⇒ <code>Object</code>
+        * [~getReviewOpportunitiesInit(state, action)](#module_reducers.challenge-listing..getReviewOpportunitiesInit) ⇒ <code>Object</code>
+        * [~getReviewOpportunitiesDone(state, action)](#module_reducers.challenge-listing..getReviewOpportunitiesDone) ⇒ <code>Object</code>
+        * [~getSrmsInit(state, action)](#module_reducers.challenge-listing..getSrmsInit) ⇒ <code>Object</code>
+        * [~getSrmsDone(state, action)](#module_reducers.challenge-listing..getSrmsDone) ⇒ <code>Object</code>
+        * [~selectCommunity(state, action)](#module_reducers.challenge-listing..selectCommunity) ⇒ <code>Object</code>
+        * [~setFilter(state, action)](#module_reducers.challenge-listing..setFilter) ⇒ <code>Object</code>
+        * [~setSort(state, action)](#module_reducers.challenge-listing..setSort) ⇒ <code>Object</code>
+        * [~setDatePickerStatus(state, action)](#module_reducers.challenge-listing..setDatePickerStatus) ⇒ <code>Object</code>
+        * [~create(initialState)](#module_reducers.challenge..create) ⇒ <code>function</code>
+
+<a name="module_reducers.challenge-listing.default"></a>
+### reducers.challenge-listing.default
+Reducer with default intial state.
+**Kind**: static property of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+<a name="module_reducers.challenge-listing.factory"></a>
+### reducers.challenge-listing.factory() ⇒ <code>Promise</code>
+Factory which creates a new reducer with its initial state tailored to the
+given options object, if specified (for server-side rendering). If options
+object is not specified, it creates just the default reducer. Accepted options are:
+
+**Kind**: static method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Resolves**: <code>Function(state, action): state</code> New reducer.  
+
+<a name="module_reducers.challenge-listing..dropChallenges"></a>
+### reducers.challenge-listing~dropChallenges(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/DROP_CHALLENGES action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> | 
+
+<a name="module_reducers.challenge-listing..getMoreChallenges"></a>
+### reducers.challenge-listing~getMoreChallenges(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_MORE_CHALLENGES action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> | 
+
+<a name="module_reducers.challenge-listing..expandTag"></a>
+### reducers.challenge-listing~expandTag(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/EXPAND_TAG action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> | 
+
+<a name="module_reducers.challenge-listing..getAllActiveChallengesInit"></a>
+### reducers.challenge-listing~getAllActiveChallengesInit(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_ALL_ACTIVE_CHALLENGES_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> |
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getAllActiveChallengesDone"></a>
+### reducers.challenge-listing~getAllActiveChallengesDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_ALL_ACTIVE_CHALLENGES_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getActiveChallengesInit"></a>
+### reducers.challenge-listing~getActiveChallengesInit(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_ACTIVE_CHALLENGES_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getActiveChallengesDone"></a>
+### reducers.challenge-listing~getActiveChallengesDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_ACTIVE_CHALLENGES_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getRestActiveChallengesInit"></a>
+### reducers.challenge-listing~getRestActiveChallengesInit(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_REST_ACTIVE_CHALLENGES_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getRestActiveChallengesDone"></a>
+### reducers.challenge-listing~getRestActiveChallengesDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_REST_ACTIVE_CHALLENGES_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getChallengeSubtracksInit"></a>
+### reducers.challenge-listing~getChallengeSubtracksInit() ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_CHALLENGE_SUBTRACKS_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+<a name="module_reducers.challenge-listing..getChallengeSubtracksDone"></a>
+### reducers.challenge-listing~getChallengeSubtracksDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_CHALLENGE_SUBTRACKS_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getChallengeTagsInit"></a>
+### reducers.challenge-listing~getChallengeTagsInit() ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_CHALLENGE_TAGS_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+
+<a name="module_reducers.challenge-listing..getChallengeTagsDone"></a>
+### reducers.challenge-listing~getChallengeTagsDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_CHALLENGE_TAGS_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getPastChallengesInit"></a>
+### reducers.challenge-listing~getPastChallengesInit(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_PAST_CHALLENGES_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getPastChallengesDone"></a>
+### reducers.challenge-listing~getPastChallengesDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_PAST_CHALLENGES_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getReviewOpportunitiesInit"></a>
+### reducers.challenge-listing~getReviewOpportunitiesInit(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_REVIEW_OPPORTUNITIES_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| uuid | <code>Object</code> |
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getReviewOpportunitiesDone"></a>
+### reducers.challenge-listing~getReviewOpportunitiesDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_REVIEW_OPPORTUNITIES_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| uuid | <code>Object</code> | 
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getSrmsInit"></a>
+### reducers.challenge-listing~getSrmsInit(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_SRMS_INIT action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| uuid | <code>Object</code> |
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..getSrmsDone"></a>
+### reducers.challenge-listing~getSrmsDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/GET_SRMS_DONE action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| uuid | <code>Object</code> |
+| action | <code>Object</code> |
+
+
+<a name="module_reducers.challenge-listing..selectCommunity"></a>
+### reducers.challenge-listing~selectCommunity(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/SELECT_COMMUNITY action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| uuid | <code>Object</code> |
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..setFilter"></a>
+### reducers.challenge-listing~setFilter(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/SET_FILTER action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| uuid | <code>Object</code> |
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge-listing..setDatePickerStatus"></a>
+### reducers.challenge-listing~setDatePickerStatus(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE_LISTING/SET_DATEPICKER_STATUS action.
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)  
+
+**Returns**: <code>Object</code> - New state  
+
+| Param | Type |
+| --- | --- |
+| uuid | <code>Object</code> |
+| action | <code>Object</code> |
+
+
+<a name="module_reducers.challenge..create"></a>
+
+### reducers.challenge-listing~create(initialState) ⇒ <code>function</code>
+Creates a new Challenge-listing reducer with the specified initial state.
+
+**Kind**: inner method of [<code>reducers.challenge-listing</code>](#module_reducers.challenge-listing)
+**Returns**: <code>function</code> - Challenge-listing reducer.  
+
+| Param | Type | Description |
+| --- | --- | --- |
+| initialState | <code>Object</code> | Optional. Initial state. |
+
diff --git a/docs/sort.md b/docs/sort.md
new file mode 100644
index 00000000..a7f21d61
--- /dev/null
+++ b/docs/sort.md
@@ -0,0 +1,20 @@
+<a name="module_challenge_sort"></a>
+
+## sort
+Collection of challenge list sort
+
+* [challenge_sort](#module_challenge_sort)
+    * [.SORTS](#module_challenge_sort.BSORTS)
+    * [.SORTS_DATA](#module_challenge_sort.SORTS_DATA)
+
+
+<a name="module_challenge_sort.SORTS"></a>
+### challenge_sort.SORTS
+Sort types
+**Kind**: static constant of [<code>challenge_sort</code>](#module_challenge_sort)  
+
+
+<a name="module_challenge_sort.SORTS_DATA"></a>
+### challenge_sort.SORTS_DATA
+The data of sort
+**Kind**: static constant of [<code>challenge_sort</code>](#module_challenge_sort)
diff --git a/docs/tc.md b/docs/tc.md
index 8e060798..1345ed26 100644
--- a/docs/tc.md
+++ b/docs/tc.md
@@ -11,23 +11,34 @@ Collection of small Topcoder-related functions.
 * [tc](#module_tc)
     * [.REVIEW_OPPORTUNITY_TYPES](#module_tc.REVIEW_OPPORTUNITY_TYPES)
     * [.getApiResponsePayload(res)](#module_tc.getApiResponsePayload) ⇒ <code>Promise</code>
+    * [.processSRM(res)](#module_tc.processSRM) ⇒ <code>Promise</code>
 
 <a name="module_tc.REVIEW_OPPORTUNITY_TYPES"></a>
 
 ### tc.REVIEW_OPPORTUNITY_TYPES
 Review Opportunity types
 
-**Kind**: static constant of [<code>tc</code>](#module_tc)  
+**Kind**: static constant of [<code>tc</code>](#module_tc)
 <a name="module_tc.getApiResponsePayload"></a>
 
 ### tc.getApiResponsePayload(res) ⇒ <code>Promise</code>
-Gets payload from a standard success response from TC v2 API; or throws
+Gets payload from a standard success response from TC API; or throws
 an error in case of a failure response.
 
-**Kind**: static method of [<code>tc</code>](#module_tc)  
+**Kind**: static method of [<code>tc</code>](#module_tc)
 **Returns**: <code>Promise</code> - Resolves to the payload.  
 
 | Param | Type |
 | --- | --- |
 | res | <code>Object</code> | 
 
+<a name="module_tc.processSRM"></a>
+### tc.processSRM(res) ⇒ <code>Promise</code>
+process srm to populate additional infomation
+
+**Kind**: static method of [<code>tc</code>](#module_tc)
+**Returns**: <code>Promise</code> - Resolves to the payload.  
+
+| Param | Type |
+| --- | --- |
+| res | <code>Object</code> | 
diff --git a/docs/url.md b/docs/url.md
new file mode 100644
index 00000000..acdfb108
--- /dev/null
+++ b/docs/url.md
@@ -0,0 +1,27 @@
+<a name="module_url"></a>
+
+## url
+Collection of url functions.
+
+* [url](#module_url)
+    * [.updateQuery](#module_url.updateQuery)
+    * [.removeTrailingSlash(res)](#module_url.removeTrailingSlash) ⇒ <code>Promise</code>
+
+<a name="module_url.updateQuery"></a>
+### url.updateQuery
+If executed client-side (determined in this case by the presence of global
+ * window object), this function updates query section of URL; otherwise does
+ * nothing.
+**Kind**: static method of [<code>tc</code>](#module_url)
+
+
+<a name="module_url.removeTrailingSlash"></a>
+### url.removeTrailingSlash(res) ⇒ <code>Promise</code>
+Cleans/removes trailing slash from url
+**Kind**: static method of [<code>url</code>](#module_url)
+**Returns**: <code>Promise</code> - Resolves to the payload.  
+
+| Param | Type |
+| --- | --- |
+| res | <code>String</code> |
+
diff --git a/package.json b/package.json
index cc4ec5c2..d65f4707 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": "0.7.11",
+  "version": "0.7.12",
   "dependencies": {
     "auth0-js": "^6.8.4",
     "isomorphic-fetch": "^2.2.1",
diff --git a/src/actions/challenge-listing.js b/src/actions/challenge-listing.js
new file mode 100644
index 00000000..d8acf22a
--- /dev/null
+++ b/src/actions/challenge-listing.js
@@ -0,0 +1,450 @@
+/**
+ * Challenge listing actions.
+ */
+
+import _ from 'lodash';
+import { createActions } from 'redux-actions';
+import { decodeToken } from 'tc-accounts';
+import 'isomorphic-fetch';
+import { processSRM, COMPETITION_TRACKS } from '../utils/tc';
+import { services } from '../services';
+import { errors } from '../utils';
+import * as filterUtil from '../utils/challenge/filter';
+import * as config from '../config';
+
+const { fireErrorMessage } = errors;
+const { getService } = services.challenge;
+const { getReviewOpportunitiesService } = services.reviewOpportunities;
+const { PAGE_SIZE, REVIEW_OPPORTUNITY_PAGE_SIZE } = config;
+
+/**
+ * Process filter
+ * Development challenges having Data Science tech tag, still should be
+ * included into data science track.
+ * @param filter
+ * @returns {string}
+ */
+function processFilter(filter) {
+  const newFilter = _.clone(filter);
+  if (_.has(filter, 'track')
+    && filter.track.includes(COMPETITION_TRACKS.DATA_SCIENCE.toUpperCase())
+    && !filter.track.includes(COMPETITION_TRACKS.DEVELOP.toUpperCase())
+  ) {
+    newFilter.track = `${newFilter.track},${COMPETITION_TRACKS.DEVELOP.toUpperCase()}`;
+  }
+  return newFilter;
+}
+
+/**
+ * Private. Loads from the backend all challenges matching some conditions.
+ * @param {Function} getter Given params object of shape { limit, offset }
+ *  loads from the backend at most "limit" challenges, skipping the first
+ *  "offset" ones. Returns loaded challenges as an array.
+ * @param {Number} page Optional. Next page of challenges to load.
+ * @param {Array} prev Optional. Challenges loaded so far.
+ */
+function getAll(getter, page = 0, prev) {
+  /* Amount of challenges to fetch in one API call. 50 is the current maximum
+   * amount of challenges the backend returns, event when the larger limit is
+   * explicitely required. */
+  return getter({
+    limit: PAGE_SIZE,
+    offset: page * PAGE_SIZE,
+  }).then((res) => {
+    if (res.challenges.length === 0) {
+      return prev || res;
+    }
+    // parse challenges and meta
+    let current = {};
+    if (prev) {
+      current.challenges = prev.challenges.concat(res.challenges);
+      current.meta = res.meta;
+    } else {
+      current = res;
+    }
+    return getAll(getter, 1 + page, current);
+  });
+}
+
+/**
+ * Gets possible challenge subtracks.
+ * @return {Promise}
+ */
+function getChallengeSubtracksDone() {
+  return getService()
+    .getChallengeSubtracks()
+    .then(res => res.sort((a, b) => a.name.localeCompare(b.name)));
+}
+
+/**
+ * Gets possible challenge tags (technologies).
+ * @return {Promise}
+ */
+function getChallengeTagsDone() {
+  return getService()
+    .getChallengeTags()
+    .then(res => res.map(item => item.name)
+      .sort((a, b) => a.localeCompare(b)));
+}
+
+/**
+ * Notifies about reloading of all active challenges. The UUID is stored in the
+ * state, and only challenges fetched by getAllActiveChallengesDone action with
+ * the same UUID will be accepted into the state.
+ * @param {String} uuid
+ * @param {String} page
+ * @param {Object} frontFilter
+ * @param {String} sort
+ * @param {String} bucket
+ * @return {String}
+ */
+function getActiveChallengesInit(uuid, page, frontFilter, sort, bucket) {
+  return {
+    uuid, page, frontFilter, sort, bucket,
+  };
+}
+
+/** TODO: Inspect if the 2 actions bellow can be removed?
+ * They do  duplicate what is done in `getActiveChallengesDone` but fetch all challenges
+ * which was refactored in listing-improve
+ */
+function getAllActiveChallengesInit(uuid) {
+  return uuid;
+}
+function getAllActiveChallengesDone(uuid, tokenV3) {
+  const filter = { status: 'ACTIVE' };
+  const service = getService(tokenV3);
+  const calls = [
+    getAll(params => service.getChallenges(filter, params)),
+  ];
+  let user;
+  if (tokenV3) {
+    user = decodeToken(tokenV3).handle;
+    // Handle any errors on this endpoint so that the non-user specific challenges
+    // will still be loaded.
+    calls.push(getAll(params => service.getUserChallenges(user, filter, params)
+      .catch(() => ({ challenges: [] }))));
+  }
+  return Promise.all(calls).then(([ch, uch]) => {
+    /* uch array contains challenges where the user is participating in
+@@ -111,8 +124,8 @@ function getAllActiveChallengesDone(uuid, tokenV3) {
+     * challenges in an efficient way. */
+    if (uch) {
+      const map = {};
+      uch.challenges.forEach((item) => { map[item.id] = item; });
+      ch.challenges.forEach((item) => {
+        if (map[item.id]) {
+          /* It is fine to reassing, as the array we modifying is created just
+           * above within the same function. */
+          /* eslint-disable no-param-reassign */
+          item.users[user] = true;
+          item.userDetails = map[item.id].userDetails;
+          /* eslint-enable no-param-reassign */
+        }
+      });
+    }
+
+    return { uuid, challenges: ch.challenges };
+  });
+}
+
+/**
+ * Gets 1 page of active challenges (including marathon matches) from the backend.
+ * Once this action is completed any active challenges saved to the state before
+ * will be dropped, and the newly fetched ones will be stored there.
+ * Loading of all challenges wil start in background.
+ * @param {String} uuid
+ * @param {Number} page
+ * @param {Object} backendFilter Backend filter to use.
+ * @param {String} tokenV3 Optional. Topcoder auth token v3. Without token only
+ *  public challenges will be fetched. With the token provided, the action will
+ *  also fetch private challenges related to this user.
+ * @param {Object} frontFilter
+ * @param {String} sort
+ * @param {String} bucket
+
+ * @return {Promise}
+ */
+function getActiveChallengesDone(
+  uuid, page, backendFilter, tokenV3, frontFilter = {}, sort, bucket,
+) {
+  const filter = processFilter({
+    ...backendFilter,
+    status: 'ACTIVE',
+  });
+
+  const service = getService(tokenV3);
+  const calls = [
+    service.getChallenges(filter, {
+      limit: PAGE_SIZE,
+      offset: page * PAGE_SIZE,
+    }),
+  ];
+  let user;
+  if (tokenV3) {
+    user = decodeToken(tokenV3).handle;
+    // Handle any errors on this endpoint so that the non-user specific challenges
+    // will still be loaded.
+    calls.push(service.getUserChallenges(user, filter, {
+      limit: PAGE_SIZE,
+      offset: page * PAGE_SIZE,
+    }).catch(() => ({ challenges: [] })));
+  }
+  return Promise.all(calls).then(([ch, uch]) => {
+    /* uch array contains challenges where the user is participating in
+     * some role. The same challenge are already listed in res array, but they
+     * are not attributed to the user there. This block of code marks user
+     * challenges in an efficient way. */
+    if (uch) {
+      const map = {};
+      uch.challenges.forEach((item) => { map[item.id] = item; });
+      ch.challenges.forEach((item) => {
+        if (map[item.id]) {
+          /* It is fine to reassing, as the array we modifying is created just
+           * above within the same function. */
+          /* eslint-disable no-param-reassign */
+          item.users[user] = true;
+          item.userDetails = map[item.id].userDetails;
+          /* eslint-enable no-param-reassign */
+        }
+      });
+    }
+
+    let { challenges, meta } = ch;
+    // filter by date range and re-compute meta
+    // we can safely remove the next two lines when backend support date range
+    challenges = filterUtil.filterByDate(challenges, frontFilter);
+    meta = filterUtil.newMeta(meta, challenges, frontFilter);
+    return {
+      uuid,
+      handle: tokenV3 ? user : null,
+      challenges,
+      meta,
+      frontFilter,
+      sort,
+      bucket,
+      tokenV3,
+    };
+  });
+}
+
+/**
+ * Init loading of all challenges
+ * @param {String} uuid
+ */
+function getRestActiveChallengesInit(uuid) {
+  return { uuid };
+}
+
+/**
+ * Loading all challenges
+ * @param {String} uuid
+ * @param {String} tokenV3
+ * @param {Object} backendFilter
+ * @param {Object} frontFilter
+ * @param {String} sort
+ * @param {String} bucket
+ */
+function getRestActiveChallengesDone(
+  uuid, tokenV3, backendFilter, frontFilter, sort, bucket,
+) {
+  const filter = processFilter({
+    ...backendFilter,
+    status: 'ACTIVE',
+  });
+
+  const service = getService(tokenV3);
+  const calls = [
+    getAll(params => service.getChallenges(filter, params), 1),
+  ];
+  let user;
+  if (tokenV3) {
+    user = decodeToken(tokenV3).handle;
+    calls.push(getAll(params => service.getUserChallenges(user, filter, params)
+      .catch(() => ({ challenges: [] }))), 1);
+  }
+  return Promise.all(calls).then(([ch, uch]) => {
+    /* uch array contains challenges where the user is participating in
+     * some role. The same challenge are already listed in res array, but they
+     * are not attributed to the user there. This block of code marks user
+     * challenges in an efficient way. */
+    if (uch) {
+      const map = {};
+      uch.challenges.forEach((item) => { map[item.id] = item; });
+      ch.challenges.forEach((item) => {
+        if (map[item.id]) {
+          /* It is fine to reassing, as the array we modifying is created just
+           * above within the same function. */
+          /* eslint-disable no-param-reassign */
+          item.users[user] = true;
+          item.userDetails = map[item.id].userDetails;
+          /* eslint-enable no-param-reassign */
+        }
+      });
+    }
+
+    let { challenges } = ch;
+    // filter by date range and re-compute meta
+    // we can safely remove the next two lines when backend support date range
+    challenges = filterUtil.filterByDate(challenges, frontFilter);
+    const meta = filterUtil.newMeta(undefined, challenges, frontFilter);
+
+    return {
+      uuid,
+      handle: tokenV3 ? user : null,
+      challenges,
+      frontFilter,
+      meta,
+      sort,
+      bucket,
+    };
+  });
+}
+
+/**
+ * Notifies the state that we are about to load the specified page of past
+ * challenges.
+ * @param {String} uuid
+ * @param {Number} page
+ * @param {Object} frontFilter
+ * @param {String} sort
+ * @return {Object}
+ */
+function getPastChallengesInit(uuid, page, frontFilter, sort) {
+  return {
+    uuid,
+    page,
+    frontFilter,
+    sort,
+  };
+}
+
+/**
+ * Gets the specified page of past challenges (including MMs).
+ * @param {String} uuid
+ * @param {Number} page Page of challenges to fetch.
+ * @param {Object} filter Backend filter to use.
+ * @param {String} tokenV3 Optional. Topcoder auth token v3.
+ * @param {Object} frontFilter Optional. Original frontend filter.
+ * @param {String} sort
+ * @return {Object}
+ */
+function getPastChallengesDone(uuid, page, filter, tokenV3, frontFilter = {}, sort) {
+  const service = getService(tokenV3);
+  const newFilter = processFilter({
+    ...filter,
+    status: 'COMPLETED',
+  });
+  return service.getChallenges(newFilter, {
+    limit: PAGE_SIZE,
+    offset: page * PAGE_SIZE,
+  }).then(({ challenges }) => ({
+    uuid,
+    challenges,
+    frontFilter,
+    sort,
+  }));
+}
+
+/**
+ * Action to get a list of currently open Review Opportunities using V3 API
+ * @param {String} uuid Unique identifier for init/donen instance from shortid module
+ * @param {Number} page Page of review opportunities to fetch.
+ * @param {String} tokenV3 Optional.
+ * @param {String} sort Optional.
+ * @param {Object} frontFilter Optional.
+ * @return {Object} Action object
+ */
+function getReviewOpportunitiesDone(uuid, page, tokenV3, sort, frontFilter = {}) {
+  return getReviewOpportunitiesService(tokenV3)
+    .getReviewOpportunities(REVIEW_OPPORTUNITY_PAGE_SIZE, page * REVIEW_OPPORTUNITY_PAGE_SIZE)
+    .then(loaded => ({
+      uuid, loaded, sort, frontFilter,
+    }))
+    .catch((error) => {
+      fireErrorMessage('Error Getting Review Opportunities', error.content || error);
+      return Promise.reject(error);
+    });
+}
+
+/**
+ * Payload creator for the action that inits the loading of SRMs.
+ * @param {String} uuid
+ * @return {String}
+ */
+function getSrmsInit(uuid) {
+  return uuid;
+}
+
+/**
+ * Payload creator for the action that loads SRMs.
+ * @param {String} uuid
+ * @param {String} handle
+ * @param {Object} params
+ * @param {String} tokenV3
+ */
+function getSrmsDone(uuid, handle, params, tokenV3) {
+  const service = getService(tokenV3);
+  const promises = [service.getSrms(params)];
+  if (handle) {
+    promises.push(service.getUserSrms(handle, params));
+  }
+  return Promise.all(promises).then((data) => {
+    let srms = data[0];
+    const userSrms = data[1];
+    const userSrmsMap = {};
+    _.forEach(userSrms, (srm) => {
+      userSrmsMap[srm.id] = srm;
+    });
+    srms = _.map(srms, (srm) => {
+      if (userSrmsMap[srm.id]) {
+        return processSRM(srm);
+      }
+      return srm;
+    });
+    return { uuid, data: srms };
+  });
+}
+
+export default createActions({
+  CHALLENGE_LISTING: {
+    DROP_CHALLENGES: bucket => ({ bucket }),
+
+    GET_MORE_CHALLENGES: bucket => ({ bucket }),
+
+    GET_ALL_ACTIVE_CHALLENGES_INIT: getAllActiveChallengesInit,
+    GET_ALL_ACTIVE_CHALLENGES_DONE: getAllActiveChallengesDone,
+
+    GET_ACTIVE_CHALLENGES_INIT: getActiveChallengesInit,
+    GET_ACTIVE_CHALLENGES_DONE: getActiveChallengesDone,
+
+    GET_REST_ACTIVE_CHALLENGES_INIT: getRestActiveChallengesInit,
+    GET_REST_ACTIVE_CHALLENGES_DONE: getRestActiveChallengesDone,
+
+    GET_CHALLENGE_SUBTRACKS_INIT: _.noop,
+    GET_CHALLENGE_SUBTRACKS_DONE: getChallengeSubtracksDone,
+
+    GET_CHALLENGE_TAGS_INIT: _.noop,
+    GET_CHALLENGE_TAGS_DONE: getChallengeTagsDone,
+
+    GET_PAST_CHALLENGES_INIT: getPastChallengesInit,
+    GET_PAST_CHALLENGES_DONE: getPastChallengesDone,
+
+    GET_REVIEW_OPPORTUNITIES_INIT: (uuid, page, sort) => ({ uuid, page, sort }),
+    GET_REVIEW_OPPORTUNITIES_DONE: getReviewOpportunitiesDone,
+
+    GET_SRMS_INIT: getSrmsInit,
+    GET_SRMS_DONE: getSrmsDone,
+
+    EXPAND_TAG: id => id,
+
+    /* Pass in community ID. */
+    SELECT_COMMUNITY: _.identity,
+
+    SET_FILTER: _.identity,
+
+    SET_DATEPICKER_STATUS: status => ({ status }),
+
+    SET_SORT: (bucket, sort) => ({ bucket, sort }),
+  },
+});
diff --git a/src/actions/index.js b/src/actions/index.js
index 8b1a241b..db1d6f66 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -13,6 +13,7 @@ import reviewOpportunityActions from './reviewOpportunity';
 import lookupActions from './lookup';
 import settingsActions from './settings';
 import lookerActions from './looker';
+import challengeListingActions from './challenge-listing';
 
 export const actions = {
   auth: authActions.auth,
@@ -30,6 +31,7 @@ export const actions = {
   lookup: lookupActions.lookup,
   settings: settingsActions.settings,
   looker: lookerActions.looker,
+  challengeListing: challengeListingActions.challengeListing,
 };
 
 export default undefined;
diff --git a/src/config/index.js b/src/config/index.js
new file mode 100644
index 00000000..2a5ab790
--- /dev/null
+++ b/src/config/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+  PAGE_SIZE: 50,
+  REVIEW_OPPORTUNITY_PAGE_SIZE: 1000,
+};
diff --git a/src/index.js b/src/index.js
index a108716a..15b0e7d7 100644
--- a/src/index.js
+++ b/src/index.js
@@ -12,5 +12,5 @@ export { actions } from './actions';
 export { services } from './services';
 
 export {
-  challenge, logger, errors, tc, time, mock,
+  challenge, logger, errors, tc, time, mock, url,
 } from './utils';
diff --git a/src/reducers/challenge-listing.js b/src/reducers/challenge-listing.js
new file mode 100644
index 00000000..b1d87967
--- /dev/null
+++ b/src/reducers/challenge-listing.js
@@ -0,0 +1,848 @@
+/**
+ * Reducer for state.challengeListing.
+ */
+
+import _ from 'lodash';
+import { handleActions } from 'redux-actions';
+import moment from 'moment';
+import { updateQuery } from '../utils/url';
+import { SORTS_DATA } from '../utils/challenge/sort';
+import actions from '../actions/challenge-listing';
+import { logger, errors, challenge as challengeUtils } from '../utils';
+
+const { fireErrorMessage } = errors;
+const { filter: Filter } = challengeUtils;
+const { BUCKETS, BUCKET_DATA, getBuckets } = challengeUtils.buckets;
+
+/**
+ * Process challenge data for bucket
+ * @param handle user handle
+ * @param challenges all challenges
+ * @param loaded fetched challenges of bucket
+ * @param bucket bucket name
+ * @param sorts all sorts data
+ * @param sort sort name
+ * @param filter filter object
+ * @param frontFilter filter object
+ */
+function processBucketData(handle, challenges, loaded, bucket, sorts, sort, filter, frontFilter) {
+  const buckets = _.isEmpty(handle) ? BUCKET_DATA : getBuckets(handle);
+  const data = _.has(challenges, bucket) ? challenges[bucket]
+    .filter(filter)
+    .concat(loaded) : _.clone(loaded);
+
+  const finalFilters = {
+    ...frontFilter,
+    ...buckets[bucket].filter,
+  };
+
+  const bucketFilter = bucket !== BUCKETS.REVIEW_OPPORTUNITIES
+    ? Filter.getFilterFunction(finalFilters)
+    : Filter.getReviewOpportunitiesFilterFunction(finalFilters);
+  const filteredData = [];
+  for (let i = 0; i < data.length; i += 1) {
+    if (bucketFilter(data[i])) {
+      filteredData.push(data[i]);
+    }
+  }
+
+  if (bucket !== BUCKETS.ALL) {
+    if (!_.isEmpty(sort)) {
+      filteredData.sort(SORTS_DATA[sort].func);
+      return filteredData;
+    }
+
+    if (_.has(sorts, bucket)) {
+      filteredData.sort(SORTS_DATA[sorts[bucket]].func);
+    } else {
+      filteredData.sort(SORTS_DATA[BUCKET_DATA[bucket].sorts[0]].func);
+    }
+  }
+
+  return filteredData;
+}
+
+/**
+ * Check the challenges of bucket have been loaded all
+ * @param challenges all challenges
+ * @param bucket bucket name
+ * @param loaded loaded challenges this time
+ * @param data processed data
+ * @returns {boolean}
+ */
+function checkAllLoaded(challenges, bucket, loaded, data) {
+  let isAll = false;
+  if (loaded.length === 0) {
+    isAll = true;
+  } else if (!_.isEmpty(_.get(challenges, bucket))
+    && challenges[bucket].length === data.length) {
+    isAll = true;
+  }
+
+  return isAll;
+}
+
+/** TODO: Inspect if the 2 actions bellow can be removed?
+ * They do  duplicate what is done in `getActiveChallengesDone` but fetch all challenges
+ * which was refactored in listing-improve
+ */
+function onGetAllActiveChallengesInit(state, { payload }) {
+  return { ...state, loadingActiveChallengesUUID: payload };
+}
+function onGetAllActiveChallengesDone(state, { error, payload }) {
+  if (error) {
+    logger.error(payload);
+    return state;
+  }
+  const { uuid, challenges: loaded } = payload;
+  if (uuid !== state.loadingActiveChallengesUUID) return state;
+  /* Once all active challenges are fetched from the API, we remove from the
+   * store any active challenges stored there previously, and also any
+   * challenges with IDs matching any challenges loaded now as active. */
+  const ids = new Set();
+  loaded.forEach(item => ids.add(item.id));
+  const challenges = state.challenges
+    .filter(item => item.status !== 'ACTIVE' && !ids.has(item.id))
+    .concat(loaded);
+
+  return {
+    ...state,
+    challenges,
+    lastUpdateOfActiveChallenges: Date.now(),
+    loadingActiveChallengesUUID: '',
+  };
+}
+
+/**
+ * Called when 1st page of ative challenges is loaded from `/challenges` api
+ * @param {*} state
+ * @param {*} param1
+ */
+function onGetActiveChallengesDone(state, { error, payload }) {
+  if (error) {
+    logger.error(payload);
+    return state;
+  }
+  const {
+    uuid, challenges: loaded, sort, bucket, tokenV3, handle, frontFilter,
+    meta,
+  } = payload;
+
+  /* Once all active challenges are fetched from the API, we remove from the
+   * store any active challenges stored there previously, and also any
+   * challenges with IDs matching any challenges loaded now as active. */
+  const ids = new Set();
+  loaded.forEach(item => ids.add(item.id));
+
+  let filter;
+  let newChallenges = {};
+  const otherState = {};
+  switch (bucket) {
+    case BUCKETS.ALL: {
+      if (uuid !== state.loadingActiveChallengesUUID) return state;
+      /* Fetching 0 page of active challenges also drops any active challenges
+ * loaded to the state before. */
+      filter = state.lastRequestedPageOfActiveChallenges
+        ? item => !ids.has(item.id)
+        : item => !ids.has(item.id) && item.status !== 'ACTIVE';
+
+      // my
+      const my = !_.isEmpty(tokenV3) ? processBucketData(
+        handle, state.challenges, loaded, BUCKETS.MY, state.sorts, sort, filter,
+      ) : [];
+      // open for registration
+      const open = processBucketData(
+        handle, state.challenges, loaded, BUCKETS.OPEN_FOR_REGISTRATION, state.sorts, sort, filter,
+      );
+      // ongoing
+      const ongoing = processBucketData(
+        handle, state.challenges, loaded, BUCKETS.ONGOING, state.sorts, sort, filter,
+      );
+      newChallenges = _.clone(state.challenges);
+      newChallenges[BUCKETS.MY] = my;
+      newChallenges[BUCKETS.OPEN_FOR_REGISTRATION] = open;
+      newChallenges[BUCKETS.ONGOING] = ongoing;
+      otherState.loadingActiveChallengesUUID = '';
+      otherState.meta = _.clone(meta);
+    }
+      break;
+    case BUCKETS.MY: {
+      if (uuid !== state.loadingMyChallengesUUID) return state;
+      /* Fetching 0 page of active challenges also drops any active challenges
+ * loaded to the state before. */
+      filter = state.lastRequestedPageOfMyChallenges
+        ? item => !ids.has(item.id)
+        : item => !ids.has(item.id) && item.status !== 'ACTIVE';
+
+      const data = processBucketData(
+        handle, state.challenges, loaded, bucket, state.sorts, sort, filter,
+      );
+      newChallenges = _.cloneDeep(state.challenges);
+      newChallenges[bucket] = data;
+      otherState.loadingMyChallengesUUID = '';
+      otherState.allMyChallengesLoaded = checkAllLoaded(state.challenges, bucket, loaded, data);
+      otherState.gettingMoreMyChallenges = !otherState.allMyChallengesLoaded;
+      otherState.meta = _.clone(meta);
+      /* TODO Due to the meta of backend response is currently not correct,
+/* so should update counts after fetch all challenges of bucket */
+      if (_.get(meta, 'myChallengesCount') !== data.length && otherState.allMyChallengesLoaded) {
+        otherState.meta.myChallengesCount = data.length;
+        otherState.meta.allChallengesCount = meta.allChallengesCount
+          + data.length - meta.myChallengesCount;
+      }
+    }
+      break;
+    case BUCKETS.OPEN_FOR_REGISTRATION: {
+      if (uuid !== state.loadingOpenChallengesUUID) return state;
+      /* Fetching 0 page of active challenges also drops any active challenges
+ * loaded to the state before. */
+      filter = state.lastRequestedPageOfOpenChallenges
+        ? item => !ids.has(item.id)
+        : item => !ids.has(item.id) && item.status !== 'ACTIVE';
+
+      const data = processBucketData(
+        handle, state.challenges, loaded, bucket, state.sorts, sort, filter,
+      );
+
+      newChallenges = _.cloneDeep(state.challenges);
+      newChallenges[bucket] = data;
+      otherState.loadingOpenChallengesUUID = '';
+      otherState.allOpenChallengesLoaded = checkAllLoaded(state.challenges, bucket, loaded, data);
+      otherState.gettingMoreOpenChallenges = !otherState.allOpenChallengesLoaded;
+      otherState.meta = _.clone(meta);
+      /* TODO Due to the meta of backend response is currently not correct,
+      /* so should update counts after fetch all challenges of bucket */
+      if (_.get(meta, 'openChallengesCount') !== data.length && otherState.allOpenChallengesLoaded) {
+        otherState.meta.openChallengesCount = data.length;
+        otherState.meta.allChallengesCount = meta.allChallengesCount
+          + data.length - meta.openChallengesCount;
+      }
+    }
+      break;
+    case BUCKETS.ONGOING: {
+      if (uuid !== state.loadingOnGoingChallengesUUID) return state;
+      /* Fetching 0 page of active challenges also drops any active challenges
+ * loaded to the state before. */
+      filter = state.lastRequestedPageOfOnGoingChallenges
+        ? item => !ids.has(item.id)
+        : item => !ids.has(item.id) && item.status !== 'ACTIVE';
+
+      const data = processBucketData(
+        handle, state.challenges, loaded, bucket, state.sorts, sort, filter,
+      );
+      newChallenges = _.cloneDeep(state.challenges);
+      newChallenges[bucket] = data;
+      otherState.loadingOnGoingChallengesUUID = '';
+      otherState.allOnGoingChallengesLoaded = checkAllLoaded(state.challenges,
+        bucket, loaded, data);
+      otherState.gettingMoreOnGoingChallenges = !otherState.allOnGoingChallengesLoaded;
+      /* TODO Due to the meta of backend response is currently not correct,
+      /* so should update counts after fetch all challenges of bucket */
+      otherState.meta = _.clone(meta);
+      if (_.get(meta, 'ongoingChallengesCount') !== data.length && otherState.allOnGoingChallengesLoaded) {
+        otherState.meta.ongoingChallengesCount = data.length;
+        otherState.meta.allChallengesCount = meta.allChallengesCount
+          + data.length - meta.ongoingChallengesCount;
+      }
+    }
+      break;
+    default:
+      break;
+  }
+
+  // all challenges used for other components like sub communities
+  newChallenges[BUCKETS.ALL] = processBucketData(
+    handle, state.challenges, loaded, BUCKETS.ALL, null, null, filter, frontFilter,
+  );
+
+  return {
+    ...state,
+    ...otherState,
+    challenges: newChallenges,
+    lastUpdateOfActiveChallenges: Date.now(),
+  };
+}
+
+/**
+ * Called when loading of 1st page of active challenges is started
+ * @param {*} state
+ * @param {*} param1
+ */
+function onGetActiveChallengesInit(state, { payload }) {
+  const { page, bucket, uuid } = payload;
+  const otherState = {};
+  switch (bucket) {
+    case BUCKETS.ALL:
+      otherState.loadingActiveChallengesUUID = uuid;
+      otherState.lastRequestedPageOfActiveChallenges = page;
+      break;
+    case BUCKETS.MY:
+      otherState.loadingMyChallengesUUID = uuid;
+      otherState.lastRequestedPageOfMyChallenges = page;
+      break;
+    case BUCKETS.OPEN_FOR_REGISTRATION:
+      otherState.loadingOpenChallengesUUID = uuid;
+      otherState.lastRequestedPageOfOpenChallenges = page;
+      break;
+    case BUCKETS.ONGOING:
+      otherState.loadingOnGoingChallengesUUID = uuid;
+      otherState.lastRequestedPageOfOnGoingChallenges = page;
+      break;
+    default:
+      break;
+  }
+
+  return {
+    ...state,
+    ...otherState,
+  };
+}
+function onGetRestActiveChallengesInit(state, { payload }) {
+  return {
+    ...state,
+    loadingRestActiveChallengesUUID: payload.uuid,
+  };
+}
+
+/**
+ * Called when all challenges are loaded
+ * @param {*} state
+ * @param {*} param1
+ */
+function onGetRestActiveChallengesDone(state, { error, payload }) {
+  if (error) {
+    logger.error(payload);
+    return state;
+  }
+  const {
+    uuid, challenges: loaded, meta: newMeta, sort, bucket, handle, frontFilter,
+  } = payload;
+  if (uuid !== state.loadingRestActiveChallengesUUID) return state;
+
+  /* Once all active challenges are fetched from the API, we remove from the
+   * store any active challenges stored there previously, and also any
+   * challenges with IDs matching any challenges loaded now as active. */
+  const ids = new Set();
+  loaded.forEach(item => ids.add(item.id));
+
+  /* Fetching 0 page of active challenges also drops any active challenges
+   * loaded to the state before. */
+  const filter = item => !ids.has(item.id);
+
+  const otherState = {};
+  let newChallenges = {};
+  switch (bucket) {
+    case BUCKETS.MY:
+    case BUCKETS.OPEN_FOR_REGISTRATION:
+    case BUCKETS.ONGOING: {
+      const data = processBucketData(
+        handle, state.challenges, loaded, bucket, state.sorts, sort, filter, frontFilter,
+      );
+      newChallenges = _.cloneDeep(state.challenges);
+      newChallenges[bucket] = data;
+      switch (bucket) {
+        case BUCKETS.MY:
+          otherState.allMyChallengesLoaded = true;
+          otherState.gettingMoreMyChallenges = false;
+          break;
+        case BUCKETS.OPEN_FOR_REGISTRATION:
+          otherState.allOpenChallengesLoaded = true;
+          otherState.gettingMoreOpenChallenges = false;
+          break;
+        case BUCKETS.ONGOING:
+          otherState.allOnGoingChallengesLoaded = true;
+          otherState.gettingMoreOnGoingChallenges = false;
+          break;
+        default:
+          break;
+      }
+    }
+      break;
+    default:
+      break;
+  }
+
+  const meta = newMeta || state.meta;
+
+  return {
+    ...state,
+    challenges: newChallenges,
+    ...otherState,
+    meta,
+    lastUpdateOfActiveChallenges: Date.now(),
+    lastRequestedPageOfActiveChallenges: -1,
+    loadingRestActiveChallengesUUID: '',
+  };
+}
+
+/**
+ * Handles CHALLENGE_LISTING/GET_CHALLENGE_SUBTRACKS_DONE action.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object}
+ */
+function onGetChallengeSubtracksDone(state, action) {
+  if (action.error) logger.error(action.payload);
+  return {
+    ...state,
+    challengeSubtracks: action.error ? [] : action.payload,
+    challengeSubtracksMap: action.error ? {} : _.keyBy(action.payload, 'subTrack'),
+    loadingChallengeSubtracks: false,
+  };
+}
+
+/**
+ * Handles CHALLENGE_LISTING/GET_CHALLENGE_TAGS_DONE action.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object}
+ */
+function onGetChallengeTagsDone(state, action) {
+  if (action.error) logger.error(action.payload);
+  return {
+    ...state,
+    challengeTags: action.error ? [] : action.payload,
+    loadingChallengeTags: false,
+  };
+}
+
+function onGetPastChallengesInit(state, action) {
+  const { frontFilter, page, uuid } = action.payload;
+  const tracks = frontFilter && frontFilter.tracks;
+  if (tracks && _.isEmpty(tracks)) {
+    return {
+      ...state,
+      allPastChallengesLoaded: true,
+      loadingPastChallengesUUID: '',
+    };
+  }
+
+  return {
+    ...state,
+    lastRequestedPageOfPastChallenges: page,
+    loadingPastChallengesUUID: uuid,
+  };
+}
+
+function onGetPastChallengesDone(state, { error, payload }) {
+  if (error) {
+    logger.error(payload);
+    return state;
+  }
+  const {
+    uuid, challenges: loaded, frontFilter, sort,
+  } = payload;
+  if (uuid !== state.loadingPastChallengesUUID) return state;
+
+  const ids = new Set();
+  loaded.forEach(item => ids.add(item.id));
+
+  /* Fetching 0 page of past challenges also drops any past challenges
+   * loaded to the state before. */
+  const filter = state.lastRequestedPageOfPastChallenges
+    ? item => !ids.has(item.id)
+    : item => !ids.has(item.id) && item.status !== 'COMPLETED' && item.status !== 'PAST';
+
+  const pasts = processBucketData(
+    null, state.challenges, loaded, BUCKETS.PAST, state.sorts, sort, filter, frontFilter,
+  );
+
+  let keepPastPlaceholders = false;
+  if (loaded.length) {
+    const ff = Filter.getFilterFunction(frontFilter);
+    keepPastPlaceholders = pasts.filter(ff).length
+      - (_.has(state.challenges, BUCKETS.PAST)
+        ? state.challenges[BUCKETS.PAST].filter(ff).length : 0) < 10;
+  }
+
+  const newChallenges = _.cloneDeep(state.challenges);
+  newChallenges[BUCKETS.PAST] = pasts;
+
+  return {
+    ...state,
+    allPastChallengesLoaded: loaded.length === 0,
+    challenges: newChallenges,
+    keepPastPlaceholders,
+    loadingPastChallengesUUID: '',
+  };
+}
+
+function onSelectCommunity(state, { payload }) {
+  updateQuery({ communityId: payload || undefined });
+  return {
+    ...state,
+    selectedCommunityId: payload,
+
+    /* Page numbers of past/upcoming challenges depend on the filters. To keep
+      * the code simple we just reset them each time a filter is modified.
+      * (This community selection defines community-specific filter for
+      * challenges). */
+    allPastChallengesLoaded: false,
+    lastRequestedPageOfPastChallenges: -1,
+  };
+}
+
+/**
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object}
+ */
+function onSetFilter(state, { payload }) {
+  /* Validation of filter parameters: they may come from URL query, thus
+   * validation is not a bad idea. As you may note, at the moment we do not
+   * do it very carefully (many params are not validated). */
+  const filter = _.clone(payload);
+  if (_.isPlainObject(filter.tags)) {
+    filter.tags = _.values(filter.tags);
+  }
+  if (_.isPlainObject(filter.subtracks)) {
+    filter.subtracks = _.values(filter.subtracks);
+  }
+  if (filter.startDate && !moment(filter.startDate).isValid()) {
+    delete filter.startDate;
+  }
+  if (filter.endDate && !moment(filter.endDate).isValid()) {
+    delete filter.endDate;
+  }
+
+  /* Update of URL and generation of the state. */
+  updateQuery({ filter });
+  return {
+    ...state,
+    filter,
+
+    /* Page numbers of past/upcoming challenges depend on the filters. To keep
+     * the code simple we just reset them each time a filter is modified. */
+    allPastChallengesLoaded: false,
+    lastRequestedPageOfPastChallenges: -1,
+  };
+}
+
+/**
+ * Handles CHALLENGE_LISTING/GET_REVIEW_OPPORTUNITIES_INIT action.
+ * @param {Object} state
+ * @param {Object} action Payload will be page, uuid
+ * @return {Object} New state
+ */
+function onGetReviewOpportunitiesInit(state, { payload }) {
+  return {
+    ...state,
+    lastRequestedPageOfReviewOpportunities: payload.page,
+    loadingReviewOpportunitiesUUID: payload.uuid,
+  };
+}
+
+/**
+ * Handles CHALLENGE_LISTING/GET_REVIEW_OPPORTUNITIES_DONE action.
+ * @param {Object} state
+ * @param {Object} action Payload will be JSON from api call and UUID
+ * @return {Object} New state
+ */
+function onGetReviewOpportunitiesDone(state, { payload, error }) {
+  if (error) {
+    return state;
+  }
+
+  const {
+    uuid,
+    loaded,
+    sort,
+    frontFilter,
+  } = payload;
+
+  if (uuid !== state.loadingReviewOpportunitiesUUID) return state;
+
+  const ids = new Set();
+  loaded.forEach(item => ids.add(item.id));
+
+  const filter = item => !ids.has(item.id);
+
+  const reviewOpportunities = processBucketData(
+    null, state, loaded, BUCKETS.REVIEW_OPPORTUNITIES,
+    state.sorts, sort, filter, frontFilter,
+  );
+
+  return {
+    ...state,
+    reviewOpportunities,
+    loadingReviewOpportunitiesUUID: '',
+    allReviewOpportunitiesLoaded: loaded.length === 0,
+  };
+}
+
+/**
+ * Inits the loading of SRMs.
+ * @param {Object} state
+ * @param {String} payload Operation UUID.
+ * @return {Object} New state.
+ */
+function onGetSrmsInit(state, { payload }) {
+  return {
+    ...state,
+    srms: {
+      ...state.srms,
+      loadingUuid: payload,
+    },
+  };
+}
+
+/**
+ * Handles loaded SRMs.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onGetSrmsDone(state, { error, payload }) {
+  if (error) {
+    logger.error('Failed to load SRMs', payload);
+    fireErrorMessage('Failed to load SRMs', '');
+    return state;
+  }
+
+  const { uuid, data } = payload;
+  if (state.srms.loadingUuid !== uuid) return state;
+  return {
+    ...state,
+    srms: {
+      data,
+      loadingUuid: '',
+      timestamp: Date.now(),
+    },
+  };
+}
+
+/**
+ * Creates a new Challenge Listing reducer with the specified initial state.
+ * @param {Object} initialState Optional. Initial state.
+ * @return Challenge Listing reducer.
+ */
+function create(initialState) {
+  const a = actions.challengeListing;
+  return handleActions({
+    [a.dropChallenges]: (state, { payload }) => {
+      const { bucket } = payload;
+      const otherState = {};
+      switch (bucket) {
+        case BUCKETS.REVIEW_OPPORTUNITIES:
+          otherState.lastRequestedPageOfReviewOpportunities = -1;
+          otherState.reviewOpportunities = [];
+          otherState.allReviewOpportunitiesLoaded = false;
+          break;
+        case BUCKETS.PAST:
+          otherState.challenges = _.cloneDeep(state.challenges);
+          otherState.lastRequestedPageOfPastChallenges = -1;
+          otherState.challenges.past = [];
+          otherState.allPastChallengesLoaded = false;
+          break;
+        default:
+          otherState.challenges = {};
+          otherState.allMyChallengesLoaded = false;
+          otherState.allOnGoingChallengesLoaded = false;
+          otherState.allOpenChallengesLoaded = false;
+          otherState.allActiveChallengesLoaded = false;
+          otherState.allPastChallengesLoaded = false;
+          otherState.allReviewOpportunitiesLoaded = false;
+          otherState.lastRequestedPageOfActiveChallenges = -1;
+          otherState.lastRequestedPageOfMyChallenges = -1;
+          otherState.lastRequestedPageOfOpenChallenges = -1;
+          otherState.lastRequestedPageOfOnGoingChallenges = -1;
+          otherState.lastRequestedPageOfPastChallenges = -1;
+          otherState.lastRequestedPageOfReviewOpportunities = -1;
+          otherState.lastUpdateOfActiveChallenges = -1;
+          otherState.loadingActiveChallengesUUID = '';
+          otherState.loadingMyChallengesUUID = '';
+          otherState.loadingOpenChallengesUUID = '';
+          otherState.loadingOnGoingChallengesUUID = '';
+          otherState.loadingRestActiveChallengesUUID = '';
+          otherState.loadingPastChallengesUUID = '';
+          otherState.loadingReviewOpportunitiesUUID = '';
+          otherState.reviewOpportunities = [];
+          otherState.meta = {
+            allChallengesCount: 0,
+            myChallengesCount: 0,
+            ongoingChallengesCount: 0,
+            openChallengesCount: 0,
+            totalCount: 0,
+          };
+          break;
+      }
+
+      return ({
+        ...state,
+        ...otherState,
+      });
+    },
+
+    [a.getMoreChallenges]: (state, { payload }) => {
+      const { bucket } = payload;
+      const otherState = {};
+      switch (bucket) {
+        case BUCKETS.MY:
+          otherState.gettingMoreMyChallenges = true;
+          break;
+        case BUCKETS.ONGOING:
+          otherState.gettingMoreOnGoingChallenges = true;
+          break;
+        case BUCKETS.OPEN_FOR_REGISTRATION:
+          otherState.gettingMoreOpenChallenges = true;
+          break;
+        default:
+          break;
+      }
+      return ({
+        ...state,
+        ...otherState,
+      });
+    },
+
+    [a.expandTag]: (state, { payload }) => ({
+      ...state,
+      expandedTags: [...state.expandedTags, payload],
+    }),
+
+    [a.getAllActiveChallengesInit]: onGetAllActiveChallengesInit,
+    [a.getAllActiveChallengesDone]: onGetAllActiveChallengesDone,
+
+    [a.getActiveChallengesInit]: onGetActiveChallengesInit,
+    [a.getActiveChallengesDone]: onGetActiveChallengesDone,
+
+    [a.getRestActiveChallengesInit]: onGetRestActiveChallengesInit,
+    [a.getRestActiveChallengesDone]: onGetRestActiveChallengesDone,
+
+    [a.getChallengeSubtracksInit]: state => ({
+      ...state,
+      loadingChallengeSubtracks: true,
+    }),
+    [a.getChallengeSubtracksDone]: onGetChallengeSubtracksDone,
+
+    [a.getChallengeTagsInit]: state => ({
+      ...state,
+      loadingChallengeTags: true,
+    }),
+    [a.getChallengeTagsDone]: onGetChallengeTagsDone,
+
+    [a.getPastChallengesInit]: onGetPastChallengesInit,
+    [a.getPastChallengesDone]: onGetPastChallengesDone,
+
+    [a.getReviewOpportunitiesInit]: onGetReviewOpportunitiesInit,
+    [a.getReviewOpportunitiesDone]: onGetReviewOpportunitiesDone,
+
+    [a.getSrmsInit]: onGetSrmsInit,
+    [a.getSrmsDone]: onGetSrmsDone,
+
+    [a.selectCommunity]: onSelectCommunity,
+
+    [a.setFilter]: onSetFilter,
+    [a.setSort]: (state, { payload }) => {
+      const otherState = {};
+      switch (payload.bucket) {
+        case BUCKETS.PAST:
+          otherState.lastRequestedPageOfPastChallenges = -1;
+          break;
+        case BUCKETS.MY:
+        case BUCKETS.OPEN_FOR_REGISTRATION:
+        case BUCKETS.ONGOING:
+          otherState.lastRequestedPageOfActiveChallenges = -1;
+          break;
+        case BUCKETS.REVIEW_OPPORTUNITIES:
+          otherState.lastRequestedPageOfReviewOpportunities = -1;
+          break;
+        default:
+          break;
+      }
+      return ({
+        ...state,
+        ...otherState,
+        sorts: {
+          ...state.sorts,
+          [payload.bucket]: payload.sort,
+        },
+      });
+    },
+
+    [a.setDatePickerStatus]: (state, { payload }) => {
+      const { status } = payload;
+      return ({
+        ...state,
+        datepickerOpen: status,
+      });
+    },
+  }, _.defaults(_.clone(initialState) || {}, {
+    allMyChallengesLoaded: false,
+    allOnGoingChallengesLoaded: false,
+    allOpenChallengesLoaded: false,
+    allActiveChallengesLoaded: false,
+    allPastChallengesLoaded: false,
+    allReviewOpportunitiesLoaded: false,
+
+    challenges: {},
+    challengeSubtracks: [],
+    challengeSubtracksMap: {},
+    challengeTags: [],
+
+    expandedTags: [],
+
+    gettingMoreChallenges: false,
+    gettingMoreMyChallenges: false,
+    gettingMoreOnGoingChallenges: false,
+    gettingMoreOpenChallenges: false,
+
+    filter: {},
+
+    keepPastPlaceholders: false,
+
+    lastRequestedPageOfActiveChallenges: -1,
+    lastRequestedPageOfMyChallenges: -1,
+    lastRequestedPageOfOnGoingChallenges: -1,
+    lastRequestedPageOfOpenChallenges: -1,
+    lastRequestedPageOfPastChallenges: -1,
+    lastRequestedPageOfReviewOpportunities: -1,
+    lastUpdateOfActiveChallenges: 0,
+
+    loadingActiveChallengesUUID: '',
+    loadingMyChallengesUUID: '',
+    loadingOnGoingChallengesUUID: '',
+    loadingOpenChallengesUUID: '',
+    loadingRestActiveChallengesUUID: '',
+    loadingPastChallengesUUID: '',
+    loadingReviewOpportunitiesUUID: '',
+
+    loadingChallengeSubtracks: false,
+    loadingChallengeTags: false,
+
+    reviewOpportunities: [],
+
+    selectedCommunityId: '',
+
+    sorts: {},
+
+    srms: {
+      data: [],
+      loadingUuid: '',
+      timestamp: 0,
+    },
+
+    meta: {
+      allChallengesCount: 0,
+      myChallengesCount: 0,
+      ongoingChallengesCount: 0,
+      openChallengesCount: 0,
+      totalCount: 0,
+    },
+
+    datepickerOpen: false,
+  }));
+}
+
+/**
+ * The factory creates the new reducer with initial state tailored to the given
+ * ExpressJS HTTP request, if specified (for server-side rendering). If no HTTP
+ * request is specified, it creates the default reducer.
+ * @return {Promise} Resolves to the new reducer.
+ */
+export function factory() {
+  return Promise.resolve(create());
+}
+
+/* Default reducer with empty initial state. */
+export default create();
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 15fd144c..aee34c61 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -22,6 +22,7 @@ import settings, { factory as settingsFactory }
   from './settings';
 import looker, { factory as lookerFactory }
   from './looker';
+import challengeListing, { factory as challengeListingFactory } from './challenge-listing';
 
 
 export function factory(options) {
@@ -41,6 +42,7 @@ export function factory(options) {
     mySubmissionsManagement: mySubmissionsManagementFactory(options),
     settings: settingsFactory(options),
     looker: lookerFactory(options),
+    challengeListing: challengeListingFactory(options),
   });
 }
 
@@ -60,4 +62,5 @@ export default ({
   mySubmissionsManagement,
   settings,
   looker,
+  challengeListing,
 });
diff --git a/src/services/challenges.js b/src/services/challenges.js
index 604a2879..04244726 100644
--- a/src/services/challenges.js
+++ b/src/services/challenges.js
@@ -244,10 +244,10 @@ class ChallengesService {
       params = {},
     ) => {
       const query = {
-        filter: qs.stringify(filters, { encode: false }),
+        filter: qs.stringify(filters, { encode: false }).replace('&', '%26'),
         ...params,
       };
-      const url = `${endpoint}?${qs.stringify(query)}`;
+      const url = `${endpoint}?${qs.stringify(query, { encode: false })}`;
       const res = await this.private.api.get(url).then(checkError);
       return {
         challenges: res.content || [],
diff --git a/src/utils/challenge/buckets.js b/src/utils/challenge/buckets.js
new file mode 100644
index 00000000..d9e9221c
--- /dev/null
+++ b/src/utils/challenge/buckets.js
@@ -0,0 +1,153 @@
+/**
+ * Standard challenge buckets
+ */
+
+import _ from 'lodash';
+import { SORTS } from './sort';
+
+export const BUCKETS = {
+  ALL: 'all',
+  MY: 'my',
+  OPEN_FOR_REGISTRATION: 'openForRegistration',
+  ONGOING: 'ongoing',
+  PAST: 'past',
+  SAVED_FILTER: 'saved-filter',
+  UPCOMING: 'upcoming',
+  REVIEW_OPPORTUNITIES: 'reviewOpportunities',
+  SAVED_REVIEW_OPPORTUNITIES_FILTER: 'savedReviewOpportunitiesFilter',
+};
+
+export const BUCKET_DATA = {
+  [BUCKETS.ALL]: {
+    filter: {
+      started: true,
+      status: ['ACTIVE'],
+    },
+    hideCount: false,
+    name: 'All Challenges',
+    sorts: [],
+  },
+  [BUCKETS.MY]: {
+    filter: {
+      started: true,
+      status: ['ACTIVE'],
+    },
+    hideCount: false,
+    name: 'My Challenges',
+    sorts: [
+      SORTS.MOST_RECENT,
+      SORTS.TIME_TO_SUBMIT,
+      SORTS.NUM_REGISTRANTS,
+      SORTS.NUM_SUBMISSIONS,
+      SORTS.PRIZE_HIGH_TO_LOW,
+      SORTS.TITLE_A_TO_Z,
+    ],
+  },
+  [BUCKETS.OPEN_FOR_REGISTRATION]: {
+    filter: {
+      registrationOpen: true,
+      started: true,
+      status: ['ACTIVE'],
+    },
+    hideCount: false,
+    name: 'Open for registration',
+    sorts: [
+      SORTS.MOST_RECENT,
+      SORTS.TIME_TO_REGISTER,
+      SORTS.TIME_TO_SUBMIT,
+      SORTS.NUM_REGISTRANTS,
+      SORTS.NUM_SUBMISSIONS,
+      SORTS.PRIZE_HIGH_TO_LOW,
+      SORTS.TITLE_A_TO_Z,
+    ],
+  },
+  [BUCKETS.ONGOING]: {
+    filter: {
+      registrationOpen: false,
+      started: true,
+      status: ['ACTIVE'],
+    },
+    hideCount: false,
+    name: 'Ongoing challenges',
+    sorts: [
+      SORTS.MOST_RECENT,
+      SORTS.CURRENT_PHASE,
+      SORTS.TITLE_A_TO_Z,
+      SORTS.PRIZE_HIGH_TO_LOW,
+    ],
+  },
+  [BUCKETS.UPCOMING]: {
+    filter: {
+      upcoming: true,
+    },
+    hideCount: true,
+    name: 'Upcoming challenges',
+    sorts: [
+      SORTS.MOST_RECENT,
+      SORTS.PRIZE_HIGH_TO_LOW,
+      SORTS.TITLE_A_TO_Z,
+    ],
+  },
+  [BUCKETS.PAST]: {
+    filter: { status: ['COMPLETED', 'PAST'] },
+    hideCount: true,
+    name: 'Past challenges',
+    sorts: [
+      SORTS.MOST_RECENT,
+      SORTS.PRIZE_HIGH_TO_LOW,
+      SORTS.TITLE_A_TO_Z,
+    ],
+  },
+  [BUCKETS.REVIEW_OPPORTUNITIES]: {
+    filter: {},
+    hideCount: true,
+    name: 'Open for review',
+    sorts: [
+      SORTS.REVIEW_OPPORTUNITIES_START_DATE,
+      SORTS.REVIEW_OPPORTUNITIES_PAYMENT,
+      SORTS.REVIEW_OPPORTUNITIES_TITLE_A_TO_Z,
+    ],
+  },
+  [BUCKETS.SAVED_REVIEW_OPPORTUNITIES_FILTER]: {
+    filter: {},
+    sorts: [
+      SORTS.REVIEW_OPPORTUNITIES_START_DATE,
+      SORTS.REVIEW_OPPORTUNITIES_PAYMENT,
+      SORTS.REVIEW_OPPORTUNITIES_TITLE_A_TO_Z,
+    ],
+  },
+};
+
+/**
+ * Returns configuration of all possible challenge buckets.
+ * @param {String} userHandle Handle of the authenticated
+ * user to filter out My Challenges.
+ */
+export function getBuckets(userHandle) {
+  const res = _.cloneDeep(BUCKET_DATA);
+  res[BUCKETS.MY].filter.users = [userHandle];
+  return res;
+}
+
+/**
+ * Tests if a given bucket is of any of the Review Opportunities types
+ * @param {String} bucket The bucket in question
+ * @return {Boolean} True if the bucket contains Review Opportunities
+ */
+export const isReviewOpportunitiesBucket = bucket => (
+  bucket === BUCKETS.REVIEW_OPPORTUNITIES || bucket === BUCKETS.SAVED_REVIEW_OPPORTUNITIES_FILTER);
+
+/**
+ * Registers a new bucket.
+ * @param {String} id
+ * @param {Object} bucket
+ */
+export function registerBucket(id, bucket) {
+  if (BUCKET_DATA[id]) {
+    throw new Error('Bucket ID clush with an existing bucket');
+  }
+  BUCKETS[id] = id;
+  BUCKET_DATA[id] = bucket;
+}
+
+export default undefined;
diff --git a/src/utils/challenge/filter.js b/src/utils/challenge/filter.js
index 28e00654..bdd343af 100644
--- a/src/utils/challenge/filter.js
+++ b/src/utils/challenge/filter.js
@@ -177,6 +177,39 @@ function filterByUsers(challenge, state) {
   return state.users.find(user => challenge.users[user]);
 }
 
+/**
+ * [filterByDate filter challenges by date reange]
+ * @param  {[type]} challenges input challenges
+ * @param  {[type]} filter     filter including startDate and endDate
+ * @return {[type]}            filtered challenges array
+ */
+export function filterByDate(challenges, filter) {
+  let cs = challenges.filter(c => filterByStartDate(c, filter));
+  cs = cs.filter(c => filterByEndDate(c, filter));
+  return cs;
+}
+
+/**
+ * [newMeta compute new meta via challenges and filter]
+ * @param  {[type]} meta       old meta
+ * @param  {[type]} challenges input challenges
+ * @param  {[type]} filter     filter including startDate and end endDate
+ * @return {[type]}            new meta
+ */
+export function newMeta(meta, challenges, filter) {
+  if (!filter.startDate && !filter.endDate) {
+    return meta;
+  }
+  const m = {
+  };
+  m.allChallengesCount = challenges.length;
+  m.openChallengesCount = challenges.filter(c => c.registrationOpen === 'Yes').length;
+  m.ongoingChallengesCount = m.allChallengesCount - m.openChallengesCount;
+  m.myChallengesCount = challenges.filter(c => c.user && !_.isEmpty(c.user)).length;
+  m.totalCount = challenges.length;
+  return m;
+}
+
 /**
  * Returns clone of the state with the specified competition track added.
  * @param {Object} state
diff --git a/src/utils/challenge/sort.js b/src/utils/challenge/sort.js
new file mode 100644
index 00000000..4091b0c0
--- /dev/null
+++ b/src/utils/challenge/sort.js
@@ -0,0 +1,84 @@
+/**
+ * Collection of compare function to sort challenges in different ways.
+ */
+
+import moment from 'moment';
+import { sumBy } from 'lodash';
+
+export const SORTS = {
+  CURRENT_PHASE: 'current-phase',
+  MOST_RECENT: 'most-recent',
+  NUM_REGISTRANTS: 'num-registrants',
+  NUM_SUBMISSIONS: 'num-submissions',
+  PRIZE_HIGH_TO_LOW: 'prize-high-to-low',
+  TIME_TO_REGISTER: 'time-to-register',
+  TIME_TO_SUBMIT: 'time-to-submit',
+  TITLE_A_TO_Z: 'title-a-to-z',
+  REVIEW_OPPORTUNITIES_TITLE_A_TO_Z: 'review-opportunities-title-a-to-z',
+  REVIEW_OPPORTUNITIES_PAYMENT: 'review-opportunities-payment',
+  REVIEW_OPPORTUNITIES_START_DATE: 'review-opportunities-start-date',
+};
+
+export const SORTS_DATA = {
+  [SORTS.CURRENT_PHASE]: {
+    func: (a, b) => a.status.localeCompare(b.status),
+    name: 'Current phase',
+  },
+  [SORTS.MOST_RECENT]: {
+    func: (a, b) => moment(b.registrationStartDate).diff(a.registrationStartDate),
+    name: 'Most recent',
+  },
+  [SORTS.NUM_REGISTRANTS]: {
+    func: (a, b) => b.numRegistrants - a.numRegistrants,
+    name: '# of registrants',
+  },
+  [SORTS.NUM_SUBMISSIONS]: {
+    func: (a, b) => b.numSubmissions - a.numSubmissions,
+    name: '# of submissions',
+  },
+  [SORTS.PRIZE_HIGH_TO_LOW]: {
+    func: (a, b) => b.totalPrize - a.totalPrize,
+    name: 'Prize high to low',
+  },
+  [SORTS.TIME_TO_REGISTER]: {
+    func: (a, b) => moment(a.registrationEndDate || a.submissionEndDate)
+      .diff(b.registrationEndDate || b.submissionEndDate),
+    name: 'Time to register',
+  },
+  [SORTS.TIME_TO_SUBMIT]: {
+    func: (a, b) => {
+      function nextSubEndDate(o) {
+        if (o.checkpointSubmissionEndDate && moment(o.checkpointSubmissionEndDate).isAfter()) {
+          return o.checkpointSubmissionEndDate;
+        }
+        return o.submissionEndDate;
+      }
+
+      const aDate = nextSubEndDate(a);
+      const bDate = nextSubEndDate(b);
+
+      if (moment(aDate).isBefore()) return 1;
+      if (moment(bDate).isBefore()) return -1;
+
+      return moment(aDate).diff(bDate);
+    },
+    name: 'Time to submit',
+  },
+  [SORTS.TITLE_A_TO_Z]: {
+    func: (a, b) => a.name.localeCompare(b.name),
+    name: 'Title A-Z',
+  },
+  [SORTS.REVIEW_OPPORTUNITIES_TITLE_A_TO_Z]: {
+    func: (a, b) => a.challenge.title.localeCompare(b.challenge.title),
+    name: 'Title A-Z',
+  },
+  [SORTS.REVIEW_OPPORTUNITIES_PAYMENT]: {
+    func: (a, b) => sumBy(b.payments, 'payment') - sumBy(a.payments, 'payment'),
+    name: 'Payment',
+  },
+  [SORTS.REVIEW_OPPORTUNITIES_START_DATE]: {
+    // This will implicitly use moment#valueOf
+    func: (a, b) => moment(a.startDate) - moment(b.startDate),
+    name: 'Review start date',
+  },
+};
diff --git a/src/utils/index.js b/src/utils/index.js
index b63d63b8..e7e4ff52 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -7,9 +7,14 @@ import * as time from './time';
 import * as mock from './mock';
 import * as errors from './errors';
 import * as filter from './challenge/filter';
+import * as buckets from './challenge/buckets';
+import * as sort from './challenge/sort';
+import * as url from './url';
 
 const challenge = {
   filter,
+  buckets,
+  sort,
 };
 
 export {
@@ -19,4 +24,5 @@ export {
   time,
   mock,
   errors,
+  url,
 };
diff --git a/src/utils/tc.js b/src/utils/tc.js
index 77fce85a..08309d16 100644
--- a/src/utils/tc.js
+++ b/src/utils/tc.js
@@ -4,6 +4,9 @@
  * @todo More TC-related utils should be moved here from Community-app.
  */
 
+import _ from 'lodash';
+import moment from 'moment';
+
 /**
  * Codes of the Topcoder communities.
  */
@@ -60,3 +63,47 @@ export async function getLookerApiResponsePayload(res) {
     status: x.status,
   };
 }
+
+/**
+ * process srm to populate additional infomation
+ * adopt from topcoder-app repo
+ * @param  {Object} s  srm to process
+ * @return {Object}    processed srm
+ */
+export function processSRM(s) {
+  const srm = _.cloneDeep(s);
+  srm.userStatus = 'registered';
+  if (Array.isArray(srm.rounds) && srm.rounds.length) {
+    if (srm.rounds[0].userSRMDetails && srm.rounds[0].userSRMDetails.rated) {
+      srm.result = srm.rounds[0].userSRMDetails;
+    }
+    if (srm.rounds[0].codingStartAt) {
+      srm.codingStartAt = srm.rounds[0].codingStartAt;
+    }
+    if (srm.rounds[0].codingEndAt) {
+      srm.codingEndAt = srm.rounds[0].codingEndAt;
+    }
+    if (srm.rounds[0].registrationStartAt) {
+      srm.registrationStartAt = srm.rounds[0].registrationStartAt;
+    }
+    if (srm.rounds[0].registrationEndAt) {
+      srm.registrationEndAt = srm.rounds[0].registrationEndAt;
+    }
+  }
+
+  // determines if the current phase is registration
+  let start = moment(srm.registrationStartAt).unix();
+  let end = moment(srm.registrationEndAt).unix();
+  let now = moment().unix();
+  if (start <= now && end >= now) {
+    srm.currentPhase = 'REGISTRATION';
+  }
+  // determines if the current phase is coding
+  start = moment(srm.codingStartAt).unix();
+  end = moment(srm.codingEndAt).unix();
+  now = moment().unix();
+  if (start <= now && end >= now) {
+    srm.currentPhase = 'CODING';
+  }
+  return srm;
+}
diff --git a/src/utils/url.js b/src/utils/url.js
new file mode 100644
index 00000000..73154aa7
--- /dev/null
+++ b/src/utils/url.js
@@ -0,0 +1,49 @@
+/**
+ * Various URL-related functions.
+ */
+
+/* global window */
+
+import _ from 'lodash';
+import qs from 'qs';
+import { isomorphy } from 'topcoder-react-utils';
+
+/**
+ * If executed client-side (determined in this case by the presence of global
+ * window object), this function updates query section of URL; otherwise does
+ * nothing.
+ * @param {Object} update Specifies the update to make. Current query will be
+ *  parsed into JS object, then update will be merged into that object, and the
+ *  result will be pushed back to the query section of URL. I.e. to unset some
+ *  field of the query, that field should be explicitely mentioned inside
+ *  'update' as undefined.
+ */
+export function updateQuery(update) {
+  if (isomorphy.isServerSide()) return;
+
+  let query = qs.parse(window.location.search.slice(1));
+
+  /* _.merge won't work here, because it just ignores the fields explicitely
+   * set as undefined in the objects to be merged, rather than deleting such
+   * fields in the target object. */
+  _.forIn(update, (value, key) => {
+    if (_.isUndefined(value)) delete query[key];
+    else query[key] = value;
+  });
+  query = `?${qs.stringify(query, { encodeValuesOnly: true })}`;
+  window.history.replaceState(window.history.state, '', query);
+}
+
+/**
+ * Cleans/removes trailing slash from url
+ *
+ * @param  {String} url The url to clean
+ * @return {String}
+ */
+export function removeTrailingSlash(url) {
+  return url.charAt(url.length - 1) === '/'
+    ? url.slice(0, -1)
+    : url;
+}
+
+export default undefined;