Skip to content

Enhance: EM - save customer select contact type -> tabs#6916

Merged
Bilguun0410 merged 20 commits intomainfrom
frontline-imap
Feb 2, 2026
Merged

Enhance: EM - save customer select contact type -> tabs#6916
Bilguun0410 merged 20 commits intomainfrom
frontline-imap

Conversation

@Bilguun0410
Copy link
Copy Markdown
Collaborator

@Bilguun0410 Bilguun0410 commented Feb 2, 2026

Summary by Sourcery

Update messenger customer notification and preview experiences and surface additional configuration-driven messaging and contact options.

New Features:

  • Display configured contact links with favicons and tooltips in the messenger header intro.
  • Show an initial welcome message bubble in conversations using configured or default text.
  • Default customer contact type to email in ticket creation forms and expose contact type selection as tabs.

Enhancements:

  • Align preview components with live messenger behavior for greeting titles, messages, welcome text, and chat input placeholder and styling.
  • Extend online hours formatting to include listed available days and adjust timezone label punctuation.
  • Simplify contact type selection UI by replacing the popover combobox with inline tabs.

Important

Enhance messenger UI with improved chat input, conversation details, header, and notification form, adding link favicons and refining message grouping and online hours formatting.

  • Chat Input:
    • chat-input.tsx: Changed placeholder logic to always use InitialMessage.WELCOME when online.
  • Conversation Details:
    • conversation-details.tsx: Added default logo and welcome message display.
    • Group messages by time and user.
  • Header:
    • header.tsx: Added link favicons and contact links display.
    • Updated online hours formatting to include days.
  • Notify Customer Form:
    • notify-customer-form.tsx: Replaced contact type selection with tabs.
    • Default contact type set to 'email'.
  • Misc:
    • Added link-favicon.tsx for displaying favicons.
    • formatOnlineHours.ts: Enhanced to include days in the formatted string.
    • Removed unused components and imports in EMPreviewChatInput.tsx and EMPreviewIntro.tsx.

This description was created by Ellipsis for 07665df. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added welcome message display in conversation area with avatar.
    • Introduced contact information section with website links and tooltips in messenger header.
    • Enhanced online status to include days of availability alongside hours.
  • Improvements

    • Simplified contact type selection from dropdown to tab interface.
    • Improved messenger preview with dynamic greeting and welcome message support.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Feb 2, 2026

Reviewer's Guide

Refines the messenger widget’s customer notification and intro experiences by replacing the contact-type combobox with tabs (defaulting to email), surfacing configurable greeting/welcome text and contact links (with favicons), adjusting availability formatting, and aligning the in-widget UI with the admin preview components while cleaning out unused/old UI code.

Sequence diagram for rendering header contact links with favicons

sequenceDiagram
  actor User
  participant HeaderIntro
  participant Tooltip
  participant LinkFavicon
  participant getGoogleFavicon
  participant Avatar

  User->>HeaderIntro: Open_messenger_widget
  HeaderIntro->>HeaderIntro: Read_messengerData_links
  alt links_configured
    loop For_each_link_entry
      HeaderIntro->>Tooltip: Render_with_key_and_children
      Tooltip->>LinkFavicon: Render_url(url)
      LinkFavicon->>getGoogleFavicon: getGoogleFavicon(url)
      getGoogleFavicon-->>LinkFavicon: faviconUrl
      LinkFavicon->>Avatar: Render_with_Image_src(faviconUrl)
      Avatar-->>LinkFavicon: Avatar_component_tree
      LinkFavicon-->>Tooltip: Avatar_with_favicon
      Tooltip-->>User: Show_icon_and_hover_tooltip(key)
    end
  else no_links
    HeaderIntro-->>User: Show_intro_without_contact_links
  end
Loading

Updated class diagram for messenger components and utilities

