ui: Clean up empty states, modals, animations

This commit is contained in:
Dries Augustyns
2026-04-17 12:27:40 +02:00
parent 03a058ad48
commit 0803fd7bc4
32 changed files with 946 additions and 1047 deletions
+69 -54
View File
@@ -24,6 +24,34 @@ import {Check, CheckCircle2, ChevronDown, Copy, Loader2, RefreshCw, Trash2, XCir
import {useConfig} from '../lib/hooks/useConfig';
import {useAddDomain, useCheckDomainVerification, useDomains, useRemoveDomain} from '../lib/hooks/useDomains';
function AnimatedCopyIcon({isCopied}: {isCopied: boolean}) {
return (
<AnimatePresence mode="wait" initial={false}>
{isCopied ? (
<motion.span
key="copied"
initial={{opacity: 0, y: 4}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -4}}
transition={{duration: 0.15}}
>
<Check className="h-3 w-3 text-green-600" />
</motion.span>
) : (
<motion.span
key="idle"
initial={{opacity: 0, y: 4}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -4}}
transition={{duration: 0.15}}
>
<Copy className="h-3 w-3" />
</motion.span>
)}
</AnimatePresence>
);
}
interface DomainsSettingsProps {
projectId: string;
}
@@ -419,14 +447,13 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
onClick={() =>
handleCopyToken(`${token}._domainkey.${domain.domain}`, index + 2000)
}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken ===
`${token}._domainkey.${domain.domain}-${index + 2000}` ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon
isCopied={
copiedToken === `${token}._domainkey.${domain.domain}-${index + 2000}`
}
/>
</Button>
</div>
</td>
@@ -439,13 +466,11 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
variant="ghost"
size="sm"
onClick={() => handleCopyToken(`${token}.dkim.amazonses.com`, index)}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken === `${token}.dkim.amazonses.com-${index}` ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon
isCopied={copiedToken === `${token}.dkim.amazonses.com-${index}`}
/>
</Button>
</div>
</td>
@@ -500,13 +525,11 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
variant="ghost"
size="sm"
onClick={() => handleCopyToken(`plunk.${domain.domain}`, 3000)}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken === `plunk.${domain.domain}-3000` ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon
isCopied={copiedToken === `plunk.${domain.domain}-3000`}
/>
</Button>
</div>
</td>
@@ -524,14 +547,14 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
1000,
)
}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken ===
`10 feedback-smtp.${config.aws.sesRegion}.amazonses.com-1000` ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon
isCopied={
copiedToken ===
`10 feedback-smtp.${config.aws.sesRegion}.amazonses.com-1000`
}
/>
</Button>
</div>
</td>
@@ -551,13 +574,11 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
variant="ghost"
size="sm"
onClick={() => handleCopyToken(`plunk.${domain.domain}`, 3001)}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken === `plunk.${domain.domain}-3001` ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon
isCopied={copiedToken === `plunk.${domain.domain}-3001`}
/>
</Button>
</div>
</td>
@@ -572,13 +593,11 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
onClick={() =>
handleCopyToken('"v=spf1 include:amazonses.com ~all"', 1001)
}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken === '"v=spf1 include:amazonses.com ~all"-1001' ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon
isCopied={copiedToken === '"v=spf1 include:amazonses.com ~all"-1001'}
/>
</Button>
</div>
</td>
@@ -632,13 +651,9 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
variant="ghost"
size="sm"
onClick={() => handleCopyToken(domain.domain, 3002)}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken === `${domain.domain}-3002` ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon isCopied={copiedToken === `${domain.domain}-3002`} />
</Button>
</div>
</td>
@@ -656,14 +671,14 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
1002,
)
}
className="shrink-0 h-6 w-6 p-0"
className="shrink-0 h-6 w-6 p-0 overflow-hidden"
>
{copiedToken ===
`10 inbound-smtp.${config.aws.sesRegion}.amazonaws.com-1002` ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
<AnimatedCopyIcon
isCopied={
copiedToken ===
`10 inbound-smtp.${config.aws.sesRegion}.amazonaws.com-1002`
}
/>
</Button>
</div>
</td>
@@ -674,8 +689,8 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
</div>
)}
<div className="flex items-start gap-2 p-3 bg-blue-50 rounded-lg border border-blue-200 mt-3">
<div className="text-blue-600 mt-0.5">
<div className="flex items-start gap-2 p-3 bg-neutral-50 rounded-lg border border-neutral-200 mt-3">
<div className="text-neutral-500 mt-0.5">
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
@@ -684,7 +699,7 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
/>
</svg>
</div>
<p className="text-xs text-blue-900">
<p className="text-xs text-neutral-600">
Click the copy icon to copy record values. After adding all records to your DNS
provider, use the refresh button above to verify your domain.
</p>