Files
sol-journal/src/routes/Search.js
2019-05-29 20:50:22 -06:00

175 lines
4.6 KiB
JavaScript

import { Component } from "react"
/** @jsx jsx */
import { jsx, css, keyframes } from "@emotion/core"
import styled from "@emotion/styled"
import { compose } from "recompose"
import { withTheme } from "emotion-theming"
import { BeatLoader } from "react-spinners"
import { AppLink as Link } from "components/elements"
import { Input } from "components/elements"
import { withFirebase } from "components/firebase"
import { withAuthentication } from "components/session"
import { pad } from "utils/date"
const SearchGrid = styled.div`
display: grid;
grid-template-columns: 1fr;
grid-gap: 10px;
margin-bottom: 60px;
`
const SearchLayout = styled.div`
width: 100%;
align-self: center;
margin-top: 20px;
`
const SearchResult = styled.div`
margin-top: 5px;
padding: 20px;
border-radius: 5px;
color: ${props => props.theme.colors.primary};
border: 1px solid;
border-color: ${props => props.theme.colors.quarternary};
&:hover {
cursor: pointer;
border-color: ${props => props.theme.colors.tertiary};
}
`
const fadeKeyFrames = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const LoadingSpinner = styled(BeatLoader)`
opacity: 0;
`
class Search extends Component {
state = {
entries: [],
allEntries: [],
searchInput: "",
loading: true,
}
componentDidMount() {
this.getEntries()
}
onChange = event => {
const searchInput = event.target.value
this.setState({ searchInput })
this.filterEntries(searchInput)
}
filterEntries = searchTerm => {
const { allEntries } = this.state
if (searchTerm === "") {
this.setState({ entries: allEntries })
} else {
const filteredEntries = allEntries.filter(entry => {
return entry.text.toLowerCase().includes(searchTerm.toLowerCase())
})
this.setState({ entries: filteredEntries })
}
}
// all entries for a user are fetched this scales poorly but is much
// simpler than an API key and setting up an index for Algolia
getEntries = async _ => {
const { firebase, authUser } = this.props
const entriesRef = await firebase.db
.collection("entries")
.where("userId", "==", authUser.uid)
.get()
const entries = entriesRef.docs.map(doc => doc.data()).reverse()
this.setState({ entries, allEntries: entries, loading: false })
}
render() {
const { entries, searchInput, loading } = this.state
const { theme } = this.props
return (
<SearchLayout>
<SearchGrid>
<Input
autoFocus={true}
value={searchInput}
onChange={e => this.onChange(e)}
type="text"
placeholder="Search..."
colors={theme.colors}
/>
{loading ? (
<div style={{ marginTop: 10, margin: "0 auto" }}>
<LoadingSpinner
color={theme.colors.quarternary}
size={10}
margin="4px"
css={css`
animation: ${fadeKeyFrames} 1s ease-in;
`}
/>
</div>
) : entries.length > 0 ? (
entries.map(
(entry, index) =>
entry.text.length > 1 && (
<Link
key={index}
to={`${entry.year}/${pad(entry.month)}/${pad(entry.day)}`}
style={{ textDecoration: "none" }}
>
<SearchResult
css={css`
animation: ${fadeKeyFrames} 0.2s ease-in;
`}
>
<div
css={css`
font-style: italic;
color: ${theme.colors.secondary};
margin-bottom: 5px;
`}
>
{entry.day}/{entry.month}/{entry.year}
</div>
<div>
{entry.text.substring(0, 128)}
{entry.text.length >= 128 && "..."}
</div>
</SearchResult>
</Link>
)
)
) : (
<div
css={css`
text-align: center;
font-style: italic;
color: ${theme.colors.tertiary};
margin-top: 5px;
`}
>
No entries to display
</div>
)}
</SearchGrid>
</SearchLayout>
)
}
}
export default compose(
withFirebase,
withTheme,
withAuthentication
)(Search)