@kiwicom/smart-faq
Version:
576 lines (521 loc) • 16.8 kB
JavaScript
// @flow
import * as React from 'react';
import ReactDOM from 'react-dom';
import Cookies from 'js-cookie';
import Raven from 'raven-js';
import { ThemeProvider } from 'styled-components';
import { getBrandTheme } from '@kiwicom/nitro/lib/records/Theme';
import SmartFAQApp from './SmartFAQApp';
import ContactFormApp from './ContactFormApp';
import { Requester } from './staging/Requests';
import type { User } from './types';
import { loadStaticTranslations } from './staging/utils';
import brandConfigs from './staging/static/brands.json';
import LoginModal from './staging/LoginModal';
import Meta from './staging/Meta';
import SmartFAQDebugTools, {
cabinBaggageArticleId,
} from './staging/SmartFAQDebugTools';
type Props = {||};
type State = {|
openArticle: ?string,
isOpen: boolean,
showLoginModal: boolean,
showPhoneLines: boolean,
user: ?User,
loginToken: ?string,
kwAuthToken: string,
simpleToken: string,
showEmergencies: boolean,
showPriorityLine: boolean,
enableChat: boolean,
forceChat: boolean,
brand: string,
language: string,
translations: { [key: string]: string },
languageDirection: string,
bid: ?number,
|};
const emergencies = [
'Because of the middle age ways how to get to Prague Airport and all donkeys are at strike, we are receiving far more contacts than usual. Please solve your request in our shiny mobile app.',
'The political unrest in Catalan republic has affected many flights and we are experiencing a high number of contacts. Please departure to Belgium to avoid prison.',
];
const chatConfig = {
CHAT_GUID: process.env.CHAT_GUID ?? '',
CHAT_DEPLOYMENT_KEY: process.env.CHAT_DEPLOYMENT_KEY ?? '',
CHAT_ORG_ID: process.env.CHAT_ORG_ID ?? '',
CHAT_QUEUE_NAME: process.env.CHAT_QUEUE_NAME ?? 'CHAT TEST',
};
const getUser = (loginToken: string, kwAuthUser: ?string) => {
if (loginToken) {
return {
id: '1',
email: 'joe.doe@example.com',
firstname: 'Joe',
lastname: 'Doe',
};
}
if (kwAuthUser) {
return {
id: null,
email: 'joe.doe@example.com',
firstname: null,
lastname: null,
};
}
return null;
};
const doNothing = () => {};
class Root extends React.Component<Props, State> {
input: ?HTMLInputElement;
constructor(props) {
super(props);
const loginToken = Cookies.get('loginToken');
const simpleToken = Cookies.get('simpleToken');
const kwAuthToken = Cookies.get('kwAuthToken');
const forceChat = sessionStorage.getItem('forceChat');
window.GuaranteeChatForce = forceChat
? Boolean(parseInt(forceChat, 10))
: false;
// helpers for cypress
const enableChat = Cookies.get('enableChat')
? Boolean(parseInt(Cookies.get('enableChat'), 10))
: true;
const showEmergencies = Cookies.get('showEmergencies')
? Boolean(parseInt(Cookies.get('showEmergencies'), 10))
: false;
const bid = Cookies.get('selectedBooking')
? Number(Cookies.get('selectedBooking'))
: null;
const showArticle = Cookies.get('showArticle')
? Boolean(parseInt(Cookies.get('showArticle'), 10))
: false;
const brand = Cookies.get('brand') ?? 'kiwicom';
this.state = {
openArticle: showArticle ? cabinBaggageArticleId : null,
isOpen: true,
user: getUser(loginToken, kwAuthToken),
loginToken,
simpleToken,
kwAuthToken,
enableChat,
showPriorityLine: true,
forceChat: window.GuaranteeChatForce,
showEmergencies,
showLoginModal: false,
brand,
language: 'en',
translations: loadStaticTranslations('en'),
languageDirection: 'ltr',
bid,
showPhoneLines: false,
};
this.setupLogs();
}
componentDidMount() {
window.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.onKeyDown);
}
onKeyDown = e => {
if (e.noInputFocus) {
return;
}
if (this.input) {
this.input.focus();
}
};
onToggleChat = () => {
this.setState(({ enableChat }) => ({ enableChat: !enableChat }));
};
onForceChat = () => {
if (typeof window === 'undefined') {
return;
}
const forceChat = !window.GuaranteeChatForce;
sessionStorage.setItem('forceChat', forceChat ? '1' : '0');
window.GuaranteeChatForce = forceChat;
this.setState({ forceChat });
};
setupLogs = () => {
if (
process.env.NODE_ENV === 'production' &&
process.env.SENTRY_URL_STAGING
) {
window.Raven = Raven;
Raven.config(process.env.SENTRY_URL_STAGING).install();
}
};
processLogin = async (email: string, password: string) => {
if (!(email && password)) {
// eslint-disable-next-line no-console
console.error('Testing user not set in env vars.');
return;
}
const loginToken = await Requester.login(
email,
password,
'',
this.state.brand,
);
Cookies.set('loginToken', loginToken);
this.setState(() => ({
showLoginModal: false,
loginToken: loginToken,
user: getUser(loginToken),
}));
};
handleClose = () => this.setState({ showLoginModal: false });
handleLoginOpen = () => {
this.setState({ showLoginModal: true });
};
handleLogin = (email, password) => this.processLogin(email, password);
handleSidebarClick = () => this.handleToggleSmartFAQ(false);
handleLogout = () => {
this.setState({
user: null,
loginToken: null,
simpleToken: '',
kwAuthToken: '',
});
Cookies.remove('loginToken');
Cookies.remove('simpleToken');
Cookies.remove('kwAuthToken');
return Promise.resolve(true);
};
handleBidChange = e => {
const value = e.target.value;
const bid = value ? Number(value) : null;
Cookies.set('selectedBooking', bid);
this.setState({ bid: Number.isFinite(bid) ? bid : null });
};
handleTokenChange = e => {
const simpleToken = e.target.value;
Cookies.set('simpleToken', simpleToken);
this.setState({ simpleToken });
};
handleKwTokenChange = e => {
this.setState({ kwAuthToken: e.target.value });
};
toggleApp = () => {
this.setState(({ isOpen }) => ({ isOpen: !isOpen }));
};
handleToggleSmartFAQ = (shouldBeOpened: boolean) => {
this.setState({ isOpen: shouldBeOpened, openArticle: null });
};
handleToggleArticle = () =>
this.setState(({ openArticle }) => ({
openArticle: openArticle ? null : cabinBaggageArticleId,
isOpen: !openArticle,
}));
toggleEmergencies = () => {
this.setState(({ showEmergencies }) => ({
showEmergencies: !showEmergencies,
}));
};
togglePhonesList = () => {
this.setState(({ showPhoneLines }) => ({
showPhoneLines: !showPhoneLines,
}));
};
togglePriorityLine = () => {
this.setState(({ showPriorityLine }) => ({
showPriorityLine: !showPriorityLine,
}));
};
changeLanguage = (e: SyntheticInputEvent<HTMLInputElement>) => {
const language = e.target.value;
this.setState({ language, translations: loadStaticTranslations(language) });
};
changelanguageDirection = (e: SyntheticInputEvent<HTMLInputElement>) => {
this.setState({ languageDirection: e.target.value });
};
changeBrand = (e: SyntheticInputEvent<HTMLInputElement>) => {
const brand = e.target.value;
this.setState({ brand });
Cookies.set('brand', brand);
};
render() {
const {
isOpen,
showEmergencies,
language,
languageDirection,
brand,
showLoginModal,
showPhoneLines,
} = this.state;
const brandConfig = brandConfigs[brand] ?? brandConfigs.kiwicom;
const isRtl = languageDirection === 'rtl';
const theme = getBrandTheme(brandConfig, isRtl);
return (
<ThemeProvider theme={theme}>
<div>
<Meta />
{showLoginModal && (
<LoginModal
onSubmit={this.handleLogin}
onClose={this.handleClose}
/>
)}
<div
className="toggler"
onKeyUp={doNothing}
role="button"
tabIndex="-1"
onClick={this.toggleApp}
>
Toggle SmartFAQ
</div>
<div className="mockedMainView">
<div>
<h3>Show some emergencies</h3>
<input
type="checkbox"
checked={showEmergencies}
onChange={this.toggleEmergencies}
/>
<h3>Enable chat</h3>
<input
type="checkbox"
checked={this.state.enableChat}
onChange={this.onToggleChat}
/>
<h3>Force chat to be always available</h3>
<input
type="checkbox"
onChange={this.onForceChat}
checked={this.state.forceChat}
/>
<h3>Show Customer Support Phones List</h3>
<input
type="checkbox"
onChange={this.togglePhonesList}
checked={this.state.showPhoneLines}
/>
<br />
<label htmlFor="priorityLine">
show priority line
<input
id="priorityLine"
type="checkbox"
onChange={this.togglePriorityLine}
checked={this.state.showPriorityLine}
/>
</label>
<h3>Set BID & simple token for debugging.</h3>
<input
onChange={this.handleBidChange}
value={this.state.bid ?? ''}
placeholder="BID"
/>
<input
onChange={this.handleTokenChange}
value={this.state.simpleToken}
placeholder="Simple token"
/>
<div>
<h3>Set KW-Auth-Token for debugging.</h3>
<input
onChange={this.handleKwTokenChange}
value={this.state.kwAuthToken}
placeholder="KW-Auth-Token"
/>
</div>
<h3>Change languages</h3>
<label htmlFor="en-GB">
<input
type="radio"
value="en"
id="en-GB"
checked={language === 'en'}
onChange={this.changeLanguage}
/>
English(en-GB)
</label>
<label htmlFor="es-ES">
<input
type="radio"
value="es"
id="es-ES"
checked={language === 'es'}
onChange={this.changeLanguage}
/>
Spanish(es-ES)
</label>
<label htmlFor="cs-CZ">
<input
type="radio"
value="cz"
id="cs-CZ"
checked={language === 'cz'}
onChange={this.changeLanguage}
/>
Czech(cs-CZ)
</label>
<h3>Change brand</h3>
<label htmlFor="kiwicom">
<input
type="radio"
value="kiwicom"
id="kiwicom"
checked={brand === 'kiwicom'}
onChange={this.changeBrand}
/>
Kiwi.com
</label>
<label htmlFor="visitberlin">
<input
type="radio"
value="visitberlin"
id="visitberlin"
checked={brand === 'visitberlin'}
onChange={this.changeBrand}
/>
Visit Berlin
</label>
<label htmlFor="holidaypirates">
<input
type="radio"
value="holidaypirates"
id="holidaypirates"
checked={brand === 'holidaypirates'}
onChange={this.changeBrand}
/>
Holiday Pirates
</label>
<h3>Change from LTR to RTL languages</h3>
<label htmlFor="LTR">
<input
type="radio"
value="ltr"
id="LTR"
checked={languageDirection === 'ltr'}
onChange={this.changelanguageDirection}
/>
LTR
</label>
<label htmlFor="RTL">
<input
type="radio"
value="rtl"
id="RTL"
checked={languageDirection === 'rtl'}
onChange={this.changelanguageDirection}
/>
RTL
</label>
<SmartFAQDebugTools
showArticle={Boolean(this.state.openArticle)}
onToggleArticle={this.handleToggleArticle}
/>
</div>
<div>
<ContactFormApp.BookingInfoProvider
value={{
// eslint-disable-next-line no-console
onLoad: booking => console.log(booking),
}}
>
<ContactFormApp.ContactFormChatApp
language={language}
brand={this.state.brand}
translations={null}
chatConfig={chatConfig}
loginToken={this.state.loginToken}
simpleToken={this.state.simpleToken}
kwAuthToken={this.state.kwAuthToken}
bid={this.state.bid}
enableChat={this.state.enableChat}
/>
</ContactFormApp.BookingInfoProvider>
</div>
<div>
{showPhoneLines && (
<ContactFormApp.CustomerSupportPhones
language={language}
showPriorityLine={this.state.showPriorityLine}
translations={this.state.translations}
linkToLearnMoreArticle={doNothing}
/>
)}
</div>
</div>
{isOpen && (
<div className="sidebarOverlay" onClick={this.handleSidebarClick} />
)}
<div className="sidebar" dir={languageDirection}>
<SmartFAQApp
onToggle={this.handleToggleSmartFAQ}
openArticle={this.state.openArticle}
onLogin={this.handleLoginOpen}
onLogout={this.handleLogout}
translations={this.state.translations}
language={language}
brand={this.state.brand}
user={this.state.user}
isOpen={isOpen}
bid={this.state.bid}
loginToken={this.state.loginToken}
simpleToken={this.state.simpleToken}
kwAuthToken={this.state.kwAuthToken}
enableChat={this.state.enableChat}
chatConfig={chatConfig}
emergencies={showEmergencies ? emergencies : []}
log={(event, props) =>
/* eslint-disable-next-line no-console */
console.log('TRACKING', event, props)
}
/>
</div>
<style jsx global>
{`
.sidebar {
position: fixed;
right: 0;
top: 0;
}
.sidebarOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.mockedMainView {
margin-top: 100px;
display: flex;
}
.toggler {
position: fixed;
top: 20px;
left: 20px;
cursor: pointer;
color: white;
border: 3px solid green;
padding: 5px;
outline: none;
background: green;
}
`}
</style>
</div>
</ThemeProvider>
);
}
}
(() => {
const root = document.createElement('div');
root.setAttribute('id', 'root');
if (!document.body) {
throw new Error('No browser?');
}
document.body.appendChild(root);
const id = document.getElementById('root');
if (!id) {
throw new Error('Root element is missing!');
}
ReactDOM.render(<Root />, id);
})();