classDiagram
  direction LR

  class NotifyCustomerForm {
    +NotifyCustomerForm()
    -form
    -handleSubmit()
    -onTypeChange(value string)
  }

  class Tabs {
    +value string
    +onValueChange(value string)
    +className string
  }

  class TabsList {
    +className string
  }

  class TabsTrigger {
    +value string
    +className string
    +children
  }

  class useCreateCustomerForm {
    +useCreateCustomerForm()
    +form
    -defaultValues_type string = "email"
  }

  class ChatInput {
    +ChatInput(className string, otherProps)
    -connection
    -messengerData
    -messages
    -isOnline boolean
    -requireAuth boolean
    -placeholder string
    -id
    -message
    -handleInputChange(event)
    -handleSubmit(event)
    -isDisabled boolean
    -loading boolean
  }

  class formatOnlineHours {
    +formatOnlineHours(onlineHours, showTimezone boolean, timezone string, timezoneLabel string) string
    -parseTime(timeString string) hour minute
    -formatTimeZoneLabel(timezone string) string
  }

  class HeaderIntro {
    +HeaderIntro()
    -connection
    -messengerData
    -messages
    -onlineHours
    -showTimezone boolean
    -timezone string
    -links Record
  }

  class LinkFavicon {
    +LinkFavicon(url string)
    -getGoogleFavicon(url string) string
  }

  class getGoogleFavicon {
    +getGoogleFavicon(url string) string
  }

  class Avatar {
    +Avatar(size string, className string)
    +Image(src string)
    +Fallback()
  }

  class ConversationDetails {
    +ConversationDetails()
    -connection
    -messengerData
    -messagesConfig
    -botGreetMessage
    -botShowInitialMessage boolean
    -defaultLogo string
  }

  class InitialMessage {
    <<enum>>
    +WELCOME string
    +AWAY string
  }

  class EMPreviewIntro {
    +EMPreviewIntro()
    -greeting
    -hours
    -availabilityMethod string
  }

  class EMPreviewMessages {
    +EMPreviewMessages()
    -config
    -intro
    -greeting
  }

  class EMPreviewChatInput {
    +EMPreviewChatInput()
    -appearance
    -backgroundStyles
  }

  %% Relationships
  NotifyCustomerForm --> useCreateCustomerForm : uses
  NotifyCustomerForm --> Tabs : renders
  Tabs --> TabsList : contains
  TabsList --> TabsTrigger : contains

  useCreateCustomerForm ..> ChatInput : shares_type_default(email)

  HeaderIntro ..> formatOnlineHours : calls
  HeaderIntro --> LinkFavicon : renders
  LinkFavicon --> Avatar : composes
  LinkFavicon ..> getGoogleFavicon : duplicates_logic_of

  ChatInput ..> InitialMessage : reads_constants
  ConversationDetails ..> InitialMessage : reads_constants

  ConversationDetails ..> EMPreviewMessages : mirrors_behavior
  ConversationDetails ..> EMPreviewIntro : mirrors_behavior

  EMPreviewMessages --> EMPreviewChatInput : renders
Loading

File-Level Changes

Change Details Files
Replace the contact type combobox selector with tab-based selection and default to email for new customer forms.
  • Swap SelectContactType popover/combobox control with Tabs that toggle between email and phone bound to the form field
  • Set the create-customer form default type value to 'email' instead of undefined
  • Remove the SelectContactType component implementation as it is no longer used
apps/frontline-widgets/src/app/messenger/components/notify-customer-form.tsx
apps/frontline-widgets/src/app/messenger/ticket/hooks/useCreateCustomerForm.ts
Improve greeting/intro and availability messaging in both the widget and the admin preview to use configured messages and show online days.
  • Update EMPreviewIntro to use greeting.title for the heading and greeting.message for the body, dropping obsolete commented UI
  • Update formatOnlineHours to build the timezone suffix with better formatting and append a comma-separated list of available days, while adjusting the error fallback message punctuation
  • Adjust ChatInput placeholder to always use InitialMessage.WELCOME for online state instead of a configurable welcome message
frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewIntro.tsx
apps/frontline-widgets/src/lib/formatOnlineHours.ts
apps/frontline-widgets/src/app/messenger/components/chat-input.tsx
Add support for displaying configurable contact links with favicons in the messenger header intro.
  • Extend HeaderIntro to read links from messengerData and render a row of link buttons with tooltips and a small helper text line when links are present
  • Introduce a LinkFavicon component that renders a site favicon inside an Avatar, and add its usage into the header
  • Add a shared getGoogleFavicon helper for generating the favicon URL from a link (though Header currently uses the component-local implementation)
apps/frontline-widgets/src/app/messenger/components/header.tsx
apps/frontline-widgets/src/app/messenger/components/link-favicon.tsx
apps/frontline-widgets/src/lib/getGoogleFavicon.ts
Align conversation initial/welcome messages and admin preview chat bubbles with configuration, and remove debug/unused code.
  • In ConversationDetails, include messengerData.messages as messagesConfig, and render a default avatar bubble showing messagesConfig.welcome or InitialMessage.WELCOME after the bot greet message
  • In EMPreviewMessages, remove unused BrandsInline import and console.log, show intro.welcome as a right-aligned user bubble, and use config.botSetup.greetingMessage for the agent bubble text with wrapping styles
  • In EMPreviewChatInput, remove dependency on greeting state, fix placeholder to a fixed 'Send message', and simplify the send button styling to use bg-primary instead of dynamic background styles
apps/frontline-widgets/src/app/messenger/components/conversation-details.tsx
frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewMessages.tsx
frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewChatInput.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to 07665df in 13 seconds. Click for details.
  • Reviewed 446 lines of code in 11 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_iqNvUrPZnmPIG3Yv

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In formatOnlineHours, when onlineHours is missing or empty, onlineDays becomes undefined, which will render as ..., undefined.; consider guarding for this case or omitting the day list when there are no entries.
  • The getGoogleFavicon helper is now defined both inline in LinkFavicon and in lib/getGoogleFavicon.ts; it would be cleaner to reuse the shared helper instead of duplicating the implementation.
  • The large defaultLogo base64 string in conversation-details.tsx makes the component harder to read and maintain; consider moving it to a separate constants/assets module and referencing it from there.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `formatOnlineHours`, when `onlineHours` is missing or empty, `onlineDays` becomes `undefined`, which will render as `..., undefined.`; consider guarding for this case or omitting the day list when there are no entries.
