@kiwicom/smart-faq
Version:
431 lines (396 loc) • 12.7 kB
JavaScript
// @flow
// TODO - refactor this component - separated component for root categories & subcategories
import * as React from 'react';
import { graphql } from 'react-relay';
import { Link, withRouter } from 'react-router-dom';
import Translate from '@kiwicom/nitro/lib/components/Translate';
import { Heading } from '@kiwicom/orbit-components';
import type { ContextRouter } from 'react-router-dom';
import LogContext from '@kiwicom/nitro/lib/services/log/context';
import FAQCategoriesFullpageHeader from './FAQCategoriesFullpageHeader';
import { Desktop, Mobile } from '../../SmartFAQ/common/Responsive';
import {
ExtraInfoState,
categories as extraCategories,
} from '../../SmartFAQ/context/ExtraInfoState';
import type { Context } from '../../SmartFAQ/context/ExtraInfoState';
import Emergencies from '../../SmartFAQ/context/Emergencies';
import UserStatus from '../../SmartFAQ/helpers/UserStatus';
import Loader from '../../SmartFAQ/common/Loader';
import ScrollableBox from '../../SmartFAQ/common/ScrollableBox';
import QueryRenderer from '../../SmartFAQ/relay/QueryRenderer';
import BaggageInfo from './FAQExtraInfo/BaggageInfo/index';
import BoardingPassesInfo from './FAQExtraInfo/BoardingPassesInfo/index';
import CustomerSupportNumber from './CustomerSupportNumber';
import FAQArticle from './FAQArticle';
import FAQCategory from './FAQCategory';
import Emergency from './emergencies/Emergency';
import EmergencyHeader from './emergencies/EmergencyHeader';
import Breadcrumbs from './breadcrumbs/Breadcrumbs';
import StaticFAQError from './StaticFAQError';
import { GUARANTEE_ARTICLE_ID } from './ArticleDetail/ArticleContent';
import { SearchState } from '../../SmartFAQ/context/SearchState';
import type { SearchStateType } from '../../SmartFAQ/context/SearchState';
import type { FAQArticle_article as FAQArticleType } from './__generated__/FAQArticle_article.graphql';
import type { FAQCategory_category as FAQCategoryType } from './__generated__/FAQCategory_category.graphql';
import type { FAQCategoryListRootQueryResponse } from './__generated__/FAQCategoryListRootQuery.graphql';
import type { FAQCategoryListSubcategoryQueryResponse } from './__generated__/FAQCategoryListSubcategoryQuery.graphql';
import { BookingState } from '../../SmartFAQ/context/BookingState';
import type {
FAQSectionType,
State,
} from '../../SmartFAQ/context/BookingState';
import { GuaranteeChatContext } from '../context/GuaranteeChatInfo';
import {
FullPageVersion,
SidebarVersion,
} from '../../SmartFAQ/common/PageVariant';
import { events } from '../../const/events';
import type { log } from '../../const/events';
import features from '../../feature-toggles.json';
type OwnProps = {|
...ContextRouter,
categoryId: ?number,
|};
type ContainerProps = {|
section: ?FAQSectionType,
showGuaranteeArticle: boolean,
log: log,
|};
type Props = {|
...OwnProps,
...ContainerProps,
|};
type RootQueryRendererParams = {
props: ?FAQCategoryListRootQueryResponse,
error: ?Error,
};
type SubcategoryQueryRendererParams = {
props: ?FAQCategoryListSubcategoryQueryResponse,
error: ?Error,
};
type CategoryFragment = {|
+id: string,
+originalId: string,
+title: ?string,
+$fragmentRefs: FAQCategoryType,
|};
type FAQArticlePerexFragment = {|
+id: string,
+$fragmentRefs: FAQArticleType,
|};
type FAQCategoryItemProps = {
section: ?FAQSectionType,
category: CategoryFragment,
log: log,
};
class FAQCategoryItem extends React.Component<FAQCategoryItemProps> {
context: SearchStateType;
static contextType = SearchState;
onClick = () => {
const { category, section, log } = this.props;
if (this.context.searchText.length) {
this.context.clearSearch();
this.context.resetQueriesCount();
}
log(events.FAQ_CATEGORY_CLICK, {
section: section ?? '',
categoryId: category.originalId,
categoryName: category.title ?? '',
});
};
render() {
const { category } = this.props;
return (
<Link
key={category.id}
to={`/faq/${category.originalId}`}
style={{
textDecoration: 'none',
display: 'block',
}}
onClick={this.onClick}
>
<FAQCategory category={category} />
</Link>
);
}
}
const queryRoot = graphql`
query FAQCategoryListRootQuery(
$section: FAQSection!
$articleId: Int!
$showGuaranteeArticle: Boolean!
) {
FAQArticle(originalId: $articleId) @include(if: $showGuaranteeArticle) {
...FAQArticle_article
}
FAQSection(section: $section) {
id
originalId: id(opaque: false)
subcategories {
id
originalId: id(opaque: false)
title
...FAQCategory_category
}
FAQs {
id
...FAQArticle_article
}
}
...CustomerSupportNumberMobile
}
`;
const querySubcategory = graphql`
query FAQCategoryListSubcategoryQuery($id: Int!) {
FAQCategory(originalId: $id) {
id
originalId: id(opaque: false)
title
subcategories {
id
originalId: id(opaque: false)
title
...FAQCategory_category
}
ancestors {
id
...Breadcrumbs_breadcrumbs
}
FAQs {
id
...FAQArticle_article
}
}
...CustomerSupportNumberMobile
}
`;
class RawFAQCategoryList extends React.Component<Props> {
renderFAQArticlePerexes = (
faqs: $ReadOnlyArray<?FAQArticlePerexFragment>,
categoryId: string,
) => {
return (
<div>
{faqs.filter(Boolean).map(faq => (
<FAQArticle key={faq.id} article={faq} categoryId={categoryId} />
))}
</div>
);
};
renderExtraInfoCategory = activeExtraInfoCategory => {
switch (activeExtraInfoCategory) {
case 'baggage':
return <BaggageInfo />;
case 'boarding-passes':
return <BoardingPassesInfo />;
default:
return null;
}
};
renderCategories = (categories: $ReadOnlyArray<CategoryFragment>) => {
const { log, history } = this.props;
const { pathname } = history.location;
const isBaggageRoute = pathname.includes(String(extraCategories.BAGGAGE));
const isBoardingPassRoute = pathname.includes(
String(extraCategories.BOARDING_PASS),
);
return (
<React.Fragment>
<Desktop>
<UserStatus.LoggedIn>
{(isBaggageRoute || isBoardingPassRoute) && (
<ExtraInfoState.Consumer>
{({ activeExtraInfoCategory }: Context) =>
this.renderExtraInfoCategory(activeExtraInfoCategory)
}
</ExtraInfoState.Consumer>
)}
</UserStatus.LoggedIn>
</Desktop>
<BookingState.Consumer>
{({ FAQSection }: State) => (
<div data-cy="faq-categories">
{categories.map(category => {
if (category) {
return (
<FAQCategoryItem
category={category}
section={FAQSection}
log={log}
key={category.id}
/>
);
}
return null;
})}
</div>
)}
</BookingState.Consumer>
</React.Fragment>
);
};
renderRootCategory = (rendererProps: RootQueryRendererParams) => {
if (rendererProps.error) {
return <StaticFAQError />;
}
if (rendererProps.props) {
const categories = rendererProps.props.FAQSection?.subcategories ?? [];
const faqs = rendererProps.props.FAQSection?.FAQs ?? [];
const categoryId = rendererProps.props.FAQSection?.originalId ?? '';
const guaranteeArticle =
rendererProps.props && rendererProps.props.FAQArticle;
const Contents = ({ hasEmergencies, emergencies }) => (
<>
{hasEmergencies && (
<EmergencyHeader
styles="margin-top: 8px"
title={__('smartfaq.faq.emergencies.current')}
/>
)}
{emergencies &&
emergencies.map((emergency, i) => (
<Emergency key={i} emergency={emergency} />
))}
{hasEmergencies && (
<EmergencyHeader
styles="margin-top: 32px"
title={__('smartfaq.faq.emergencies.solve')}
/>
)}
<Desktop>
<FullPageVersion>
{features.fullpage_loggedIn && <FAQCategoriesFullpageHeader />}
<Heading type="title3" element="h3" spaceAfter="large">
<Translate t="smartfaq.full_page.categories.header.suggested_topics" />
</Heading>
</FullPageVersion>
</Desktop>
{guaranteeArticle && (
<FAQArticle article={guaranteeArticle} isSearchResult />
)}
{this.renderCategories(categories.filter(Boolean))}
<SidebarVersion>
{this.renderFAQArticlePerexes(faqs, categoryId)}
</SidebarVersion>
<Mobile>
<UserStatus.LoggedIn>
<CustomerSupportNumber data={rendererProps.props} />
</UserStatus.LoggedIn>
</Mobile>
</>
);
return (
<Emergencies.Consumer>
{emergencies => {
const hasEmergencies = emergencies && emergencies.length > 0;
return (
<>
<FullPageVersion>
<ScrollableBox disabled={true}>
<Contents
hasEmergencies={hasEmergencies}
emergencies={emergencies}
/>
</ScrollableBox>
</FullPageVersion>
<SidebarVersion>
<ScrollableBox>
<Contents
hasEmergencies={hasEmergencies}
emergencies={emergencies}
/>
</ScrollableBox>
</SidebarVersion>
</>
);
}}
</Emergencies.Consumer>
);
}
return (
<SidebarVersion>
<Loader fullHeight />
</SidebarVersion>
);
};
renderSubcategory = (rendererProps: SubcategoryQueryRendererParams) => {
if (rendererProps.error) {
return <StaticFAQError />;
}
const FAQCategory = rendererProps.props?.FAQCategory;
if (rendererProps.props) {
const categories = rendererProps.props.FAQCategory?.subcategories ?? [];
const ancestors = rendererProps.props.FAQCategory?.ancestors ?? [];
const currentCategory = rendererProps.props.FAQCategory?.title;
const faqs = rendererProps.props.FAQCategory?.FAQs ?? [];
const categoryId = rendererProps.props.FAQCategory?.originalId ?? '';
if (FAQCategory === null) {
return <StaticFAQError />;
}
return (
<>
<div>
<Breadcrumbs
breadcrumbs={ancestors}
currentCategory={currentCategory}
/>
</div>
<ScrollableBox disabled>
{this.renderCategories(categories.filter(Boolean))}
{this.renderFAQArticlePerexes(faqs, categoryId)}
</ScrollableBox>
</>
);
}
return <Loader fullHeight />;
};
render() {
const { categoryId, section, showGuaranteeArticle, match } = this.props;
const { categoryId: ssrCategoryId } = match?.params ?? {};
if (categoryId || ssrCategoryId) {
return (
<QueryRenderer
query={querySubcategory}
render={this.renderSubcategory}
variables={{ id: Number(categoryId ?? ssrCategoryId) }}
/>
);
}
return (
<QueryRenderer
query={queryRoot}
render={this.renderRootCategory}
variables={{
section,
showGuaranteeArticle,
articleId: GUARANTEE_ARTICLE_ID,
}}
/>
);
}
}
const FAQCategoryList = (props: OwnProps) => (
<BookingState.Consumer>
{({ FAQSection }) => (
<GuaranteeChatContext.Consumer>
{({ showGuaranteeChat }) => (
<LogContext.Consumer>
{({ log }) => (
<RawFAQCategoryList
log={log}
section={FAQSection}
showGuaranteeArticle={
showGuaranteeChat && props.categoryId === null
}
{...props}
/>
)}
</LogContext.Consumer>
)}
</GuaranteeChatContext.Consumer>
)}
</BookingState.Consumer>
);
export default withRouter(FAQCategoryList);