From 18870556a17d50b4bd33adcfb2c43e03d2b3529d Mon Sep 17 00:00:00 2001 From: Ndifreke000 Date: Fri, 19 Jun 2026 12:15:47 +0100 Subject: [PATCH] feat(auth): wire login and signup pages to backend API (#12) - Add `src/lib/api/auth.ts` with typed `login()`, `signup()`, `logout()`, and `extractApiErrorMessage()` functions; both auth functions store the JWT token and current-user profile on success - Update `LoginForm` to call `login()` instead of raw `api.post`, surface real API error messages (NestJS validation arrays joined, 401 mapped to a credential-specific message), and add `role="alert"` to the error paragraph - Update `SignupForm` to call `signup()`, fix missing `setCurrentUser()` call (user profile was never stored after signup), surface real API error messages, and map 409 Conflict to an email-already-in-use message - Add `.env.example` documenting the required `NEXT_PUBLIC_API_URL` variable Closes #12 --- .env.example | 4 +++ src/app/login/login-form.tsx | 27 +++++++++----------- src/app/signup/signup-form.tsx | 24 +++++++++--------- src/lib/api/auth.ts | 46 ++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 .env.example create mode 100644 src/lib/api/auth.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b1cb023 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Base URL of the MyFans NestJS backend +# Development default: http://localhost:3000 +# Must be prefixed with NEXT_PUBLIC_ so it is inlined into the browser bundle. +NEXT_PUBLIC_API_URL=http://localhost:3000 diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx index f0bdfdf..41ba3c0 100644 --- a/src/app/login/login-form.tsx +++ b/src/app/login/login-form.tsx @@ -4,7 +4,8 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter, useSearchParams } from "next/navigation"; -import { api } from "@/lib/api/client"; +import { login, extractApiErrorMessage } from "@/lib/api/auth"; +import { ApiError } from "@/lib/api/errors"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -16,7 +17,6 @@ import { FormMessage, } from "@/components/ui/form"; import { loginSchema, type LoginFormValues } from "@/lib/validations/auth"; -import type { AuthResponse } from "@/types/api"; export function LoginForm() { const router = useRouter(); @@ -24,23 +24,22 @@ export function LoginForm() { const redirectTo = searchParams.get("redirect") || "/dashboard"; const [serverError, setServerError] = useState(null); - // useForm wires up RHF with our Zod schema as the validator. - // zodResolver translates Zod errors into the format RHF expects. const form = useForm({ resolver: zodResolver(loginSchema), defaultValues: { email: "", password: "" }, }); - // This function only runs when ALL fields pass Zod validation. async function onSubmit(values: LoginFormValues) { setServerError(null); try { - const response = await api.post("/auth/login", values); - api.setToken(response.access_token); - api.setCurrentUser(response.user); + await login(values); router.replace(redirectTo); - } catch { - setServerError("Login failed. Check your credentials and try again."); + } catch (error) { + if (error instanceof ApiError && error.statusCode === 401) { + setServerError("Invalid email or password."); + } else { + setServerError(extractApiErrorMessage(error)); + } } } @@ -49,21 +48,17 @@ export function LoginForm() {

Log in

- {/* Form spreads the RHF context so all FormField children can access it */}
- ( Email - {/* FormControl merges id + aria-invalid onto the Input */} - {/* Renders the Zod error message, or nothing when valid */} )} @@ -84,7 +79,9 @@ export function LoginForm() { /> {serverError ? ( -

{serverError}

+

+ {serverError} +

) : null}