EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
page.tsx
Go to the documentation of this file.
1'use client';
2
3import React, { useState, useEffect } from 'react';
4import Link from 'next/link';
5
6interface MarketingProgram {
7 id: number;
8 name: string;
9 createDt: string;
10 startDate: string;
11 endDate: string;
12 status: 'planning' | 'active' | 'completed' | 'paused';
13 type: string;
14 description?: string;
15}
16
17export default function MarketingCampaignsPage() {
18 const [programs, setPrograms] = useState<MarketingProgram[]>([]);
19 const [loading, setLoading] = useState(true);
20 const [createModal, setCreateModal] = useState(false);
21 const [reviewModal, setReviewModal] = useState(false);
22 const [selectedProgram, setSelectedProgram] = useState<MarketingProgram | null>(null);
23
24 // Form fields
25 const [name, setName] = useState('');
26 const [startDate, setStartDate] = useState('');
27 const [endDate, setEndDate] = useState('');
28 const [programType, setProgramType] = useState('loyalty');
29 const [description, setDescription] = useState('');
30
31 useEffect(() => {
32 loadPrograms();
33 }, []);
34
35 const loadPrograms = async () => {
36 try {
37 const response = await fetch('/api/v1/marketing/programs');
38
39 if (!response.ok || !response.headers.get('content-type')?.includes('application/json')) {
40 // Demo mode
41 setPrograms(getDemoPrograms());
42 setLoading(false);
43 return;
44 }
45
46 const data = await response.json();
47 setPrograms(data.programs || []);
48 setLoading(false);
49 } catch (error) {
50 console.error('Error loading marketing programs:', error);
51 setPrograms(getDemoPrograms());
52 setLoading(false);
53 }
54 };
55
56 const getDemoPrograms = (): MarketingProgram[] => {
57 return [
58 {
59 id: 1,
60 name: 'Summer Sale 2025',
61 createDt: '2025-11-15T10:00:00Z',
62 startDate: '2025-12-01',
63 endDate: '2026-02-28',
64 status: 'active',
65 type: 'promotion',
66 description: 'Seasonal summer promotional campaign targeting new customers',
67 },
68 {
69 id: 2,
70 name: 'VIP Loyalty Club',
71 createDt: '2025-09-01T08:30:00Z',
72 startDate: '2025-10-01',
73 endDate: '2026-09-30',
74 status: 'active',
75 type: 'loyalty',
76 description: 'Year-long loyalty program with tiered benefits',
77 },
78 {
79 id: 3,
80 name: 'Back to School Email Campaign',
81 createDt: '2025-12-10T14:20:00Z',
82 startDate: '2026-01-15',
83 endDate: '2026-02-15',
84 status: 'planning',
85 type: 'email',
86 description: 'Targeted email campaign for school supplies',
87 },
88 ];
89 };
90
91 const getStatusBadgeColor = (status: string) => {
92 switch (status) {
93 case 'active': return 'bg-green-100 text-green-800';
94 case 'planning': return 'bg-blue-100 text-blue-800';
95 case 'completed': return 'bg-gray-100 text-gray-800';
96 case 'paused': return 'bg-yellow-100 text-yellow-800';
97 default: return 'bg-gray-100 text-gray-800';
98 }
99 };
100
101 const getStatusLabel = (status: string) => {
102 switch (status) {
103 case 'active': return 'Active';
104 case 'planning': return 'Planning';
105 case 'completed': return 'Completed';
106 case 'paused': return 'Paused';
107 default: return status;
108 }
109 };
110
111 const handleCreateProgram = async () => {
112 if (!name || !startDate || !endDate) {
113 alert('Please fill in all required fields');
114 return;
115 }
116
117 const newProgram = {
118 name,
119 startDate,
120 endDate,
121 type: programType,
122 description,
123 };
124
125 try {
126 const response = await fetch('/api/v1/marketing/programs', {
127 method: 'POST',
128 headers: { 'Content-Type': 'application/json' },
129 body: JSON.stringify(newProgram),
130 });
131
132 if (!response.ok) {
133 alert('Program created (demo mode)');
134 const demo: MarketingProgram = {
135 id: programs.length + 1,
136 name,
137 createDt: new Date().toISOString(),
138 startDate,
139 endDate,
140 status: 'planning',
141 type: programType,
142 description,
143 };
144 setPrograms([demo, ...programs]);
145 setCreateModal(false);
146 resetForm();
147 return;
148 }
149
150 alert('Marketing program created successfully');
151 loadPrograms();
152 setCreateModal(false);
153 resetForm();
154 } catch (error) {
155 console.error('Error creating program:', error);
156 alert('Program created (demo mode)');
157 setCreateModal(false);
158 resetForm();
159 }
160 };
161
162 const resetForm = () => {
163 setName('');
164 setStartDate('');
165 setEndDate('');
166 setProgramType('loyalty');
167 setDescription('');
168 };
169
170 const handleReviewProgram = (program: MarketingProgram) => {
171 setSelectedProgram(program);
172 setReviewModal(true);
173 };
174
175 const formatDate = (dateString: string): string => {
176 const date = new Date(dateString);
177 return date.toLocaleDateString('en-US', {
178 year: 'numeric',
179 month: 'short',
180 day: 'numeric'
181 });
182 };
183
184 const formatDateTime = (dateString: string): string => {
185 const date = new Date(dateString);
186 return date.toLocaleDateString('en-US', {
187 year: 'numeric',
188 month: 'short',
189 day: 'numeric',
190 hour: '2-digit',
191 minute: '2-digit'
192 });
193 };
194
195 return (
196 <div className="container mx-auto px-4 py-8">
197 <div className="mb-6">
198 <div className="flex items-center gap-4 mb-4">
199 <Link href="/marketing" className="text-blue-600 hover:text-blue-800">
200 ← Back to Marketing
201 </Link>
202 <h1 className="text-3xl font-bold text-gray-900">Marketing Programs</h1>
203 </div>
204
205 <p className="text-gray-600 mb-4">
206 Marketing programs allows you to manage and report on customer and sales promotion efforts.
207 A marketing program is used to track things like loyalty clubs and also the responses to
208 promotional offers sent to a subset of customers.
209 </p>
210 </div>
211
212 {/* Active Programs Summary */}
213 <div className="bg-white rounded-lg shadow-md p-6 mb-6">
214 <div className="flex items-center justify-between">
215 <div>
216 <h2 className="text-xl font-semibold text-gray-900 mb-2">Active Programs</h2>
217 <p className="text-3xl font-bold text-[#00946b]">
218 {programs.filter(p => p.status === 'active').length}
219 </p>
220 <p className="text-sm text-gray-500">Currently running</p>
221 </div>
222 <div className="text-right">
223 <p className="text-sm text-gray-500">Planning</p>
224 <p className="text-2xl font-semibold text-blue-600">
225 {programs.filter(p => p.status === 'planning').length}
226 </p>
227 </div>
228 <div className="text-right">
229 <p className="text-sm text-gray-500">Completed</p>
230 <p className="text-2xl font-semibold text-gray-600">
231 {programs.filter(p => p.status === 'completed').length}
232 </p>
233 </div>
234 </div>
235 </div>
236
237 {/* Action Buttons */}
238 <div className="flex flex-wrap gap-4 mb-6">
239 <button
240 onClick={() => setCreateModal(true)}
241 className="flex items-center gap-3 bg-white border-2 border-[#00946b] rounded-lg shadow-md p-6 hover:shadow-lg transition"
242 >
243 <div className="text-5xl">📋</div>
244 <div className="text-left">
245 <div className="font-semibold text-gray-900">Create New Program</div>
246 <div className="text-sm text-gray-600">Start a new campaign</div>
247 </div>
248 </button>
249
250 <Link
251 href="/marketing/campaigns/analytics"
252 className="flex items-center gap-3 bg-white border-2 border-gray-300 rounded-lg shadow-md p-6 hover:shadow-lg transition opacity-50 cursor-not-allowed"
253 onClick={(e) => { e.preventDefault(); alert('Analytics coming soon'); }}
254 >
255 <div className="text-5xl">📊</div>
256 <div className="text-left">
257 <div className="font-semibold text-gray-900">Campaign Analytics</div>
258 <div className="text-sm text-gray-600">View performance reports</div>
259 </div>
260 </Link>
261 </div>
262
263 {/* Programs Table */}
264 <div className="bg-white rounded-lg shadow-md overflow-hidden">
265 <div className="px-6 py-4 border-b border-gray-200">
266 <h3 className="text-lg font-semibold text-gray-900">
267 All Programs ({programs.length})
268 </h3>
269 </div>
270
271 <div className="overflow-x-auto">
272 <table className="min-w-full divide-y divide-gray-200">
273 <thead className="bg-gray-50">
274 <tr>
275 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
276 Created
277 </th>
278 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
279 Name
280 </th>
281 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
282 Type
283 </th>
284 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
285 Start Date
286 </th>
287 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
288 End Date
289 </th>
290 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
291 Status
292 </th>
293 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
294 Actions
295 </th>
296 </tr>
297 </thead>
298 <tbody className="bg-white divide-y divide-gray-200">
299 {loading ? (
300 <tr>
301 <td colSpan={7} className="px-6 py-8 text-center text-gray-500">
302 Loading programs...
303 </td>
304 </tr>
305 ) : programs.length === 0 ? (
306 <tr>
307 <td colSpan={7} className="px-6 py-8 text-center text-gray-500">
308 No marketing programs found. Create your first program to get started.
309 </td>
310 </tr>
311 ) : (
312 programs.map((program) => (
313 <tr key={program.id} className="hover:bg-gray-50">
314 <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
315 {formatDateTime(program.createDt)}
316 </td>
317 <td className="px-6 py-4">
318 <div className="text-sm font-medium text-gray-900">{program.name}</div>
319 {program.description && (
320 <div className="text-sm text-gray-500 truncate max-w-xs">
321 {program.description}
322 </div>
323 )}
324 </td>
325 <td className="px-6 py-4 whitespace-nowrap">
326 <span className="text-sm text-gray-900 capitalize">{program.type}</span>
327 </td>
328 <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
329 {formatDate(program.startDate)}
330 </td>
331 <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
332 {formatDate(program.endDate)}
333 </td>
334 <td className="px-6 py-4 whitespace-nowrap">
335 <span className={`px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full ${getStatusBadgeColor(program.status)}`}>
336 {getStatusLabel(program.status)}
337 </span>
338 </td>
339 <td className="px-6 py-4 whitespace-nowrap text-sm">
340 <button
341 onClick={() => handleReviewProgram(program)}
342 className="text-blue-600 hover:text-blue-900 font-medium"
343 >
344 Review
345 </button>
346 </td>
347 </tr>
348 ))
349 )}
350 </tbody>
351 </table>
352 </div>
353 </div>
354
355 {/* Create Program Modal */}
356 {createModal && (
357 <div
358 className="modal-overlay"
359 onClick={() => setCreateModal(false)}
360 >
361 <div
362 className="bg-white rounded-lg shadow-xl max-w-2xl w-full"
363 onClick={(e) => e.stopPropagation()}
364 >
365 <div className="border-b border-gray-200 px-6 py-4 flex items-center justify-between">
366 <h2 className="text-xl font-semibold text-gray-900">Create New Marketing Program</h2>
367 <button
368 onClick={() => setCreateModal(false)}
369 className="text-gray-400 hover:text-gray-600 text-2xl font-bold"
370 >
371 ×
372 </button>
373 </div>
374
375 <div className="p-6">
376 <div className="space-y-4">
377 <div>
378 <label className="block text-sm font-medium text-gray-700 mb-1">
379 Program Name <span className="text-red-500">*</span>
380 </label>
381 <input
382 type="text"
383 value={name}
384 onChange={(e) => setName(e.target.value)}
385 placeholder="e.g., Summer Sale 2025"
386 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
387 />
388 <p className="text-xs text-gray-500 mt-1">
389 What short nickname will you internally refer to this program in reports etc
390 </p>
391 </div>
392
393 <div>
394 <label className="block text-sm font-medium text-gray-700 mb-1">
395 Program Type
396 </label>
397 <select
398 value={programType}
399 onChange={(e) => setProgramType(e.target.value)}
400 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
401 >
402 <option value="loyalty">Loyalty Program</option>
403 <option value="promotion">Promotional Campaign</option>
404 <option value="email">Email Campaign</option>
405 <option value="sms">SMS Campaign</option>
406 <option value="event">Event Marketing</option>
407 <option value="other">Other</option>
408 </select>
409 </div>
410
411 <div className="grid grid-cols-2 gap-4">
412 <div>
413 <label className="block text-sm font-medium text-gray-700 mb-1">
414 Start Date <span className="text-red-500">*</span>
415 </label>
416 <input
417 type="date"
418 value={startDate}
419 onChange={(e) => setStartDate(e.target.value)}
420 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
421 />
422 <p className="text-xs text-gray-500 mt-1">
423 When are you planning on starting promotional efforts? This is used in reports to determine any change in sales.
424 </p>
425 </div>
426
427 <div>
428 <label className="block text-sm font-medium text-gray-700 mb-1">
429 End Date <span className="text-red-500">*</span>
430 </label>
431 <input
432 type="date"
433 value={endDate}
434 onChange={(e) => setEndDate(e.target.value)}
435 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
436 />
437 <p className="text-xs text-gray-500 mt-1">
438 When are you planning on stopping promotional efforts?
439 </p>
440 </div>
441 </div>
442
443 <div>
444 <label className="block text-sm font-medium text-gray-700 mb-1">
445 Description
446 </label>
447 <textarea
448 value={description}
449 onChange={(e) => setDescription(e.target.value)}
450 placeholder="Brief description of the program goals and target audience..."
451 rows={3}
452 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
453 />
454 </div>
455 </div>
456 </div>
457
458 <div className="border-t border-gray-200 px-6 py-4 flex justify-end gap-3">
459 <button
460 onClick={() => setCreateModal(false)}
461 className="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
462 >
463 Cancel
464 </button>
465 <button
466 onClick={handleCreateProgram}
467 className="px-4 py-2 bg-[#00946b] text-white rounded hover:bg-[#007a59]"
468 >
469 Create Program
470 </button>
471 </div>
472 </div>
473 </div>
474 )}
475
476 {/* Review Program Modal */}
477 {reviewModal && selectedProgram && (
478 <div
479 className="modal-overlay"
480 onClick={() => setReviewModal(false)}
481 >
482 <div
483 className="bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-hidden"
484 onClick={(e) => e.stopPropagation()}
485 >
486 <div className="border-b border-gray-200 px-6 py-4 flex items-center justify-between">
487 <h2 className="text-xl font-semibold text-gray-900">{selectedProgram.name}</h2>
488 <button
489 onClick={() => setReviewModal(false)}
490 className="text-gray-400 hover:text-gray-600 text-2xl font-bold"
491 >
492 ×
493 </button>
494 </div>
495
496 <div className="p-6 overflow-y-auto max-h-[calc(90vh-120px)]">
497 <div className="space-y-6">
498 {/* Status & Type */}
499 <div className="grid grid-cols-2 gap-4">
500 <div>
501 <label className="block text-sm font-medium text-gray-500 mb-1">Status</label>
502 <span className={`px-3 py-1 inline-flex text-sm font-semibold rounded-full ${getStatusBadgeColor(selectedProgram.status)}`}>
503 {getStatusLabel(selectedProgram.status)}
504 </span>
505 </div>
506 <div>
507 <label className="block text-sm font-medium text-gray-500 mb-1">Type</label>
508 <p className="text-gray-900 capitalize">{selectedProgram.type}</p>
509 </div>
510 </div>
511
512 {/* Dates */}
513 <div className="grid grid-cols-3 gap-4">
514 <div>
515 <label className="block text-sm font-medium text-gray-500 mb-1">Created</label>
516 <p className="text-gray-900">{formatDateTime(selectedProgram.createDt)}</p>
517 </div>
518 <div>
519 <label className="block text-sm font-medium text-gray-500 mb-1">Start Date</label>
520 <p className="text-gray-900">{formatDate(selectedProgram.startDate)}</p>
521 </div>
522 <div>
523 <label className="block text-sm font-medium text-gray-500 mb-1">End Date</label>
524 <p className="text-gray-900">{formatDate(selectedProgram.endDate)}</p>
525 </div>
526 </div>
527
528 {/* Description */}
529 {selectedProgram.description && (
530 <div>
531 <label className="block text-sm font-medium text-gray-500 mb-1">Description</label>
532 <p className="text-gray-900">{selectedProgram.description}</p>
533 </div>
534 )}
535
536 {/* Performance Metrics */}
537 <div className="border-t pt-4">
538 <h3 className="text-lg font-semibold text-gray-900 mb-4">Performance Metrics</h3>
539 <div className="grid grid-cols-3 gap-4">
540 <div className="bg-gray-50 rounded-lg p-4">
541 <p className="text-sm text-gray-500">Total Reach</p>
542 <p className="text-2xl font-bold text-gray-900">-</p>
543 <p className="text-xs text-gray-400">Demo mode</p>
544 </div>
545 <div className="bg-gray-50 rounded-lg p-4">
546 <p className="text-sm text-gray-500">Conversion Rate</p>
547 <p className="text-2xl font-bold text-gray-900">-</p>
548 <p className="text-xs text-gray-400">Demo mode</p>
549 </div>
550 <div className="bg-gray-50 rounded-lg p-4">
551 <p className="text-sm text-gray-500">Revenue Impact</p>
552 <p className="text-2xl font-bold text-gray-900">-</p>
553 <p className="text-xs text-gray-400">Demo mode</p>
554 </div>
555 </div>
556 </div>
557
558 {/* Actions */}
559 <div className="border-t pt-4">
560 <h3 className="text-lg font-semibold text-gray-900 mb-3">Program Actions</h3>
561 <div className="flex flex-wrap gap-3">
562 <button className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
563 Edit Program
564 </button>
565 <button className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700">
566 View Report
567 </button>
568 <button className="px-4 py-2 bg-yellow-600 text-white rounded hover:bg-yellow-700">
569 Pause Program
570 </button>
571 <button className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700">
572 End Program
573 </button>
574 </div>
575 <p className="text-sm text-gray-500 mt-3">
576 Note: Program actions are in demo mode and will not affect actual data.
577 </p>
578 </div>
579 </div>
580 </div>
581
582 <div className="border-t border-gray-200 px-6 py-4 flex justify-end">
583 <button
584 onClick={() => setReviewModal(false)}
585 className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
586 >
587 Close
588 </button>
589 </div>
590 </div>
591 </div>
592 )}
593 </div>
594 );
595}