Skip to content

Commit 2aafbba

Browse files
committed
feat(ui): revamp entire UI
1 parent 3db73aa commit 2aafbba

File tree

16 files changed

+820
-571
lines changed

16 files changed

+820
-571
lines changed

frontend/app/(app)/layout.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
1-
import { SidebarToggle } from "@/app/_components/sidebar-toggle";
2-
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
1+
"use client";
2+
import {
3+
SidebarInset,
4+
SidebarProvider,
5+
SidebarTrigger,
6+
} from "@/components/ui/sidebar";
7+
import { Separator } from "@/components/ui/separator";
38
import { SelectTheme } from "@/components/ui/theme-toggler";
49
import { UIStructure } from "@/components/ui/ui-structure";
510
import { ThemeProvider } from "@/components/theme-provider";
611
import { UpgradeCTA } from "@/components/ui/upgrade-cta";
12+
import { useUser } from "@/hooks/useUser";
13+
14+
import Link from "next/link";
715

816
export default function ChatLayout({
917
children,
1018
}: {
1119
children: React.ReactNode;
1220
}) {
21+
const { user, isLoading: isUserLoading } = useUser();
22+
1323
return (
1424
<>
1525
<SidebarProvider>
@@ -23,9 +33,18 @@ export default function ChatLayout({
2333
<SidebarInset className="min-!h-svh p-2">
2434
<div className="bg-muted/60 relative h-full min-h-svh w-full rounded-xl p-4">
2535
<div className="absolute top-0 left-0 z-[50] flex h-12 w-full items-center justify-between px-3">
26-
<SidebarToggle />
36+
<SidebarTrigger className="shrink-0" />
37+
{/* <SidebarToggle /> */}
2738
<div className="flex items-center gap-2">
2839
<UpgradeCTA variant="topbar" />
40+
{!isUserLoading && !user && (
41+
<Link
42+
href="/auth"
43+
className="flex items-center w-full text-white dark:text-neutral-900 text-xs h-8 bg-neutral-800 dark:bg-zinc-100 font-bold px-4 rounded-2xl inset-shadow-xs inset-shadow-white/20 border border-black/4 outline-0"
44+
>
45+
Login
46+
</Link>
47+
)}
2948
<SelectTheme />
3049
</div>
3150
</div>

frontend/app/_components/Email.tsx

Lines changed: 73 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"use client";
22
import { toast } from "sonner";
3-
import { ArrowLeft } from "lucide-react";
43
import { Button } from "@/components/ui/button";
5-
import Link from "next/link";
64
import { Input } from "@/components/ui/input";
75
import { BACKEND_URL } from "@/lib/utils";
8-
import { useRouter } from "next/navigation";
96
import { useState } from "react";
7+
import Link from "next/link";
8+
import {
9+
Tooltip,
10+
TooltipContent,
11+
TooltipTrigger,
12+
} from "@/components/ui/tooltip";
1013

1114
const isEmailValid = (email: string) => {
1215
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
@@ -21,73 +24,82 @@ export function Email({
2124
setStep: (step: string) => void;
2225
email: string;
2326
}) {
24-
const router = useRouter();
2527
const [sendingRequest, setSendingRequest] = useState(false);
28+
29+
const handleAuth = (e: React.FormEvent<HTMLFormElement>) => {
30+
e.preventDefault();
31+
setSendingRequest(true);
32+
fetch(`${BACKEND_URL}/auth/initiate_signin`, {
33+
method: "POST",
34+
body: JSON.stringify({ email }),
35+
headers: {
36+
"Content-Type": "application/json",
37+
},
38+
})
39+
.then((res) => {
40+
if (res.status === 200) {
41+
setStep("otp");
42+
toast.success("OTP sent to email");
43+
} else {
44+
toast.error("Failed to send OTP, please retry after a few minutes");
45+
}
46+
})
47+
.catch((err) => {
48+
console.error(err);
49+
toast.error("Failed to send OTP, please retry after a few minutes");
50+
})
51+
.finally(() => {
52+
setSendingRequest(false);
53+
});
54+
};
55+
2656
return (
27-
<div className="mx-auto max-h-screen max-w-6xl">
28-
<div className="absolute top-4 left-4">
29-
{/* <Button asChild variant="ghost" className="font-semibold" onClick={() => router.push("/")}>
30-
<Link className="flex items-center gap-2" href="/">
31-
<ArrowLeft className="size-4" />
32-
Back to chat
33-
</Link>
34-
</Button> */}
35-
</div>
36-
<div className="flex h-full flex-col items-center justify-center gap-8 max-w-xl ">
37-
<h1 className="text-3xl lg:text-4xl font-bold tracking-tighter text-center">
38-
Welcome to <span className="text-primary">1ai</span>
57+
<div className="mx-auto w-full max-h-screen max-w-sm px-2 sm:px-0">
58+
<div className="flex h-full flex-col items-center justify-center gap-8">
59+
<h1 className="text-3xl font-serif text-foreground">
60+
Welcome to 1<span className="text-orange-400">ai</span>
3961
</h1>
40-
<div className="w-full flex flex-col gap-2">
41-
<Input
42-
onChange={(e) => setEmail(e.target.value)}
43-
placeholder="Email"
44-
/>
62+
<form onSubmit={handleAuth} className="w-full flex flex-col gap-3">
63+
<div className="border border-zinc-400/15 focus-within:border-transparent focus-within:ring-1 rounded-xl focus-within:ring-orange-400/80 dark:focus-within:ring-orange-400/60">
64+
<Input
65+
className="border-none dark:bg-transparent focus:border-none focus-visible:ring-0 outline-none focus:ring-0"
66+
spellCheck={false}
67+
onChange={(e) => setEmail(e.target.value)}
68+
placeholder="Email"
69+
/>
70+
</div>
4571
<Button
72+
type="submit"
4673
disabled={!isEmailValid(email) || sendingRequest}
4774
variant="accent"
48-
onClick={() => {
49-
setSendingRequest(true);
50-
fetch(`${BACKEND_URL}/auth/initiate_signin`, {
51-
method: "POST",
52-
body: JSON.stringify({ email }),
53-
headers: {
54-
"Content-Type": "application/json",
55-
},
56-
})
57-
.then((res) => {
58-
if (res.status === 200) {
59-
setStep("otp");
60-
toast.success("OTP sent to email");
61-
} else {
62-
toast.error(
63-
"Failed to send OTP, please retry after a few minutes"
64-
);
65-
}
66-
})
67-
.catch((err) => {
68-
console.error(err);
69-
toast.error(
70-
"Failed to send OTP, please retry after a few minutes"
71-
);
72-
})
73-
.finally(() => {
74-
setSendingRequest(false);
75-
});
76-
}}
77-
className="w-full h-12"
75+
className="w-full text-sm text-white bg-[#fa7319] hover:bg-[#fa7319]/90 h-10 px-3.5 rounded-xl inset-shadow-sm inset-shadow-white/60 font-medium border border-black/4 outline-0"
7876
>
7977
Continue with Email
8078
</Button>
81-
</div>
82-
<div className="text-muted-foreground text-sm">
83-
By continuing, you agree to our{" "}
84-
<Link href="/terms" className="text-muted-foreground font-medium">
85-
Terms
86-
</Link>{" "}
87-
and{" "}
88-
<Link href="/privacy" className="text-muted-foreground font-medium">
89-
Privacy Policy
90-
</Link>
79+
</form>
80+
<div className="mt-2">
81+
<Tooltip>
82+
<TooltipTrigger>
83+
<div className="group text-muted-foreground text-sm font-serif">
84+
<Link
85+
href="/terms"
86+
className="text-muted-foreground group-hover:text-orange-400 hover:underline hover:underline-offset-3"
87+
>
88+
Terms
89+
</Link>{" "}
90+
<span className="group-hover:text-orange-400">and </span>
91+
<Link
92+
href="/privacy"
93+
className="text-muted-foreground group-hover:text-orange-400 hover:underline hover:underline-offset-3"
94+
>
95+
Privacy Policy
96+
</Link>
97+
</div>
98+
</TooltipTrigger>
99+
<TooltipContent sideOffset={1} className="bg-orange-400">
100+
<p>By continuing, you agree</p>
101+
</TooltipContent>
102+
</Tooltip>
91103
</div>
92104
</div>
93105
</div>

frontend/app/_components/Otp.tsx

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,33 @@
11
"use client";
22

3+
import { useRef, useEffect } from "react";
34
import { Button } from "@/components/ui/button";
45
import Link from "next/link";
56
import { Input } from "@/components/ui/input";
7+
import {
8+
Tooltip,
9+
TooltipContent,
10+
TooltipTrigger,
11+
} from "@/components/ui/tooltip";
12+
import {
13+
InputOTP,
14+
InputOTPGroup,
15+
InputOTPSlot,
16+
} from "@/components/ui/input-otp";
617
import { useState } from "react";
718
import { BACKEND_URL } from "@/lib/utils";
819
import { toast } from "sonner";
920

1021
export function Otp({ email }: { email: string }) {
22+
const initialRef = useRef<HTMLInputElement>(null);
1123
const [otp, setOtp] = useState("");
1224

25+
useEffect(() => {
26+
if (initialRef.current) {
27+
initialRef.current.focus();
28+
}
29+
}, []);
30+
1331
const handleLogin = async () => {
1432
try {
1533
const response = await fetch(`${BACKEND_URL}/auth/signin`, {
@@ -43,42 +61,79 @@ export function Otp({ email }: { email: string }) {
4361
};
4462

4563
return (
46-
<div className="mx-auto max-h-screen max-w-6xl">
47-
<div className="absolute top-4 left-4">
48-
{/* <Button asChild variant="ghost" className="font-semibold" onClick={() => router.push("/auth")}>
49-
<Link className="flex items-center gap-2" href="/">
50-
<ArrowLeft className="size-4" />
51-
Back to chat
52-
</Link>
53-
</Button> */}
54-
</div>
64+
<div className="mx-auto w-full max-h-screen max-w-sm px-2 sm:px-0">
5565
<div className="flex h-full flex-col items-center justify-center gap-8">
5666
<div className="flex flex-col items-center gap-2">
57-
<h1 className="text-3xl lg:text-4xl font-bold tracking-tighter">
58-
Welcome to <span className="text-primary">1ai</span>
67+
<h1 className="text-3xl font-serif text-foreground">
68+
Welcome to 1<span className="text-orange-400">ai</span>
5969
</h1>
6070
</div>
61-
<div className="flex flex-col gap-2 w-full">
71+
<form onSubmit={handleLogin} className="w-full flex flex-col gap-3">
6272
<Input disabled placeholder="Email" value={email} />
63-
64-
<Input placeholder="OTP" onChange={(e) => setOtp(e.target.value)} />
73+
<InputOTP
74+
ref={initialRef}
75+
maxLength={6}
76+
value={otp}
77+
onChange={(value) => setOtp(value)}
78+
>
79+
<InputOTPGroup className="w-full flex items-center">
80+
<InputOTPSlot
81+
index={0}
82+
className="h-12 w-16 rounded-none rounded-tl-xl rounded-bl-xl border border-zinc-400/20"
83+
/>
84+
<InputOTPSlot
85+
index={1}
86+
className="h-12 w-16 rounded-none border border-zinc-400/20"
87+
/>
88+
<InputOTPSlot
89+
index={2}
90+
className="h-12 w-16 rounded-none border border-zinc-400/20"
91+
/>
92+
<InputOTPSlot
93+
index={3}
94+
className="h-12 w-16 rounded-none border border-zinc-400/20"
95+
/>
96+
<InputOTPSlot
97+
index={4}
98+
className="h-12 w-16 rounded-none border border-zinc-400/20"
99+
/>
100+
<InputOTPSlot
101+
index={5}
102+
className="h-12 w-16 rounded-none rounded-br-xl rounded-tr-xl border border-zinc-400/20"
103+
/>
104+
</InputOTPGroup>
105+
</InputOTP>
65106
<Button
107+
type="submit"
66108
variant="accent"
67-
onClick={handleLogin}
68-
className="w-full h-12"
109+
className="w-full text-sm text-white bg-[#fa7319] hover:bg-[#fa7319]/90 h-10 px-3.5 rounded-xl inset-shadow-sm inset-shadow-white/60 font-medium border border-black/4 outline-0"
69110
>
70111
Login
71112
</Button>
72-
</div>
73-
<div className="text-muted-foreground text-sm">
74-
By continuing, you agree to our{" "}
75-
<Link href="/terms" className="text-muted-foreground font-medium">
76-
Terms of Service
77-
</Link>{" "}
78-
and{" "}
79-
<Link href="/privacy" className="text-muted-foreground font-medium">
80-
Privacy Policy
81-
</Link>
113+
</form>
114+
<div className="mt-2">
115+
<Tooltip>
116+
<TooltipTrigger>
117+
<div className="group text-muted-foreground text-sm font-serif">
118+
<Link
119+
href="/terms"
120+
className="text-muted-foreground group-hover:text-orange-400 hover:underline hover:underline-offset-3"
121+
>
122+
Terms
123+
</Link>{" "}
124+
<span className="group-hover:text-orange-400">and </span>
125+
<Link
126+
href="/privacy"
127+
className="text-muted-foreground group-hover:text-orange-400 hover:underline hover:underline-offset-3"
128+
>
129+
Privacy Policy
130+
</Link>
131+
</div>
132+
</TooltipTrigger>
133+
<TooltipContent sideOffset={1} className="bg-orange-400">
134+
<p>By continuing, you agree</p>
135+
</TooltipContent>
136+
</Tooltip>
82137
</div>
83138
</div>
84139
</div>

frontend/app/layout.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,29 @@ import { BlurProvider } from "@/contexts/blur-context";
55
import { Toaster } from "sonner";
66
import { siteConfig } from "@/config/site";
77
import { ThemeProvider } from "@/components/theme-provider";
8-
import { Plus_Jakarta_Sans } from "next/font/google";
8+
import { Inter, Syne } from "next/font/google";
99
import { ExecutionProvider } from "@/contexts/execution-context";
1010
export const metadata: Metadata = siteConfig;
1111

12-
const font = Plus_Jakarta_Sans({
12+
const inter = Inter({
1313
subsets: ["latin"],
14-
variable: "--font-jakarta",
14+
variable: "--font-inter",
15+
weight: ["200", "400", "500"],
16+
});
17+
18+
const syne = Syne({
19+
subsets: ["latin"],
20+
variable: "--font-syne",
21+
weight: ["700"],
1522
});
1623

1724
export default function RootLayout({
1825
children,
1926
}: Readonly<{ children: React.ReactNode }>) {
2027
return (
2128
<html lang="en" suppressHydrationWarning>
22-
<body className={`${font.className}`}>
23-
<ExecutionProvider>
29+
<body className={`${inter.variable} ${syne.variable}`}>
30+
<ExecutionProvider>
2431
<ThemeProvider
2532
attribute="class"
2633
defaultTheme="dark"
@@ -34,7 +41,7 @@ export default function RootLayout({
3441
</BlurProvider>
3542
</FontProvider>
3643
</ThemeProvider>
37-
</ExecutionProvider>
44+
</ExecutionProvider>
3845
</body>
3946
</html>
4047
);

0 commit comments

Comments
 (0)