From d6202c5c5736e6485d8cbac412401ba23795c4bd Mon Sep 17 00:00:00 2001 From: ngc2207 Date: Tue, 3 Dec 2024 18:03:14 +0800 Subject: [PATCH] feat: refactor AdminCreateUserForm to named export and improve error message handling; add LoginForm component with validation --- .../components/admin-create-user-form.tsx | 4 +- src/app/(main)/gitea/admin/users/page.tsx | 2 +- .../(main)/login/components/login-form.tsx | 157 ++++++++++++++++++ src/app/(main)/login/page.tsx | 9 + src/app/actions/(gitea)/user/index.ts | 6 +- src/app/actions/(gitea)/user/store.ts | 2 + src/lib/gitea.ts | 7 +- 7 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 src/app/(main)/login/components/login-form.tsx create mode 100644 src/app/(main)/login/page.tsx diff --git a/src/app/(main)/gitea/admin/users/components/admin-create-user-form.tsx b/src/app/(main)/gitea/admin/users/components/admin-create-user-form.tsx index fd4925b..78491d3 100644 --- a/src/app/(main)/gitea/admin/users/components/admin-create-user-form.tsx +++ b/src/app/(main)/gitea/admin/users/components/admin-create-user-form.tsx @@ -63,7 +63,7 @@ const defaultValues: z.infer = { must_change_password: true, }; -export default function AdminCreateUserForm() { +export function AdminCreateUserForm() { const { toast } = useToast(); const { user, error, loading, adminCreateUser } = adminCreateUserStore(); @@ -101,7 +101,7 @@ export default function AdminCreateUserForm() { toast({ variant: "destructive", title: "Failed to create user", - description: `${error.message}`, + description: error.message, action: Try again, }); } diff --git a/src/app/(main)/gitea/admin/users/page.tsx b/src/app/(main)/gitea/admin/users/page.tsx index fba92a9..aa7d635 100644 --- a/src/app/(main)/gitea/admin/users/page.tsx +++ b/src/app/(main)/gitea/admin/users/page.tsx @@ -1,4 +1,4 @@ -import AdminCreateUserForm from "./components/admin-create-user-form"; +import { AdminCreateUserForm } from "./components/admin-create-user-form"; export default function AdminCreateUserPage() { return ( diff --git a/src/app/(main)/login/components/login-form.tsx b/src/app/(main)/login/components/login-form.tsx new file mode 100644 index 0000000..a907595 --- /dev/null +++ b/src/app/(main)/login/components/login-form.tsx @@ -0,0 +1,157 @@ +"use client"; + +import { z } from "zod"; +import Link from "next/link"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import logger from "@/lib/logger"; +import { useEffect } from "react"; +import { Loader2 } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { useToast } from "@/hooks/use-toast"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { ToastAction } from "@/components/ui/toast"; +import { zodResolver } from "@hookform/resolvers/zod"; +import userGetCurrentStore from "@/app/actions/(gitea)/user/store"; +import { Form, FormField, FormItem, FormLabel } from "@/components/ui/form"; + +const formSchema = z.object({ + username: z.string().refine( + (value) => { + const isEmail = value.includes("@"); + if (isEmail) { + return z + .string() + .email("Invalid email address") + .max(254, "Email is too long (max 254 characters)") + .safeParse(value).success; + } else { + return z + .string() + .min(1, "Username cannot be empty") + .max(40, "Username is too long (max 40 characters)") + .safeParse(value).success; + } + }, + { + message: "Invalid email address or username", + } + ), + password: z + .string() + .min(8, "Password must be at least 8 characters") + .max(255, "Password must be at most 255 characters"), +}); + +const defaultValues: z.infer = { + username: "", + password: "", +}; + +export function LoginForm() { + const { toast } = useToast(); + const { user, error, loading, userGetCurrent } = userGetCurrentStore(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues, + }); + + function onSubmit(values: z.infer) { + logger.info({ values }, "submitting form"); + userGetCurrent(values.username, values.password).then(() => { + form.reset(defaultValues); + }); + } + + function onReset() { + form.reset(defaultValues); + } + + useEffect(() => { + form.reset(defaultValues); + }, [form]); + + useEffect(() => { + if (user) { + toast({ + variant: "default", + title: "Successfully Logged In", + description: `Welcome back, ${user.login}`, + action: Continue, + }); + } else if (error) { + toast({ + variant: "destructive", + title: "Error Logging In", + description: error.message, + action: Retry, + }); + } + }, [user, error, toast]); + + return ( + + + Login + + Enter your email below to login to your account + + + +
+ + ( + + Username or Email Address + + + )} + /> + ( + +
+ Password + + Forgot your password? + +
+ +
+ )} + /> +
+ + +
+ + +
+
+ ); +} diff --git a/src/app/(main)/login/page.tsx b/src/app/(main)/login/page.tsx new file mode 100644 index 0000000..79f18bb --- /dev/null +++ b/src/app/(main)/login/page.tsx @@ -0,0 +1,9 @@ +import { LoginForm } from "@/app/(main)/login/components/login-form"; + +export default function LoginPage() { + return ( +
+ +
+ ); +} diff --git a/src/app/actions/(gitea)/user/index.ts b/src/app/actions/(gitea)/user/index.ts index fac0285..f690879 100644 --- a/src/app/actions/(gitea)/user/index.ts +++ b/src/app/actions/(gitea)/user/index.ts @@ -1,6 +1,8 @@ +"use server"; + import base64 from "base-64"; import { APIError, RequestParams, User } from "gitea-js"; -import { callGiteaApi, gitea, GiteaApiResponse } from "@/lib/gitea"; +import { api, callGiteaApi, GiteaApiResponse } from "@/lib/gitea"; export async function userGetCurrent( username: string, @@ -14,7 +16,7 @@ export async function userGetCurrent( }; const context = { username, password }; const result = await callGiteaApi( - () => gitea.user.userGetCurrent(params), + () => api.user.userGetCurrent(params), "Successfully retrieved current user", "Failed to retrieve current user", context diff --git a/src/app/actions/(gitea)/user/store.ts b/src/app/actions/(gitea)/user/store.ts index 5f588fd..b8a569f 100644 --- a/src/app/actions/(gitea)/user/store.ts +++ b/src/app/actions/(gitea)/user/store.ts @@ -33,3 +33,5 @@ const userGetCurrentStore = create((set) => ({ } }, })); + +export default userGetCurrentStore; diff --git a/src/lib/gitea.ts b/src/lib/gitea.ts index 4f9a417..a755d20 100644 --- a/src/lib/gitea.ts +++ b/src/lib/gitea.ts @@ -2,6 +2,11 @@ import logger from "./logger"; import fetch from "cross-fetch"; import { APIError, giteaApi } from "gitea-js"; +const api = giteaApi(process.env.GITEA_URL as string, { + token: "", + customFetch: fetch, +}); + const gitea = giteaApi(process.env.GITEA_URL as string, { token: process.env.GITEA_TOKEN, customFetch: fetch, @@ -31,5 +36,5 @@ async function callGiteaApi( } } -export { gitea, callGiteaApi }; +export { api, gitea, callGiteaApi }; export type { GiteaApiResponse };