initial commit. gotta go
This commit is contained in:
@@ -1,42 +1,42 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { api } from "~/trpc/react";
|
||||
import { Separator } from "@usesend/ui/src/separator";
|
||||
import { EmailStatusBadge, EmailStatusIcon } from "./email-status-badge";
|
||||
import { formatDate } from "date-fns";
|
||||
import { motion } from "framer-motion";
|
||||
import { EmailStatus } from "@prisma/client";
|
||||
import { JsonValue } from "@prisma/client/runtime/library";
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { api } from '~/trpc/react';
|
||||
import { Separator } from '@usesend/ui/src/separator';
|
||||
import { EmailStatusBadge, EmailStatusIcon } from './email-status-badge';
|
||||
import { formatDate } from 'date-fns';
|
||||
import { motion } from 'framer-motion';
|
||||
import { EmailStatus } from '@prisma/client';
|
||||
import { JsonValue } from '@prisma/client/runtime/library';
|
||||
import {
|
||||
SesBounce,
|
||||
SesClick,
|
||||
SesComplaint,
|
||||
SesDeliveryDelay,
|
||||
SesOpen,
|
||||
} from "~/types/aws-types";
|
||||
} from '~/types/aws-types';
|
||||
import {
|
||||
BOUNCE_ERROR_MESSAGES,
|
||||
COMPLAINT_ERROR_MESSAGES,
|
||||
DELIVERY_DELAY_ERRORS,
|
||||
} from "~/lib/constants/ses-errors";
|
||||
import CancelEmail from "./cancel-email";
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
} from '~/lib/constants/ses-errors';
|
||||
import CancelEmail from './cancel-email';
|
||||
import { useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function EmailDetails({ emailId }: { emailId: string }) {
|
||||
const emailQuery = api.email.getEmail.useQuery({ id: emailId });
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-auto px-4 no-scrollbar">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="no-scrollbar h-full overflow-auto px-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<h1 className="font-bold">{emailQuery.data?.to}</h1>
|
||||
<EmailStatusBadge status={emailQuery.data?.latestStatus ?? "SENT"} />
|
||||
<EmailStatusBadge status={emailQuery.data?.latestStatus ?? 'SENT'} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col mt-8 items-start gap-8">
|
||||
<div className="p-2 rounded-lg border flex flex-col gap-2 w-full shadow">
|
||||
<div className="mt-8 flex flex-col items-start gap-8">
|
||||
<div className="flex w-full flex-col gap-2 rounded-lg border p-2 shadow">
|
||||
{/* <div className="flex gap-2">
|
||||
<span className="w-[100px] text-muted-foreground text-sm">
|
||||
From
|
||||
@@ -59,23 +59,23 @@ export default function EmailDetails({ emailId }: { emailId: string }) {
|
||||
{/* <div className=" text-[15px] font-medium">
|
||||
{emailQuery.data?.to}
|
||||
</div> */}
|
||||
<div className=" text-sm">Subject: {emailQuery.data?.subject}</div>
|
||||
<div className="text-sm">Subject: {emailQuery.data?.subject}</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
From: {emailQuery.data?.from}
|
||||
</div>
|
||||
</div>
|
||||
{emailQuery.data?.latestStatus === "SCHEDULED" &&
|
||||
{emailQuery.data?.latestStatus === 'SCHEDULED' &&
|
||||
emailQuery.data?.scheduledAt ? (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex gap-2 items-center px-4">
|
||||
<span className="w-[100px] text-muted-foreground text-sm ">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
<span className="text-muted-foreground w-[100px] text-sm">
|
||||
Scheduled at
|
||||
</span>
|
||||
<span className="text-sm">
|
||||
{formatDate(
|
||||
emailQuery.data?.scheduledAt,
|
||||
"MMM dd'th', hh:mm a"
|
||||
"MMM dd'th', hh:mm a",
|
||||
)}
|
||||
</span>
|
||||
<div className="ml-4">
|
||||
@@ -90,32 +90,32 @@ export default function EmailDetails({ emailId }: { emailId: string }) {
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2, delay: 0.3 }}
|
||||
>
|
||||
<EmailPreview html={emailQuery.data?.html ?? ""} />
|
||||
<EmailPreview html={emailQuery.data?.html ?? ''} />
|
||||
</motion.div>
|
||||
</div>
|
||||
{emailQuery.data?.latestStatus !== "SCHEDULED" ? (
|
||||
<div className=" border rounded-lg w-full shadow mb-2 ">
|
||||
<div className=" p-4 flex flex-col gap-8 w-full">
|
||||
{emailQuery.data?.latestStatus !== 'SCHEDULED' ? (
|
||||
<div className="mb-2 w-full rounded-lg border shadow">
|
||||
<div className="flex w-full flex-col gap-8 p-4">
|
||||
<div className="font-medium">Events History</div>
|
||||
<div className="flex items-stretch px-4 w-full">
|
||||
<div className="border-r border-gray-300 dark:border-gray-700 border-dashed" />
|
||||
<div className="flex flex-col gap-12 w-full">
|
||||
<div className="flex w-full items-stretch px-4">
|
||||
<div className="border-r border-dashed border-gray-300 dark:border-gray-700" />
|
||||
<div className="flex w-full flex-col gap-12">
|
||||
{emailQuery.data?.emailEvents.map((evt) => (
|
||||
<div
|
||||
key={evt.status}
|
||||
className="flex gap-5 items-start w-full"
|
||||
className="flex w-full items-start gap-5"
|
||||
>
|
||||
<div className=" -ml-2.5">
|
||||
<div className="-ml-2.5">
|
||||
<EmailStatusIcon status={evt.status} />
|
||||
</div>
|
||||
<div className="-mt-[0.125rem] w-full">
|
||||
<div className=" capitalize font-medium">
|
||||
<div className="font-medium capitalize">
|
||||
<EmailStatusBadge status={evt.status} />
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-2">
|
||||
{formatDate(evt.createdAt, "MMM dd, hh:mm a")}
|
||||
<div className="text-muted-foreground mt-2 text-xs">
|
||||
{formatDate(evt.createdAt, 'MMM dd, hh:mm a')}
|
||||
</div>
|
||||
<div className="mt-1 text-foreground/80">
|
||||
<div className="text-foreground/80 mt-1">
|
||||
<EmailStatusText
|
||||
status={evt.status}
|
||||
data={evt.data}
|
||||
@@ -147,14 +147,14 @@ const EmailPreview = ({ html }: { html: string }) => {
|
||||
|
||||
if (!show) {
|
||||
return (
|
||||
<div className="dark:bg-slate-200 h-[350px] overflow-visible rounded border-t"></div>
|
||||
<div className="h-[350px] overflow-visible rounded border-t dark:bg-slate-200"></div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dark:bg-slate-200 h-[350px] overflow-visible rounded border-t">
|
||||
<div className="h-[350px] overflow-visible rounded border-t dark:bg-slate-200">
|
||||
<iframe
|
||||
className="w-full h-full"
|
||||
className="h-full w-full"
|
||||
srcDoc={html}
|
||||
sandbox="allow-same-origin"
|
||||
/>
|
||||
@@ -169,106 +169,106 @@ const EmailStatusText = ({
|
||||
status: EmailStatus;
|
||||
data: JsonValue;
|
||||
}) => {
|
||||
if (status === "SENT") {
|
||||
if (status === 'SENT') {
|
||||
return (
|
||||
<div>
|
||||
We received your request and sent the email to recipient's server.
|
||||
</div>
|
||||
);
|
||||
} else if (status === "DELIVERED") {
|
||||
} else if (status === 'DELIVERED') {
|
||||
return <div>Mail is successfully delivered to the recipient.</div>;
|
||||
} else if (status === "DELIVERY_DELAYED") {
|
||||
} else if (status === 'DELIVERY_DELAYED') {
|
||||
const _errorData = data as unknown as SesDeliveryDelay;
|
||||
const errorMessage = DELIVERY_DELAY_ERRORS[_errorData.delayType];
|
||||
|
||||
return <div>{errorMessage}</div>;
|
||||
} else if (status === "BOUNCED") {
|
||||
} else if (status === 'BOUNCED') {
|
||||
const _errorData = data as unknown as SesBounce;
|
||||
_errorData.bounceType;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<p>{getErrorMessage(_errorData)}</p>
|
||||
<div className="rounded-xl p-4 bg-muted/30 flex flex-col gap-4">
|
||||
<div className="flex gap-2 w-full">
|
||||
<div className="bg-muted/30 flex flex-col gap-4 rounded-xl p-4">
|
||||
<div className="flex w-full gap-2">
|
||||
<div className="w-1/2">
|
||||
<p className="text-sm text-muted-foreground">Type</p>
|
||||
<p className="text-muted-foreground text-sm">Type</p>
|
||||
<p>{_errorData.bounceType}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Sub Type</p>
|
||||
<p className="text-muted-foreground text-sm">Sub Type</p>
|
||||
<p>{_errorData.bounceSubType}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">SMTP response</p>
|
||||
<p className="text-muted-foreground text-sm">SMTP response</p>
|
||||
<p>{_errorData.bouncedRecipients[0]?.diagnosticCode}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (status === "FAILED") {
|
||||
} else if (status === 'FAILED') {
|
||||
const _errorData = data as unknown as { error: string };
|
||||
return <div>{_errorData.error}</div>;
|
||||
} else if (status === "OPENED") {
|
||||
} else if (status === 'OPENED') {
|
||||
const _data = data as unknown as SesOpen;
|
||||
const userAgent = getUserAgent(_data.userAgent);
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-xl p-4 bg-muted/30 mt-4">
|
||||
<div className="flex w-full ">
|
||||
<div className="bg-muted/30 mt-4 w-full rounded-xl p-4">
|
||||
<div className="flex w-full">
|
||||
{userAgent.os.name ? (
|
||||
<div className="w-1/2">
|
||||
<p className="text-sm text-muted-foreground">OS</p>
|
||||
<p className="text-muted-foreground text-sm">OS</p>
|
||||
<p>{userAgent.os.name}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{userAgent.browser.name ? (
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Browser</p>
|
||||
<p className="text-muted-foreground text-sm">Browser</p>
|
||||
<p>{userAgent.browser.name}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (status === "CLICKED") {
|
||||
} else if (status === 'CLICKED') {
|
||||
const _data = data as unknown as SesClick;
|
||||
const userAgent = getUserAgent(_data.userAgent);
|
||||
|
||||
return (
|
||||
<div className="w-full mt-4 flex flex-col gap-4 rounded-xl p-4 bg-muted/30">
|
||||
<div className="flex w-full ">
|
||||
<div className="bg-muted/30 mt-4 flex w-full flex-col gap-4 rounded-xl p-4">
|
||||
<div className="flex w-full">
|
||||
{userAgent.os.name ? (
|
||||
<div className="w-1/2">
|
||||
<p className="text-sm text-muted-foreground">OS </p>
|
||||
<p className="text-muted-foreground text-sm">OS </p>
|
||||
<p>{userAgent.os.name}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{userAgent.browser.name ? (
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Browser </p>
|
||||
<p className="text-muted-foreground text-sm">Browser </p>
|
||||
<p>{userAgent.browser.name}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<p className="text-sm text-muted-foreground">URL</p>
|
||||
<p className="text-muted-foreground text-sm">URL</p>
|
||||
<p>{_data.link}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (status === "COMPLAINED") {
|
||||
} else if (status === 'COMPLAINED') {
|
||||
const _errorData = data as unknown as SesComplaint;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<p>{getComplaintMessage(_errorData.complaintFeedbackType)}</p>
|
||||
</div>
|
||||
);
|
||||
} else if (status === "CANCELLED") {
|
||||
} else if (status === 'CANCELLED') {
|
||||
return <div>This scheduled email was cancelled</div>;
|
||||
} else if (status === "SUPPRESSED") {
|
||||
} else if (status === 'SUPPRESSED') {
|
||||
return (
|
||||
<div>
|
||||
This email was suppressed because this email is previously either
|
||||
@@ -281,24 +281,24 @@ const EmailStatusText = ({
|
||||
};
|
||||
|
||||
const getErrorMessage = (data: SesBounce) => {
|
||||
if (data.bounceType === "Permanent") {
|
||||
if (data.bounceType === 'Permanent') {
|
||||
return BOUNCE_ERROR_MESSAGES[data.bounceType][
|
||||
data.bounceSubType as
|
||||
| "General"
|
||||
| "NoEmail"
|
||||
| "Suppressed"
|
||||
| "OnAccountSuppressionList"
|
||||
| 'General'
|
||||
| 'NoEmail'
|
||||
| 'Suppressed'
|
||||
| 'OnAccountSuppressionList'
|
||||
];
|
||||
} else if (data.bounceType === "Transient") {
|
||||
} else if (data.bounceType === 'Transient') {
|
||||
return BOUNCE_ERROR_MESSAGES[data.bounceType][
|
||||
data.bounceSubType as
|
||||
| "General"
|
||||
| "MailboxFull"
|
||||
| "MessageTooLarge"
|
||||
| "ContentRejected"
|
||||
| "AttachmentRejected"
|
||||
| 'General'
|
||||
| 'MailboxFull'
|
||||
| 'MessageTooLarge'
|
||||
| 'ContentRejected'
|
||||
| 'AttachmentRejected'
|
||||
];
|
||||
} else if (data.bounceType === "Undetermined") {
|
||||
} else if (data.bounceType === 'Undetermined') {
|
||||
return BOUNCE_ERROR_MESSAGES.Undetermined;
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user