feat(13-04): wire all new backends into RemoteConfigStep

- Replace manual backendLabel record with registry-derived Object.fromEntries
- Import OAuthInstructions and GdriveAuthToggle components
- Refactor 3-branch ternary into renderBackendFields() switch function
- Add gdrive branch: GdriveAuthToggle + generic loop for non-auth fields
- Add onedrive branch: OAuthInstructions + generic loop
- Add dropbox/box/pcloud branches: OAuthInstructions + generic loop
- Default branch covers all other backends (azure-files, swift, ftp, webdav, smb, http, seafile)
This commit is contained in:
2026-04-01 16:55:22 +02:00
parent 7c6f0acfb1
commit d0c00f7e56
+173 -60
View File
@@ -6,13 +6,185 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useWizard } from '../../store/context';
import { BACKEND_REGISTRY } from '../../schemas/registry';
import type { BackendType } from '../../schemas/registry';
import { BACKEND_SCHEMAS } from '../../schemas';
import { FieldRenderer } from '../ui/FieldRenderer';
import { AzureAuthToggle } from './AzureAuthToggle';
import { SftpAuthToggle } from './SftpAuthToggle';
import { OAuthInstructions } from './OAuthInstructions';
import { GdriveAuthToggle } from './GdriveAuthToggle';
import type { FieldError, FieldErrors } from 'react-hook-form';
import { MD3_BTN_FILLED, MD3_BTN_OUTLINED } from '../../styles/md3-buttons';
// backendLabel derived from registry — no manual duplication
const backendLabel = Object.fromEntries(
Object.entries(BACKEND_REGISTRY).map(([k, v]) => [k, v.displayName])
) as Record<BackendType, string>;
function renderBackendFields(
backendType: BackendType,
register: ReturnType<typeof useForm>['register'],
errors: Record<string, unknown>
) {
switch (backendType) {
case 'azureblob':
return (
<>
{/* Account field via FieldRenderer */}
<FieldRenderer
field={BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'account')!}
register={register}
error={errors.account as FieldError | undefined}
/>
{/* Auth toggle handles key + sas_url — both always registered */}
<AzureAuthToggle
register={register}
errors={{
key: errors.key as FieldError | undefined,
sas_url: errors.sas_url as FieldError | undefined,
}}
/>
</>
);
case 'sftp':
return (
<>
{/* host and user via FieldRenderer */}
<FieldRenderer
field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'host')!}
register={register}
error={errors.host as FieldError | undefined}
/>
<FieldRenderer
field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'user')!}
register={register}
error={errors.user as FieldError | undefined}
/>
{/* Auth toggle handles pass + key_pem — both always registered */}
<SftpAuthToggle
register={register}
errors={{
pass: errors.pass as FieldError | undefined,
key_pem: errors.key_pem as FieldError | undefined,
}}
/>
</>
);
case 'gdrive':
return (
<>
<GdriveAuthToggle
register={register}
errors={{
token: errors.token as FieldError | undefined,
service_account_credentials: errors.service_account_credentials as FieldError | undefined,
}}
/>
{BACKEND_REGISTRY.gdrive.fields
.filter(f => f.key !== 'token' && f.key !== 'service_account_credentials')
.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))}
</>
);
case 'onedrive':
return (
<>
<OAuthInstructions
backendName="OneDrive"
authorizeCommand='rclone authorize "onedrive"'
/>
{BACKEND_REGISTRY.onedrive.fields.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))}
</>
);
case 'dropbox':
return (
<>
<OAuthInstructions
backendName="Dropbox"
authorizeCommand='rclone authorize "dropbox"'
/>
{BACKEND_REGISTRY.dropbox.fields.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))}
</>
);
case 'box':
return (
<>
<OAuthInstructions
backendName="Box"
authorizeCommand='rclone authorize "box"'
/>
{BACKEND_REGISTRY.box.fields.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))}
</>
);
case 'pcloud':
return (
<>
<OAuthInstructions
backendName="pCloud"
authorizeCommand='rclone authorize "pcloud"'
/>
{BACKEND_REGISTRY.pcloud.fields.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))}
</>
);
default:
// All other backends (azure-files, swift, ftp, webdav, smb, http, seafile, s3, s3-compatible, gcs, b2):
// generic FieldRenderer loop driven by registry
return (
<>
{BACKEND_REGISTRY[backendType].fields.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))}
</>
);
}
}
export function RemoteConfigStep() {
const { state, dispatch } = useWizard();
const backendType = state.remote.backendType;
@@ -50,16 +222,6 @@ export function RemoteConfigStep() {
}
}
const backendLabel: Record<NonNullable<typeof backendType>, string> = {
azureblob: 'Azure Blob Storage',
s3: 'Amazon S3',
's3-compatible': 'S3-Compatible Storage',
onedrive: 'OneDrive',
sftp: 'SFTP',
gcs: 'Google Cloud Storage',
b2: 'Backblaze B2',
};
return (
<div>
<h2 className="text-2xl font-bold text-on-surface mb-2">Step 2: Configure {backendLabel[backendType]}</h2>
@@ -68,56 +230,7 @@ export function RemoteConfigStep() {
generated rclone.conf and are never sent to any server.
</p>
<form onSubmit={handleSubmit(onNext, onInvalidSubmit)}>
{backendType === 'azureblob' ? (
<>
{/* Account field via FieldRenderer */}
<FieldRenderer
field={BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'account')!}
register={register}
error={errors.account as FieldError | undefined}
/>
{/* Auth toggle handles key + sas_url — both always registered */}
<AzureAuthToggle
register={register}
errors={{
key: errors.key as FieldError | undefined,
sas_url: errors.sas_url as FieldError | undefined,
}}
/>
</>
) : backendType === 'sftp' ? (
<>
{/* host and user via FieldRenderer */}
<FieldRenderer
field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'host')!}
register={register}
error={errors.host as FieldError | undefined}
/>
<FieldRenderer
field={BACKEND_REGISTRY.sftp.fields.find(f => f.key === 'user')!}
register={register}
error={errors.user as FieldError | undefined}
/>
{/* Auth toggle handles pass + key_pem — both always registered */}
<SftpAuthToggle
register={register}
errors={{
pass: errors.pass as FieldError | undefined,
key_pem: errors.key_pem as FieldError | undefined,
}}
/>
</>
) : (
/* All others (onedrive, gcs, b2, s3, s3-compatible): full registry loop */
BACKEND_REGISTRY[backendType].fields.map(field => (
<FieldRenderer
key={field.key}
field={field}
register={register}
error={errors[field.key] as FieldError | undefined}
/>
))
)}
{renderBackendFields(backendType, register, errors as Record<string, unknown>)}
<div className="flex flex-col sm:flex-row gap-3 mt-6">
<button
type="button"