diff --git a/README.md b/README.md index a942e08..76f1acf 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - hot-fix 핫 버그 수정 - bug-fix 버그 수정 - refactor 기능 수정 추가 및 코드 수정 +- wip 작업 중일 때 #### Commit Prefix diff --git a/package.json b/package.json index 86d8165..9633571 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,22 @@ "version": "0.1.0", "private": true, "dependencies": { + "axios": "0.21.1", "chalk": "4.1.0", "cross-env": "7.0.2", + "immutable": "4.0.0-rc.12", + "lodash-es": "4.17.21", "node-plop": "0.26.2", "plop": "2.7.4", "prettier": "2.1.1", "react": "16.13.1", "react-app-polyfill": "1.0.6", "react-dom": "16.13.1", + "react-redux": "7.2.2", "react-router-dom": "5.2.0", "react-scripts": "4.0.3", + "redux": "4.0.5", + "redux-saga": "1.1.3", "rimraf": "3.0.2", "sanitize.css": "12.0.1", "serve": "11.3.2", @@ -26,6 +32,7 @@ "@testing-library/react": "11.0.2", "@types/fontfaceobserver": "0.0.6", "@types/jest": "25.1.4", + "@types/lodash-es": "4.17.4", "@types/node": "14.6.4", "@types/react": "17.0.0", "@types/react-dom": "16.9.8", diff --git a/public/index.html b/public/index.html index 176324c..9519394 100644 --- a/public/index.html +++ b/public/index.html @@ -10,35 +10,12 @@ content="Web site created using create-react-app" /> - - + React App
- - diff --git a/src/App.tsx b/src/App.tsx index 427a581..ce4b5bc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,17 @@ import React from 'react' +import { BrowserRouter } from 'react-router-dom' +import { GlobalStyle } from 'global-styles' +import Routes from 'Routes' function App() { - return
hello world!
+ return ( + <> + + + + + + ) } export default App diff --git a/src/Routes.tsx b/src/Routes.tsx new file mode 100644 index 0000000..656e17a --- /dev/null +++ b/src/Routes.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react' +import { + Route, + RouteComponentProps, + Switch, + withRouter, +} from 'react-router-dom' +import Concerns from 'pages/Concerns' +import Concern from 'pages/Concern' + +const Routes: FC = () => { + return ( + + + + + ) +} + +export default withRouter(Routes) diff --git a/src/assets/etc.svg b/src/assets/etc.svg new file mode 100644 index 0000000..3109ba3 --- /dev/null +++ b/src/assets/etc.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/food.svg b/src/assets/food.svg new file mode 100644 index 0000000..8483ddf --- /dev/null +++ b/src/assets/food.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/heart.svg b/src/assets/heart.svg new file mode 100644 index 0000000..e5e7989 --- /dev/null +++ b/src/assets/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/job.svg b/src/assets/job.svg new file mode 100644 index 0000000..f4e0d52 --- /dev/null +++ b/src/assets/job.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/location.svg b/src/assets/location.svg new file mode 100644 index 0000000..0455ad5 --- /dev/null +++ b/src/assets/location.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/love.svg b/src/assets/love.svg new file mode 100644 index 0000000..6ee876f --- /dev/null +++ b/src/assets/love.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/relationship.svg b/src/assets/relationship.svg new file mode 100644 index 0000000..9f7305b --- /dev/null +++ b/src/assets/relationship.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/share.svg b/src/assets/share.svg new file mode 100644 index 0000000..ffa0474 --- /dev/null +++ b/src/assets/share.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/study.svg b/src/assets/study.svg new file mode 100644 index 0000000..80ed30b --- /dev/null +++ b/src/assets/study.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/.gitkeep b/src/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/ConcernItem/ConcernItem.styled.ts b/src/components/ConcernItem/ConcernItem.styled.ts new file mode 100644 index 0000000..108bc8b --- /dev/null +++ b/src/components/ConcernItem/ConcernItem.styled.ts @@ -0,0 +1,57 @@ +import styled from 'styled-components' + +export const ConcernWrapper = styled.div` + display: flex; + padding: 12px 8px; + background-color: ${({ theme }) => theme.colors.white}; + border-radius: 10px; +` + +export const CategoryWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin-right: 8px; + padding: 5px 0; +` + +export const CategoryImage = styled.img` + width: 52px; + height: 52px; +` + +export const Category = styled.p` + display: flex; + justify-content: center; + align-items: center; + width: 48px; + height: 16px; + margin-top: 5px; + background-color: ${({ theme }) => theme.colors.green}; + font-size: 12px; + border-radius: 8px; +` + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + flex: 1; +` + +export const Title = styled.p` + font-size: 14px; + font-weight: bold; +` + +export const Content = styled.p` + height: 32px; + margin-top: 6px; + overflow: hidden; + font-size: 12px; +` + +export const CreatedAt = styled.p` + margin-top: auto; + color: ${({ theme }) => theme.colors.purpleGray}; + font-size: 12px; +` diff --git a/src/components/ConcernItem/ConcernItem.tsx b/src/components/ConcernItem/ConcernItem.tsx new file mode 100644 index 0000000..2568ebf --- /dev/null +++ b/src/components/ConcernItem/ConcernItem.tsx @@ -0,0 +1,25 @@ +import React from 'react' + +import * as Styled from './ConcernItem.styled' + +interface ConcernItemProps { + concern: any +} + +function ConcernItem({ concern }: ConcernItemProps) { + return ( + + + + {concern.category.name} + + + {concern.title} + {concern.content} + {concern.createdAt} + + + ) +} + +export default ConcernItem diff --git a/src/components/ConcernItem/index.ts b/src/components/ConcernItem/index.ts new file mode 100644 index 0000000..af804a4 --- /dev/null +++ b/src/components/ConcernItem/index.ts @@ -0,0 +1 @@ +export { default } from './ConcernItem' diff --git a/src/components/ConcernList/ConcernList.styled.ts b/src/components/ConcernList/ConcernList.styled.ts new file mode 100644 index 0000000..8435919 --- /dev/null +++ b/src/components/ConcernList/ConcernList.styled.ts @@ -0,0 +1,11 @@ +import styled from 'styled-components' + +import { ConcernWrapper } from 'components/ConcernItem/ConcernItem.styled' + +export const ConcernListWrapper = styled.div` + padding: 0 16px; + + ${ConcernWrapper} + ${ConcernWrapper} { + margin-top: 12px; + } +` diff --git a/src/components/ConcernList/ConcernList.tsx b/src/components/ConcernList/ConcernList.tsx new file mode 100644 index 0000000..dca93df --- /dev/null +++ b/src/components/ConcernList/ConcernList.tsx @@ -0,0 +1,20 @@ +import React, { useMemo } from 'react' + +import ConcernItem from 'components/ConcernItem' +import * as Styled from './ConcernList.styled' + +interface ConcernListProps { + concernList: any +} + +function ConcernList({ concernList }: ConcernListProps) { + const concerns = useMemo(() => { + return concernList.map(concern => ( + + )) + }, [concernList]) + + return {concerns} +} + +export default ConcernList diff --git a/src/components/ConcernList/index.ts b/src/components/ConcernList/index.ts new file mode 100644 index 0000000..ebebae8 --- /dev/null +++ b/src/components/ConcernList/index.ts @@ -0,0 +1 @@ +export { default } from './ConcernList' diff --git a/src/components/answerlist/AnswerItem.tsx b/src/components/answerlist/AnswerItem.tsx new file mode 100644 index 0000000..32e3bba --- /dev/null +++ b/src/components/answerlist/AnswerItem.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react' +import styled from 'styled-components' +import { Answer } from 'types/Answer' +import heart from './../../assets/heart.svg' + +function AnswerItem({ name, contents, time, likeNum }: Answer) { + const [like, setLike] = useState(likeNum) + const addLike = () => setLike(like + 1) + + return ( + +
{name}
+
{contents}
+ +
{time}
+
+ heart icon + {like} +
+
+
+ ) +} + +export default AnswerItem + +const AnswerBlock = styled.div` + display: inline-block; + width: 328px; + height: fit-content; + margin: 15px 15px 0px; + text-align: left; + border-bottom: 1px solid #5e5474; + font-weight: normal; + font-size: 14px; + + .contents { + display: inline-block; + width: 328px; + height: fit-content; + margin: 10px 0px; + line-height: 130%; + } +` +const AnswerInfoBlock = styled.div` + display: flex; + margin-bottom: 10px; + font-weight: normal; + font-size: 12px; + + .time { + display: inline-block; + color: ${props => props.theme.colors.gray07}; + align-items: center; + justify-content: center; + padding-right: 120px; + padding-top: 10px; + } + + .likeButton { + display: flex; + width: 80px; + height: 24px; + align-items: center; + justify-content: center; + background-color: ${props => props.theme.colors.purpleGray}; + border-radius: 5px; + text-align: center; + cursor: pointer; + } +` diff --git a/src/components/answerlist/AnswerList.tsx b/src/components/answerlist/AnswerList.tsx new file mode 100644 index 0000000..2ac9a5a --- /dev/null +++ b/src/components/answerlist/AnswerList.tsx @@ -0,0 +1,70 @@ +import React, { useState } from 'react' +import AnswerItem from './AnswerItem' +import styled from 'styled-components' +import { AnswerListProps } from 'types/Answer' + +function AnswerList({ answers }: AnswerListProps) { + return ( + <> + + {answers.map(answer => ( + + ))} + + + 작성완료 + + ) +} + +export default AnswerList + +const AnswerListLayout = styled.div` + position: relative; + width: 360px; + height: fit-content; + margin-top: 20px; + padding-top: 40px; + background-color: ${props => props.theme.colors.darkGray1}; + border-radius: 32px 32px 0px 0px; + color: white; +` +const AnswerInput = styled.textarea` + display: inline-block; + width: 328px; + height: 140px; + resize: none; + margin: 10px 15px 20px; + background-color: transparent; + border-radius: 4px; + border-color: #5e5474; + outline: 0; + ::placeholder { + color: white; + padding: 10px; + font-weight: normal; + font-size: 12px; + } + padding: 10px; + font-weight: normal; + font-size: 12px; + color: white; +` +const AnswerInputButton = styled.button` + width: 360px; + height: 52px; + color: white; + background-color: #8567ff; + align-items: center; + justify-content: center; + border: 0; + outline: 0; + cursor: pointer; +` diff --git a/src/components/concern/Concern.styled.ts b/src/components/concern/Concern.styled.ts new file mode 100644 index 0000000..31f5630 --- /dev/null +++ b/src/components/concern/Concern.styled.ts @@ -0,0 +1,109 @@ +import styled from 'styled-components' + +export const ConcernCard = styled.div` + width: 300px; + height: 435px; + background-color: ${props => props.theme.colors.darkGray3}; + border: 1px solid ${props => props.theme.colors.point}; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 10px; +` +export const TitleBox = styled.div` + padding: 10px; +` + +export const CategoryWrapper = styled.div` + display: flex; + justify-content: space-between; +` +export const DistanceArea = styled.div` + font-size: 11px; + display: flex; + flex: 1; + align-items: center; + color: ${props => props.theme.colors.gray05}; +` + +export const CategoryArea = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + margin-top: 15px; +` + +export const AnimalArea = styled.div` + width: 100%; + display: flex; + justify-content: center; +` +export const HashtagArea = styled.div` + display: flex; +` + +export const Hashtag = styled.div<{ background?: string }>` + width: 64px; + height: 20px; + display: flex; + justify-content: center; + color: ${props => props.theme.colors.gray11}; + background: ${props => props.background}; + font-size: 12px; + align-items: center; + border-radius: 3px; + margin: 5px; +` + +export const Writer = styled.div` + color: ${props => props.theme.colors.gray05}; + font-size: 12px; + margin-top: 8px; +` + +export const ShareArea = styled.div` + flex: 1; + display: flex; + justify-content: flex-end; +` +export const Title = styled.div` + color: #fff; + font-size: 14px; + font-weight: 600; + margin-top: 8px; + text-align: center; +` + +export const DividerArea = styled.div` + display: flex; + justify-content: center; + padding: 10px 0 20px; +` +export const Divider = styled.div` + width: 280px; + border: 1px solid ${props => props.theme.colors.purpleGray}; +` +export const ContentBox = styled.div` + color: #fff; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 16px; +` +export const Content = styled.div` + font-weight: normal; + font-size: 14px; + line-height: 130%; +` +export const LikeButton = styled.div` + display: flex; + justify-content: center; + align-items: center; + font-size: 12px; + width: 80px; + height: 24px; + cursor: pointer; + border-radius: 3px; + background-color: ${props => props.theme.colors.purpleGray}; + margin-top: 16px; +` diff --git a/src/components/concern/ConcernCard.tsx b/src/components/concern/ConcernCard.tsx new file mode 100644 index 0000000..f049b6e --- /dev/null +++ b/src/components/concern/ConcernCard.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import * as Styled from './Concern.styled' +import location from './../../assets/location.svg' +import share from './../../assets/share.svg' +import love from './../../assets/love.svg' +import heart from './../../assets/heart.svg' +import colors from './../../constants/colors' +const ConcernCard = () => { + return ( + + + + + location icon 15km + + + location icon + + + + + + location icon + + + + # 연애고민 + + # 기쁨 + + 소양이팀 작성 + + + 남자친구의 판도라 상자를 열었따! + + + + + + + + 남자친구와 만난지 3년이 되어갑니다. 그런데 며칠전 남자친구의 휴대폰을 + 우연히 보게 되었어요... 평소 참 잘해주던 사람인데... 평소 참 잘해주던 + 사람인데... 평소 참 잘해주던 사람인데... 평소 참 잘해주던 사람인데... + 평소 참 잘해주던 사람인데... 평소 참 잘해주던 사람인데... 평소 참 + 잘해주던 사람인데... + + + location icon + 508 + + + + ) +} + +export default ConcernCard diff --git a/src/components/layout/Layout.styled.ts b/src/components/layout/Layout.styled.ts new file mode 100644 index 0000000..53ac40d --- /dev/null +++ b/src/components/layout/Layout.styled.ts @@ -0,0 +1,17 @@ +import styled from 'styled-components' + +export const MainLayout = styled.div` + min-width: 360px; + max-width: 640px; + min-height: 640px; + background-color: ${props => props.theme.colors.background}; +` +export const Header = styled('div')` + font-weight: bold; + font-size: 20px; + line-height: 23px; + text-align: center; + text-align: center; + color: #ffffff; + padding: 18px 0 20px; +` diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx new file mode 100644 index 0000000..ae1fb79 --- /dev/null +++ b/src/components/layout/index.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { MainLayout, Header } from './Layout.styled' + +const Layout = ({ + title = '', + children, +}: { + title: string + children: React.ReactNode +}) => { + return ( + +
{title}
+ {children} +
+ ) +} + +export default Layout diff --git a/src/constants/Gender.ts b/src/constants/Gender.ts new file mode 100644 index 0000000..e282e72 --- /dev/null +++ b/src/constants/Gender.ts @@ -0,0 +1,6 @@ +enum Gender { + Male = 'M', + Female = 'F', +} + +export default Gender diff --git a/src/constants/colors.ts b/src/constants/colors.ts new file mode 100644 index 0000000..eacc4b8 --- /dev/null +++ b/src/constants/colors.ts @@ -0,0 +1,25 @@ +const colors = { + gray01: '#EFEFEF', + gray02: '#DBD6DD', + gray03: '#D1CCD3', + gray04: '#BEB9C1', + gray05: '#9E9B9F', + gray06: '#858388', + gray07: '#716D74', + gray08: '#676569', + gray09: '#5B5A5C', + gray10: '#4B494D', + gray11: '#111111', + point: '#8567FF', + purpleGray: '#5E5474', + darkGray1: '#514184', + darkGray2: '#473D65', + darkGray3: '#41375F', + yellow: '#FFE815', + red: '#FF6060', + green: '#6DECA2', + background: '#312651', + white: '#FFF', +} + +export default colors diff --git a/src/constants/media.ts b/src/constants/media.ts new file mode 100644 index 0000000..26a47b7 --- /dev/null +++ b/src/constants/media.ts @@ -0,0 +1,17 @@ +const deviceSizes = { + mobileS: '320px', + mobileM: '375px', + mobileL: '450px', + tabletM: '768px', + tabletL: '1024px', +} + +const media = { + mobileS: `(max-width: ${deviceSizes.mobileS})`, + mobileM: `(max-width: ${deviceSizes.mobileM})`, + mobileL: `(max-width: ${deviceSizes.mobileL})`, + tabletM: `(max-width: ${deviceSizes.tabletM})`, + tabletL: `(max-width: ${deviceSizes.tabletL})`, +} + +export default media diff --git a/src/containers/.gitkeep b/src/containers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/containers/ConcernContainer.styled.ts b/src/containers/ConcernContainer.styled.ts new file mode 100644 index 0000000..273adde --- /dev/null +++ b/src/containers/ConcernContainer.styled.ts @@ -0,0 +1,6 @@ +import styled from 'styled-components' + +export const ConcernContainer = styled.div` + display: flex; + justify-content: center; +` diff --git a/src/containers/ConcernContainer.tsx b/src/containers/ConcernContainer.tsx new file mode 100644 index 0000000..7453222 --- /dev/null +++ b/src/containers/ConcernContainer.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import ConcernCard from 'components/concern/ConcernCard' +import * as Styled from './ConcernContainer.styled' + +const ConcernContainer = () => { + return ( + + + + ) +} +export default ConcernContainer diff --git a/src/containers/ConcernListContainer.tsx b/src/containers/ConcernListContainer.tsx new file mode 100644 index 0000000..16bd62d --- /dev/null +++ b/src/containers/ConcernListContainer.tsx @@ -0,0 +1,46 @@ +import React from 'react' + +import ConcernList from 'components/ConcernList' + +const MOCK_CONCERN_LIST = [ + { + id: 1, + title: 'title1', + content: 'content1', + createdAt: '2010.02.12 오전 08:00 ', + category: { + name: '연애', + imageUrl: '', + }, + }, + { + id: 2, + title: 'title2', + content: 'content2', + createdAt: '2010.02.12 오전 08:00 ', + category: { + name: '연애', + imageUrl: '', + }, + }, + { + id: 3, + title: 'title3', + content: 'content3', + createdAt: '2010.02.12 오전 08:00 ', + category: { + name: '연애', + imageUrl: '', + }, + }, +] + +function ConcernListContainer() { + return ( + <> + + + ) +} + +export default ConcernListContainer diff --git a/src/contants/.gitkeep b/src/contants/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/elements/SVGIcon/SVGIcon.styled.ts b/src/elements/SVGIcon/SVGIcon.styled.ts new file mode 100644 index 0000000..5f1d183 --- /dev/null +++ b/src/elements/SVGIcon/SVGIcon.styled.ts @@ -0,0 +1,19 @@ +/* External dependencies */ +import styled from 'styled-components' + +interface SVGIconWrapperProps { + size: number +} + +export const SVGIconWrapper = styled.div` + width: ${props => props.size}px; + height: ${props => props.size}px; + display: flex; + justify-content: center; + align-items: center; +` + +export const SVGIcon = styled.img` + width: 100%; + height: 100%; +` diff --git a/src/elements/SVGIcon/SVGIcon.tsx b/src/elements/SVGIcon/SVGIcon.tsx new file mode 100644 index 0000000..084f79d --- /dev/null +++ b/src/elements/SVGIcon/SVGIcon.tsx @@ -0,0 +1,32 @@ +/* External dependencies */ +import React, { useMemo } from 'react' +import { endsWith } from 'lodash-es' + +/* Internal dependencies */ +import * as Styled from './SVGIcon.styled' + +interface SVGIconProps { + name: string + size: number +} + +function SVGIcon({ name, size }: SVGIconProps) { + const src = useMemo(() => { + const fileName = endsWith(name, '.svg') ? name : `${name}.svg` + try { + return require(`assets/${fileName}`) + } catch (e) { + console.error( + `cannot find icon name ${name}. Pleace check again. error message : ${e.message}`, + ) + } + }, [name]) + + return ( + + + + ) +} + +export default SVGIcon diff --git a/src/elements/SVGIcon/index.ts b/src/elements/SVGIcon/index.ts new file mode 100644 index 0000000..87c0760 --- /dev/null +++ b/src/elements/SVGIcon/index.ts @@ -0,0 +1 @@ +export { default } from './SVGIcon' diff --git a/src/global-styles.ts b/src/global-styles.ts index 52d6633..a529c96 100644 --- a/src/global-styles.ts +++ b/src/global-styles.ts @@ -1,29 +1,52 @@ -import { createGlobalStyle } from 'styled-components'; +import { createGlobalStyle } from 'styled-components' export const GlobalStyle = createGlobalStyle` - html, - body { - height: 100%; - width: 100%; + html, body, div, span, applet, object, iframe, + h1, h2, h3, h4, h5, h6, p, blockquote, pre, + a, abbr, acronym, address, big, cite, code, + del, dfn, em, img, ins, kbd, q, s, samp, + small, strike, strong, sub, sup, tt, var, + b, u, i, center, + dl, dt, dd, ol, ul, li, + fieldset, form, label, legend, + table, caption, tbody, tfoot, thead, tr, th, td, + article, aside, canvas, details, embed, + figure, figcaption, footer, header, hgroup, + menu, nav, output, ruby, section, summary, + time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; + } + /* HTML5 display-role reset for older browsers */ + article, aside, details, figcaption, figure, + footer, header, hgroup, menu, nav, section { + display: block; } - body { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1; + font-family: 'NanumSquareRound', sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; } - - #root { - min-height: 100%; - min-width: 100%; + ol, ul { + list-style: none; } - - p, - label { - font-family: Georgia, Times, 'Times New Roman', serif; - line-height: 1.5em; + blockquote, q { + quotes: none; } - - input, select { - font-family: inherit; - font-size: inherit; + blockquote:before, blockquote:after, + q:before, q:after { + content: ''; + content: none; + } + table { + border-collapse: collapse; + border-spacing: 0; } -`; +` diff --git a/src/index.tsx b/src/index.tsx index c6af1d2..dbdde9f 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,14 +3,28 @@ import 'react-app-polyfill/stable' import React from 'react' import ReactDOM from 'react-dom' +import { Provider } from 'react-redux' +import { ThemeProvider } from 'styled-components' +import ReduxStore from 'modules/reduxStore' +import colors from 'constants/colors' +import media from 'constants/media' import App from 'App' import * as serviceWorker from 'serviceWorker' import 'sanitize.css/sanitize.css' +const theme = { + colors, + media, +} + ReactDOM.render( - + + + + + , document.getElementById('root'), ) diff --git a/src/models/Answer.ts b/src/models/Answer.ts new file mode 100644 index 0000000..048add0 --- /dev/null +++ b/src/models/Answer.ts @@ -0,0 +1,22 @@ +/* External dependencies */ +import Immutable from 'immutable' + +interface AnswerAttr { + id: number + content: string + createdAt: string + updatedAt: string + deletedAt: string | null +} + +const AnswerRecord = Immutable.Record({ + id: 0, + content: '', + createdAt: '', + updatedAt: '', + deletedAt: '', +}) + +class Answer extends AnswerRecord {} + +export default Answer diff --git a/src/models/Concern.ts b/src/models/Concern.ts new file mode 100644 index 0000000..cc6504d --- /dev/null +++ b/src/models/Concern.ts @@ -0,0 +1,26 @@ +/* External dependencies */ +import Immutable from 'immutable' + +export interface ConcernAttrPOJO { + id: number + title: string + content: string + createdAt: string + updatedAt: string + deletedAt: string +} + +interface ConcernAttr extends ConcernAttrPOJO {} + +const ConcernRecord = Immutable.Record({ + id: 0, + title: '', + content: '', + createdAt: '', + updatedAt: '', + deletedAt: '', +}) + +class Concern extends ConcernRecord {} + +export default Concern diff --git a/src/models/Location.ts b/src/models/Location.ts new file mode 100644 index 0000000..4619e6f --- /dev/null +++ b/src/models/Location.ts @@ -0,0 +1,18 @@ +/* External dependencies */ +import Immutable from 'immutable' + +interface LocationAttr { + id: number + latitude: number + longitude: number +} + +const LocationRecord = Immutable.Record({ + id: 0, + latitude: 0, + longitude: 0, +}) + +class Location extends LocationRecord {} + +export default Location diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..962cbaa --- /dev/null +++ b/src/models/User.ts @@ -0,0 +1,28 @@ +/* External dependencies */ +import Immutable from 'immutable' + +/* Internal dependencies */ +import Location from 'models/Location' +import Gender from 'constants/Gender' + +interface UserAttr { + id: number + nickname: string + birthday: string + gender: Gender + imageUrl: string + location: Location +} + +const UserRecord = Immutable.Record({ + id: 0, + nickname: '', + birthday: '', + gender: Gender.Male, + imageUrl: '', + location: new Location(), +}) + +class User extends UserRecord {} + +export default User diff --git a/src/modules/apis/concernAPI.ts b/src/modules/apis/concernAPI.ts new file mode 100644 index 0000000..0bcf974 --- /dev/null +++ b/src/modules/apis/concernAPI.ts @@ -0,0 +1,50 @@ +/* External dependencies */ +import axios from 'axios' + +/* Internal dependencies */ +import { GetConcernListPayload } from 'modules/reducers/concernReducer' +import { ConcernAttrPOJO } from 'models/Concern' +import { ResponseType } from 'utils/reduxUtils' + +export type getConcernListResponseType = ConcernAttrPOJO[] + +const MOCK_CONCERN_LIST = { + data: [ + { + id: 1, + title: 'title1', + content: 'content1', + createdAt: '2021-03-08', + updatedAt: '2021-03-08', + deletedAt: '2021-03-08', + }, + { + id: 2, + title: 'title2', + content: 'content2', + createdAt: '2021-03-08', + updatedAt: '2021-03-08', + deletedAt: '2021-03-08', + }, + { + id: 3, + title: 'title3', + content: 'content3', + createdAt: '2021-03-08', + updatedAt: '2021-03-08', + deletedAt: '2021-03-08', + }, + ], +} + +export const getConcernList: ResponseType = () => { + return new Promise(resolve => { + setTimeout(() => { + resolve(MOCK_CONCERN_LIST as any) + }, 300) + }) +} + +// export const getConcernList: ResponseType = ({/* concernList payload */}: GetConcernListPayload) => { +// return axios.get(/* 서버 api */) +// } diff --git a/src/modules/reducers/concernReducer.ts b/src/modules/reducers/concernReducer.ts new file mode 100644 index 0000000..39ef382 --- /dev/null +++ b/src/modules/reducers/concernReducer.ts @@ -0,0 +1,94 @@ +/* External dependencies */ +import Immutable from 'immutable' +import { takeLatest } from 'redux-saga/effects' + +/* Internal dependencies */ +import Concern, { ConcernAttrPOJO } from 'models/Concern' +import * as concernAPI from 'modules/apis/concernAPI' +import { + AsyncActionTypes, + actionCreator, + createAsyncActionsAndSaga, +} from 'utils/reduxUtils' + +type Action = AsyncActionTypes + +interface State { + concernList: Immutable.List + isFetching: boolean + hasSuccess: boolean + hasError: boolean +} + +export interface GetConcernListPayload {} + +const GET_CONCERN_LIST = 'concern/GET_CONCERN_LIST' as const +const GET_CONCERN_LIST_FETCHING = 'concern/GET_CONCERN_LIST_FETCHING' as const +const GET_CONCERN_LIST_SUCCESS = 'concern/GET_CONCERN_LIST_SUCCESS' as const +const GET_CONCERN_LIST_ERROR = 'concern/GET_CONCERN_LIST_ERROR' as const + +export const getConcernList = actionCreator( + GET_CONCERN_LIST, + { usePromise: true }, +) + +const { + asyncActions: getConcernListAsyncActions, + asyncSaga: getConcernListSaga, +} = createAsyncActionsAndSaga( + GET_CONCERN_LIST_FETCHING, + GET_CONCERN_LIST_SUCCESS, + GET_CONCERN_LIST_ERROR, +)< + ReturnType, + concernAPI.getConcernListResponseType, + any +>(concernAPI.getConcernList) + +export function* concernSaga() { + yield takeLatest(GET_CONCERN_LIST, getConcernListSaga) +} + +const initialState: State = { + concernList: Immutable.List(), + isFetching: false, + hasSuccess: false, + hasError: false, +} + +function conceruReducer(state: State = initialState, action: Action) { + switch (action.type) { + case GET_CONCERN_LIST_FETCHING: { + return { + ...state, + concernList: state.concernList.clear(), + isFetching: true, + hasSuccess: false, + hasError: false, + } + } + case GET_CONCERN_LIST_SUCCESS: { + return { + ...state, + concernList: state.concernList.withMutations(list => { + action.payload.forEach((concern: ConcernAttrPOJO) => { + list.push(new Concern(concern)) + }) + }), + isFetching: false, + hasSuccess: true, + } + } + case GET_CONCERN_LIST_ERROR: { + return { + ...state, + isFetching: false, + hasError: true, + } + } + default: + return state + } +} + +export default conceruReducer diff --git a/src/modules/reducers/index.ts b/src/modules/reducers/index.ts new file mode 100644 index 0000000..7c6d408 --- /dev/null +++ b/src/modules/reducers/index.ts @@ -0,0 +1,15 @@ +/* External dependencies */ +import { combineReducers } from 'redux' +import { all } from 'redux-saga/effects' +import concern, { concernSaga } from './concernReducer' + +const rootReducer = combineReducers({ + concern, +}) + +export function* rootSaga() { + yield all([concernSaga()]) +} + +export default rootReducer +export type RootState = ReturnType diff --git a/src/modules/reduxStore.ts b/src/modules/reduxStore.ts new file mode 100644 index 0000000..635cd0e --- /dev/null +++ b/src/modules/reduxStore.ts @@ -0,0 +1,46 @@ +/* External dependencies */ +import { createStore, applyMiddleware, compose } from 'redux' +import createSagaMiddleware from 'redux-saga' + +/* Internal dependencies */ +import rootReducer, { rootSaga } from 'modules/reducers' +import { isDevelopment } from 'utils/environmentUtils' + +declare global { + interface Window { + __PRELOADED_STATE__: any + } +} + +class ReduxStore { + readonly store + + constructor() { + const isDev = isDevelopment() + // @ts-ignore + const devtools = isDev && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + const composeEnhancers = devtools || compose + + const sagaMiddleware = createSagaMiddleware() + this.store = createStore( + rootReducer, + window.__PRELOADED_STATE__, + composeEnhancers(applyMiddleware(sagaMiddleware)), + ) + sagaMiddleware.run(rootSaga) + } + + getStore() { + return this.store + } + + dispatch(action) { + return this.store.dispatch(action) + } + + getState() { + return this.store.getState() + } +} + +export default new ReduxStore() diff --git a/src/pages/.gitkeep b/src/pages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/Concern.tsx b/src/pages/Concern.tsx new file mode 100644 index 0000000..bd4281b --- /dev/null +++ b/src/pages/Concern.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import Layout from 'components/layout' +import ConcernContainer from 'containers/ConcernContainer' +import { Answer } from 'types/Answer' +import AnswerList from 'components/answerlist/AnswerList' + +const Concern = () => ( + + + + +) +export default Concern + +// dummy +const dummy: Answer[] = [ + { + id: 1, + name: '또또로', + contents: + '남자친구와 만난지 3년이 되어갑니다. 그런데 며칠전 남자친구의 휴대폰을 우연히 보게 되었어요...', + time: '2010.02.12 오전 08:00', + likeNum: 10, + }, + { + id: 2, + name: '로또', + contents: + '남자친구와 만난지 3년이 되어갑니다. 그런데 며칠전 남자친구의 휴대폰을 우연히 보게 되었어요...', + time: '2010.02.12 오전 08:01', + likeNum: 20, + }, +] diff --git a/src/pages/Concerns.tsx b/src/pages/Concerns.tsx new file mode 100644 index 0000000..ffaacb3 --- /dev/null +++ b/src/pages/Concerns.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import Layout from 'components/layout' +import ConcernListContainer from 'containers/ConcernListContainer' + +const Concerns = () => { + return ( + + + + ) +} + +export default Concerns diff --git a/src/types/Answer.ts b/src/types/Answer.ts new file mode 100644 index 0000000..2aad7e3 --- /dev/null +++ b/src/types/Answer.ts @@ -0,0 +1,10 @@ +export interface AnswerListProps { + answers: Answer[] +} +export interface Answer { + id: number + name: string + contents: string + time: string + likeNum: number +} diff --git a/src/utils/.gitkeep b/src/utils/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/environmentUtils.ts b/src/utils/environmentUtils.ts new file mode 100644 index 0000000..d2da331 --- /dev/null +++ b/src/utils/environmentUtils.ts @@ -0,0 +1,11 @@ +export function isProduction() { + return process.env.NODE_ENV === 'production' +} + +export function isDevelopment() { + return process.env.NODE_ENV === 'development' +} + +export function getEnvironment() { + return process.env.NODE_ENV +} diff --git a/src/utils/reduxUtils.ts b/src/utils/reduxUtils.ts new file mode 100644 index 0000000..a0013eb --- /dev/null +++ b/src/utils/reduxUtils.ts @@ -0,0 +1,87 @@ +/* External dependencies */ +import { call, put } from 'redux-saga/effects' +import { get, set, isFunction } from 'lodash' + +export type AsyncActionTypes< + T extends { [K in keyof T]: (...args: any[]) => any } +> = ReturnType + +export interface OptionType { + usePromise?: boolean +} + +export interface LifeCycle { + resolve?: any + reject?: any +} + +export interface ActionType { + type: string + payload: T + meta: S & LifeCycle + promise?: Promise +} + +export type ActionGenerator = ( + payload?: T, + meta?: S, +) => ActionType + +export type ResponseType = (...args: any[]) => Promise + +export function actionCreator( + actionType: string, + option: OptionType = {}, +): ActionGenerator { + return (payload: any = {}, meta: any = {}) => { + const action = { + type: actionType, + payload, + meta, + } + if (option.usePromise) { + const promise = new Promise((resolve, reject) => { + set(action, ['meta', 'resolve'], resolve) + set(action, ['meta', 'reject'], reject) + }) + set(action, 'promise', promise) + } + + return action + } +} + +export const createAsyncActionsAndSaga = ( + fetching: F, + success: S, + error: E, +) => (request: ResponseType) => { + const asyncActions = { + fetching: () => ({ type: fetching }), + success: (payload: SP) => ({ type: success, payload }), + error: (payload: EP) => ({ type: error, payload }), + } + const asyncSaga = function* (action: ActionType) { + yield put(asyncActions.fetching()) + try { + const { data } = yield call(request, action.payload) + const resolve = get(action, ['meta', 'resolve']) + + if (isFunction(resolve)) { + resolve(data) + } + + yield put(asyncActions.success(data)) + } catch (error) { + const reject = get(action, ['meta', 'reject']) + + if (isFunction(reject)) { + reject(error) + } + + yield put(asyncActions.error(error)) + } + } + + return { asyncActions, asyncSaga } +} diff --git a/yarn.lock b/yarn.lock index 74d2bfb..f16aa1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1118,6 +1118,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.6.3": + version "7.13.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee" + integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" @@ -1482,6 +1489,50 @@ schema-utils "^2.6.5" source-map "^0.7.3" +"@redux-saga/core@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4" + integrity sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg== + dependencies: + "@babel/runtime" "^7.6.3" + "@redux-saga/deferred" "^1.1.2" + "@redux-saga/delay-p" "^1.1.2" + "@redux-saga/is" "^1.1.2" + "@redux-saga/symbols" "^1.1.2" + "@redux-saga/types" "^1.1.0" + redux "^4.0.4" + typescript-tuple "^2.2.1" + +"@redux-saga/deferred@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/deferred/-/deferred-1.1.2.tgz#59937a0eba71fff289f1310233bc518117a71888" + integrity sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ== + +"@redux-saga/delay-p@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/delay-p/-/delay-p-1.1.2.tgz#8f515f4b009b05b02a37a7c3d0ca9ddc157bb355" + integrity sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g== + dependencies: + "@redux-saga/symbols" "^1.1.2" + +"@redux-saga/is@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/is/-/is-1.1.2.tgz#ae6c8421f58fcba80faf7cadb7d65b303b97e58e" + integrity sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w== + dependencies: + "@redux-saga/symbols" "^1.1.2" + "@redux-saga/types" "^1.1.0" + +"@redux-saga/symbols@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/symbols/-/symbols-1.1.2.tgz#216a672a487fc256872b8034835afc22a2d0595d" + integrity sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ== + +"@redux-saga/types@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" + integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== + "@rollup/plugin-node-resolve@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca" @@ -1867,6 +1918,18 @@ "@types/interpret" "*" "@types/node" "*" +"@types/lodash-es@4.17.4": + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.4.tgz#b2e440d2bf8a93584a9fd798452ec497986c9b97" + integrity sha512-BBz79DCJbD2CVYZH67MBeHZRX++HF+5p8Mo5MzjZi64Wac39S3diedJYHZtScbRVf4DjZyN6LzA0SB0zy+HSSQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + "@types/mdast@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" @@ -2809,6 +2872,13 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224" integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg== +axios@0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -5789,6 +5859,11 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== +follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -6306,7 +6381,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6559,6 +6634,11 @@ immer@8.0.1: resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== +immutable@4.0.0-rc.12: + version "4.0.0-rc.12" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0-rc.12.tgz#ca59a7e4c19ae8d9bf74a97bdf0f6e2f2a5d0217" + integrity sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A== + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -8006,6 +8086,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -10556,7 +10641,7 @@ react-error-overlay@^6.0.9: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -10566,6 +10651,17 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-redux@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" + integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA== + dependencies: + "@babel/runtime" "^7.12.1" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.13.1" + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -10781,6 +10877,21 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux-saga@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112" + integrity sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw== + dependencies: + "@redux-saga/core" "^1.1.3" + +redux@4.0.5, redux@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -12249,6 +12360,11 @@ swap-case@^1.1.0: lower-case "^1.1.1" upper-case "^1.1.1" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -12657,6 +12773,25 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript-compare@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425" + integrity sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA== + dependencies: + typescript-logic "^0.0.0" + +typescript-logic@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/typescript-logic/-/typescript-logic-0.0.0.tgz#66ebd82a2548f2b444a43667bec120b496890196" + integrity sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q== + +typescript-tuple@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/typescript-tuple/-/typescript-tuple-2.2.1.tgz#7d9813fb4b355f69ac55032e0363e8bb0f04dad2" + integrity sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q== + dependencies: + typescript-compare "^0.0.2" + typescript@4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"