Backend structure / login with JWT

This commit is contained in:
Leons Aleksandrovs
2025-07-06 16:46:21 +03:00
parent 3166424426
commit 3003a961b6
18 changed files with 338 additions and 126 deletions
+5 -1
View File
@@ -2,6 +2,7 @@ import { API_BASE } from "@/consts";
import { normalizeLink } from "./utils";
import type { ApiResponse } from "@/types/api";
import toast from "react-hot-toast";
import { tryCatch } from "./tryCatch";
interface RequestProps<T> {
error?: (err: Error) => void;
@@ -34,7 +35,10 @@ class Requests {
});
// Get response data
const data = (await res.json()) as ApiResponse<T>;
const { data, error } = await tryCatch<ApiResponse<T>>(res.json());
if (error) {
throw new Error(`Parsing error: ${res.statusText} - ${res.status}`);
}
// Check if data is ok
if ("success" in data && !data.success) {
+32
View File
@@ -0,0 +1,32 @@
// Types for the result object with discriminated union
type Success<T> = {
data: T;
error: null;
};
type Failure<E> = {
data: null;
error: E;
};
type Result<T, E = Error> = Success<T> | Failure<E>;
// Main wrapper function
export async function tryCatch<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>> {
try {
const data = await promise;
return { data, error: null };
} catch (error) {
return { data: null, error: error as E };
}
}
// function for sync
export function tryCatchSync<T, E = Error>(callback: () => T): Result<T, E> {
try {
const data = callback();
return { data, error: null };
} catch (error) {
return { data: null, error: error as E };
}
}
+61 -6
View File
@@ -1,14 +1,48 @@
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { useAppForm } from "@/hooks/formHook";
import Guest from "@/layouts/Guest";
import { createFileRoute, Link } from "@tanstack/react-router";
import requests from "@/lib/requests";
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import toast from "react-hot-toast";
import * as z from "zod/v4";
export const Route = createFileRoute("/login")({
component: RouteComponent,
});
const loginSchema = z.object({
email: z.string().email(),
password: z.string().nonempty("Password is required"),
});
function RouteComponent() {
const loading = useState(false);
const navigate = useNavigate();
const form = useAppForm({
defaultValues: {
email: "",
password: "",
},
validators: {
onBlur: loginSchema,
},
onSubmit: ({ value }) => {
requests.post<{ message: string }>("/login", {
data: value,
before() {
// use state to true loading
loading[1](true);
},
success(data) {
navigate({ to: "/" });
},
});
},
});
return (
<Guest className="h-screen w-screen grid place-items-center">
<Card className="w-full max-w-[400px]">
@@ -21,10 +55,31 @@ function RouteComponent() {
</Button>
</CardAction>
</CardHeader>
<CardContent className="space-y-2">
<Input type="email" placeholder="Email address" />
<Input type="password" placeholder="Password" />
<Button className="w-full">Login</Button>
<CardContent className="space-y-3">
<form.AppField
name="email"
children={(f) => (
<f.TextField
label="Email address"
placeholder="Your email address"
type="email"
/>
)}
/>
<form.AppField
name="password"
children={(f) => (
<f.TextField
label="Password"
placeholder="Your accounts password"
type="password"
/>
)}
/>
<Button onClick={form.handleSubmit} className="w-full">
Login
</Button>
</CardContent>
</Card>
</Guest>