feat(auth): enhance login flow with notifications and path normalization
- add success notification on successful login - show error notification with detailed message on login failure - normalize API paths to prevent double slashes and trailing slashes - redirect to login page only if not on login request or page
This commit is contained in:
@@ -22,13 +22,33 @@ import type {
|
||||
} from "../types";
|
||||
|
||||
function getApiBaseUrl(): string {
|
||||
if (import.meta.env.VITE_API_URL) return import.meta.env.VITE_API_URL;
|
||||
if (import.meta.env.VITE_API_URL) return import.meta.env.VITE_API_URL.replace(/\/+$/, "");
|
||||
const { hostname, protocol, port } = window.location;
|
||||
if (hostname === "localhost" || hostname === "127.0.0.1") return "http://localhost:8080";
|
||||
return `${protocol}//${hostname}${port ? `:${port}` : ""}`;
|
||||
return `${protocol}//${hostname}${port ? `:${port}` : ""}`.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
const API_BASE_URL = getApiBaseUrl();
|
||||
const LOGIN_API_PATH = "/api/v1/auth/login";
|
||||
const LOGIN_UI_PATH = "/ui/login";
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
const collapsed = path.replace(/\/{2,}/g, "/");
|
||||
if (collapsed.length > 1 && collapsed.endsWith("/")) {
|
||||
return collapsed.slice(0, -1);
|
||||
}
|
||||
return collapsed || "/";
|
||||
}
|
||||
|
||||
function getNormalizedPathFromURL(input: string): string {
|
||||
if (!input) return "/";
|
||||
try {
|
||||
return normalizePath(new URL(input, window.location.origin).pathname);
|
||||
} catch {
|
||||
const withoutQuery = input.split("?")[0]?.split("#")[0] || "/";
|
||||
return normalizePath(withoutQuery);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeUser(raw: unknown): User | null {
|
||||
if (!raw || typeof raw !== "object") return null;
|
||||
@@ -96,9 +116,17 @@ class ApiClient {
|
||||
(response) => response,
|
||||
(error: AxiosError) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token expired or invalid, clear auth and redirect
|
||||
this.clearAuth();
|
||||
window.location.href = "/ui/login";
|
||||
const requestPath = getNormalizedPathFromURL(error.config?.url || "");
|
||||
const currentPath = normalizePath(window.location.pathname);
|
||||
const isLoginRequest = requestPath === LOGIN_API_PATH;
|
||||
const isLoginPage = currentPath === LOGIN_UI_PATH;
|
||||
|
||||
// Keep failed login attempts on the same page.
|
||||
if (!isLoginRequest && !isLoginPage) {
|
||||
// Token expired or invalid, clear auth and redirect.
|
||||
this.clearAuth();
|
||||
window.location.href = LOGIN_UI_PATH;
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
@@ -122,7 +150,7 @@ class ApiClient {
|
||||
// Auth endpoints
|
||||
async login(credentials: LoginRequest): Promise<LoginResponse> {
|
||||
const { data } = await this.client.post<LoginResponse>(
|
||||
"/api/v1/auth/login",
|
||||
LOGIN_API_PATH,
|
||||
credentials,
|
||||
);
|
||||
if (data.token) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Center,
|
||||
Box
|
||||
} from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconAlertCircle, IconBrandWhatsapp } from '@tabler/icons-react';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
|
||||
@@ -28,10 +29,24 @@ export default function LoginPage() {
|
||||
|
||||
try {
|
||||
await login(username, password);
|
||||
notifications.show({
|
||||
title: 'Login successful',
|
||||
message: 'Welcome back!',
|
||||
color: 'green',
|
||||
});
|
||||
navigate('/');
|
||||
} catch (err) {
|
||||
// Error is handled in the store
|
||||
console.error('Login failed:', err);
|
||||
} catch (err: any) {
|
||||
const responseData = err?.response?.data;
|
||||
const notificationMessage =
|
||||
responseData?.message ||
|
||||
(typeof responseData === 'string' ? responseData.trim() : '') ||
|
||||
'Invalid username or password';
|
||||
|
||||
notifications.show({
|
||||
title: 'Login failed',
|
||||
message: notificationMessage,
|
||||
color: 'red',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user