From 11fd582453fc2b2b4fe008e31b27cf205e87a074 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Mon, 22 Mar 2021 15:56:35 +0700 Subject: [PATCH] #23 lab stats --- src/components/lab/LabBanner/index.tsx | 21 ++++-- .../lab/LabBanner/styles.module.scss | 16 ++++- src/components/lab/LabHero/index.tsx | 53 ++++++++++---- src/components/lab/LabHero/styles.module.scss | 28 +++++++- src/components/lab/LabHeroes/index.tsx | 34 +++++++++ src/components/lab/LabNode/index.tsx | 4 -- src/components/lab/LabTags/index.tsx | 36 ++++++++++ src/components/lab/LabTags/styles.module.scss | 10 +++ src/constants/api.ts | 1 + src/containers/lab/LabLayout/index.tsx | 72 ++----------------- src/containers/lab/LabStats/index.tsx | 60 ++++++++++++++++ .../lab/LabStats/styles.module.scss | 17 +++++ src/redux/lab/actions.ts | 9 +++ src/redux/lab/api.ts | 4 +- src/redux/lab/constants.ts | 3 + src/redux/lab/handlers.ts | 11 ++- src/redux/lab/index.ts | 7 ++ src/redux/lab/sagas.ts | 17 ++++- src/redux/lab/selectors.ts | 3 + src/redux/lab/types.ts | 13 +++- src/styles/_colors.scss | 4 +- src/styles/_global.scss | 4 ++ src/utils/node.ts | 2 +- 23 files changed, 328 insertions(+), 101 deletions(-) create mode 100644 src/components/lab/LabHeroes/index.tsx create mode 100644 src/components/lab/LabTags/index.tsx create mode 100644 src/components/lab/LabTags/styles.module.scss create mode 100644 src/containers/lab/LabStats/index.tsx create mode 100644 src/containers/lab/LabStats/styles.module.scss diff --git a/src/components/lab/LabBanner/index.tsx b/src/components/lab/LabBanner/index.tsx index df8f60ea..856a7844 100644 --- a/src/components/lab/LabBanner/index.tsx +++ b/src/components/lab/LabBanner/index.tsx @@ -9,12 +9,21 @@ interface IProps {} const LabBanner: FC = () => ( - - - - - - +
Лаборатория!
+ + +

+ + Всё, что происходит здесь — всего лишь эксперимент, о котором не узнает никто за + пределами Убежища. + +

+ +

+ Ловим радиоактивных жуков, приручаем утконосов-вампиров, катаемся на младшем научном + сотруднике Егоре Порсифоровиче (у него как раз сейчас линька). +

