Skip to content

Commit 4c8232f

Browse files
committed
Remove MatchingSendPage and dedupe sending
1 parent 8ccb236 commit 4c8232f

26 files changed

Lines changed: 276 additions & 1026 deletions
File renamed without changes.

js/src/app/router/router.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,27 @@ import { RequireAuth } from "@/app/router/guards/RequireAuth";
66
import AdminPage from "@/features/admin/Admin.page";
77
import AdminLoginPage from "@/features/admin/AdminLogin.page";
88
import EmailAdminPage from "@/features/emails/EmailAdminPage";
9+
import { EmailHistoryDetailPage } from "@/features/emails/EmailHistoryDetailPage";
10+
import { EmailHistoryPage } from "@/features/emails/EmailHistoryPage";
11+
import { EmailProgressFallbackPage } from "@/features/emails/EmailProgressFallbackPage";
12+
import { EmailProgressPage } from "@/features/emails/EmailProgressPage";
13+
import { EmailSendPage } from "@/features/emails/EmailSendPage";
914
import HomePage from "@/features/home/Home.page";
1015
import SamplePage from "@/features/sample/Sample.page";
1116
import SampleAdminPage from "@/features/sample/SampleAdmin.page";
1217
import { SignUpPage } from "@/features/sign-up/SignUp.page";
13-
import { createBrowserRouter } from "react-router-dom";
18+
import { Navigate, createBrowserRouter } from "react-router-dom";
19+
20+
function createEmailAdminTabRoutes() {
21+
return [
22+
{ index: true, element: <Navigate to="send" replace /> },
23+
{ path: "send", element: <EmailSendPage /> },
24+
{ path: "progress", element: <EmailProgressFallbackPage /> },
25+
{ path: "progress/:requestId", element: <EmailProgressPage /> },
26+
{ path: "history", element: <EmailHistoryPage /> },
27+
{ path: "history/:requestId", element: <EmailHistoryDetailPage /> },
28+
];
29+
}
1430

