Skip to content

Commit 3f9b3fb

Browse files
committed
feat(inbox): inline signal source config modal, rounded cards, aligned warming-up state
- "Enable Inbox" button opens a Dialog with SignalSourcesSettings instead of navigating to Settings - Dialog stays mounted even after sources are enabled (controlled open state) - X close button in dialog header, "Back to Inbox" primary button in footer - "Back to Inbox" disabled with tooltip when no source is enabled - Signal source toggle cards and evaluations section now have rounded corners - Warming-up state uses same hedgehog image and layout as empty state - Warming-up state opens the same signal sources modal instead of navigating away - "Configure sources" button and clickable source icons open the modal
1 parent 0a65286 commit 3f9b3fb

6 files changed

Lines changed: 623 additions & 277 deletions

File tree

115 KB
Loading

apps/code/src/renderer/features/inbox/components/DataSourceSetup.tsx

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,41 @@ interface SetupFormProps {
5151
onCancel: () => void;
5252
}
5353

54+
const POLL_INTERVAL_GITHUB_MS = 3_000;
55+
const POLL_TIMEOUT_GITHUB_MS = 300_000;
56+
5457
function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
5558
const projectId = useAuthStore((s) => s.projectId);
59+
const cloudRegion = useAuthStore((s) => s.cloudRegion);
5660
const client = useAuthStore((s) => s.client);
5761
const { githubIntegration, repositories, isLoadingRepos } =
5862
useRepositoryIntegration();
5963
const [repo, setRepo] = useState<string | null>(null);
6064
const [loading, setLoading] = useState(false);
65+
const [connecting, setConnecting] = useState(false);
66+
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
67+
const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
68+
69+
const stopPolling = useCallback(() => {
70+
if (pollTimerRef.current) {
71+
clearInterval(pollTimerRef.current);
72+
pollTimerRef.current = null;
73+
}
74+
if (pollTimeoutRef.current) {
75+
clearTimeout(pollTimeoutRef.current);
76+
pollTimeoutRef.current = null;
77+
}
78+
}, []);
79+
80+
useEffect(() => stopPolling, [stopPolling]);
81+
82+
// Stop polling once integration appears
83+
useEffect(() => {
84+
if (githubIntegration && connecting) {
85+
stopPolling();
86+
setConnecting(false);
87+
}
88+
}, [githubIntegration, connecting, stopPolling]);
6189

6290
// Auto-select the first repo once loaded
6391
useEffect(() => {
@@ -66,6 +94,47 @@ function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
6694
}
6795
}, [repo, repositories]);
6896

