All modal inner containers now have border-2 border-oliver-azure for consistent Oliver branding across: - CreateCampaignModal, CreateProjectModal - FeedbackReport (resolve + flag modals) - UserManagement (confirmation + history modals) - Campaigns (upload, delete confirmation, version history modals) - Projects (upload, delete modals) - Login (support contact modal) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
165 lines
7.7 KiB
TypeScript
Executable file
165 lines
7.7 KiB
TypeScript
Executable file
import React, { useState, useEffect } from 'react';
|
|
import { XIcon } from './icons/XIcon';
|
|
import { useUser } from '../contexts/UserContext';
|
|
|
|
interface CreateProjectModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onAddProject: (projectData: {
|
|
name: string;
|
|
workfrontId: string;
|
|
clientLead: string;
|
|
}) => void;
|
|
}
|
|
|
|
export const CreateProjectModal: React.FC<CreateProjectModalProps> = ({ isOpen, onClose, onAddProject }) => {
|
|
const { user } = useUser();
|
|
const [name, setName] = useState('');
|
|
const [workfrontId, setWorkfrontId] = useState('');
|
|
const [clientLead, setClientLead] = useState('');
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
// Reset form when modal opens
|
|
if (isOpen) {
|
|
setName('');
|
|
setWorkfrontId('');
|
|
setClientLead('');
|
|
setError(null);
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const validateWorkfrontId = (id: string): boolean => {
|
|
const isValid = /^#WF_\d+$/.test(id);
|
|
if (!isValid && id.length > 0) {
|
|
setError("Workfront Project ID must be in the format '#WF_12345'");
|
|
} else {
|
|
setError(null);
|
|
}
|
|
return isValid || id.length === 0;
|
|
};
|
|
|
|
const handleWorkfrontIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const newId = e.target.value;
|
|
setWorkfrontId(newId);
|
|
validateWorkfrontId(newId);
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
const isIdValidOnSubmit = /^#WF_\d+$/.test(workfrontId);
|
|
|
|
if (!name.trim() || !clientLead.trim() || !workfrontId.trim()) {
|
|
return;
|
|
}
|
|
|
|
if (!isIdValidOnSubmit) {
|
|
setError("Workfront Project ID must be in the format '#WF_12345'");
|
|
return;
|
|
}
|
|
|
|
setError(null);
|
|
onAddProject({ name, workfrontId, clientLead });
|
|
onClose();
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const isFormInvalid = !name.trim() || !workfrontId.trim() || !clientLead.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-[10px] shadow-xl p-6 sm:p-8 w-full max-w-lg transform transition-all border-2 border-oliver-azure"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h2 className="text-2xl font-bold text-oliver-black">Create New Project</h2>
|
|
<button onClick={onClose} className="p-1 rounded-full text-oliver-black/60 hover:bg-oliver-grey hover:text-oliver-black transition-colors">
|
|
<XIcon className="h-6 w-6" />
|
|
</button>
|
|
</div>
|
|
<form onSubmit={handleSubmit} noValidate>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label htmlFor="project-name" className="block text-sm font-medium text-oliver-black">Project Name</label>
|
|
<input
|
|
type="text"
|
|
id="project-name"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
className="mt-1 block w-full p-2 border-2 border-oliver-azure rounded-[10px] shadow-sm focus:ring-oliver-azure focus:border-oliver-azure transition bg-white text-oliver-black"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="workfront-id" className="block text-sm font-medium text-oliver-black">Workfront Project ID</label>
|
|
<input
|
|
type="text"
|
|
id="workfront-id"
|
|
value={workfrontId}
|
|
onChange={handleWorkfrontIdChange}
|
|
className={`mt-1 block w-full p-2 border-2 rounded-[10px] shadow-sm focus:ring-oliver-azure focus:border-oliver-azure transition bg-white text-oliver-black placeholder:text-oliver-black/60 ${error ? 'border-error focus:border-error focus:ring-error' : 'border-oliver-azure'}`}
|
|
placeholder="#WF_12345"
|
|
required
|
|
aria-invalid={!!error}
|
|
aria-describedby="workfront-id-error"
|
|
/>
|
|
{error && <p id="workfront-id-error" className="mt-1 text-sm text-error">{error}</p>}
|
|
</div>
|
|
<div>
|
|
<label htmlFor="client-lead" className="block text-sm font-medium text-oliver-black">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-2 border-oliver-azure rounded-[10px] shadow-sm focus:ring-oliver-azure focus:border-oliver-azure transition bg-white text-oliver-black"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-oliver-black">Agency</label>
|
|
<input
|
|
type="text"
|
|
value={user?.agencyName ?? ''}
|
|
className="mt-1 block w-full p-2 border-2 border-grey-300 rounded-[10px] shadow-sm bg-oliver-grey text-oliver-black cursor-not-allowed"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-oliver-black">Agency Lead</label>
|
|
<input
|
|
type="text"
|
|
value={user?.name ?? ''}
|
|
className="mt-1 block w-full p-2 border-2 border-grey-300 rounded-[10px] shadow-sm bg-oliver-grey text-oliver-black cursor-not-allowed"
|
|
disabled
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="mt-8 flex justify-end gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="border-2 border-oliver-azure text-oliver-azure font-semibold py-2 px-6 rounded-full hover:bg-oliver-azure hover:text-white transition-colors duration-300"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="bg-oliver-azure text-white font-semibold py-2 px-6 rounded-full hover:bg-oliver-azure/90 transition-colors duration-300 disabled:bg-gray-400 disabled:cursor-not-allowed"
|
|
disabled={isFormInvalid || !!error}
|
|
>
|
|
Create Project
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|