-
Notifications
You must be signed in to change notification settings - Fork 399
New: SD-966-Live-Chat #13443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
New: SD-966-Live-Chat #13443
Changes from 9 commits
f81b51f
1d11488
32566af
86abc60
698cea9
e1398d5
2fdba04
7bcd431
8dfcd40
ee3c8e4
59b6506
c12fed2
c1340f3
a146bc5
d17a30d
05a8030
a618887
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,5 +9,63 @@ | |
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/index.tsx"></script> | ||
| <script type="text/javascript"> | ||
| let hasLiveChatInitialized = false; | ||
|
|
||
| function openLiveChatOnce() { | ||
| const enableLiveChat = window.sessionStorage.getItem('EnableLiveChat') === 'true'; | ||
|
|
||
| if (!enableLiveChat || hasLiveChatInitialized) { | ||
| return; | ||
| } | ||
|
|
||
| hasLiveChatInitialized = true; | ||
| window.sessionStorage.removeItem('EnableLiveChat'); | ||
|
|
||
| window.addEventListener("message", (event) => { | ||
| const { action, data } = event.data; | ||
| if (action === "prechatLoaded") { | ||
| console.debug("Received from iframe:", data); | ||
| const dataMap = { | ||
| JWE_Token: "<Token>", | ||
| Subject: window.__supportChatSubject || "<Subject>" | ||
| }; | ||
|
||
| event.source.postMessage( | ||
|
||
| { action: "hiddenParameters", data: dataMap }, | ||
| event.origin | ||
| ); | ||
| } | ||
|
||
| }); | ||
|
|
||
| window.addEventListener("onEmbeddedMessagingButtonCreated", () => { | ||
| embeddedservice_bootstrap.utilAPI.launchChat(); | ||
| }, { once: true }); | ||
|
|
||
| embeddedservice_bootstrap.settings.language = "en_US"; | ||
| embeddedservice_bootstrap.init( | ||
| "00DWH000002hLKz", | ||
| "Compute_Chat_Support", | ||
| "%REACT_APP_CHAT_DEPLOYMENT_URL%", | ||
| { | ||
| scrt2URL: | ||
| "%REACT_APP_CHAT_SCRT2_URL%" | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| function initEmbeddedMessaging() { | ||
| try { | ||
| window.addEventListener("manager:enable-live-chat", openLiveChatOnce); | ||
| openLiveChatOnce(); | ||
| } catch (err) { | ||
| console.error("Error loading Embedded Messaging: ", err); | ||
| } | ||
| } | ||
| </script> | ||
| <script | ||
| type="text/javascript" | ||
| src="%REACT_APP_CHAT_BOOTSTRAP_JS_URL%" | ||
| onload="initEmbeddedMessaging()"> | ||
| </script> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,12 +12,13 @@ | |
| Typography, | ||
| } from '@linode/ui'; | ||
| import { reduceAsync, scrollErrorIntoViewV2 } from '@linode/utilities'; | ||
| import { useLocation } from '@tanstack/react-router'; | ||
| import { useLocation, useNavigate } from '@tanstack/react-router'; | ||
| import * as React from 'react'; | ||
| import type { JSX } from 'react'; | ||
| import { Controller, FormProvider, useForm } from 'react-hook-form'; | ||
| import { debounce } from 'throttle-debounce'; | ||
|
|
||
| import { useFlags } from 'src/hooks/useFlags'; | ||
| import { sendSupportTicketExitEvent } from 'src/utilities/analytics/customEventAnalytics'; | ||
| import { getErrorStringOrDefault } from 'src/utilities/errorUtils'; | ||
| import { storage, supportTicketStorageDefaults } from 'src/utilities/storage'; | ||
|
|
@@ -147,8 +148,11 @@ | |
| prefilledTicketType, | ||
| prefilledTitle, | ||
| } = props; | ||
| const flags = useFlags(); | ||
| const liveChat = Boolean(flags.liveChat); | ||
|
|
||
| const location = useLocation(); | ||
| const navigate = useNavigate(); | ||
| const locationState = location.state as SupportTicketLocationState; | ||
|
|
||
| // Collect prefilled data from props or Link parameters. | ||
|
|
@@ -185,7 +189,11 @@ | |
| ), | ||
| entityId: _prefilledEntity?.id ? String(_prefilledEntity.id) : '', | ||
| entityInputValue: '', | ||
| entityType: _prefilledEntity?.type ?? 'general', | ||
| entityType: | ||
| _prefilledEntity?.type ?? | ||
| (valuesFromStorage.entityType === 'general' | ||
| ? 'none' | ||
| : (valuesFromStorage.entityType ?? 'none')), | ||
| summary: getInitialValue(newPrefilledTitle, valuesFromStorage.summary), | ||
| ticketType: _prefilledTicketType ?? 'general', | ||
| }, | ||
|
|
@@ -241,7 +249,11 @@ | |
| description: clearValues ? '' : valuesFromStorage.description, | ||
| entityId: clearValues ? '' : valuesFromStorage.entityId, | ||
| entityInputValue: clearValues ? '' : valuesFromStorage.entityInputValue, | ||
| entityType: clearValues ? 'general' : valuesFromStorage.entityType, | ||
| entityType: clearValues | ||
| ? 'none' | ||
| : valuesFromStorage.entityType === 'general' | ||
| ? 'none' | ||
| : valuesFromStorage.entityType, | ||
|
Comment on lines
+277
to
+281
|
||
| selectedSeverity: clearValues | ||
| ? undefined | ||
| : valuesFromStorage.selectedSeverity, | ||
|
|
@@ -277,6 +289,14 @@ | |
| setFiles(newFiles); | ||
| }; | ||
|
|
||
| const handleStartLiveChat = async () => { | ||
| window.sessionStorage.setItem('EnableLiveChat', 'true'); | ||
| window.dispatchEvent(new Event('manager:enable-live-chat')); | ||
| props.onClose(); | ||
| window.setTimeout(() => resetDialog(true), 500); | ||
| await navigate({ to: '/support' }); | ||
| }; | ||
|
|
||
| /* Reducer passed into reduceAsync (previously Bluebird.reduce) below. | ||
| * Unfortunately, this reducer has side effects. Uploads each file and accumulates a list of | ||
| * any upload errors. Also tracks loading state of each individual file. */ | ||
|
|
@@ -349,6 +369,18 @@ | |
| const handleSubmit = form.handleSubmit(async (values) => { | ||
| const { onSuccess } = props; | ||
|
|
||
| if (values.entityType === 'none') { | ||
| form.setError('entityType', { | ||
| message: 'Please select a topic.', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| if (liveChat && entityType === 'general') { | ||
| await handleStartLiveChat(); | ||
| return; | ||
| } | ||
|
Comment on lines
+434
to
+444
|
||
|
|
||
| const _description = formatDescription(values, ticketType); | ||
|
|
||
| // If this is an account limit ticket, we needed the entity type but won't actually send a valid entity selection. | ||
|
|
@@ -459,31 +491,32 @@ | |
| /> | ||
| )} | ||
| /> | ||
| {hasSeverityCapability && ( | ||
| <Controller | ||
| control={form.control} | ||
| name="selectedSeverity" | ||
| render={({ field }) => ( | ||
| <Autocomplete | ||
| autoHighlight | ||
| data-qa-ticket-severity | ||
| label="Severity" | ||
| onChange={(e, severity) => | ||
| field.onChange( | ||
| severity !== null ? severity.value : undefined | ||
| ) | ||
| } | ||
| options={SEVERITY_OPTIONS} | ||
| sx={{ maxWidth: 'initial' }} | ||
| textFieldProps={{ | ||
| tooltipPosition: 'right', | ||
| tooltipText: TICKET_SEVERITY_TOOLTIP_TEXT, | ||
| }} | ||
| value={selectedSeverityOption ?? null} | ||
| /> | ||
| )} | ||
| /> | ||
| )} | ||
| {hasSeverityCapability && | ||
| !(liveChat && entityType === 'general') && ( | ||
| <Controller | ||
| control={form.control} | ||
| name="selectedSeverity" | ||
| render={({ field }) => ( | ||
| <Autocomplete | ||
| autoHighlight | ||
| data-qa-ticket-severity | ||
| label="Severity" | ||
| onChange={(e, severity) => | ||
| field.onChange( | ||
| severity !== null ? severity.value : undefined | ||
| ) | ||
| } | ||
| options={SEVERITY_OPTIONS} | ||
| sx={{ maxWidth: 'initial' }} | ||
| textFieldProps={{ | ||
| tooltipPosition: 'right', | ||
| tooltipText: TICKET_SEVERITY_TOOLTIP_TEXT, | ||
| }} | ||
| value={selectedSeverityOption ?? null} | ||
| /> | ||
| )} | ||
| /> | ||
| )} | ||
| </> | ||
| )} | ||
| {ticketType === 'smtp' && <SupportTicketSMTPFields />} | ||
|
|
@@ -495,34 +528,38 @@ | |
| {(!ticketType || ticketType === 'general') && ( | ||
| <> | ||
| {props.hideProductSelection ? null : ( | ||
| <SupportTicketProductSelectionFields /> | ||
| <SupportTicketProductSelectionFields liveChat={liveChat} /> | ||
| )} | ||
| <Box mt={1}> | ||
| <Controller | ||
| control={form.control} | ||
| name="description" | ||
| render={({ field, fieldState }) => ( | ||
| <TabbedReply | ||
| error={fieldState.error?.message} | ||
| handleChange={field.onChange} | ||
| placeholder={ | ||
| 'Tell us more about the trouble youβre having and any steps youβve already taken to resolve it.' | ||
| } | ||
| required | ||
| value={description} | ||
| {!(liveChat && entityType === 'general') && ( | ||
| <> | ||
| <Box mt={1}> | ||
| <Controller | ||
| control={form.control} | ||
| name="description" | ||
| render={({ field, fieldState }) => ( | ||
| <TabbedReply | ||
| error={fieldState.error?.message} | ||
| handleChange={field.onChange} | ||
| placeholder={ | ||
| "Tell us more about the trouble you're having and any steps you've already taken to resolve it." | ||
| } | ||
| required | ||
| value={description} | ||
| /> | ||
| )} | ||
| /> | ||
| )} | ||
| /> | ||
| </Box> | ||
| <Accordion | ||
| detailProps={{ sx: { p: 0.25 } }} | ||
| heading="Formatting Tips" | ||
| summaryProps={{ sx: { paddingX: 0.25 } }} | ||
| sx={(theme) => ({ mt: `${theme.spacing(0.5)} !important` })} // forcefully disable margin when accordion is expanded | ||
| > | ||
| <MarkdownReference /> | ||
| </Accordion> | ||
| <AttachFileForm files={files} updateFiles={updateFiles} /> | ||
| </Box> | ||
| <Accordion | ||
| detailProps={{ sx: { p: 0.25 } }} | ||
| heading="Formatting Tips" | ||
| summaryProps={{ sx: { paddingX: 0.25 } }} | ||
| sx={(theme) => ({ mt: `${theme.spacing(0.5)} !important` })} // forcefully disable margin when accordion is expanded | ||
|
Check warning on line 556 in packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx
|
||
| > | ||
| <MarkdownReference /> | ||
| </Accordion> | ||
| <AttachFileForm files={files} updateFiles={updateFiles} /> | ||
| </> | ||
| )} | ||
| {form.formState.errors.root && ( | ||
| <Notice | ||
| data-qa-notice | ||
|
|
@@ -536,9 +573,15 @@ | |
| <ActionsPanel | ||
| primaryButtonProps={{ | ||
| 'data-testid': 'submit', | ||
| label: 'Open Ticket', | ||
| label: | ||
| liveChat && entityType === 'general' | ||
|
||
| ? 'Start a Live Chat' | ||
|
||
| : 'Open Ticket', | ||
| loading: submitting, | ||
| onClick: handleSubmit, | ||
| onClick: | ||
| liveChat && entityType === 'general' | ||
| ? handleStartLiveChat | ||
| : handleSubmit, | ||
|
||
| }} | ||
| secondaryButtonProps={{ | ||
| 'data-testid': 'cancel', | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasLiveChatInitializedpreventsopenLiveChatOnce()from doing anything after the first initialization, even if the user later tries to start another live chat in the same session. If the intent is βinit once, launch manyβ, consider separating initialization from launching (e.g., callembeddedservice_bootstrap.initonce, but still allow subsequent triggers to calllaunchChat()again).