Skip to content

Commit 1b267d7

Browse files
committed
packaging fix v3
1 parent 32ea3df commit 1b267d7

16 files changed

+4530
-2723
lines changed

app/css/globals.css

+24
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,27 @@
206206
background-color: #dcdcdc; /* Disabled state background */
207207
cursor: not-allowed; /* Change cursor to indicate disabled state */
208208
}
209+
210+
@keyframes blink {
211+
0%,
212+
100% {
213+
opacity: 0;
214+
}
215+
50% {
216+
opacity: 1;
217+
}
218+
}
219+
220+
.dot {
221+
animation: blink 1.4s infinite both;
222+
}
223+
224+
.dot:nth-child(1) {
225+
animation-delay: 0s;
226+
}
227+
.dot:nth-child(2) {
228+
animation-delay: 0.2s;
229+
}
230+
.dot:nth-child(3) {
231+
animation-delay: 0.4s;
232+
}

components/campaign-form.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ function CampaignForm() {
7474
}, [searchTerm, contacts]);
7575

7676
const handleSubmit = async (e: any) => {
77-
console.log('selectedContacts', selectedContacts);
7877
e.preventDefault();
7978
setIsLoading(true);
8079

@@ -238,7 +237,6 @@ function CampaignForm() {
238237
)}
239238

