Skip to content

Commit a8d60da

Browse files
sshadererquhart
authored andcommittedApr 15, 2025
Add UI for Convex Auth
1 parent c79f0b1 commit a8d60da

File tree

1 file changed

+98
-4
lines changed

1 file changed

+98
-4
lines changed
 

‎src/App.tsx

+98-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,116 @@
11
"use client";
22

3-
import { useMutation, useQuery } from "convex/react";
3+
import {
4+
Authenticated,
5+
Unauthenticated,
6+
useConvexAuth,
7+
useMutation,
8+
useQuery,
9+
} from "convex/react";
410
import { api } from "../convex/_generated/api";
11+
import { useAuthActions } from "@convex-dev/auth/react";
12+
import { useState } from "react";
513

614
export default function App() {
715
return (
816
<>
917
<header className="sticky top-0 z-10 bg-light dark:bg-dark p-4 border-b-2 border-slate-200 dark:border-slate-800">
10-
Convex + React
18+
Convex + React + Convex Auth
19+
<SignOutButton />
1120
</header>
1221
<main className="p-8 flex flex-col gap-16">
13-
<h1 className="text-4xl font-bold text-center">Convex + React</h1>
14-
<Content />
22+
<h1 className="text-4xl font-bold text-center">
23+
Convex + React + Convex Auth
24+
</h1>
25+
<Authenticated>
26+
<Content />
27+
</Authenticated>
28+
<Unauthenticated>
29+
<SignInForm />
30+
</Unauthenticated>
1531
</main>
1632
</>
1733
);
1834
}
1935

36+
function SignOutButton() {
37+
const { isAuthenticated } = useConvexAuth();
38+
const { signOut } = useAuthActions();
39+
return (
40+
<>
41+
{isAuthenticated && (
42+
<button
43+
className="bg-slate-200 dark:bg-slate-800 text-dark dark:text-light rounded-md px-2 py-1"
44+
onClick={() => void signOut()}
45+
>
46+
Sign out
47+
</button>
48+
)}
49+
</>
50+
);
51+
}
52+
53+
function SignInForm() {
54+
const { signIn } = useAuthActions();
55+
const [flow, setFlow] = useState<"signIn" | "signUp">("signIn");
56+
const [error, setError] = useState<string | null>(null);
57+
return (
58+
<div className="flex flex-col gap-8 w-96 mx-auto">
59+
<p>Log in to see the numbers</p>
60+
<form
61+
className="flex flex-col gap-2"
62+
onSubmit={(e) => {
63+
e.preventDefault();
64+
const formData = new FormData(e.target as HTMLFormElement);
65+
formData.set("flow", flow);
66+
void signIn("password", formData).catch((error) => {
67+
setError(error.message);
68+
});
69+
}}
70+
>
71+
<input
72+
className="bg-light dark:bg-dark text-dark dark:text-light rounded-md p-2 border-2 border-slate-200 dark:border-slate-800"
73+
type="email"
74+
name="email"
75+
placeholder="Email"
76+
/>
77+
<input
78+
className="bg-light dark:bg-dark text-dark dark:text-light rounded-md p-2 border-2 border-slate-200 dark:border-slate-800"
79+
type="password"
80+
name="password"
81+
placeholder="Password"
82+
/>
83+
<button
84+
className="bg-dark dark:bg-light text-light dark:text-dark rounded-md"
85+
type="submit"
86+
>
87+
{flow === "signIn" ? "Sign in" : "Sign up"}
88+
</button>
89+
<div className="flex flex-row gap-2">
90+
<span>
91+
{flow === "signIn"
92+
? "Don't have an account?"
93+
: "Already have an account?"}
94+
</span>
95+
<span
96+
className="text-dark dark:text-light underline hover:no-underline cursor-pointer"
97+
onClick={() => setFlow(flow === "signIn" ? "signUp" : "signIn")}
98+
>
99+
{flow === "signIn" ? "Sign up instead" : "Sign in instead"}
100+
</span>
101+
</div>
102+
{error && (
103+
<div className="bg-red-500/20 border-2 border-red-500/50 rounded-md p-2">
104+
<p className="text-dark dark:text-light font-mono text-xs">
105+
Error signing in: {error}
106+
</p>
107+
</div>
108+
)}
109+
</form>
110+
</div>
111+
);
112+
}
113+
20114
function Content() {
21115
const { viewer, numbers } =
22116
useQuery(api.myFunctions.listNumbers, {

0 commit comments

Comments
 (0)
Please sign in to comment.