Skip to content

Commit 57c7b19

Browse files
authored
Merge pull request #5 from alexechoi/alex/build-dashboard
Build dashboard frontend (new report)
2 parents 302dec8 + 08e65b5 commit 57c7b19

File tree

3 files changed

+134
-105
lines changed

3 files changed

+134
-105
lines changed

junction-app/app/dashboard/page.tsx

Lines changed: 108 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,107 +3,135 @@
33
import { useEffect, useState } from "react";
44
import { useAuth } from "@/contexts/AuthContext";
55
import { useRouter } from "next/navigation";
6-
7-
interface UserData {
8-
firstName: string;
9-
lastName: string;
10-
email: string;
11-
lastLoggedIn: Date | { toDate?: () => Date };
12-
lastLoggedInIp: string;
13-
termsAccepted: boolean;
14-
marketingAccepted: boolean;
15-
createdAt: Date | { toDate?: () => Date };
16-
}
6+
import { ArrowUpRight, FileUp, Loader2, Upload } from "lucide-react";
177

188
export default function DashboardPage() {
19-
const { user, logout, getUserData } = useAuth();
9+
const { user } = useAuth();
2010
const router = useRouter();
21-
const [userData, setUserData] = useState<UserData | null>(null);
22-
const [loading, setLoading] = useState(true);
11+
const [query, setQuery] = useState("");
12+
const [fileName, setFileName] = useState<string | null>(null);
13+
const [isSubmitting, setIsSubmitting] = useState(false);
2314

2415
useEffect(() => {
2516
if (!user) {
2617
router.push("/auth");
2718
return;
2819
}
20+
}, [user, router]);
2921

30-
const fetchUserData = async () => {
31-
const data = await getUserData(user.uid);
32-
setUserData(data);
33-
setLoading(false);
34-
};
35-
36-
fetchUserData();
37-
}, [user, router, getUserData]);
38-
39-
const handleLogout = async () => {
40-
await logout();
41-
router.push("/");
22+
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
23+
event.preventDefault();
24+
if (!query.trim() && !fileName) return;
25+
setIsSubmitting(true);
26+
// Placeholder for API request
27+
setTimeout(() => {
28+
setIsSubmitting(false);
29+
}, 1500);
4230
};
4331

44-
if (loading) {
45-
return (
46-
<div className="min-h-screen flex items-center justify-center">
47-
<div className="text-gray-600">Loading...</div>
48-
</div>
49-
);
50-
}
32+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
33+
const file = event.target.files?.[0];
34+
setFileName(file ? file.name : null);
35+
};
5136

