diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json
index 9cf54bcfac2..b6f9d05fa84 100644
--- a/client/i18n/locales/english/intro.json
+++ b/client/i18n/locales/english/intro.json
@@ -2796,7 +2796,13 @@
]
},
"mjbe": { "title": "251", "intro": [] },
- "byqx": { "title": "252", "intro": [] },
+ "workshop-fcc-authors-page": {
+ "title": "Build an fCC Authors Page",
+ "intro": [
+ "One common aspect of web development is learning how to fetch data from an external API, then work with asynchronous JavaScript.",
+ "In this workshop you will practice how to use the fetch method, dynamically update the DOM to display the fetched data and paginate your data so you can load results in batches."
+ ]
+ },
"alda": { "title": "253", "intro": [] },
"cvaf": { "title": "254", "intro": [] },
"review-asynchronous-javascript": {
diff --git a/client/src/pages/learn/full-stack-developer/workshop-fcc-authors-page/index.md b/client/src/pages/learn/full-stack-developer/workshop-fcc-authors-page/index.md
new file mode 100644
index 00000000000..6e746f63b85
--- /dev/null
+++ b/client/src/pages/learn/full-stack-developer/workshop-fcc-authors-page/index.md
@@ -0,0 +1,11 @@
+---
+title: Introduction to the Build an fCC Authors Page
+block: workshop-fcc-authors-page
+superBlock: full-stack-developer
+---
+
+## Introduction to the Build an fCC Authors Page
+
+One common aspect of web development is learning how to fetch data from an external API, then work with asynchronous JavaScript.
+
+In this workshop you will practice how to use the fetch method, dynamically update the DOM to display the fetched data and paginate your data so you can load results in batches.
diff --git a/curriculum/challenges/_meta/workshop-fcc-authors-page/meta.json b/curriculum/challenges/_meta/workshop-fcc-authors-page/meta.json
new file mode 100644
index 00000000000..0f5109fa8af
--- /dev/null
+++ b/curriculum/challenges/_meta/workshop-fcc-authors-page/meta.json
@@ -0,0 +1,129 @@
+{
+ "name": "Build an fCC Authors Page",
+ "blockType": "workshop",
+ "blockLayout": "challenge-grid",
+ "isUpcomingChange": true,
+ "usesMultifileEditor": true,
+ "hasEditableBoundaries": true,
+ "dashedName": "workshop-fcc-authors-page",
+ "superBlock": "full-stack-developer",
+ "challengeOrder": [
+ {
+ "id": "641d9a19bff38d34d5a5edb8",
+ "title": "Step 1"
+ },
+ {
+ "id": "641da3c6b6fbd742bff6ee40",
+ "title": "Step 2"
+ },
+ {
+ "id": "641da42481d90c4314c99e94",
+ "title": "Step 3"
+ },
+ {
+ "id": "641da465273051435d332b15",
+ "title": "Step 4"
+ },
+ {
+ "id": "641da4b16937be43ba24c63d",
+ "title": "Step 5"
+ },
+ {
+ "id": "641da51a9810e74411262fcc",
+ "title": "Step 6"
+ },
+ {
+ "id": "641da5462576784453146ec2",
+ "title": "Step 7"
+ },
+ {
+ "id": "641da5abaac81844a54adb03",
+ "title": "Step 8"
+ },
+ {
+ "id": "641da5dd6cd6db44f58b7787",
+ "title": "Step 9"
+ },
+ {
+ "id": "641da615af82bf454215a992",
+ "title": "Step 10"
+ },
+ {
+ "id": "641da6570acf7545931ce477",
+ "title": "Step 11"
+ },
+ {
+ "id": "641da6dcb6e4c9463d54c75b",
+ "title": "Step 12"
+ },
+ {
+ "id": "641da7071d0d45467cd59977",
+ "title": "Step 13"
+ },
+ {
+ "id": "641da73b09e7f046c758e0ed",
+ "title": "Step 14"
+ },
+ {
+ "id": "641da791d0c34a472b8d15b6",
+ "title": "Step 15"
+ },
+ {
+ "id": "641da7bfbc7f0f477438ad8a",
+ "title": "Step 16"
+ },
+ {
+ "id": "641da803d9892447d059804e",
+ "title": "Step 17"
+ },
+ {
+ "id": "641da836581c254815f785fe",
+ "title": "Step 18"
+ },
+ {
+ "id": "641da86294fd9f485d3c2bf0",
+ "title": "Step 19"
+ },
+ {
+ "id": "641da895fb7ec648a5bdf19c",
+ "title": "Step 20"
+ },
+ {
+ "id": "641da8db2a036048ebe6999e",
+ "title": "Step 21"
+ },
+ {
+ "id": "641da97c987a514959ada414",
+ "title": "Step 22"
+ },
+ {
+ "id": "641da9aceb788e49a73ebcc9",
+ "title": "Step 23"
+ },
+ {
+ "id": "641da9ea9b847a49fe6ee9b6",
+ "title": "Step 24"
+ },
+ {
+ "id": "641daa5ea050f24a7cade6e6",
+ "title": "Step 25"
+ },
+ {
+ "id": "641daa8c2c3e364ac3650b37",
+ "title": "Step 26"
+ },
+ {
+ "id": "641daabed8d0584b1150c953",
+ "title": "Step 27"
+ },
+ {
+ "id": "641daae5e18eae4b562633e4",
+ "title": "Step 28"
+ },
+ {
+ "id": "641dab13c1b6f14b9828e6b1",
+ "title": "Step 29"
+ }
+ ],
+ "helpCategory": "JavaScript"
+}
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641d9a19bff38d34d5a5edb8.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641d9a19bff38d34d5a5edb8.md
new file mode 100644
index 00000000000..32afe14d5c9
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641d9a19bff38d34d5a5edb8.md
@@ -0,0 +1,149 @@
+---
+id: 641d9a19bff38d34d5a5edb8
+title: Step 1
+challengeType: 0
+dashedName: step-1
+demoType: onLoad
+---
+
+# --description--
+
+All the HTML and CSS for this project has been provided for you. You can take a look at the two files to familiarize yourself with them.
+
+Start by getting the `#author-container` and `#load-more-btn` elements with the `.getElementById()` method. Assign them to the variables `authorContainer` and `loadMoreBtn`, respectively.
+
+The variables will not change, so use `const` to declare them.
+
+# --hints--
+
+You should use `document.getElementById()` to get the `#author-container` element.
+
+```js
+assert.match(code, /document\.getElementById\(\s*('|"|`)author\-container\1\s*\)/);
+```
+
+You should assign the `#author-container` element to the variable `authorContainer`. Don't forget to use `const` to declare the variable.
+
+```js
+assert.match(code, /const\s+authorContainer\s*\=\s*document\.getElementById\(\s*('|"|`)author\-container\1\s*\)/);
+```
+
+You should use `document.getElementById()` to get the `#load-more-btn` element.
+
+```js
+assert.match(code, /document\.getElementById\(\s*('|"|`)load\-more\-btn\1\s*\)/);
+```
+
+You should assign the `#load-more-btn` element to the variable `loadMoreBtn`. Don't forget to use `const` to declare the variable.
+
+```js
+assert.match(code, /const\s+loadMoreBtn\s*\=\s*document\.getElementById\(\s*('|"|`)load\-more\-btn\1\s*\)/);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
freeCodeCamp News Author Page
+
+
+
+
+
+
+
+
+
+```
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --main-bg-color: #1b1b32;
+ --light-grey: #f5f6f7;
+ --dark-purple: #5a01a7;
+ --golden-yellow: #feac32;
+}
+
+body {
+ background-color: var(--main-bg-color);
+ text-align: center;
+}
+
+.title {
+ color: var(--light-grey);
+ margin: 20px 0;
+}
+
+#author-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.user-card {
+ border-radius: 15px;
+ width: 300px;
+ height: 350px;
+ background-color: var(--light-grey);
+ margin: 20px;
+}
+
+.user-img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+}
+
+.purple-divider {
+ background-color: var(--dark-purple);
+ width: 100%;
+ height: 15px;
+}
+
+.author-name {
+ margin: 10px;
+}
+
+.bio {
+ margin: 20px;
+}
+
+.error-msg {
+ color: var(--light-grey);
+}
+
+.btn {
+ cursor: pointer;
+ width: 200px;
+ margin: 10px;
+ color: var(--main-bg-color);
+ font-size: 14px;
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+```
+
+```js
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da3c6b6fbd742bff6ee40.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da3c6b6fbd742bff6ee40.md
new file mode 100644
index 00000000000..d5b4b560c88
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da3c6b6fbd742bff6ee40.md
@@ -0,0 +1,143 @@
+---
+id: 641da3c6b6fbd742bff6ee40
+title: Step 2
+challengeType: 0
+dashedName: step-2
+---
+
+# --description--
+
+The Fetch API is a built-in JavaScript interface to make network requests to a server. It has a `fetch()` method you can use to make `GET`, `POST`, `PUT`, or `PATCH` requests. In this project, you'll make a `GET` request to a URL for a JSON file with information about authors on freeCodeCamp News.
+
+Here is how you can make a `GET` request with the `fetch()` method:
+
+```js
+fetch("url-goes-here")
+```
+
+Make a `GET` request to this URL: `"https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json"`. Don't terminate your code with a semicolon yet.
+
+# --hints--
+
+You should use the `fetch()` method to make a `GET` request to `"https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json"`.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/)
+```
+
+You should not have a semicolon after your `fetch` call.
+
+```js
+assert.notMatch(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*;/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
freeCodeCamp News Author Page
+
+
+
+
+
+
+
+
+
+```
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --main-bg-color: #1b1b32;
+ --light-grey: #f5f6f7;
+ --dark-purple: #5a01a7;
+ --golden-yellow: #feac32;
+}
+
+body {
+ background-color: var(--main-bg-color);
+ text-align: center;
+}
+
+.title {
+ color: var(--light-grey);
+ margin: 20px 0;
+}
+
+#author-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.user-card {
+ border-radius: 15px;
+ width: 300px;
+ height: 350px;
+ background-color: var(--light-grey);
+ margin: 20px;
+}
+
+.user-img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+}
+
+.purple-divider {
+ background-color: var(--dark-purple);
+ width: 100%;
+ height: 15px;
+}
+
+.author-name {
+ margin: 10px;
+}
+
+.bio {
+ margin: 20px;
+}
+
+.error-msg {
+ color: var(--light-grey);
+}
+
+.btn {
+ cursor: pointer;
+ width: 200px;
+ margin: 10px;
+ color: var(--main-bg-color);
+ font-size: 14px;
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+```
+
+```js
+const authorContainer = document.getElementById('author-container');
+const loadMoreBtn = document.getElementById('load-more-btn');
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da42481d90c4314c99e94.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da42481d90c4314c99e94.md
new file mode 100644
index 00000000000..dd59d6caf9b
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da42481d90c4314c99e94.md
@@ -0,0 +1,173 @@
+---
+id: 641da42481d90c4314c99e94
+title: Step 3
+challengeType: 0
+dashedName: step-3
+---
+
+# --description--
+
+The `fetch()` method returns a `Promise`, which is a placeholder object that will either be fulfilled if your request is successful, or rejected if your request is unsuccessful.
+
+If the `Promise` is fulfilled, it resolves to a `Response` object, and you can use the `.then()` method to access the `Response`.
+
+Here's how you can chain `.then()` to the `fetch()` method:
+
+```js
+fetch("sample-url-goes-here")
+ .then((res) => res)
+```
+
+Chain the `.then()` method to your `fetch` call. Inside the `.then()` method, add a callback function with `res` as a parameter, then log the `res` to the console so you can see the `Response` object. Open your browser console and expand the `Response` object to see what it contains.
+
+Again, don't terminate the code with a semicolon yet.
+
+# --hints--
+
+You should use the `fetch()` method to make a `GET` request to `"https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json"`.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/)
+```
+
+You should chain the `.then()` method to your `fetch()` syntax
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(/)
+```
+
+You should add a `res` parameter to your `.then()` method.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)/)
+```
+
+Your `.then()` method should use arrow function syntax.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*/)
+```
+
+You should log the `res` parameter to the console so you can see the `Response` object.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*console\.log\(\s*res\s*\)\s*\)/)
+```
+
+You should not have a semicolon after your `.then()`.
+
+```js
+assert.notMatch(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*console\.log\(\s*res\s*\)\s*\)\s*;/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
freeCodeCamp News Author Page
+
+
+
+
+
+
+
+
+
+```
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --main-bg-color: #1b1b32;
+ --light-grey: #f5f6f7;
+ --dark-purple: #5a01a7;
+ --golden-yellow: #feac32;
+}
+
+body {
+ background-color: var(--main-bg-color);
+ text-align: center;
+}
+
+.title {
+ color: var(--light-grey);
+ margin: 20px 0;
+}
+
+#author-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.user-card {
+ border-radius: 15px;
+ width: 300px;
+ height: 350px;
+ background-color: var(--light-grey);
+ margin: 20px;
+}
+
+.user-img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+}
+
+.purple-divider {
+ background-color: var(--dark-purple);
+ width: 100%;
+ height: 15px;
+}
+
+.author-name {
+ margin: 10px;
+}
+
+.bio {
+ margin: 20px;
+}
+
+.error-msg {
+ color: var(--light-grey);
+}
+
+.btn {
+ cursor: pointer;
+ width: 200px;
+ margin: 10px;
+ color: var(--main-bg-color);
+ font-size: 14px;
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+```
+
+```js
+const authorContainer = document.getElementById('author-container');
+const loadMoreBtn = document.getElementById('load-more-btn');
+
+--fcc-editable-region--
+fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da465273051435d332b15.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da465273051435d332b15.md
new file mode 100644
index 00000000000..c3a10f10d91
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da465273051435d332b15.md
@@ -0,0 +1,146 @@
+---
+id: 641da465273051435d332b15
+title: Step 4
+challengeType: 0
+dashedName: step-4
+---
+
+# --description--
+
+The data you get from a `GET` request is not usable at first. To make the data usable, you can use the `.json()` method on the `Response` object to parse it into JSON. If you expand the `Prototype` of the `Response` object in the browser console, you will see the `.json()` method there.
+
+Remove `console.log(res)` and implicitly return `res.json()` instead.
+
+# --hints--
+
+You should remove the `console.log(res)`.
+
+```js
+assert.notMatch(code, /\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*console\.log\(\s*res\s*\)\s*\)/)
+```
+
+You should implicitly return `res.json()` in your `.then()`.
+
+```js
+assert.match(code, /\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)/)
+```
+
+You should not have a semicolon after your `.then()`.
+
+```js
+assert.notMatch(code, /\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*;/)
+```
+
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
freeCodeCamp News Author Page
+
+
+
+
+
+
+
+
+
+```
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --main-bg-color: #1b1b32;
+ --light-grey: #f5f6f7;
+ --dark-purple: #5a01a7;
+ --golden-yellow: #feac32;
+}
+
+body {
+ background-color: var(--main-bg-color);
+ text-align: center;
+}
+
+.title {
+ color: var(--light-grey);
+ margin: 20px 0;
+}
+
+#author-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.user-card {
+ border-radius: 15px;
+ width: 300px;
+ height: 350px;
+ background-color: var(--light-grey);
+ margin: 20px;
+}
+
+.user-img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+}
+
+.purple-divider {
+ background-color: var(--dark-purple);
+ width: 100%;
+ height: 15px;
+}
+
+.author-name {
+ margin: 10px;
+}
+
+.bio {
+ margin: 20px;
+}
+
+.error-msg {
+ color: var(--light-grey);
+}
+
+.btn {
+ cursor: pointer;
+ width: 200px;
+ margin: 10px;
+ color: var(--main-bg-color);
+ font-size: 14px;
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+```
+
+```js
+const authorContainer = document.getElementById('author-container');
+const loadMoreBtn = document.getElementById('load-more-btn');
+
+fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
+--fcc-editable-region--
+.then((res) => console.log(res))
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da4b16937be43ba24c63d.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da4b16937be43ba24c63d.md
new file mode 100644
index 00000000000..ebab324b18c
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da4b16937be43ba24c63d.md
@@ -0,0 +1,176 @@
+---
+id: 641da4b16937be43ba24c63d
+title: Step 5
+challengeType: 0
+dashedName: step-5
+---
+
+# --description--
+
+In order to start working with the data, you will need to use another `.then()` method.
+
+Chain another `.then()` to the existing `.then()` method. This time, pass in `data` as the parameter for the callback function. For the callback, use a curly brace because you will have more than one expression. Within your callback function, log `data` to the console to see what it looks like.
+
+
+# --hints--
+
+You should use the `fetch()` method to make a `GET` request to `"https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json"`.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/)
+```
+
+Your first `.then()` should not have a semicolon.
+
+```js
+assert.notMatch(code, /\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*;/)
+```
+
+You should chain another `.then()` to the existing `.then()`.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(/)
+```
+
+You should add a `data` parameter to your second `.then()` method.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)/)
+```
+
+Your second `.then()` method should use arrow function syntax.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*/)
+```
+
+Your second `.then()` method should have curly braces.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*/)
+```
+
+Your second `.then()` method should log `data` to the console.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\s*\}\s*\)/)
+```
+
+Your second `.then()` should not have semicolon after it.
+
+```js
+assert.notMatch(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\s*\}\s*\)\s*;/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
freeCodeCamp News Author Page
+
+
+
+
+
+
+
+
+
+```
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --main-bg-color: #1b1b32;
+ --light-grey: #f5f6f7;
+ --dark-purple: #5a01a7;
+ --golden-yellow: #feac32;
+}
+
+body {
+ background-color: var(--main-bg-color);
+ text-align: center;
+}
+
+.title {
+ color: var(--light-grey);
+ margin: 20px 0;
+}
+
+#author-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.user-card {
+ border-radius: 15px;
+ width: 300px;
+ height: 350px;
+ background-color: var(--light-grey);
+ margin: 20px;
+}
+
+.user-img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+}
+
+.purple-divider {
+ background-color: var(--dark-purple);
+ width: 100%;
+ height: 15px;
+}
+
+.author-name {
+ margin: 10px;
+}
+
+.bio {
+ margin: 20px;
+}
+
+.error-msg {
+ color: var(--light-grey);
+}
+
+.btn {
+ cursor: pointer;
+ width: 200px;
+ margin: 10px;
+ color: var(--main-bg-color);
+ font-size: 14px;
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+```
+
+```js
+const authorContainer = document.getElementById('author-container');
+const loadMoreBtn = document.getElementById('load-more-btn');
+
+--fcc-editable-region--
+fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
+ .then((res) => res.json())
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da51a9810e74411262fcc.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da51a9810e74411262fcc.md
new file mode 100644
index 00000000000..1d5449e5bac
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da51a9810e74411262fcc.md
@@ -0,0 +1,198 @@
+---
+id: 641da51a9810e74411262fcc
+title: Step 6
+challengeType: 0
+dashedName: step-6
+---
+
+# --description--
+
+The `.catch()` method is another asynchronous JavaScript method you can use to handle errors. This is useful in case the `Promise` gets rejected.
+
+Chain `.catch()` to the last `.then()`. Pass in a callback function with `err` as the parameter. Inside the callback, use `console.error()` to log possible errors to the console with the text `There was an error: ${err}`. Since you're using `err` in the text, don't forget to use a template literal string with backticks (\`\`) instead of single or double quotes.
+
+**Note**: Now you can terminate your code with a semicolon. You couldn't do that in the previous steps because you'll signal to JavaScript to stop parsing your code, which will affect the `fetch()` syntax.
+
+# --hints--
+
+You should use the `fetch()` method to make a `GET` request to `"https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json"`.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/)
+```
+
+You should chain another `.then()` to the existing `.then()`.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(/)
+```
+
+You should add a `data` parameter to your second `.then()` method.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)/)
+```
+
+Your second `.then()` method should use arrow function syntax.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*/)
+```
+
+Your second `.then()` method should have curly braces.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*/)
+```
+
+Your second `.then()` method should log `data` to the console.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\n?\s*\}\s*\)/)
+```
+
+Your second `.then()` should not have a semicolon.
+
+```js
+assert.notMatch(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\n?\s*\}\s*\)\s*;/)
+```
+
+You should chain the `.catch()` method to the second `.then()` you already have.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\n?\s*\}\s*\)\n?\s*\.catch\(/)
+```
+
+You should add an `err` parameter to your `.catch()` method.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\n?\s*\}\s*\)\n?\s*\.catch\(\s*(\(\s*err\s*\)|err)/)
+```
+
+Your `.catch()` method should have an arrow function syntax.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\n?\s*\}\s*\)\n?\s*\.catch\(\s*(\(\s*err\s*\)|err)\s*=>\s*\{?/)
+```
+
+Your `.catch()` method should use `console.error()` to log `err` to the console with the text `` `There was an error: ${err}` ``. Don't forget to use backticks.
+
+```js
+assert.match(code, /fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)\s*\.then\(\s*(\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(\(\s*data\s*\)|data)\s*=>\s*\{\s*\n?\s*?console\.log\(\s*data\s*\)\s*;?\n?\s*\}\s*\)\n?\s*\.catch\(\s*(\(\s*err\s*\)|err)\s*=>\s*\{?\n?(\s*)?console\.error\(\s*`There\swas\san\serror:\s?\$\{\s*err\s*\}`\s*\)\s*;?\n?(\s*)?\}?\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
freeCodeCamp News Author Page
+
+
+
+
+
+
+
+
+
+```
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --main-bg-color: #1b1b32;
+ --light-grey: #f5f6f7;
+ --dark-purple: #5a01a7;
+ --golden-yellow: #feac32;
+}
+
+body {
+ background-color: var(--main-bg-color);
+ text-align: center;
+}
+
+.title {
+ color: var(--light-grey);
+ margin: 20px 0;
+}
+
+#author-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.user-card {
+ border-radius: 15px;
+ width: 300px;
+ height: 350px;
+ background-color: var(--light-grey);
+ margin: 20px;
+}
+
+.user-img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+}
+
+.purple-divider {
+ background-color: var(--dark-purple);
+ width: 100%;
+ height: 15px;
+}
+
+.author-name {
+ margin: 10px;
+}
+
+.bio {
+ margin: 20px;
+}
+
+.error-msg {
+ color: var(--light-grey);
+}
+
+.btn {
+ cursor: pointer;
+ width: 200px;
+ margin: 10px;
+ color: var(--main-bg-color);
+ font-size: 14px;
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+```
+
+```js
+const authorContainer = document.getElementById('author-container');
+const loadMoreBtn = document.getElementById('load-more-btn');
+
+fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
+ .then((res) => res.json())
+ .then((data) => {
+ console.log(data);
+ })
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da5462576784453146ec2.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da5462576784453146ec2.md
new file mode 100644
index 00000000000..71690b72723
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da5462576784453146ec2.md
@@ -0,0 +1,172 @@
+---
+id: 641da5462576784453146ec2
+title: Step 7
+challengeType: 0
+dashedName: step-7
+---
+
+# --description--
+
+Now that you have the data you want, you can use it to populate the UI. But the fetched data contains an array of 26 authors, and if you add them all to the page at the same time, it could lead to poor performance.
+
+Instead, you should add 8 authors at a time, and have a button to add 8 more until there's no more data to display.
+
+Use `let` to create 2 variables named `startingIndex` and `endingIndex`, and assign them the number values `0` and `8`, respectively. Also, create an `authorDataArr` variable with `let` and set it to an empty array.
+
+# --hints--
+
+You should use `let` to declare a variable named `startingIndex`.
+
+```js
+assert.match(code, /let\s+startingIndex/)
+```
+
+You should set your `startingIndex` variable to `0`.
+
+```js
+assert.match(code, /let\s+startingIndex\s*=\s*0\s*;?/)
+```
+
+You should use `let` to declare a variable named `endingIndex`.
+
+```js
+assert.match(code, /let\s+endingIndex/)
+```
+
+You should set your `endingIndex` variable to `8`.
+
+```js
+assert.match(code, /let\s+endingIndex\s*=\s*8\s*;?/)
+```
+
+You should use `let` to declare a variable named `authorDataArr`.
+
+```js
+assert.match(code, /let\s+authorDataArr/)
+```
+
+You should set your `authorDataArr` variable to an empty array (`[]`).
+
+```js
+assert.match(code, /let\s+authorDataArr\s*=\s*\[\s*\]\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
freeCodeCamp News Author Page
+
+
+
+
+
+
+
+
+
+```
+
+```css
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --main-bg-color: #1b1b32;
+ --light-grey: #f5f6f7;
+ --dark-purple: #5a01a7;
+ --golden-yellow: #feac32;
+}
+
+body {
+ background-color: var(--main-bg-color);
+ text-align: center;
+}
+
+.title {
+ color: var(--light-grey);
+ margin: 20px 0;
+}
+
+#author-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.user-card {
+ border-radius: 15px;
+ width: 300px;
+ height: 350px;
+ background-color: var(--light-grey);
+ margin: 20px;
+}
+
+.user-img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+}
+
+.purple-divider {
+ background-color: var(--dark-purple);
+ width: 100%;
+ height: 15px;
+}
+
+.author-name {
+ margin: 10px;
+}
+
+.bio {
+ margin: 20px;
+}
+
+.error-msg {
+ color: var(--light-grey);
+}
+
+.btn {
+ cursor: pointer;
+ width: 200px;
+ margin: 10px;
+ color: var(--main-bg-color);
+ font-size: 14px;
+ background-color: var(--golden-yellow);
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border-color: var(--golden-yellow);
+ border-width: 3px;
+}
+```
+
+```js
+const authorContainer = document.getElementById('author-container');
+const loadMoreBtn = document.getElementById('load-more-btn');
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+
+fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json')
+ .then((res) => res.json())
+ .then((data) => {
+ console.log(data);
+ })
+ .catch((err) => {
+ console.error(`There was an error: ${err}`);
+ });
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da5abaac81844a54adb03.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da5abaac81844a54adb03.md
new file mode 100644
index 00000000000..eb2683f23f8
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da5abaac81844a54adb03.md
@@ -0,0 +1,168 @@
+---
+id: 641da5abaac81844a54adb03
+title: Step 8
+challengeType: 0
+dashedName: step-8
+---
+
+# --description--
+
+Now you'll create a function to populate the UI with the author data. You will call this function inside the second `.then()` method.
+
+Create an empty arrow function named `displayAuthors` that takes `authors` as a parameter.
+
+# --hints--
+
+You should use `const` to create an empty function named `displayAuthors`.
+
+```js
+assert.match(code, /const\s+displayAuthors\s*=\s*/)
+```
+
+`displayAuthors` should be a function.
+
+```js
+assert.isFunction(displayAuthors)
+```
+
+Your `displayAuthors` function should use an arrow syntax.
+
+```js
+assert.match(code, /const\s+displayAuthors\s*=\s*(\(.*\)|[^\s()]+)\s*=>\s*/)
+```
+
+Your `displayAuthors` function should take an `authors` parameter.
+
+```js
+assert.match(code, /const\s+displayAuthors\s*=\s*\(?\s*authors/)
+```
+
+Your `displayAuthors` function should be empty.
+
+```js
+assert.match(code, /const\s+displayAuthors\s*=\s*(\(\s*authors\s*\)|authors)\s*=>\s*\{\n?\s*?\}\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da73b09e7f046c758e0ed.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da73b09e7f046c758e0ed.md
new file mode 100644
index 00000000000..b0008d137c8
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da73b09e7f046c758e0ed.md
@@ -0,0 +1,159 @@
+---
+id: 641da73b09e7f046c758e0ed
+title: Step 14
+challengeType: 0
+dashedName: step-14
+---
+
+# --description--
+
+To see the authors' names on the page, you need to call the `displayAuthors` function inside the second `.then()` method. But before that, you need to assign the author data to the empty `authorDataArr` array.
+
+First, remove your `console.log()` statement. Then, assign `data` to the `authorDataArr` variable.
+
+# --hints--
+
+You should remove the console log showing the `data`.
+
+```js
+assert.notMatch(code, /console\.log\(\s*data\s*\)\s*;?/)
+```
+
+You should assign `data` to the `authorDataArr` variable
+
+```js
+assert.match(code, /authorDataArr\s*=\s*data\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da791d0c34a472b8d15b6.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da791d0c34a472b8d15b6.md
new file mode 100644
index 00000000000..2ef2d866bee
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da791d0c34a472b8d15b6.md
@@ -0,0 +1,171 @@
+---
+id: 641da791d0c34a472b8d15b6
+title: Step 15
+challengeType: 0
+dashedName: step-15
+---
+
+# --description--
+
+Now `authorDataArr` is the same as the `data` you logged to the console a while ago. Log `authorDataArr` to the console to confirm this.
+
+Inside your `console.log()` statement, add the text `"Author Data Array:"` as the first argument and `authorDataArr` as the second argument. Use comma to separate the text from `authorDataArr`.
+
+# --hints--
+
+You should assign `data` to the `authorDataArr` variable
+
+```js
+assert.match(code, /authorDataArr\s*=\s*data\s*;?/)
+```
+
+You should have a console log with the text `"Author Data Array:"`.
+
+```js
+assert.match(code, /console\.log\(\s*("|'|`)Author\s+Data\s+Array:\s*\1/)
+```
+
+You should use comma to separate your `"Author Data Array:"` text and `authorDataArr`.
+
+```js
+assert.match(code, /console\.log\(\s*("|'|`)Author\s+Data\s+Array:\s*\1\s*,/)
+```
+
+`authorDataArr` should be the second argument of your console log statement.
+
+```js
+assert.match(code, /console\.log\(\s*("|'|`)Author\s+Data\s+Array:\s*\1\s*,\s*authorDataArr\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da7bfbc7f0f477438ad8a.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da7bfbc7f0f477438ad8a.md
new file mode 100644
index 00000000000..11581586d1a
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da7bfbc7f0f477438ad8a.md
@@ -0,0 +1,184 @@
+---
+id: 641da7bfbc7f0f477438ad8a
+title: Step 16
+challengeType: 0
+dashedName: step-16
+---
+
+# --description--
+
+Now it's time to call the `displayAuthors` function. But again, you don't want to populate the page with all the authors at once. Instead, you can extract a portion of the authors with the `startingIndex` and `endingIndex` variables. The best method to do this is the `.slice()` array method.
+
+First, remove the console log statement showing `authorDataArr`. Then, call the `displayAuthors` function with the `authorDataArr` array and `.slice()`. Use the `startingIndex` variable for the starting point and the `endingIndex` variable for the ending point.
+
+# --hints--
+
+You should assign `data` to the `authorDataArr` variable
+
+```js
+assert.match(code, /authorDataArr\s*=\s*data\s*;?/)
+```
+
+You should remove the console log statement showing `authorDataArr`
+
+```js
+assert.notMatch(code, /console\.log\(\s*("|'|`)Author\s+Data\s+Array:\s*\1\s*,\s*authorDataArr\s*\)\s*;?/)
+```
+
+You should call your `displayAuthors` function.
+
+```js
+assert.match(code, /displayAuthors\(/)
+```
+
+You should call your `displayAuthors` function and pass in an argument of `authorDataArr.slice()`.
+
+```js
+assert.match(code, /displayAuthors\(\s*authorDataArr\.slice\(/)
+```
+
+The first parameter of your `slice()` method should be `startingIndex`.
+
+```js
+assert.match(code, /displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex/)
+```
+
+The second parameter of your `slice()` method should be `endingIndex`.
+
+```js
+assert.match(code, /displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex\s*,\s*endingIndex\s*\)\s*\)\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da803d9892447d059804e.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da803d9892447d059804e.md
new file mode 100644
index 00000000000..a9fd3e8cc0a
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da803d9892447d059804e.md
@@ -0,0 +1,182 @@
+---
+id: 641da803d9892447d059804e
+title: Step 17
+challengeType: 0
+dashedName: step-17
+---
+
+# --description--
+
+Now create an image tag and give it the `class` `"user-img"`. Use string interpolation to set the `src` attribute to `image` you destructured earlier. Set the `alt` attribute to `author` followed by the text `"avatar"`. Make sure there is a space between the `author` variable and the word `"avatar"`, for example, `"Quincy Larson avatar"`.
+
+# --hints--
+
+You should create an `img` element.
+
+```js
+const retryingTest = (test, message, tries = 20) => {
+ if (tries < 1) return Promise.reject(message);
+ if (test()) return Promise.resolve();
+
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ retryingTest(test, message, tries - 1)
+ .then(resolve)
+ .catch(reject);
+ }, 100);
+ });
+};
+() => retryingTest(() => document.querySelector('img'), "'img' element not found");
+```
+
+Your `img` element should have the class `"user-img"`.
+
+```js
+assert.include(document.querySelector('img')?.className, "user-img");
+```
+
+You should set the `src` attribute of your `img` element to `${image}`.
+
+```js
+assert.equal(document.querySelector('img')?.getAttribute('src'), authorDataArr[0].image);
+```
+
+You should set the `alt` attribute of your `img` element to `${author} avatar`.
+
+```js
+assert.equal(document.querySelector('img')?.getAttribute('alt'), `${authorDataArr[0].author} avatar`);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da86294fd9f485d3c2bf0.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da86294fd9f485d3c2bf0.md
new file mode 100644
index 00000000000..541ee10992a
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da86294fd9f485d3c2bf0.md
@@ -0,0 +1,193 @@
+---
+id: 641da86294fd9f485d3c2bf0
+title: Step 19
+challengeType: 0
+dashedName: step-19
+---
+
+# --description--
+
+Next, add a link to the author's page on freeCodeCamp News.
+
+Add an anchor element with the `class` `"author-link"`, interpolate `url` as the value for the `href` attribute, and set `target` to `"_blank"`. For the text of the anchor element, interpolate `author` followed by the text `"'s author page"`. For example, `"Quincy Larson's author page"`.
+
+# --hints--
+
+You should create an anchor element.
+
+```js
+const retryingTest = (test, message, tries = 20) => {
+ if (tries < 1) return Promise.reject(message);
+ if (test()) return Promise.resolve();
+
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ retryingTest(test, message, tries - 1)
+ .then(resolve)
+ .catch(reject);
+ }, 100);
+ });
+};
+() => retryingTest(() => document.querySelector('a'), "'a' element not found");
+```
+
+Your anchor element should have the class `"author-link"`.
+
+```js
+assert.include(document.querySelector('a')?.className, "author-link");
+```
+
+You should interpolate `url` as the value of your anchor's `href` attribute.
+
+```js
+assert.equal(document.querySelector('a')?.getAttribute('href'), authorDataArr[0].url);
+```
+
+You should set the `target` attribute of your anchor element to `"_blank"`.
+
+```js
+assert.equal(document.querySelector('a')?.getAttribute('target'), '_blank');
+```
+
+You should interpolate `author` followed by the text `"'s author page"` inside your anchor element.
+
+```js
+assert.equal(document.querySelector('a')?.textContent.trim(), `${authorDataArr[0].author}'s author page`);
+```
+
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `)
+ )
+};
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da895fb7ec648a5bdf19c.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da895fb7ec648a5bdf19c.md
new file mode 100644
index 00000000000..f4f4699c89d
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da895fb7ec648a5bdf19c.md
@@ -0,0 +1,182 @@
+---
+id: 641da895fb7ec648a5bdf19c
+title: Step 20
+challengeType: 0
+dashedName: step-20
+---
+
+# --description--
+
+Now you have everything you want to include in the UI. The next step is to make the `Load More Authors` button fetch more authors whenever it's clicked. You can do this by adding a `click` event to the button and carefully incrementing the `startingIndex` and `endingIndex` variables.
+
+Create a `fetchMoreAuthors` function with the arrow function syntax. Don't put anything in it yet. Make sure you use curly braces because you'll have more than one expression inside the function.
+
+# --hints--
+
+You should use `const` to create a `fetchMoreAuthors` function.
+
+```js
+assert.match(code, /const\s+fetchMoreAuthors\s*=\s*/)
+```
+
+`fetchMoreAuthors` should be a function.
+
+```js
+assert.isFunction(fetchMoreAuthors)
+```
+
+Your `fetchMoreAuthors` function should not take any parameter.
+
+```js
+assert.match(code, /const\s+fetchMoreAuthors\s*=\s*\(\s*\)\s*/)
+```
+
+Your `fetchMoreAuthors` function should use arrow syntax.
+
+```js
+assert.match(code, /const\s+fetchMoreAuthors\s*=\s*\(\s*\)\s*=>\s*/)
+```
+
+Your `fetchMoreAuthors` function should be empty.
+
+```js
+assert.match(code, /const\s+fetchMoreAuthors\s*=\s*\(\s*\)\s*=>\s*\{\s*\}/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da97c987a514959ada414.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da97c987a514959ada414.md
new file mode 100644
index 00000000000..0368cca3ac4
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da97c987a514959ada414.md
@@ -0,0 +1,187 @@
+---
+id: 641da97c987a514959ada414
+title: Step 22
+challengeType: 0
+dashedName: step-22
+---
+
+# --description--
+
+Now call the `displayAuthors` function with a portion of the author data just like you did before.
+
+If you click the `Load More Authors` button after calling the function, it won't work. That's because you still have to add the `click` event listener to the button. You'll do that next.
+
+# --hints--
+
+You should call your `displayAuthors` function.
+
+```js
+const afterAdd = code.split("endingIndex += 8;")[1];
+assert.match(afterAdd, /displayAuthors\(/)
+```
+
+You should call your `displayAuthors` function and pass in an argument of `authorDataArr.slice()`.
+
+```js
+const afterAdd = code.split("endingIndex += 8;")[1];
+assert.match(afterAdd, /displayAuthors\(\s*authorDataArr\.slice\(/)
+```
+
+The first argument of your `slice()` method should be `startingIndex`.
+
+```js
+const afterAdd = code.split("endingIndex += 8;")[1];
+assert.match(afterAdd, /displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex/)
+```
+
+The second argument of your `slice()` method should be `endingIndex`.
+
+```js
+const afterAdd = code.split("endingIndex += 8;")[1];
+assert.match(afterAdd, /displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex\s*,\s*endingIndex\s*\)\s*\)/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+```
+
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da9aceb788e49a73ebcc9.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da9aceb788e49a73ebcc9.md
new file mode 100644
index 00000000000..9a70f705e76
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da9aceb788e49a73ebcc9.md
@@ -0,0 +1,180 @@
+---
+id: 641da9aceb788e49a73ebcc9
+title: Step 23
+challengeType: 0
+dashedName: step-23
+---
+
+# --description--
+
+Remember that in step 1 you selected the `Load More Authors` button and assigned it to `loadMoreBtn`.
+
+Use `addEventListener` to add a `"click"` event listener to `loadMoreBtn`. Also, pass in a reference to the `fetchMoreAuthors` function to run whenever the button is clicked.
+
+After that, when you click the button you should see 8 more authors.
+
+# --hints--
+
+You should call the `addEventListener()` method on your `loadMoreBtn` variable.
+
+```js
+assert.match(code, /loadMoreBtn\.addEventListener\(/)
+```
+
+Your event listener should listen for a `"click"` event.
+
+```js
+assert.match(code, /loadMoreBtn\.addEventListener\(\s*('|"|`)click\1/)
+```
+
+Your event listener should take `fetchMoreAuthors` as the function to run.
+
+```js
+assert.match(code, /loadMoreBtn\.addEventListener\(\s*('|"|`)click\1\s*,\s*fetchMoreAuthors\s*\)\s*;?/)
+```
+
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+
+--fcc-editable-region--
+
+--fcc-editable-region--
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da9ea9b847a49fe6ee9b6.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da9ea9b847a49fe6ee9b6.md
new file mode 100644
index 00000000000..46541bbd2ed
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641da9ea9b847a49fe6ee9b6.md
@@ -0,0 +1,180 @@
+---
+id: 641da9ea9b847a49fe6ee9b6
+title: Step 24
+challengeType: 0
+dashedName: step-24
+---
+
+# --description--
+
+Your fCC Authors Page is now complete. But you could improve on a few things.
+
+First, if you click the `Load More Authors` button a couple of times, you'll see that it won't add more authors to the page. That's because you've reached the end of the authors list. For a better user experience, you should make it clear when there's no more data to display by disabling the button and changing its text. An `if` statement is the perfect tool for this.
+
+Inside the `fetchMoreAuthors` function, write an `if` statement and set the condition to `authorDataArr.length <= endingIndex` – meaning there's no more data to load.
+
+# --hints--
+
+You should have an `if` statement.
+
+```js
+assert.match(code, /if\s*\(/)
+```
+
+Your `if` statement should take in `authorDataArr.length <= endingIndex` as its condition.
+
+```js
+assert.match(code, /if\s*\(\s*authorDataArr\.length\s*<=\s*endingIndex\s*\)\s*/)
+```
+
+Your `if` statement should be empty.
+
+```js
+assert.match(code, /if\s*\(\s*authorDataArr\.length\s*<=\s*endingIndex\s*\)\s*\{\s*\}/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+
+loadMoreBtn.addEventListener('click', fetchMoreAuthors);
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daa5ea050f24a7cade6e6.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daa5ea050f24a7cade6e6.md
new file mode 100644
index 00000000000..38b38aa02ec
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daa5ea050f24a7cade6e6.md
@@ -0,0 +1,172 @@
+---
+id: 641daa5ea050f24a7cade6e6
+title: Step 25
+challengeType: 0
+dashedName: step-25
+---
+
+# --description--
+
+If this condition is met, disable the button by setting its `disabled` property to `true`. Also, set the `textContent` of the button to `"No more data to load"`.
+
+# --hints--
+
+You should set the `disabled` property of `loadMoreBtn` to `true`.
+
+```js
+assert.match(code, /loadMoreBtn\.disabled\s*=\s*true\s*;?/)
+```
+
+You should set the `textContent` of `loadMoreBtn` to `"No more data to load"`.
+
+```js
+assert.match(code, /loadMoreBtn\.textContent\s*=\s*('|"|`)No\s+more\s+data\s+to\s+load\1\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+
+loadMoreBtn.addEventListener('click', fetchMoreAuthors);
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daa8c2c3e364ac3650b37.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daa8c2c3e364ac3650b37.md
new file mode 100644
index 00000000000..a705aeb481c
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daa8c2c3e364ac3650b37.md
@@ -0,0 +1,188 @@
+---
+id: 641daa8c2c3e364ac3650b37
+title: Step 26
+challengeType: 0
+dashedName: step-26
+---
+
+# --description--
+
+Next, there's not a lot of separation between each author's name and image, and the rest of the details on the card. A divider will give the author cards a clear visual hierarchy.
+
+Add a `div` element above the author's bio and give it the `class` `"purple-divider"`.
+
+# --hints--
+
+You should create a `div` element before your `p` element.
+
+```js
+const retryingTest = (test, message, tries = 20) => {
+ if (tries < 1) return Promise.reject(message);
+ if (test()) return Promise.resolve();
+
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ retryingTest(test, message, tries - 1)
+ .then(resolve)
+ .catch(reject);
+ }, 100);
+ });
+};
+() => retryingTest(() => document.querySelector('p')?.previousElementSibling?.tagName === 'DIV', "'div' element not found");
+```
+
+Your `div` element should have the `class` set to `"purple-divider"`.
+
+```js
+assert.include(document.querySelector('p')?.previousElementSibling?.className, 'purple-divider');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+
+ `;
+ });
+};
+
+loadMoreBtn.addEventListener('click', fetchMoreAuthors);
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daabed8d0584b1150c953.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daabed8d0584b1150c953.md
new file mode 100644
index 00000000000..1ee8ba8decc
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daabed8d0584b1150c953.md
@@ -0,0 +1,194 @@
+---
+id: 641daabed8d0584b1150c953
+title: Step 27
+challengeType: 0
+dashedName: step-27
+---
+
+# --description--
+
+Some of the author bios are much longer than others. To give the cards a uniform look, you can extract the first 50 characters of each one and replace the rest with an ellipsis `("...")`. Otherwise, you can show the entire bio.
+
+Within the paragraph element, replace `bio` with a ternary operator. For the condition, check if the length of `bio` is greater than 50. If it is, use the `.slice()` method to extract the first 50 characters of `bio` and add an ellipsis at the end. Otherwise, show the full `bio`.
+
+# --hints--
+
+You should create a `p` element.
+
+```js
+assert.match(code, /
/);
+```
+
+You should check if the length of the `bio` text is greater than `50`.
+
+```js
+assert.match(code, /
\s*\$\{\s*bio\.length\s*>\s*50/)
+```
+
+If the `bio` text is greater than `50` characters, you should extract the first 50 characters with `slice()` and replace the rest with `"..."`. Don't forget that indexes are zero-based.
+
+```js
+assert.match(code, /
\s*\$\{\s*bio\.length\s*>\s*50\s*\?\s*(?:bio\.slice\(\s*0\s*,\s*50\s*\)\s*\+\s*("|')\.\.\.\2|`\$\{\s*bio\.slice\(\s*0\s*,\s*50\s*\)\s*\}\.\.\.`)\s*:/);
+```
+
+If the `bio` text is less than 50 characters, use the `bio` text directly.
+
+```js
+assert.match(code, /
+ `;
+ });
+};
+
+loadMoreBtn.addEventListener('click', fetchMoreAuthors);
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daae5e18eae4b562633e4.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daae5e18eae4b562633e4.md
new file mode 100644
index 00000000000..ec73d564637
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641daae5e18eae4b562633e4.md
@@ -0,0 +1,191 @@
+---
+id: 641daae5e18eae4b562633e4
+title: Step 28
+challengeType: 0
+dashedName: step-28
+---
+
+# --description--
+
+Finally, what if there's an error and the author data fail to load? Then we need to show an error in the UI. That's exactly what the `.catch()` method is for – handling errors.
+
+Inside the `.catch()`, remove the `console.error()` and set the `innerHTML` of the `authorContainer` to a `p` element with the `class` `"error-msg"` and text `"There was an error loading the authors"`.
+
+# --hints--
+
+You should remove your `console.error` and its text.
+
+```js
+assert.notMatch(code, /console\.error\(\s*`There\s+was\s+an\s+error:\s+\$\{err\}`\s*\)\s*;?/)
+```
+
+You should access the `innerHTML` of `authorContainer` and set it to a `p` element. Don't forget to surround the `p` element with a template literal.
+
+
+```js
+assert.match(code, /authorContainer\.innerHTML\s*=\s*(`|"|')\s*.*<\/p>\s*\1/)
+```
+
+Your `p` element should have the class `"error-msg"`.
+
+```js
+assert.match(code, /(`|"|')\s*
/)
+```
+
+Your `p` element should have the text `"There was an error loading the authors"`.
+
+```js
+assert.match(code, /(`|"|')
+ `;
+ });
+};
+
+loadMoreBtn.addEventListener('click', fetchMoreAuthors);
+```
diff --git a/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641dab13c1b6f14b9828e6b1.md b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641dab13c1b6f14b9828e6b1.md
new file mode 100644
index 00000000000..eedcda11086
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/workshop-fcc-authors-page/641dab13c1b6f14b9828e6b1.md
@@ -0,0 +1,332 @@
+---
+id: 641dab13c1b6f14b9828e6b1
+title: Step 29
+challengeType: 0
+dashedName: step-29
+---
+
+# --description--
+
+One more thing. If you keep clicking the `Load More Authors` button until there's no more data to load and the text changes to `"No more data to load"`, the cursor value is still `pointer`. Why not change the cursor value to `not-allowed` instead?
+
+Access the `style` property of the `Load More Authors` button and set `cursor` to `"not-allowed"`.
+
+With that, your author page is complete!
+
+# --hints--
+
+You should access the `style` property of `loadMoreBtn` with a dot notation.
+
+```js
+assert.match(code, /loadMoreBtn\.style/)
+```
+
+You should access `cursor` from the `style` property with dot notation.
+
+```js
+assert.match(code, /loadMoreBtn\.style\.cursor/)
+```
+
+You should set the value of the `cursor` property to `"not-allowed"`.
+
+```js
+assert.match(code, /loadMoreBtn\.style\.cursor\s*=\s*('|"|`)not\-allowed\1\s*;?/)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+ freeCodeCamp News Author Page
+
+
+
+