From 20a51978e5ae7cbdff45f3fa88a4c03c00f674d2 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:15:03 -0700 Subject: [PATCH] Polish setup guide integration layout (#5856) --- config/scripts/locale-ko-key-overrides.json | 3 - .../feature-wall/ConnectIntegrationsList.tsx | 186 +++++++++--------- .../FeatureWallSetupChecklist.tsx | 8 +- .../feature-wall/connect-integration-step.tsx | 22 +-- .../cli-source-control-integration-cards.tsx | 17 +- .../integration-card-presentation.tsx | 70 +++++++ .../settings/integration-card-shell.tsx | 9 +- .../settings/jira-integration-card.tsx | 11 +- .../task-tracker-integration-cards.tsx | 12 +- src/renderer/src/i18n/locales/en.json | 1 - src/renderer/src/i18n/locales/es.json | 1 - src/renderer/src/i18n/locales/ja.json | 1 - src/renderer/src/i18n/locales/ko.json | 1 - src/renderer/src/i18n/locales/zh.json | 1 - 14 files changed, 194 insertions(+), 149 deletions(-) create mode 100644 src/renderer/src/components/settings/integration-card-presentation.tsx diff --git a/config/scripts/locale-ko-key-overrides.json b/config/scripts/locale-ko-key-overrides.json index bafc999aad5..071a249e01c 100644 --- a/config/scripts/locale-ko-key-overrides.json +++ b/config/scripts/locale-ko-key-overrides.json @@ -1007,9 +1007,6 @@ "auto.components.feature.wall.ConnectIntegrationsList.33b650af52": { "ko": "팀이 작업을 추적하는 서비스를 연결하세요. Orca는 이슈 제목, 링크, 컨텍스트가 이미 첨부된 워크스페이스를 시작합니다." }, - "auto.components.feature.wall.ConnectIntegrationsList.3a1fcdddad": { - "ko": "두 단계만 거치면 됩니다. 먼저 코드 리뷰 서비스를 연결한 다음, 팀이 작업을 계획하는 서비스를 연결하세요." - }, "auto.components.feature.wall.ConnectIntegrationsList.3dddb2d565": { "ko": "작업용으로 연결됨" }, diff --git a/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx b/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx index cd378eb3ff2..21f504dcfb0 100644 --- a/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx +++ b/src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx @@ -10,8 +10,12 @@ import { JiraIntegrationCard, LinearIntegrationCard } from '@/components/settings/task-tracker-integration-cards' +import { + IntegrationCardGroup, + IntegrationCardPresentationProvider +} from '@/components/settings/integration-card-presentation' import { useIntegrationProviderStatusRefresh } from '@/components/settings/use-integration-provider-status-refresh' -import { IntegrationProgress, IntegrationStep } from './connect-integration-step' +import { IntegrationStep } from './connect-integration-step' import { deriveIntegrationFlowState, useIntegrationConnectionStatus @@ -91,100 +95,98 @@ export function ConnectIntegrationsList(): React.JSX.Element { const taskExpanded = taskToggleCurrent ? taskToggle.expanded : reviewDone && !trackerDone return ( -
-
-

- {translate( - 'auto.components.feature.wall.ConnectIntegrationsList.3a1fcdddad', - 'Two quick steps: connect where your code is reviewed, then where your team plans work.' + +

+ - -
+ description={translate( + 'auto.components.feature.wall.ConnectIntegrationsList.review_step_description', + 'Connect a review provider so Orca can show PR or MR status, checks, and reviews.' + )} + summary={ + <> + {status.reviewProviderName}{' '} + {translate( + 'auto.components.feature.wall.ConnectIntegrationsList.5b3577a492', + 'connected for review status' + )} + + } + onToggle={() => setReviewReopened((value) => !value)} + canToggle={reviewCanToggle} + > + + + + + + + + - - {status.reviewProviderName}{' '} + + {' '} + {translate( + 'auto.components.feature.wall.ConnectIntegrationsList.3dddb2d565', + 'connected for tasks' + )} + + ) : ( + <> + + {status.codeHostTaskProviderName} + {' '} + {translate( + 'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_summary', + 'issues available as tasks · add Linear or Jira if your team plans work there' + )} + + ) + } + onToggle={() => + setTaskToggle({ + expanded: !taskExpanded, + whenTrackerDone: trackerDone, + whenReviewDone: reviewDone + }) + } + > + + + + +

{translate( - 'auto.components.feature.wall.ConnectIntegrationsList.5b3577a492', - 'connected for review status' + 'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_caption', + "Your code host's issues also work as tasks." )} - - } - onToggle={() => setReviewReopened((value) => !value)} - canToggle={reviewCanToggle} - > - - - - - - - - - {' '} - {translate( - 'auto.components.feature.wall.ConnectIntegrationsList.3dddb2d565', - 'connected for tasks' - )} - - ) : ( - <> - - {status.codeHostTaskProviderName} - {' '} - {translate( - 'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_summary', - 'issues available as tasks · add Linear or Jira if your team plans work there' - )} - - ) - } - onToggle={() => - setTaskToggle({ - expanded: !taskExpanded, - whenTrackerDone: trackerDone, - whenReviewDone: reviewDone - }) - } - > - - -