5237
return (
53-
<div className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
54-
<div className="max-w-3xl mx-auto">
55-
<div className="bg-white shadow rounded-lg">
56-
<div className="px-4 py-5 sm:p-6">
57-
<h1 className="text-2xl font-bold text-gray-900 mb-6">Dashboard</h1>
38+
<div className="min-h-screen bg-zinc-950 px-4 py-12 text-white sm:px-6 lg:px-8">
39+
<div className="mx-auto max-w-5xl space-y-10">
40+
<section className="rounded-3xl border border-white/10 bg-zinc-900/60 p-8 shadow-2xl">
41+
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
42+
<div>
43+
<p className="text-sm uppercase tracking-widest text-emerald-400">
44+
Trust assessment
45+
</p>
46+
<h2 className="mt-2 text-3xl font-semibold tracking-tight">
47+
Run a new analysis
48+
</h2>
49+
<p className="mt-2 text-white/60">
50+
Enter a vendor/app name or upload a binary, installer, or config
51+
file for inspection.
52+
</p>
53+
</div>
54+
<button className="inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 text-sm text-white/70 transition hover:border-white/30 hover:text-white">
55+
View reports
56+
<ArrowUpRight className="size-4" />
57+
</button>
58+
</div>
5859

59-
<div className="space-y-4">
60-
<div>
61-
<h2 className="text-lg font-medium text-gray-900 mb-4">
62-
Welcome back!
63-
</h2>
64-
<div className="bg-gray-50 p-4 rounded-md space-y-2">
65-
<p className="text-sm text-gray-600">
66-
<span className="font-medium">Email:</span> {user?.email}
60+
<form onSubmit={handleSubmit} className="space-y-6">
61+
<div className="relative group">
62+
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-emerald-500/30 to-blue-500/30 opacity-0 blur-xl transition group-focus-within:opacity-40" />
63+
<div className="relative rounded-2xl border border-white/10 bg-black/30 p-1">
64+
<textarea
65+
value={query}
66+
onChange={(event) => setQuery(event.target.value)}
67+
placeholder="e.g. Slack, notion.so, AWS S3, or describe the artifact you want assessed."
68+
className="h-40 w-full resize-none rounded-2xl border border-white/10 bg-black/50 p-6 text-lg text-white placeholder:text-white/40 focus:outline-none focus:ring-1 focus:ring-emerald-400"
69+
/>
70+
<div className="flex flex-wrap items-center justify-between gap-3 border-t border-white/5 px-4 py-3 text-sm text-white/60">
71+
<span>
72+
We auto-enrich your query with SOC 2, CVE, and vendor intel.
73+
</span>
74+
<span className="rounded-full border border-white/10 px-3 py-1 text-xs uppercase tracking-widest text-white/50">
75+
Text input
76+
</span>
77+
</div>
78+
</div>
79+
</div>
80+
81+
<label
82+
htmlFor="file-upload"
83+
className="flex cursor-pointer flex-col gap-4 rounded-2xl border border-dashed border-white/20 bg-white/5 p-6 transition hover:border-white/40 hover:bg-white/10"
84+
>
85+
<div className="flex items-center gap-4">
86+
<div className="flex size-12 items-center justify-center rounded-2xl bg-white/10">
87+
<Upload className="size-6 text-white/70" />
88+
</div>
89+
<div>
90+
<p className="text-lg font-medium text-white">
91+
Upload executable or package
6792
</p>
68-
{userData?.firstName && (
69-
<p className="text-sm text-gray-600">
70-
<span className="font-medium">Name:</span>{" "}
71-
{userData.firstName} {userData.lastName}
72-
</p>
73-
)}
74-
<p className="text-sm text-gray-600">
75-
<span className="font-medium">User ID:</span> {user?.uid}
93+
<p className="text-sm text-white/60">
94+
.exe, .msi, .zip, .dmg, .pkg, .deb, .rpm (max 250MB)
7695
</p>
77-
{userData?.createdAt && (
78-
<p className="text-sm text-gray-600">
79-
<span className="font-medium">Member since:</span>{" "}
80-
{(() => {
81-
const dateValue =
82-
userData.createdAt instanceof Date
83-
? userData.createdAt
84-
: (
85-
userData.createdAt as { toDate?: () => Date }
86-
).toDate?.();
87-
return dateValue
88-
? new Date(dateValue).toLocaleDateString()
89-
: "";
90-
})()}
91-
</p>
92-
)}
9396
</div>
9497
</div>
95-
96-
<div className="pt-4">
97-
<button
98-
onClick={handleLogout}
99-
className="w-full sm:w-auto bg-red-600 text-white hover:bg-red-700 px-6 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
100-
>
101-
Sign Out
102-
</button>
98+
<div className="flex items-center justify-between text-sm text-white/60">
99+
<span>{fileName ?? "No file selected"}</span>
100+
<div className="flex items-center gap-2 text-emerald-400">
101+
<FileUp className="size-4" />
102+
<span>Attach file</span>
103+
</div>
103104
</div>
105+
<input
106+
id="file-upload"
107+
type="file"
108+
accept=".exe,.msi,.zip,.dmg,.pkg,.deb,.rpm"
109+
onChange={handleFileChange}
110+
className="hidden"
111+
/>
112+
</label>
113+
114+
<div className="flex flex-wrap items-center gap-4">
115+
<button
116+
type="submit"
117+
disabled={isSubmitting || (!query.trim() && !fileName)}
118+
className="inline-flex h-14 items-center justify-center rounded-full bg-white px-8 text-base font-semibold text-zinc-950 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
119+
>
120+
{isSubmitting ? (
121+
<>
122+
<Loader2 className="mr-2 size-5 animate-spin" />
123+
Processing
124+
</>
125+
) : (
126+
"Start assessment"
127+
)}
128+
</button>
129+
<p className="text-sm text-white/60">
130+
Average processing time: 2 minutes • 15+ sources cited
131+
</p>
104132
</div>
105-
</div>
106-
</div>
133+
</form>
134+
</section>
107135
</div>
108136
</div>
109137
);

