Skip to main content

LoginPage

A ready-to-use, full-page login screen. It combines a decorative background (YusrBusBackground) with a two-column card that holds the login form on one side and an illustration on the other.

LoginForm lives in a separate file purely for readability — together, they form a single feature.


Preview


How to Use

The page is a default Next.js route component. Drop it inside your app/(auth)/login/ directory (or wherever your auth routes live) and it works out of the box.

app/
└── (auth)/
└── login/
├── page.tsx ← LoginPage (default export)
└── loginForm.tsx ← LoginForm (named export)

page.tsx

"use client"

import { YusrBusBackground } from "@yusr_systems/ui";
import { LoginForm } from "./loginForm";

export default function LoginPage() {
return (
<div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
<YusrBusBackground />
<div className="w-full max-w-sm md:max-w-4xl">
<LoginForm />
</div>
</div>
);
}

loginForm.tsx

"use client"

import type { Setting } from "@/app/core/data/setting";
import { login, updateLoggedInUser, useAppDispatch } from "@/app/core/state/store";
import {
ApiConstants,
LoginRequest,
SystemPermissions,
User,
type ValidationRule,
Validators,
YusrApiHelper,
} from "@yusr_systems/core";
import {
Button, Card, CardContent, Checkbox, cn,
Field, FieldDescription, FieldGroup,
PasswordField, TextField, useEntityForm,
} from "@yusr_systems/ui";
import { Loader2 } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react";

export function LoginForm({ className, ...props }: React.ComponentProps<"div">) {
// ... see full source
}

What happens on submit

  1. Validates the form — stops if any field is missing.
  2. Saves (or clears) the email and username in localStorage based on the Remember Me checkbox.
  3. Calls POST /Login via YusrApiHelper.
  4. On success, dispatches login and updateLoggedInUser to the Redux store, then navigates to the first permitted route (or a callbackUrl query param if one exists).

LoginPage Props

LoginPage accepts no props. It is a self-contained route component.


LoginForm Props

LoginForm forwards all div props to its root element.

PropTypeRequiredDefaultExampleDescription
classNamestringNoundefined"mt-8"Extra Tailwind classes merged onto the root <div>.
...propsReact.ComponentProps<"div">Nodata-testid="login-form"Any valid HTML div attribute is forwarded to the root element.
note

All business logic (API call, routing, store dispatch) is internal. There are no callback props — the component manages everything itself.


Dependencies

NameRoleLink
YusrBusBackgroundDecorative animated backgroundyusr_systems/ui
useEntityFormForm state and validation hookyusr_systems/ui
YusrApiHelperHTTP utility for calling the Yusr APIyusr_systems/core
LoginRequestRequest model/DTO for the login endpointyusr_systems/core
SystemPermissions.getFirstPermissionPathResolves the first route a user is allowed to visityusr_systems/core
login / updateLoggedInUserRedux actionsapp/core/state/store
useAppDispatchTyped Redux dispatch hookapp/core/state/store
ValidatorsBuilt-in validation rules (e.g., required)yusr_systems/core
lucide-reactIcon library (Loader2 spinner)https://lucide.dev
next/navigationuseRouter, useSearchParamshttps://nextjs.org/docs/app/api-reference/functions/use-router

Notes

  • Remember Me persists companyEmail and username to localStorage under the keys remembered_email and remembered_username. It intentionally never saves the password.
  • After a successful login, navigation is wrapped in a setTimeout(..., 10) to give the Redux store one tick to settle before the route change.
  • The form validates all three fields (companyEmail, username, password) as required. Errors are shown inline and cleared as the user types.
  • The right-hand illustration column is hidden on mobile (hidden md:block) — on small screens the form takes the full card width.
  • The callbackUrl query parameter is respected: if present, the user is redirected there after login instead of their first permission path.