Browse Source

chore(web): improve webhooks section mobile responsiveness

pull/48/head
isra el 1 year ago
parent
commit
379e8e70e4
  1. 50
      web/app/(app)/dashboard/(components)/webhooks/webhook-card.tsx
  2. 8
      web/app/(app)/dashboard/(components)/webhooks/webhook-docs.tsx
  3. 46
      web/app/(app)/dashboard/(components)/webhooks/webhooks-section.tsx
  4. 7
      web/components/shared/copy-button.tsx

50
web/app/(app)/dashboard/(components)/webhooks/webhook-card.tsx

@ -65,10 +65,10 @@ export function WebhookCard({ webhook, onEdit, onDelete }: WebhookCardProps) {
return ( return (
<Card> <Card>
<CardHeader className='flex flex-row items-center justify-between'>
<CardHeader className='flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4'>
<div className='space-y-1'> <div className='space-y-1'>
<div className='flex items-center space-x-2'>
<h3 className='text-lg font-semibold'>Webhook Endpoint</h3>
<div className='flex flex-wrap items-center gap-2'>
<h3 className='text-base font-semibold'>Webhook Endpoint</h3>
<Badge variant={webhook.isActive ? 'default' : 'secondary'}> <Badge variant={webhook.isActive ? 'default' : 'secondary'}>
{webhook.isActive ? 'Active' : 'Inactive'} {webhook.isActive ? 'Active' : 'Inactive'}
</Badge> </Badge>
@ -77,15 +77,15 @@ export function WebhookCard({ webhook, onEdit, onDelete }: WebhookCardProps) {
Notifications for SMS events Notifications for SMS events
</p> </p>
</div> </div>
<div className='flex items-center space-x-2'>
<div className='flex flex-wrap items-center gap-2'>
<Switch <Switch
checked={webhook.isActive} checked={webhook.isActive}
onCheckedChange={handleToggle} onCheckedChange={handleToggle}
disabled={isLoading} disabled={isLoading}
/> />
<Button variant='outline' size='sm' onClick={onEdit}> <Button variant='outline' size='sm' onClick={onEdit}>
<Edit2 className='h-4 w-4 mr-2' />
Edit
<Edit2 className='h-4 w-4 sm:mr-2' />
<span className='hidden sm:inline'>Edit</span>
</Button> </Button>
<DeleteWebhookButton onDelete={onDelete} /> <DeleteWebhookButton onDelete={onDelete} />
</div> </div>
@ -94,32 +94,34 @@ export function WebhookCard({ webhook, onEdit, onDelete }: WebhookCardProps) {
<div className='space-y-4'> <div className='space-y-4'>
<div> <div>
<label className='text-sm font-medium'>Delivery URL</label> <label className='text-sm font-medium'>Delivery URL</label>
<div className='flex items-center mt-1'>
<code className='flex-1 bg-muted px-3 py-2 rounded-md text-sm'>
<div className='flex items-center gap-1 mt-1'>
<code className='flex-1 bg-muted px-3 py-2 rounded-md text-sm break-all'>
{webhook.deliveryUrl} {webhook.deliveryUrl}
</code> </code>
<CopyButton value={webhook.deliveryUrl} label='Copy URL' />
<CopyButton value={webhook.deliveryUrl} label='Copy URL' className="ml-1" />
</div> </div>
</div> </div>
<div> <div>
<label className='text-sm font-medium'>Signing Secret</label> <label className='text-sm font-medium'>Signing Secret</label>
<div className='flex items-center mt-1'>
<code className='flex-1 bg-muted px-3 py-2 rounded-md text-sm font-mono'>
<div className='flex items-center gap-1 mt-1'>
<code className='flex-1 bg-muted px-3 py-2 rounded-md text-sm font-mono break-all'>
{showSecret ? webhook.signingSecret : maskSecret(webhook.signingSecret)} {showSecret ? webhook.signingSecret : maskSecret(webhook.signingSecret)}
</code> </code>
<Button
variant="ghost"
size="icon"
onClick={() => setShowSecret(!showSecret)}
className="mx-2"
>
{showSecret ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
<CopyButton value={webhook.signingSecret} label='Copy Secret' />
<div className='flex items-center gap-1 shrink-0 ml-1'>
<Button
variant="ghost"
size="icon"
onClick={() => setShowSecret(!showSecret)}
className="h-8 w-8"
>
{showSecret ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
<CopyButton value={webhook.signingSecret} label='Copy Secret' />
</div>
</div> </div>
</div> </div>
<div> <div>

8
web/app/(app)/dashboard/(components)/webhooks/webhook-docs.tsx

@ -80,15 +80,15 @@ def webhook():
export function WebhookDocs() { export function WebhookDocs() {
return ( return (
<Accordion type='multiple' className='w-full space-y-4'>
<Accordion type='multiple' className='w-full space-y-2 sm:space-y-4'>
<AccordionItem value='delivery' className='border rounded-lg'> <AccordionItem value='delivery' className='border rounded-lg'>
<AccordionTrigger className='px-4 hover:no-underline [&[data-state=open]>div]:bg-muted'>
<AccordionTrigger className='px-3 sm:px-4 hover:no-underline [&[data-state=open]>div]:bg-muted'>
<div className='flex items-center gap-2 py-2 -my-2 px-2 rounded-md'> <div className='flex items-center gap-2 py-2 -my-2 px-2 rounded-md'>
<AlertCircle className='h-4 w-4' /> <AlertCircle className='h-4 w-4' />
<span>Webhook Delivery Information</span>
<span className='text-sm sm:text-base'>Webhook Delivery Information</span>
</div> </div>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className='px-4 pb-4'>
<AccordionContent className='px-3 sm:px-4 pb-4'>
<div className='space-y-2 mt-2 text-sm text-muted-foreground'> <div className='space-y-2 mt-2 text-sm text-muted-foreground'>
<p> <p>
When a new SMS is received, we&apos;ll send a POST request to your When a new SMS is received, we&apos;ll send a POST request to your

46
web/app/(app)/dashboard/(components)/webhooks/webhooks-section.tsx

@ -84,11 +84,11 @@ export default function WebhooksSection() {
} }
return ( return (
<div className='container mx-auto py-8'>
<div className='flex justify-between items-center mb-8'>
<div className='container mx-auto py-4 sm:py-8 px-4 sm:px-6'>
<div className='flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-8'>
<div> <div>
<h1 className='text-3xl font-bold flex items-center gap-2'>
<Webhook className='h-8 w-8' />
<h1 className='text-xl sm:text-2xl font-bold flex flex-wrap items-center gap-2'>
<Webhook className='h-5 w-5 sm:h-6 sm:w-6' />
Webhooks Webhooks
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
@ -103,22 +103,36 @@ export default function WebhooksSection() {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</h1> </h1>
<p className='text-muted-foreground mt-2'>
<p className='text-sm text-muted-foreground mt-2'>
Manage webhook notifications for your SMS events Manage webhook notifications for your SMS events
</p> </p>
</div> </div>
<Button
onClick={handleCreateClick}
disabled={webhooks?.data?.length > 0 || isLoading}
variant='default'
>
<PlusCircle className='mr-2 h-4 w-4' />
Create Webhook
</Button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>
<Button
onClick={handleCreateClick}
disabled={webhooks?.data?.length > 0 || isLoading}
variant='default'
className='w-full sm:w-auto'
>
<PlusCircle className='mr-2 h-4 w-4' />
Create Webhook
</Button>
</div>
</TooltipTrigger>
{webhooks?.data?.length > 0 && (
<TooltipContent>
<p>You already have an active webhook subscription. You can edit or manage the existing webhook instead.</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
</div> </div>
<div className='grid grid-cols-1 lg:grid-cols-2 gap-8'>
<div>
<div className='grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-8'>
<div className='space-y-4'>
{isLoading ? ( {isLoading ? (
<div className='grid gap-4'> <div className='grid gap-4'>
<WebhookCardSkeleton /> <WebhookCardSkeleton />
@ -157,7 +171,7 @@ export default function WebhooksSection() {
</div> </div>
</div> </div>
<div className='block lg:hidden mt-8'>
<div className='block lg:hidden mt-4 sm:mt-8'>
<WebhookDocs /> <WebhookDocs />
</div> </div>

7
web/components/shared/copy-button.tsx

@ -8,9 +8,10 @@ import { useToast } from "@/hooks/use-toast";
interface CopyButtonProps { interface CopyButtonProps {
value: string; value: string;
label: string; label: string;
className?: string;
} }
export function CopyButton({ value, label }: CopyButtonProps) {
export function CopyButton({ value, label, className }: CopyButtonProps) {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
@ -35,9 +36,9 @@ export function CopyButton({ value, label }: CopyButtonProps) {
return ( return (
<Button <Button
variant="ghost" variant="ghost"
size="sm"
size="icon"
className={className}
onClick={copyToClipboard} onClick={copyToClipboard}
className="ml-2"
> >
{copied ? ( {copied ? (
<Check className="h-4 w-4 text-green-500" /> <Check className="h-4 w-4 text-green-500" />

Loading…
Cancel
Save