Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 35e2492

Browse files
feat: add data source configuration and API key storage
- Add data source selection in auth screen (PostHog, call recording) - Add OpenAI API key input and secure storage for transcription - Update auth flow to support enabling recording-only mode Users can now choose which data sources to enable. OpenAI key is encrypted using Electron's safeStorage before persistence. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 71486d3 commit 35e2492

2 files changed

Lines changed: 191 additions & 70 deletions

File tree

‎src/renderer/components/AuthScreen.tsx‎

Lines changed: 112 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import {
33
Button,
44
Callout,
55
Card,
6+
Checkbox,
67
Container,
78
Flex,
89
Heading,
910
Text,
1011
TextField,
1112
} from "@radix-ui/themes";
13+
import type { DataSource } from "@shared/types";
1214
import type React from "react";
1315
import { useId, useState } from "react";
1416
import { useAuthStore } from "../stores/authStore";
@@ -17,20 +19,48 @@ import { AsciiArt } from "./AsciiArt";
1719
export function AuthScreen() {
1820
const apiKeyId = useId();
1921
const apiHostId = useId();
22+
const [selectedSources, setSelectedSources] = useState<DataSource[]>([
23+
"posthog",
24+
]);
2025
const [apiKey, setApiKey] = useState("");
2126
const [apiHost, setApiHost] = useState("https://app.posthog.com");
2227
const [error, setError] = useState("");
2328
const [isLoading, setIsLoading] = useState(false);
2429

25-
const { setCredentials } = useAuthStore();
30+
const { setCredentials, setEnabledSources } = useAuthStore();
31+
32+
const toggleSource = (source: DataSource) => {
33+
setSelectedSources((prev) =>
34+
prev.includes(source)
35+
? prev.filter((s) => s !== source)
36+
: [...prev, source],
37+
);
38+
};
2639

2740
const handleSubmit = async (e: React.FormEvent) => {
2841
e.preventDefault();
2942
setError("");
3043
setIsLoading(true);
3144

3245
try {
33-
await setCredentials(apiKey, apiHost);
46+
if (selectedSources.length === 0) {
47+
throw new Error("Please select at least one data source");
48+
}
49+
50+
// Save enabled sources
51+
setEnabledSources(selectedSources);
52+
53+
// If PostHog is selected, validate credentials
54+
if (selectedSources.includes("posthog")) {
55+
if (!apiKey) {
56+
throw new Error("PostHog API key is required");
57+
}
58+
await setCredentials(apiKey, apiHost);
59+
}
60+
61+
// For non-PostHog sources or successful PostHog auth, reload the page
62+
// to trigger the authentication check
63+
window.location.reload();
3464
} catch (err) {
3565
setError(err instanceof Error ? err.message : "Failed to authenticate");
3666
} finally {
@@ -57,65 +87,103 @@ export function AuthScreen() {
5787

5888
<form onSubmit={handleSubmit}>
5989
<Flex direction="column" gap="4">
60-
<Flex direction="column" gap="6">
90+
{/* Data Source Selection */}
91+
<Flex direction="column" gap="3">
92+
<Text size="2" weight="medium" color="gray">
93+
Select Data Sources
94+
</Text>
6195
<Flex direction="column" gap="2">
62-
<Text
63-
as="label"
64-
htmlFor="apiKey"
65-
size="2"
66-
weight="medium"
67-
color="gray"
68-
>
69-
Personal API Key
96+
<Text as="label" size="2">
97+
<Flex gap="2" align="center">
98+
<Checkbox
99+
checked={selectedSources.includes("posthog")}
100+
onCheckedChange={() => toggleSource("posthog")}
101+
/>
102+
PostHog
103+
</Flex>
70104
</Text>
71-
<TextField.Root
72-
id={apiKeyId}
73-
type="password"
74-
value={apiKey}
75-
onChange={(e) => setApiKey(e.target.value)}
76-
placeholder="phx_..."
77-
required
78-
/>
79-
<Text size="1" color="gray">
80-
Get your API key from PostHog settings
105+
<Text as="label" size="2">
106+
<Flex gap="2" align="center">
107+
<Checkbox
108+
checked={selectedSources.includes(
109+
"call_recording",
110+
)}
111+
onCheckedChange={() =>
112+
toggleSource("call_recording")
113+
}
114+
/>
115+
Call Recording
116+
</Flex>
81117
</Text>
82118
</Flex>
119+
</Flex>
83120

84-
<Flex direction="column" gap="2">
85-
<Text
86-
as="label"
87-
htmlFor="apiHost"
88-
size="2"
89-
weight="medium"
90-
color="gray"
91-
>
92-
PostHog Instance URL
93-
</Text>
94-
<TextField.Root
95-
id={apiHostId}
96-
type="url"
97-
value={apiHost}
98-
onChange={(e) => setApiHost(e.target.value)}
99-
placeholder="https://app.posthog.com"
100-
required
101-
/>
121+
{/* PostHog Credentials - only show if PostHog is selected */}
122+
{selectedSources.includes("posthog") && (
123+
<Flex direction="column" gap="6">
124+
<Flex direction="column" gap="2">
125+
<Text
126+
as="label"
127+
htmlFor="apiKey"
128+
size="2"
129+
weight="medium"
130+
color="gray"
131+
>
132+
Personal API Key
133+
</Text>
134+
<TextField.Root
135+
id={apiKeyId}
136+
type="password"
137+
value={apiKey}
138+
onChange={(e) => setApiKey(e.target.value)}
139+
placeholder="phx_..."
140+
required={selectedSources.includes("posthog")}
141+
/>
142+
<Text size="1" color="gray">
143+
Get your API key from PostHog settings
144+
</Text>
145+
</Flex>
146+
147+
<Flex direction="column" gap="2">
148+
<Text
149+
as="label"
150+
htmlFor="apiHost"
151+
size="2"
152+
weight="medium"
153+
color="gray"
154+
>
155+
PostHog Instance URL
156+
</Text>
157+
<TextField.Root
158+
id={apiHostId}
159+
type="url"
160+
value={apiHost}
161+
onChange={(e) => setApiHost(e.target.value)}
162+
placeholder="https://app.posthog.com"
163+
required={selectedSources.includes("posthog")}
164+
/>
165+
</Flex>
102166
</Flex>
103-
</Flex>
167+
)}
104168

105-
{error && (
169+
{error ? (
106170
<Callout.Root color="red">
107171
<Callout.Text>{error}</Callout.Text>
108172
</Callout.Root>
109-
)}
173+
) : null}
110174

111175
<Button
112176
type="submit"
113-
disabled={isLoading || !apiKey}
177+
disabled={
178+
isLoading ||
179+
selectedSources.length === 0 ||
180+
(selectedSources.includes("posthog") && !apiKey)
181+
}
114182
variant="classic"
115183
size="3"
116184
mt="4"
117185
>
118-
{isLoading ? "Connecting..." : "Connect"}
186+
{isLoading ? "Connecting..." : "Continue"}
119187
</Button>
120188
</Flex>
121189
</form>

‎src/renderer/stores/authStore.ts‎

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
import { PostHogAPIClient } from "@api/posthogClient";
2-
import type { User } from "@shared/types";
2+
import type { DataSource, User } from "@shared/types";
33
import { create } from "zustand";
44
import { persist } from "zustand/middleware";
55

66
interface AuthState {
7+
// Data sources
8+
enabledSources: DataSource[];
9+
setEnabledSources: (sources: DataSource[]) => void;
10+
11+
// PostHog credentials
712
apiKey: string | null;
813
apiHost: string;
914
encryptedKey: string | null;
1015
user: User | null;
1116
isAuthenticated: boolean;
1217
client: PostHogAPIClient | null;
1318

19+
// OpenAI credentials
20+
openaiApiKey: string | null;
21+
encryptedOpenaiKey: string | null;
22+
setOpenaiKey: (apiKey: string) => Promise<void>;
23+
1424
setCredentials: (apiKey: string, apiHost: string) => Promise<void>;
1525
checkAuth: () => Promise<boolean>;
1626
logout: () => void;
@@ -19,12 +29,28 @@ interface AuthState {
1929
export const useAuthStore = create<AuthState>()(
2030
persist(
2131
(set, get) => ({
32+
enabledSources: [],
2233
apiKey: null,
2334
apiHost: "https://app.posthog.com",
2435
encryptedKey: null,
2536
user: null,
2637
isAuthenticated: false,
2738
client: null,
39+
openaiApiKey: null,
40+
encryptedOpenaiKey: null,
41+
42+
setEnabledSources: (sources: DataSource[]) => {
43+
set({ enabledSources: sources });
44+
},
45+
46+
setOpenaiKey: async (apiKey: string) => {
47+
// Encrypt the API key using Electron's secure storage
48+
const encryptedKey = await window.electronAPI.storeApiKey(apiKey);
49+
set({
50+
openaiApiKey: apiKey,
51+
encryptedOpenaiKey: encryptedKey,
52+
});
53+
},
2854

2955
setCredentials: async (apiKey: string, apiHost: string) => {
3056
// Encrypt the API key using Electron's secure storage
@@ -53,33 +79,56 @@ export const useAuthStore = create<AuthState>()(
5379
checkAuth: async () => {
5480
const state = get();
5581

56-
// Note: Environment variables are not available in renderer process
57-
// They need to be passed from main process if needed
58-
59-
// Check stored encrypted key
60-
if (state.encryptedKey) {
61-
const decryptedKey = await window.electronAPI.retrieveApiKey(
62-
state.encryptedKey,
63-
);
64-
65-
if (decryptedKey) {
66-
try {
67-
const client = new PostHogAPIClient(decryptedKey, state.apiHost);
68-
const user = await client.getCurrentUser();
69-
70-
set({
71-
apiKey: decryptedKey,
72-
user,
73-
isAuthenticated: true,
74-
client,
75-
});
76-
77-
return true;
78-
} catch {
79-
// Invalid stored credentials
80-
set({ encryptedKey: null, isAuthenticated: false });
82+
// If user has enabled sources, they're considered "authenticated"
83+
// even if they don't have PostHog credentials
84+
if (state.enabledSources.length > 0) {
85+
// Restore OpenAI key if available
86+
if (state.encryptedOpenaiKey) {
87+
const decryptedOpenaiKey = await window.electronAPI.retrieveApiKey(
88+
state.encryptedOpenaiKey,
89+
);
90+
if (decryptedOpenaiKey) {
91+
set({ openaiApiKey: decryptedOpenaiKey });
92+
}
93+
}
94+
95+
// If PostHog is enabled, verify credentials
96+
if (state.enabledSources.includes("posthog")) {
97+
if (state.encryptedKey) {
98+
const decryptedKey = await window.electronAPI.retrieveApiKey(
99+
state.encryptedKey,
100+
);
101+
102+
if (decryptedKey) {
103+
try {
104+
const client = new PostHogAPIClient(
105+
decryptedKey,
106+
state.apiHost,
107+
);
108+
const user = await client.getCurrentUser();
109+
110+
set({
111+
apiKey: decryptedKey,
112+
user,
113+
isAuthenticated: true,
114+
client,
115+
});
116+
117+
return true;
118+
} catch {
119+
// Invalid stored credentials
120+
set({ encryptedKey: null, isAuthenticated: false });
121+
return false;
122+
}
123+
}
81124
}
125+
// PostHog enabled but no valid credentials
126+
return false;
82127
}
128+
129+
// Non-PostHog sources only - mark as authenticated
130+
set({ isAuthenticated: true });
131+
return true;
83132
}
84133

85134
return false;
@@ -92,14 +141,18 @@ export const useAuthStore = create<AuthState>()(
92141
user: null,
93142
isAuthenticated: false,
94143
client: null,
144+
openaiApiKey: null,
145+
encryptedOpenaiKey: null,
95146
});
96147
},
97148
}),
98149
{
99150
name: "mission-control-auth",
100151
partialize: (state) => ({
152+
enabledSources: state.enabledSources,
101153
apiHost: state.apiHost,
102154
encryptedKey: state.encryptedKey,
155+
encryptedOpenaiKey: state.encryptedOpenaiKey,
103156
}),
104157
},
105158
),

0 commit comments

Comments
 (0)