Skip to content

Commit cddbd0b

Browse files
committed
Make event location links clickable
1 parent 19541f7 commit cddbd0b

1 file changed

Lines changed: 68 additions & 2 deletions

File tree

client/src/features/modal/EventModal.jsx

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,60 @@ import dataService from "../../services/dataService";
99
import { useAuthContext } from "../../contexts/AuthContext";
1010
import { useEventsContext } from "../../contexts/EventsContext";
1111

12+
// URLRegex matches strings that look like URLs, including:
13+
// 1. URLs starting with "http://" or "https://" (e.g., "https://example.com/path")
14+
// 2. URLs starting with "www." (e.g., "www.example.com")
15+
// 3. Bare domain names without protocol or www (e.g., "example.com")
16+
17+
const URLRegex =
18+
/(https?:\/\/[^\s]+|www\.[^\s]+|[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(?:[^\s]*))/g;
19+
20+
const LinkText = ({ children }) => {
21+
if (typeof children !== "string")
22+
throw new Error("LinkText can only accept a string as children");
23+
24+
const separated = children.split(URLRegex);
25+
26+
return separated.map((res, index) => {
27+
if (URLRegex.test(res)) {
28+
let href;
29+
30+
// Determine href and always enforce HTTPS
31+
if (res.startsWith("https://")) {
32+
href = res;
33+
} else if (res.startsWith("http://")) {
34+
href = res.replace(/^http:\/\//, "https://");
35+
} else if (res.startsWith("www.")) {
36+
href = `https://${res}`;
37+
} else {
38+
href = `https://${res}`;
39+
}
40+
41+
// Convert Unicode domain to punycode for display
42+
let displayURL;
43+
try {
44+
const urlObj = new URL(href);
45+
displayURL = urlObj.toString(); // toString() converts hostname to punycode
46+
} catch {
47+
displayURL = href; // fallback if URL parsing fails
48+
}
49+
50+
return (
51+
<a
52+
key={`${res}-${index}`}
53+
href={href}
54+
target="_blank"
55+
rel="noreferrer"
56+
className="text-blue-600 underline hover:text-blue-800"
57+
>
58+
{displayURL}
59+
</a>
60+
);
61+
}
62+
return res;
63+
});
64+
};
65+
1266
const EventModal = () => {
1367
const { setEvents } = useEventsContext();
1468
const modal = useModalContext();
@@ -94,7 +148,9 @@ const EventModal = () => {
94148
</div>
95149
<h3 className="mb-0">Description:</h3>{" "}
96150
<div className="description break-words w-auto min-h-20 mb-2 p-2 border-solid border-black border-2 font-semibold rounded-xl bg-neutral-200/50">
97-
<p>{modal.activeEvent.description}</p>
151+
<p>
152+
<LinkText>{modal.activeEvent.description}</LinkText>
153+
</p>
98154
</div>
99155
<div>
100156
{/* <section className="flex m-3 gap-1 font-semibold">
@@ -106,7 +162,17 @@ const EventModal = () => {
106162
</section> */}
107163
<section className="flex m-3 gap-1 font-semibold">
108164
<IoLocationOutline className="mt-1" />{" "}
109-
<span>Location: {modal.activeEvent.location}</span>
165+
<span>
166+
Location:{" "}
167+
<a
168+
href={modal.activeEvent.location}
169+
target="_blank"
170+
rel="noopener noreferrer"
171+
className="text-blue-600 underline hover:text-blue-800"
172+
>
173+
{modal.activeEvent.location}
174+
</a>
175+
</span>
110176
</section>
111177
{user ? (
112178
<>

0 commit comments

Comments
 (0)