Skip to content

Commit 328627c

Browse files
committed
reemove template selector
1 parent 2bda14c commit 328627c

3 files changed

Lines changed: 374 additions & 96 deletions

File tree

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
import type {
2+
EmailTemplate,
3+
SendRequest,
4+
MessagePreview,
5+
} from "@/features/emails/dto/emailDto";
6+
7+
import { EmailPreviewer } from "@/features/emails/_components/EmailPreviewer";
8+
import {
9+
listTemplates,
10+
createTemplate,
11+
deleteTemplate,
12+
sendToLegacyPreviewApi,
13+
} from "@/features/emails/api/emailAPI";
14+
import {
15+
showEmailSuccess,
16+
showEmailError,
17+
} from "@/features/emails/api/emailError";
18+
import {
19+
Tabs,
20+
Stack,
21+
Table,
22+
Button,
23+
Modal,
24+
Text,
25+
TextInput,
26+
Textarea,
27+
Group,
28+
ActionIcon,
29+
Tooltip,
30+
Box,
31+
Flex,
32+
} from "@mantine/core";
33+
import { IconTrash, IconPlus } from "@tabler/icons-react";
34+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
35+
import { useState } from "react";
36+
37+
/**
38+
* UNREACHABLE: This component is currently not reachable in the app's UI. It is intended for future implementation in managing email templates.
39+
* TemplateManager component allows users to view, create, and delete email templates.
40+
* It also provides a preview feature to visualize how the email will look with sample data.
41+
*/
42+
export function TemplateManager() {
43+
const queryClient = useQueryClient();
44+
const [showCreateModal, setShowCreateModal] = useState(false);
45+
const [formData, setFormData] = useState({ name: "", subject: "", body: "" });
46+
const [previewData, setPreviewData] = useState<MessagePreview[] | null>(null);
47+
48+
const { data: templates, isLoading } = useQuery<EmailTemplate[]>({
49+
queryKey: ["emailTemplates"],
50+
queryFn: () => listTemplates(),
51+
});
52+
53+
const createMutation = useMutation({
54+
mutationFn: async () => {
55+
return await createTemplate({
56+
name: formData.name,
57+
subject: formData.subject,
58+
body: formData.body,
59+
});
60+
},
61+
onSuccess: () => {
62+
queryClient.invalidateQueries({ queryKey: ["emailTemplates"] });
63+
showEmailSuccess(
64+
"Template Created",
65+
`"${formData.name}" created successfully`,
66+
);
67+
setFormData({ name: "", subject: "", body: "" });
68+
setShowCreateModal(false);
69+
},
70+
onError: (error) => {
71+
showEmailError(
72+
"Creation Failed",
73+
error instanceof Error ? error.message : "Unknown error",
74+
);
75+
},
76+
});
77+
78+
const deleteMutation = useMutation({
79+
mutationFn: async (templateId: string) => {
80+
return await deleteTemplate(templateId);
81+
},
82+
onSuccess: () => {
83+
queryClient.invalidateQueries({ queryKey: ["emailTemplates"] });
84+
showEmailSuccess("Template Deleted", "Template removed successfully");
85+
},
86+
onError: (error) => {
87+
showEmailError(
88+
"Delete Failed",
89+
error instanceof Error ? error.message : "Unknown error",
90+
);
91+
},
92+
});
93+
94+
const handlePreviewTemplate = async () => {
95+
if (!formData.subject || !formData.body) {
96+
showEmailError("Missing Fields", "Subject and body are required");
97+
return;
98+
}
99+
100+
try {
101+
// Create fake messages with sample variables to preview
102+
const request: SendRequest = {
103+
subject: formData.subject,
104+
body: formData.body,
105+
replyTo: null,
106+
messages: [
107+
{
108+
recipients: [
109+
{
110+
email: "sample@example.com",
111+
variableToValue: {
112+
"per1.name": "Alice",
113+
"per1.email": "alice@example.com",
114+
"per2.name": "Bob",
115+
"per2.email": "bob@example.com",
116+
},
117+
},
118+
],
119+
},
120+
],
121+
};
122+
123+
const previews = await sendToLegacyPreviewApi(request);
124+
setPreviewData(previews || []);
125+
} catch (error) {
126+
showEmailError(
127+
"Preview Failed",
128+
error instanceof Error ? error.message : "Unknown error",
129+
);
130+
}
131+
};
132+
133+
const rows = (templates || []).map((template) => (
134+
<Table.Tr key={template.id}>
135+
<Table.Td>
136+
<Text size="sm" fw={600}>
137+
{template.name}
138+
</Text>
139+
</Table.Td>
140+
<Table.Td>
141+
<Text size="xs" c="dimmed">
142+
{new Date(template.createdAt).toLocaleDateString()}
143+
</Text>
144+
</Table.Td>
145+
<Table.Td>
146+
<Tooltip label="Delete template">
147+
<ActionIcon
148+
size="sm"
149+
color="red"
150+
onClick={() => {
151+
if (confirm(`Delete template "${template.name}"?`)) {
152+
deleteMutation.mutate(template.id);
153+
}
154+
}}
155+
loading={deleteMutation.isPending}
156+
>
157+
<IconTrash size={16} />
158+
</ActionIcon>
159+
</Tooltip>
160+
</Table.Td>
161+
</Table.Tr>
162+
));
163+
164+
return (
165+
<Tabs defaultValue="list">
166+
<Tabs.List>
167+
<Tabs.Tab value="list">Templates ({templates?.length || 0})</Tabs.Tab>
168+
<Tabs.Tab value="create">Create New</Tabs.Tab>
169+
</Tabs.List>
170+
<Tabs.Panel value="list" pt="lg">
171+
<Stack gap="lg">
172+
<Group justify="space-between">
173+
<Text fw={600}>Available Templates</Text>
174+
<Button
175+
leftSection={<IconPlus size={16} />}
176+
size="sm"
177+
onClick={() => setShowCreateModal(true)}
178+
>
179+
New Template
180+
</Button>
181+
</Group>
182+
{isLoading ?
183+
<Text>Loading templates...</Text>
184+
: (templates || []).length === 0 ?
185+
<Text c="dimmed">No templates yet. Create one to get started.</Text>
186+
: <Table striped withTableBorder withColumnBorders>
187+
<Table.Thead>
188+
<Table.Tr>
189+
<Table.Th>Name</Table.Th>
190+
<Table.Th>Created</Table.Th>
191+
<Table.Th>Action</Table.Th>
192+
</Table.Tr>
193+
</Table.Thead>
194+
<Table.Tbody>{rows}</Table.Tbody>
195+
</Table>
196+
}
197+
</Stack>
198+
</Tabs.Panel>
199+
<Tabs.Panel value="create" pt="lg">
200+
<Flex gap="lg" align="flex-start">
201+
<Stack w="40%" gap="md">
202+
<Text fw={600}>Create New Template</Text>
203+
<TextInput
204+
label="Template Name"
205+
placeholder="e.g., Monthly Pairing"
206+
value={formData.name}
207+
onChange={(e) =>
208+
setFormData({
209+
...formData,
210+
name: e.currentTarget.value,
211+
})
212+
}
213+
/>
214+
<Textarea
215+
label="Subject (use ${variable} syntax)"
216+
placeholder="e.g., Meet ${per1.name} & ${per2.name}!"
217+
rows={3}
218+
value={formData.subject}
219+
onChange={(e) =>
220+
setFormData({
221+
...formData,
222+
subject: e.currentTarget.value,
223+
})
224+
}
225+
/>
226+
<Textarea
227+
label="Body (use ${variable} syntax)"
228+
placeholder="e.g., Hi ${per1.name}, meet your match ${per2.name}..."
229+
rows={6}
230+
value={formData.body}
231+
onChange={(e) =>
232+
setFormData({
233+
...formData,
234+
body: e.currentTarget.value,
235+
})
236+
}
237+
/>
238+
<Box
239+
p="md"
240+
style={{ backgroundColor: "#f5f5f5", borderRadius: "4px" }}
241+
>
242+
<Text size="xs" fw={600} mb="xs" c="dimmed">
243+
AVAILABLE VARIABLES
244+
</Text>
245+
<Text
246+
size="xs"
247+
style={{ fontFamily: "monospace", lineHeight: 1.6 }}
248+
>
249+
{/* Per-side variables */}
250+
<Text>per1.name, per1.email, per1.bio</Text>
251+
<Text>per1.industry, per1.role, per1.topics</Text>
252+
<Text>per1.linkedUrl</Text>
253+
<Text mt="xs">per2.name, per2.email, per2.bio</Text>
254+
<Text>per2.industry, per2.role, per2.topics</Text>
255+
<Text>per2.linkedUrl</Text>
256+
<Text mt="xs">period, year, (shared vars)</Text>
257+
</Text>
258+
</Box>
259+
<Group>
260+
<Button onClick={handlePreviewTemplate} variant="light" fullWidth>
261+
Preview Template
262+
</Button>
263+
<Button
264+
onClick={() => createMutation.mutate()}
265+
disabled={!formData.name || !formData.subject || !formData.body}
266+
loading={createMutation.isPending}
267+
fullWidth
268+
>
269+
Create Template
270+
</Button>
271+
</Group>
272+
</Stack>
273+
<Box w="60%">
274+
{previewData !== null && previewData.length > 0 && (
275+
<Stack>
276+
<Text fw={600} size="sm">
277+
Preview
278+
</Text>
279+
<EmailPreviewer
280+
previews={previewData}
281+
setPreviews={setPreviewData}
282+
request={null}
283+
/>
284+
</Stack>
285+
)}
286+
</Box>
287+
</Flex>
288+
</Tabs.Panel>
289+
<Modal
290+
opened={showCreateModal}
291+
onClose={() => setShowCreateModal(false)}
292+
title="Quick Create Template"
293+
size="md"
294+
>
295+
<Stack gap="md">
296+
<TextInput
297+
label="Template Name"
298+
placeholder="e.g., Monthly Pairing"
299+
value={formData.name}
300+
onChange={(e) =>
301+
setFormData({
302+
...formData,
303+
name: e.currentTarget.value,
304+
})
305+
}
306+
/>
307+
<Textarea
308+
label="Subject"
309+
placeholder="e.g., Meet ${per1.name} & ${per2.name}!"
310+
rows={2}
311+
value={formData.subject}
312+
onChange={(e) =>
313+
setFormData({
314+
...formData,
315+
subject: e.currentTarget.value,
316+
})
317+
}
318+
/>
319+
<Textarea
320+
label="Body"
321+
placeholder="e.g., Hi ${per1.name}, meet your match..."
322+
rows={4}
323+
value={formData.body}
324+
onChange={(e) =>
325+
setFormData({
326+
...formData,
327+
body: e.currentTarget.value,
328+
})
329+
}
330+
/>
331+
<Group justify="flex-end">
332+
<Button variant="default" onClick={() => setShowCreateModal(false)}>
333+
Cancel
334+
</Button>
335+
<Button
336+
onClick={() => createMutation.mutate()}
337+
disabled={!formData.name || !formData.subject || !formData.body}
338+
loading={createMutation.isPending}
339+
>
340+
Create
341+
</Button>
342+
</Group>
343+
</Stack>
344+
</Modal>
345+
</Tabs>
346+
);
347+
}

0 commit comments

Comments
 (0)