Skip to content

Commit 6462c62

Browse files
committed
feat(frontend): wire upload to backend
1 parent 388b9b8 commit 6462c62

2 files changed

Lines changed: 135 additions & 41 deletions

File tree

apps/frontend/src/app/globals.css

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,56 @@ body {
285285
font-size: 28px;
286286
}
287287

288+
.file-input {
289+
display: none;
290+
}
291+
292+
.upload-actions {
293+
display: flex;
294+
flex-direction: column;
295+
gap: 8px;
296+
}
297+
298+
.upload-actions button:disabled {
299+
opacity: 0.6;
300+
cursor: not-allowed;
301+
}
302+
303+
.upload-status {
304+
font-size: 12px;
305+
color: rgba(11, 11, 22, 0.6);
306+
}
307+
308+
.status-pill {
309+
padding: 8px 12px;
310+
border-radius: 12px;
311+
font-size: 12px;
312+
background: rgba(92, 225, 230, 0.18);
313+
}
314+
315+
.result-card {
316+
border-radius: 18px;
317+
border: 1px solid var(--ios-outline);
318+
background: rgba(255, 255, 255, 0.7);
319+
padding: 14px;
320+
box-shadow: var(--ios-shadow);
321+
}
322+
323+
.result-card pre {
324+
margin: 0;
325+
max-height: 220px;
326+
overflow: auto;
327+
font-size: 12px;
328+
line-height: 1.5;
329+
color: #111;
330+
white-space: pre-wrap;
331+
}
332+
333+
.result-header {
334+
font-weight: 600;
335+
margin-bottom: 8px;
336+
}
337+
288338
.card-footer {
289339
display: flex;
290340
align-items: center;
@@ -331,39 +381,14 @@ body {
331381
color: rgba(11, 11, 22, 0.6);
332382
}
333383

334-
.footer-card {
335-
display: flex;
336-
align-items: center;
337-
justify-content: space-between;
338-
gap: 18px;
339-
padding: 24px;
340-
border-radius: 22px;
341-
background: linear-gradient(
342-
135deg,
343-
rgba(140, 82, 255, 0.16),
344-
rgba(92, 225, 230, 0.2)
345-
);
346-
border: 1px solid var(--ios-outline);
347-
}
348-
349-
.footer-card h3 {
350-
margin: 0 0 8px;
351-
}
352-
353-
.footer-card p {
354-
margin: 0;
355-
color: rgba(11, 11, 22, 0.7);
356-
}
357-
358384
@media (max-width: 720px) {
359385
.nav {
360386
flex-direction: column;
361387
align-items: flex-start;
362388
gap: 16px;
363389
}
364390

365-
.card-footer,
366-
.footer-card {
391+
.card-footer {
367392
flex-direction: column;
368393
align-items: flex-start;
369394
}

apps/frontend/src/app/page.tsx

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,58 @@
1+
import { useRef, useState } from "react";
2+
13
export default function Home() {
4+
const fileRef = useRef<HTMLInputElement>(null);
5+
const [selectedFile, setSelectedFile] = useState<File | null>(null);
6+
const [status, setStatus] = useState<string | null>(null);
7+
const [result, setResult] = useState<string | null>(null);
8+
const [isUploading, setIsUploading] = useState(false);
9+
10+
const handleChooseFile = () => {
11+
fileRef.current?.click();
12+
};
13+
14+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
15+
const file = event.target.files?.[0] ?? null;
16+
setSelectedFile(file);
17+
setStatus(file ? `Selected ${file.name}` : "No file selected");
18+
setResult(null);
19+
};
20+
21+
const handleUpload = async () => {
22+
if (!selectedFile || isUploading) {
23+
return;
24+
}
25+
26+
setIsUploading(true);
27+
setStatus("Uploading bundle...");
28+
setResult(null);
29+
30+
try {
31+
const form = new FormData();
32+
form.append("bundle", selectedFile);
33+
form.append("profile", "full");
34+
35+
const response = await fetch("http://127.0.0.1:7070/api/v1/scan", {
36+
method: "POST",
37+
body: form,
38+
});
39+
40+
const payload = await response.json();
41+
if (!response.ok) {
42+
setStatus(payload?.error ?? "Scan failed");
43+
setResult(null);
44+
} else {
45+
setStatus("Scan complete");
46+
setResult(JSON.stringify(payload, null, 2));
47+
}
48+
} catch (error) {
49+
setStatus("Failed to reach backend. Is it running on :7070?");
50+
setResult(null);
51+
} finally {
52+
setIsUploading(false);
53+
}
54+
};
55+
256
return (
357
<div className="page">
458
<div className="page-glow page-glow--left" />
@@ -39,8 +93,8 @@ export default function Home() {
3993
Designed for AI agents and human reviewers.
4094
</p>
4195
<div className="hero-actions">
42-
<button className="primary-button" type="button">
43-
Upload bundle
96+
<button className="primary-button" type="button" onClick={handleChooseFile}>
97+
Choose bundle
4498
</button>
4599
<button className="secondary-button" type="button">
46100
View example report
@@ -76,10 +130,37 @@ export default function Home() {
76130
<strong>Drag &amp; drop your bundle</strong>
77131
<span>.ipa or .app, up to 1GB</span>
78132
</div>
79-
<button className="secondary-button" type="button">
133+
<input
134+
ref={fileRef}
135+
className="file-input"
136+
type="file"
137+
accept=".ipa,.app"
138+
onChange={handleFileChange}
139+
/>
140+
<button className="secondary-button" type="button" onClick={handleChooseFile}>
80141
Choose file
81142
</button>
82143
</div>
144+
<div className="upload-actions">
145+
<button
146+
className="primary-button"
147+
type="button"
148+
onClick={handleUpload}
149+
disabled={!selectedFile || isUploading}
150+
>
151+
{isUploading ? "Uploading..." : "Run scan"}
152+
</button>
153+
<div className="upload-status">
154+
{selectedFile ? selectedFile.name : "No file selected"}
155+
</div>
156+
</div>
157+
{status ? <div className="status-pill">{status}</div> : null}
158+
{result ? (
159+
<div className="result-card">
160+
<div className="result-header">Latest report</div>
161+
<pre>{result}</pre>
162+
</div>
163+
) : null}
83164
<div className="card-footer">
84165
<div>
85166
<strong>Next:</strong> privacy manifest, entitlements, ATS rules
@@ -117,18 +198,6 @@ export default function Home() {
117198
</div>
118199
</section>
119200

120-
<section className="footer-card">
121-
<div>
122-
<h3>Backend-first flow</h3>
123-
<p>
124-
This UI runs alongside the Rust backend. Start the backend first,
125-
then open the frontend to upload bundles and review reports.
126-
</p>
127-
</div>
128-
<button className="primary-button" type="button">
129-
Start backend
130-
</button>
131-
</section>
132201
</main>
133202
</div>
134203
);

0 commit comments

Comments
 (0)