diff --git a/src/components/wizard/OAuthInstructions.test.tsx b/src/components/wizard/OAuthInstructions.test.tsx
new file mode 100644
index 0000000..f15093b
--- /dev/null
+++ b/src/components/wizard/OAuthInstructions.test.tsx
@@ -0,0 +1,58 @@
+// @vitest-environment jsdom
+// Tests for OAuthInstructions — collapsible OAuth step-by-step guide
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { OAuthInstructions } from './OAuthInstructions';
+
+describe('OAuthInstructions', () => {
+ it('renders collapsed by default (instructions not visible)', () => {
+ render(
+
+ );
+ // The numbered steps should not be visible in collapsed state
+ expect(screen.queryByText(/Install rclone/i)).toBeNull();
+ });
+
+ it('expands when user clicks the toggle button, showing numbered steps', async () => {
+ const user = userEvent.setup();
+ render(
+
+ );
+ const toggleBtn = screen.getByRole('button');
+ await user.click(toggleBtn);
+ // After expanding, steps should be visible
+ expect(screen.getByText(/Install rclone/i)).toBeDefined();
+ });
+
+ it('shows the backend-specific authorizeCommand in the expanded content', async () => {
+ const user = userEvent.setup();
+ const cmd = 'rclone authorize "drive"';
+ render(
+
+ );
+ const toggleBtn = screen.getByRole('button');
+ await user.click(toggleBtn);
+ expect(screen.getByText(cmd)).toBeDefined();
+ });
+
+ it('shows backend name in the toggle button text', () => {
+ render(
+
+ );
+ const toggleBtn = screen.getByRole('button');
+ expect(toggleBtn.textContent).toContain('Dropbox');
+ });
+});
diff --git a/src/components/wizard/OAuthInstructions.tsx b/src/components/wizard/OAuthInstructions.tsx
new file mode 100644
index 0000000..19c0529
--- /dev/null
+++ b/src/components/wizard/OAuthInstructions.tsx
@@ -0,0 +1,69 @@
+import { useState } from 'react';
+
+interface OAuthInstructionsProps {
+ backendName: string; // e.g. "Google Drive"
+ authorizeCommand: string; // e.g. 'rclone authorize "drive"'
+ steps?: string[]; // optional custom steps (defaults to generic OAuth flow)
+}
+
+const DEFAULT_STEPS = (authorizeCommand: string): string[] => [
+ 'Install rclone on a machine that has a web browser.',
+ `Run the authorize command: ${authorizeCommand}`,
+ 'A browser window will open — authenticate with your account.',
+ 'Copy the JSON token printed in the terminal.',
+ 'Paste the token into the field above.',
+];
+
+export function OAuthInstructions({ backendName, authorizeCommand, steps }: OAuthInstructionsProps) {
+ const [expanded, setExpanded] = useState(false);
+
+ const resolvedSteps = steps ?? DEFAULT_STEPS(authorizeCommand);
+
+ return (
+
+
+
+ {expanded && (
+
+
+ {resolvedSteps.map((step, index) => {
+ // The second step contains the command — render it with a code block
+ if (step.startsWith('Run the authorize command:')) {
+ return (
+
+ Run the authorize command:{' '}
+
+ {authorizeCommand}
+
+