1531
export const router = createBrowserRouter([
1632
// Public admin login: its page owns the full viewport.
@@ -24,8 +40,12 @@ export const router = createBrowserRouter([
2440
{ path: "sign-up", element: <SignUpPage /> },
2541
],
2642
},
27-
// Temporary public email admin page for TESTING
28-
{ path: "email", element: <EmailAdminPage /> },
43+
// Temporary public email routes for TESTING (before auth is wired)
44+
{
45+
path: "email",
46+
element: <EmailAdminPage />,
47+
children: createEmailAdminTabRoutes(),
48+
},
2949
// Authenticated: guard -> layout -> page. Admin nests a second guard + layout.
3050
{
3151
element: <RequireAuth />,
@@ -41,7 +61,11 @@ export const router = createBrowserRouter([
4161
element: <AdminLayout />,
4262
children: [
4363
{ path: "admin", element: <AdminPage /> },
44-
{ path: "admin/email", element: <EmailAdminPage /> },
64+
{
65+
path: "admin/email",
66+
element: <EmailAdminPage />,
67+
children: createEmailAdminTabRoutes(),
68+
},
4569
{ path: "sample/admin", element: <SampleAdminPage /> },
4670
],
4771
},
Lines changed: 29 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,47 @@
1-
import { CsvUploader } from "@/features/emails/_components/CsvUploader";
2-
import { EmailHistory } from "@/features/emails/_components/EmailHistory";
3-
import { EmailPreviewer } from "@/features/emails/_components/EmailPreviewer";
4-
import { EmailProgress } from "@/features/emails/_components/EmailProgress";
5-
import { TemplateSelector } from "@/features/emails/_components/TemplateSelector";
6-
import { enqueueEmails, triggerProcess } from "@/features/emails/api/emailAPI";
7-
import {
8-
showEmailSuccess,
9-
showEmailError,
10-
} from "@/features/emails/api/emailError";
11-
import {
12-
type MessagePreview,
13-
type SendAsyncRequest,
14-
type EnqueueEmailRequest,
15-
} from "@/features/emails/dto/emailDto";
16-
import { Box, Flex, Stack, Tabs, Button, Text, Group } from "@mantine/core";
17-
import { useState } from "react";
1+
import { Box, Tabs } from "@mantine/core";
2+
import { Outlet, useLocation, useNavigate } from "react-router-dom";
3+
4+
type EmailAdminTabValue = "send" | "progress" | "history";
5+
6+
function getSelectedTab(pathname: string): EmailAdminTabValue {
7+
if (pathname.includes("/history")) {
8+
return "history";
9+
}
10+
if (pathname.includes("/progress")) {
11+
return "progress";
12+
}
13+
return "send";
14+
}
1815

1916
export default function EmailAdminPage() {
20-
const [selectedTab, setSelectedTab] = useState<string | null>("send");
21-
const [request, setRequest] = useState<SendAsyncRequest | null>(null);
22-
const [previews, setPreviews] = useState<MessagePreview[] | null>(null);
23-
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(
24-
null,
25-
);
26-
const [requestId, setRequestId] = useState<string | null>(null);
27-
const [isSending, setIsSending] = useState(false);
17+
const location = useLocation();
18+
const navigate = useNavigate();
19+
const selectedTab = getSelectedTab(location.pathname);
2820

29-
const handleAsyncSend = async () => {
30-
if (!selectedTemplateId) {
31-
showEmailError("Missing Template", "Please select a template");
21+
const handleTabChange = (value: string | null) => {
22+
if (value === "send") {
23+
navigate("send");
3224
return;
3325
}
34-
if (!request) {
35-
showEmailError("Missing Request", "Please process CSV files first");
26+
if (value === "progress") {
27+
navigate("progress");
3628
return;
3729
}
38-
39-
setIsSending(true);
40-
try {
41-
// Transform the SendRequest to EnqueueEmailRequest
42-
const enqueueRequest: EnqueueEmailRequest = {
43-
templateId: selectedTemplateId,
44-
replyTo: request.replyTo || undefined,
45-
messages: request.messages,
46-
};
47-
48-
const response = await enqueueEmails(enqueueRequest);
49-
showEmailSuccess("Emails Queued", `Accepted ${response.accepted} emails`);
50-
setRequestId(response.requestId);
51-
52-
// Kick the runner to start draining
53-
await triggerProcess();
54-
55-
// Switch to progress view
56-
setSelectedTab("progress");
57-
} catch (err) {
58-
showEmailError(
59-
"Send Failed",
60-
err instanceof Error ? err.message : "Unknown error",
61-
);
62-
} finally {
63-
setIsSending(false);
30+
if (value === "history") {
31+
navigate("history");
6432
}
6533
};
6634

67-
const handleReset = () => {
68-
setRequest(null);
69-
setPreviews(null);
70-
setSelectedTemplateId(null);
71-
setRequestId(null);
72-
setSelectedTab("send");
73-
};
74-
75-
const handleTemplateChange = (templateId: string | null) => {
76-
setSelectedTemplateId(templateId);
77-
setRequest(null);
78-
setPreviews(null);
79-
};
80-
8135
return (
82-
<Tabs value={selectedTab} onChange={setSelectedTab}>
36+
<Tabs value={selectedTab} onChange={handleTabChange}>
8337
<Tabs.List>
8438
<Tabs.Tab value="send">Send Emails</Tabs.Tab>
85-
{requestId && <Tabs.Tab value="progress">Live Progress</Tabs.Tab>}
39+
<Tabs.Tab value="progress">Live Progress</Tabs.Tab>
8640
<Tabs.Tab value="history">History</Tabs.Tab>
8741
</Tabs.List>
88-
<Tabs.Panel value="send" pt="lg">
89-
<Flex align="flex-start" gap="lg" wrap="nowrap">
90-
<Stack w="30%" gap="lg">
91-
{/* Template selector - new for async */}
92-
<TemplateSelector
93-
value={selectedTemplateId}
94-
onChange={handleTemplateChange}
95-
label="Email Template"
96-
/>
97-
{/* CSV uploader */}
98-
<CsvUploader
99-
templateId={selectedTemplateId}
100-
setRequest={setRequest}
101-
/>
102-
{/* Send button */}
103-
<Button
104-
onClick={handleAsyncSend}
105-
disabled={!request || !selectedTemplateId}
106-
loading={isSending}
107-
fullWidth
108-
>
109-
Send Async
110-
</Button>
111-
</Stack>
112-
<Box w="70%">
113-
{/* Preview */}
114-
<EmailPreviewer
115-
previews={previews}
116-
setPreviews={setPreviews}
117-
request={request}
118-
/>
119-
</Box>
120-
</Flex>
121-
</Tabs.Panel>
122-
{requestId && (
123-
<Tabs.Panel value="progress" pt="lg">
124-
<Stack gap="lg">
125-
<Group justify="space-between">
126-
<Text fw={600}>Batch {requestId}</Text>
127-
<Button variant="subtle" onClick={handleReset} size="xs">
128-
New Send
129-
</Button>
130-
</Group>
131-
<EmailProgress requestId={requestId} />
132-
</Stack>
133-
</Tabs.Panel>
134-
)}
135-
<Tabs.Panel value="history" pt="lg">
136-
<EmailHistory />
137-
</Tabs.Panel>
42+
<Box pt="lg">
43+
<Outlet />
44+
</Box>
13845
</Tabs>
13946
);
14047
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { EmailHistory } from "@/features/emails/_components/EmailHistory";
2+
import { useNavigate, useParams } from "react-router-dom";
3+
4+
export function EmailHistoryDetailPage() {
5+
const { requestId } = useParams<{ requestId: string }>();
6+
const navigate = useNavigate();
7+
8+
return (
9+
<EmailHistory
10+
detailRequestId={requestId ?? null}
11+
onBack={() => navigate("../history")}
12+
/>
13+
);
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { EmailHistory } from "@/features/emails/_components/EmailHistory";
2+
import { useNavigate } from "react-router-dom";
3+
4+
export function EmailHistoryPage() {
5+
const navigate = useNavigate();
6+
7+
return <EmailHistory onSelectRequest={(requestId) => navigate(requestId)} />;
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Text } from "@mantine/core";
2+
3+
export function EmailProgressFallbackPage() {
4+
return (
5+
<Text c="dimmed">
6+
No Email Sending Session in progress. Please select a batch from history
7+
</Text>
8+
);
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { EmailProgress } from "@/features/emails/_components/EmailProgress";
2+
import { Stack, Group, Text, Button } from "@mantine/core";
3+
import { useNavigate, useParams } from "react-router-dom";
4+
5+
export function EmailProgressPage() {
6+
const { requestId } = useParams<{ requestId: string }>();
7+
const navigate = useNavigate();
8+
9+
if (!requestId) {
10+
return <Text c="dimmed">Missing request ID.</Text>;
11+
}
12+
13+
return (
14+
<Stack gap="lg">
15+
<Group justify="space-between">
16+
<Text fw={600}>Batch {requestId}</Text>
17+
<Button variant="subtle" onClick={() => navigate("../send")} size="xs">
18+
New Send
19+
</Button>
20+
</Group>
21+
<EmailProgress requestId={requestId} />
22+
</Stack>
23+
);
24+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type {
2+
MessagePreview,
3+
SendAsyncRequest,
4+
} from "@/features/emails/dto/emailDto";
5+
6+
import { CsvUploader } from "@/features/emails/_components/CsvUploader";
7+
import { EmailPreviewer } from "@/features/emails/_components/EmailPreviewer";
8+
import { EmailSender } from "@/features/emails/_components/EmailSender";
9+
import { TemplateSelector } from "@/features/emails/_components/TemplateSelector";
10+
import { Box, Flex, Stack } from "@mantine/core";
11+
import { useState } from "react";
12+
import { useNavigate } from "react-router-dom";
13+
14+
export function EmailSendPage() {
15+
const [request, setRequest] = useState<SendAsyncRequest | null>(null);
16+
const [previews, setPreviews] = useState<MessagePreview[] | null>(null);
17+
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(
18+
null,
19+
);
20+
const [isSending, setIsSending] = useState(false);
21+
const navigate = useNavigate();
22+
23+
const handleTemplateChange = (templateId: string | null) => {
24+
setSelectedTemplateId(templateId);
25+
setRequest(null);
26+
setPreviews(null);
27+
};
28+
29+
return (
30+
<Flex align="flex-start" gap="lg" wrap="nowrap">
31+
<Stack w="30%" gap="lg">
32+
{/* Template selector - new for async */}
33+
<TemplateSelector
34+
value={selectedTemplateId}
35+
onChange={handleTemplateChange}
36+
label="Email Template"
37+
/>
38+
{/* CSV uploader */}
39+
<CsvUploader templateId={selectedTemplateId} setRequest={setRequest} />
40+
{/* Send button */}
41+
<EmailSender
42+
request={request}
43+
selectedTemplateId={selectedTemplateId}
44+
isSending={isSending}
45+
setIsSending={setIsSending}
46+
navigate={navigate}
47+
/>
48+
</Stack>
49+
<Box w="70%">
50+
{/* Preview */}
51+
<EmailPreviewer
52+
previews={previews}
53+
setPreviews={setPreviews}
54+
request={request}
55+
/>
56+
</Box>
57+
</Flex>
58+
);
59+
}

0 commit comments

Comments
 (0)