Fix Agency Admin campaign creation and proof upload permissions
Switch canWrite from blacklist (role !== 'oversight_admin') to explicit whitelist (super_admin, agency_admin, basic_user) for clearer permission logic. Propagate readOnly prop to CampaignDetail and ProofDetailView subcomponents so upload/delete buttons are properly hidden for read-only roles at all navigation levels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3207ec301c
commit
a08d54ec6d
2 changed files with 61 additions and 49 deletions
|
|
@ -1025,8 +1025,8 @@ const CampaignDeleteConfirmationModal: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
const CampaignDetail: React.FC<{
|
||||
campaignName: string;
|
||||
const CampaignDetail: React.FC<{
|
||||
campaignName: string;
|
||||
onBack: () => void;
|
||||
onSelectProof: (proof: any) => void;
|
||||
campaignProofs: { [key: string]: any[] };
|
||||
|
|
@ -1034,7 +1034,8 @@ const CampaignDetail: React.FC<{
|
|||
dropdownOptions: DropdownOptions;
|
||||
onRetryAnalysis: (campaignName: string, tempId: string) => void;
|
||||
onDeleteProof: (campaignName: string, proofName: string) => void;
|
||||
}> = ({ campaignName, onBack, onSelectProof, campaignProofs, onProofUpload, dropdownOptions, onRetryAnalysis, onDeleteProof }) => {
|
||||
readOnly?: boolean;
|
||||
}> = ({ campaignName, onBack, onSelectProof, campaignProofs, onProofUpload, dropdownOptions, onRetryAnalysis, onDeleteProof, readOnly = false }) => {
|
||||
const [isUploadFormVisible, setIsUploadFormVisible] = useState(false);
|
||||
const [proofToDelete, setProofToDelete] = useState<any | null>(null);
|
||||
const [proofForUpload, setProofForUpload] = useState<any | null>(null);
|
||||
|
|
@ -1206,13 +1207,15 @@ const CampaignDetail: React.FC<{
|
|||
{isExporting ? <SpinnerIcon className="h-5 w-5 custom-spinner" /> : <ExportIcon className="h-5 w-5" />}
|
||||
{isExporting ? 'Exporting...' : 'Export Campaign Report'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsUploadFormVisible(true)}
|
||||
className="flex items-center gap-2 bg-active-blue text-white font-semibold py-2 px-4 rounded-full hover:bg-active-blue/90 transition-colors duration-300"
|
||||
>
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
Upload New Proof
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button
|
||||
onClick={() => setIsUploadFormVisible(true)}
|
||||
className="flex items-center gap-2 bg-active-blue text-white font-semibold py-2 px-4 rounded-full hover:bg-active-blue/90 transition-colors duration-300"
|
||||
>
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
Upload New Proof
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-[10px] shadow-md overflow-hidden border border-grey-300">
|
||||
|
|
@ -1310,14 +1313,16 @@ const CampaignDetail: React.FC<{
|
|||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<button
|
||||
onClick={(e) => handleNewVersionClick(e, proof)}
|
||||
className="p-2 text-grey-700 rounded-full hover:bg-info-light hover:text-active-blue transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title={`Upload new version for ${proof.proofName}`}
|
||||
disabled={isUploading || isExporting}
|
||||
>
|
||||
<UploadIcon className="h-5 w-5" />
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button
|
||||
onClick={(e) => handleNewVersionClick(e, proof)}
|
||||
className="p-2 text-grey-700 rounded-full hover:bg-info-light hover:text-active-blue transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title={`Upload new version for ${proof.proofName}`}
|
||||
disabled={isUploading || isExporting}
|
||||
>
|
||||
<UploadIcon className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -1329,17 +1334,19 @@ const CampaignDetail: React.FC<{
|
|||
>
|
||||
<PDFIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setProofToDelete(proof);
|
||||
}}
|
||||
disabled={isExporting}
|
||||
className="p-2 text-grey-700 rounded-full hover:bg-error-light hover:text-error transition-colors disabled:opacity-50"
|
||||
title={`Delete ${proof.proofName}`}
|
||||
>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setProofToDelete(proof);
|
||||
}}
|
||||
disabled={isExporting}
|
||||
className="p-2 text-grey-700 rounded-full hover:bg-error-light hover:text-error transition-colors disabled:opacity-50"
|
||||
title={`Delete ${proof.proofName}`}
|
||||
>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -1364,7 +1371,8 @@ const ProofDetailView: React.FC<{
|
|||
onResolveSubmit: (resolveData: Omit<ResolvedItem, 'id' | 'timestamp' | 'submitter' | 'submitAgency'>) => void;
|
||||
flaggedItems: FlaggedItem[];
|
||||
resolvedItems: ResolvedItem[];
|
||||
}> = ({ campaignName, proof, onBack, onNewVersionUpload, isUploadingNewVersion, onFlagSubmit, onResolveSubmit, flaggedItems, resolvedItems }) => {
|
||||
readOnly?: boolean;
|
||||
}> = ({ campaignName, proof, onBack, onNewVersionUpload, isUploadingNewVersion, onFlagSubmit, onResolveSubmit, flaggedItems, resolvedItems, readOnly = false }) => {
|
||||
|
||||
const getInitialVersionIndex = () => {
|
||||
if (proof.initialVersion && proof.versions) {
|
||||
|
|
@ -1621,24 +1629,26 @@ const ProofDetailView: React.FC<{
|
|||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleUploadClick}
|
||||
disabled={isUploadingNewVersion}
|
||||
className="flex items-center gap-2 text-sm bg-white text-active-blue font-semibold py-1.5 px-3 rounded-full border-2 border-active-blue hover:bg-active-blue hover:text-white transition-colors duration-200 disabled:bg-grey-300 disabled:text-grey-700 disabled:border-grey-300 disabled:cursor-wait"
|
||||
title="Upload a new version of this proof"
|
||||
>
|
||||
{isUploadingNewVersion ? (
|
||||
<>
|
||||
<SpinnerIcon className="h-4 w-4 custom-spinner" />
|
||||
Uploading...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<UploadIcon className="h-4 w-4" />
|
||||
New Version
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button
|
||||
onClick={handleUploadClick}
|
||||
disabled={isUploadingNewVersion}
|
||||
className="flex items-center gap-2 text-sm bg-white text-active-blue font-semibold py-1.5 px-3 rounded-full border-2 border-active-blue hover:bg-active-blue hover:text-white transition-colors duration-200 disabled:bg-grey-300 disabled:text-grey-700 disabled:border-grey-300 disabled:cursor-wait"
|
||||
title="Upload a new version of this proof"
|
||||
>
|
||||
{isUploadingNewVersion ? (
|
||||
<>
|
||||
<SpinnerIcon className="h-4 w-4 custom-spinner" />
|
||||
Uploading...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<UploadIcon className="h-4 w-4" />
|
||||
New Version
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
|
|
@ -1778,6 +1788,7 @@ export const Campaigns: React.FC<CampaignsProps> = ({
|
|||
onResolveSubmit={onResolveSubmit}
|
||||
flaggedItems={flaggedItems}
|
||||
resolvedItems={resolvedItems}
|
||||
readOnly={readOnly}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
@ -1791,6 +1802,7 @@ export const Campaigns: React.FC<CampaignsProps> = ({
|
|||
dropdownOptions={dropdownOptions}
|
||||
onRetryAnalysis={onRetryAnalysis}
|
||||
onDeleteProof={onDeleteProof}
|
||||
readOnly={readOnly}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||
isLoading,
|
||||
isSuperAdmin: role === 'super_admin',
|
||||
isOversightAdmin: role === 'oversight_admin',
|
||||
canWrite: role !== 'oversight_admin' && role != null,
|
||||
canWrite: role === 'super_admin' || role === 'agency_admin' || role === 'basic_user',
|
||||
canSeeAnalytics: role === 'super_admin' || role === 'oversight_admin' || role === 'agency_admin',
|
||||
canSeeAuditing: role === 'super_admin' || role === 'oversight_admin',
|
||||
canSeeKnowledgeBase: role === 'super_admin',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue