feat(FN-1932): improve GitHub onboarding messaging and connect CTA
- Replace generic GitHub copy with a clear benefits list for issue, PR, and code-link workflows - Promote the unauthenticated connect experience into a dedicated CTA container with primary button, icon, and inline feedback states - Keep connected users focused on status and disconnect actions while hiding the connect CTA when already authenticated - Expand ModelOnboardingModal tests to cover fallback guidance, benefits rendering, CTA styling, and connected-state behavior
This commit is contained in:
@@ -1249,9 +1249,21 @@ export function ModelOnboardingModal({
|
||||
{step === "github" && (
|
||||
<div className="model-onboarding-github">
|
||||
<p className="model-onboarding-description">
|
||||
Linking GitHub lets you import issues as tasks and keep pull
|
||||
requests in sync with your work. This is entirely optional —
|
||||
Fusion works without it.
|
||||
Connect GitHub to unlock issue and PR integration:
|
||||
</p>
|
||||
<ul className="onboarding-github-benefits">
|
||||
<li>
|
||||
<strong>Import issues as tasks</strong> — Turn GitHub issues into trackable tasks
|
||||
</li>
|
||||
<li>
|
||||
<strong>Track pull requests</strong> — See PR status alongside your work
|
||||
</li>
|
||||
<li>
|
||||
<strong>Link code changes</strong> — Connect commits and PRs to tasks automatically
|
||||
</li>
|
||||
</ul>
|
||||
<p className="onboarding-helper-text">
|
||||
GitHub integration is entirely optional — Fusion works without it.
|
||||
</p>
|
||||
|
||||
{/* Skip-state banner: shown when AI setup was skipped */}
|
||||
@@ -1281,7 +1293,7 @@ export function ModelOnboardingModal({
|
||||
in Settings → Authentication.
|
||||
</p>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => setStep("first-task")}
|
||||
>
|
||||
Continue without GitHub →
|
||||
@@ -1304,51 +1316,61 @@ export function ModelOnboardingModal({
|
||||
: "Not connected"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{authActionInProgress === "github" ? (
|
||||
<>
|
||||
<button className="btn btn-sm" disabled>
|
||||
{isGithubAuthenticated
|
||||
? "Logging out…"
|
||||
: "Waiting for login…"}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => handleCancelLogin("github")}
|
||||
style={{ marginLeft: 8 }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</>
|
||||
) : isGithubAuthenticated ? (
|
||||
{isGithubAuthenticated && (
|
||||
authActionInProgress === "github" ? (
|
||||
<button className="btn btn-sm" disabled>
|
||||
Logging out…
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => handleLogout("github")}
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isGithubAuthenticated && (
|
||||
<div className="onboarding-github-connect-cta" data-testid="onboarding-github-connect-cta">
|
||||
{authActionInProgress === "github" ? (
|
||||
<div className="onboarding-github-connect-actions">
|
||||
<button className="btn btn-sm" disabled>
|
||||
Waiting for login…
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => handleCancelLogin("github")}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => handleLogin("github")}
|
||||
>
|
||||
<GitPullRequest size={16} />
|
||||
Connect
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Show timeout message */}
|
||||
{loginOutcomes["github"] === "timeout" && authActionInProgress !== "github" && (
|
||||
<p className="onboarding-helper-text onboarding-github-connect-feedback">
|
||||
Login timed out. Please try again.
|
||||
</p>
|
||||
)}
|
||||
{/* Show failure message */}
|
||||
{loginOutcomes["github"] === "failed" && authActionInProgress !== "github" && (
|
||||
<p className="field-error onboarding-github-connect-feedback">
|
||||
Login failed. Please try again.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* Show timeout message */}
|
||||
{loginOutcomes["github"] === "timeout" && authActionInProgress !== "github" && (
|
||||
<p className="onboarding-helper-text" style={{ marginTop: 4 }}>
|
||||
Login timed out. Please try again.
|
||||
</p>
|
||||
)}
|
||||
{/* Show failure message */}
|
||||
{loginOutcomes["github"] === "failed" && authActionInProgress !== "github" && (
|
||||
<p className="field-error" style={{ marginTop: 4 }}>
|
||||
Login failed. Please try again.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isGithubAuthenticated && (
|
||||
<p className="onboarding-helper-text">
|
||||
No worries if you're not ready — connect GitHub anytime from Settings → Authentication.
|
||||
|
||||
@@ -490,16 +490,17 @@ describe("ModelOnboardingModal", () => {
|
||||
});
|
||||
|
||||
describe("GitHub step", () => {
|
||||
it("shows optional guidance when GitHub provider is not configured", async () => {
|
||||
it("GitHub step shows fallback when no GitHub provider", async () => {
|
||||
render(<ModelOnboardingModal onComplete={vi.fn()} addToast={vi.fn()} />);
|
||||
|
||||
await navigateToGitHubStep();
|
||||
|
||||
expect(screen.getByText(/GitHub integration isn't set up yet/)).toBeTruthy();
|
||||
expect(screen.getByText("Continue without GitHub →")).toBeTruthy();
|
||||
expect(screen.getByText(/Settings → Authentication/)).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Continue without GitHub →" })).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows GitHub status and login/logout actions when GitHub provider is present", async () => {
|
||||
it("GitHub step shows benefits list with connect action", async () => {
|
||||
mockFetchAuthStatus.mockResolvedValueOnce({
|
||||
providers: [
|
||||
{ id: "github", name: "GitHub", authenticated: false, type: "oauth" },
|
||||
@@ -510,13 +511,18 @@ describe("ModelOnboardingModal", () => {
|
||||
|
||||
await navigateToGitHubStep();
|
||||
|
||||
// Use more specific selector to avoid matching the header title
|
||||
expect(screen.getByTestId("onboarding-auth-status-github")).toBeTruthy();
|
||||
expect(screen.getByText("Not connected")).toBeTruthy();
|
||||
expect(screen.getByText("Connect")).toBeTruthy();
|
||||
expect(screen.getByText("Connect GitHub to unlock issue and PR integration:")).toBeTruthy();
|
||||
expect(screen.getByText(/Import issues as tasks/)).toBeTruthy();
|
||||
expect(screen.getByText(/Track pull requests/)).toBeTruthy();
|
||||
expect(screen.getByText(/Link code changes/)).toBeTruthy();
|
||||
|
||||
const ctaContainer = screen.getByTestId("onboarding-github-connect-cta");
|
||||
expect(ctaContainer).toHaveClass("onboarding-github-connect-cta");
|
||||
const connectButton = screen.getByRole("button", { name: /Connect/ });
|
||||
expect(connectButton).toHaveClass("btn", "btn-primary", "btn-sm");
|
||||
});
|
||||
|
||||
it("shows connected status when GitHub is authenticated", async () => {
|
||||
it("GitHub step shows connected state when already authenticated", async () => {
|
||||
mockFetchAuthStatus.mockResolvedValueOnce({
|
||||
providers: [
|
||||
{ id: "github", name: "GitHub", authenticated: true, type: "oauth" },
|
||||
@@ -527,9 +533,11 @@ describe("ModelOnboardingModal", () => {
|
||||
|
||||
await navigateToGitHubStep();
|
||||
|
||||
expect(screen.getByText(/Import issues as tasks/)).toBeTruthy();
|
||||
expect(screen.getByTestId("onboarding-auth-status-github")).toBeTruthy();
|
||||
expect(screen.getByText("✓ Connected")).toBeTruthy();
|
||||
expect(screen.getByText("Disconnect")).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Disconnect" })).toBeTruthy();
|
||||
expect(screen.queryByTestId("onboarding-github-connect-cta")).toBeNull();
|
||||
});
|
||||
|
||||
it("allows navigating to First Task step via Continue without GitHub", async () => {
|
||||
|
||||
@@ -24109,6 +24109,54 @@ html .column.drag-over * {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* === Onboarding GitHub Step === */
|
||||
.onboarding-github-benefits {
|
||||
margin: 0;
|
||||
padding-left: var(--space-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.onboarding-github-benefits li {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.onboarding-github-benefits li strong {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.onboarding-github-connect-cta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-md);
|
||||
border: 1px solid color-mix(in srgb, var(--todo) 35%, var(--border));
|
||||
border-left: var(--space-xs) solid var(--todo);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--todo) 10%, transparent);
|
||||
}
|
||||
|
||||
.onboarding-github-connect-cta:focus-within {
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
.onboarding-github-connect-cta .btn {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.onboarding-github-connect-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.onboarding-github-connect-feedback {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.model-onboarding-github-optional {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user