diff --git a/artifacts/tx-os/src/locales/ar.json b/artifacts/tx-os/src/locales/ar.json index 34907262..4c7c4917 100644 --- a/artifacts/tx-os/src/locales/ar.json +++ b/artifacts/tx-os/src/locales/ar.json @@ -1721,7 +1721,11 @@ "startsAt": "من", "endsAt": "إلى", "viewList": "قائمة", - "viewCalendar": "تقويم" + "viewCalendar": "تقويم", + "shareLinkTitle": "رابط الحجز العام (للمشاركة مع الضيوف)", + "copyLink": "نسخ الرابط", + "linkCopied": "تم نسخ الرابط", + "linkCopyFailed": "تعذّر نسخ الرابط" }, "external": { "new": "لقاء جديد", diff --git a/artifacts/tx-os/src/locales/en.json b/artifacts/tx-os/src/locales/en.json index fa7af018..ec6df6fa 100644 --- a/artifacts/tx-os/src/locales/en.json +++ b/artifacts/tx-os/src/locales/en.json @@ -1594,7 +1594,11 @@ "startsAt": "From", "endsAt": "To", "viewList": "List", - "viewCalendar": "Calendar" + "viewCalendar": "Calendar", + "shareLinkTitle": "Public booking link (share with guests)", + "copyLink": "Copy link", + "linkCopied": "Link copied", + "linkCopyFailed": "Could not copy the link" }, "external": { "new": "New meeting", diff --git a/artifacts/tx-os/src/pages/protocol.tsx b/artifacts/tx-os/src/pages/protocol.tsx index ef1bb440..29357cc6 100644 --- a/artifacts/tx-os/src/pages/protocol.tsx +++ b/artifacts/tx-os/src/pages/protocol.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useRoute } from "wouter"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; @@ -20,6 +20,8 @@ import { PackageCheck, CalendarDays, List as ListIcon, + Link2, + Copy, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -361,6 +363,43 @@ export default function ProtocolPage() { id: number; } | null>(null); const [bookingsView, setBookingsView] = useState<"list" | "calendar">("list"); + const publicBookingUrl = `${window.location.origin}${import.meta.env.BASE_URL.replace(/\/$/, "")}/protocol/request`; + const [linkCopied, setLinkCopied] = useState(false); + const linkCopiedTimer = useRef(null); + const copyBookingLink = async () => { + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(publicBookingUrl); + } else { + const ta = document.createElement("textarea"); + ta.value = publicBookingUrl; + ta.style.position = "fixed"; + ta.style.opacity = "0"; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + let ok = false; + try { + ok = document.execCommand("copy"); + } finally { + document.body.removeChild(ta); + } + if (!ok) throw new Error("execCommand copy failed"); + } + setLinkCopied(true); + toast({ title: t("protocol.bookings.linkCopied") }); + if (linkCopiedTimer.current) window.clearTimeout(linkCopiedTimer.current); + linkCopiedTimer.current = window.setTimeout( + () => setLinkCopied(false), + 2000, + ); + } catch { + toast({ + title: t("protocol.bookings.linkCopyFailed"), + variant: "destructive", + }); + } + }; const [deleteTarget, setDeleteTarget] = useState< | { kind: "room" | "booking" | "external" | "gift" | "issue"; id: number } | null @@ -667,6 +706,40 @@ export default function ProtocolPage() { )} +
+
+
+ +
+

+ {t("protocol.bookings.shareLinkTitle")} +

+

+ {publicBookingUrl} +

+
+
+ +
+
{bookings.data?.length === 0 && ( )}