fix: fix pointer events on phone call dialog

This commit is contained in:
Abhishek Kumar 2025-11-29 20:45:55 +07:00
parent beb0091c01
commit cf4870fe38
4 changed files with 245 additions and 77 deletions

View file

@ -38,9 +38,146 @@ services:
networks:
- app-network
# langfuse-worker:
# image: docker.io/langfuse/langfuse-worker:3.131
# restart: always
# depends_on: &langfuse-depends-on
# postgres:
# condition: service_healthy
# minio:
# condition: service_healthy
# redis:
# condition: service_healthy
# clickhouse:
# condition: service_healthy
# ports:
# - 127.0.0.1:3030:3030
# environment: &langfuse-worker-env
# NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000}
# DATABASE_URL: ${DATABASE_URL_LF:-postgresql://postgres:postgres@postgres:5432/langfuse}
# SALT: ${SALT:-blahbluh}
# ENCRYPTION_KEY: ${ENCRYPTION_KEY:-208ac38e6da9dd09a8640505712c6f454bd20c53022104f3f56aee1596fb2c8d}
# TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
# LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-true}
# CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL:-clickhouse://clickhouse:9000}
# CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123}
# CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
# CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
# CLICKHOUSE_CLUSTER_ENABLED: ${CLICKHOUSE_CLUSTER_ENABLED:-false}
# LANGFUSE_USE_AZURE_BLOB: ${LANGFUSE_USE_AZURE_BLOB:-false}
# LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
# LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto}
# LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
# LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
# LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000}
# LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true}
# LANGFUSE_S3_EVENT_UPLOAD_PREFIX: ${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/}
# LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: ${LANGFUSE_S3_MEDIA_UPLOAD_BUCKET:-langfuse}
# LANGFUSE_S3_MEDIA_UPLOAD_REGION: ${LANGFUSE_S3_MEDIA_UPLOAD_REGION:-auto}
# LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID:-minio}
# LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY:-miniosecret}
# LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://localhost:9090}
# LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE:-true}
# LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: ${LANGFUSE_S3_MEDIA_UPLOAD_PREFIX:-media/}
# LANGFUSE_S3_BATCH_EXPORT_ENABLED: ${LANGFUSE_S3_BATCH_EXPORT_ENABLED:-false}
# LANGFUSE_S3_BATCH_EXPORT_BUCKET: ${LANGFUSE_S3_BATCH_EXPORT_BUCKET:-langfuse}
# LANGFUSE_S3_BATCH_EXPORT_PREFIX: ${LANGFUSE_S3_BATCH_EXPORT_PREFIX:-exports/}
# LANGFUSE_S3_BATCH_EXPORT_REGION: ${LANGFUSE_S3_BATCH_EXPORT_REGION:-auto}
# LANGFUSE_S3_BATCH_EXPORT_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_ENDPOINT:-http://minio:9000}
# LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT:-http://localhost:9090}
# LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID: ${LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID:-minio}
# LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY: ${LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY:-miniosecret}
# LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE: ${LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE:-true}
# LANGFUSE_INGESTION_QUEUE_DELAY_MS: ${LANGFUSE_INGESTION_QUEUE_DELAY_MS:-}
# LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS: ${LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS:-}
# REDIS_HOST: ${REDIS_HOST:-redis}
# REDIS_PORT: ${REDIS_PORT:-6379}
# REDIS_AUTH: ${REDIS_AUTH:-redissecret}
# REDIS_TLS_ENABLED: ${REDIS_TLS_ENABLED:-false}
# REDIS_TLS_CA: ${REDIS_TLS_CA:-/certs/ca.crt}
# REDIS_TLS_CERT: ${REDIS_TLS_CERT:-/certs/redis.crt}
# REDIS_TLS_KEY: ${REDIS_TLS_KEY:-/certs/redis.key}
# EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-}
# SMTP_CONNECTION_URL: ${SMTP_CONNECTION_URL:-}
# networks:
# - app-network
# langfuse-web:
# image: docker.io/langfuse/langfuse:3.131
# restart: always
# depends_on: *langfuse-depends-on
# ports:
# - 3000:3000
# environment:
# <<: *langfuse-worker-env
# NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-mysecret}
# LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
# LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-}
# LANGFUSE_INIT_PROJECT_ID: ${LANGFUSE_INIT_PROJECT_ID:-}
# LANGFUSE_INIT_PROJECT_NAME: ${LANGFUSE_INIT_PROJECT_NAME:-}
# LANGFUSE_INIT_PROJECT_PUBLIC_KEY: ${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
# LANGFUSE_INIT_PROJECT_SECRET_KEY: ${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
# LANGFUSE_INIT_USER_EMAIL: ${LANGFUSE_INIT_USER_EMAIL:-}
# LANGFUSE_INIT_USER_NAME: ${LANGFUSE_INIT_USER_NAME:-}
# LANGFUSE_INIT_USER_PASSWORD: ${LANGFUSE_INIT_USER_PASSWORD:-}
# networks:
# - app-network
# clickhouse:
# image: docker.io/clickhouse/clickhouse-server
# restart: always
# user: "101:101"
# environment:
# CLICKHOUSE_DB: default
# CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
# CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse}
# volumes:
# - langfuse_clickhouse_data:/var/lib/clickhouse
# - langfuse_clickhouse_logs:/var/log/clickhouse-server
# ports:
# - 127.0.0.1:8123:8123
# - 127.0.0.1:9000:9000
# healthcheck:
# test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
# interval: 5s
# timeout: 5s
# retries: 10
# start_period: 1s
# networks:
# - app-network
# minio:
# image: docker.io/minio/minio
# restart: always
# entrypoint: sh
# # create the 'langfuse' bucket before starting the service
# command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
# environment:
# MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
# MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniosecret}
# ports:
# - 9090:9000
# - 127.0.0.1:9091:9001
# volumes:
# - langfuse_minio_data:/data
# healthcheck:
# test: ["CMD", "mc", "ready", "local"]
# interval: 1s
# timeout: 5s
# retries: 5
# start_period: 1s
# networks:
# - app-network
volumes:
postgres_data:
redis_data:
# langfuse_clickhouse_data:
# driver: local
# langfuse_clickhouse_logs:
# driver: local
# langfuse_minio_data:
# driver: local
networks:
app-network:

