import * as React from "react"
import { InputSingleLine } from "./Input"
import styled from "styled-components"
import bp from "styled-components-breakpoint"
import Router from "next/router"
import withTranslate from "jamplay-common/i18n/withTranslate"
import { withState, lifecycle } from "recompose"
import getConfig from "jamplay-common/remote-config"
import { provider } from "jamplay-common/client"
import { getCategory } from "jamplay-common/enum/book-categories"
import { Icon } from ".."
import { compose, Query, QueryResult } from "react-apollo"
import Analytic, { getInstance } from "jamplay-common/client/analytic"
import { Link } from "jamplay-common/routing"
import { BookThumbnail } from "./Book"
import gql from "graphql-tag"
const SuggestionResultContainer = styled.div`
position: absolute;
top: 100%;
right: 0;
left: 0;
z-index: 9;
display: none;
.SuggestionResultDialog__wrapper {
border-radius: 0 0 5px 5px;
border: 1px solid ${(props) => props.theme.borderGrey};
background-color: ${(props) => props.theme.matteWhite};
}
.SuggestionResultDialog__type-label {
background-color: ${(props) => props.theme.pumpkin};
color: ${(props) => props.theme.white};
padding: 3px 5px;
display: inline-block;
border-radius: 5px;
}
.SuggestionResultDialog__see-all-wrapper {
text-align: right;
border-top: solid 1px ${(props) => props.theme.borderGrey};
padding: 5px 13px 8px 5px;
a {
font-weight: bold;
color: ${(props) => props.theme.matteBlack};
}
}
.SuggestionResultDialog__no-content-wrapper {
text-align: center;
margin-top: 21px;
}
.SugestionResultDialog__loading-wrap {
background: ${(props) => props.theme.matteWhite};
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
margin: 8px;
}
.SuggestionResultDialog__scroll {
${bp("mobile")`
-webkit-overflow-scrolling: touch;
overflow: auto;
`} ${bp("tablet")`
max-height: 999999px;
`};
}
.AuthorContainer {
.AuthorInfo__avatar-image {
width: 40px;
height: 40px;
border: 1px solid ${(props) => props.theme.borderGrey};
}
.AuthorInfo__info-wrapper p {
font-size: 1rem;
font-weight: bold;
color: ${(props) => props.theme.matteBlack};
}
}
h6 {
${bp("mobile")`
font-size: 16px;
`} ${bp("tablet")`
font-size: inherit;
`};
}
transition: 0.222s ease-in-out all;
transform: translate(0, 8px);
opacity: 0;
pointer-events: none;
display: block;
${bp("mobile")`
&.visible {
opacity: 1;
pointer-events: all;
transform: translate(0, 0px);
}
`} ${bp("tablet")`
`};
`
const BookSuggestionResultItemContainer = styled.div`
cursor: pointer;
a {
text-decoration: none;
}
a label {
cursor: pointer;
color: #333;
}
${bp("mobile")`
padding: 5px 8px 5px 8px;
.AuthorInfo {
}
`} ${bp("tablet")`
.AuthorInfo {
display: block;
}
`}
&:hover {
background-color: ${(props) => props.theme.white};
}
&:first-child {
}
&:last-child {
border-bottom: none;
}
.BookSuggestionResultItem__cover {
width: 35px;
}
.BookSuggestionResultItem__info-wrapper {
width: 100%;
display: flex;
align-items: center;
}
.BookSuggestionResultItem__label {
margin-left: 8px;
font-weight: bold;
word-break: break-all;
}
.BookSuggestionResultItem__label--highlight {
background-color: ${(props) => props.theme.gold};
}
.AuthorInfo {
clear: both;
}
.AuthorInfo__avatar-image {
width: 35px;
height: 35px;
${bp("mobile")`
display: none;
`} ${bp("tablet")`
display: block;
`};
}
.BookSuggestionResultItem__cover-wrapper {
width: 35px;
margin-right: 8px;
${bp("mobile")`
display: none;
`} ${bp("tablet")`
display: block;
`};
&.author {
border: none;
.thumbnail {
width: 35px;
height: 35px;
border-radius: 50%;
}
.thumbnail:before {
padding-top: 0;
}
.thumbnail:after {
border: none;
}
}
}
.BookSuggestionResultItem__tags-container {
font-size: 0.6em;
${bp("mobile")`
display: none;
`} ${bp("tablet")`
display: block;
`};
}
.BookSuggestionResultItem__tag {
border: 1px solid ${(props) => props.theme.borderGrey};
color: ${(props) => props.theme.darkGrey};
padding: 0px 5px;
margin: 2px 1px;
border-radius: 8px;
float: left;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
`
function navigateToResultPage(value: string, type: string = "book") {
Link.Search.go(Router, {
category: "ALL",
v: value,
sort: "SCORE",
type
})
}
function preventDefault(e) {
e.preventDefault()
}
export const BookSuggestionResultItem = (
props: {
_id: string
thumbnailImage?: string
category?: string
onClearSearchText?: () => void
} & {
title?: string
highlightText: string
__typename: "Author" | "Book"
} & withTranslatePropType
) => {
let url = `/search?v=${props.title}&category=ALL&sort=SCORE`
let type = "book"
if (props.__typename === "Author") {
url = `${url}&type=author`
type = "author"
}
function SendEventAndNavigate(sendEvent) {
return (e) => {
e.preventDefault()
sendEvent("search_input", "click-suggestion", `${props.title}`, 0)
navigateToResultPage(props.title || "", type)
}
}
function categoryLowerCase(cat) {
if (!cat) {
return "all"
}
return getCategory(cat).prefix
}
function dismissSearchSuggestion() {
if (props.onClearSearchText) {
props.onClearSearchText()
}
const activeElement: any = document.activeElement
activeElement.blur()
}
const Routing = type === "book" ? Link.BookDetail : Link.Author
const paramsCustom =
type === "book"
? { _id: props._id, category: categoryLowerCase(props.category) }
: { id: props._id }
return (
{({ sendEvent }) => (
)}
)
}
export const SEARCH_SUGGESTION_QUERY = gql`
query($value: String!) {
books: searchBook(value: $value, limit: 5) {
pageInfo {
total
}
data {
_id
title
thumbnailImage
categories
tags
}
}
authors: searchAuthor(value: $value, limit: 3) {
pageInfo {
total
}
data {
name
_id
profilePicture
}
}
}
`
type SearchSuggestionTData = {
autoSuggestion: {
books: BookItemData[]
authors: AuthorItemData[]
}
}
type SearchSuggestionQueryPropTypes = {
variables: { value: string }
children: (
results: SearchSuggestionTData["autoSuggestion"],
loading
) => JSX.Element | JSX.Element[]
}
const SearchSuggestionQuery: React.SFC = ({
children,
variables
}) => {
return (
{({
data,
loading
}: QueryResult<{ books: any; authors: any }, { value: string }>) => {
if (data) {
if (data.books && data.authors) {
return children(
{
books: data.books.data,
authors: data.authors.data
},
loading
)
} else {
return children({ books: [], authors: [] }, loading)
}
} else {
return children({ books: [], authors: [] }, loading)
}
}}
)
}
export interface SuggestionResultDialogPropTypes extends withTranslatePropType {
visible: boolean
value: string
onClearSearchText?: () => void
}
interface ISuggestionResultContainer
extends React.SFC {
fragments: {
author: any
book: any
}
}
export const SuggestionResultDialog: ISuggestionResultContainer = Object.assign(
(props: SuggestionResultDialogPropTypes & withTranslatePropType) => {
if (!props.t) {
return (
)
}
return (
{(results) => {
const BookItemResult = results.books.map((book) => (
))
const AuthorItemResult = results.authors.map((author) => (
))
return [...BookItemResult, ...AuthorItemResult]
}}
{(results) =>
[...results.authors, ...results.books].length <= 0 ? (
) : (
)
}
)
}
)
export interface SearchTextInputPropTypes
extends withTranslatePropType,
React.InputHTMLAttributes {
value: string
isLoading: { [key: string]: boolean }
forceFocus?: boolean
onChange: (s: any) => void
}
const SearchTextInputContainer = styled.div.attrs({
className: "SearchTextInput"
})`
display: block;
position: relative;
.SearchTextInputContainer__input-wrap {
display: flex;
.jamplay-icon {
padding: 8px 18px 8px 18px;
color: white;
font-size: 21px;
}
}
.SeachTextInputContainer__input-wrap__input--result-active {
border-radius: 5px 0px 0 0 !important;
}
.SeachTextInputContainer__search-button--result-active {
border-radius: 0px 5px 0 0 !important;
}
`
export class SearchTextInput extends React.Component<
SearchTextInputPropTypes,
{ isFocus: boolean }
> {
public static defaultProps = {}
constructor(props) {
super(props)
this.state = {
isFocus: props.initFocus || false
}
}
public onInputFocus = (value: boolean) => {
return (e) => {
this.setState({
isFocus: value
})
}
}
public pushResult = () => {
Link.Search.go(Router, {
category: "ALL",
v: this.props.value,
sort: "SCORE"
})
}
public onKeyPress = (e) => {
if (e.key === "Enter") {
console.log("[search] confirm...")
const ga = getInstance()
ga.sendEvent("search_input", "press_key", this.props.value, 0)
this.pushResult()
}
}
public onSearchIconClick = (e) => {
const ga = getInstance()
ga.sendEvent("search_input", "click-icon", this.props.value, 0)
this.pushResult()
}
public onChange = (e) => {
if (this.props.onChange) {
this.props.onChange(e.target.value)
}
}
public onClearSearchText = () => {
if (this.props.onChange) {
this.props.onChange("")
}
}
public render() {
if (!this.props.t) {
throw new Error("1i8n component not provide")
}
const isResultVisible =
(this.state.isFocus || !!this.props.forceFocus) &&
this.props.value.length > 0
return (
)
}
}
const _providers = {}
function getProvider(uri: string) {
if (_providers[uri]) { return _providers[uri] }
_providers[uri] = provider({ uri })(({ children }) => children as any)
return _providers[uri]
}
export const SearchTextInputStandalone = compose(
withTranslate,
withState("isLoading", "setIsLoading", {}),
withState("value", "onChange", (props) => {
return props.initValue || ""
}),
withState("result", "setResult", { "": [], "books": [], "authors": [] }),
lifecycle({
componentWillReceiveProps(nextProps) {
if (this.props.initValue !== nextProps.initValue) {
this.props.onChange(nextProps.initValue)
}
},
componentDidMount() {
this.props.onChange(this.props.value)
}
}),
(Component) => {
return (props) => {
// if not provide coreGraphQLUrl or server side will not create provider
if (!props.coreGraphQLUrl) {
return
}
const Provider = getProvider(props.coreGraphQLUrl)
return (
)
}
}
)(SearchTextInput as any) as React.ComponentClass<{
initValue?: string
forceFocus?: boolean
t?: any
coreGraphQLUrl?: string
}>
export interface SearchInput
extends React.ComponentClass<{
autoFocus?: boolean
forceFocus?: boolean
initValue?: string,
coreGraphQLUrl?: string,
}> {}
export default withTranslate(SearchTextInputStandalone) as SearchInput