- The `getGoogleFavicon` helper is now defined both inline in `LinkFavicon` and in `lib/getGoogleFavicon.ts`; it would be cleaner to reuse the shared helper instead of duplicating the implementation.
- The large `defaultLogo` base64 string in `conversation-details.tsx` makes the component harder to read and maintain; consider moving it to a separate constants/assets module and referencing it from there.

## Individual Comments

### Comment 1
<location> `apps/frontline-widgets/src/app/messenger/components/header.tsx:51-44` </location>
<code_context>
             : WelcomeMessage.AVAILABILITY_MESSAGE}{' '}
-          {messages?.thank || ''}
+        </div>
+        <div className='flex flex-col gap-1'>
+          {links && <span className="text-muted-foreground font-medium text-xs">Contact us for any questions or concerns.</span>}
+          <div className='flex gap-1'>
+            {
+              Object.entries(links || {})?.map(([key, value]) => (
+                <Tooltip key={key}>
+                  <Tooltip.Trigger>
+                    <a href={value as string} target="_blank" rel="noopener noreferrer">
+                      <LinkFavicon url={value as string} />
+                    </a>
+                  </Tooltip.Trigger>
+                  <Tooltip.Content>
+                    {key}
+                  </Tooltip.Content>
+                </Tooltip>
+              ))
+            }
+          </div>
         </div>
       </div>
</code_context>

<issue_to_address>
**suggestion:** Guard the 'Contact us' copy against an empty `links` object.

With the current `links &&` check, the helper text can appear even when `links` is an empty object, leaving a call-to-action without any contact methods. Consider checking that `links` has at least one entry (e.g. `Object.keys(links || {}).length > 0`) before rendering the helper text and icons.

Suggested implementation:

```typescript
        <div className="text-muted-foreground font-medium text-sm">
          {messages?.greetings?.message || WelcomeMessage.MESSAGE}{'. '}
          {onlineHours
            ? formatOnlineHours({ onlineHours, showTimezone, timezone })
            : WelcomeMessage.AVAILABILITY_MESSAGE}{' '}
        </div>
        <div className='flex flex-col gap-1'>
          {Object.keys(links || {}).length > 0 && (
            <span className="text-muted-foreground font-medium text-xs">
              Contact us for any questions or concerns.
            </span>
          )}

```

```typescript
        <div className='flex flex-col gap-1'>
          {Object.keys(links || {}).length > 0 && (
            <span className="text-muted-foreground font-medium text-xs">
              Contact us for any questions or concerns.
            </span>
          )}
          {Object.keys(links || {}).length > 0 && (
            <div className='flex gap-1'>
              {Object.entries(links || {}).map(([key, value]) => (
                <Tooltip key={key}>
                  <Tooltip.Trigger>
                    <a href={value as string} target="_blank" rel="noopener noreferrer">
                      <LinkFavicon url={value as string} />
                    </a>
                  </Tooltip.Trigger>
                  <Tooltip.Content>
                    {key}
                  </Tooltip.Content>
                </Tooltip>
              ))}
            </div>
          )}
        </div>

```
</issue_to_address>

### Comment 2
<location> `apps/frontline-widgets/src/app/messenger/components/chat-input.tsx:16-17` </location>
<code_context>
   const { messengerData } = connection.widgetsMessengerConnect || {};
   const { messages, isOnline, requireAuth } = messengerData || {};
   const placeholder = isOnline
-    ? messages?.welcome || InitialMessage.WELCOME
+    ? InitialMessage.WELCOME
     : messages?.away || InitialMessage.AWAY;
</code_context>

<issue_to_address>
**question (bug_risk):** Revisiting the removal of the configurable online placeholder message.

This now ignores any configured `messages?.welcome` value in the online state and always shows `InitialMessage.WELCOME`. If custom welcome text is expected to appear in the placeholder, this may be a breaking behavioral change. Please confirm product expectations, and if the decoupling is intentional, consider updating naming or documentation to reduce confusion for admins.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@@ -42,11 +43,29 @@ export const HeaderIntro = () => {
{messages?.greetings?.title || WelcomeMessage.TITLE}
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Guard the 'Contact us' copy against an empty links object.

With the current links && check, the helper text can appear even when links is an empty object, leaving a call-to-action without any contact methods. Consider checking that links has at least one entry (e.g. Object.keys(links || {}).length > 0) before rendering the helper text and icons.

Suggested implementation:

        <div className="text-muted-foreground font-medium text-sm">
          {messages?.greetings?.message || WelcomeMessage.MESSAGE}{'. '}
          {onlineHours
            ? formatOnlineHours({ onlineHours, showTimezone, timezone })
            : WelcomeMessage.AVAILABILITY_MESSAGE}{' '}
        </div>
        <div className='flex flex-col gap-1'>
          {Object.keys(links || {}).length > 0 && (
            <span className="text-muted-foreground font-medium text-xs">
              Contact us for any questions or concerns.
            </span>
          )}
        <div className='flex flex-col gap-1'>
          {Object.keys(links || {}).length > 0 && (
            <span className="text-muted-foreground font-medium text-xs">
              Contact us for any questions or concerns.
            </span>
          )}
          {Object.keys(links || {}).length > 0 && (
            <div className='flex gap-1'>
              {Object.entries(links || {}).map(([key, value]) => (
                <Tooltip key={key}>
                  <Tooltip.Trigger>
                    <a href={value as string} target="_blank" rel="noopener noreferrer">
                      <LinkFavicon url={value as string} />
                    </a>
                  </Tooltip.Trigger>
                  <Tooltip.Content>
                    {key}
                  </Tooltip.Content>
                </Tooltip>
              ))}
            </div>
          )}
        </div>

Comment on lines 16 to -17
const placeholder = isOnline
? messages?.welcome || InitialMessage.WELCOME
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): Revisiting the removal of the configurable online placeholder message.

