@seamly/web-ui 24.3.1 → 24.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5427,7 +5427,7 @@ class API {
5427
5427
  return {
5428
5428
  clientName: "@seamly/web-ui",
5429
5429
  clientVariant: this.#layoutMode,
5430
- clientVersion: "24.3.1",
5430
+ clientVersion: "24.4.0",
5431
5431
  currentUrl: window.location.toString(),
5432
5432
  screenResolution: `${window.screen.width}x${window.screen.height}`,
5433
5433
  timezone: getTimeZone(),
@@ -12003,6 +12003,10 @@ const participantReducer = (participantInfo, action) => {
12003
12003
  };
12004
12004
  };
12005
12005
  const calculateNewEntryMeta = (entryMeta, channelEvent) => {
12006
+ // Events originating from the client should leave the entry meta as is
12007
+ if (channelEvent?.payload?.fromClient === true) {
12008
+ return entryMeta;
12009
+ }
12006
12010
  const entry = channelEvent?.type === 'message' ? channelEvent?.payload.translatedEntry || channelEvent?.payload.entry : {
12007
12011
  options: undefined,
12008
12012
  type: undefined
@@ -12224,7 +12228,7 @@ const storeSlice = createSlice({
12224
12228
  // Use the messages from the payload, as `events` only contains displayable ones.
12225
12229
  // The first participant message found is the 'last', as we receive them from newest to oldest.
12226
12230
  const lastParticipantEvent = messages.find(m => m.type === 'participant');
12227
- // @ts-ignore TypeScript incorrectly assumes that the payload can be any of info/message/participant
12231
+ // @ts-expect-error TypeScript incorrectly assumes that the payload can be any of info/message/participant
12228
12232
  const lastParticipantId = lastParticipantEvent?.payload?.participant?.id;
12229
12233
  const {
12230
12234
  entry
@@ -12238,10 +12242,25 @@ const storeSlice = createSlice({
12238
12242
  ...(entry?.options ? entry.options : {})
12239
12243
  }
12240
12244
  }, events[events.length - 1]);
12241
- const newFeatures = {
12245
+ let newFeatures = {
12242
12246
  ...state.options.features
12243
12247
  };
12248
+ // The first service message found is the 'last', as we receive them from newest to oldest.
12249
+ const lastServiceMessage = messages.find(m => !m.payload.fromClient && ['message', 'participant'].includes(m.type));
12244
12250
  const newFeaturesHasUpload = newFeatures.hasOwnProperty(featureKeys.uploads);
12251
+
12252
+ // Check for upload enabled by entry type
12253
+ if (newFeaturesHasUpload && lastServiceMessage?.type === 'message') {
12254
+ // @ts-expect-error TypeScript incorrectly assumes that the payload can be any of info/message/participant
12255
+ const entryType = lastServiceMessage.payload.entry?.type || '';
12256
+ newFeatures = {
12257
+ ...newFeatures,
12258
+ uploads: {
12259
+ enabled: newFeatures.uploads?.enabled || false,
12260
+ enabledFromEntry: entryType === entryTypes.upload
12261
+ }
12262
+ };
12263
+ }
12245
12264
  state.unreadEvents = unreadMessageCount;
12246
12265
  state.userHasResponded = userResponded;
12247
12266
  state.events = events.filter(e => e.type !== 'participant' || !!e.payload.participant?.introduction);
@@ -15361,6 +15380,7 @@ const CarouselMessage = ({
15361
15380
 
15362
15381
 
15363
15382
 
15383
+
15364
15384
  const useChoicePrompt = event => {
15365
15385
  const {
15366
15386
  payload
@@ -15371,6 +15391,7 @@ const useChoicePrompt = event => {
15371
15391
  emitEvent,
15372
15392
  sendAction
15373
15393
  } = use_seamly_commands();
15394
+ const hasConversation = useSeamlyHasConversation();
15374
15395
  const {
15375
15396
  activeServiceSessionId
15376
15397
  } = useSeamlyServiceInfo();
@@ -15400,6 +15421,10 @@ const useChoicePrompt = event => {
15400
15421
  setShowOptions(payload.id === lastEventId);
15401
15422
  }, [payload, lastEventId]);
15402
15423
  const onChoiceClickHandler = choice => {
15424
+ // Do not allow interaction without a conversation
15425
+ if (!hasConversation()) {
15426
+ return;
15427
+ }
15403
15428
  const transactionId = randomId();
15404
15429
  const action = {
15405
15430
  type: actionTypes.pickChoice,
@@ -15575,6 +15600,7 @@ const SuggestionsList = ({
15575
15600
 
15576
15601
 
15577
15602
 
15603
+
15578
15604
  const useSuggestions = event => {
15579
15605
  const {
15580
15606
  payload
@@ -15599,6 +15625,7 @@ const ConversationSuggestions = ({
15599
15625
  emitEvent,
15600
15626
  sendAction
15601
15627
  } = use_seamly_commands();
15628
+ const hasConversation = useSeamlyHasConversation();
15602
15629
  const {
15603
15630
  suggestions,
15604
15631
  payload
@@ -15623,6 +15650,10 @@ const ConversationSuggestions = ({
15623
15650
  id,
15624
15651
  question
15625
15652
  }) => {
15653
+ // Do not allow interaction without a conversation
15654
+ if (!hasConversation()) {
15655
+ return;
15656
+ }
15626
15657
  setIsExpanded(false);
15627
15658
  dispatch(setHasResponded(true));
15628
15659
  const transactionId = randomId();
@@ -15643,7 +15674,7 @@ const ConversationSuggestions = ({
15643
15674
  sendAction(action);
15644
15675
  addMessageBubble(question, transactionId);
15645
15676
  emitEvent(`action.${action.type}`, action);
15646
- }, [addMessageBubble, dispatch, emitEvent, payload.id, sendAction]);
15677
+ }, [addMessageBubble, dispatch, emitEvent, hasConversation, payload.id, sendAction]);
15647
15678
  if (!isExpanded || userHasResponded || !hasLastTransactionEvent || !showSuggestions) {
15648
15679
  return null;
15649
15680
  }
@@ -19809,6 +19840,7 @@ const InOutTransition = ({
19809
19840
 
19810
19841
 
19811
19842
 
19843
+
19812
19844
 
19813
19845
 
19814
19846
  const Suggestions = ({
@@ -19826,6 +19858,7 @@ const Suggestions = ({
19826
19858
  emitEvent,
19827
19859
  sendAction
19828
19860
  } = use_seamly_commands();
19861
+ const hasConversation = useSeamlyHasConversation();
19829
19862
  const {
19830
19863
  isOpen,
19831
19864
  setVisibility
@@ -19888,6 +19921,10 @@ const Suggestions = ({
19888
19921
  id,
19889
19922
  question
19890
19923
  }) => {
19924
+ // Do not allow interaction without a conversation
19925
+ if (!hasConversation()) {
19926
+ return;
19927
+ }
19891
19928
  if (hasCountdown) {
19892
19929
  endCountdown(true);
19893
19930
  }
@@ -19918,7 +19955,7 @@ const Suggestions = ({
19918
19955
  });
19919
19956
  }
19920
19957
  focusSkiplinkTarget();
19921
- }, [addMessageBubble, continueChat, endCountdown, emitEvent, focusSkiplinkTarget, hasCountdown, hasPrompt, isOpen, payload, sendAction, setVisibility]);
19958
+ }, [addMessageBubble, continueChat, endCountdown, emitEvent, focusSkiplinkTarget, hasConversation, hasCountdown, hasPrompt, isOpen, payload, sendAction, setVisibility]);
19922
19959
  (0,hooks_.useEffect)(() => {
19923
19960
  if (prevSuggestions.current !== suggestions && !hideSuggestions) {
19924
19961
  if (hasSuggestions) {
@@ -22085,6 +22122,19 @@ const upload_Upload = () => {
22085
22122
  }
22086
22123
  prevIsComplete.current = isComplete;
22087
22124
  }, [isUploading, isComplete, clearUploads, cancelEntrySelection, focusSkiplinkTarget, sendPolite, t]);
22125
+
22126
+ // Reset form when service no longer allows uploads
22127
+ (0,hooks_.useEffect)(() => {
22128
+ // If we are currently uploading, don't clear the uploads as that
22129
+ // may be confusing to the user.
22130
+ // When the upload completes and the user clicks the submit button,
22131
+ // there will be an error.
22132
+ if (!serviceAllowsUploads && !isUploading) {
22133
+ clearUploads();
22134
+ cancelEntrySelection();
22135
+ focusSkiplinkTarget();
22136
+ }
22137
+ }, [serviceAllowsUploads, isUploading, clearUploads, cancelEntrySelection, focusSkiplinkTarget]);
22088
22138
  const handleSubmit = (0,hooks_.useCallback)(({
22089
22139
  fileList
22090
22140
  }) => {
@@ -22277,7 +22327,6 @@ const EntryContainer = () => {
22277
22327
  upload: entry_upload
22278
22328
  });
22279
22329
  const [renderEntry, setRenderEntry] = (0,hooks_.useState)(() => activeEntry);
22280
- const [renderEntryOptions, setRenderEntryOptions] = (0,hooks_.useState)(() => activeEntryOptions);
22281
22330
  const config = useConfig();
22282
22331
  const {
22283
22332
  accountAllowsUploads
@@ -22308,11 +22357,10 @@ const EntryContainer = () => {
22308
22357
  }, [hasCountdown, hasResumeConversationPrompt, focusFn]);
22309
22358
  (0,hooks_.useEffect)(() => {
22310
22359
  setRenderEntry(activeEntry);
22311
- setRenderEntryOptions(activeEntryOptions);
22312
22360
  // This focus action is required for auto entry changes. User driven
22313
22361
  // changes should be handled in the originating components.
22314
22362
  focusFn();
22315
- }, [activeEntry, activeEntryOptions, focusFn, entryContainer]);
22363
+ }, [activeEntry, focusFn, entryContainer]);
22316
22364
 
22317
22365
  // Check if the active element is inside this container and save it.
22318
22366
  containedFocus.current = !!(entryContainer.current && entryContainer.current.contains(document.activeElement));
@@ -22321,7 +22369,7 @@ const EntryContainer = () => {
22321
22369
  // Once we do, this property should be moved to that component instead.
22322
22370
  const {
22323
22371
  allowManualInput = true
22324
- } = renderEntryOptions;
22372
+ } = activeEntryOptions;
22325
22373
  return /*#__PURE__*/(0,jsx_runtime_namespaceObject.jsxs)("div", {
22326
22374
  className: css_className('chat__entry'),
22327
22375
  ref: entryContainer,