feat(06-02): create SftpAuthToggle.tsx following AzureAuthToggle CSS-hidden pattern
- Password and Private Key tabs using CSS-hidden segmented control - Both pass and key_pem fields always registered with react-hook-form - Password tab active by default (authMethod = 'password') - Active tab: bg-blue-600 text-white; inactive: bg-white text-gray-700 hover:bg-gray-50 - Wired into RemoteConfigStep in Plan 03 (06-03-PLAN.md)
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import type { UseFormRegister, FieldError } from 'react-hook-form';
|
||||||
|
import { PasswordField } from '../ui/PasswordField';
|
||||||
|
|
||||||
|
type AuthMethod = 'password' | 'key';
|
||||||
|
|
||||||
|
interface SftpAuthToggleProps {
|
||||||
|
register: UseFormRegister<any>;
|
||||||
|
errors: {
|
||||||
|
pass?: FieldError;
|
||||||
|
key_pem?: FieldError;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SftpAuthToggle({ register, errors }: SftpAuthToggleProps) {
|
||||||
|
const [authMethod, setAuthMethod] = useState<AuthMethod>('password');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{/* Segmented control */}
|
||||||
|
<div className="flex rounded-md border border-gray-300 overflow-hidden">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setAuthMethod('password')}
|
||||||
|
className={[
|
||||||
|
'flex-1 py-1.5 text-sm font-medium transition-colors',
|
||||||
|
authMethod === 'password'
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'bg-white text-gray-700 hover:bg-gray-50',
|
||||||
|
].join(' ')}
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setAuthMethod('key')}
|
||||||
|
className={[
|
||||||
|
'flex-1 py-1.5 text-sm font-medium transition-colors',
|
||||||
|
authMethod === 'key'
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'bg-white text-gray-700 hover:bg-gray-50',
|
||||||
|
].join(' ')}
|
||||||
|
>
|
||||||
|
Private Key
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Both fields always registered — only active one visible via CSS */}
|
||||||
|
<div className={authMethod === 'password' ? 'block' : 'hidden'}>
|
||||||
|
<PasswordField
|
||||||
|
id="pass"
|
||||||
|
label="Password"
|
||||||
|
error={errors.pass}
|
||||||
|
registration={register('pass')}
|
||||||
|
helpText="SFTP password. Note: rclone may require the password to be obscured using `rclone obscure <password>`. If authentication fails, use the obscured value."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={authMethod === 'key' ? 'block' : 'hidden'}>
|
||||||
|
<PasswordField
|
||||||
|
id="key_pem"
|
||||||
|
label="Private Key (PEM)"
|
||||||
|
error={errors.key_pem}
|
||||||
|
registration={register('key_pem')}
|
||||||
|
helpText="Paste your private key in PEM format (-----BEGIN ... PRIVATE KEY-----)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user