loading...
1import { useEffect, useState } from "react";
2
3export const useIsTyping = (...values: string[]) => {
4 const [typing, setTyping] = useState(false);
5
6 useEffect(() => {
7 setTyping(true);
8
9 const timeout = setTimeout(() => {
10 setTyping(false);
11 }, 150);
12
13 return () => clearTimeout(timeout);
14 }, [...values]);
15
16 return typing;
17};
18
1"use server";2..3import { refresh } from "next/cache";4import {5 authorizeDevice,6 createAnswer,1"use server";23import { redirect } from "next/navigation";45import { refresh } from "next/cache";6import {7 authorizeDevice,26 UserResponse,27 VoteResponse,28} from "./lib/dto";2930export async function getCurrentUserAction(): Promise<UserResponse | null> {..31 return await getCurrentUser();32}3334export type AuthActionResult34export type AuthActionResult =35 | { success: true }36 | { success: false; error: string };3738export async function signup(formData: FormData): Promise<AuthActionResult> {......10import { authFetch, authHead, GITDOT_SERVER_URL, handleResponse, NotFound } from "./util";1112export async function validateUsername(username: string): Promise<boolean> {13 const response = await authHead(`${GITDOT_SERVER_URL}/user/${username}`);14 return response.ok;..15}1617export async function getUser(username: string): Promise<UserResponse | null> {18 const response = await authFetch(`${GITDOT_SERVER_URL}/user/${username}`);10import {11 authFetch,12 authHead,13 GITDOT_SERVER_URL,14 handleResponse,15 NotFound,16} from "./util";1718export async function validateUsername(username: string): Promise<boolean> {19 const response = await authHead24 });25}2627export async function authHead(url: string, options?: RequestInit): Promise<Response> {28 return await authFetch(url, {..29 ...options,30 method: "HEAD",31 });32}24 });25}2627export async function authHead(28 url: string,29 options?: RequestInit,30): Promise<Response> {31 return await authFetch(url, {32 ...options,33 method: "HEAD",23import { useRouter } from "next/navigation";4import { useActionState, useEffect, useState } from "react";5import { type AuthActionResult, login } from "@/actions";..6import { cn, validateEmail, validatePassword } from "@/util";78export default function LoginForm({ redirect }: { redirect?: string }) {9 const router = useRouter();1011 const [email, setEmail] = useState("");12 const [password, setPassword] = useState("");13 const [debouncedPassword, setDebouncedPassword] = useState("");1415 useEffect(() => {16 const timer = setTimeout(() => {17 setDebouncedPassword(password);18 }, 200);1920 return () => clearTimeout(timer);21 }, [password]);2223 const [state, formAction, isPending] = useActionState(24 async (_prevState: AuthActionResult | null, formData: FormData) => {25 const result = await login(formData);2627 if (result.success) {28 router.push(redirect ?? "/home");..29 }..30 return result;31 },32 null,33 );3435 const canSubmit =36 validateEmail(email) && validatePassword(debouncedPassword) && !isPending;3738 return (39 <form action={formAction} className="flex flex-col text-sm w-sm">40 <p className="pb-2">Login.</p>4142 <input43 type="email"44 name="email"45 placeholder="Email"46 value={email}47 onChange={(e) => setEmail(e.target.value)}48 className="border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150"49 />5051 <input52 type="password"53 name="password"54 placeholder="Password"55 value={password}56 onChange={(e) => setPassword(e.target.value)}57 className="border-border border-b ring-0 outline-0 focus:border-black transition-colors duration-150"..58 />5960 <div className="flex flex-row mt-2 w-full justify-end">..........61 <button62 type="submit"63 className={cn(64 "underline transition-all duration-300",65 canSubmit66 ? "cursor-pointer decoration-current"67 : "text-primary/60 cursor-not-allowed decoration-transparent",68 )}69 disabled={isPending || email === "" || password === ""}70 >71 {isPending ? "Submitting..." : "Submit."}72 </button>73 </div>1"use client";23import { login } from "@/actions";4import { useIsTyping } from "@/hooks/use-is-typing";5import { cn, validateEmail, validatePassword } from "@/util";6import23import { useActionState, useEffect, useState } from "react";4import { type AuthActionResult, signup } from "@/actions";5import { cn, validateEmail, validatePassword, validateName } from "@/util";67export default function SignupForm() {8 const [email, setEmail] = useState("");9 const [name, setName] = useState("");10 const [password, setPassword] = useState("");11 const [debouncedPassword, setDebouncedPassword] = useState("");1213 useEffect(() => {14 const timer = setTimeout(() => {15 setDebouncedPassword(password);..16 }, 200);1718 return () => clearTimeout(timer);19 }, [password]);2021 const [state, formAction, isPending] = useActionState(22 async (_prevState: AuthActionResult | null, formData: FormData) => {23 return await signup(formData);24 },25 null,26 );2728 const canSubmit =29 validateEmail(email) &&30 validateName(name) &&31 validatePassword(debouncedPassword) &&32 !isPending;3334 if (state?.success) {35 return (36 <div className="flex flex-col text-sm w-sm">37 <p className="pb-2">Check your email.</p>38 <p className="text-primary/60">1"use client";23import { type AuthError, signup } from "@/actions";4import { cn, validateEmail, validateName } from "@/util";................65 className="border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150"66 />6768 <input69 type="password"70 name="password"71 placeholder="Password"72 value
82 canSubmit83 ? "cursor-pointer decoration-current"84 : "text-primary/60 cursor-not-allowed decoration-transparent",85 )}86 disabled={isPending || email === "" || name === "" || password === ""}87 >88 {isPending ? 30 formData.append("password", password);3132 startTransition(async () => {33 const action = mode === "login" ? login : signup;34 const result: AuthActionResult = await action(formData);3536 if (result.success) {37 refreshUser();38 setOpen(false);39 setEmail("");40 setPassword("");30 formData.append("password", password);3132 startTransition(async () => {33 const action = mode === "login" ? login : signup;34 const result = await action(null, formData);3536 if ("success" {
useActionState
,
useEffect
,
useState
}
from
"
react
"
;
7
8export default function LoginForm({ redirect }: { redirect?: string }) {
9 const [state, formAction, isPending] = useActionState(login, null);
..
10 const [email, setEmail] = useState("");
11 const [password, setPassword] = useState("");
12 const [canSubmit, setCanSubmit] = useState(false);
..
..
13 const isTyping = useIsTyping(email, password);
..
..
..
..
..
14
15 useEffect(() => {
..
..
..
16 if (!isTyping) {
17 setCanSubmit(
18 validateEmail(email) && validatePassword(password) && !isPending,
..
19 );
20 }
21 }, [email, password, isTyping, isPending]);
..
..
..
..
..
22
23 return (
24 <form action={formAction} className="flex flex-col text-sm w-sm" noValidate>
25 <p className="pb-2">Login.</p>
26
27 <input
28 type="email"
29 name="email"
30 placeholder="Email"
31 value={email}
32 onChange={(e) => setEmail(e.target.value)}
33 className="border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150"
34 />
35
36 <input
37 type="password"
38 name="password"
39 placeholder="Password"
40 value={password}
41 onChange={(e) => setPassword(e.target.value)}
42 className="border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150"
43 />
44 <input type="hidden" name="redirect" value={redirect || "/home"} />
45
46 <div className="flex flex-row w-full justify-between">
47 <div className="flex">
48 {state && "error" in state && (
49 <p className="text-red-500">{state.error}</p>
50 )}
51 </div>
52 <button
53 type="submit"
54 className={cn(
55 "cursor-pointer underline transition-all duration-300",
56 canSubmit ? "decoration-current" : "decoration-transparent",
57 isPending ? "cursor-default" : "",
..
58 )}
59 disabled={isPending}
60 >
61 {isPending ? "Submitting..." : "Submit."}
62 </button>
63 </div>
..
..
5import { useActionState, useState } from "react";
6
7export default function SignupForm() {
8 const [email, setEmail] = useState("");
9 const [name, setName] = useState("");
10
11 const [state, formAction, isPending] = useActionState(
12 async (_prevState: AuthError | null, formData: FormData) => {
..
..
..
..
..
13 return await signup(formData);
14 },
15 null,
16 );
17 const canSubmit = validateEmail(email) && validateName(name) && !isPending;
18
19 if (state && "success" in state) {
20 return (
21 <div className="flex flex-col text-sm w-sm">
22 <p className="pb-2">Check your email.</p>
23 <p className="text-primary/60">
={
password
}
73 onChange={(e) => setPassword(e.target.value)}
74 className="border-border border-b ring-0 outline-0 focus:border-black transition-colors duration-150"
75 />
76
77 <div className="flex flex-row mt-2 w-full justify-end">
78 <button
79 type="submit"
80 className={cn(
81 "underline transition-all duration-300",
49 onChange={(e) => setName(e.target.value)}50 className="border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150"51 />5253 <div className="flex flex-row w-full justify-between">54 <div className="flex">55 {state && "error" in state && (56 <p className="text-red-500">{state.error}</p>57 )}......58 </div>59 <button60 type="submit"61 className={cn(62 "underline transition-all duration-300","
Submitting...
"
:
"
Submit.
"
}
89 </button>
90 </div>
63 canSubmit64 ? "cursor-pointer decoration-current"65 : "text-primary/60 cursor-not-allowed decoration-transparent",66 )}67 disabled={isPending || email === "" || name === ""}68 >69 {isPending ? "Submitting..." : "Submit."}70 </button>71 </div>in
result
)
{
37 refreshUser();
38 setOpen(false);
39 setEmail("");
40 setPassword("");
=
26 CreateRepositoryResponse,27 QuestionResponse,28 UserResponse,29 VoteResponse,30} from "./lib/dto";31import { validateEmail, validatePassword } from "./util";3233export async function getCurrentUserAction(): Promise<UserResponse | null> {34 return await getCurrentUser();35}39 const email = formData.get("email") as string;
40 const name = formData.get("name") as string;
41 const password = formData.get("password") as string;
42
43 const valid = await validateUsername(name);
44 if (!valid) {
45 return { success: false, error: "Username taken"};
46 }
47
48 const supabase = await createSupabaseClient();
49 const { error } = await supabase.auth.signUp({
50 email,
51 password,
52 options: {
53 data: { name },
54 },
55 });
56
57 if (error) {
58 return { success: false, error: error.message };
59 }
60 return { success: true };
61}
62
63export async function login(formData: FormData): Promise<AuthActionResult> {
64 const supabase = await createSupabaseClient();
65
66 // todo: add validation
67 const email = formData.get("email") as string;
68 const password = formData.get("password") as string;
69
70 const { error } = await supabase.auth.signInWithPassword({
71 email,
72 password,
73 });
74
75 if (error) {
76 return { success: false, error: error.message };
77 }
78 return { success: true };
79}
80
35}3637export type AuthActionResult = { success: true } | { error: string };3839export async function login(40 _prev: AuthActionResult | null,41 formData: FormData,42): Promise<AuthActionResult> {43 const supabase = await createSupabaseClient();44 const email = formData.get("email") as string;..45 const password = formData.get("password") as string;46 const redirectTo = formData.get("redirect") as string;47........48 const { error } = await supabase.auth.signInWithPassword({......49 email,50 password,51 });5253 if (error) return { error: error.message };54 if (redirectTo) redirect(redirectTo);..55 return { success: true };56}5758export async function signup(59 _prev: AuthActionResult | null,60 formData: FormData,61): Promise<AuthActionResult> {62 const email = formData.get("email") as string;63 const name = formData.get("name") as string;..64 const valid = await validateUsername(name);........65 if (!valid) {66 return { error: "Username taken" };67 }6869 const supabase = await createSupabaseClient();70 // const { error } = await supabase.auth.signUp({(
`${
GITDOT_SERVER_URL
}
/user/
${
username
}`
);