Skip to content

Commit b99d83a

Browse files
committed
feat: style email and fix content
1 parent 195be8c commit b99d83a

File tree

2 files changed

+243
-64
lines changed

2 files changed

+243
-64
lines changed

src/components/MainPage.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ import TestStatementButton from './test/TestButton';
1212

1313
const MainPage: React.FC = () => {
1414
const { data } = useEntries();
15-
const { username, managerEmail, entries } = data;
15+
const { username, managerName, managerEmail, entries } = data;
1616
const [isWizardOpen, setIsWizardOpen] = React.useState(false);
1717
const [isShareModalOpen, setIsShareModalOpen] = React.useState(false);
1818

1919
// Determine if email button should be disabled:
2020
const hasManagerEmail = managerEmail && managerEmail.trim().length > 0;
21+
// Include all public statements (including archived ones)
2122
const publicStatementsCount = entries.filter(
22-
(entry) => entry.isPublic && !entry.isResolved
23+
(entry) => entry.isPublic
2324
).length;
25+
26+
// Email button should be disabled if no manager email or no public statements
2427
const isEmailDisabled = !hasManagerEmail || publicStatementsCount === 0;
2528

2629
// Handler to open the wizard for creating a new statement from scratch
@@ -36,7 +39,10 @@ const MainPage: React.FC = () => {
3639
return (
3740
<main className='min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 py-12'>
3841
<h1 className='text-3xl font-bold mb-8 text-center'>
39-
Statement builder for {username}
42+
{managerName
43+
? `${username} would like to share with ${managerName}`
44+
: `${username}'s statements for sharing`
45+
}
4046
</h1>
4147
<div className='container mx-auto px-4'>
4248
<StatementList username={username} />
@@ -58,9 +64,11 @@ const MainPage: React.FC = () => {
5864
</span>
5965
</TooltipTrigger>
6066
<TooltipContent className='bg-gray-800 text-white p-2 rounded'>
61-
{isEmailDisabled
62-
? "Please add your line manager's email and ensure you have public statements to share."
63-
: 'Send email to your line manager with your public statements.'}
67+
{!hasManagerEmail
68+
? "Please add your manager's email address first."
69+
: publicStatementsCount === 0
70+
? "Please create at least one public statement to share."
71+
: 'Send email with your public statements.'}
6472
</TooltipContent>
6573
</Tooltip>
6674
{/* Create Your Own Statement Button */}

src/components/ShareEmailModal.tsx

Lines changed: 229 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,253 @@
11
'use client';
22

3-
import React from 'react';
3+
import React, { useState } from 'react';
44
import {
55
Dialog,
66
DialogContent,
7-
DialogTitle,
87
DialogDescription,
9-
DialogFooter,
108
} from '../components/ui/dialog';
119
import { Button } from '../components/ui/button';
1210
import { useEntries } from '../hooks/useEntries';
11+
import { sendEmail } from '../api/emailApi';
12+
import { Email } from '../../types/emails';
13+
import { Loader2 } from 'lucide-react';
1314

1415
const ShareEmailModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
1516
const { data } = useEntries();
16-
const managerEmail = data.managerEmail;
17-
// Only include public statements that are not resolved.
17+
const { username, managerName, managerEmail } = data;
18+
const [isSending, setIsSending] = useState(false);
19+
const [sendError, setSendError] = useState<string | null>(null);
20+
const [sendSuccess, setSendSuccess] = useState(false);
21+
22+
// Only include public statements that are not resolved
1823
const publicStatements = data.entries.filter(
1924
(entry) => entry.isPublic && !entry.isResolved
2025
);
2126

27+
const generateEmailHtml = () => {
28+
let html = `
29+
<h1 style="color: #333; font-size: 24px; margin-bottom: 20px;">Statements from ${username}</h1>
30+
<p style="font-size: 16px; margin-bottom: 30px;">
31+
${username} would like to share the following statements with you:
32+
</p>
33+
`;
34+
35+
publicStatements.forEach((entry) => {
36+
html += `
37+
<div style="margin-bottom: 25px; padding: 15px; border-left: 4px solid #ff69b4; background-color: #f9f9f9;">
38+
<p style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">
39+
${entry.input}
40+
</p>
41+
`;
42+
43+
// Add actions if they exist
44+
if (entry.actions && entry.actions.length > 0) {
45+
const pendingActions = entry.actions.filter(
46+
(action) => !action.completed
47+
);
48+
49+
if (pendingActions.length > 0) {
50+
html += `<div style="margin-top: 10px;">
51+
<p style="font-weight: bold; color: #555; font-size: 14px;">Actions:</p>
52+
<ul style="padding-left: 20px;">`;
53+
54+
pendingActions.forEach((action) => {
55+
html += `
56+
<li style="margin-bottom: 5px;">
57+
<div style="font-size: 14px;">
58+
${action.action}
59+
${
60+
action.byDate
61+
? `<span style="color: #888; font-size: 12px;"> (Due: ${action.byDate})</span>`
62+
: ''
63+
}
64+
</div>
65+
</li>
66+
`;
67+
});
68+
69+
html += `</ul></div>`;
70+
}
71+
}
72+
73+
html += `</div>`;
74+
});
75+
76+
html += `
77+
<p style="font-size: 14px; color: #666; margin-top: 30px; border-top: 1px solid #eee; padding-top: 20px;">
78+
This email was sent from the Lift application.
79+
</p>
80+
`;
81+
82+
return html;
83+
};
84+
85+
const handleSendEmail = async () => {
86+
try {
87+
setIsSending(true);
88+
setSendError(null);
89+
90+
if (!managerEmail || managerEmail.trim() === '') {
91+
throw new Error(
92+
'Manager email is not set. Please set a manager email first.'
93+
);
94+
}
95+
96+
const emailPayload: Email = {
97+
from: '[email protected]', // This should be your app's email
98+
to: [managerEmail],
99+
subject: `${username} would like to share statements with you`,
100+
html: generateEmailHtml(),
101+
};
102+
103+
await sendEmail(emailPayload);
104+
setSendSuccess(true);
105+
106+
// Automatically close after successful send
107+
setTimeout(() => onClose(), 2000);
108+
} catch (error) {
109+
console.error('Failed to send email:', error);
110+
setSendError(
111+
typeof error === 'object' && error !== null && 'message' in error
112+
? (error as Error).message
113+
: 'Failed to send email. Please try again later.'
114+
);
115+
} finally {
116+
setIsSending(false);
117+
}
118+
};
119+
22120
return (
23121
<Dialog open onOpenChange={onClose}>
24-
<DialogContent className='sm:max-w-[600px] w-full p-4'>
25-
<DialogTitle className='text-lg font-bold'>
26-
Sharing with: {managerEmail || 'No manager email set'}
27-
</DialogTitle>
28-
<DialogDescription className='mt-2'>
29-
Below are your public, unresolved statements and their pending
30-
actions:
31-
</DialogDescription>
32-
<div className='mt-4 space-y-4'>
33-
{publicStatements.length > 0 ? (
34-
publicStatements.map((entry) => (
35-
<div
36-
key={entry.id}
37-
className='p-4 border rounded bg-white shadow-sm'
38-
>
39-
<p className='text-base font-semibold'>{entry.input}</p>
40-
{entry.actions && entry.actions.length > 0 && (
41-
<div className='mt-2 space-y-2'>
42-
{entry.actions
43-
.filter((action) => !action.completed)
44-
.map((action) => (
45-
<div
46-
key={action.id}
47-
className='pl-4 border-l-2 border-gray-300'
48-
>
49-
<p className='text-sm'>{action.action}</p>
50-
{action.byDate && action.byDate.trim() !== '' && (
51-
<p className='text-xs text-gray-500'>
52-
Due: {action.byDate}
53-
</p>
54-
)}
55-
</div>
56-
))}
57-
</div>
58-
)}
59-
</div>
60-
))
61-
) : (
62-
<p className='text-gray-600'>
63-
No public unresolved statements available.
122+
<DialogContent
123+
headerTitle={`Sharing with ${managerName || 'your manager'}`}
124+
>
125+
<div
126+
className='relative rounded-md overflow-hidden'
127+
style={{
128+
border: '8px solid transparent',
129+
borderImage:
130+
'repeating-linear-gradient(45deg, #ff69b4, #ff69b4 10px, #4169e1 10px, #4169e1 20px) 8',
131+
padding: '16px',
132+
margin: '-8px',
133+
background: '#f9f9f9',
134+
}}
135+
>
136+
{/* Postal stamp decoration */}
137+
<div className='absolute top-4 right-4 w-16 h-20 border-2 border-gray-400 border-dashed rounded-sm opacity-20 flex items-center justify-center pointer-events-none'>
138+
<div className='w-12 h-12 border-2 border-gray-500 rounded-full flex items-center justify-center'>
139+
<span className='transform rotate-12 text-gray-500 text-[10px] font-mono'>
140+
MAIL
141+
</span>
142+
</div>
143+
</div>
144+
145+
<DialogDescription className='mt-0 text-center'>
146+
Below are your public, unresolved statements
147+
</DialogDescription>
148+
149+
<div className='text-center mb-6'>
150+
<p className='text-sm text-gray-500 mt-1'>
151+
Email will be sent to: {managerEmail || 'No email set'}
64152
</p>
153+
{sendSuccess && (
154+
<p className='text-green-600 text-sm font-medium mt-2 bg-green-50 py-1 px-2 rounded-full inline-block'>
155+
✓ Email sent successfully!
156+
</p>
157+
)}
158+
</div>
159+
160+
{sendError && (
161+
<div className='bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4'>
162+
{sendError}
163+
</div>
65164
)}
165+
166+
<div className='mt-4 space-y-5 max-h-[50vh] overflow-y-auto px-1'>
167+
{publicStatements.length > 0 ? (
168+
publicStatements.map((entry) => (
169+
<div
170+
key={entry.id}
171+
className='p-4 border bg-white shadow-sm rounded-sm relative'
172+
style={{
173+
backgroundImage:
174+
'linear-gradient(0deg, rgba(255, 105, 180, 0.05) 1px, transparent 1px)',
175+
backgroundSize: '100% 20px',
176+
borderColor: '#e5e5e5',
177+
}}
178+
>
179+
<p className='text-base font-semibold'>{entry.input}</p>
180+
{entry.actions && entry.actions.length > 0 && (
181+
<div className='mt-3 space-y-2'>
182+
<div className='text-xs uppercase tracking-wider text-gray-500 font-semibold border-b border-gray-200 pb-1 mb-2'>
183+
Actions
184+
</div>
185+
{entry.actions
186+
.filter((action) => !action.completed)
187+
.map((action) => (
188+
<div
189+
key={action.id}
190+
className='pl-3 border-l-2 border-pink-200 ml-2 py-1'
191+
>
192+
<p className='text-sm'>{action.action}</p>
193+
{action.byDate && action.byDate.trim() !== '' && (
194+
<p className='text-xs text-gray-500 italic'>
195+
Due by: {action.byDate}
196+
</p>
197+
)}
198+
</div>
199+
))}
200+
</div>
201+
)}
202+
<div className='absolute top-1 right-2 opacity-20'>
203+
<svg
204+
width='24'
205+
height='24'
206+
viewBox='0 0 24 24'
207+
fill='none'
208+
xmlns='http://www.w3.org/2000/svg'
209+
>
210+
<path
211+
d='M21 14H3M21 4H3M21 9H3M21 19H3'
212+
stroke='#ff69b4'
213+
strokeWidth='2'
214+
strokeLinecap='round'
215+
strokeLinejoin='round'
216+
/>
217+
</svg>
218+
</div>
219+
</div>
220+
))
221+
) : (
222+
<div className='text-gray-600 text-center p-8 bg-white border border-gray-200 rounded-sm'>
223+
No public unresolved statements available.
224+
</div>
225+
)}
226+
</div>
227+
228+
<div className='mt-6 flex justify-center gap-4'>
229+
<Button
230+
variant='pink'
231+
onClick={handleSendEmail}
232+
disabled={
233+
isSending || publicStatements.length === 0 || !managerEmail
234+
}
235+
className='px-6'
236+
>
237+
{isSending ? (
238+
<>
239+
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
240+
Sending...
241+
</>
242+
) : (
243+
'Send Email'
244+
)}
245+
</Button>
246+
<Button variant='pink' onClick={onClose} className='px-6'>
247+
Close
248+
</Button>
249+
</div>
66250
</div>
67-
<DialogFooter className='mt-4 flex justify-end space-x-4'>
68-
<Button
69-
variant='pink'
70-
onClick={() => {
71-
/* placeholder for Send */
72-
}}
73-
>
74-
Send
75-
</Button>
76-
<Button variant='pink' onClick={onClose}>
77-
Close
78-
</Button>
79-
</DialogFooter>
80251
</DialogContent>
81252
</Dialog>
82253
);

0 commit comments

Comments
 (0)