11'use client'
22
33import { useState } from 'react'
4+ import { useClipboard } from '@/lib/hooks/use-clipboard'
5+ import { defaultSuccessToast , toast } from '@/lib/hooks/use-toast'
46import { cn } from '@/lib/utils'
57import { Badge } from '@/ui/primitives/badge'
8+ import { Button } from '@/ui/primitives/button'
69import {
710 DropdownMenu ,
811 DropdownMenuContent ,
@@ -12,6 +15,8 @@ import {
1215} from '@/ui/primitives/dropdown-menu'
1316import { IconButton } from '@/ui/primitives/icon-button'
1417import {
18+ CheckIcon ,
19+ CopyIcon ,
1520 EditIcon ,
1621 IndicatorDotsIcon ,
1722 PrivateIcon ,
@@ -36,6 +41,62 @@ type WebhookRowActionsProps = {
3641 webhook : Webhook
3742}
3843
44+ type WebhookNameAndUrlProps = {
45+ name : string
46+ url : string
47+ }
48+
49+ type UrlIconState = 'copied' | 'hovered' | 'idle'
50+
51+ const urlIconMap : Record < UrlIconState , typeof WebhookIcon > = {
52+ copied : CheckIcon ,
53+ hovered : CopyIcon ,
54+ idle : WebhookIcon ,
55+ }
56+
57+ const WebhookNameAndUrl = ( { name, url } : WebhookNameAndUrlProps ) => {
58+ const [ wasCopied , copy ] = useClipboard ( 1500 )
59+ const [ isUrlHovered , setIsUrlHovered ] = useState ( false )
60+ const iconState : UrlIconState = wasCopied
61+ ? 'copied'
62+ : isUrlHovered
63+ ? 'hovered'
64+ : 'idle'
65+ const UrlIcon = urlIconMap [ iconState ]
66+
67+ const handleCopy = async ( e : React . MouseEvent ) => {
68+ e . stopPropagation ( )
69+ await copy ( url )
70+ toast ( defaultSuccessToast ( 'Webhook URL copied' ) )
71+ }
72+
73+ return (
74+ < >
75+ < div
76+ aria-hidden = "true"
77+ className = "border-stroke flex size-8 shrink-0 items-center justify-center border"
78+ >
79+ < UrlIcon className = "size-4 text-fg-secondary" />
80+ </ div >
81+
82+ < div className = "flex min-w-0 flex-1 flex-col justify-center gap-0.5 pb-0.5" >
83+ < p className = "truncate text-left text-fg prose-body" > { name } </ p >
84+ < Button
85+ variant = "quaternary"
86+ size = "none"
87+ onClick = { handleCopy }
88+ onMouseEnter = { ( ) => setIsUrlHovered ( true ) }
89+ onMouseLeave = { ( ) => setIsUrlHovered ( false ) }
90+ aria-label = { `Copy webhook URL ${ url } ` }
91+ className = "w-full min-w-0 justify-start font-mono uppercase prose-label-numeric"
92+ >
93+ < span className = "truncate" > { url } </ span >
94+ </ Button >
95+ </ div >
96+ </ >
97+ )
98+ }
99+
39100const rowCellClassName = 'h-[50px] p-0 align-top'
40101const rowContentClassName = 'flex h-11 items-center'
41102const actionIconClassName = 'size-4 text-fg-tertiary'
@@ -101,18 +162,7 @@ export const WebhookTableRow = ({ webhook, className }: WebhookRowProps) => {
101162 < TableRow className = { cn ( 'h-[50px] bg-bg hover:bg-transparent' , className ) } >
102163 < TableCell className = { cn ( rowCellClassName , 'max-w-0 pr-12' ) } >
103164 < div className = { cn ( rowContentClassName , 'min-w-0 gap-3' ) } >
104- < div className = "border-stroke flex size-8 shrink-0 items-center justify-center border" >
105- < WebhookIcon className = "size-4 text-fg-secondary" />
106- </ div >
107-
108- < div className = "flex min-w-0 flex-1 flex-col justify-center gap-0.5 pb-0.5" >
109- < p className = "truncate text-left text-fg prose-body" >
110- { webhook . name }
111- </ p >
112- < p className = "truncate font-mono text-[12px] leading-[17px] text-fg-tertiary uppercase" >
113- { webhook . url }
114- </ p >
115- </ div >
165+ < WebhookNameAndUrl name = { webhook . name } url = { webhook . url } />
116166 </ div >
117167 </ TableCell >
118168
0 commit comments