Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,27 @@
[lints]

[options]
module.name_mapper='^@lib' ->'<PROJECT_ROOT>/src/lib'
module.name_mapper='^@features' ->'<PROJECT_ROOT>/src/features'
all=false
server.max_workers=1
include_warnings=false
max_header_tokens=5
esproposal.class_instance_fields=enable
esproposal.class_static_fields=enable
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
suppress_comment=\\(.\\|\n\\)*\\$off
suppress_comment=\\(.\\|\n\\)*\\$todo
suppress_type=$FlowIssue
suppress_type=$off
suppress_type=$todo
emoji=true
esproposal.decorators=ignore
module.use_strict=true
esproposal.export_star_as=enable

module.name_mapper='^@lib/' ->'<PROJECT_ROOT>/src/lib/'
module.name_mapper='^@features/' ->'<PROJECT_ROOT>/src/features/'
module.name_mapper='^@howtocards/ui' ->'<PROJECT_ROOT>/src/ui'

[strict]
2 changes: 1 addition & 1 deletion src/features/cards/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type Card } from "./types"

export { $registry as $cardsRegistry } from "./model/registry.store"
export { cardsToObject } from "./model/registry.model"
export { CardItem, CardsList } from "./organisms"
export { CardItem, CardsList, SkeletonList } from "./organisms"
export { cardsRoutes } from "./routes"

export { Card }
3 changes: 1 addition & 2 deletions src/features/cards/model/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createEvent, createEffect, createStore } from "effector"
import type { Event, Effect, Store } from "effector"

import { createFetching, type Fetching } from "@lib/fetching"

