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 */}
|
||||
<div className="mt-8">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
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"
|
||||
|
||||
@@ -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 () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -52,8 +60,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
setSetupData(response.data);
|
||||
setStep('setup');
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to setup 2FA';
|
||||
setError(errorMessage);
|
||||
setError(extractErrorMessage(err, 'Failed to setup 2FA'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -74,8 +81,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
setIsEnabled(true);
|
||||
onStatusChange?.(true);
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Invalid verification code';
|
||||
setError(errorMessage);
|
||||
setError(extractErrorMessage(err, 'Invalid verification code'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -97,8 +103,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
setSuccess('Two-factor authentication has been disabled');
|
||||
onStatusChange?.(false);
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to disable 2FA';
|
||||
setError(errorMessage);
|
||||
setError(extractErrorMessage(err, 'Failed to disable 2FA'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -116,16 +121,27 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
setStep('backup-codes');
|
||||
setSuccess('New backup codes generated successfully');
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to regenerate backup codes';
|
||||
setError(errorMessage);
|
||||
setError(extractErrorMessage(err, 'Failed to regenerate backup codes'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copyBackupCodes = () => {
|
||||
const copyBackupCodes = async () => {
|
||||
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);
|
||||
setTimeout(() => setBackupCodesCopied(false), 2000);
|
||||
};
|
||||
@@ -206,6 +222,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
{isEnabled ? (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRegenerateBackupCodes}
|
||||
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"
|
||||
@@ -213,6 +230,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
Backup Codes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStep('disable')}
|
||||
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"
|
||||
@@ -222,6 +240,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSetup}
|
||||
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"
|
||||
@@ -299,6 +318,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStep('status');
|
||||
setSetupData(null);
|
||||
@@ -310,6 +330,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleVerify}
|
||||
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"
|
||||
@@ -358,12 +379,14 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
{backupCodesCopied ? 'Copied!' : 'Copy All'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
@@ -373,6 +396,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStep('status');
|
||||
setBackupCodes([]);
|
||||
@@ -433,6 +457,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setStep('status');
|
||||
setDisablePassword('');
|
||||
@@ -443,6 +468,7 @@ export function TwoFactorSettings({ onStatusChange }: TwoFactorSettingsProps) {
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDisable}
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user