+
); diff --git a/src/components/lab/LabBanner/styles.module.scss b/src/components/lab/LabBanner/styles.module.scss index f235ed41..c82c7319 100644 --- a/src/components/lab/LabBanner/styles.module.scss +++ b/src/components/lab/LabBanner/styles.module.scss @@ -1,5 +1,19 @@ @import "~/styles/variables.scss"; .wrap { - background: $red_gradient_alt; + background: linear-gradient(darken($dark_blue, 0%), darken($blue, 30%)); +} + +.title { + font: $font_24_bold; + text-transform: uppercase; +} + +.content { + font: $font_14_regular; + line-height: 19px; + + strong { + font-weight: bold; + } } diff --git a/src/components/lab/LabHero/index.tsx b/src/components/lab/LabHero/index.tsx index 1a059815..be4d28d1 100644 --- a/src/components/lab/LabHero/index.tsx +++ b/src/components/lab/LabHero/index.tsx @@ -1,22 +1,51 @@ -import React, { FC } from 'react'; +import React, { FC, useCallback } from 'react'; import { Placeholder } from '~/components/placeholders/Placeholder'; import { Group } from '~/components/containers/Group'; import { Icon } from '~/components/input/Icon'; import styles from './styles.module.scss'; +import { INode } from '~/redux/types'; +import { getPrettyDate } from '~/utils/dom'; +import { URLS } from '~/constants/urls'; +import { Link, useHistory } from 'react-router-dom'; -interface IProps {} +interface IProps { + node?: Partial; + isLoading?: boolean; +} -const LabHero: FC = () => ( - -
- -
+const LabHero: FC = ({ node, isLoading }) => { + const history = useHistory(); + const onClick = useCallback(() => { + history.push(URLS.NODE_URL(node?.id)); + }, [history, node]); - - - + if (!node || isLoading) { + return ( + +
+ +
+ +
+ + +
+
+ ); + } + + return ( + +
+ +
+ +
+
{node.title}
+
{getPrettyDate(node.created_at)}
+
-
-); + ); +}; export { LabHero }; diff --git a/src/components/lab/LabHero/styles.module.scss b/src/components/lab/LabHero/styles.module.scss index b1a7e9cc..bb6f9b8d 100644 --- a/src/components/lab/LabHero/styles.module.scss +++ b/src/components/lab/LabHero/styles.module.scss @@ -1,10 +1,34 @@ @import "~/styles/variables.scss"; .wrap { - margin-bottom: $gap; + min-width: 0; + text-decoration: none; + cursor: pointer; } .star { - fill: #2c2c2c; + fill: darken(white, 76%); + flex: 0 0 32px; +} + +.title { + font: $font_18_semibold; + text-overflow: ellipsis; + line-height: 22px; + word-break: break-all; + color: darken(white, 40%); + + @include clamp(2, 22px) +} + +.description { + font: $font_10_regular; + color: darken(white, 50%); + padding-top: 4px; +} + +.content { + padding: $gap / 2 0; + text-decoration: none; } diff --git a/src/components/lab/LabHeroes/index.tsx b/src/components/lab/LabHeroes/index.tsx new file mode 100644 index 00000000..96a3643c --- /dev/null +++ b/src/components/lab/LabHeroes/index.tsx @@ -0,0 +1,34 @@ +import React, { FC } from 'react'; +import { INode } from '~/redux/types'; +import styles from '~/containers/lab/LabStats/styles.module.scss'; +import { LabHero } from '~/components/lab/LabHero'; +import { Group } from '~/components/containers/Group'; + +interface IProps { + nodes: Partial[]; + isLoading: boolean; +} + +const empty = [...new Array(5)].map((_, i) => i); + +const LabHeroes: FC = ({ nodes, isLoading }) => { + if (isLoading) { + return ( + + {empty.map(i => ( + + ))} + + ); + } + + return ( + + {nodes.slice(0, 7).map(node => ( + + ))} + + ); +}; + +export { LabHeroes }; diff --git a/src/components/lab/LabNode/index.tsx b/src/components/lab/LabNode/index.tsx index e4207484..9b5a212a 100644 --- a/src/components/lab/LabNode/index.tsx +++ b/src/components/lab/LabNode/index.tsx @@ -1,11 +1,7 @@ import React, { FC } from 'react'; import { INode } from '~/redux/types'; -import { NodePanelInner } from '~/components/node/NodePanelInner'; import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks'; import styles from './styles.module.scss'; -import { Card } from '~/components/containers/Card'; -import { LabNodeTitle } from '~/components/lab/LabNodeTitle'; -import { Grid } from '~/components/containers/Grid'; interface IProps { node: INode; diff --git a/src/components/lab/LabTags/index.tsx b/src/components/lab/LabTags/index.tsx new file mode 100644 index 00000000..29a527d6 --- /dev/null +++ b/src/components/lab/LabTags/index.tsx @@ -0,0 +1,36 @@ +import React, { FC } from 'react'; +import styles from './/styles.module.scss'; +import { Placeholder } from '~/components/placeholders/Placeholder'; +import { ITag } from '~/redux/types'; +import { Tag } from '~/components/tags/Tag'; +import { Group } from '~/components/containers/Group'; + +interface IProps { + tags: ITag[]; + isLoading: boolean; +} + +const LabTags: FC = ({ tags, isLoading }) => { + if (isLoading) { + return ( +
+ + + + + + +
+ ); + } + + return ( +
+ {tags.slice(0, 10).map(tag => ( + + ))} +
+ ); +}; + +export { LabTags }; diff --git a/src/components/lab/LabTags/styles.module.scss b/src/components/lab/LabTags/styles.module.scss new file mode 100644 index 00000000..41f211dd --- /dev/null +++ b/src/components/lab/LabTags/styles.module.scss @@ -0,0 +1,10 @@ +@import "~/styles/variables.scss"; + +.tags { + display: flex; + flex-wrap: wrap; + + & > * { + margin: $gap / 2; + } +} diff --git a/src/constants/api.ts b/src/constants/api.ts index c9c4c287..e968e3e8 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -52,5 +52,6 @@ export const API = { }, LAB: { NODES: `/lab/`, + STATS: '/lab/stats', }, }; diff --git a/src/containers/lab/LabLayout/index.tsx b/src/containers/lab/LabLayout/index.tsx index df778d28..d27e9136 100644 --- a/src/containers/lab/LabLayout/index.tsx +++ b/src/containers/lab/LabLayout/index.tsx @@ -5,7 +5,7 @@ import { Sticky } from '~/components/containers/Sticky'; import { Container } from '~/containers/main/Container'; import { LabGrid } from '~/containers/lab/LabGrid'; import { useDispatch } from 'react-redux'; -import { labGetList } from '~/redux/lab/actions'; +import { labGetList, labGetStats } from '~/redux/lab/actions'; import { Placeholder } from '~/components/placeholders/Placeholder'; import { Grid } from '~/components/containers/Grid'; import { Group } from '~/components/containers/Group'; @@ -13,6 +13,7 @@ import { LabHero } from '~/components/lab/LabHero'; import { LabBanner } from '~/components/lab/LabBanner'; import { LabHead } from '~/components/lab/LabHead'; import { Filler } from '~/components/containers/Filler'; +import { LabStats } from '~/containers/lab/LabStats'; interface IProps {} @@ -21,6 +22,7 @@ const LabLayout: FC = () => { useEffect(() => { dispatch(labGetList()); + dispatch(labGetStats()); }, [dispatch]); return ( @@ -34,73 +36,7 @@ const LabLayout: FC = () => {
- - - - - - - - - - - -
-
- - - -
- -
- - - - - - -
- -
-
- - - -
- - - -
- -
- -
- -
- -
- -
- - - -
-
- - - - - - -
- - - - - - - - +
diff --git a/src/containers/lab/LabStats/index.tsx b/src/containers/lab/LabStats/index.tsx new file mode 100644 index 00000000..e1dbab77 --- /dev/null +++ b/src/containers/lab/LabStats/index.tsx @@ -0,0 +1,60 @@ +import React, { FC } from 'react'; +import styles from './styles.module.scss'; +import { LabBanner } from '~/components/lab/LabBanner'; +import { Card } from '~/components/containers/Card'; +import { Group } from '~/components/containers/Group'; +import { Placeholder } from '~/components/placeholders/Placeholder'; +import { Filler } from '~/components/containers/Filler'; +import { LabHero } from '~/components/lab/LabHero'; +import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; +import { + selectLabStatsHeroes, + selectLabStatsLoading, + selectLabStatsTags, +} from '~/redux/lab/selectors'; +import { LabTags } from '~/components/lab/LabTags'; +import { LabHeroes } from '~/components/lab/LabHeroes'; + +interface IProps {} + +const LabStats: FC = () => { + const tags = useShallowSelect(selectLabStatsTags); + const heroes = useShallowSelect(selectLabStatsHeroes); + const isLoading = useShallowSelect(selectLabStatsLoading); + + return ( + + + + + + {isLoading ? ( + + ) : ( +
Тэги
+ )} + +
+ +
+ +
+
+
+ + {isLoading ? ( + + ) : ( +
Важные
+ )} + +
+ +
+ + + + ); +}; + +export { LabStats }; diff --git a/src/containers/lab/LabStats/styles.module.scss b/src/containers/lab/LabStats/styles.module.scss new file mode 100644 index 00000000..15742aa5 --- /dev/null +++ b/src/containers/lab/LabStats/styles.module.scss @@ -0,0 +1,17 @@ +@import "~/styles/variables.scss"; + +.title { + font: $font_14_semibold; + color: darken(white, 50%); + text-transform: uppercase; + padding: 0 $gap / 2; + padding-bottom: $gap / 2; +} + +.tags.tags { + margin: 0 -$gap / 2; +} + +.heroes { + margin-top: -$gap; +} diff --git a/src/redux/lab/actions.ts b/src/redux/lab/actions.ts index 1e1ff97a..438a8ad6 100644 --- a/src/redux/lab/actions.ts +++ b/src/redux/lab/actions.ts @@ -10,3 +10,12 @@ export const labSetList = (list: Partial) => ({ type: LAB_ACTIONS.SET_LIST, list, }); + +export const labGetStats = () => ({ + type: LAB_ACTIONS.GET_STATS, +}); + +export const labSetStats = (stats: Partial) => ({ + type: LAB_ACTIONS.SET_STATS, + stats, +}); diff --git a/src/redux/lab/api.ts b/src/redux/lab/api.ts index 5fa97bc0..16bac864 100644 --- a/src/redux/lab/api.ts +++ b/src/redux/lab/api.ts @@ -1,8 +1,10 @@ import { api, cleanResult } from '~/utils/api'; import { API } from '~/constants/api'; -import { GetLabNodesRequest, GetLabNodesResult } from '~/redux/lab/types'; +import { GetLabNodesRequest, GetLabNodesResult, GetLabStatsResult } from '~/redux/lab/types'; export const getLabNodes = ({ after }: GetLabNodesRequest) => api .get(API.LAB.NODES, { params: { after } }) .then(cleanResult); + +export const getLabStats = () => api.get(API.LAB.STATS).then(cleanResult); diff --git a/src/redux/lab/constants.ts b/src/redux/lab/constants.ts index d2e670da..0b7979b8 100644 --- a/src/redux/lab/constants.ts +++ b/src/redux/lab/constants.ts @@ -3,4 +3,7 @@ const prefix = 'LAB.'; export const LAB_ACTIONS = { GET_LIST: `${prefix}GET_LIST`, SET_LIST: `${prefix}SET_LIST`, + + GET_STATS: `${prefix}GET_STATS`, + SET_STATS: `${prefix}SET_STATS`, }; diff --git a/src/redux/lab/handlers.ts b/src/redux/lab/handlers.ts index b09812e2..f23fada1 100644 --- a/src/redux/lab/handlers.ts +++ b/src/redux/lab/handlers.ts @@ -1,5 +1,5 @@ import { LAB_ACTIONS } from '~/redux/lab/constants'; -import { labSetList } from '~/redux/lab/actions'; +import { labSetList, labSetStats } from '~/redux/lab/actions'; import { ILabState } from '~/redux/lab/types'; type LabHandler any> = ( @@ -15,6 +15,15 @@ const setList: LabHandler = (state, { list }) => ({ }, }); +const setStats: LabHandler = (state, { stats }) => ({ + ...state, + stats: { + ...state.stats, + ...stats, + }, +}); + export const LAB_HANDLERS = { [LAB_ACTIONS.SET_LIST]: setList, + [LAB_ACTIONS.SET_STATS]: setStats, }; diff --git a/src/redux/lab/index.ts b/src/redux/lab/index.ts index 56879a52..1a0bc0aa 100644 --- a/src/redux/lab/index.ts +++ b/src/redux/lab/index.ts @@ -1,6 +1,7 @@ import { createReducer } from '~/utils/reducer'; import { LAB_HANDLERS } from '~/redux/lab/handlers'; import { ILabState } from '~/redux/lab/types'; +import { INode, ITag } from '~/redux/types'; const INITIAL_STATE: ILabState = { list: { @@ -9,6 +10,12 @@ const INITIAL_STATE: ILabState = { count: 0, error: '', }, + stats: { + is_loading: false, + heroes: [], + tags: [], + error: undefined, + }, }; export default createReducer(INITIAL_STATE, LAB_HANDLERS); diff --git a/src/redux/lab/sagas.ts b/src/redux/lab/sagas.ts index 5fc48b8b..1a66b3a5 100644 --- a/src/redux/lab/sagas.ts +++ b/src/redux/lab/sagas.ts @@ -1,8 +1,8 @@ import { takeLeading, call, put } from 'redux-saga/effects'; -import { labGetList, labSetList } from '~/redux/lab/actions'; +import { labGetList, labSetList, labSetStats } from '~/redux/lab/actions'; import { LAB_ACTIONS } from '~/redux/lab/constants'; import { Unwrap } from '~/redux/types'; -import { getLabNodes } from '~/redux/lab/api'; +import { getLabNodes, getLabStats } from '~/redux/lab/api'; function* getList({ after = '' }: ReturnType) { try { @@ -16,6 +16,19 @@ function* getList({ after = '' }: ReturnType) { } } +function* getStats() { + try { + yield put(labSetStats({ is_loading: true })); + const { heroes, tags }: Unwrap = yield call(getLabStats); + yield put(labSetStats({ heroes, tags })); + } catch (error) { + yield put(labSetStats({ error: error.message })); + } finally { + yield put(labSetStats({ is_loading: false })); + } +} + export default function* labSaga() { yield takeLeading(LAB_ACTIONS.GET_LIST, getList); + yield takeLeading(LAB_ACTIONS.GET_STATS, getStats); } diff --git a/src/redux/lab/selectors.ts b/src/redux/lab/selectors.ts index 0854ac25..19bdf7e8 100644 --- a/src/redux/lab/selectors.ts +++ b/src/redux/lab/selectors.ts @@ -2,3 +2,6 @@ import { IState } from '~/redux/store'; export const selectLab = (state: IState) => state.lab; export const selectLabListNodes = (state: IState) => state.lab.list.nodes; +export const selectLabStatsHeroes = (state: IState) => state.lab.stats.heroes; +export const selectLabStatsTags = (state: IState) => state.lab.stats.tags; +export const selectLabStatsLoading = (state: IState) => state.lab.stats.is_loading; diff --git a/src/redux/lab/types.ts b/src/redux/lab/types.ts index 7614807b..9a30aebe 100644 --- a/src/redux/lab/types.ts +++ b/src/redux/lab/types.ts @@ -1,4 +1,4 @@ -import { IError, INode } from '~/redux/types'; +import { IError, INode, ITag } from '~/redux/types'; export type ILabState = Readonly<{ list: { @@ -7,6 +7,12 @@ export type ILabState = Readonly<{ count: number; error: IError; }; + stats: { + is_loading: boolean; + heroes: Partial[]; + tags: ITag[]; + error?: string; + }; }>; export type GetLabNodesRequest = { @@ -17,3 +23,8 @@ export type GetLabNodesResult = { nodes: INode[]; count: number; }; + +export type GetLabStatsResult = { + heroes: INode[]; + tags: ITag[]; +}; diff --git a/src/styles/_colors.scss b/src/styles/_colors.scss index 15fb6329..957df9be 100644 --- a/src/styles/_colors.scss +++ b/src/styles/_colors.scss @@ -2,7 +2,7 @@ // $red: #ff3344; $red: #ff3344; $yellow: #ffd60f; -$dark_blue: #3c75ff; +$dark_blue: #592071; $blue: #582cd0; $green: #00d2b9; //$green: #00503c; @@ -16,7 +16,7 @@ $primary: $red; $secondary: $wisegreen; $red_gradient: linear-gradient(165deg, $orange -50%, $red 150%); -$blue_gradient: linear-gradient(170deg, $green, $dark_blue); +$blue_gradient: linear-gradient(170deg, $blue, $dark_blue); $green_gradient: linear-gradient( 170deg, lighten(adjust_hue($wisegreen, 15deg), 10%) 0%, diff --git a/src/styles/_global.scss b/src/styles/_global.scss index 8f755483..f13d045b 100644 --- a/src/styles/_global.scss +++ b/src/styles/_global.scss @@ -27,6 +27,10 @@ body { background-size: 600px 600px; pointer-events: none; } + + * { + min-width: 0; + } } #app { diff --git a/src/utils/node.ts b/src/utils/node.ts index f00d006c..0e2f426e 100644 --- a/src/utils/node.ts +++ b/src/utils/node.ts @@ -16,6 +16,6 @@ export const canLikeNode = (node: Partial, user: Partial): boolean path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST; export const canStarNode = (node: Partial, user: Partial): boolean => - node.type === NODE_TYPES.IMAGE && + (node.type === NODE_TYPES.IMAGE || node.is_promoted === false) && path(['role'], user) && path(['role'], user) === USER_ROLES.ADMIN;