fix: Add explicit button types, improve error message extraction, and implement clipboard fallback for 2FA settings.
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user