fix: Add explicit button types, improve error message extraction, and implement clipboard fallback for 2FA settings.

This commit is contained in:
pradeepkumar
2026-03-03 15:49:35 +05:30
parent 9b89e71764
commit 335c3a0268
2 changed files with 37 additions and 10 deletions

View File

@@ -220,6 +220,7 @@ export function PasswordSecurityForm({ onSave }: PasswordSecurityFormProps) {
{/* Save Button */} {/* Save Button */}
<div className="mt-8"> <div className="mt-8">
<button <button
type="button"
onClick={handleSave} onClick={handleSave}
disabled={loading} disabled={loading}
className="px-8 py-3 bg-[#e58625] text-white rounded-[15px] font-fractul font-medium text-[14px] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="px-8 py-3 bg-[#e58625] text-white rounded-[15px] font-fractul font-medium text-[14px] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"

View File

@@ -44,6 +44,14 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
} }
}; };
const extractErrorMessage = (err: unknown, fallback: string): string => {
const axiosErr = err as { response?: { data?: { message?: string | string[] } } };
const message = axiosErr?.response?.data?.message;
if (Array.isArray(message)) return message[0] || fallback;
if (typeof message === 'string') return message;
return err instanceof Error ? err.message : fallback;
};
const handleSetup = async () => { const handleSetup = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
@@ -52,8 +60,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
setSetupData(response.data); setSetupData(response.data);
setStep('setup'); setStep('setup');
} catch (err: unknown) { } catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Failed to setup 2FA'; setError(extractErrorMessage(err, 'Failed to setup 2FA'));
setError(errorMessage);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -74,8 +81,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
setIsEnabled(true); setIsEnabled(true);
onStatusChange?.(true); onStatusChange?.(true);
} catch (err: unknown) { } catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Invalid verification code'; setError(extractErrorMessage(err, 'Invalid verification code'));
setError(errorMessage);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -97,8 +103,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
setSuccess('Two-factor authentication has been disabled'); setSuccess('Two-factor authentication has been disabled');
onStatusChange?.(false); onStatusChange?.(false);
} catch (err: unknown) { } catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Failed to disable 2FA'; setError(extractErrorMessage(err, 'Failed to disable 2FA'));
setError(errorMessage);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -116,16 +121,27 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
setStep('backup-codes'); setStep('backup-codes');
setSuccess('New backup codes generated successfully'); setSuccess('New backup codes generated successfully');
} catch (err: unknown) { } catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Failed to regenerate backup codes'; setError(extractErrorMessage(err, 'Failed to regenerate backup codes'));
setError(errorMessage);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
const copyBackupCodes = () => { const copyBackupCodes = async () => {
const codesText = backupCodes.join('\n'); const codesText = backupCodes.join('\n');
navigator.clipboard.writeText(codesText); try {
await navigator.clipboard.writeText(codesText);
} catch {
// Fallback for non-secure contexts (HTTP)
const textarea = document.createElement('textarea');
textarea.value = codesText;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
setBackupCodesCopied(true); setBackupCodesCopied(true);
setTimeout(() => setBackupCodesCopied(false), 2000); setTimeout(() => setBackupCodesCopied(false), 2000);
}; };
@@ -206,6 +222,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
{isEnabled ? ( {isEnabled ? (
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
type="button"
onClick={handleRegenerateBackupCodes} onClick={handleRegenerateBackupCodes}
disabled={isLoading} disabled={isLoading}
className="px-4 py-2 border border-[#00293D]/20 text-[#00293D] rounded-[15px] font-serif text-[12px] hover:bg-gray-50 transition-colors disabled:opacity-50" className="px-4 py-2 border border-[#00293D]/20 text-[#00293D] rounded-[15px] font-serif text-[12px] hover:bg-gray-50 transition-colors disabled:opacity-50"
@@ -213,6 +230,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
Backup Codes Backup Codes
</button> </button>
<button <button
type="button"
onClick={() => setStep('disable')} onClick={() => setStep('disable')}
disabled={isLoading} disabled={isLoading}
className="px-4 py-2 border border-red-300 text-red-600 rounded-[15px] font-serif text-[12px] hover:bg-red-50 transition-colors disabled:opacity-50" className="px-4 py-2 border border-red-300 text-red-600 rounded-[15px] font-serif text-[12px] hover:bg-red-50 transition-colors disabled:opacity-50"
@@ -222,6 +240,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
</div> </div>
) : ( ) : (
<button <button
type="button"
onClick={handleSetup} onClick={handleSetup}
disabled={isLoading} disabled={isLoading}
className="px-6 py-2 bg-[#e58625] text-white rounded-[15px] font-serif text-[12px] hover:bg-[#d47920] transition-colors disabled:opacity-50" className="px-6 py-2 bg-[#e58625] text-white rounded-[15px] font-serif text-[12px] hover:bg-[#d47920] transition-colors disabled:opacity-50"
@@ -299,6 +318,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
type="button"
onClick={() => { onClick={() => {
setStep('status'); setStep('status');
setSetupData(null); setSetupData(null);
@@ -310,6 +330,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
Cancel Cancel
</button> </button>
<button <button
type="button"
onClick={handleVerify} onClick={handleVerify}
disabled={isLoading || verificationCode.length !== 6} disabled={isLoading || verificationCode.length !== 6}
className="px-6 py-2 bg-[#e58625] text-white rounded-[15px] font-serif text-[12px] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="px-6 py-2 bg-[#e58625] text-white rounded-[15px] font-serif text-[12px] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
@@ -358,12 +379,14 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
</p> </p>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
type="button"
onClick={copyBackupCodes} onClick={copyBackupCodes}
className="px-4 py-2 border border-[#00293D]/20 text-[#00293D] rounded-[15px] font-serif text-[12px] hover:bg-gray-50 transition-colors" className="px-4 py-2 border border-[#00293D]/20 text-[#00293D] rounded-[15px] font-serif text-[12px] hover:bg-gray-50 transition-colors"
> >
{backupCodesCopied ? 'Copied!' : 'Copy All'} {backupCodesCopied ? 'Copied!' : 'Copy All'}
</button> </button>
<button <button
type="button"
onClick={downloadBackupCodes} onClick={downloadBackupCodes}
className="px-4 py-2 border border-[#00293D]/20 text-[#00293D] rounded-[15px] font-serif text-[12px] hover:bg-gray-50 transition-colors" className="px-4 py-2 border border-[#00293D]/20 text-[#00293D] rounded-[15px] font-serif text-[12px] hover:bg-gray-50 transition-colors"
> >
@@ -373,6 +396,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
</div> </div>
<button <button
type="button"
onClick={() => { onClick={() => {
setStep('status'); setStep('status');
setBackupCodes([]); setBackupCodes([]);
@@ -433,6 +457,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
type="button"
onClick={() => { onClick={() => {
setStep('status'); setStep('status');
setDisablePassword(''); setDisablePassword('');
@@ -443,6 +468,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
Cancel Cancel
</button> </button>
<button <button
type="button"
onClick={handleDisable} onClick={handleDisable}
disabled={isLoading || !disablePassword} disabled={isLoading || !disablePassword}
className="px-6 py-2 bg-red-600 text-white rounded-[15px] font-serif text-[12px] hover:bg-red-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="px-6 py-2 bg-red-600 text-white rounded-[15px] font-serif text-[12px] hover:bg-red-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"