fix: Harmonize rings and hover states

This commit is contained in:
Dries Augustyns
2026-04-24 16:01:18 +02:00
parent 6f31fa2503
commit f40cc86cec
34 changed files with 177 additions and 219 deletions
+3 -3
View File
@@ -105,9 +105,9 @@ export default function ResetPassword() {
This link is invalid or has expired. Request a new one from the login page.
</p>
</div>
<Link href="/auth/login" className="mt-2">
<Button>Back to login</Button>
</Link>
<Button asChild className="mt-2">
<Link href="/auth/login">Back to login</Link>
</Button>
</div>
</CardContent>
</Card>
+6 -10
View File
@@ -190,11 +190,9 @@ export default function VerifyEmail() {
</p>
)}
<Link href="/auth/login">
<Button variant="outline" className="w-full">
Back to login
</Button>
</Link>
<Button asChild variant="outline" className="w-full">
<Link href="/auth/login">Back to login</Link>
</Button>
</div>
</motion.div>
)}
@@ -273,11 +271,9 @@ export default function VerifyEmail() {
</p>
)}
<Link href="/auth/login">
<Button variant="outline" className="w-full">
Back to login
</Button>
</Link>
<Button asChild variant="outline" className="w-full">
<Link href="/auth/login">Back to login</Link>
</Button>
</div>
</motion.div>
)}
+6 -10
View File
@@ -342,11 +342,9 @@ export default function CampaignDetailsPage() {
{/* Header */}
<div className="space-y-4">
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/campaigns">
<Button type="button" variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href="/campaigns"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 sm:gap-3">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900 truncate">{c.name}</h1>
@@ -848,11 +846,9 @@ export default function CampaignDetailsPage() {
{/* Header */}
<div className="space-y-4">
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/campaigns">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href="/campaigns"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 sm:gap-3 mb-2">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900 truncate">{c.name}</h1>
+6 -8
View File
@@ -177,11 +177,9 @@ export default function CreateCampaignPage() {
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/campaigns">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href="/campaigns"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900">Create Campaign</h1>
<p className="text-neutral-500 mt-1 text-sm sm:text-base">
@@ -396,9 +394,9 @@ export default function CreateCampaignPage() {
{/* Actions */}
<div className="flex justify-end gap-3">
<Link href="/campaigns">
<Button type="button" variant="outline">Cancel</Button>
</Link>
<Button asChild variant="outline">
<Link href="/campaigns">Cancel</Link>
</Button>
<Button type="submit" disabled={saving}>
{saving ? 'Creating...' : 'Create Campaign'}
</Button>
+11 -12
View File
@@ -328,12 +328,12 @@ export default function CampaignsPage() {
</DropdownMenuContent>
</DropdownMenu>
) : (
<Link href="/campaigns/create">
<Button>
<Button asChild>
<Link href="/campaigns/create">
<Plus className="h-4 w-4" />
Create Campaign
</Button>
</Link>
</Link>
</Button>
)
}
/>
@@ -347,10 +347,11 @@ export default function CampaignsPage() {
const deliveryPct = campaign.totalRecipients > 0 ? (campaign.sentCount / campaign.totalRecipients) * 100 : 0;
return (
<Card key={campaign.id} className="transition-colors hover:border-neutral-300 flex flex-col">
<Card key={campaign.id} className="transition-colors hover:border-neutral-300 flex flex-col [&:has([data-card-link]:focus-visible)]:ring-2 [&:has([data-card-link]:focus-visible)]:ring-ring [&:has([data-card-link]:focus-visible)]:ring-offset-2">
<Link
href={`/campaigns/${campaign.id}`}
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl"
data-card-link=""
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl focus-visible:outline-none"
aria-label={`Open ${campaign.name}`}
>
<div className="flex items-start justify-between gap-3 mb-3">
@@ -432,17 +433,15 @@ export default function CampaignsPage() {
<Calendar className="h-3 w-3" />
<div className="group relative inline-block cursor-help">
<span>Updated {formatRelativeTime(campaign.updatedAt)}</span>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(campaign.updatedAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
</div>
<div className="flex items-center gap-1">
<Link href={`/campaigns/${campaign.id}`} aria-label={campaign.status === 'DRAFT' ? 'Edit campaign' : 'View campaign'}>
<Button variant="ghost" size="sm" title={campaign.status === 'DRAFT' ? 'Edit campaign' : 'View campaign'}>
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm" title={campaign.status === 'DRAFT' ? 'Edit campaign' : 'View campaign'}>
<Link href={`/campaigns/${campaign.id}`} aria-label={campaign.status === 'DRAFT' ? 'Edit campaign' : 'View campaign'}><Edit className="h-4 w-4" /></Link>
</Button>
<Button variant="ghost" size="sm" title="Duplicate campaign" onClick={() => handleDuplicate(campaign.id)}>
<Copy className="h-4 w-4" />
</Button>
+9 -11
View File
@@ -105,12 +105,12 @@ export default function ContactDetailPage() {
<p className="text-neutral-500 mb-6">
The contact you&apos;re looking for doesn&apos;t exist or has been deleted.
</p>
<Link href="/contacts">
<Button>
<Button asChild>
<Link href="/contacts">
<ArrowLeft className="h-4 w-4" />
Back to Contacts
</Button>
</Link>
</Link>
</Button>
</div>
</DashboardLayout>
);
@@ -124,11 +124,9 @@ export default function ContactDetailPage() {
{/* Header */}
<div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-3 sm:gap-4 min-w-0">
<Link href="/contacts">
<Button variant="outline" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="outline" size="sm">
<Link href="/contacts"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div className="min-w-0">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900 truncate">{contact.email}</h1>
<p className="mt-1">
@@ -261,7 +259,7 @@ export default function ContactDetailPage() {
<p className="text-sm font-medium text-neutral-900">Created</p>
<div className="group relative inline-block cursor-help">
<p className="text-sm text-neutral-500">{dayjs(contact.createdAt).fromNow()}</p>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(contact.createdAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
@@ -271,7 +269,7 @@ export default function ContactDetailPage() {
<p className="text-sm font-medium text-neutral-900">Last Updated</p>
<div className="group relative inline-block cursor-help">
<p className="text-sm text-neutral-500">{dayjs(contact.updatedAt).fromNow()}</p>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(contact.updatedAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
+8 -12
View File
@@ -338,18 +338,16 @@ export default function ContactsPage() {
<td className="px-6 py-4 whitespace-nowrap text-sm text-neutral-500">
<div className="group relative inline-block cursor-help">
{formatRelativeTime(contact.createdAt)}
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-1/2 transform -translate-x-1/2 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-1/2 transform -translate-x-1/2 mb-1 whitespace-nowrap">
{dayjs(contact.createdAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end gap-2">
<Link href={`/contacts/${contact.id}`}>
<Button variant="ghost" size="sm">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href={`/contacts/${contact.id}`}><Edit className="h-4 w-4" /></Link>
</Button>
<Button variant="ghost" size="sm" onClick={() => promptDelete(contact.id)}>
<Trash2 className="h-4 w-4" />
</Button>
@@ -388,16 +386,14 @@ export default function ContactsPage() {
<div className="flex items-center justify-between">
<div className="group relative inline-block cursor-help">
<span className="text-xs text-neutral-500">{formatRelativeTime(contact.createdAt)}</span>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(contact.createdAt).format('Do MMMM YYYY, h:mm A')}
</div>
</div>
<div className="flex items-center gap-1">
<Link href={`/contacts/${contact.id}`}>
<Button variant="ghost" size="sm">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href={`/contacts/${contact.id}`}><Edit className="h-4 w-4" /></Link>
</Button>
<Button variant="ghost" size="sm" onClick={() => promptDelete(contact.id)}>
<Trash2 className="h-4 w-4" />
</Button>
+6 -10
View File
@@ -113,11 +113,9 @@ export default function Index() {
Please contact support to resolve this issue and get your project re-enabled.
</p>
</div>
<Link href="/settings?tab=security">
<Button size="sm" variant="outline" className="w-full sm:w-auto flex-shrink-0">
View Details
</Button>
</Link>
<Button asChild size="sm" variant="outline" className="w-full sm:w-auto flex-shrink-0">
<Link href="/settings?tab=security">View Details</Link>
</Button>
</AlertDescription>
</Alert>
)}
@@ -167,11 +165,9 @@ export default function Index() {
<span className="text-sm">
Your emails currently include Plunk branding. Upgrade to a subscription to remove it.
</span>
<Link href="/settings?tab=billing">
<Button size="sm" className="w-full sm:w-auto">
Upgrade Now
</Button>
</Link>
<Button asChild size="sm" className="w-full sm:w-auto">
<Link href="/settings?tab=billing">Upgrade Now</Link>
</Button>
</AlertDescription>
</Alert>
)}
+1 -1
View File
@@ -165,7 +165,7 @@ export default function Manage() {
<button
onClick={() => void handleToggleSubscription()}
disabled={updating}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-2 ${
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
contact?.subscribed ? 'bg-neutral-900' : 'bg-neutral-200'
} ${updating ? 'opacity-50 cursor-not-allowed' : ''}`}
>
+1 -1
View File
@@ -80,7 +80,7 @@ export default function OnboardingWelcome() {
transition={{duration: 0.35, delay: 0.08 + index * 0.06, ease: [0.22, 1, 0.36, 1]}}
whileHover={{y: -2}}
whileTap={{scale: 0.995}}
className="group flex flex-col gap-5 rounded-xl border border-neutral-200 bg-white p-6 text-left shadow-sm transition-colors hover:border-neutral-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900 focus-visible:ring-offset-2"
className="group flex flex-col gap-5 rounded-xl border border-neutral-200 bg-white p-6 text-left shadow-sm transition-colors hover:border-neutral-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
<div className="flex items-center justify-between">
<span className="flex h-10 w-10 items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 text-neutral-900">
+19 -23
View File
@@ -7,6 +7,7 @@ import {
CardHeader,
CardTitle,
ConfirmDialog,
EmptyState,
IconSpinner,
Input,
Label,
@@ -194,12 +195,12 @@ export default function SegmentDetailPage() {
<p className="text-neutral-500 mb-6">
The segment you&apos;re looking for doesn&apos;t exist or has been deleted.
</p>
<Link href="/segments">
<Button>
<Button asChild>
<Link href="/segments">
<ArrowLeft className="h-4 w-4" />
Back to Segments
</Button>
</Link>
</Link>
</Button>
</div>
</DashboardLayout>
);
@@ -213,11 +214,9 @@ export default function SegmentDetailPage() {
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/segments">
<Button variant="outline" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="outline" size="sm">
<Link href="/segments"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div>
<div className="flex items-center gap-2">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900">{segment.name}</h1>
@@ -276,7 +275,7 @@ export default function SegmentDetailPage() {
type="checkbox"
checked={trackMembership}
onChange={e => setTrackMembership(e.target.checked)}
className="mt-1 h-4 w-4 text-neutral-900 focus:ring-neutral-900 border-neutral-300 rounded"
className="mt-1 h-4 w-4 text-neutral-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 border-neutral-300 rounded"
/>
<div className="flex-1">
<Label htmlFor="trackMembership" className="font-medium cursor-pointer">
@@ -365,12 +364,11 @@ export default function SegmentDetailPage() {
<p className="text-sm text-neutral-500">Loading contacts...</p>
</div>
) : contactsData?.data.length === 0 ? (
<div className="text-center py-8">
<Users className="h-12 w-12 text-neutral-400 mx-auto mb-4" />
<p className="text-neutral-500">
{isStatic ? 'No members in this segment yet' : 'No contacts match this segment'}
</p>
</div>
<EmptyState
icon={Users}
title={isStatic ? 'No members yet' : 'No contacts match'}
description={isStatic ? 'Add contacts to this segment to get started.' : 'No contacts currently match these filter conditions.'}
/>
) : (
<>
<div className="space-y-2">
@@ -385,11 +383,9 @@ export default function SegmentDetailPage() {
<span className="text-sm font-medium">{contact.email}</span>
</div>
<div className="flex items-center gap-2">
<Link href={`/contacts/${contact.id}`}>
<Button variant="ghost" size="sm">
View
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href={`/contacts/${contact.id}`}>View</Link>
</Button>
{isStatic && (
<Button
variant="destructiveGhost"
@@ -494,7 +490,7 @@ export default function SegmentDetailPage() {
<p className="text-sm font-medium text-neutral-900">Created</p>
<div className="group relative inline-block cursor-help">
<p className="text-sm text-neutral-500">{dayjs(segment.createdAt).fromNow()}</p>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(segment.createdAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
@@ -504,7 +500,7 @@ export default function SegmentDetailPage() {
<p className="text-sm font-medium text-neutral-900">Last Updated</p>
<div className="group relative inline-block cursor-help">
<p className="text-sm text-neutral-500">{dayjs(segment.updatedAt).fromNow()}</p>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(segment.updatedAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
+15 -16
View File
@@ -90,13 +90,13 @@ export default function SegmentsPage() {
Create dynamic audience groups based on contact attributes and behaviors
</p>
</div>
<Link href="/segments/new" className="w-full sm:w-auto">
<Button className="w-full sm:w-auto">
<Button asChild className="w-full sm:w-auto">
<Link href="/segments/new">
<Plus className="h-4 w-4" />
<span className="hidden sm:inline">Create Segment</span>
<span className="sm:hidden">Create</span>
</Button>
</Link>
</Link>
</Button>
</div>
{/* Search */}
@@ -145,12 +145,12 @@ export default function SegmentsPage() {
description={searchInput ? 'Try a different search term.' : 'Group contacts by attributes to target specific audiences.'}
action={
!searchInput ? (
<Link href="/segments/new">
<Button>
<Button asChild>
<Link href="/segments/new">
<Plus className="h-4 w-4" />
Create Segment
</Button>
</Link>
</Link>
</Button>
) : undefined
}
/>
@@ -162,10 +162,11 @@ export default function SegmentsPage() {
const isDynamic = (segment as unknown as {type: string}).type !== 'STATIC';
const filterCount = isDynamic ? countFiltersInCondition(segment.condition) : 0;
return (
<Card key={segment.id} className="transition-colors hover:border-neutral-300 flex flex-col">
<Card key={segment.id} className="transition-colors hover:border-neutral-300 flex flex-col [&:has([data-card-link]:focus-visible)]:ring-2 [&:has([data-card-link]:focus-visible)]:ring-ring [&:has([data-card-link]:focus-visible)]:ring-offset-2">
<Link
href={`/segments/${segment.id}`}
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl"
data-card-link=""
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl focus-visible:outline-none"
aria-label={`Open ${segment.name}`}
>
<div className="flex items-start justify-between gap-3 mb-3">
@@ -195,17 +196,15 @@ export default function SegmentsPage() {
<Calendar className="h-3 w-3" />
<div className="group relative inline-block cursor-help">
<span>Updated {formatRelativeTime(segment.updatedAt)}</span>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(segment.updatedAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
</div>
<div className="flex items-center gap-1">
<Link href={`/segments/${segment.id}`} aria-label="Edit segment">
<Button variant="ghost" size="sm" title="Edit segment">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm" title="Edit segment">
<Link href={`/segments/${segment.id}`} aria-label="Edit segment"><Edit className="h-4 w-4" /></Link>
</Button>
<Button
variant="ghost"
size="sm"
+7 -11
View File
@@ -79,11 +79,9 @@ export default function NewSegmentPage() {
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-4">
<Link href="/segments">
<Button variant="outline" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="outline" size="sm">
<Link href="/segments"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900">Create Segment</h1>
<p className="text-neutral-500 mt-1">
@@ -159,7 +157,7 @@ export default function NewSegmentPage() {
type="checkbox"
checked={trackMembership}
onChange={e => setTrackMembership(e.target.checked)}
className="mt-1 h-4 w-4 text-neutral-900 focus:ring-neutral-900 border-neutral-300 rounded"
className="mt-1 h-4 w-4 text-neutral-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 border-neutral-300 rounded"
/>
<div className="flex-1">
<Label htmlFor="trackMembership" className="font-medium cursor-pointer">
@@ -204,11 +202,9 @@ export default function NewSegmentPage() {
{/* Actions */}
<div className="flex items-center justify-end gap-2">
<Link href="/segments">
<Button type="button" variant="outline" disabled={isSubmitting}>
Cancel
</Button>
</Link>
<Button asChild variant="outline" disabled={isSubmitting}>
<Link href="/segments">Cancel</Link>
</Button>
<Button type="submit" disabled={isSubmitting}>
<Save className="h-4 w-4 mr-2" />
{isSubmitting ? 'Creating...' : 'Create Segment'}
+3 -5
View File
@@ -130,11 +130,9 @@ export default function TemplateEditorPage() {
{/* Header */}
<div className="space-y-4">
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/templates">
<Button type="button" variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href="/templates"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900">Edit Template</h1>
<p className="text-neutral-500 mt-1 text-sm sm:text-base">Make changes to your email template</p>
+6 -8
View File
@@ -73,11 +73,9 @@ export default function CreateTemplatePage() {
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/templates">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href="/templates"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900">Create Template</h1>
<p className="text-neutral-500 mt-1 text-sm sm:text-base">
@@ -217,9 +215,9 @@ export default function CreateTemplatePage() {
{/* Actions */}
<div className="flex justify-end gap-3">
<Link href="/templates">
<Button type="button" variant="outline">Cancel</Button>
</Link>
<Button asChild variant="outline">
<Link href="/templates">Cancel</Link>
</Button>
<Button type="submit" disabled={saving}>
{saving ? 'Creating...' : 'Create Template'}
</Button>
+15 -16
View File
@@ -80,13 +80,13 @@ export default function TemplatesPage() {
{data?.total ? `${data.total} total templates` : ''}
</p>
</div>
<Link href="/templates/create" className="w-full sm:w-auto">
<Button className="w-full sm:w-auto">
<Button asChild className="w-full sm:w-auto">
<Link href="/templates/create">
<Plus className="h-4 w-4" />
<span className="hidden sm:inline">Create Template</span>
<span className="sm:hidden">Create</span>
</Button>
</Link>
</Link>
</Button>
</div>
{/* Search & Filters */}
@@ -149,12 +149,12 @@ export default function TemplatesPage() {
description={search ? 'Try a different search term.' : 'Create reusable email designs for campaigns.'}
action={
!search ? (
<Link href="/templates/create">
<Button>
<Button asChild>
<Link href="/templates/create">
<Plus className="h-4 w-4" />
Create Template
</Button>
</Link>
</Link>
</Button>
) : undefined
}
/>
@@ -164,10 +164,11 @@ export default function TemplatesPage() {
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{data?.data.map(template => (
<Card key={template.id} className="transition-colors hover:border-neutral-300 flex flex-col">
<Card key={template.id} className="transition-colors hover:border-neutral-300 flex flex-col [&:has([data-card-link]:focus-visible)]:ring-2 [&:has([data-card-link]:focus-visible)]:ring-ring [&:has([data-card-link]:focus-visible)]:ring-offset-2">
<Link
href={`/templates/${template.id}`}
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl"
data-card-link=""
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl focus-visible:outline-none"
aria-label={`Edit ${template.name}`}
>
<div className="flex items-start justify-between gap-3 mb-3">
@@ -186,17 +187,15 @@ export default function TemplatesPage() {
<Calendar className="h-3 w-3" />
<div className="group relative inline-block cursor-help">
<span>Updated {formatRelativeTime(template.updatedAt)}</span>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(template.updatedAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
</div>
<div className="flex items-center gap-1">
<Link href={`/templates/${template.id}`} aria-label="Edit template">
<Button variant="ghost" size="sm" title="Edit template">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm" title="Edit template">
<Link href={`/templates/${template.id}`} aria-label="Edit template"><Edit className="h-4 w-4" /></Link>
</Button>
<Button variant="ghost" size="sm" title="Duplicate template" onClick={() => handleDuplicate(template.id)}>
<Copy className="h-4 w-4" />
</Button>
+6 -8
View File
@@ -443,11 +443,9 @@ export default function WorkflowEditorPage() {
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/workflows">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm">
<Link href="/workflows"><ArrowLeft className="h-4 w-4" /></Link>
</Button>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 sm:gap-3">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900 truncate">{workflow.name}</h1>
@@ -573,7 +571,7 @@ export default function WorkflowEditorPage() {
<nav className="-mb-px flex space-x-8">
<button
onClick={() => setActiveTab('builder')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
className={`py-2 px-1 border-b-2 font-medium text-sm rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
activeTab === 'builder'
? 'border-neutral-900 text-neutral-900'
: 'border-transparent text-neutral-500 hover:text-neutral-700 hover:border-neutral-300'
@@ -583,7 +581,7 @@ export default function WorkflowEditorPage() {
</button>
<button
onClick={() => setActiveTab('executions')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
className={`py-2 px-1 border-b-2 font-medium text-sm rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
activeTab === 'executions'
? 'border-neutral-900 text-neutral-900'
: 'border-transparent text-neutral-500 hover:text-neutral-700 hover:border-neutral-300'
@@ -682,7 +680,7 @@ export default function WorkflowEditorPage() {
<td className="px-6 py-4 whitespace-nowrap text-sm text-neutral-500">
<div className="group relative inline-block cursor-help">
{dayjs(execution.startedAt).fromNow()}
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-1/2 transform -translate-x-1/2 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-1/2 transform -translate-x-1/2 mb-1 whitespace-nowrap">
{dayjs(execution.startedAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
+9 -10
View File
@@ -157,10 +157,11 @@ export default function WorkflowsPage() {
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{data?.data.map(workflow => (
<Card key={workflow.id} className="transition-colors hover:border-neutral-300 flex flex-col">
<Card key={workflow.id} className="transition-colors hover:border-neutral-300 flex flex-col [&:has([data-card-link]:focus-visible)]:ring-2 [&:has([data-card-link]:focus-visible)]:ring-ring [&:has([data-card-link]:focus-visible)]:ring-offset-2">
<Link
href={`/workflows/${workflow.id}`}
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl"
data-card-link=""
className="flex-1 block p-6 pb-4 hover:bg-neutral-50/50 transition-colors rounded-t-xl focus-visible:outline-none"
aria-label={`Open ${workflow.name}`}
>
<div className="flex items-start justify-between gap-3 mb-3">
@@ -201,7 +202,7 @@ export default function WorkflowsPage() {
<Calendar className="h-3 w-3" />
<div className="group relative inline-block cursor-help">
<span>Updated {formatRelativeTime(workflow.updatedAt)}</span>
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-lg bottom-full left-0 mb-1 whitespace-nowrap">
<div className="hidden group-hover:block absolute z-10 w-48 p-2 bg-neutral-900 text-white text-xs rounded shadow-md bottom-full left-0 mb-1 whitespace-nowrap">
{dayjs(workflow.updatedAt).format('DD MMMM YYYY, hh:mm')}
</div>
</div>
@@ -215,11 +216,9 @@ export default function WorkflowsPage() {
>
{workflow.enabled ? <PowerOff className="h-4 w-4" /> : <Power className="h-4 w-4" />}
</Button>
<Link href={`/workflows/${workflow.id}`} aria-label="Edit workflow">
<Button variant="ghost" size="sm" title="Edit workflow">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button asChild variant="ghost" size="sm" title="Edit workflow">
<Link href={`/workflows/${workflow.id}`} aria-label="Edit workflow"><Edit className="h-4 w-4" /></Link>
</Button>
<Button
variant="ghost"
size="sm"
@@ -359,7 +358,7 @@ function CreateWorkflowDialog({open, onOpenChange, onSuccess}: CreateWorkflowDia
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Send a series of welcome emails to new subscribers"
className="w-full px-3 py-2 border border-neutral-200 rounded-md text-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
className="w-full px-3 py-2 border border-neutral-200 rounded-md text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
rows={3}
/>
</div>
@@ -433,7 +432,7 @@ function CreateWorkflowDialog({open, onOpenChange, onSuccess}: CreateWorkflowDia
type="checkbox"
checked={allowReentry}
onChange={e => setAllowReentry(e.target.checked)}
className="mt-0.5 h-4 w-4 text-neutral-900 focus:ring-neutral-900 border-neutral-300 rounded"
className="mt-0.5 h-4 w-4 text-neutral-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 border-neutral-300 rounded"
/>
<div className="flex-1">
<Label htmlFor="allowReentry" className="font-medium cursor-pointer">