modcomms/frontend/components/CreateProjectModal.tsx
michael 8f2f561c71 Fix stale UserContext after agency/role changes and remove hardcoded values in CreateProjectModal
UserManagement now calls refresh() on the global UserContext when the
current user's agency or role is changed, so downstream consumers
(e.g. CreateCampaignModal) immediately reflect the update.

CreateProjectModal now reads the Agency and Agency Lead fields from
the current user's profile instead of hardcoding "OLIVER Agency" and
"Steve O'Donoghue".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:44:55 -06:00

165 lines
7.6 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"
onClick={(e) => e.stopPropagation()}
>
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-primary-blue">Create New Project</h2>
<button onClick={onClose} className="p-1 rounded-full text-grey-700 hover:bg-grey-100 hover:text-black-title 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-black-title">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-grey-700 rounded-[10px] shadow-sm focus:ring-active-blue focus:border-active-blue transition bg-white text-black-title"
required
/>
</div>
<div>
<label htmlFor="workfront-id" className="block text-sm font-medium text-black-title">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-active-blue focus:border-active-blue transition bg-white text-black-title placeholder:text-grey-700 ${error ? 'border-error focus:border-error focus:ring-error' : 'border-grey-700'}`}
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-black-title">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-grey-700 rounded-[10px] shadow-sm focus:ring-active-blue focus:border-active-blue transition bg-white text-black-title"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-black-title">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-grey-100 text-grey-900 cursor-not-allowed"
disabled
/>
</div>
<div>
<label className="block text-sm font-medium text-black-title">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-grey-100 text-grey-900 cursor-not-allowed"
disabled
/>
</div>
</div>
<div className="mt-8 flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="border-2 border-active-blue text-active-blue font-semibold py-2 px-6 rounded-full hover:bg-active-blue hover:text-white transition-colors duration-300"
>
Cancel
</button>
<button
type="submit"
className="bg-active-blue text-white font-semibold py-2 px-6 rounded-full hover:bg-active-blue/90 transition-colors duration-300 disabled:bg-grey-700 disabled:cursor-not-allowed"
disabled={isFormInvalid || !!error}
>
Create Project
</button>
</div>
</form>
</div>
</div>
);
};