View file

@ -2,6 +2,7 @@
import 'react-international-phone/style.css';
import { Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { PhoneInput } from 'react-international-phone';
@ -44,14 +45,15 @@ export const PhoneCallDialog = ({
const [callError, setCallError] = useState<string | null>(null);
const [callSuccessMsg, setCallSuccessMsg] = useState<string | null>(null);
const [phoneChanged, setPhoneChanged] = useState(false);
const [configureDialogOpen, setConfigureDialogOpen] = useState(false);
const [needsConfiguration, setNeedsConfiguration] = useState(false);
const [checkingConfig, setCheckingConfig] = useState(false);
const [needsConfiguration, setNeedsConfiguration] = useState<boolean | null>(null);
// Check telephony configuration when dialog opens
useEffect(() => {
const checkConfig = async () => {
if (!open) return;
setCheckingConfig(true);
try {
const accessToken = await getAccessToken();
const configResponse = await getTelephonyConfigurationApiV1OrganizationsTelephonyConfigGet({
@ -60,18 +62,19 @@ export const PhoneCallDialog = ({
if (configResponse.error || (!configResponse.data?.twilio && !configResponse.data?.vonage && !configResponse.data?.vobiz)) {
setNeedsConfiguration(true);
setConfigureDialogOpen(true);
onOpenChange(false);
} else {
setNeedsConfiguration(false);
}
} catch (err) {
console.error("Failed to check telephony config:", err);
setNeedsConfiguration(false);
} finally {
setCheckingConfig(false);
}
};
checkConfig();
}, [open, getAccessToken, onOpenChange]);
}, [open, getAccessToken]);
// Reset state when dialog closes
useEffect(() => {
@ -79,6 +82,7 @@ export const PhoneCallDialog = ({
setCallError(null);
setCallSuccessMsg(null);
setCallLoading(false);
setNeedsConfiguration(null);
}
}, [open]);
@ -101,7 +105,7 @@ export const PhoneCallDialog = ({
};
const handleConfigureContinue = () => {
setConfigureDialogOpen(false);
onOpenChange(false);
router.push('/telephony-configurations');
};
@ -146,75 +150,96 @@ export const PhoneCallDialog = ({
}
};
return (
// Render loading state
const renderLoading = () => (
<>
{/* Phone Call Dialog */}
<Dialog open={open && !needsConfiguration} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Phone Call</DialogTitle>
<DialogDescription>
Enter the phone number to call. The number will be saved automatically.
</DialogDescription>
</DialogHeader>
<PhoneInput
defaultCountry="in"
value={phoneNumber}
onChange={handlePhoneInputChange}
/>
<DialogFooter className="flex-col sm:flex-row gap-2">
<Button
variant="outline"
onClick={() => {
onOpenChange(false);
router.push('/telephony-configurations');
}}
>
Configure Telephony
</Button>
<div className="flex gap-2 flex-1 justify-end">
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
{!callSuccessMsg ? (
<Button
onClick={handleStartCall}
disabled={callLoading || !phoneNumber}
>
{callLoading ? "Calling..." : "Start Call"}
</Button>
) : (
<Button onClick={() => onOpenChange(false)}>
Close
</Button>
)}
</div>
</DialogFooter>
{callError && <div className="text-red-500 text-sm mt-2">{callError}</div>}
{callSuccessMsg && <div className="text-green-600 text-sm mt-2">{callSuccessMsg}</div>}
</DialogContent>
</Dialog>
{/* Configure Telephony Dialog */}
<Dialog open={configureDialogOpen} onOpenChange={setConfigureDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Configure Telephony</DialogTitle>
<DialogDescription>
You need to configure your telephony settings before making phone calls.
You will be redirected to the telephony configuration page.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost" onClick={() => setConfigureDialogOpen(false)}>
Do it Later
</Button>
<Button onClick={handleConfigureContinue}>
Continue
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<DialogHeader>
<DialogTitle>Phone Call</DialogTitle>
</DialogHeader>
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
</>
);
// Render configuration needed state
const renderConfigurationNeeded = () => (
<>
<DialogHeader>
<DialogTitle>Configure Telephony</DialogTitle>
<DialogDescription>
You need to configure your telephony settings before making phone calls.
You will be redirected to the telephony configuration page.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost" onClick={() => onOpenChange(false)}>
Do it Later
</Button>
<Button onClick={handleConfigureContinue}>
Continue
</Button>
</DialogFooter>
</>
);
// Render phone call form
const renderPhoneCallForm = () => (
<>
<DialogHeader>
<DialogTitle>Phone Call</DialogTitle>
<DialogDescription>
Enter the phone number to call. The number will be saved automatically.
</DialogDescription>
</DialogHeader>
<PhoneInput
defaultCountry="in"
value={phoneNumber}
onChange={handlePhoneInputChange}
/>
<DialogFooter className="flex-col sm:flex-row gap-2">
<Button
variant="outline"
onClick={() => {
onOpenChange(false);
router.push('/telephony-configurations');
}}
>
Configure Telephony
</Button>
<div className="flex gap-2 flex-1 justify-end">
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
{!callSuccessMsg ? (
<Button
onClick={handleStartCall}
disabled={callLoading || !phoneNumber}
>
{callLoading ? "Calling..." : "Start Call"}
</Button>
) : (
<Button onClick={() => onOpenChange(false)}>
Close
</Button>
)}
</div>
</DialogFooter>
{callError && <div className="text-red-500 text-sm mt-2">{callError}</div>}
{callSuccessMsg && <div className="text-green-600 text-sm mt-2">{callSuccessMsg}</div>}
</>
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
{checkingConfig || needsConfiguration === null
? renderLoading()
: needsConfiguration
? renderConfigurationNeeded()
: renderPhoneCallForm()
}
</DialogContent>
</Dialog>
);
};

View file

@ -125,7 +125,12 @@ export const WorkflowEditorHeader = ({
Web Call
</DropdownMenuItem>
<DropdownMenuItem
onClick={onPhoneCallClick}
onClick={() => {
// Delay opening dialog to next event cycle to allow DropdownMenu
// to clean up first, preventing pointer-events: none stuck on body
// See: https://github.com/radix-ui/primitives/issues/1241
setTimeout(onPhoneCallClick, 0);
}}
className="text-white hover:bg-[#2a2a2a] cursor-pointer"
>
<Phone className="w-4 h-4 mr-2" />

View file

@ -22,6 +22,7 @@ import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import React from "react";
import ThemeToggle from "@/components/ThemeSwitcher";
import { Button } from "@/components/ui/button";
import {
Sidebar,
@ -405,7 +406,7 @@ export function AppSidebar() {
)}
{/* Theme Toggle - at the very bottom */}
{/* <div className={cn(
<div className={cn(
"mt-2 pt-2 border-t",
state === "collapsed" ? "flex justify-center" : ""
)}>
@ -431,7 +432,7 @@ export function AppSidebar() {
className="hover:bg-accent hover:text-accent-foreground"
/>
)}
</div> */}
</div>
</div>
</SidebarFooter>
<SidebarRail />