Add support chat (#1066)

* Add support chat

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Refactor the chat logic

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add HMAC signing

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Update the button styles

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Update the button styles

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Refactor the chat logic

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Fix the chat not loading

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Fix the chat not loading

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add requested changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add requested changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add requested changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add requested changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add requested changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

---------

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
This commit is contained in:
gitstart-twenty
2023-08-05 07:52:59 +08:00
committed by GitHub
parent 5e6351e099
commit 57c465176a
17 changed files with 239 additions and 11 deletions

View File

@ -1,9 +1,10 @@
import { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { telemetryState } from '@/client-config/states/telemetryState';
import { useGetClientConfigQuery } from '~/generated/graphql';
@ -15,6 +16,7 @@ export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
const [, setSignInPrefilled] = useRecoilState(isSignInPrefilledState);
const [, setTelemetry] = useRecoilState(telemetryState);
const [isLoading, setIsLoading] = useState(true);
const setSupportChat = useSetRecoilState(supportChatState);
const { data, loading } = useGetClientConfigQuery();
@ -31,6 +33,7 @@ export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
setDebugMode(data?.clientConfig.debugMode);
setSignInPrefilled(data?.clientConfig.signInPrefilled);
setTelemetry(data?.clientConfig.telemetry);
setSupportChat(data?.clientConfig.supportChat);
}
}, [
data,
@ -40,6 +43,7 @@ export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
setTelemetry,
setIsLoading,
loading,
setSupportChat,
]);
return isLoading ? <></> : <>{children}</>;

View File

@ -13,6 +13,10 @@ export const GET_CLIENT_CONFIG = gql`
enabled
anonymizationEnabled
}
supportChat {
supportDriver
supportFrontendKey
}
}
}
`;

View File

@ -0,0 +1,11 @@
import { atom } from 'recoil';
import { SupportChat } from '~/generated/graphql';
export const supportChatState = atom<SupportChat>({
key: 'supportChatState',
default: {
supportDriver: 'front',
supportFrontendKey: null,
},
});

3
front/src/modules/types/custom.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare interface Window {
FrontChat?: (method: string, ...args: any[]) => void;
}

View File

@ -2,20 +2,29 @@ import styled from '@emotion/styled';
import NavItemsContainer from './NavItemsContainer';
import NavWorkspaceButton from './NavWorkspaceButton';
import SupportChat from './SupportChat';
type OwnProps = {
children: React.ReactNode;
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
margin-bottom: ${({ theme }) => theme.spacing(2.5)};
width: 100%;
`;
export default function MainNavbar({ children }: OwnProps) {
return (
<StyledContainer>
<NavWorkspaceButton />
<NavItemsContainer>{children}</NavItemsContainer>
<div>
<NavWorkspaceButton />
<NavItemsContainer>{children}</NavItemsContainer>
</div>
<SupportChat />
</StyledContainer>
);
}

View File

@ -0,0 +1,115 @@
import { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { supportChatState } from '@/client-config/states/supportChatState';
import {
Button,
ButtonSize,
ButtonVariant,
} from '@/ui/button/components/Button';
const StyledButtonContainer = styled.div`
display: flex;
`;
const StyledQuestionMark = styled.div`
align-items: center;
border-radius: 50%;
border-style: solid;
border-width: ${({ theme }) => theme.spacing(0.25)};
display: flex;
height: ${({ theme }) => theme.spacing(3.5)};
justify-content: center;
margin-right: ${({ theme }) => theme.spacing(1)};
width: ${({ theme }) => theme.spacing(3.5)};
`;
// insert a script tag into the DOM right before the closing body tag
function insertScript({
src,
innerHTML,
onLoad,
}: {
src?: string;
innerHTML?: string;
onLoad?: (...args: any[]) => void;
}) {
const script = document.createElement('script');
if (src) script.src = src;
if (innerHTML) script.innerHTML = innerHTML;
if (onLoad) script.onload = onLoad;
document.body.appendChild(script);
}
function configureFront(chatId: string) {
const url = 'https://chat-assets.frontapp.com/v1/chat.bundle.js';
// check if Front Chat script is already loaded
const script = document.querySelector(`script[src="${url}"]`);
if (!script) {
// insert script and initialize Front Chat when it loads
insertScript({
src: url,
onLoad: () => {
window.FrontChat?.('init', {
chatId,
useDefaultLauncher: false,
});
},
});
}
}
export default function SupportChat() {
const user = useRecoilValue(currentUserState);
const supportChatConfig = useRecoilValue(supportChatState);
const [isFrontChatLoaded, setIsFrontChatLoaded] = useState(false);
const [isChatShowing, setIsChatShowing] = useState(false);
useEffect(() => {
if (
supportChatConfig?.supportDriver === 'front' &&
supportChatConfig.supportFrontendKey &&
!isFrontChatLoaded
) {
configureFront(supportChatConfig.supportFrontendKey);
setIsFrontChatLoaded(true);
}
if (user?.email && isFrontChatLoaded) {
window.FrontChat?.('identity', {
email: user.email,
name: user.displayName,
userHash: user?.supportHMACKey,
});
}
}, [
isFrontChatLoaded,
supportChatConfig?.supportDriver,
supportChatConfig.supportFrontendKey,
user?.displayName,
user?.email,
user?.supportHMACKey,
]);
function handleSupportClick() {
if (supportChatConfig?.supportDriver === 'front') {
const action = isChatShowing ? 'hide' : 'show';
setIsChatShowing(!isChatShowing);
window.FrontChat?.(action);
}
}
return isFrontChatLoaded ? (
<StyledButtonContainer>
<Button
variant={ButtonVariant.Tertiary}
size={ButtonSize.Small}
title="Support"
icon={<StyledQuestionMark>?</StyledQuestionMark>}
onClick={handleSupportClick}
/>
</StyledButtonContainer>
) : null;
}

View File

@ -27,6 +27,7 @@ export const GET_CURRENT_USER = gql`
locale
colorScheme
}
supportHMACKey
}
}
`;