From 1a15fd78473a8869343a5079bdfd20593521510f Mon Sep 17 00:00:00 2001 From: wilfredeveloper Date: Tue, 16 Jun 2026 15:50:20 +0300 Subject: [PATCH] fix(chat): render URLs in messages as clickable links Backend recommendation messages embed raw application URLs, but the frontend rendered all message text as plain text, so job links were not clickable. Auto-linkify http(s)/www URLs in string messages in ChatBubble, opening in a new tab. Add a Storybook story. --- .../chatBubble/ChatBubble.stories.tsx | 14 +++++++++++ .../components/chatBubble/ChatBubble.tsx | 23 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.stories.tsx b/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.stories.tsx index 65feccdb0..ba8424dc0 100644 --- a/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.stories.tsx +++ b/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.stories.tsx @@ -35,3 +35,17 @@ export const ShownWithFooter: Story = { children: , }, }; + +export const WithClickableLinks: Story = { + args: { + message: + "Here are some opportunities that match you:\n\n" + + "1. Junior Baker\n" + + " Employer: Sunrise Bakery\n" + + " Apply here: https://jobs.example.com/junior-baker\n\n" + + "2. Pastry Assistant\n" + + " Employer: City Cafe\n" + + " Apply here: www.citycafe.co.ke/careers/pastry-assistant", + sender: ConversationMessageSender.COMPASS, + }, +}; diff --git a/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.tsx b/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.tsx index 2f11289d0..157069aec 100644 --- a/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.tsx +++ b/frontend-new/src/chat/chatMessage/components/chatBubble/ChatBubble.tsx @@ -1,6 +1,25 @@ import React from "react"; import { ConversationMessageSender } from "src/chat/ChatService/ChatService.types"; -import { Box, Typography, styled } from "@mui/material"; +import { Box, Link, Typography, styled } from "@mui/material"; + +// Matches http(s) URLs and www. URLs, stopping at whitespace or common trailing punctuation. +const URL_REGEX = /((?:https?:\/\/|www\.)[^\s<]+[^\s<.,;:!?)\]}'"])/gi; + +// Splits a plain-text message into text and clickable link segments, preserving the original text. +const renderTextWithLinks = (text: string): React.ReactNode => { + const parts = text.split(URL_REGEX); + return parts.map((part, index) => { + if (index % 2 === 1) { + const href = part.startsWith("www.") ? `https://${part}` : part; + return ( + + {part} + + ); + } + return part; + }); +}; export interface ChatBubbleProps { message: string | React.ReactNode; @@ -39,7 +58,7 @@ const ChatBubble: React.FC = ({ message, sender, children }) => return ( - {message} + {typeof message === "string" ? renderTextWithLinks(message) : message} {children}