room url copy button for ics

This commit is contained in:
Igor Loskutov
2025-09-16 16:52:12 -04:00
parent 88fe0e051d
commit f4d59a4af8
5 changed files with 101 additions and 14 deletions

View File

@@ -11,15 +11,18 @@ import {
createListCollection, createListCollection,
Spinner, Spinner,
Box, Box,
IconButton,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { useState, useEffect } from "react"; import { useState, useEffect, useRef } from "react";
import { LuRefreshCw } from "react-icons/lu"; import { LuRefreshCw, LuCopy, LuCheck } from "react-icons/lu";
import { FaCheckCircle, FaExclamationCircle } from "react-icons/fa"; import { FaCheckCircle, FaExclamationCircle } from "react-icons/fa";
import { useRoomIcsSync, useRoomIcsStatus } from "../../../lib/apiHooks"; import { useRoomIcsSync, useRoomIcsStatus } from "../../../lib/apiHooks";
import { toaster } from "../../../components/ui/toaster";
import { roomAbsoluteUrl } from "../../../lib/routesClient";
import { assertExists } from "../../../lib/utils";
interface ICSSettingsProps { interface ICSSettingsProps {
roomId?: string; roomName: string;
roomName?: string;
icsUrl?: string; icsUrl?: string;
icsEnabled?: boolean; icsEnabled?: boolean;
icsFetchInterval?: number; icsFetchInterval?: number;
@@ -45,7 +48,6 @@ const fetchIntervalOptions = [
]; ];
export default function ICSSettings({ export default function ICSSettings({
roomId,
roomName, roomName,
icsUrl = "", icsUrl = "",
icsEnabled = false, icsEnabled = false,
@@ -66,6 +68,8 @@ export default function ICSSettings({
eventsCreated: number; eventsCreated: number;
eventsUpdated: number; eventsUpdated: number;
} | null>(null); } | null>(null);
const [justCopied, setJustCopied] = useState(false);
const roomUrlInputRef = useRef<HTMLInputElement>(null);
const syncMutation = useRoomIcsSync(); const syncMutation = useRoomIcsSync();
@@ -73,6 +77,51 @@ export default function ICSSettings({
items: fetchIntervalOptions, items: fetchIntervalOptions,
}); });
const handleCopyRoomUrl = async () => {
try {
await navigator.clipboard.writeText(
roomAbsoluteUrl(assertExists(roomName)),
);
setJustCopied(true);
toaster
.create({
placement: "top",
duration: 3000,
render: ({ dismiss }) => (
<Box
bg="green.500"
color="white"
px={4}
py={3}
borderRadius="md"
display="flex"
alignItems="center"
gap={2}
boxShadow="lg"
>
<LuCheck />
<Text>Room URL copied to clipboard!</Text>
</Box>
),
})
.then(() => {});
setTimeout(() => {
setJustCopied(false);
}, 2000);
} catch (err) {
console.error("Failed to copy room url:", err);
}
};
const handleRoomUrlClick = () => {
if (roomUrlInputRef.current) {
roomUrlInputRef.current.select();
handleCopyRoomUrl();
}
};
// Clear sync results when dialog closes // Clear sync results when dialog closes
useEffect(() => { useEffect(() => {
if (!isEditing) { if (!isEditing) {
@@ -136,6 +185,39 @@ export default function ICSSettings({
{icsEnabled && ( {icsEnabled && (
<> <>
<Field.Root>
<Field.Label>Room URL</Field.Label>
<Field.HelperText>
To enable Reflector to recognize your calendar events as meetings,
add this URL as the location in your calendar events
</Field.HelperText>
<HStack gap={0} position="relative" width="100%">
<Input
ref={roomUrlInputRef}
value={roomAbsoluteUrl(roomName)}
readOnly
onClick={handleRoomUrlClick}
cursor="pointer"
bg="gray.100"
_hover={{ bg: "gray.200" }}
_focus={{ bg: "gray.200" }}
pr="48px"
width="100%"
/>
<IconButton
aria-label="Copy room URL"
onClick={handleCopyRoomUrl}
variant="ghost"
position="absolute"
right="4px"
size="sm"
zIndex={1}
>
{justCopied ? <LuCheck /> : <LuCopy />}
</IconButton>
</HStack>
</Field.Root>
<Field.Root> <Field.Root>
<Field.Label>ICS Calendar URL</Field.Label> <Field.Label>ICS Calendar URL</Field.Label>
<Input <Input

View File

@@ -33,6 +33,7 @@ import { RoomList } from "./_components/RoomList";
import { PaginationPage } from "../browse/_components/Pagination"; import { PaginationPage } from "../browse/_components/Pagination";
import { assertExists } from "../../lib/utils"; import { assertExists } from "../../lib/utils";
import ICSSettings from "./_components/ICSSettings"; import ICSSettings from "./_components/ICSSettings";
import { roomAbsoluteUrl } from "../../lib/routesClient";
type Room = components["schemas"]["Room"]; type Room = components["schemas"]["Room"];
@@ -187,13 +188,12 @@ export default function RoomsList() {
}); });
const handleCopyUrl = (roomName: string) => { const handleCopyUrl = (roomName: string) => {
const roomUrl = `${window.location.origin}/${roomName}`; navigator.clipboard.writeText(roomAbsoluteUrl(roomName)).then(() => {
navigator.clipboard.writeText(roomUrl); setLinkCopied(roomName);
setLinkCopied(roomName); setTimeout(() => {
setLinkCopied("");
setTimeout(() => { }, 2000);
setLinkCopied(""); });
}, 2000);
}; };
const handleCloseDialog = () => { const handleCloseDialog = () => {
@@ -620,7 +620,6 @@ export default function RoomsList() {
<Tabs.Content value="calendar" pt={6}> <Tabs.Content value="calendar" pt={6}>
<ICSSettings <ICSSettings
roomId={editRoomId ?? undefined}
roomName={room.name} roomName={room.name}
icsUrl={room.icsUrl} icsUrl={room.icsUrl}
icsEnabled={room.icsEnabled} icsEnabled={room.icsEnabled}

View File

@@ -4,6 +4,7 @@ import { Flex, Link, Button, Text, HStack } from "@chakra-ui/react";
import NextLink from "next/link"; import NextLink from "next/link";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { roomUrl } from "../lib/routes";
interface MeetingMinimalHeaderProps { interface MeetingMinimalHeaderProps {
roomName: string; roomName: string;
@@ -30,7 +31,7 @@ export default function MeetingMinimalHeader({
if (onLeave) { if (onLeave) {
onLeave(); onLeave();
} else { } else {
router.push(`/${roomName}`); router.push(roomUrl(roomName));
} }
}; };

1
www/app/lib/routes.ts Normal file
View File

@@ -0,0 +1 @@
export const roomUrl = (roomName: string) => `/${roomName}`;

View File

@@ -0,0 +1,4 @@
import { roomUrl } from "./routes";
export const roomAbsoluteUrl = (roomName: string) =>
`${window.location.origin}${roomUrl(roomName)}`;