Merge branch 'develop' of github.com:Tria-plc/edr-platform into feat/customer_registration

This commit is contained in:
yaschalew
2026-05-18 12:10:12 +03:00
3 changed files with 68 additions and 15 deletions

View File

@@ -28,7 +28,12 @@ import TrackingPage from "./pages/tracking/TrackingPage";
import BillingPage from "./pages/billing/BillingPage";
import TrainsPage from "./pages/trains/TrainsPage";
import DashboardPage from "./pages/dashboard/DashboardPage";
import { IamLoginPage, LoadingScreen, useAuth } from "@tria-plc/iamui-common";
import {
IamLoginPage,
LoadingScreen,
useAuth,
useAuthUser,
} from "@tria-plc/iamui-common";
import CustomersPage from "./pages/customers/CustomersPage";
import CustomerDetailPage from "./pages/customers/CustomerDetailPage";
import NewCustomerPage from "./pages/customers/NewCustomerPage";
@@ -53,6 +58,7 @@ const App = () => {
const navigate = useNavigate();
const location = useLocation();
const { user, loading } = useAuth();
const { logout } = useAuthUser();
if (loading) {
return <LoadingScreen />;
@@ -67,9 +73,19 @@ const App = () => {
);
}
if (location.pathname === "/auth") {
return <Navigate to="/" replace />;
}
const displayName = user?.name?.en || user?.username || user?.email || "User";
const userEmail = user?.email;
const handleLogout = () => {
// Best-effort server-side session invalidation; local cleanup always runs
// so a failed API call (e.g. expired token) never leaves the user stuck.
logout();
["auth-token", "refresh-token"].forEach((name) => {
document.cookie = `${name}=; Max-Age=0; path=/`;
});
localStorage.clear();
window.location.replace("/auth");
};
return (
<DashboardLayout
@@ -78,6 +94,9 @@ const App = () => {
activeHref={location.pathname}
onNavigate={navigate}
enableThemeToggle
userName={displayName}
userEmail={userEmail}
onLogout={handleLogout}
>
<Routes>
<Route path="/" element={<DashboardPage />} />

View File

@@ -13,6 +13,19 @@ import {
axiosInstance,
} from "@tria-plc/iamui-common";
// Purge cookies that were stored as the literal string "undefined" before the
// envelope interceptor fix. Without this, stale sessions would keep sending
// "Authorization: Bearer undefined" and get 401 JsonWebTokenError forever.
["auth-token", "refresh-token"].forEach((name) => {
const entry = document.cookie
.split("; ")
.find((c) => c.startsWith(`${name}=`));
const value = entry?.split("=").slice(1).join("=");
if (value === "undefined" || value === "null" || value === "") {
document.cookie = `${name}=; Max-Age=0; path=/`;
}
});
const queryClient = new QueryClient();
window.__IAM_CONFIG__ = {
apiUrl: `${import.meta.env.VITE_BASE_API_URL}/api`,

View File

@@ -17,6 +17,10 @@ export interface DashboardLayoutProps {
onNavigate?: (href: string) => void;
headerRight?: ReactNode;
enableThemeToggle?: boolean;
userName?: string;
userEmail?: string;
userInitials?: string;
onLogout?: () => void;
children: ReactNode;
}
@@ -42,8 +46,20 @@ const DashboardLayout = ({
onNavigate,
headerRight,
enableThemeToggle = false,
userName = "User",
userEmail,
userInitials,
onLogout,
children,
}: DashboardLayoutProps) => {
const initials =
userInitials ??
userName
.split(" ")
.filter(Boolean)
.slice(0, 2)
.map((n) => n[0].toUpperCase())
.join("");
const [theme, setTheme] = useState<Theme>(() =>
enableThemeToggle ? getInitialTheme() : "light",
);
@@ -148,10 +164,10 @@ const DashboardLayout = ({
className="flex items-center gap-2 rounded-xl border border-transparent px-2 py-1.5 transition hover:border-[#33578D]/20 hover:bg-[#33578D]/5 aria-expanded:border-[#33578D]/30 aria-expanded:bg-[#33578D]/10 dark:hover:border-[#33578D]/30 dark:hover:bg-[#33578D]/10 dark:aria-expanded:border-[#33578D]/40 dark:aria-expanded:bg-[#33578D]/20"
>
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-[#33578D] text-xs font-semibold text-white">
JD
{initials}
</div>
<span className="hidden text-sm font-medium text-slate-700 md:block dark:text-slate-200">
John Doe
{userName}
</span>
<ChevronDown
className={`h-4 w-4 text-slate-400 transition dark:text-slate-500 ${
@@ -167,11 +183,13 @@ const DashboardLayout = ({
>
<div className="border-b border-slate-100 px-4 py-3 dark:border-slate-700">
<p className="text-sm font-semibold text-slate-900 dark:text-slate-100">
John Doe
</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
john.doe@edr.com
{userName}
</p>
{userEmail ? (
<p className="text-xs text-slate-500 dark:text-slate-400">
{userEmail}
</p>
) : null}
</div>
<a
href="#profile"
@@ -182,15 +200,18 @@ const DashboardLayout = ({
<User className="h-4 w-4" />
Profile
</a>
<a
href="#logout"
<button
type="button"
role="menuitem"
onClick={() => setIsUserMenuOpen(false)}
className="flex items-center gap-2 px-4 py-2 text-sm text-red-600 transition hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/30"
onClick={() => {
setIsUserMenuOpen(false);
onLogout?.();
}}
className="flex w-full items-center gap-2 px-4 py-2 text-sm text-red-600 transition hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/30"
>
<LogOut className="h-4 w-4" />
Logout
</a>
</button>
</div>
) : null}
</div>