97+
const handleConnectGitHub = useCallback(async () => {
98+
if (!cloudRegion || !projectId) return;
99+
setConnecting(true);
100+
try {
101+
await trpcClient.githubIntegration.startFlow.mutate({
102+
region: cloudRegion,
103+
projectId,
104+
});
105+
106+
pollTimerRef.current = setInterval(async () => {
107+
try {
108+
if (!client) return;
109+
// Trigger a refetch of integrations
110+
const integrations =
111+
await client.getIntegrationsForProject(projectId);
112+
const hasGithub = integrations.some(
113+
(i: { kind: string }) => i.kind === "github",
114+
);
115+
if (hasGithub) {
116+
stopPolling();
117+
setConnecting(false);
118+
toast.success("GitHub connected");
119+
}
120+
} catch {
121+
// Ignore individual poll failures
122+
}
123+
}, POLL_INTERVAL_GITHUB_MS);
124+
125+
pollTimeoutRef.current = setTimeout(() => {
126+
stopPolling();
127+
setConnecting(false);
128+
toast.error("Connection timed out. Please try again.");
129+
}, POLL_TIMEOUT_GITHUB_MS);
130+
} catch (error) {
131+
setConnecting(false);
132+
toast.error(
133+
error instanceof Error ? error.message : "Failed to start GitHub flow",
134+
);
135+
}
136+
}, [cloudRegion, projectId, client, stopPolling]);
137+
69138
const handleSubmit = useCallback(async () => {
70139
if (!projectId || !client || !repo || !githubIntegration) return;
71140

@@ -96,10 +165,28 @@ function GitHubSetup({ onComplete, onCancel }: SetupFormProps) {
96165
if (!githubIntegration) {
97166
return (
98167
<SetupFormContainer title="Connect GitHub">
99-
<Text size="2" style={{ color: "var(--gray-11)" }}>
100-
No GitHub integration found. Please connect GitHub during onboarding
101-
first.
102-
</Text>
168+
<Flex direction="column" gap="3">
169+
<Text size="2" style={{ color: "var(--gray-11)" }}>
170+
Connect your GitHub account to import issues as signals.
171+
</Text>
172+
<Flex gap="2" justify="end">
173+
<Button
174+
size="2"
175+
variant="soft"
176+
onClick={onCancel}
177+
disabled={connecting}
178+
>
179+
Cancel
180+
</Button>
181+
<Button
182+
size="2"
183+
onClick={() => void handleConnectGitHub()}
184+
disabled={connecting}
185+
>
186+
{connecting ? "Waiting for authorization..." : "Connect GitHub"}
187+
</Button>
188+
</Flex>
189+
</Flex>
103190
</SetupFormContainer>
104191
);
105192
}

apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx

Lines changed: 108 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from "@features/inbox/utils/filterReports";
1919
import { INBOX_REFETCH_INTERVAL_MS } from "@features/inbox/utils/inboxConstants";
2020
import { useDraftStore } from "@features/message-editor/stores/draftStore";
21-
import { useSettingsDialogStore } from "@features/settings/stores/settingsDialogStore";
21+
import { SignalSourcesSettings } from "@features/settings/components/sections/SignalSourcesSettings";
2222
import { useCreateTask } from "@features/tasks/hooks/useTasks";
2323
import { useFeatureFlag } from "@hooks/useFeatureFlag";
2424
import { useRepositoryIntegration } from "@hooks/useIntegrations";
@@ -35,10 +35,12 @@ import {
3535
Badge,
3636
Box,
3737
Button,
38+
Dialog,
3839
Flex,
3940
ScrollArea,
4041
Select,
4142
Text,
43+
Tooltip,
4244
} from "@radix-ui/themes";
4345
import graphsHog from "@renderer/assets/images/graphs-hog.png";
4446
import { getCloudUrlFromRegion } from "@shared/constants/oauth";
@@ -124,7 +126,7 @@ export function InboxSignalsTab() {
124126
const statusFilter = useInboxSignalsFilterStore((s) => s.statusFilter);
125127
const { data: signalSourceConfigs } = useSignalSourceConfigs();
126128
const hasSignalSources = signalSourceConfigs?.some((c) => c.enabled) ?? false;
127-
const openSettings = useSettingsDialogStore((s) => s.open);
129+
const [sourcesDialogOpen, setSourcesDialogOpen] = useState(false);
128130

129131
const windowFocused = useRendererWindowFocusStore((s) => s.focused);
130132
const isInboxView = useNavigationStore((s) => s.view.type === "inbox");
@@ -324,81 +326,126 @@ export function InboxSignalsTab() {
324326
);
325327
}
326328

329+
const sourcesDialog = (
330+
<Dialog.Root open={sourcesDialogOpen} onOpenChange={setSourcesDialogOpen}>
331+
<Dialog.Content maxWidth="520px">
332+
<Flex align="center" justify="between" mb="3">
333+
<Dialog.Title size="3" mb="0">
334+
Signal sources
335+
</Dialog.Title>
336+
<Dialog.Close>
337+
<button
338+
type="button"
339+
className="rounded p-1 text-gray-11 hover:bg-gray-3 hover:text-gray-12"
340+
aria-label="Close"
341+
>
342+
<XIcon size={16} />
343+
</button>
344+
</Dialog.Close>
345+
</Flex>
346+
<SignalSourcesSettings />
347+
<Flex justify="end" mt="4">
348+
{hasSignalSources ? (
349+
<Dialog.Close>
350+
<Button size="2">Back to Inbox</Button>
351+
</Dialog.Close>
352+
) : (
353+
<Tooltip content="You haven't enabled any signal source yet!">
354+
<Button size="2" disabled>
355+
Back to Inbox
356+
</Button>
357+
</Tooltip>
358+
)}
359+
</Flex>
360+
</Dialog.Content>
361+
</Dialog.Root>
362+
);
363+
327364
if (allReports.length === 0) {
328365
if (!hasSignalSources) {
329366
return (
330-
<Flex
331-
direction="column"
332-
align="center"
333-
justify="center"
334-
height="100%"
335-
px="5"
336-
style={{ margin: "0 auto" }}
337-
>
338-
<Flex direction="column" align="center" style={{ maxWidth: 420 }}>
339-
<img
340-
src={graphsHog}
341-
alt=""
342-
style={{ width: 128, marginBottom: 20 }}
343-
/>
344-
345-
<Text
346-
size="4"
347-
weight="bold"
348-
align="center"
349-
style={{ color: "var(--gray-12)" }}
350-
>
351-
Welcome to your Inbox
352-
</Text>
367+
<>
368+
<Flex
369+
direction="column"
370+
align="center"
371+
justify="center"
372+
height="100%"
373+
px="5"
374+
style={{ margin: "0 auto" }}
375+
>
376+
<Flex direction="column" align="center" style={{ maxWidth: 420 }}>
377+
<img
378+
src={graphsHog}
379+
alt=""
380+
style={{ width: 128, marginBottom: 20 }}
381+
/>
353382

354-
<Flex
355-
direction="column"
356-
align="center"
357-
gap="3"
358-
mt="3"
359-
style={{ maxWidth: 360 }}
360-
>
361383
<Text
362-
size="1"
384+
size="4"
385+
weight="bold"
363386
align="center"
364-
style={{ color: "var(--gray-11)", lineHeight: 1.35 }}
387+
style={{ color: "var(--gray-12)" }}
365388
>
366-
<Text weight="medium" style={{ color: "var(--gray-12)" }}>
367-
Background analysis of your data — while you sleep.
368-
</Text>
369-
<br />
370-
Session recordings watched automatically. Issues, tickets, and
371-
evals analyzed around the clock.
389+
Welcome to your Inbox
372390
</Text>
373391

374-
<ArrowDownIcon size={14} style={{ color: "var(--gray-8)" }} />
375-
376-
<Text
377-
size="1"
392+
<Flex
393+
direction="column"
378394
align="center"
379-
style={{ color: "var(--gray-11)", lineHeight: 1.35 }}
395+
gap="3"
396+
mt="3"
397+
style={{ maxWidth: 360 }}
380398
>
381-
<Text weight="medium" style={{ color: "var(--gray-12)" }}>
382-
Ready-to-run fixes for real user problems.
399+
<Text
400+
size="1"
401+
align="center"
402+
style={{ color: "var(--gray-11)", lineHeight: 1.35 }}
403+
>
404+
<Text weight="medium" style={{ color: "var(--gray-12)" }}>
405+
Background analysis of your data — while you sleep.
406+
</Text>
407+
<br />
408+
Session recordings watched automatically. Issues, tickets, and
409+
evals analyzed around the clock.
383410
</Text>
384-
<br />
385-
Each report includes evidence and impact numbers — just execute
386-
the prompt in your agent.
387-
</Text>
388-
</Flex>
389411

390-
<Button
391-
size="2"
392-
style={{ marginTop: 20 }}
393-
onClick={() => openSettings("signals")}
394-
>
395-
Enable Inbox
396-
</Button>
412+
<ArrowDownIcon size={14} style={{ color: "var(--gray-8)" }} />
413+
414+
<Text
415+
size="1"
416+
align="center"
417+
style={{ color: "var(--gray-11)", lineHeight: 1.35 }}
418+
>
419+
<Text weight="medium" style={{ color: "var(--gray-12)" }}>
420+
Ready-to-run fixes for real user problems.
421+
</Text>
422+
<br />
423+
Each report includes evidence and impact numbers — just
424+
execute the prompt in your agent.
425+
</Text>
426+
</Flex>
427+
428+
<Button
429+
size="2"
430+
style={{ marginTop: 20 }}
431+
onClick={() => setSourcesDialogOpen(true)}
432+
>
433+
Enable Inbox
434+
</Button>
435+
</Flex>
397436
</Flex>
398-
</Flex>
437+
{sourcesDialog}
438+
</>
399439
);
400440
}
401-
return <InboxWarmingUpState />;
441+
return (
442+
<>
443+
<InboxWarmingUpState
444+
onConfigureSources={() => setSourcesDialogOpen(true)}
445+
/>
446+
{sourcesDialog}
447+
</>
448+
);
402449
}
403450

404451
return (

0 commit comments

Comments
 (0)