import { cardsApi } from "../api"
import type { Card } from "../types"
import { $registry } from "./registry.store"
Expand Down Expand Up @@ -34,5 +35,3 @@ $registry.on(homeCardsLoading.done, (registry, { result }) => {
pageReady.watch(() => {
homeCardsLoading()
})

export const cardsFetching = createFetching(homeCardsLoading, "loading")
79 changes: 37 additions & 42 deletions src/features/cards/organisms/cards-list.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,58 @@
import React from "react"
import PropTypes from "prop-types"
import { createStoreObject } from "effector"
// @flow
import * as React from "react"
import { createComponent } from "effector-react"
import styled from "styled-components"

import { ConditionalList } from "@howtocards/ui"
import { cardsFetching } from "../model/home"
import { $registry, getCard } from "../model/registry.store"
import { setUsefulMark } from "../model/registry.events"
import { CardSkeleton } from "./card-skeleton"
import { type Card } from "../types"
import { CardItem } from "./card-item"

const onUsefulClick = (cardId) => {
const handleUsefulClick = (cardId) => {
const card = getCard(cardId)
if (card) {
setUsefulMark({ cardId: card.id, isUseful: !card.meta.isUseful })
}
}

const selectCards = (props) =>
createStoreObject({
cards: $registry.map((reg) => props.ids.map((id) => reg[id])),
isLoading: cardsFetching.isLoading,
})
$registry.map((reg) => props.ids.map((id) => reg[id]))

export const CardsList = createComponent(
type Props = {
ids: number[],
renderCard?: (param: { card: Card, onUsefulClick: () => * }) => React.Node,
renderEmpty?: () => React.Node,
}

export const CardsList = createComponent<Props, Card[]>(
selectCards,
({ renderCard }, { cards, isLoading }) => {
return (
<>
{isLoading ? (
<>
<CardSkeleton />
<CardSkeleton />
<CardSkeleton />
</>
) : (
<ConditionalList
list={cards}
renderExists={(list) => (
<CardsItemsBlock>
{list.filter(Boolean).map((card) =>
renderCard({
card,
onUsefulClick: () => onUsefulClick(card.id),
}),
)}
</CardsItemsBlock>
)}
/>
)}
</>
)
},
({ renderCard = defaultCardRender, renderEmpty = emptyRenderer }, cards) => (
<ConditionalList
list={cards}
renderExists={(list) => (
<CardsItemsBlock>
{list.filter(Boolean).map((card) =>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list.map(card => Boolean(card) && render...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case you will have array with empty elements

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array of false and needed elements, so what?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is eager optimization

renderCard({
card,
onUsefulClick: () => handleUsefulClick(card.id),
}),
)}
</CardsItemsBlock>
)}
renderEmpty={renderEmpty}
/>
),
)

CardsList.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
renderCard: PropTypes.func.isRequired,
}
const emptyRenderer = () => <p>No cards in that list</p>
const defaultCardRender = ({
card,
onUsefulClick,
}: {
card: Card,
onUsefulClick: () => *,
}) => <CardItem key={card.id} card={card} onUsefulClick={onUsefulClick} />

export const CardsItemsBlock = styled.div`
display: flex;
Expand Down
3 changes: 3 additions & 0 deletions src/features/cards/organisms/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
// @flow
export { CardItem } from "./card-item"
export { CardSkeleton } from "./card-skeleton"
export { CardsList } from "./cards-list"
export { SkeletonList } from "./skeleton-list"
34 changes: 34 additions & 0 deletions src/features/cards/organisms/skeleton-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @flow
import * as React from "react"
import { CardsList } from "./cards-list"
import { CardSkeleton } from "./card-skeleton"

type Props = {
isLoading: boolean,
ids: number[],
count?: number,
renderEmpty: () => React.Node,
renderCard?: *,
}

export const SkeletonList = ({
isLoading,
ids,
count = 3,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic numbers. Need to move to named const

renderEmpty,
renderCard,
}: Props) =>
isLoading ? (
<>
{Array.from({ length: count }, (_, idx) => (
<CardSkeleton key={idx} />
))}
</>
) : (
<CardsList ids={ids} renderEmpty={renderEmpty} renderCard={renderCard} />
)

SkeletonList.defaultProps = {
count: undefined,
renderCard: undefined,
}
21 changes: 12 additions & 9 deletions src/features/cards/pages/home.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import React, { useEffect } from "react"
// @flow
import * as React from "react"
import { useStore } from "effector-react"

import { Col, Row } from "lib/styled-components-layout"
import { Col, Row } from "@lib/styled-components-layout"
import { H2 } from "@howtocards/ui"
import { $cardsIds, pageReady } from "../model/home"

import { $cardsIds, pageReady, homeCardsFetching } from "../model/home"
import { CardsCommonTemplate } from "../templates/common"
import { CardsList, CardItem } from "../organisms"
import { SkeletonList } from "../organisms"

export const CardsHomePage = () => {
const ids = useStore($cardsIds)
useEffect(() => {
const isLoading = useStore(homeCardsFetching.isLoading)

React.useEffect(() => {
pageReady()
}, [])

return (
<CardsCommonTemplate sidebar={<Sidebar />}>
<CardsList
<SkeletonList
isLoading={isLoading}
ids={ids}
renderCard={({ card, onUsefulClick }) => (
<CardItem key={card.id} card={card} onUsefulClick={onUsefulClick} />
)}
renderEmpty={() => <p>What about to create new card?</p>}
/>
</CardsCommonTemplate>
)
Expand Down
70 changes: 47 additions & 23 deletions src/features/cards/pages/view.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
// @flow
/* eslint-disable import/no-duplicates */
import React from "react"
import * as React from "react"
import PropTypes from "prop-types"
import { useStore } from "effector-react"
import { distanceInWordsToNow } from "date-fns"

import { Col, Row } from "lib/styled-components-layout"
import { Col, Row } from "@lib/styled-components-layout"
import { Text } from "@howtocards/ui"
import { CardsCommonTemplate } from "../templates/common"
import { cardLoading, $card } from "../model/view"
import { CardItem, CardsList } from "../organisms"
import { CardItem, CardSkeleton, CardsList } from "../organisms"

export const CardViewPage = ({ match }) => {
type Props = {
match: {
params: {
cardId: string,
},
},
}

export const CardViewPage = ({ match }: Props) => {
const cardId = parseInt(match.params.cardId, 10)

React.useEffect(() => {
cardLoading({ cardId })
}, [cardId])

const current = useStore($card)

if (!current) {
return <p>Loading...</p>
}

return (
<CardsCommonTemplate sidebar={<Sidebar card={current} />}>
<CardsList
ids={[current.id]}
renderCard={({ card, onUsefulClick }) => (
<CardItem
maximized
key={card.id}
card={card}
onUsefulClick={onUsefulClick}
/>
)}
/>
{current ? (
<CardsList
key={`card-view-${current.id}`}
ids={[current.id]}
renderCard={({ card, onUsefulClick }) => (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That function can be static (defined in the upper scope)

<CardItem
key={card.id}
maximized
card={card}
onUsefulClick={onUsefulClick}
/>
)}
/>
) : (
<CardSkeleton />
)}
</CardsCommonTemplate>
)
}
Expand All @@ -50,13 +61,26 @@ CardViewPage.propTypes = {
const Sidebar = ({ card }) => (
<Col gap="3rem">
<Row>
<Text>
Created {distanceInWordsToNow(card.createdAt, { addSuffix: true })}
</Text>
{card ? (
<Text title={createdAt(card)}>
Created {distanceInWordsToNow(card.createdAt, { addSuffix: true })}
</Text>
) : (
<p>&nbsp;</p>
)}
</Row>
</Col>
)

Sidebar.propTypes = {
card: PropTypes.shape({}).isRequired,
card: PropTypes.shape({}),
}

Sidebar.defaultProps = {
card: null,
}

function createdAt(card) {
const date = new Date(card.createdAt)
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
}
Loading