fix colors in dashboard (#206)
This commit is contained in:
@@ -146,35 +146,35 @@ export default function CampaignDetailsPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CampaignStatusBadge: React.FC<{ status: string }> = ({ status }) => {
|
const CampaignStatusBadge: React.FC<{ status: string }> = ({ status }) => {
|
||||||
let outsideColor = "bg-gray-600";
|
let outsideColor = "bg-gray";
|
||||||
let insideColor = "bg-gray-600/50";
|
let insideColor = "bg-gray/50";
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "delivered":
|
case "delivered":
|
||||||
outsideColor = "bg-emerald-500/30";
|
outsideColor = "bg-green/30";
|
||||||
insideColor = "bg-emerald-500";
|
insideColor = "bg-green";
|
||||||
break;
|
break;
|
||||||
case "bounced":
|
case "bounced":
|
||||||
case "unsubscribed":
|
case "unsubscribed":
|
||||||
outsideColor = "bg-red-500/30";
|
outsideColor = "bg-red/30";
|
||||||
insideColor = "bg-red-500";
|
insideColor = "bg-red";
|
||||||
break;
|
break;
|
||||||
case "clicked":
|
case "clicked":
|
||||||
outsideColor = "bg-cyan-500/30";
|
outsideColor = "bg-blue/30";
|
||||||
insideColor = "bg-cyan-500";
|
insideColor = "bg-blue";
|
||||||
break;
|
break;
|
||||||
case "opened":
|
case "opened":
|
||||||
outsideColor = "bg-indigo-500/30";
|
outsideColor = "bg-purple/30";
|
||||||
insideColor = "bg-indigo-500";
|
insideColor = "bg-purple";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "complained":
|
case "complained":
|
||||||
outsideColor = "bg-yellow-500/30";
|
outsideColor = "bg-yellow/30";
|
||||||
insideColor = "bg-yellow-500";
|
insideColor = "bg-yellow";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
outsideColor = "bg-gray-600/40";
|
outsideColor = "bg-gray/40";
|
||||||
insideColor = "bg-gray-600";
|
insideColor = "bg-gray";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -103,10 +103,10 @@ export default function CampaignList() {
|
|||||||
<div
|
<div
|
||||||
className={`text-center w-[130px] rounded capitalize py-1 text-xs ${
|
className={`text-center w-[130px] rounded capitalize py-1 text-xs ${
|
||||||
campaign.status === CampaignStatus.DRAFT
|
campaign.status === CampaignStatus.DRAFT
|
||||||
? "bg-gray-500/15 dark:bg-gray-400/15 text-gray-700 dark:text-gray-400/90 border border-gray-500/25 dark:border-gray-700/25"
|
? "bg-gray/15 text-gray border border-gray/25"
|
||||||
: campaign.status === CampaignStatus.SENT
|
: campaign.status === CampaignStatus.SENT
|
||||||
? "bg-green-500/15 dark:bg-green-600/10 text-green-700 dark:text-green-600/90 border border-green-500/25 dark:border-green-700/25"
|
? "bg-green/15 text-green border border-green/25"
|
||||||
: "bg-yellow-500/15 dark:bg-yellow-600/10 text-yellow-700 dark:text-yellow-600/90 border border-yellow-500/25 dark:border-yellow-700/25"
|
: "bg-yellow/15 text-yellow border border-yellow/25"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{campaign.status.toLowerCase()}
|
{campaign.status.toLowerCase()}
|
||||||
|
@@ -62,7 +62,7 @@ export const DeleteCampaign: React.FC<{
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
toast.success(`Campaign deleted`);
|
toast.success(`Campaign deleted`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ export const DeleteCampaign: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
||||||
<Trash2 className="h-[18px] w-[18px] text-red-600/80" />
|
<Trash2 className="h-[18px] w-[18px] text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -35,7 +35,7 @@ export const DuplicateCampaign: React.FC<{
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
toast.success(`Campaign duplicated`);
|
toast.success(`Campaign duplicated`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export const DuplicateCampaign: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
||||||
<Copy className="h-[18px] w-[18px] text-blue-600/80" />
|
<Copy className="h-[18px] w-[18px] text-blue/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -152,13 +152,13 @@ export default function ContactList({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{contact.subscribed ? (
|
{contact.subscribed ? (
|
||||||
<div className="text-center w-[130px] rounded capitalize py-1 text-xs bg-green-500/15 dark:bg-green-600/10 text-green-700 dark:text-green-600/90 border border-green-500/25 dark:border-green-700/25">
|
<div className="text-center w-[130px] rounded capitalize py-1 text-xs bg-green/15 text-green border border-green/25">
|
||||||
Subscribed
|
Subscribed
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<div className="text-center w-[130px] rounded capitalize py-1 text-xs bg-red-500/10 text-red-600 dark:text-red-700/90 border border-red-600/10">
|
<div className="text-center w-[130px] rounded capitalize py-1 text-xs bg-red/10 text-red border border-red/10">
|
||||||
Unsubscribed
|
Unsubscribed
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -166,7 +166,7 @@ export default function ContactList({
|
|||||||
<p>
|
<p>
|
||||||
{getUnsubscribeReason(
|
{getUnsubscribeReason(
|
||||||
contact.unsubscribeReason ??
|
contact.unsubscribeReason ??
|
||||||
UnsubscribeReason.UNSUBSCRIBED
|
UnsubscribeReason.UNSUBSCRIBED,
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
|
@@ -66,7 +66,7 @@ export const DeleteContact: React.FC<{
|
|||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
toast.error(`Contact not deleted: ${e.message}`);
|
toast.error(`Contact not deleted: ${e.message}`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ export const DeleteContact: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Trash2 className="h-4 w-4 text-red-600/80" />
|
<Trash2 className="h-4 w-4 text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -46,7 +46,7 @@ export const DeleteContactBook: React.FC<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function onContactBookDelete(
|
async function onContactBookDelete(
|
||||||
values: z.infer<typeof contactBookSchema>
|
values: z.infer<typeof contactBookSchema>,
|
||||||
) {
|
) {
|
||||||
if (values.name !== contactBook.name) {
|
if (values.name !== contactBook.name) {
|
||||||
contactBookForm.setError("name", {
|
contactBookForm.setError("name", {
|
||||||
@@ -65,7 +65,7 @@ export const DeleteContactBook: React.FC<{
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
toast.success(`Contact book deleted`);
|
toast.success(`Contact book deleted`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ export const DeleteContactBook: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent ">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent ">
|
||||||
<Trash2 className="h-[18px] w-[18px] text-red-600/80 hover:text-red-600/70" />
|
<Trash2 className="h-[18px] w-[18px] text-red/80 hover:text-red/70" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -15,7 +15,6 @@ import { EmailStatus } from "@prisma/client";
|
|||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
import Spinner from "@unsend/ui/src/spinner";
|
||||||
import { useTheme } from "@unsend/ui";
|
import { useTheme } from "@unsend/ui";
|
||||||
import { EMAIL_COLORS } from "~/lib/constants/colors";
|
|
||||||
import { useColors } from "./hooks/useColors";
|
import { useColors } from "./hooks/useColors";
|
||||||
|
|
||||||
interface EmailChartProps {
|
interface EmailChartProps {
|
||||||
@@ -127,7 +126,10 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
|
|||||||
</p>
|
</p>
|
||||||
{data.delivered ? (
|
{data.delivered ? (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-2.5 h-2.5 bg-[#40a02bcc] dark:bg-[#a6e3a1] rounded-[2px]"></div>
|
<div
|
||||||
|
className="w-2.5 h-2.5 rounded-[2px]"
|
||||||
|
style={{ backgroundColor: currentColors.delivered }}
|
||||||
|
></div>
|
||||||
<p className="text-xs text-muted-foreground w-[70px]">
|
<p className="text-xs text-muted-foreground w-[70px]">
|
||||||
Delivered
|
Delivered
|
||||||
</p>
|
</p>
|
||||||
@@ -136,7 +138,10 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
{data.bounced ? (
|
{data.bounced ? (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-2.5 h-2.5 bg-[#d20f39cc] dark:bg-[#f38ba8] rounded-[2px]"></div>
|
<div
|
||||||
|
className="w-2.5 h-2.5 rounded-[2px]"
|
||||||
|
style={{ backgroundColor: currentColors.bounced }}
|
||||||
|
></div>
|
||||||
<p className="text-xs text-muted-foreground w-[70px]">
|
<p className="text-xs text-muted-foreground w-[70px]">
|
||||||
Bounced
|
Bounced
|
||||||
</p>
|
</p>
|
||||||
@@ -145,7 +150,12 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
{data.complained ? (
|
{data.complained ? (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-2.5 h-2.5 bg-[#df8e1dcc] dark:bg-[#F9E2AF] rounded-[2px]"></div>
|
<div
|
||||||
|
className="w-2.5 h-2.5 rounded-[2px]"
|
||||||
|
style={{
|
||||||
|
backgroundColor: currentColors.complained,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
<p className="text-xs text-muted-foreground w-[70px]">
|
<p className="text-xs text-muted-foreground w-[70px]">
|
||||||
Complained
|
Complained
|
||||||
</p>
|
</p>
|
||||||
@@ -154,7 +164,10 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
{data.opened ? (
|
{data.opened ? (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-2.5 h-2.5 bg-[#8839efcc] dark:bg-[#cba6f7] rounded-[2px]"></div>
|
<div
|
||||||
|
className="w-2.5 h-2.5 rounded-[2px]"
|
||||||
|
style={{ backgroundColor: currentColors.opened }}
|
||||||
|
></div>
|
||||||
<p className="text-xs text-muted-foreground w-[70px]">
|
<p className="text-xs text-muted-foreground w-[70px]">
|
||||||
Opened
|
Opened
|
||||||
</p>
|
</p>
|
||||||
@@ -163,7 +176,10 @@ export default function EmailChart({ days, domain }: EmailChartProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
{data.clicked ? (
|
{data.clicked ? (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-2.5 h-2.5 bg-[#04a5e5cc] dark:bg-[#93c5fd] rounded-[2px]"></div>
|
<div
|
||||||
|
className="w-2.5 h-2.5 rounded-[2px]"
|
||||||
|
style={{ backgroundColor: currentColors.clicked }}
|
||||||
|
></div>
|
||||||
<p className="text-xs text-muted-foreground w-[70px]">
|
<p className="text-xs text-muted-foreground w-[70px]">
|
||||||
Clicked
|
Clicked
|
||||||
</p>
|
</p>
|
||||||
@@ -236,16 +252,34 @@ const EmailChartItem: React.FC<DashboardItemCardProps> = ({
|
|||||||
count,
|
count,
|
||||||
percentage,
|
percentage,
|
||||||
}) => {
|
}) => {
|
||||||
const color = EMAIL_COLORS[status];
|
const currentColors = useColors();
|
||||||
|
|
||||||
|
const getColorForStatus = (status: EmailStatus | "total"): string => {
|
||||||
|
switch (status) {
|
||||||
|
case "DELIVERED":
|
||||||
|
return currentColors.delivered;
|
||||||
|
case "BOUNCED":
|
||||||
|
return currentColors.bounced;
|
||||||
|
case "COMPLAINED":
|
||||||
|
return currentColors.complained;
|
||||||
|
case "OPENED":
|
||||||
|
return currentColors.opened;
|
||||||
|
case "CLICKED":
|
||||||
|
return currentColors.clicked;
|
||||||
|
case "total":
|
||||||
|
default:
|
||||||
|
return "#6b7280"; // gray-500 for total and other statuses
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-3 items-stretch font-mono">
|
<div className="flex gap-3 items-stretch font-mono">
|
||||||
{/* <div className={`${color} w-0.5 rounded-full`}></div> */}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className=" flex items-center gap-2">
|
<div className=" flex items-center gap-2">
|
||||||
<div className={`${color} w-2.5 h-2.5 rounded-[3px]`}></div>
|
<div
|
||||||
{/* <div className={`${color} w-0.5 rounded-full`}></div> */}
|
className="w-2.5 h-2.5 rounded-[3px]"
|
||||||
|
style={{ backgroundColor: getColorForStatus(status) }}
|
||||||
|
></div>
|
||||||
|
|
||||||
<div className="text-xs uppercase text-muted-foreground ">
|
<div className="text-xs uppercase text-muted-foreground ">
|
||||||
{status.toLowerCase()}
|
{status.toLowerCase()}
|
||||||
|
@@ -62,7 +62,7 @@ export default function AddApiKey() {
|
|||||||
setApiKey(data);
|
setApiKey(data);
|
||||||
apiKeyForm.reset();
|
apiKeyForm.reset();
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ export default function AddApiKey() {
|
|||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
>
|
>
|
||||||
{isCopied ? (
|
{isCopied ? (
|
||||||
<CheckIcon className="h-4 w-4 text-green-500" />
|
<CheckIcon className="h-4 w-4 text-green" />
|
||||||
) : (
|
) : (
|
||||||
<ClipboardCopy className="h-4 w-4" />
|
<ClipboardCopy className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
|
@@ -62,7 +62,7 @@ export const DeleteApiKey: React.FC<{
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
toast.success(`API key deleted`);
|
toast.success(`API key deleted`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ export const DeleteApiKey: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Trash2 className="h-4 w-4 text-red-600/80" />
|
<Trash2 className="h-4 w-4 text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -42,7 +42,7 @@ export default function DomainItemPage({
|
|||||||
{
|
{
|
||||||
refetchInterval: (q) => (q?.state.data?.isVerifying ? 10000 : false),
|
refetchInterval: (q) => (q?.state.data?.isVerifying ? 10000 : false),
|
||||||
refetchIntervalInBackground: true,
|
refetchIntervalInBackground: true,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const verifyQuery = api.domain.startVerification.useMutation();
|
const verifyQuery = api.domain.startVerification.useMutation();
|
||||||
@@ -54,7 +54,7 @@ export default function DomainItemPage({
|
|||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
domainQuery.refetch();
|
domainQuery.refetch();
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ const DomainSettings: React.FC<{ domain: Domain }> = ({ domain }) => {
|
|||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const [clickTracking, setClickTracking] = React.useState(
|
const [clickTracking, setClickTracking] = React.useState(
|
||||||
domain.clickTracking,
|
domain.clickTracking
|
||||||
);
|
);
|
||||||
const [openTracking, setOpenTracking] = React.useState(domain.openTracking);
|
const [openTracking, setOpenTracking] = React.useState(domain.openTracking);
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ const DomainSettings: React.FC<{ domain: Domain }> = ({ domain }) => {
|
|||||||
utils.domain.invalidate();
|
utils.domain.invalidate();
|
||||||
toast.success("Click tracking updated");
|
toast.success("Click tracking updated");
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ const DomainSettings: React.FC<{ domain: Domain }> = ({ domain }) => {
|
|||||||
utils.domain.invalidate();
|
utils.domain.invalidate();
|
||||||
toast.success("Open tracking updated");
|
toast.success("Open tracking updated");
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -303,24 +303,20 @@ const DomainSettings: React.FC<{ domain: Domain }> = ({ domain }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DnsVerificationStatus: React.FC<{ status: string }> = ({ status }) => {
|
const DnsVerificationStatus: React.FC<{ status: string }> = ({ status }) => {
|
||||||
let badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10"; // Default color
|
let badgeColor = "bg-gray/10 text-gray border-gray/10"; // Default color
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DomainStatus.SUCCESS:
|
case DomainStatus.SUCCESS:
|
||||||
badgeColor =
|
badgeColor = "bg-green/15 text-green border border-green/25";
|
||||||
"bg-green-500/15 dark:bg-green-600/10 text-green-700 dark:text-green-600/90 border border-green-500/25 dark:border-green-700/25";
|
|
||||||
break;
|
break;
|
||||||
case DomainStatus.FAILED:
|
case DomainStatus.FAILED:
|
||||||
badgeColor =
|
badgeColor = "bg-red/10 text-red border border-red/10";
|
||||||
"bg-red-500/10 text-red-600 dark:text-red-700/90 border border-red-600/10";
|
|
||||||
break;
|
break;
|
||||||
case DomainStatus.TEMPORARY_FAILURE:
|
case DomainStatus.TEMPORARY_FAILURE:
|
||||||
case DomainStatus.PENDING:
|
case DomainStatus.PENDING:
|
||||||
badgeColor =
|
badgeColor = "bg-yellow/20 text-yellow border border-yellow/10";
|
||||||
"bg-yellow-500/20 dark:bg-yellow-500/10 text-yellow-600 border border-yellow-600/10";
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
badgeColor =
|
badgeColor = "bg-gray/10 text-gray border border-gray/20";
|
||||||
"bg-gray-200/70 dark:bg-gray-400/10 text-gray-600 dark:text-gray-400 border border-gray-300 dark:border-gray-400/20";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -3,24 +3,20 @@ import { DomainStatus } from "@prisma/client";
|
|||||||
export const DomainStatusBadge: React.FC<{ status: DomainStatus }> = ({
|
export const DomainStatusBadge: React.FC<{ status: DomainStatus }> = ({
|
||||||
status,
|
status,
|
||||||
}) => {
|
}) => {
|
||||||
let badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10"; // Default color
|
let badgeColor = "bg-gray/10 text-gray border-gray/10"; // Default color
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DomainStatus.SUCCESS:
|
case DomainStatus.SUCCESS:
|
||||||
badgeColor =
|
badgeColor = "bg-green/15 text-green border border-green/25";
|
||||||
"bg-green-500/15 dark:bg-green-600/10 text-green-700 dark:text-green-600/90 border border-green-500/25 dark:border-green-700/25";
|
|
||||||
break;
|
break;
|
||||||
case DomainStatus.FAILED:
|
case DomainStatus.FAILED:
|
||||||
badgeColor =
|
badgeColor = "bg-red/10 text-red border border-red/10";
|
||||||
"bg-red-500/10 text-red-600 dark:text-red-700/90 border border-red-600/10";
|
|
||||||
break;
|
break;
|
||||||
case DomainStatus.TEMPORARY_FAILURE:
|
case DomainStatus.TEMPORARY_FAILURE:
|
||||||
case DomainStatus.PENDING:
|
case DomainStatus.PENDING:
|
||||||
badgeColor =
|
badgeColor = "bg-yellow/20 text-yellow border border-yellow/10";
|
||||||
"bg-yellow-500/20 dark:bg-yellow-500/10 text-yellow-600 border border-yellow-600/10";
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
badgeColor =
|
badgeColor = "bg-gray/70 text-gray border border-gray/20";
|
||||||
"bg-gray-200/70 dark:bg-gray-400/10 text-gray-600 dark:text-gray-400 border border-gray-300 dark:border-gray-400/20";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -3,23 +3,23 @@ import { DomainStatus } from "@prisma/client";
|
|||||||
export const StatusIndicator: React.FC<{ status: DomainStatus }> = ({
|
export const StatusIndicator: React.FC<{ status: DomainStatus }> = ({
|
||||||
status,
|
status,
|
||||||
}) => {
|
}) => {
|
||||||
let badgeColor = "bg-gray-400"; // Default color
|
let badgeColor = "bg-gray"; // Default color
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DomainStatus.NOT_STARTED:
|
case DomainStatus.NOT_STARTED:
|
||||||
badgeColor = "bg-gray-400";
|
badgeColor = "bg-gray";
|
||||||
break;
|
break;
|
||||||
case DomainStatus.SUCCESS:
|
case DomainStatus.SUCCESS:
|
||||||
badgeColor = "bg-emerald-500";
|
badgeColor = "bg-green";
|
||||||
break;
|
break;
|
||||||
case DomainStatus.FAILED:
|
case DomainStatus.FAILED:
|
||||||
badgeColor = "bg-red-500";
|
badgeColor = "bg-red";
|
||||||
break;
|
break;
|
||||||
case DomainStatus.TEMPORARY_FAILURE:
|
case DomainStatus.TEMPORARY_FAILURE:
|
||||||
case DomainStatus.PENDING:
|
case DomainStatus.PENDING:
|
||||||
badgeColor = "bg-yellow-500";
|
badgeColor = "bg-yellow";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
badgeColor = "bg-gray-400";
|
badgeColor = "bg-gray";
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={` w-[2px] ${badgeColor} my-1.5 rounded-full`}></div>;
|
return <div className={` w-[2px] ${badgeColor} my-1.5 rounded-full`}></div>;
|
||||||
|
@@ -64,7 +64,7 @@ export const CancelEmail: React.FC<{
|
|||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
toast.error(`Error cancelling email: ${e.message}`);
|
toast.error(`Error cancelling email: ${e.message}`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export const CancelEmail: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Trash2 className="h-4 w-4 text-red-500" />
|
<Trash2 className="h-4 w-4 text-red" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -46,12 +46,12 @@ import { SheetTitle, SheetDescription } from "@unsend/ui/src/sheet";
|
|||||||
/* Stupid hydrating error. And I so stupid to understand the stupid NextJS docs */
|
/* Stupid hydrating error. And I so stupid to understand the stupid NextJS docs */
|
||||||
const DynamicSheetWithNoSSR = dynamic(
|
const DynamicSheetWithNoSSR = dynamic(
|
||||||
() => import("@unsend/ui/src/sheet").then((mod) => mod.Sheet),
|
() => import("@unsend/ui/src/sheet").then((mod) => mod.Sheet),
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const DynamicSheetContentWithNoSSR = dynamic(
|
const DynamicSheetContentWithNoSSR = dynamic(
|
||||||
() => import("@unsend/ui/src/sheet").then((mod) => mod.SheetContent),
|
() => import("@unsend/ui/src/sheet").then((mod) => mod.SheetContent),
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function EmailsList() {
|
export default function EmailsList() {
|
||||||
@@ -231,7 +231,7 @@ export default function EmailsList() {
|
|||||||
Scheduled at{" "}
|
Scheduled at{" "}
|
||||||
{formatDate(
|
{formatDate(
|
||||||
email.scheduledAt,
|
email.scheduledAt,
|
||||||
"MMM dd'th', hh:mm a"
|
"MMM dd'th', hh:mm a",
|
||||||
)}
|
)}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -247,7 +247,7 @@ export default function EmailsList() {
|
|||||||
{email.latestStatus !== "SCHEDULED"
|
{email.latestStatus !== "SCHEDULED"
|
||||||
? formatDate(
|
? formatDate(
|
||||||
email.scheduledAt ?? email.createdAt,
|
email.scheduledAt ?? email.createdAt,
|
||||||
"MMM do, hh:mm a"
|
"MMM do, hh:mm a",
|
||||||
)
|
)
|
||||||
: "--"}
|
: "--"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -301,39 +301,39 @@ const EmailIcon: React.FC<{ status: EmailStatus }> = ({ status }) => {
|
|||||||
case "SENT":
|
case "SENT":
|
||||||
return (
|
return (
|
||||||
// <div className="border border-gray-400/60 p-2 rounded-lg bg-gray-400/10">
|
// <div className="border border-gray-400/60 p-2 rounded-lg bg-gray-400/10">
|
||||||
<Mail className="w-6 h-6 text-gray-500 " />
|
<Mail className="w-6 h-6 text-gray" />
|
||||||
// </div>
|
// </div>
|
||||||
);
|
);
|
||||||
case "DELIVERED":
|
case "DELIVERED":
|
||||||
return (
|
return (
|
||||||
// <div className="border border-emerald-600/60 p-2 rounded-lg bg-emerald-500/10">
|
// <div className="border border-emerald-600/60 p-2 rounded-lg bg-emerald-500/10">
|
||||||
<MailCheck className="w-6 h-6 text-emerald-800" />
|
<MailCheck className="w-6 h-6 text-green" />
|
||||||
// </div>
|
// </div>
|
||||||
);
|
);
|
||||||
case "BOUNCED":
|
case "BOUNCED":
|
||||||
case "FAILED":
|
case "FAILED":
|
||||||
return (
|
return (
|
||||||
// <div className="border border-red-600/60 p-2 rounded-lg bg-red-500/10">
|
// <div className="border border-red-600/60 p-2 rounded-lg bg-red-500/10">
|
||||||
<MailX className="w-6 h-6 text-red-900" />
|
<MailX className="w-6 h-6 text-red" />
|
||||||
// </div>
|
// </div>
|
||||||
);
|
);
|
||||||
case "CLICKED":
|
case "CLICKED":
|
||||||
return (
|
return (
|
||||||
// <div className="border border-cyan-600/60 p-2 rounded-lg bg-cyan-500/10">
|
// <div className="border border-cyan-600/60 p-2 rounded-lg bg-cyan-500/10">
|
||||||
<MailSearch className="w-6 h-6 text-cyan-700" />
|
<MailSearch className="w-6 h-6 text-blue" />
|
||||||
// </div>
|
// </div>
|
||||||
);
|
);
|
||||||
case "OPENED":
|
case "OPENED":
|
||||||
return (
|
return (
|
||||||
// <div className="border border-indigo-600/60 p-2 rounded-lg bg-indigo-500/10">
|
// <div className="border border-indigo-600/60 p-2 rounded-lg bg-indigo-500/10">
|
||||||
<MailOpen className="w-6 h-6 text-indigo-700" />
|
<MailOpen className="w-6 h-6 text-purple" />
|
||||||
// </div>
|
// </div>
|
||||||
);
|
);
|
||||||
case "DELIVERY_DELAYED":
|
case "DELIVERY_DELAYED":
|
||||||
case "COMPLAINED":
|
case "COMPLAINED":
|
||||||
return (
|
return (
|
||||||
// <div className="border border-yellow-600/60 p-2 rounded-lg bg-yellow-500/10">
|
// <div className="border border-yellow-600/60 p-2 rounded-lg bg-yellow-500/10">
|
||||||
<MailWarning className="w-6 h-6 text-yellow-700" />
|
<MailWarning className="w-6 h-6 text-yellow" />
|
||||||
// </div>
|
// </div>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
@@ -37,7 +37,7 @@ function VerifySuccess() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" />
|
<CheckCircle2 className="h-4 w-4 text-green flex-shrink-0" />
|
||||||
<p>Your account has been upgraded to the paid plan.</p>
|
<p>Your account has been upgraded to the paid plan.</p>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/settings/billing" className="mt-8">
|
<Link href="/settings/billing" className="mt-8">
|
||||||
|
@@ -36,7 +36,7 @@ export const DeleteTeamInvite: React.FC<{
|
|||||||
onError: async (error) => {
|
onError: async (error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export const DeleteTeamInvite: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Trash2 className="h-4 w-4 text-red-600/80" />
|
<Trash2 className="h-4 w-4 text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -38,7 +38,7 @@ export const DeleteTeamMember: React.FC<{
|
|||||||
onError: async (error) => {
|
onError: async (error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +50,9 @@ export const DeleteTeamMember: React.FC<{
|
|||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
{self ? (
|
{self ? (
|
||||||
<LogOut className="h-4 w-4 text-red-600/80" />
|
<LogOut className="h-4 w-4 text-red/80" />
|
||||||
) : (
|
) : (
|
||||||
<Trash2 className="h-4 w-4 text-red-600/80" />
|
<Trash2 className="h-4 w-4 text-red/80" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
@@ -67,7 +67,7 @@ export default function TeamMembersList() {
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="text-center w-[100px] rounded capitalize py-1 text-xs bg-green-500/15 dark:bg-green-600/10 text-green-700 dark:text-green-600/90 border border-green-500/25 dark:border-green-700/25">
|
<div className="text-center w-[100px] rounded capitalize py-1 text-xs bg-green/15 text-green border border-green/25">
|
||||||
Active
|
Active
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -122,7 +122,7 @@ export default function TeamMembersList() {
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="text-center w-[100px] rounded capitalize py-1 text-xs bg-yellow-500/15 dark:bg-yellow-600/10 text-yellow-700 dark:text-yellow-600/90 border border-yellow-500/25 dark:border-yellow-700/25">
|
<div className="text-center w-[100px] rounded capitalize py-1 text-xs bg-yellow/15 text-yellow border border-yellow/25">
|
||||||
Pending
|
Pending
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@@ -29,7 +29,7 @@ export default function EditTemplatePage({
|
|||||||
{ templateId: templateId },
|
{ templateId: templateId },
|
||||||
{
|
{
|
||||||
enabled: !!templateId,
|
enabled: !!templateId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -63,7 +63,7 @@ function TemplateEditor({
|
|||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const [json, setJson] = useState<Record<string, any> | undefined>(
|
const [json, setJson] = useState<Record<string, any> | undefined>(
|
||||||
template.content ? JSON.parse(template.content) : undefined
|
template.content ? JSON.parse(template.content) : undefined,
|
||||||
);
|
);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [name, setName] = useState(template.name);
|
const [name, setName] = useState(template.name);
|
||||||
@@ -86,13 +86,13 @@ function TemplateEditor({
|
|||||||
|
|
||||||
const deboucedUpdateTemplate = useDebouncedCallback(
|
const deboucedUpdateTemplate = useDebouncedCallback(
|
||||||
updateEditorContent,
|
updateEditorContent,
|
||||||
1000
|
1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFileChange = async (file: File) => {
|
const handleFileChange = async (file: File) => {
|
||||||
if (file.size > IMAGE_SIZE_LIMIT) {
|
if (file.size > IMAGE_SIZE_LIMIT) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`File should be less than ${IMAGE_SIZE_LIMIT / 1024 / 1024}MB`
|
`File should be less than ${IMAGE_SIZE_LIMIT / 1024 / 1024}MB`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ function TemplateEditor({
|
|||||||
toast.error(`${e.message}. Reverting changes.`);
|
toast.error(`${e.message}. Reverting changes.`);
|
||||||
setName(template.name);
|
setName(template.name);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -152,9 +152,9 @@ function TemplateEditor({
|
|||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||||||
{isSaving ? (
|
{isSaving ? (
|
||||||
<div className="h-2 w-2 bg-yellow-500 rounded-full" />
|
<div className="h-2 w-2 bg-yellow rounded-full" />
|
||||||
) : (
|
) : (
|
||||||
<div className="h-2 w-2 bg-emerald-500 rounded-full" />
|
<div className="h-2 w-2 bg-green rounded-full" />
|
||||||
)}
|
)}
|
||||||
{formatDistanceToNow(template.updatedAt) === "less than a minute"
|
{formatDistanceToNow(template.updatedAt) === "less than a minute"
|
||||||
? "just now"
|
? "just now"
|
||||||
@@ -188,7 +188,7 @@ function TemplateEditor({
|
|||||||
toast.error(`${e.message}. Reverting changes.`);
|
toast.error(`${e.message}. Reverting changes.`);
|
||||||
setSubject(template.subject);
|
setSubject(template.subject);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="mt-1 py-1 text-sm block w-full outline-none border-b border-transparent focus:border-border bg-transparent"
|
className="mt-1 py-1 text-sm block w-full outline-none border-b border-transparent focus:border-border bg-transparent"
|
||||||
|
@@ -62,7 +62,7 @@ export const DeleteTemplate: React.FC<{
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
toast.success(`Template deleted`);
|
toast.success(`Template deleted`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ export const DeleteTemplate: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
||||||
<Trash2 className="h-[18px] w-[18px] text-red-600/80" />
|
<Trash2 className="h-[18px] w-[18px] text-red/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -35,7 +35,7 @@ export const DuplicateTemplate: React.FC<{
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
toast.success(`Template duplicated`);
|
toast.success(`Template duplicated`);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export const DuplicateTemplate: React.FC<{
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
<Button variant="ghost" size="sm" className="p-0 hover:bg-transparent">
|
||||||
<Copy className="h-[18px] w-[18px] text-blue-600/80" />
|
<Copy className="h-[18px] w-[18px] text-blue/80" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@@ -36,7 +36,7 @@ export const PlanDetails = () => {
|
|||||||
<ul className="mt-4 space-y-3">
|
<ul className="mt-4 space-y-3">
|
||||||
{perks.map((perk, index) => (
|
{perks.map((perk, index) => (
|
||||||
<li key={index} className="flex items-center gap-2">
|
<li key={index} className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" />
|
<CheckCircle2 className="h-4 w-4 text-green flex-shrink-0" />
|
||||||
<span className="text-sm">{perk}</span>
|
<span className="text-sm">{perk}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@@ -52,7 +52,7 @@ export const UpgradeModal = () => {
|
|||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{basicPlanPerks.map((perk, index) => (
|
{basicPlanPerks.map((perk, index) => (
|
||||||
<li key={index} className="flex items-start gap-2">
|
<li key={index} className="flex items-start gap-2">
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0 mt-0.5" />
|
<CheckCircle2 className="h-4 w-4 text-green flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-sm">{perk}</span>
|
<span className="text-sm">{perk}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
Reference in New Issue
Block a user