- {translate( - 'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_caption', - "Your code host's issues also work as tasks." - )} -

- - -
-
+

+ + + + + +
+ ) } diff --git a/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx b/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx index 76ef6bacf42..ead0f742795 100644 --- a/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx +++ b/src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx @@ -279,19 +279,21 @@ export function FeatureWallSetupChecklist( ? 'gap-8 xl:grid-cols-[minmax(0,1fr)_auto] xl:gap-12' : 'gap-8 sm:grid-cols-[minmax(0,48ch)_auto] sm:gap-10' + // Why: in the modal, a stacked checklist can leave medium-width windows with + // only a tiny action pane; switch to the rail/content layout sooner. return (
{activeStep ? ( diff --git a/src/renderer/src/components/feature-wall/connect-integration-step.tsx b/src/renderer/src/components/feature-wall/connect-integration-step.tsx index d63b53421a9..fa2c3e71b2e 100644 --- a/src/renderer/src/components/feature-wall/connect-integration-step.tsx +++ b/src/renderer/src/components/feature-wall/connect-integration-step.tsx @@ -92,28 +92,8 @@ export function IntegrationStep(props: { ) : null} {expanded ? ( -
{props.children}
+
{props.children}
) : null}
) } - -// Two progress dots tracking step state; the active one stretches into a bar. -export function IntegrationProgress(props: { - states: readonly IntegrationStepState[] -}): React.JSX.Element { - return ( -
- {props.states.map((state, i) => ( - - ))} -
- ) -} diff --git a/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx b/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx index 7d8d8c564c8..70af9e76c05 100644 --- a/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx +++ b/src/renderer/src/components/settings/cli-source-control-integration-cards.tsx @@ -2,15 +2,15 @@ import { ExternalLink, Github, Gitlab, Terminal } from 'lucide-react' import { Button } from '@/components/ui/button' import { useAppStore } from '@/store' import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell' +import { + useIntegrationCommandRowClass, + useIntegrationSubordinateRowClass +} from './integration-card-presentation' import { getProviderAccountScope } from './provider-account-scope' import { ProviderHostScopeControl } from './ProviderHostScopeControl' import { usePreflightCardStatuses } from './source-control-preflight-card-status' import { translate } from '@/i18n/i18n' -const INTEGRATION_COMMAND_ROW_CLASS = - 'flex items-center gap-2 rounded-md border border-border/50 bg-muted/50 px-3 py-2 font-mono text-xs' -const INTEGRATION_SUBORDINATE_ROW_CLASS = 'rounded-md border border-border/50 bg-muted/50 px-3 py-2' - function ProviderAccountScopeDetails({ children }: { @@ -18,6 +18,7 @@ function ProviderAccountScopeDetails({ }): React.JSX.Element { const settings = useAppStore((s) => s.settings) const accountScope = getProviderAccountScope(settings) + const subordinateRowClass = useIntegrationSubordinateRowClass('text-xs') return ( @@ -27,7 +28,7 @@ function ProviderAccountScopeDetails({ 'Account scope' )} scope={accountScope} - className={`text-xs ${INTEGRATION_SUBORDINATE_ROW_CLASS}`} + className={subordinateRowClass} /> {children} @@ -38,6 +39,7 @@ export function GitHubIntegrationCard(): React.JSX.Element { const { statuses, unavailable, refresh } = usePreflightCardStatuses('gh') const status = unavailable ? 'unavailable' : statuses.ghStatus const connected = status === 'connected' + const commandRowClass = useIntegrationCommandRowClass() return ( -
+
{translate( 'auto.components.settings.cli.source.control.integration.cards.8d90249d22', @@ -166,6 +168,7 @@ export function GitLabIntegrationCard(): React.JSX.Element { const { statuses, unavailable, refresh } = usePreflightCardStatuses('glab') const status = unavailable ? 'unavailable' : statuses.glabStatus const connected = status === 'connected' + const commandRowClass = useIntegrationCommandRowClass() return ( -
+
{translate( 'auto.components.settings.cli.source.control.integration.cards.707180d09c', diff --git a/src/renderer/src/components/settings/integration-card-presentation.tsx b/src/renderer/src/components/settings/integration-card-presentation.tsx new file mode 100644 index 00000000000..b67c339abfd --- /dev/null +++ b/src/renderer/src/components/settings/integration-card-presentation.tsx @@ -0,0 +1,70 @@ +import { createContext, useContext } from 'react' +import { cn } from '@/lib/utils' + +type IntegrationCardPresentation = 'default' | 'setup-guide' + +const IntegrationCardPresentationContext = createContext('default') + +export function IntegrationCardPresentationProvider(props: { + value: IntegrationCardPresentation + children: React.ReactNode +}): React.JSX.Element { + return ( + + {props.children} + + ) +} + +export function useIntegrationCardPresentation(): IntegrationCardPresentation { + return useContext(IntegrationCardPresentationContext) +} + +export function useIntegrationCardShellClass(className?: string): string { + const presentation = useIntegrationCardPresentation() + return cn( + presentation === 'setup-guide' + ? 'bg-transparent px-4 py-3' + : 'rounded-xl border border-border bg-card px-4 py-3.5 shadow-xs', + className + ) +} + +export function IntegrationCardGroup(props: { + children: React.ReactNode + className?: string +}): React.JSX.Element { + const presentation = useIntegrationCardPresentation() + return ( +
+ {props.children} +
+ ) +} + +export function useIntegrationSubordinateRowClass(className?: string): string { + const presentation = useIntegrationCardPresentation() + return cn( + presentation === 'setup-guide' + ? 'border-t border-border/40 px-0 py-2 first:border-t-0' + : 'rounded-md border border-border/50 bg-muted/50 px-3 py-2', + className + ) +} + +export function useIntegrationCommandRowClass(): string { + const presentation = useIntegrationCardPresentation() + return cn( + 'flex items-center gap-2 font-mono text-xs', + presentation === 'setup-guide' + ? 'border-t border-border/40 px-0 py-2' + : 'rounded-md border border-border/50 bg-muted/50 px-3 py-2' + ) +} diff --git a/src/renderer/src/components/settings/integration-card-shell.tsx b/src/renderer/src/components/settings/integration-card-shell.tsx index 0087e797459..54c288d4258 100644 --- a/src/renderer/src/components/settings/integration-card-shell.tsx +++ b/src/renderer/src/components/settings/integration-card-shell.tsx @@ -1,5 +1,6 @@ import { LoaderCircle } from 'lucide-react' import { cn } from '@/lib/utils' +import { useIntegrationCardShellClass } from './integration-card-presentation' export type IntegrationCardStatusTone = 'connected' | 'attention' | 'neutral' @@ -20,6 +21,7 @@ export function IntegrationCardShell(props: { actions?: React.ReactNode children?: React.ReactNode }): React.JSX.Element { + const shellClass = useIntegrationCardShellClass(props.className) const status = props.checking ? ( ) : ( @@ -34,12 +36,7 @@ export function IntegrationCardShell(props: { ) return ( -
+
{props.icon}
diff --git a/src/renderer/src/components/settings/jira-integration-card.tsx b/src/renderer/src/components/settings/jira-integration-card.tsx index e79a696754a..0501d05faf1 100644 --- a/src/renderer/src/components/settings/jira-integration-card.tsx +++ b/src/renderer/src/components/settings/jira-integration-card.tsx @@ -10,12 +10,12 @@ import { } from '@/lib/provider-runtime-context' import { useAppStore } from '@/store' import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell' +import { useIntegrationSubordinateRowClass } from './integration-card-presentation' import { getProviderAccountScope } from './provider-account-scope' import { ProviderHostScopeControl } from './ProviderHostScopeControl' import { translate } from '@/i18n/i18n' type VerificationResult = { state: 'ok' | 'error'; error?: string } -const INTEGRATION_SUBORDINATE_ROW_CLASS = 'rounded-md border border-border/50 bg-muted/50 px-3 py-2' export function JiraIntegrationCard(): React.JSX.Element { const jiraStatus = useAppStore((s) => s.jiraStatus) @@ -40,6 +40,8 @@ export function JiraIntegrationCard(): React.JSX.Element { const credentialCopy = hasRemoteProviderRuntime(settings) ? 'Connect a Jira Cloud site with your Atlassian email and an API token. Credentials are sent to the selected remote runtime and stored there with runtime-supported encryption.' : 'Connect a Jira Cloud site with your Atlassian email and an API token. Credentials are stored locally and encrypted when local runtime storage supports it.' + const subordinateRowClass = useIntegrationSubordinateRowClass('flex items-center gap-3') + const accountScopeRowClass = useIntegrationSubordinateRowClass('text-xs') const handleDisconnect = async (siteId?: string): Promise => { await disconnectJira(siteId) @@ -117,7 +119,7 @@ export function JiraIntegrationCard(): React.JSX.Element { 'Account scope' )} scope={accountScope} - className={`text-xs ${INTEGRATION_SUBORDINATE_ROW_CLASS}`} + className={accountScopeRowClass} /> {connected && sites.length > 0 ? (
@@ -125,10 +127,7 @@ export function JiraIntegrationCard(): React.JSX.Element { const testResult = testResultBySite[site.id] const testing = testingSiteId === site.id return ( -
+

{site.displayName} diff --git a/src/renderer/src/components/settings/task-tracker-integration-cards.tsx b/src/renderer/src/components/settings/task-tracker-integration-cards.tsx index 40c9aa4a22a..aca7be03f84 100644 --- a/src/renderer/src/components/settings/task-tracker-integration-cards.tsx +++ b/src/renderer/src/components/settings/task-tracker-integration-cards.tsx @@ -7,12 +7,12 @@ import { useMountedRef } from '@/hooks/useMountedRef' import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' import { useAppStore } from '@/store' import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell' +import { useIntegrationSubordinateRowClass } from './integration-card-presentation' import { getProviderAccountScope } from './provider-account-scope' import { ProviderHostScopeControl } from './ProviderHostScopeControl' import { translate } from '@/i18n/i18n' type VerificationResult = { state: 'ok' | 'error'; error?: string } -const INTEGRATION_SUBORDINATE_ROW_CLASS = 'rounded-md border border-border/50 bg-muted/50 px-3 py-2' export function LinearIntegrationCard(): React.JSX.Element { const linearStatus = useAppStore((s) => s.linearStatus) @@ -36,6 +36,7 @@ export function LinearIntegrationCard(): React.JSX.Element { const connected = contextMatches && linearStatus.connected const workspaces = linearStatus.workspaces ?? [] const accountScope = getProviderAccountScope(settings) + const subordinateRowClass = useIntegrationSubordinateRowClass('flex items-center gap-3') const handleDisconnect = async (workspaceId?: string): Promise => { await (workspaceId ? disconnectLinearWorkspace(workspaceId) : disconnectLinear()) @@ -116,10 +117,7 @@ export function LinearIntegrationCard(): React.JSX.Element { const testResult = testResultByWorkspace[workspace.id] const testing = testingWorkspaceId === workspace.id return ( -

+

{workspace.organizationName} @@ -217,6 +215,8 @@ export function LinearIntegrationCard(): React.JSX.Element { } function ProviderAccountScopeRow({ scope }: { scope: ReturnType }) { + const subordinateRowClass = useIntegrationSubordinateRowClass('text-xs') + return ( ) } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 7b386f780ce..20488fa7ea9 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -10146,7 +10146,6 @@ "3dddb2d565": "connected for tasks", "33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.", "5b3577a492": "connected for review status", - "3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.", "list_end": ", and ", "list_pair": " and ", "list_mid": ", ", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 2e97c7df5a4..23d644fa99f 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -10146,7 +10146,6 @@ "3dddb2d565": "connected for tasks", "33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.", "5b3577a492": "connected for review status", - "3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.", "list_end": ", and ", "list_pair": " and ", "list_mid": ", ", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 07368bf8558..dcce2ac18ed 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -10146,7 +10146,6 @@ "3dddb2d565": "connected for tasks", "33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.", "5b3577a492": "connected for review status", - "3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.", "list_end": ", and ", "list_pair": " and ", "list_mid": ", ", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index d2297f98aa5..31d7c09eb57 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -10146,7 +10146,6 @@ "3dddb2d565": "작업용으로 연결됨", "33b650af52": "팀이 작업을 추적하는 서비스를 연결하세요. Orca는 이슈 제목, 링크, 컨텍스트가 이미 첨부된 워크스페이스를 시작합니다.", "5b3577a492": "리뷰 상태용으로 연결됨", - "3a1fcdddad": "두 단계만 거치면 됩니다. 먼저 코드 리뷰 서비스를 연결한 다음, 팀이 작업을 계획하는 서비스를 연결하세요.", "list_end": ", 그리고 ", "list_pair": " and ", "list_mid": ", ", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 388e543e47f..a2a27399a9d 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -10146,7 +10146,6 @@ "3dddb2d565": "connected for tasks", "33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.", "5b3577a492": "connected for review status", - "3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.", "list_end": ", and ", "list_pair": " and ", "list_mid": ", ",