import CopyInput from '@/components/forms/copy-input'; import FullPageLoadingState from '@/components/full-page-loading-state'; import Syntax from '@/components/syntax'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Widget, WidgetBody, WidgetHead } from '@/components/widget'; import { useAppContext } from '@/hooks/use-app-context'; import { useAppParams } from '@/hooks/use-app-params'; import { useTRPC } from '@/integrations/trpc/react'; import type { IRealtimeWidgetOptions, IWidgetType, } from '@openpanel/validation'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; import { ExternalLinkIcon } from 'lucide-react'; import { useEffect, useState } from 'react'; import { toast } from 'sonner'; export const Route = createFileRoute( '/_app/$organizationId/$projectId/settings/_tabs/widgets', )({ component: Component, }); function Component() { const { projectId, organizationId } = useAppParams(); const { dashboardUrl } = useAppContext(); const trpc = useTRPC(); const queryClient = useQueryClient(); // Fetch both widget types const realtimeWidgetQuery = useQuery( trpc.widget.get.queryOptions({ projectId, type: 'realtime' }), ); const counterWidgetQuery = useQuery( trpc.widget.get.queryOptions({ projectId, type: 'counter' }), ); // Toggle mutation const toggleMutation = useMutation( trpc.widget.toggle.mutationOptions({ onSuccess: (_, variables) => { queryClient.invalidateQueries( trpc.widget.get.queryFilter({ projectId, type: variables.type }), ); toast.success(variables.enabled ? 'Widget enabled' : 'Widget disabled'); }, onError: (error) => { toast.error(error.message || 'Failed to update widget'); }, }), ); // Update options mutation const updateOptionsMutation = useMutation( trpc.widget.updateOptions.mutationOptions({ onSuccess: () => { queryClient.invalidateQueries( trpc.widget.get.queryFilter({ projectId, type: 'realtime' }), ); toast.success('Widget options updated'); }, onError: (error) => { toast.error(error.message || 'Failed to update options'); }, }), ); const handleToggle = (type: IWidgetType, enabled: boolean) => { toggleMutation.mutate({ projectId, organizationId, type, enabled, }); }; if (realtimeWidgetQuery.isLoading || counterWidgetQuery.isLoading) { return ; } const realtimeWidget = realtimeWidgetQuery.data; const counterWidget = counterWidgetQuery.data; return (
handleToggle('realtime', enabled)} onUpdateOptions={(options) => updateOptionsMutation.mutate({ projectId, organizationId, options, }) } /> handleToggle('counter', enabled)} />
); } interface RealtimeWidgetSectionProps { widget: { id: string; public: boolean; options: IRealtimeWidgetOptions; } | null; dashboardUrl: string; isToggling: boolean; isUpdatingOptions: boolean; onToggle: (enabled: boolean) => void; onUpdateOptions: (options: IRealtimeWidgetOptions) => void; } function RealtimeWidgetSection({ widget, dashboardUrl, isToggling, isUpdatingOptions, onToggle, onUpdateOptions, }: RealtimeWidgetSectionProps) { const isEnabled = widget?.public ?? false; const widgetUrl = isEnabled && widget?.id ? `${dashboardUrl}/widget/realtime?shareId=${widget.id}` : null; const embedCode = widgetUrl ? `` : null; // Default options const defaultOptions: IRealtimeWidgetOptions = { type: 'realtime', referrers: true, countries: true, paths: false, }; const [options, setOptions] = useState( (widget?.options as IRealtimeWidgetOptions) || defaultOptions, ); // Update local options when widget data changes useEffect(() => { if (widget?.options) { setOptions(widget.options as IRealtimeWidgetOptions); } }, [widget?.options]); const handleUpdateOptions = (newOptions: IRealtimeWidgetOptions) => { setOptions(newOptions); onUpdateOptions(newOptions); }; return (
Realtime Widget

Embed a realtime visitor counter widget on your website. The widget shows live visitor count, activity histogram, top countries, referrers and paths.

{isEnabled && (

Widget Options

handleUpdateOptions({ ...options, referrers: checked }) } disabled={isUpdatingOptions} />
handleUpdateOptions({ ...options, countries: checked }) } disabled={isUpdatingOptions} />
handleUpdateOptions({ ...options, paths: checked }) } disabled={isUpdatingOptions} />

Widget URL

Direct link to the widget. You can open this in a new tab or embed it.

Embed Code

Copy this code and paste it into your website HTML where you want the widget to appear.

Preview

` : null; return (
Counter Widget

A compact live visitor counter badge you can embed anywhere. Shows the current number of unique visitors with a live indicator.

{isEnabled && counterUrl && (

Widget URL

Direct link to the counter widget.

Embed Code

Copy this code and paste it into your website HTML where you want the counter to appear.

Preview