Skip to content

Commit

Permalink
Merge pull request #83 from papercups-io/support-persisting-open-state
Browse files Browse the repository at this point in the history
Add support for persisting the open/closed state of the chat (`persistOpenState`)
  • Loading branch information
reichert621 committed Mar 2, 2021
2 parents 517b224 + 86d0cc3 commit 3be84cf
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 53 deletions.
6 changes: 5 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,21 @@ const App = ({disco, displayChatWindow}: Props) => {
requireEmailUpfront
showAgentAvailability
hideToggleButton={false}
defaultIsOpen={false}
isOpenByDefault
iconVariant='filled'
persistOpenState
position={{side: 'right', offset: 80}}
styles={{
chatContainer: {
// left: 20,
// right: 'auto',
bottom: 160,
maxHeight: 640,
},
toggleContainer: {
// left: 20,
// right: 'auto',
bottom: 80,
},
toggleButton: {},
}}
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@papercups-io/chat-widget",
"version": "1.1.7",
"version": "1.1.8-beta.1",
"description": "Papercups chat widget",
"author": "reichert621",
"license": "MIT",
Expand Down
10 changes: 10 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type Account = {
};

export type WidgetSettings = {
id?: string;
subtitle?: string;
title?: string;
base_url?: string;
Expand All @@ -44,6 +45,15 @@ export type WidgetSettings = {
new_message_placeholder?: string;
email_input_placeholder?: string;
new_messages_notification_text?: string;
is_branding_hidden?: boolean;
show_agent_availability?: boolean;
agent_available_text?: string;
agent_unavailable_text?: string;
require_email_upfront?: boolean;
is_open_by_default?: boolean;
custom_icon_url?: string;
iframe_url_override?: string;
icon_variant?: 'outlined' | 'filled';
account?: Account;
};

Expand Down
71 changes: 65 additions & 6 deletions src/components/ChatWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,73 @@ type ToggleButtonOptions = {
onToggleOpen: () => void;
};

type StyleOverrides = {
chatContainer?: CSSProperties;
toggleContainer?: CSSProperties;
toggleButton?: CSSProperties;
};

type PositionConfig = {
side: 'left' | 'right';
offset: number;
};

const DEFAULT_X_OFFSET = 20;

const normalizePositionConfig = (
position?: 'left' | 'right' | PositionConfig
): PositionConfig => {
if (!position) {
return {side: 'right', offset: DEFAULT_X_OFFSET};
}

switch (position) {
case 'left':
return {side: 'left', offset: DEFAULT_X_OFFSET};
case 'right':
return {side: 'right', offset: DEFAULT_X_OFFSET};
default:
return position;
}
};

const getDefaultStyles = (
styles: StyleOverrides = {},
position: PositionConfig
): StyleOverrides => {
const {
chatContainer: chatContainerStyle = {},
toggleContainer: toggleContainerStyle = {},
toggleButton: toggleButtonStyle = {},
} = styles;
const {side = 'right', offset = DEFAULT_X_OFFSET} = position;

switch (side) {
case 'left':
return {
chatContainer: {left: offset, right: 'auto', ...chatContainerStyle},
toggleContainer: {left: offset, right: 'auto', ...toggleContainerStyle},
toggleButton: toggleButtonStyle,
};
case 'right':
default:
return {
chatContainer: {right: offset, left: 'auto', ...chatContainerStyle},
toggleContainer: {right: offset, left: 'auto', ...toggleContainerStyle},
toggleButton: toggleButtonStyle,
};
}
};

type Props = SharedProps & {
defaultIsOpen?: boolean;
isOpenByDefault?: boolean;
persistOpenState?: boolean;
hideToggleButton?: boolean;
iconVariant?: 'outlined' | 'filled';
position?: 'left' | 'right' | PositionConfig;
renderToggleButton?: (options: ToggleButtonOptions) => React.ReactElement;
styles?: {
chatContainer?: CSSProperties;
toggleContainer?: CSSProperties;
toggleButton?: CSSProperties;
};
styles?: StyleOverrides;
};

const ChatWidget = (props: Props) => {
Expand All @@ -48,13 +105,15 @@ const ChatWidget = (props: Props) => {
hideToggleButton,
iconVariant,
renderToggleButton,
position = 'right',
styles = {},
} = props;
const positionConfig = normalizePositionConfig(position);
const {
chatContainer: chatContainerStyle = {},
toggleContainer: toggleContainerStyle = {},
toggleButton: toggleButtonStyle = {},
} = styles;
} = getDefaultStyles(styles, positionConfig);

return (
<React.Fragment>
Expand Down
50 changes: 42 additions & 8 deletions src/components/ChatWidgetContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export type SharedProps = {

type Props = SharedProps & {
defaultIsOpen?: boolean;
isOpenByDefault?: boolean;
persistOpenState?: boolean;
canToggle?: boolean;
children: (data: any) => any;
};
Expand Down Expand Up @@ -169,8 +171,6 @@ class ChatWidgetContainer extends React.Component<Props, State> {
const config: WidgetConfig = {
accountId,
baseUrl,
agentAvailableText,
agentUnavailableText,
title: await this.getDefaultTitle(settings),
subtitle: await this.getDefaultSubtitle(settings),
primaryColor: primaryColor || settings.color,
Expand All @@ -182,13 +182,19 @@ class ChatWidgetContainer extends React.Component<Props, State> {
newMessagesNotificationText:
newMessagesNotificationText || settings.new_messages_notification_text,
companyName: settings?.account?.company_name,
requireEmailUpfront: requireEmailUpfront ? 1 : 0,
showAgentAvailability: showAgentAvailability ? 1 : 0,
requireEmailUpfront:
requireEmailUpfront || settings.require_email_upfront ? 1 : 0,
showAgentAvailability:
showAgentAvailability || settings.show_agent_availability ? 1 : 0,
agentAvailableText: settings.agent_available_text || agentAvailableText,
agentUnavailableText:
settings.agent_unavailable_text || agentUnavailableText,
closeable: canToggle ? 1 : 0,
customerId: this.storage.getCustomerId(),
subscriptionPlan: settings?.account?.subscription_plan,
isBrandingHidden: settings?.is_branding_hidden,
metadata: JSON.stringify(metadata),
version: '1.1.6',
version: '1.1.8',
};

const query = qs.stringify(config, {skipEmptyString: true, skipNull: true});
Expand Down Expand Up @@ -466,18 +472,38 @@ class ChatWidgetContainer extends React.Component<Props, State> {
this.send('notifications:display', {shouldDisplayNotifications: false});
};

shouldOpenByDefault = (): boolean => {
const {
defaultIsOpen,
isOpenByDefault,
persistOpenState,
canToggle,
} = this.props;
if (!canToggle) {
return true;
}

const isOpenFromCache = this.storage.getOpenState();

if (persistOpenState) {
return isOpenFromCache;
}

return !!(isOpenByDefault || defaultIsOpen);
};

handleChatLoaded = () => {
this.setState({isLoaded: true});

const {config = {} as WidgetConfig} = this.state;
const {subscriptionPlan = null} = config;
const {defaultIsOpen, canToggle, onChatLoaded = noop} = this.props;
const {onChatLoaded = noop} = this.props;

if (onChatLoaded && typeof onChatLoaded === 'function') {
onChatLoaded();
}

if (defaultIsOpen || !canToggle) {
if (this.shouldOpenByDefault()) {
this.setState({isOpen: true}, () => this.emitToggleEvent(true));
}

Expand Down Expand Up @@ -526,7 +552,15 @@ class ChatWidgetContainer extends React.Component<Props, State> {
emitToggleEvent = (isOpen: boolean) => {
this.send('papercups:toggle', {isOpen});

const {onChatOpened = noop, onChatClosed = noop} = this.props;
const {
persistOpenState = false,
onChatOpened = noop,
onChatClosed = noop,
} = this.props;

if (persistOpenState) {
this.storage.setOpenState(isOpen);
}

if (isOpen) {
onChatOpened && onChatOpened();
Expand Down
Loading

0 comments on commit 3be84cf

Please sign in to comment.