This now ignores any configured messages?.welcome value in the online state and always shows InitialMessage.WELCOME. If custom welcome text is expected to appear in the placeholder, this may be a breaking behavioral change. Please confirm product expectations, and if the decoupling is intentional, consider updating naming or documentation to reduce confusion for admins.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Feb 2, 2026

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

This PR enhances the frontline widgets messaging UI with improved welcome messaging displays, contact link rendering with favicon support, and refactored notification forms. It simplifies placeholder logic, introduces favicon utilities, updates preview components to use dynamic greeting data, and adjusts form field defaults.

Changes

Cohort / File(s) Summary
Chat Input & Messaging
apps/frontline-widgets/src/app/messenger/components/chat-input.tsx, conversation-details.tsx, notify-customer-form.tsx
Simplified online placeholder to always use InitialMessage.WELCOME; added welcome block with avatar and greeting to conversation details; replaced SelectContactType component with inline Tabs-based control for Email/Phone selection in notification form.
Header & Contact Links
apps/frontline-widgets/src/app/messenger/components/header.tsx, link-favicon.tsx
Extended header to display contact section with LinkFavicon icons wrapped in tooltips; added new LinkFavicon component that renders avatars with Google favicons for given URLs.
Form & Hook Updates
apps/frontline-widgets/src/app/messenger/ticket/hooks/useCreateCustomerForm.ts
Changed default form field type value from undefined to 'email'.
Utility Functions
apps/frontline-widgets/src/lib/getGoogleFavicon.ts, formatOnlineHours.ts
Introduced getGoogleFavicon utility to construct Google favicon URLs; updated formatOnlineHours to append online days list to result message.
Preview Components
frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewChatInput.tsx, EMPreviewIntro.tsx, EMPreviewMessages.tsx
Removed dependency on greeting atom and static placeholders; replaced with dynamic greeting data (title, message) with fallbacks; refactored preview layouts to display greeting and welcome messages dynamically; updated message rendering to use config.botSetup.greetingMessage.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Hacktoberfest, hacktoberfest-accepted, hacktoberfest2025

Suggested reviewers

  • Enkhtuvshin0513

Poem

🐰 A bunny's welcome, shiny and bright,
With favicons dancing in the light,
Contact links aligned, a messenger's dream,
Greetings now flow like a friendly stream.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title specifically references the main change: converting SelectContactType to a tabs-based component in the NotifyCustomerForm, which is the most significant public API alteration in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch frontline-imap

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/frontline-widgets/src/app/messenger/components/header.tsx (1)

37-68: ⚠️ Potential issue | 🟠 Major

Validate link protocols and avoid rendering empty contact sections.

href is built from untrusted data and can include javascript:. Also, an empty object still shows the “Contact us” line. Filter entries by allowed protocols and gate rendering on a non-empty list.

