@kiwicom/smart-faq
Version:
Smart FAQ
368 lines (338 loc) • 13.8 kB
JavaScript
// @flow
import * as React from 'react';
import App, { Container } from 'next/app';
import reactTreeWalker from 'react-tree-walker';
import { fetchQuery } from 'react-relay';
import { Environment } from 'relay-runtime';
import { StaticRouter, BrowserRouter } from 'react-router-dom';
import { ThemeProvider, ThemeConsumer } from 'styled-components';
import { getTokens } from '@kiwicom/orbit-design-tokens';
import InitIntl from '@kiwicom/nitro/lib/components/InitIntl';
import { Provider as BrandProvider } from '@kiwicom/nitro/lib/services/brand/context';
import { Provider as IntlProvider } from '@kiwicom/nitro/lib/services/intl/context';
import { Provider as FetchedProvider } from '@kiwicom/nitro/lib/services/fetched/context';
import { Provider as CurrencyProvider } from '@kiwicom/nitro/lib/services/currency/context';
import ModalValue from '@kiwicom/nitro/lib/components/Value';
import { Provider as ModalProvider } from '@kiwicom/nitro/lib/services/modal/context';
import InitCurrency from '@kiwicom/nitro/lib/components/InitCurrency';
import NavBar from '@kiwicom/nitro/lib/components/NavBar';
import PageVariantContext from '../SmartFAQ/context/PageVariant';
import { UserContext } from '../SmartFAQ/context/User';
import fallbackTranslations from '../translations/enKeys.json';
import { langInfos } from '../translations/langInfos';
import createEnvironment from '../shared/relay/environment';
import RelayProvider from '../shared/ssr/RelayProvider';
import { isBrowser } from '../shared/helpers';
import SSRContext from '../shared/ssr/SSRContext';
import countries from './countries.json';
import languages from './languages.json';
import airlines from './airlines.json';
import continents from './continents.json';
import brands from './brands.json';
import brandLanguages from './brandLanguages.json';
const TRANSLATION_FILES = require('../../data/translationsFiles.json');
export const theme = { orbit: getTokens() };
const collectQueryDefinitions = async (
Component: React.ComponentType<Object>,
location,
): Promise<Array<Object>> => {
const fetchers = [];
const WrappedComponent = () => (
<StaticRouter basename="" location={location} context={{}}>
<Component />
</StaticRouter>
);
await reactTreeWalker(<WrappedComponent />, (element, instance) => {
const fetcher = (value => value && value.fetchSSRData)(instance || element);
if (fetcher) {
fetchers.push(fetcher);
}
});
return fetchers.map(fetcher => fetcher());
};
const getTranslations = (phraseAppLanguageCode: string) => {
// eslint-disable-next-line import/no-dynamic-require
return require(`../../data/${TRANSLATION_FILES[phraseAppLanguageCode]}`);
};
type QueryDefinition = {|
query: string,
variables: {
[string]: mixed,
},
|};
const IsomorphicRouter = props =>
isBrowser() ? (
<BrowserRouter {...props}>{props.children}</BrowserRouter>
) : (
<StaticRouter {...props}>{props.children}</StaticRouter>
);
class MyApp extends App {
static async fetchData(
environment: Environment,
query: string,
variablesMap: Object,
) {
try {
return await fetchQuery(environment, query, variablesMap);
} catch (e) {
console.warn('GraphQL error:', e); // eslint-disable-line no-console
return { relayFetchError: true };
}
}
static async getStaticInitialProps(
environment: Environment,
Props: { [string]: mixed },
) {
return {
dataProps: {},
variablesFromSSR: {},
recordStoreFromSSR: isBrowser()
? {}
: environment
.getStore()
.getSource()
.toJSON(),
...Props,
};
}
static async getDynamicInitialProps(
queryDefinitions: Array<QueryDefinition>,
environment: Environment,
ctx: Object,
Props: { [string]: mixed },
) {
const { variables } = queryDefinitions[0];
const dataProps = await this.fetchData(
environment,
queryDefinitions[0].query,
variables,
);
if (dataProps.relayFetchError && ctx.res) {
ctx.res.status(500);
}
return {
dataProps,
variablesFromSSR: variables,
recordStoreFromSSR: isBrowser()
? {}
: environment
.getStore()
.getSource()
.toJSON(),
...Props,
};
}
static getPhraseAppLanguageCode(lng: string) {
try {
return langInfos[lng].phraseApp;
} catch (e) {
return 'en-GB';
}
}
static async getTranslationData(ctx: Object) {
const lng = ctx.query.lng || 'en';
const phraseAppLanguageCode = this.getPhraseAppLanguageCode(lng);
return {
lng,
phraseAppLanguageCode,
translations: getTranslations(phraseAppLanguageCode),
};
}
static async getInitialProps({ Component, ctx }: Object) {
const fullPath = isBrowser() ? window.location.pathname : ctx?.req?.url;
const location = fullPath.slice(3);
const queryDefinitions = await collectQueryDefinitions(Component, location);
const Props = {
location,
pageProps: await this.getPageProps(Component, ctx),
...(await this.getTranslationData(ctx)),
};
const environment = createEnvironment(null, null, Props.lng);
if (queryDefinitions.length === 0) {
return await this.getStaticInitialProps(environment, Props);
}
return await this.getDynamicInitialProps(
queryDefinitions,
environment,
ctx,
Props,
);
}
static async getPageProps(Component: Object, ctx: Object) {
if (Component.getInitialProps) {
return Component.getInitialProps(ctx);
}
return {};
}
environment = createEnvironment(
null,
null,
this.props.lng,
this.props.recordStoreFromSSR,
);
render() {
const { Component, pageProps, location } = this.props;
const langInfo = langInfos[this.props.lng];
const translations = this.props.translations
? this.props.translations
: fallbackTranslations;
const intl = { language: langInfo, translations };
const brandId = 'kiwicom';
const localeId = 'en';
const brand = brands[brandId];
const language = languages[localeId];
const fetched = {
brandLanguage: brandLanguages[brandId][localeId],
countries,
airlines,
continents,
};
const currencyId = 'eur';
const userContext = {
user: null,
brand: 'kiwicom',
loginToken: null,
simpleToken: null,
kwAuthToken: null,
onLogin: () => {},
onLogout: () => Promise.resolve(null),
};
return (
<Container>
<BrandProvider value={brand}>
<RelayProvider
environment={this.environment}
variables={this.props.variablesFromSSR}
>
<SSRContext.Provider
value={{
data: this.props.dataProps,
}}
>
<InitIntl raw={intl}>
{intl => (
<IntlProvider value={intl}>
<FetchedProvider value={fetched}>
<InitCurrency
brand={brand}
countries={countries}
affiliate=""
ip="1.3.3.7"
initialCurrency="EUR"
langCurrency={language.currency}
onChange={() => undefined}
>
{currency => (
<CurrencyProvider
value={{
...currency,
currency:
currency.available[currencyId] ||
currency.currency,
}}
>
<ModalValue>
{modal => (
<ModalProvider value={modal}>
<IsomorphicRouter
basename={`/${this.props.lng}/`}
location={location}
context={{}}
>
<ThemeProvider theme={theme}>
<div>
<NavBar
headerLinks={<div />}
chat={<h1>Chat</h1>}
subscription={<h1>Subscription</h1>}
debug={<h1>Debug</h1>}
portal="modal-portal"
starred=""
onOpenFaq={() => undefined}
onSetModal={() => undefined}
onLogoClick={() => undefined}
onSaveLanguage={() => undefined}
onSelectTrip={() => undefined}
/>
<UserContext.Provider
value={userContext}
>
<div className="SmartFAQ">
<PageVariantContext.Provider
value={{ variant: 'fullPage' }}
>
<Component {...pageProps} />
</PageVariantContext.Provider>
</div>
<ThemeConsumer>
{// eslint-disable-next-line react/no-danger
theme => (
<style
dangerouslySetInnerHTML={{
__html: `
.SmartFAQMarkdownField {
font-family: ${
theme.orbit.fontFamily
};
font-size: ${
theme.orbit
.fontSizeTextNormal
};
font-weight: ${
theme.orbit
.fontWeightNormal
};
color: ${
theme.orbit
.colorTextPrimary
};
line-height: ${
theme.orbit
.lineHeightText
};
text-align: left;
margin: 0;
}
.SmartFAQ {
max-width: 740px;
margin: auto;
font-family: ${
theme.orbit.fontFamily
};
}
body {
padding-top: 92px;
padding-bottom: 40px;
background: ${
theme.orbit
.backgroundBody
};
}
`,
}}
/>
)}
</ThemeConsumer>
</UserContext.Provider>
</div>
</ThemeProvider>
</IsomorphicRouter>
<div id="modal-portal" />
</ModalProvider>
)}
</ModalValue>
</CurrencyProvider>
)}
</InitCurrency>
</FetchedProvider>
</IntlProvider>
)}
</InitIntl>
</SSRContext.Provider>
</RelayProvider>
</BrandProvider>
</Container>
);
}
}
export default MyApp;