|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import React from 'react'; |
| 3 | +import React, { useState } from 'react'; |
4 | 4 | import { |
5 | 5 | Dialog, |
6 | 6 | DialogContent, |
7 | | - DialogTitle, |
8 | 7 | DialogDescription, |
9 | | - DialogFooter, |
10 | 8 | } from '../components/ui/dialog'; |
11 | 9 | import { Button } from '../components/ui/button'; |
12 | 10 | import { useEntries } from '../hooks/useEntries'; |
| 11 | +import { sendEmail } from '../api/emailApi'; |
| 12 | +import { Email } from '../../types/emails'; |
| 13 | +import { Loader2 } from 'lucide-react'; |
13 | 14 |
|
14 | 15 | const ShareEmailModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { |
15 | 16 | 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 |
18 | 23 | const publicStatements = data.entries.filter( |
19 | 24 | (entry) => entry.isPublic && !entry.isResolved |
20 | 25 | ); |
21 | 26 |
|
| 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 | + |
22 | 120 | return ( |
23 | 121 | <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'} |
64 | 152 | </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> |
65 | 164 | )} |
| 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> |
66 | 250 | </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> |
80 | 251 | </DialogContent> |
81 | 252 | </Dialog> |
82 | 253 | ); |
|
0 commit comments