🛡️ Suggested fix
 export const HeaderIntro = () => {
   const [connection] = useAtom(connectionAtom);
   const { messengerData } = connection.widgetsMessengerConnect || {};
-  const { messages, onlineHours, showTimezone, timezone, links } = messengerData || {};
+  const { messages, onlineHours, showTimezone, timezone, links } = messengerData || {};
+
+  function isAllowedLink(href: string): boolean {
+    return /^(https?:|mailto:|tel:)/i.test(href);
+  }
+
+  const linkEntries = Object.entries(links ?? {}).filter(
+    ([, value]) => typeof value === 'string' && isAllowedLink(value),
+  );
+  const hasLinks = linkEntries.length > 0;
 
   return (
     <div className="flex flex-col gap-4">
       <div className="gap-2 flex flex-col">
@@
-        <div className='flex flex-col gap-1'>
-          {links && <span className="text-muted-foreground font-medium text-xs">Contact us for any questions or concerns.</span>}
-          <div className='flex gap-1'>
-            {
-              Object.entries(links || {})?.map(([key, value]) => (
-                <Tooltip key={key}>
-                  <Tooltip.Trigger>
-                    <a href={value as string} target="_blank" rel="noopener noreferrer">
-                      <LinkFavicon url={value as string} />
-                    </a>
-                  </Tooltip.Trigger>
-                  <Tooltip.Content>
-                    {key}
-                  </Tooltip.Content>
-                </Tooltip>
-              ))
-            }
-          </div>
-        </div>
+        <div className='flex flex-col gap-1'>
+          {hasLinks && (
+            <span className="text-muted-foreground font-medium text-xs">
+              Contact us for any questions or concerns.
+            </span>
+          )}
+          {hasLinks && (
+            <div className='flex gap-1'>
+              {linkEntries.map(([key, value]) => (
+                <Tooltip key={key}>
+                  <Tooltip.Trigger>
+                    <a href={value} target="_blank" rel="noopener noreferrer">
+                      <LinkFavicon url={value} />
+                    </a>
+                  </Tooltip.Trigger>
+                  <Tooltip.Content>
+                    {key}
+                  </Tooltip.Content>
+                </Tooltip>
+              ))}
+            </div>
+          )}
+        </div>
frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewIntro.tsx (1)

45-52: ⚠️ Potential issue | 🟡 Minor

Use function keyword and guard against undefined schedule values; compute schedule once.

The from and to fields are optional; template strings will render "undefined - undefined" (truthy), so the fallback won't trigger. Additionally, this pure helper should use the function keyword per style guidelines, and the schedule is computed twice on line 67 when it could be computed once.

🔧 Proposed fix
-  const getSchedule = (obj: Partial<Record<Weekday | ScheduleDay, { work?: boolean | undefined; from?: string | undefined; to?: string | undefined; }>>) => {
-    const days = Object.entries(obj).filter(([_, value]) => value.work).map(([key, _]) => key);
-    const times = Object.entries(obj).filter(([_, value]) => value.work).map(([_, value]) => `${value.from} - ${value.to}`);
-    return {
-      days,
-      times
-    }
-  }
+  function getSchedule(
+    obj: Partial<Record<Weekday | ScheduleDay, { work?: boolean; from?: string; to?: string }>>,
+  ) {
+    const entries = Object.entries(obj).filter(
+      ([_, value]) => value.work && value.from && value.to,
+    );
+    const days = entries.map(([key]) => key);
+    const times = entries.map(([_, value]) => `${value.from} - ${value.to}`);
+    return { days, times };
+  }

Also compute the schedule once before use on line 67:

-            <p className='text-sm text-medium text-accent-foreground'>We're available between {getSchedule(hours?.onlineHours || {}).times[0] || '9.00 am - 5.00 pm'}, {getSchedule(hours?.onlineHours || {}).days.join(', ') || ''}</p>
+            {(() => { const schedule = getSchedule(hours?.onlineHours || {}); return <p className='text-sm text-medium text-accent-foreground'>We're available between {schedule.times[0] || '9.00 am - 5.00 pm'}, {schedule.days.join(', ') || ''}</p>; })()}
🤖 Fix all issues with AI agents
In `@apps/frontline-widgets/src/app/messenger/components/chat-input.tsx`:
- Around line 16-18: The online placeholder currently always uses
InitialMessage.WELCOME when isOnline is true, ignoring a configured welcome
override; change the placeholder logic in the chat-input.tsx where the const
placeholder is defined so that when isOnline is true it prefers
messages?.welcome (if present) and falls back to InitialMessage.WELCOME, and
when not online it still uses messages?.away || InitialMessage.AWAY; update the
expression using the isOnline check and the messages?.welcome symbol
accordingly.

In
`@apps/frontline-widgets/src/app/messenger/components/conversation-details.tsx`:
- Around line 240-246: The welcome block after BotMessage is rendered
unconditionally; gate it with the same flag (botShowInitialMessage) so the
entire welcome UI (the avatar div and the span that uses messagesConfig?.welcome
|| InitialMessage.WELCOME) only renders when botShowInitialMessage is true.
Locate the JSX in conversation-details.tsx around the BotMessage line and wrap
the subsequent welcome div (or the whole fragment containing BotMessage and the
welcome div) with the botShowInitialMessage check so the setting controls both
BotMessage and the welcome block.

In `@apps/frontline-widgets/src/app/messenger/components/link-favicon.tsx`:
- Around line 3-14: The LinkFavicon component leaves Avatar.Fallback empty,
reimplements getGoogleFavicon, and types url as a required string despite
checking it; update LinkFavicon to import and use the shared getGoogleFavicon
utility (replace the local getGoogleFavicon function), change the prop type to
accept url?: string (or string | undefined) and guard rendering accordingly, and
provide meaningful fallback content inside Avatar.Fallback (e.g., first letter
of hostname, a default icon label, or an explicit placeholder) so users see a
visual when Avatar.Image fails to load; keep references to Avatar.Image,
Avatar.Fallback and the LinkFavicon component when making these edits.

In `@apps/frontline-widgets/src/lib/getGoogleFavicon.ts`:
- Around line 1-2: Remove the duplicated local getGoogleFavicon function from
the LinkFavicon component and import the shared utility getGoogleFavicon
(exported as getGoogleFavicon) instead; update the LinkFavicon file to add an
import for getGoogleFavicon from the module that defines it, delete the local
definition inside the LinkFavicon component, and ensure all uses inside
LinkFavicon call the imported getGoogleFavicon(url) so behavior remains
identical.

In
`@frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewMessages.tsx`:
- Line 93: The paragraph uses a non-existent CSS class 'wrap-break-word' in
EMPreviewMessages.tsx; replace that class with Tailwind's correct utility
'break-words' on the <p> element (the JSX containing
config?.botSetup?.greetingMessage) so the overflow-wrap behavior is applied
properly.
🧹 Nitpick comments (7)
frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewChatInput.tsx (2)

10-14: Remove unused backgroundStyles variable.

The backgroundStyles memoized value is computed but never applied to any element in the render output. This appears to be leftover code after switching the Button to use bg-primary class.

♻️ Proposed fix
-import { useMemo } from "react"
 
 export const EMPreviewChatInput = () => {
   const appearance = useAtomValue(erxesMessengerSetupAppearanceAtom)
 
-  const backgroundStyles = useMemo(() => {
-    return {
-      backgroundColor: appearance?.primary?.DEFAULT || '#000'
-    }
-  }, [appearance])
-
   return (

18-19: Minor: Unnecessary curly braces in placeholder.

Per coding guidelines, avoid unnecessary curly braces in JSX attributes when using string literals.

♻️ Proposed fix
-      <Input placeholder={'Send message'} className="flex-1 shadow-2xs" />
-      <Button size={'icon'} className="shrink-0 size-8 bg-primary"><IconSend /></Button>
+      <Input placeholder="Send message" className="flex-1 shadow-2xs" />
+      <Button size="icon" className="shrink-0 size-8 bg-primary"><IconSend /></Button>
apps/frontline-widgets/src/lib/formatOnlineHours.ts (1)

110-112: Error fallback output differs from success path format.

The error fallback omits the trailing period and online days list. While acceptable for a fallback, consider whether the period should be added for consistency:

♻️ Optional fix for consistency
     return `We're available between ${firstHour.from} and ${firstHour.to}${
       showTimezone && timezone ? ` (${formatTimeZoneLabel(timezone)})` : ''
-    }`;
+    }.`;
frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewMessages.tsx (1)

77-80: Consider conditional rendering for empty intro.welcome.

The div renders even when intro?.welcome is undefined or empty, resulting in an empty styled container. Consider conditionally rendering only when content exists.

♻️ Proposed fix
-        <div className="flex items-start self-end text-right gap-2 flex-row-reverse max-w-2/3 text-xs text-accent-foreground">
-          {intro?.welcome}
-        </div>
+        {intro?.welcome && (
+          <div className="flex items-start self-end text-right gap-2 flex-row-reverse max-w-2/3 text-xs text-accent-foreground">
+            {intro.welcome}
+          </div>
+        )}
apps/frontline-widgets/src/app/messenger/components/conversation-details.tsx (1)

16-18: Rename the global constant to UPPER_SNAKE_CASE.

defaultLogo is a module-level constant; rename to DEFAULT_LOGO to match the naming convention.

♻️ Suggested change
-const defaultLogo = 'url(data:image/png;base64,...)'
+const DEFAULT_LOGO = 'url(data:image/png;base64,...)'
-          <div className='w-8 h-8 rounded-full bg-contain bg-center bg-primary' style={{ backgroundImage: defaultLogo }} />
+          <div className='w-8 h-8 rounded-full bg-contain bg-center bg-primary' style={{ backgroundImage: DEFAULT_LOGO }} />

As per coding guidelines: “Use camelCase naming for functions and variables, PascalCase for classes and components, and UPPER_SNAKE_CASE for global constants”.

apps/frontline-widgets/src/app/messenger/components/header.tsx (1)

6-10: Use an absolute import for LinkFavicon.

Please switch the new relative import to the project’s absolute alias scheme for consistency.

As per coding guidelines: “Always use absolute paths when importing”.

frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewIntro.tsx (1)

64-68: Compute the schedule once per render.

getSchedule(...) is called twice with the same input. Cache the result to avoid duplicate work and keep the display consistent.

♻️ Proposed refactor
   const greeting = useAtomValue(erxesMessengerSetupGreetingAtom);
   const hours = useAtomValue(erxesMessengerSetupHoursAtom);
+  const schedule = getSchedule(hours?.onlineHours ?? {});
@@
-            <p className='text-sm text-medium text-accent-foreground'>We're available between {getSchedule(hours?.onlineHours || {}).times[0] || '9.00 am - 5.00 pm'}, {getSchedule(hours?.onlineHours || {}).days.join(', ') || ''}</p>
+            <p className='text-sm text-medium text-accent-foreground'>
+              We're available between {schedule.times[0] || '9.00 am - 5.00 pm'}, {schedule.days.join(', ') || ''}
+            </p>

Comment on lines 16 to 18
const placeholder = isOnline
? messages?.welcome || InitialMessage.WELCOME
? InitialMessage.WELCOME
: messages?.away || InitialMessage.AWAY;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential regression: online placeholder ignores configured welcome text.

If messages?.welcome is set, the input placeholder will no longer reflect it, which can diverge from the configured welcome messaging used elsewhere. Consider restoring the configured welcome as the preferred online placeholder.

💡 Suggested fix
-  const placeholder = isOnline
-    ? InitialMessage.WELCOME
-    : messages?.away || InitialMessage.AWAY;
+  const placeholder = isOnline
+    ? messages?.welcome || InitialMessage.WELCOME
+    : messages?.away || InitialMessage.AWAY;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const placeholder = isOnline
? messages?.welcome || InitialMessage.WELCOME
? InitialMessage.WELCOME
: messages?.away || InitialMessage.AWAY;
const placeholder = isOnline
? messages?.welcome || InitialMessage.WELCOME
: messages?.away || InitialMessage.AWAY;
🤖 Prompt for AI Agents
In `@apps/frontline-widgets/src/app/messenger/components/chat-input.tsx` around
lines 16 - 18, The online placeholder currently always uses
InitialMessage.WELCOME when isOnline is true, ignoring a configured welcome
override; change the placeholder logic in the chat-input.tsx where the const
placeholder is defined so that when isOnline is true it prefers
messages?.welcome (if present) and falls back to InitialMessage.WELCOME, and
when not online it still uses messages?.away || InitialMessage.AWAY; update the
expression using the isOnline check and the messages?.welcome symbol
accordingly.

Comment on lines 240 to +246
{botShowInitialMessage && <BotMessage content={botGreetMessage} />}
<div className='flex justify-start gap-2'>
<div className='w-8 h-8 rounded-full bg-contain bg-center bg-primary' style={{ backgroundImage: defaultLogo }} />
<span className='h-auto font-medium flex flex-col justify-start items-start text-[13px] leading-relaxed text-foreground text-left gap-1 px-3 py-2 bg-background whitespace-break-spaces wrap-break-word break-all rounded-lg shadow-2xs'>
{messagesConfig?.welcome || InitialMessage.WELCOME}
</span>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Welcome block should respect botShowInitialMessage.

The new welcome block renders unconditionally, so it still appears even if botShowInitialMessage is false. Consider gating it to keep the setting authoritative.

💡 Suggested guard
-        <div className='flex justify-start gap-2'>
-          <div className='w-8 h-8 rounded-full bg-contain bg-center bg-primary' style={{ backgroundImage: defaultLogo }} />
-          <span className='h-auto font-medium flex flex-col justify-start items-start text-[13px] leading-relaxed text-foreground text-left gap-1 px-3 py-2 bg-background whitespace-break-spaces wrap-break-word break-all rounded-lg shadow-2xs'>
-            {messagesConfig?.welcome || InitialMessage.WELCOME}
-          </span>
-        </div>
+        {botShowInitialMessage && (
+          <div className='flex justify-start gap-2'>
+            <div className='w-8 h-8 rounded-full bg-contain bg-center bg-primary' style={{ backgroundImage: DEFAULT_LOGO }} />
+            <span className='h-auto font-medium flex flex-col justify-start items-start text-[13px] leading-relaxed text-foreground text-left gap-1 px-3 py-2 bg-background whitespace-break-spaces wrap-break-word break-all rounded-lg shadow-2xs'>
+              {messagesConfig?.welcome || InitialMessage.WELCOME}
+            </span>
+          </div>
+        )}
🤖 Prompt for AI Agents
In `@apps/frontline-widgets/src/app/messenger/components/conversation-details.tsx`
around lines 240 - 246, The welcome block after BotMessage is rendered
unconditionally; gate it with the same flag (botShowInitialMessage) so the
entire welcome UI (the avatar div and the span that uses messagesConfig?.welcome
|| InitialMessage.WELCOME) only renders when botShowInitialMessage is true.
Locate the JSX in conversation-details.tsx around the BotMessage line and wrap
the subsequent welcome div (or the whole fragment containing BotMessage and the
welcome div) with the botShowInitialMessage check so the setting controls both
BotMessage and the welcome block.

Comment on lines +3 to +14
export const LinkFavicon = ({ url }: { url: string }) => {
const getGoogleFavicon = (url: string) =>
`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url}&size=128`;

return (
<Avatar size="lg" className="rounded">
{url && (
<Avatar.Image src={getGoogleFavicon(url)} />
)}
<Avatar.Fallback></Avatar.Fallback>
</Avatar>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Provide meaningful fallback content and use the shared utility.

  1. The Avatar.Fallback is empty, providing no visual feedback if the favicon fails to load.
  2. The getGoogleFavicon function duplicates the utility in @/lib/getGoogleFavicon.ts.
  3. The url prop is typed as required string, but the conditional {url && ...} suggests empty strings may be passed.
♻️ Proposed fix
 import { Avatar } from 'erxes-ui';
+import { getGoogleFavicon } from '@/lib/getGoogleFavicon';
+import { IconLink } from '@tabler/icons-react';
 
-export const LinkFavicon = ({ url }: { url: string }) => {
-  const getGoogleFavicon = (url: string) =>
-    `https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url}&size=128`;
-
+export const LinkFavicon = ({ url }: { url?: string }) => {
   return (
     <Avatar size="lg" className="rounded">
       {url && (
         <Avatar.Image src={getGoogleFavicon(url)} />
       )}
-      <Avatar.Fallback></Avatar.Fallback>
+      <Avatar.Fallback>
+        <IconLink className="size-4" />
+      </Avatar.Fallback>
     </Avatar>
   );
 };
🤖 Prompt for AI Agents
In `@apps/frontline-widgets/src/app/messenger/components/link-favicon.tsx` around
lines 3 - 14, The LinkFavicon component leaves Avatar.Fallback empty,
reimplements getGoogleFavicon, and types url as a required string despite
checking it; update LinkFavicon to import and use the shared getGoogleFavicon
utility (replace the local getGoogleFavicon function), change the prop type to
accept url?: string (or string | undefined) and guard rendering accordingly, and
provide meaningful fallback content inside Avatar.Fallback (e.g., first letter
of hostname, a default icon label, or an explicit placeholder) so users see a
visual when Avatar.Image fails to load; keep references to Avatar.Image,
Avatar.Fallback and the LinkFavicon component when making these edits.

Comment on lines +1 to +2
export const getGoogleFavicon = (url: string) =>
`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url}&size=128`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Code duplication: This utility is duplicated in link-favicon.tsx.

The same getGoogleFavicon function is defined locally inside LinkFavicon component at apps/frontline-widgets/src/app/messenger/components/link-favicon.tsx. The component should import this utility instead of duplicating it.

♻️ Update link-favicon.tsx to use this utility

In apps/frontline-widgets/src/app/messenger/components/link-favicon.tsx:

 import { Avatar } from 'erxes-ui';
+import { getGoogleFavicon } from '@/lib/getGoogleFavicon';
 
 export const LinkFavicon = ({ url }: { url: string }) => {
-  const getGoogleFavicon = (url: string) =>
-    `https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url}&size=128`;
-
   return (
🤖 Prompt for AI Agents
In `@apps/frontline-widgets/src/lib/getGoogleFavicon.ts` around lines 1 - 2,
Remove the duplicated local getGoogleFavicon function from the LinkFavicon
component and import the shared utility getGoogleFavicon (exported as
getGoogleFavicon) instead; update the LinkFavicon file to add an import for
getGoogleFavicon from the module that defines it, delete the local definition
inside the LinkFavicon component, and ensure all uses inside LinkFavicon call
the imported getGoogleFavicon(url) so behavior remains identical.

className="h-auto font-normal flex flex-col justify-start items-start text-left gap-1 p-3"
>
<p>{intro?.welcome || "Hi, any questions?"}</p>
<p className='wrap-break-word'>{config?.botSetup?.greetingMessage || "Hi, any questions?"}</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for wrap-break-word definition in CSS/SCSS and tailwind config files
echo "=== Searching for wrap-break-word definition in CSS/SCSS files ==="
rg -n "wrap-break-word" --type css --type scss

echo -e "\n=== Searching in tailwind.config files ==="
fd tailwind.config -x cat -n {}

echo -e "\n=== Searching for wrap-break-word in all files ==="
rg -n "wrap-break-word" -t js -t ts -t json 2>/dev/null | head -20

Repository: erxes/erxes

Length of output: 9668


🏁 Script executed:

#!/bin/bash
# Search for CSS files in the project
echo "=== Searching for CSS/global style files ==="
find . -name "*.css" -type f | grep -E "(global|styles|tailwind)" | head -20

echo -e "\n=== Checking for any CSS content with wrap-break-word definition ==="
rg "\.wrap-break-word|wrap-break-word\s*{" --type css -A 2

echo -e "\n=== Checking if it might be defined elsewhere ==="
rg "wrap-break-word" --type css --type js --type ts | grep -E "(:|-\>|=|\{)" | head -10

Repository: erxes/erxes

Length of output: 2915


🏁 Script executed:

#!/bin/bash
# Check the actual CSS files to see if wrap-break-word is defined there
echo "=== Content of styles.css files ==="
cat ./apps/frontline-widgets/src/styles.css | head -50

echo -e "\n=== Content of core-ui styles.css ==="
cat ./frontend/core-ui/src/styles.css | head -50

Repository: erxes/erxes

Length of output: 2981


Replace wrap-break-word with valid Tailwind CSS class break-words.

wrap-break-word is not defined anywhere in the codebase—not in Tailwind config, CSS files, or any other styling mechanism. This class name will not apply any styles. Use break-words instead, which is Tailwind's standard utility for overflow-wrap: break-word.

🤖 Prompt for AI Agents
In
`@frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewMessages.tsx`
at line 93, The paragraph uses a non-existent CSS class 'wrap-break-word' in
EMPreviewMessages.tsx; replace that class with Tailwind's correct utility
'break-words' on the <p> element (the JSX containing
config?.botSetup?.greetingMessage) so the overflow-wrap behavior is applied
properly.

@Bilguun0410 Bilguun0410 merged commit 1f3be89 into main Feb 2, 2026
13 of 20 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 26, 2026
@coderabbitai coderabbitai bot mentioned this pull request Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants