Skip to content

Commit 4b7c372

Browse files
committed
feat: add project secrets
1 parent 8b3af7f commit 4b7c372

File tree

17 files changed

+987
-198
lines changed

17 files changed

+987
-198
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
export CSC_LINK="/tmp/certificate.p12"
3535
export CSC_KEY_PASSWORD="${{ secrets.MACOS_CERTIFICATE_PWD }}"
3636
fi
37-
37+
3838
# Notarization credentials (optional - will skip if not provided)
3939
if [ -n "${{ secrets.AC_APIKEY_ID }}" ]; then
4040
# Decode API key to file
@@ -46,7 +46,7 @@ jobs:
4646
else
4747
echo "⚠️ No notarization credentials - skipping notarization"
4848
fi
49-
49+
5050
bun run dist:mac
5151
5252
- name: Upload macOS DMG (x64)

src/App.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,17 @@ function AppInner() {
219219
}
220220
};
221221

222+
const handleGetSecrets = useCallback(async (projectPath: string) => {
223+
return await window.api.secrets.get(projectPath);
224+
}, []);
225+
226+
const handleUpdateSecrets = useCallback(
227+
async (projectPath: string, secrets: Array<{ key: string; value: string }>) => {
228+
await window.api.secrets.update(projectPath, secrets);
229+
},
230+
[]
231+
);
232+
222233
const handleNavigateWorkspace = useCallback(
223234
(direction: "next" | "prev") => {
224235
if (!selectedWorkspace) return;
@@ -407,6 +418,8 @@ function AppInner() {
407418
getWorkspaceState={getWorkspaceState}
408419
collapsed={sidebarCollapsed}
409420
onToggleCollapsed={() => setSidebarCollapsed((prev) => !prev)}
421+
onGetSecrets={handleGetSecrets}
422+
onUpdateSecrets={handleUpdateSecrets}
410423
/>
411424
<MainContent>
412425
<AppHeader>

src/components/Modal.tsx

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import React, { useEffect, useCallback } from "react";
2+
import styled from "@emotion/styled";
3+
import { matchesKeybind, KEYBINDS } from "@/utils/ui/keybinds";
4+
5+
// Styled Components
6+
export const ModalOverlay = styled.div`
7+
position: fixed;
8+
top: 0;
9+
left: 0;
10+
right: 0;
11+
bottom: 0;
12+
background-color: rgba(0, 0, 0, 0.5);
13+
display: flex;
14+
justify-content: center;
15+
align-items: center;
16+
z-index: 1000;
17+
`;
18+
19+
export const ModalContent = styled.div<{ maxWidth?: string; maxHeight?: string }>`
20+
background: #1e1e1e;
21+
border-radius: 8px;
22+
padding: 24px;
23+
width: 90%;
24+
max-width: ${(props) => props.maxWidth ?? "500px"};
25+
${(props) => props.maxHeight && `max-height: ${props.maxHeight};`}
26+
display: flex;
27+
flex-direction: column;
28+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
29+
border: 1px solid #333;
30+
31+
h2 {
32+
margin-top: 0;
33+
margin-bottom: 8px;
34+
color: #fff;
35+
}
36+
`;
37+
38+
export const ModalSubtitle = styled.p`
39+
color: #888;
40+
font-size: 14px;
41+
margin-bottom: 20px;
42+
`;
43+
44+
export const ModalInfo = styled.div`
45+
background: #2d2d2d;
46+
border: 1px solid #444;
47+
border-radius: 4px;
48+
padding: 12px;
49+
margin-bottom: 20px;
50+
font-size: 13px;
51+
52+
p {
53+
margin: 0 0 8px 0;
54+
color: #888;
55+
56+
&:last-child {
57+
margin-bottom: 0;
58+
}
59+
}
60+
61+
code {
62+
color: #569cd6;
63+
font-family: var(--font-monospace);
64+
}
65+
`;
66+
67+
export const ModalActions = styled.div`
68+
display: flex;
69+
justify-content: flex-end;
70+
gap: 12px;
71+
margin-top: 24px;
72+
`;
73+
74+
export const Button = styled.button`
75+
padding: 8px 20px;
76+
border: none;
77+
border-radius: 4px;
78+
cursor: pointer;
79+
font-size: 14px;
80+
font-weight: 500;
81+
transition: all 0.2s;
82+
83+
&:disabled {
84+
opacity: 0.5;
85+
cursor: not-allowed;
86+
}
87+
`;
88+
89+
export const CancelButton = styled(Button)`
90+
background: #444;
91+
color: #ccc;
92+
93+
&:hover:not(:disabled) {
94+
background: #555;
95+
}
96+
`;
97+
98+
export const PrimaryButton = styled(Button)`
99+
background: #007acc;
100+
color: white;
101+
102+
&:hover:not(:disabled) {
103+
background: #005a9e;
104+
}
105+
`;
106+
107+
// Modal wrapper component
108+
interface ModalProps {
109+
isOpen: boolean;
110+
title: string;
111+
subtitle?: string;
112+
onClose: () => void;
113+
children: React.ReactNode;
114+
maxWidth?: string;
115+
maxHeight?: string;
116+
isLoading?: boolean;
117+
}
118+
119+
export const Modal: React.FC<ModalProps> = ({
120+
isOpen,
121+
title,
122+
subtitle,
123+
onClose,
124+
children,
125+
maxWidth,
126+
maxHeight,
127+
isLoading = false,
128+
}) => {
129+
const handleCancel = useCallback(() => {
130+
if (!isLoading) {
131+
onClose();
132+
}
133+
}, [isLoading, onClose]);
134+
135+
// Handle cancel keybind to close modal
136+
useEffect(() => {
137+
if (!isOpen) return;
138+
139+
const handleKeyDown = (e: KeyboardEvent) => {
140+
if (matchesKeybind(e, KEYBINDS.CANCEL) && !isLoading) {
141+
handleCancel();
142+
}
143+
};
144+
145+
window.addEventListener("keydown", handleKeyDown);
146+
return () => window.removeEventListener("keydown", handleKeyDown);
147+
}, [isOpen, isLoading, handleCancel]);
148+
149+
if (!isOpen) return null;
150+
151+
return (
152+
<ModalOverlay>
153+
<ModalContent maxWidth={maxWidth} maxHeight={maxHeight}>
154+
<h2>{title}</h2>
155+
{subtitle && <ModalSubtitle>{subtitle}</ModalSubtitle>}
156+
{children}
157+
</ModalContent>
158+
</ModalOverlay>
159+
);
160+
};

0 commit comments

Comments
 (0)