240239
{errorMessage && <div className="text-red-500">{errorMessage}</div>}
241-
242240
{successMessage ? (
243241
<div className="w-full max-w-8xl mx-auto">
244242
{campaignId >= 0 && <EmailEventsDisplay campaignId={campaignId} />}

components/campaign-list.tsx

+15-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ function ContactList({ searchTerm }: { searchTerm: string }) {
115115
Created At
116116
</th>
117117
<th className="px-6 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">
118-
Template ID
118+
Template
119+
</th>
120+
<th className="px-6 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">
121+
View
119122
</th>
120123
</tr>
121124
</thead>
@@ -136,7 +139,17 @@ function ContactList({ searchTerm }: { searchTerm: string }) {
136139
{new Date(campaign.createdAt).toLocaleTimeString()}
137140
</td>
138141
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-700">
139-
{campaign.templateId}
142+
{campaign.template.name}
143+
</td>
144+
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-700">
145+
<a
146+
href={`/campaign/${campaign.id}`}
147+
target="_blank"
148+
rel="noopener noreferrer"
149+
className="text-blue-500 hover:text-blue-800"
150+
>
151+
link
152+
</a>
140153
</td>
141154
</tr>
142155
</React.Fragment>

components/contact-list.tsx

+24-2
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,32 @@ function ContactList({ searchTerm }: { searchTerm: string }) {
159159
{contact.jobTitle ? contact.jobTitle : 'N/A'}
160160
</td>
161161
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-700">
162-
{contact.companyWebsite ? contact.companyWebsite : 'N/A'}
162+
{contact.companyWebsite ? (
163+
<a
164+
href={contact.companyWebsite}
165+
target="_blank"
166+
rel="noopener noreferrer"
167+
className="text-blue-500 hover:text-blue-800"
168+
>
169+
{contact.companyWebsite}
170+
</a>
171+
) : (
172+
'N/A'
173+
)}
163174
</td>
164175
<td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-700">
165-
{contact.linkedIn ? contact.linkedIn : 'N/A'}
176+
{contact.linkedIn ? (
177+
<a
178+
href={contact.linkedIn}
179+
target="_blank"
180+
rel="noopener noreferrer"
181+
className="text-blue-500 hover:text-blue-800"
182+
>
183+
link
184+
</a>
185+
) : (
186+
'N/A'
187+
)}
166188
</td>
167189
</tr>
168190
</React.Fragment>

components/dot.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React, { useState, useEffect } from 'react';
2+
3+
const Dot = ({ delay }) => {
4+
const [isVisible, setIsVisible] = useState(false);
5+
6+
useEffect(() => {
7+
const intervalId = setInterval(() => {
8+
setIsVisible((prev) => !prev);
9+
}, delay);
10+
11+
return () => clearInterval(intervalId);
12+
}, [delay]);
13+
14+
return (
15+
<span
16+
className={`h-3 w-3 bg-blue-500 rounded-full mr-1 transition-opacity duration-500 ${
17+
isVisible ? 'opacity-100' : 'opacity-0'
18+
}`}
19+
></span>
20+
);
21+
};
22+
23+
export default Dot;

components/email-event-display.tsx

+99-40
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React, { useState, useEffect } from 'react';
2+
import '../app/css/globals.css';
3+
import Dot from './dot';
24

35
interface Contact {
46
email: string;
@@ -22,6 +24,10 @@ interface EmailEventsDisplayProps {
2224
campaignId: number;
2325
}
2426

27+
interface CopiedStatuses {
28+
[key: number]: boolean;
29+
}
30+
2531
const EmailEventCard: React.FC<EmailEventCardProps> = ({
2632
event,
2733
onSend,
@@ -51,7 +57,7 @@ const EmailEventCard: React.FC<EmailEventCardProps> = ({
5157
</div>
5258
<div className="text-right">
5359
<button
54-
onClick={handleCopy}
60+
onClick={() => navigator.clipboard.writeText(event.eventContent)}
5561
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-4 rounded mr-2"
5662
>
5763
Copy
@@ -75,40 +81,39 @@ const EmailEventsDisplay: React.FC<EmailEventsDisplayProps> = ({
7581
const [campaign, setCampaign] = useState<any>({});
7682
const [stopFetch, setStopFetch] = useState(false);
7783
useEffect(() => {
78-
7984
const fetchCampaign = async () => {
80-
setIsLoading(true);
81-
try {
82-
const response = await fetch(
83-
`/api/campaigns?campaignId=${campaignId}`
84-
);
85-
if (!response.ok) throw new Error('Failed to fetch email events.');
86-
const data = await response.json();
87-
if(data.length > 0) {
88-
setCampaign(data[0]);
89-
}
90-
} catch (error) {
91-
console.error('Error fetching email events:', error);
92-
} finally {
93-
setIsLoading(false);
85+
setIsLoading(true);
86+
try {
87+
const response = await fetch(`/api/campaigns?campaignId=${campaignId}`);
88+
if (!response.ok) throw new Error('Failed to fetch email events.');
89+
const data = await response.json();
90+
if (data.length > 0) {
91+
setCampaign(data[0]);
9492
}
95-
};
93+
} catch (error) {
94+
console.error('Error fetching email events:', error);
95+
} finally {
96+
setIsLoading(false);
97+
}
98+
};
9699

97100
const fetchEmailEvents = async () => {
101+
if (stopFetch) {
102+
return;
103+
}
98104
setIsLoading(true);
99105
try {
100-
if(stopFetch) {
101-
return;
102-
}
103106
const response = await fetch(
104107
`/api/email-events?campaignId=${campaignId}`
105108
);
106109
if (!response.ok) throw new Error('Failed to fetch email events.');
107110
const data = await response.json();
108111
setEmailEvents(data);
109-
if(campaign.numUsers === emailEvents.length) {
112+
if (campaign.numUsers === emailEvents.length) {
110113
setStopFetch(true);
111114
}
115+
console.log('emailEvents.length:', emailEvents.length);
116+
console.log('campaign.numUsers:', campaign.numUsers);
112117
} catch (error) {
113118
console.error('Error fetching email events:', error);
114119
} finally {
@@ -125,11 +130,38 @@ const EmailEventsDisplay: React.FC<EmailEventsDisplayProps> = ({
125130

126131
const handleSendEmail = (event: EmailEvent) => {
127132
console.log('Sending email:', event);
128-
// Implement sending email logic here
129133
};
130134

131-
const handleCopyEmailContent = (event: EmailEvent) => {
132-
console.log('Email content copied:', event);
135+
const [copiedStatuses, setCopiedStatuses] = useState<CopiedStatuses>({});
136+
137+
// const handleCopy = (event: EmailEvent) => {
138+
// navigator.clipboard.writeText(event.eventContent)
139+
// .then(() => {
140+
// setCopied(true); // Set copied to true when the text is successfully copied
141+
// setTimeout(() => setCopied(false), 2000); // Reset copied status after 2 seconds
142+
// })
143+
// .catch(error => console.error('Copy failed', error));
144+
// };
145+
146+
const handleCopy = (eventId: number, event: EmailEvent) => {
147+
navigator.clipboard
148+
.writeText(event.eventContent)
149+
.then(() => {
150+
// Set the copied status for the specific event ID to true
151+
setCopiedStatuses((prevStatuses) => ({
152+
...prevStatuses,
153+
[eventId]: true
154+
}));
155+
156+
// Optional: Reset copied status for this event ID after 2 seconds
157+
setTimeout(() => {
158+
setCopiedStatuses((prevStatuses) => ({
159+
...prevStatuses,
160+
[eventId]: false
161+
}));
162+
}, 2000);
163+
})
164+
.catch((error) => console.error('Copy failed', error));
133165
};
134166

135167
if (isLoading)
@@ -141,29 +173,56 @@ const EmailEventsDisplay: React.FC<EmailEventsDisplayProps> = ({
141173
if (!emailEvents.length) {
142174
return (
143175
<div className="text-xl flex justify-center items-center font-semibold text-blue-500 mb-2 mt-2">
144-
<div className="animate-spin rounded-full h-12 w-12 border-t-4 border-b-4 border-blue-500"></div>
145-
Generating Emails...
176+
<div className="text-xl flex justify-center items-center font-semibold text-blue-500 mb-2 mt-2">
177+
Generating Emails
178+
<div className="flex ml-2">
179+
<Dot delay={300} />
180+
<Dot delay={600} />
181+
<Dot delay={900} />
182+
</div>
183+
</div>
146184
</div>
147185
);
148186
}
149187
return (
150188
<div className="max-w-4xl mx-auto mt-2">
151-
<div className="text-xl flex justify-center items-center font-semibold text-blue-500 mb-2">
152-
<div className="animate-spin rounded-full h-12 w-12 border-t-4 border-b-4 border-blue-500"></div>
153-
Generated Emails
154-
<div className="mt-2 grid grid-cols-4 text-sm">
155-
<div className="flex justify-start items-justify-start">
156-
<div className="loader"></div>
189+
{emailEvents.map((event) => (
190+
<div className="bg-white p-4 shadow rounded-lg mb-4">
191+
<div className="mb-2 text-gray-900">
192+
<strong>To:</strong> {event.contact.email}
193+
</div>
194+
<div className="mb-2 text-gray-600">
195+
<strong>Subject:</strong> {event.eventType}
196+
</div>
197+
<div className="mb-4 text-gray-700">
198+
<strong>Message:</strong>
199+
<div
200+
dangerouslySetInnerHTML={{
201+
__html: event.eventContent.replace(/\n\n/g, '<br /><br />')
202+
}}
203+
></div>
204+
</div>
205+
<div className="text-right">
206+
<button
207+
type="button"
208+
onClick={() => handleCopy(event.id, event)}
209+
className={`bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-4 rounded mr-2 ${
210+
copiedStatuses[event.id]
211+
? 'bg-green-500 hover:bg-green-600'
212+
: ''
213+
}`}
214+
>
215+
{copiedStatuses[event.id] ? 'Copied' : 'Copy'}
216+
</button>
217+
<button
218+
type="button"
219+
onClick={() => handleSendEmail(event)}
220+
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-4 rounded"
221+
>
222+
Send
223+
</button>
157224
</div>
158225
</div>
159-
</div>
160-
{emailEvents.map((event) => (
161-
<EmailEventCard
162-
key={event.id}
163-
event={event}
164-
onSend={handleSendEmail}
165-
onCopy={handleCopyEmailContent}
166-
/>
167226
))}
168227
</div>
169228
);

components/template-form.tsx

+31-1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ function TemplateForm() {
9999
const [selectedTemplate, setSelectedTemplate] = useState(
100100
'The Direct Approach'
101101
);
102+
const [minimumWords, setMinimumWords] = useState(100);
103+
const [maximumWords, setMaximumWords] = useState(200);
102104

103105
// Update email content when the selected template changes
104106
const handleTemplateChange = (e: any) => {
@@ -159,7 +161,9 @@ function TemplateForm() {
159161
name,
160162
campaignType,
161163
content: emailContent,
162-
tone
164+
tone,
165+
minWords: minimumWords,
166+
maxWords: maximumWords
163167
})
164168
});
165169
if (!response.ok) throw new Error('Network response was not ok.');
@@ -244,6 +248,32 @@ function TemplateForm() {
244248
<option value="informative">Informative</option>
245249
</select>
246250
</div>
251+
252+
<div className="w-full px-2 mb-4">
253+
<label className="block text-gray-700 font-bold mb-1 mt-1">
254+
Minimum Words
255+
</label>
256+
<input
257+
type="number"
258+
value={minimumWords}
259+
onChange={(e) => setMinimumWords(parseInt(e.target.value))}
260+
className="w-full border rounded-lg p-3 text-gray-700"
261+
required
262+
/>
263+
</div>
264+
265+
<div className="w-full px-2 mb-4">
266+
<label className="block text-gray-700 font-bold mb-1 mt-1">
267+
Maximum Words
268+
</label>
269+
<input
270+
type="number"
271+
value={maximumWords}
272+
onChange={(e) => setMaximumWords(parseInt(e.target.value))}
273+
className="w-full border rounded-lg p-3 text-gray-700"
274+
required
275+
/>
276+
</div>
247277
</div>
248278

249279
{campaignType === 'email' && (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "Template" ADD COLUMN "minWords" INTEGER;

0 commit comments

Comments
 (0)