The Workfront Campaign ID field is now optional when creating campaigns. When provided, it is used as the base for proof workfront IDs instead of generating random ones. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
193 lines
9.3 KiB
TypeScript
Executable file
193 lines
9.3 KiB
TypeScript
Executable file
|
|
import React, { useState, useEffect } from 'react';
|
|
import { XIcon } from './icons/XIcon';
|
|
import apiService from '../services/apiService';
|
|
|
|
interface CreateCampaignModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onAddCampaign: (campaignData: {
|
|
name: string;
|
|
workfrontId: string;
|
|
clientLead: string;
|
|
agencyLead: string;
|
|
brandGuidelines: string;
|
|
}) => void;
|
|
brandGuidelines?: string[];
|
|
}
|
|
|
|
export const CreateCampaignModal: React.FC<CreateCampaignModalProps> = ({ isOpen, onClose, onAddCampaign, brandGuidelines: brandGuidelineOptions = [] }) => {
|
|
const [name, setName] = useState('');
|
|
const [selectedBrandGuideline, setSelectedBrandGuideline] = useState('');
|
|
const [workfrontId, setWorkfrontId] = useState('');
|
|
const [clientLead, setClientLead] = useState('');
|
|
const [agencyLead, setAgencyLead] = useState('');
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
// Reset form when modal opens
|
|
if (isOpen) {
|
|
setName('');
|
|
setSelectedBrandGuideline('');
|
|
setWorkfrontId('');
|
|
setClientLead('');
|
|
setAgencyLead('');
|
|
setError(null);
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const validateWorkfrontId = (id: string): boolean => {
|
|
// Empty is valid (field is optional)
|
|
if (id.length === 0) {
|
|
setError(null);
|
|
return true;
|
|
}
|
|
const isValid = /^#WF_\d+$/.test(id);
|
|
if (!isValid) {
|
|
setError("Workfront Campaign ID must be in the format '#WF_12345'");
|
|
} else {
|
|
setError(null);
|
|
}
|
|
return isValid;
|
|
};
|
|
|
|
const handleWorkfrontIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const newId = e.target.value;
|
|
setWorkfrontId(newId);
|
|
validateWorkfrontId(newId);
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!name.trim() || !clientLead.trim() || !agencyLead.trim() || !selectedBrandGuideline.trim()) {
|
|
return;
|
|
}
|
|
|
|
// Validate workfrontId format only if provided
|
|
if (workfrontId.trim() && !/^#WF_\d+$/.test(workfrontId)) {
|
|
setError("Workfront Campaign ID must be in the format '#WF_12345'");
|
|
return;
|
|
}
|
|
|
|
setError(null);
|
|
onAddCampaign({ name, workfrontId: workfrontId.trim(), clientLead, agencyLead, brandGuidelines: selectedBrandGuideline });
|
|
onClose();
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const isFormInvalid = !name.trim() || !clientLead.trim() || !agencyLead.trim() || !selectedBrandGuideline.trim();
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50 transition-opacity duration-300"
|
|
onClick={onClose}
|
|
aria-modal="true"
|
|
role="dialog"
|
|
>
|
|
<div
|
|
className="bg-white rounded-lg shadow-xl p-6 sm:p-8 w-full max-w-lg transform transition-all"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h2 className="text-2xl font-bold text-brand-dark-blue">Create New Campaign</h2>
|
|
<button onClick={onClose} className="p-1 rounded-full text-gray-500 hover:bg-gray-200 hover:text-gray-800 transition-colors">
|
|
<XIcon className="h-6 w-6" />
|
|
</button>
|
|
</div>
|
|
<form onSubmit={handleSubmit} noValidate>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label htmlFor="campaign-name" className="block text-sm font-medium text-gray-700">Campaign Name</label>
|
|
<input
|
|
type="text"
|
|
id="campaign-name"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-brand-accent focus:border-brand-accent transition bg-white text-gray-900"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="brand-guidelines" className="block text-sm font-medium text-gray-700">Brand Guidelines</label>
|
|
<select
|
|
id="brand-guidelines"
|
|
value={selectedBrandGuideline}
|
|
onChange={(e) => setSelectedBrandGuideline(e.target.value)}
|
|
className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-brand-accent focus:border-brand-accent transition bg-white text-gray-900"
|
|
required
|
|
>
|
|
<option value="" disabled>Select brand guidelines</option>
|
|
{brandGuidelineOptions.map((brand) => (
|
|
<option key={brand} value={brand}>{brand}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="workfront-id" className="block text-sm font-medium text-gray-700">Workfront Campaign ID <span className="text-gray-400 font-normal">(Optional)</span></label>
|
|
<input
|
|
type="text"
|
|
id="workfront-id"
|
|
value={workfrontId}
|
|
onChange={handleWorkfrontIdChange}
|
|
className={`mt-1 block w-full p-2 border rounded-md shadow-sm focus:ring-brand-accent focus:border-brand-accent transition bg-white text-gray-900 placeholder:text-gray-400 ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'border-gray-300'}`}
|
|
placeholder="#WF_12345"
|
|
aria-invalid={!!error}
|
|
aria-describedby="workfront-id-error"
|
|
/>
|
|
{error && <p id="workfront-id-error" className="mt-1 text-sm text-red-600">{error}</p>}
|
|
</div>
|
|
<div>
|
|
<label htmlFor="client-lead" className="block text-sm font-medium text-gray-700">Client Lead</label>
|
|
<input
|
|
type="text"
|
|
id="client-lead"
|
|
value={clientLead}
|
|
onChange={(e) => setClientLead(e.target.value)}
|
|
className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-brand-accent focus:border-brand-accent transition bg-white text-gray-900"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700">Agency</label>
|
|
<input
|
|
type="text"
|
|
value="OLIVER Agency"
|
|
className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm bg-gray-100 text-gray-600 cursor-not-allowed"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="agency-lead" className="block text-sm font-medium text-gray-700">Agency Lead</label>
|
|
<input
|
|
type="text"
|
|
id="agency-lead"
|
|
value={agencyLead}
|
|
onChange={(e) => setAgencyLead(e.target.value)}
|
|
className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-brand-accent focus:border-brand-accent transition bg-white text-gray-900"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="mt-8 flex justify-end gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="bg-gray-200 text-gray-800 font-semibold py-2 px-4 rounded-md hover:bg-gray-300 transition-colors duration-300"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="bg-brand-accent text-white font-semibold py-2 px-4 rounded-md hover:bg-brand-dark-blue transition-colors duration-300 disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
disabled={isFormInvalid || !!error}
|
|
>
|
|
Create Campaign
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|