Updates all remaining frontend components to use the new Barclays design system color tokens: - brand-dark-blue → primary-blue (#1A2142) - brand-accent → active-blue (#006DE3) - brand-light-blue → cyan-brand (#00AEEF) - brand-gray → grey-100 (#F6F6F6) Components updated: - CampaignDetail and ProofDetailView in Campaigns.tsx - Projects.tsx (full component migration) - StatusDashboard.tsx (status tiles and colors) - CreateProjectModal.tsx (modal styling) - FeedbackReport.tsx (remaining brand colors) - Login.tsx and Profile.tsx - WIPReviewer.tsx and CopyGenAI.tsx - Header, LoadingVisual, ToggleSwitch - AssetPreview, ProofPreview, AssetUpload - ProofTypeManager Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
237 lines
12 KiB
TypeScript
Executable file
237 lines
12 KiB
TypeScript
Executable file
|
|
import React, { useState } from 'react';
|
|
import { useMsal } from '@azure/msal-react';
|
|
import { BarclaysLogo } from './icons/BarclaysLogo';
|
|
import { XIcon } from './icons/XIcon';
|
|
import { MicrosoftLogo } from './icons/MicrosoftLogo';
|
|
import { loginRequest } from '../services/authConfig';
|
|
|
|
const API_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000';
|
|
|
|
const SupportModal: React.FC<{
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}> = ({ isOpen, onClose }) => {
|
|
const [query, setQuery] = useState('');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [submitStatus, setSubmitStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(null);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!query.trim()) return;
|
|
|
|
setIsSubmitting(true);
|
|
setSubmitStatus(null);
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/api/support/email`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
message: query,
|
|
subject: 'Support Request from Mod Comms Login',
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to send');
|
|
}
|
|
|
|
setSubmitStatus({ type: 'success', message: 'Thank you for your query. A member of the support team will be in touch with you shortly.' });
|
|
setQuery('');
|
|
setTimeout(() => {
|
|
onClose();
|
|
setSubmitStatus(null);
|
|
}, 3000);
|
|
} catch {
|
|
setSubmitStatus({ type: 'error', message: 'Failed to send your message. Please try again later.' });
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50 transition-opacity duration-300"
|
|
onClick={onClose}
|
|
>
|
|
<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-start mb-4">
|
|
<h3 className="text-xl font-bold text-primary-blue">Contact Support</h3>
|
|
<button onClick={onClose} className="-mt-2 -mr-2 p-2 rounded-full hover:bg-gray-200 transition-colors">
|
|
<XIcon className="h-6 w-6 text-gray-600" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<p className="text-gray-600 mb-4">Please describe your issue or query below. A member of our team will be in touch shortly.</p>
|
|
{submitStatus && (
|
|
<div className={`mb-4 p-3 rounded-md ${submitStatus.type === 'success' ? 'bg-green-50 text-green-700 border border-green-200' : 'bg-red-50 text-red-700 border border-red-200'}`}>
|
|
{submitStatus.message}
|
|
</div>
|
|
)}
|
|
<textarea
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-active-blue focus:border-active-blue transition"
|
|
rows={5}
|
|
placeholder="Type your message here..."
|
|
required
|
|
disabled={isSubmitting}
|
|
/>
|
|
<div className="mt-6 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"
|
|
disabled={isSubmitting}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="bg-active-blue text-white font-semibold py-2 px-4 rounded-md hover:bg-primary-blue transition-colors duration-300 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center gap-2"
|
|
disabled={!query.trim() || isSubmitting}
|
|
>
|
|
{isSubmitting ? (
|
|
<>
|
|
<svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Sending...
|
|
</>
|
|
) : (
|
|
'Submit Query'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const Login: React.FC = () => {
|
|
const { instance } = useMsal();
|
|
const [isSupportModalOpen, setIsSupportModalOpen] = useState(false);
|
|
const [isLoggingIn, setIsLoggingIn] = useState(false);
|
|
const [loginError, setLoginError] = useState<string | null>(null);
|
|
|
|
const handleMicrosoftLogin = async () => {
|
|
console.log('[MSAL Login] Starting Microsoft login popup...');
|
|
console.log('[MSAL Login] Login request scopes:', loginRequest.scopes);
|
|
setIsLoggingIn(true);
|
|
setLoginError(null);
|
|
|
|
try {
|
|
const response = await instance.loginPopup(loginRequest);
|
|
// Success - MSAL Provider will detect the login and re-render App
|
|
console.log('[MSAL Login] Login successful!');
|
|
console.log('[MSAL Login] Account:', response.account?.username);
|
|
console.log('[MSAL Login] Token type:', response.tokenType);
|
|
console.log('[MSAL Login] Expires on:', response.expiresOn);
|
|
} catch (error: unknown) {
|
|
console.error('[MSAL Login] Login failed:', error);
|
|
if (error instanceof Error) {
|
|
// Handle user cancellation differently from errors
|
|
if (error.message.includes('user_cancelled')) {
|
|
console.log('[MSAL Login] User cancelled login');
|
|
setLoginError(null); // Don't show error for cancellation
|
|
} else {
|
|
console.error('[MSAL Login] Error details:', error.message);
|
|
setLoginError('Login failed. Please try again or contact support.');
|
|
}
|
|
}
|
|
} finally {
|
|
setIsLoggingIn(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<SupportModal
|
|
isOpen={isSupportModalOpen}
|
|
onClose={() => setIsSupportModalOpen(false)}
|
|
/>
|
|
<div className="fixed inset-0 overflow-y-auto bg-[#0f172a] flex items-center justify-center font-sans p-4">
|
|
{/* Modern Glassy Background */}
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
{/* Top Left Blob */}
|
|
<div className="absolute -top-[20%] -left-[10%] w-[70%] h-[70%] rounded-full bg-gradient-to-br from-active-blue/30 to-purple-600/30 blur-[120px] animate-pulse" style={{ animationDuration: '8s' }}></div>
|
|
|
|
{/* Bottom Right Blob */}
|
|
<div className="absolute -bottom-[20%] -right-[10%] w-[70%] h-[70%] rounded-full bg-gradient-to-tl from-cyan-brand/30 to-emerald-500/30 blur-[120px] animate-pulse" style={{ animationDuration: '10s', animationDelay: '1s' }}></div>
|
|
|
|
{/* Noise Texture */}
|
|
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-20 mix-blend-soft-light"></div>
|
|
</div>
|
|
|
|
{/* Login Card */}
|
|
<div className="relative z-10 bg-white/90 backdrop-blur-xl p-8 sm:p-12 shadow-2xl rounded-3xl w-full max-w-md flex flex-col items-center text-center border border-white/50 ring-1 ring-white/50">
|
|
<div className="mb-8 transform transition-transform hover:scale-105 duration-300">
|
|
<BarclaysLogo className="h-12 w-auto text-primary-blue" />
|
|
</div>
|
|
|
|
<h1 className="text-3xl font-extrabold text-primary-blue mb-2 tracking-tight">Mod Comms</h1>
|
|
<p className="text-slate-500 mb-8 font-medium">Proof Review & Compliance Platform</p>
|
|
|
|
<div className="w-full space-y-6">
|
|
<div className="p-5 bg-blue-50/80 rounded-xl border border-blue-100 text-left">
|
|
<p className="text-sm text-blue-900 leading-relaxed">
|
|
<span className="font-bold block mb-1 flex items-center gap-2">
|
|
<span className="w-2 h-2 rounded-full bg-blue-500 animate-pulse"></span>
|
|
Enterprise Sign-In
|
|
</span>
|
|
This application is connected to Azure Active Directory. Please sign in using your corporate Microsoft account.
|
|
</p>
|
|
</div>
|
|
|
|
{loginError && (
|
|
<div className="w-full p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
|
|
{loginError}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
onClick={handleMicrosoftLogin}
|
|
disabled={isLoggingIn}
|
|
className="w-full flex items-center justify-center gap-3 bg-white hover:bg-gray-50 text-slate-700 font-bold py-4 px-6 rounded-xl border border-gray-200 shadow-lg shadow-gray-200/50 hover:shadow-xl hover:border-gray-300 transition-all duration-300 group disabled:opacity-70 disabled:cursor-wait transform hover:-translate-y-0.5"
|
|
>
|
|
{isLoggingIn ? (
|
|
<svg className="animate-spin h-5 w-5 text-active-blue" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
) : (
|
|
<>
|
|
<MicrosoftLogo className="h-5 w-5" />
|
|
<span>Sign in with Microsoft</span>
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="mt-10 pt-6 border-t border-gray-200/60 w-full">
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsSupportModalOpen(true)}
|
|
className="text-sm text-slate-500 hover:text-active-blue transition-colors font-medium"
|
|
>
|
|
Having trouble signing in? Contact Support
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<footer className="absolute bottom-0 left-0 right-0 text-center p-6 z-10">
|
|
<p className="text-xs text-slate-400/80 font-medium">© {new Date().getFullYear()} OLIVER Agency Mod Comms. All rights reserved.</p>
|
|
</footer>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|