junction-app/components/AppChrome.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export function AppChrome({ children }: AppChromeProps) {
1111
const pathname = usePathname();
1212
const isLanding = pathname === "/";
1313

14-
const theme = isLanding ? "bg-zinc-950 text-white" : "bg-white text-zinc-900";
14+
const isDarkShell = isLanding || pathname?.startsWith("/dashboard");
15+
const theme = isDarkShell
16+
? "bg-zinc-950 text-white"
17+
: "bg-white text-zinc-900";
1518

1619
return (
1720
<div className={`${theme} min-h-screen`}>

junction-app/components/Nav.tsx

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,20 @@ export default function Nav() {
2323
return () => window.removeEventListener("scroll", handleScroll);
2424
}, [isLanding]);
2525

26-
const navTheme = isLanding
27-
? scrolled
26+
const isDarkShell = isLanding || pathname?.startsWith("/dashboard");
27+
28+
const navTheme = isDarkShell
29+
? scrolled && isLanding
2830
? "border-white/10 bg-zinc-950/80 text-white shadow-lg shadow-black/30"
29-
: "border-transparent bg-transparent text-white"
31+
: "border-white/10 bg-zinc-950/60 text-white"
3032
: "border-gray-200 bg-white text-gray-900 shadow-sm";
31-
const linkTheme = isLanding
32-
? "text-sm font-medium text-white/80 hover:text-white"
33+
const linkTheme = isDarkShell
34+
? "text-sm font-medium text-white/70 hover:text-white"
3335
: "text-sm font-medium text-gray-700 hover:text-gray-900";
34-
const buttonTheme = isLanding
36+
const buttonTheme = isDarkShell
3537
? "bg-white text-zinc-950 hover:bg-zinc-100"
3638
: "bg-blue-600 text-white hover:bg-blue-700";
37-
const secondaryLinkTheme = isLanding ? "text-white" : "text-gray-900";
39+
const secondaryLinkTheme = isDarkShell ? "text-white" : "text-gray-900";
3840

3941
const landingLinks = [
4042
{ href: "/#how-it-works", label: "How it works" },
@@ -65,25 +67,21 @@ export default function Nav() {
6567
)}
6668
</div>
6769

68-
<div className="flex items-center gap-4">
70+
<div className="flex items-center gap-3">
6971
{user ? (
70-
<>
71-
<Link
72-
href="/dashboard"
73-
className={`${linkTheme} rounded-md px-3 py-2`}
74-
>
75-
Dashboard
76-
</Link>
77-
</>
72+
<Link
73+
href="/dashboard"
74+
className={`${linkTheme} rounded-full px-4 py-2 transition`}
75+
>
76+
Dashboard
77+
</Link>
7878
) : (
79-
<>
80-
<Link
81-
href="/auth"
82-
className={`${buttonTheme} rounded-full px-4 py-2 text-sm font-medium transition`}
83-
>
84-
Sign In
85-
</Link>
86-
</>
79+
<Link
80+
href="/auth"
81+
className={`${buttonTheme} rounded-full px-4 py-2 text-sm font-medium transition`}
82+
>
83+
Sign In
84+
</Link>
8785
)}
8886
</div>
8987
</div>

0 commit comments

Comments
 (0)