-
-
Save codecademydev/501a1665076701102676b78b66da5ecd to your computer and use it in GitHub Desktop.
Codecademy export
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=swap'); | |
html, | |
body { | |
margin: 0; | |
font-family: 'Oxygen', sans-serif; | |
} | |
main { | |
display: flex; | |
flex-direction: column; | |
} | |
.articles-container { | |
width: 100%; | |
display: grid; | |
grid-template-columns: repeat(2, 215px); | |
grid-gap: 16px; | |
justify-content: center; | |
background-color: #f2f2f2; | |
padding-bottom: 48px; | |
} | |
.current-article-container { | |
padding: 24px 16px 32px 16px; | |
} | |
.comments-container { | |
padding: 0 16px 0 16px; | |
} | |
.article-container { | |
cursor: pointer; | |
border-radius: 6px; | |
border: solid 1px #141c3a; | |
background-color: #ffffff; | |
color: #141c3a; | |
padding: 0; | |
text-align: start; | |
width: 215px; | |
height: 100%; | |
display: flex; | |
flex-direction: column; | |
} | |
.article-title { | |
font-weight: bold; | |
margin: 0px; | |
margin-bottom: 8px; | |
font-size: 20px; | |
font-stretch: normal; | |
font-style: normal; | |
line-height: normal; | |
letter-spacing: normal; | |
} | |
.article-container:hover { | |
background-color: #141c3a; | |
color: #ffffff; | |
} | |
.article-preview { | |
font-style: italic; | |
margin: 0; | |
font-size: 16px; | |
font-weight: normal; | |
font-stretch: normal; | |
font-style: italic; | |
line-height: 1.31; | |
letter-spacing: normal; | |
display: -webkit-box; | |
-webkit-line-clamp: 2; | |
-webkit-box-orient: vertical; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
.article-content-container { | |
padding: 9px 16px 31px 16px; | |
} | |
.section-title { | |
font-size: 32px; | |
font-weight: bold; | |
font-stretch: normal; | |
font-style: normal; | |
line-height: normal; | |
letter-spacing: normal; | |
grid-column-end: 3; | |
grid-column-start: 1; | |
margin: 0; | |
margin-top: 32px; | |
margin-bottom: 8px; | |
} | |
.article-image { | |
width: 100%; | |
display: block; | |
} | |
.current-article { | |
padding-bottom: 32px; | |
} | |
.article-full-image-container { | |
width: 100%; | |
height: 220px; | |
} | |
.article-full-image-container > img { | |
object-fit: cover; | |
height: 100%; | |
width: 100%; | |
} | |
.current-article-title { | |
margin-top: 0; | |
margin-bottom: 16px; | |
font-size: 20px; | |
font-weight: bold; | |
font-stretch: normal; | |
font-style: normal; | |
line-height: normal; | |
letter-spacing: normal; | |
color: #141c3a; | |
} | |
.comments-title { | |
margin-top: 0; | |
padding-bottom: 8px; | |
border-bottom: solid 1px #141c3a; | |
} | |
.comments-list { | |
list-style: none; | |
padding-left: 0; | |
} | |
.comment-container { | |
padding: 10px; | |
border-radius: 6px; | |
border: solid 1px #e5e5e5; | |
background-color: #ffffff; | |
font-size: 14px; | |
font-weight: normal; | |
font-stretch: normal; | |
font-style: normal; | |
line-height: 1.43; | |
letter-spacing: normal; | |
color: #141c3a; | |
overflow-wrap: anywhere; | |
} | |
.comment-container:not(:last-of-type) { | |
margin-bottom: 8px; | |
} | |
.label { | |
font-size: 15px; | |
font-weight: bold; | |
font-stretch: normal; | |
font-style: normal; | |
line-height: normal; | |
letter-spacing: normal; | |
color: #141c3a; | |
display: block; | |
margin-bottom: 16px; | |
} | |
#comment { | |
padding: 7px 13px 7px 13px; | |
border-radius: 6px; | |
border: solid 1px #141c3a; | |
background-color: #ffffff; | |
flex-grow: 1; | |
margin-right: 8px; | |
} | |
.comment-button { | |
padding: 6px 12px; | |
border-radius: 6px; | |
border: solid 1px #141c3a; | |
background-color: #141c3a; | |
font-size: 18px; | |
font-weight: bold; | |
font-stretch: normal; | |
font-style: normal; | |
line-height: normal; | |
letter-spacing: normal; | |
color: #ffffff; | |
margin-left: auto; | |
cursor: pointer; | |
} | |
.comment-button:hover { | |
border: solid 1px #6400e4; | |
background-color: #6400e4; | |
} | |
.comment-button:disabled { | |
border: solid 1px #b9bbc4; | |
background-color: #b9bbc4; | |
} | |
#input-container { | |
display: flex; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// import './App.css'; | |
import React from 'react'; | |
import ArticlePreviews from '../features/articlePreviews/ArticlePreviews'; | |
import CurrentArticle from '../features/currentArticle/CurrentArticle'; | |
import Comments from '../features/comments/Comments'; | |
function App() { | |
return ( | |
<div className='App'> | |
<header className='App-header' /> | |
<main> | |
<div className='current-article'> | |
<CurrentArticle /> | |
<Comments /> | |
</div> | |
<ArticlePreviews /> | |
</main> | |
</div> | |
); | |
} | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
export default function ArticleListItem({ article }) { | |
return ( | |
<button key={article.id} className='article-container'> | |
<img src={article.image} alt='' className='article-image' /> | |
<div className='article-content-container'> | |
<h3 className='article-title'>{article.title}</h3> | |
<p className='article-preview'>{article.preview}</p> | |
</div> | |
</button> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect } from 'react'; | |
import { useDispatch, useSelector } from 'react-redux'; | |
import { | |
loadAllPreviews, | |
selectAllPreviews, | |
isLoading, | |
} from './articlePreviewsSlice'; | |
import { loadCurrentArticle } from '../currentArticle/currentArticleSlice'; | |
import ArticleListItem from '../../components/ArticleListItem'; | |
const ArticlePreviews = () => { | |
const dispatch = useDispatch(); | |
const articlePreviews = useSelector(selectAllPreviews); | |
const isLoadingPreviews = useSelector(isLoading); | |
useEffect(() => { | |
dispatch(loadAllPreviews()); | |
}, [dispatch]); | |
if (isLoadingPreviews) { | |
return <div>loading state</div>; | |
} | |
return ( | |
<> | |
<section className='articles-container'> | |
<h2 className='section-title'>All Articles</h2> | |
{articlePreviews.map((article) => ( | |
<div key={article.id} onClick={(e) => dispatch(loadCurrentArticle(article.id))}> | |
<ArticleListItem article={article} /> | |
</div> | |
))} | |
</section> | |
</> | |
); | |
}; | |
export default ArticlePreviews; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
export const loadAllPreviews = createAsyncThunk( | |
'articlePreviews/loadAllPreviews', | |
async () => { | |
const data = await fetch('api/articles'); | |
const json = await data.json(); | |
return json; | |
} | |
); | |
export const articlePreviewsSlice = createSlice({ | |
name: 'articlePreviews', | |
initialState: { | |
articles: [], | |
isLoadingArticlePreviews: false, | |
hasError: false | |
}, | |
extraReducers: (builder) => { | |
builder | |
.addCase(loadAllPreviews.pending, (state) => { | |
state.isLoadingArticlePreviews = true; | |
state.hasError = false; | |
}) | |
.addCase(loadAllPreviews.fulfilled, (state, action) => { | |
state.isLoadingArticlePreviews = false; | |
state.articles = action.payload; | |
}) | |
.addCase(loadAllPreviews.rejected, (state, action) => { | |
state.isLoadingArticlePreviews = false; | |
state.hasError = true; | |
state.articles = []; | |
}) | |
}, | |
}); | |
export const selectAllPreviews = (state) => state.articlePreviews.articles; | |
export const isLoading = (state) => state.articlePreviews.isLoading; | |
export default articlePreviewsSlice.reducer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": 1, | |
"title": "Biden Inaugurated as 46th President - The New York Times", | |
"preview": "Joseph Robinette Biden Jr. and Kamala Devi Harris took the oath of office at a Capitol still reeling from the attack of a violent mob at a time when a deadly pandemic is still ravaging the country.", | |
"fullText": "Joseph Robinette Biden Jr. and Kamala Devi Harris took the oath of office at a Capitol still reeling from the attack of a violent mob at a time when a deadly pandemic is still ravaging the country.", | |
"image": "https://static01.nyt.com/images/2021/01/20/us/politics/20dc-biden1-sub3/20dc-biden1-sub3-facebookJumbo.jpg" | |
}, | |
{ | |
"id": 2, | |
"title": "LG says it might quit the smartphone market", | |
"preview": "LG says it needs to make \"a cold judgment\" about its only money-losing division.", | |
"fullText": "LG says it needs to make \"a cold judgment\" about its only money-losing division.", | |
"image": "https://cdn.arstechnica.net/wp-content/uploads/2021/01/37-760x380.jpg" | |
}, | |
{ | |
"id": 3, | |
"title": "VW CEO teases Tesla’s Elon Musk in Twitter debut", | |
"preview": "VW CEO Herbert Diess is poking fun at his friendly rivalry with Tesla CEO Elon Musk in his Twitter debut as he tries to position Volkswagen as a leader in electrification.", | |
"fullText": "TVW CEO Herbert Diess is poking fun at his friendly rivalry with Tesla CEO Elon Musk in his Twitter debut as he tries to position Volkswagen as a leader in electrification.", | |
"image": "https://i1.wp.com/electrek.co/wp-content/uploads/sites/3/2020/09/VW-CEO-Hebert-Diess-Tesla-CEO-Elon-Musk-selfie-hero.jpg?resize=1200%2C628&quality=82&strip=all&ssl=1" | |
}, | |
{ | |
"id": 4, | |
"title": "QAnon believers struggle with inauguration.", | |
"preview": "As President Biden took office, some QAnon believers tried to rejigger their theories to accommodate a transfer of power.", | |
"fullText": "As President Biden took office, some QAnon believers tried to rejigger their theories to accommodate a transfer of power.", | |
"image": "https://static01.nyt.com/images/2021/01/20/business/20distortions-qanon/20distortions-qanon-facebookJumbo.jpg" | |
}, | |
{ | |
"id": 5, | |
"title": "Kamala Harris sworn into history", | |
"preview": "Harris becomes the first woman, Black woman and Asian American to serve as vice president.", | |
"fullText": "Harris becomes the first woman, Black woman and Asian American to serve as vice president.", | |
"image": "https://www.washingtonpost.com/wp-apps/imrs.php?src=https://arc-anglerfish-washpost-prod-washpost.s3.amazonaws.com/public/H4TGAGS3IEI6XKCJN6KCHJ277U.jpg&w=1440" | |
}, | |
{ | |
"id": 6, | |
"title": "SpaceX expands public beta test of Starlink satellite internet to Canada and the UK", | |
"preview": "Elon Musk's company is now offering early public access to its Starlink satellite internet service in Canada and the U.K.", | |
"fullText": "Elon Musk's company is now offering early public access to its Starlink satellite internet service in Canada and the U.K.", | |
"image": "https://image.cnbcfm.com/api/v1/image/106758975-1603465968180-EkzQ0UbXgAAGYVe-orig.jpg?v=1603466027" | |
}, | |
{ | |
"id": 7, | |
"title": "Scientists have finally worked out how butterflies fly", | |
"preview": "Experts, long puzzled by how butterflies fly, have found that the insects \"clap\" their wings together -- and their wings are perfectly evolved for better propulsion.", | |
"fullText": "Experts, long puzzled by how butterflies fly, have found that the insects \"clap\" their wings together -- and their wings are perfectly evolved for better propulsion.", | |
"image": "https://cdn.cnn.com/cnnnext/dam/assets/210120064324-restricted-butterflies-clap-intl-scli-super-tease.jpg" | |
}, | |
{ | |
"id": 8, | |
"title": "Navalny releases investigation into decadent billion-dollar 'Putin palace'", | |
"preview": "Even locked up in a detention center on the outskirts of Moscow, Kremlin critic Alexey Navalny continues to be a thorn in Russian President Vladimir Putin's side.", | |
"fullText": "Even locked up in a detention center on the outskirts of Moscow, Kremlin critic Alexey Navalny continues to be a thorn in Russian President Vladimir Putin's side.", | |
"image": "https://cdn.cnn.com/cnnnext/dam/assets/210120111237-restricted-05-putin-palace-navalny-russia-intl-super-tease.jpeg" | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { setupWorker } from 'msw'; | |
import { handlers } from './handlers'; | |
export const worker = setupWorker(...handlers); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
export default function Comment({ comment }) { | |
const { id, text } = comment | |
return ( | |
<li key={id} className='comment-container'> | |
<span>{text}</span> | |
</li> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from "react"; | |
import { useDispatch, useSelector } from "react-redux"; | |
import { | |
createCommentIsPending, | |
postCommentForArticleId, | |
} from "../features/comments/commentsSlice"; | |
export default function CommentForm({ articleId }) { | |
const dispatch = useDispatch(); | |
const [comment, setComment] = useState(""); | |
// Declare isCreatePending here. | |
const isCreatePending = useSelector(createCommentIsPending); | |
const handleSubmit = (e) => { | |
e.preventDefault(); | |
// dispatch your asynchronous action here! | |
dispatch(postCommentForArticleId({ articleId, comment })); | |
setComment(""); | |
}; | |
return ( | |
<form onSubmit={handleSubmit}> | |
<label for="comment" className="label"> | |
Add Comment: | |
</label> | |
<div id="input-container"> | |
<input | |
id="comment" | |
value={comment} | |
onChange={(e) => setComment(e.currentTarget.value)} | |
type="text" | |
/> | |
<button disabled={isCreatePending} className="comment-button"> | |
Submit | |
</button> | |
</div> | |
</form> | |
); | |
} |
Raw
CommentList.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
import React from "react"; | |
import Comment from "./Comment"; | |
export default function CommentList({ comments }) { | |
if (!comments) { | |
return null; | |
} | |
return ( | |
<ul className="comments-list"> | |
{comments.map((comment) => { | |
return <Comment comment={comment} />; | |
})} | |
</ul> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect } from "react"; | |
import { useDispatch, useSelector } from "react-redux"; | |
import { | |
loadCommentsForArticleId, | |
selectComments, | |
isLoadingComments, | |
} from "../comments/commentsSlice"; | |
import { selectCurrentArticle } from "../currentArticle/currentArticleSlice"; | |
import CommentList from "../../components/CommentList"; | |
import CommentForm from "../../components/CommentForm"; | |
const Comments = () => { | |
const dispatch = useDispatch(); | |
const article = useSelector(selectCurrentArticle); | |
// Declare additional selected data here. | |
const comments = useSelector(selectComments); | |
const commentsAreLoading = useSelector(isLoadingComments); | |
// Dispatch loadCommentsForArticleId with useEffect here. | |
const commentsForArticleId = | |
article === undefined ? [] : comments[article.id]; | |
useEffect(() => { | |
if (article !== undefined) { | |
dispatch(loadCommentsForArticleId(article.id)); | |
} | |
}, [dispatch, article]); | |
if (commentsAreLoading) { | |
return <div>Loading Comments</div>; | |
} | |
if (!article) { | |
return null; | |
} | |
return ( | |
<div className="comments-container"> | |
<h3 className="comments-title">Comments</h3> | |
<CommentList comments={commentsForArticleId} /> | |
<CommentForm articleId={article.id} /> | |
</div> | |
); | |
}; | |
export default Comments; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": 1, | |
"articleId": 5, | |
"text": "Congratulations Kamala." | |
}, | |
{ | |
"id": 2, | |
"articleId": 5, | |
"text": "Wow, very cool." | |
}, | |
{ | |
"id": 2, | |
"articleId": 7, | |
"text": "Butterflies are so awesome!!!" | |
}, | |
{ | |
"id": 2, | |
"articleId": 2, | |
"text": "Sad, I love my LG phone :(" | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import createAsyncThunk and createSlice here. | |
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; | |
// Create loadCommentsForArticleId here. | |
export const loadCommentsForArticleId = createAsyncThunk( | |
"comments/loadCommentsForArticleId", | |
async (id) => { | |
const response = await fetch(`api/articles/${id}/comments`); | |
const json = await response.json(); | |
return json; | |
} | |
); | |
// Create postCommentForArticleId here. | |
export const postCommentForArticleId = createAsyncThunk( | |
"comments/postCommentForArticleId", | |
async ({ articleId, comment }) => { | |
const requestBody = JSON.stringify({ comment: comment }); | |
const response = await fetch(`api/articles/${articleId}/comments`, { | |
method: "POST", | |
body: requestBody, | |
}); | |
const json = await response.json(); | |
return json; | |
} | |
); | |
export const commentsSlice = createSlice({ | |
name: "comments", | |
initialState: { | |
// Add initial state properties here. | |
byArticleId: {}, | |
isLoadingComments: false, | |
failedToLoadComments: false, | |
createCommentIsPending: false, | |
failedToCreateComment: false, | |
}, | |
// Add extraReducers here. | |
extraReducers: { | |
[loadCommentsForArticleId.pending]: (state, action) => { | |
state.isLoadingComments = true; | |
state.failedToLoadComments = false; | |
}, | |
[loadCommentsForArticleId.fulfilled]: (state, action) => { | |
state.isLoadingComments = false; | |
state.failedToLoadComments = false; | |
state.byArticleId[action.payload.articleId] = action.payload.comments; | |
}, | |
[loadCommentsForArticleId.rejected]: (state, action) => { | |
state.isLoadingComments = false; | |
state.failedToLoadComments = true; | |
}, | |
[postCommentForArticleId.pending]: (state, action) => { | |
state.createCommentIsPending = true; | |
state.failedToCreateComment = false; | |
}, | |
[postCommentForArticleId.fulfilled]: (state, action) => { | |
state.createCommentIsPending = false; | |
state.failedToCreateComment = false; | |
state.byArticleId[action.payload.articleId].push(action.payload); | |
}, | |
[postCommentForArticleId.rejected]: (state, action) => { | |
state.createCommentIsPending = false; | |
state.failedToCreateComment = true; | |
}, | |
}, | |
}); | |
export const selectComments = (state) => state.comments.byArticleId; | |
export const isLoadingComments = (state) => state.comments.isLoadingComments; | |
export const createCommentIsPending = (state) => | |
state.comments.createCommentIsPending; | |
export default commentsSlice.reducer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect } from 'react'; | |
import { useDispatch, useSelector } from 'react-redux'; | |
import { | |
selectCurrentArticle, | |
isLoadingCurrentArticle, | |
} from '../currentArticle/currentArticleSlice'; | |
import FullArticle from '../../components/FullArticle'; | |
const CurrentArticle = () => { | |
const dispatch = useDispatch(); | |
const article = useSelector(selectCurrentArticle); | |
const currentArticleIsLoading = useSelector(isLoadingCurrentArticle); | |
if (currentArticleIsLoading) { | |
return <div>Loading</div>; | |
} else if (!article) { | |
return null; | |
} | |
return <FullArticle article={article} />; | |
}; | |
export default CurrentArticle; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
export const loadCurrentArticle = createAsyncThunk( | |
'currentArticle/loadCurrentArticle', | |
async (articleId) => { | |
const data = await fetch(`api/articles/${articleId}`); | |
const json = await data.json(); | |
return json; | |
} | |
); | |
export const currentArticleSlice = createSlice({ | |
name: 'currentArticle', | |
initialState: { | |
article: undefined, | |
isLoadingCurrentArticle: false, | |
hasError: false | |
}, | |
extraReducers: (builder) => { | |
builder | |
.addCase(loadCurrentArticle.pending, (state) => { | |
state.isLoadingCurrentArticle = true; | |
state.hasError = false; | |
}) | |
.addCase(loadCurrentArticle.fulfilled, (state, action) => { | |
state.isLoadingCurrentArticle = false; | |
state.hasError = false; | |
state.article = action.payload; | |
}) | |
.addCase(loadCurrentArticle.rejected, (state) => { | |
state.isLoadingCurrentArticle = false; | |
state.hasError = true; | |
state.article = {}; | |
}) | |
}, | |
}); | |
export const selectCurrentArticle = (state) => state.currentArticle.article; | |
export const isLoadingCurrentArticle = (state) => state.currentArticle.isLoadingCurrentArticle; | |
export default currentArticleSlice.reducer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
export default function FullArticle({ article }) { | |
return ( | |
<> | |
<div className='article-full-image-container'> | |
<img src={article.image} alt='' /> | |
</div> | |
<div key={article.id} className='current-article-container'> | |
<h1 className='current-article-title'>{article.title}</h1> | |
<div className='article-full-text'>{article.fullText}</div> | |
</div> | |
</> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { rest } from 'msw'; | |
import articlesData from './articles.json'; | |
import commentsData from './comments.json'; | |
const userComments = {}; | |
function mockDelay(milliseconds) { | |
const date = Date.now(); | |
let currentDate = null; | |
do { | |
currentDate = Date.now(); | |
} while (currentDate - date < milliseconds); | |
} | |
export const handlers = [ | |
rest.get('/api/articles', (req, res, ctx) => { | |
mockDelay(500); | |
return res( | |
ctx.status(200), | |
ctx.json( | |
articlesData.map((article) => ({ | |
id: article.id, | |
title: article.title, | |
preview: article.preview, | |
image: article.image, | |
})) | |
) | |
); | |
}), | |
rest.get('/api/articles/:articleId', (req, res, ctx) => { | |
mockDelay(500); | |
const { articleId } = req.params; | |
return res( | |
ctx.status(200), | |
ctx.json( | |
articlesData.find((article) => article.id === parseInt(articleId)) | |
) | |
); | |
}), | |
rest.get('/api/articles/:articleId/comments', (req, res, ctx) => { | |
mockDelay(500); | |
const { articleId } = req.params; | |
const userCommentsForArticle = userComments[articleId] || []; | |
return res( | |
ctx.status(200), | |
ctx.json({ | |
articleId: parseInt(articleId), | |
comments: commentsData | |
.filter((comment) => comment.articleId === parseInt(articleId)) | |
.concat(userCommentsForArticle), | |
}) | |
); | |
}), | |
rest.post('/api/articles/:articleId/comments', (req, res, ctx) => { | |
mockDelay(500); | |
const { articleId } = req.params; | |
const commentResponse = { | |
id: commentsData.length, | |
articleId: parseInt(articleId), | |
text: JSON.parse(req.body).comment, | |
}; | |
if (userComments[articleId]) { | |
userComments[articleId].push(commentResponse); | |
} else { | |
userComments[articleId] = [commentResponse]; | |
} | |
return res(ctx.status(200), ctx.json(commentResponse)); | |
}), | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<link rel="stylesheet" href="./app/App.css"> | |
<title>Learn ReactJS</title> | |
</head> | |
<body> | |
<main id="root"> | |
</main> | |
<script src="https://content.codecademy.com/courses/React/react-16-full.min.js"></script> | |
<script src="./index.compiled.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import App from './app/App'; | |
import store from './app/store'; | |
import { Provider } from 'react-redux'; | |
const { worker } = require('./mocks/browser'); | |
worker.start(); | |
ReactDOM.render( | |
<React.StrictMode> | |
<Provider store={store}> | |
<App /> | |
</Provider> | |
</React.StrictMode>, | |
document.getElementById('root') | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Mock Service Worker. | |
* @see https://github.com/mswjs/msw | |
* - Please do NOT modify this file. | |
* - Please do NOT serve this file on production. | |
*/ | |
/* eslint-disable */ | |
/* tslint:disable */ | |
const INTEGRITY_CHECKSUM = '65d33ca82955e1c5928aed19d1bdf3f9' | |
const bypassHeaderName = 'x-msw-bypass' | |
let clients = {} | |
self.addEventListener('install', function () { | |
return self.skipWaiting() | |
}) | |
self.addEventListener('activate', async function (event) { | |
return self.clients.claim() | |
}) | |
self.addEventListener('message', async function (event) { | |
const clientId = event.source.id | |
const client = await event.currentTarget.clients.get(clientId) | |
const allClients = await self.clients.matchAll() | |
const allClientIds = allClients.map((client) => client.id) | |
switch (event.data) { | |
case 'KEEPALIVE_REQUEST': { | |
sendToClient(client, { | |
type: 'KEEPALIVE_RESPONSE', | |
}) | |
break | |
} | |
case 'INTEGRITY_CHECK_REQUEST': { | |
sendToClient(client, { | |
type: 'INTEGRITY_CHECK_RESPONSE', | |
payload: INTEGRITY_CHECKSUM, | |
}) | |
break | |
} | |
case 'MOCK_ACTIVATE': { | |
clients = ensureKeys(allClientIds, clients) | |
clients[clientId] = true | |
sendToClient(client, { | |
type: 'MOCKING_ENABLED', | |
payload: true, | |
}) | |
break | |
} | |
case 'MOCK_DEACTIVATE': { | |
clients = ensureKeys(allClientIds, clients) | |
clients[clientId] = false | |
break | |
} | |
case 'CLIENT_CLOSED': { | |
const remainingClients = allClients.filter((client) => { | |
return client.id !== clientId | |
}) | |
// Unregister itself when there are no more clients | |
if (remainingClients.length === 0) { | |
self.registration.unregister() | |
} | |
break | |
} | |
} | |
}) | |
self.addEventListener('fetch', function (event) { | |
const { clientId, request } = event | |
const requestClone = request.clone() | |
const getOriginalResponse = () => fetch(requestClone) | |
// Bypass navigation requests. | |
if (request.mode === 'navigate') { | |
return | |
} | |
// Bypass mocking if the current client isn't present in the internal clients map | |
// (i.e. has the mocking disabled). | |
if (!clients[clientId]) { | |
return | |
} | |
// Opening the DevTools triggers the "only-if-cached" request | |
// that cannot be handled by the worker. Bypass such requests. | |
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { | |
return | |
} | |
event.respondWith( | |
new Promise(async (resolve, reject) => { | |
const client = await event.target.clients.get(clientId) | |
// Bypass mocking when the request client is not active. | |
if (!client) { | |
return resolve(getOriginalResponse()) | |
} | |
// Bypass requests with the explicit bypass header | |
if (requestClone.headers.get(bypassHeaderName) === 'true') { | |
const modifiedHeaders = serializeHeaders(requestClone.headers) | |
// Remove the bypass header to comply with the CORS preflight check | |
delete modifiedHeaders[bypassHeaderName] | |
const originalRequest = new Request(requestClone, { | |
headers: new Headers(modifiedHeaders), | |
}) | |
return resolve(fetch(originalRequest)) | |
} | |
const reqHeaders = serializeHeaders(request.headers) | |
const body = await request.text() | |
const rawClientMessage = await sendToClient(client, { | |
type: 'REQUEST', | |
payload: { | |
url: request.url, | |
method: request.method, | |
headers: reqHeaders, | |
cache: request.cache, | |
mode: request.mode, | |
credentials: request.credentials, | |
destination: request.destination, | |
integrity: request.integrity, | |
redirect: request.redirect, | |
referrer: request.referrer, | |
referrerPolicy: request.referrerPolicy, | |
body, | |
bodyUsed: request.bodyUsed, | |
keepalive: request.keepalive, | |
}, | |
}) | |
const clientMessage = rawClientMessage | |
switch (clientMessage.type) { | |
case 'MOCK_SUCCESS': { | |
setTimeout( | |
resolve.bind(this, createResponse(clientMessage)), | |
clientMessage.payload.delay, | |
) | |
break | |
} | |
case 'MOCK_NOT_FOUND': { | |
return resolve(getOriginalResponse()) | |
} | |
case 'NETWORK_ERROR': { | |
const { name, message } = clientMessage.payload | |
const networkError = new Error(message) | |
networkError.name = name | |
// Rejecting a request Promise emulates a network error. | |
return reject(networkError) | |
} | |
case 'INTERNAL_ERROR': { | |
const parsedBody = JSON.parse(clientMessage.payload.body) | |
console.error( | |
`\ | |
[MSW] Request handler function for "%s %s" has thrown the following exception: | |
${parsedBody.errorType}: ${parsedBody.message} | |
(see more detailed error stack trace in the mocked response body) | |
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error. | |
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ | |
`, | |
request.method, | |
request.url, | |
) | |
return resolve(createResponse(clientMessage)) | |
} | |
} | |
}).catch((error) => { | |
console.error( | |
'[MSW] Failed to mock a "%s" request to "%s": %s', | |
request.method, | |
request.url, | |
error, | |
) | |
}), | |
) | |
}) | |
function serializeHeaders(headers) { | |
const reqHeaders = {} | |
headers.forEach((value, name) => { | |
reqHeaders[name] = reqHeaders[name] | |
? [].concat(reqHeaders[name]).concat(value) | |
: value | |
}) | |
return reqHeaders | |
} | |
function sendToClient(client, message) { | |
return new Promise((resolve, reject) => { | |
const channel = new MessageChannel() | |
channel.port1.onmessage = (event) => { | |
if (event.data && event.data.error) { | |
reject(event.data.error) | |
} else { | |
resolve(event.data) | |
} | |
} | |
client.postMessage(JSON.stringify(message), [channel.port2]) | |
}) | |
} | |
function createResponse(clientMessage) { | |
return new Response(clientMessage.payload.body, { | |
...clientMessage.payload, | |
headers: clientMessage.payload.headers, | |
}) | |
} | |
function ensureKeys(keys, obj) { | |
return Object.keys(obj).reduce((acc, key) => { | |
if (keys.includes(key)) { | |
acc[key] = obj[key] | |
} | |
return acc | |
}, {}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { configureStore } from '@reduxjs/toolkit'; | |
import articlePreviewsReducer from '../features/articlePreviews/articlePreviewsSlice'; | |
import currentArticleReducer from '../features/currentArticle/currentArticleSlice'; | |
import commentsReducer from '../features/comments/commentsSlice'; | |
export default configureStore({ | |
reducer: { | |
articlePreviews: articlePreviewsReducer, | |
currentArticle: currentArticleReducer, | |
comments: commentsReducer | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment