mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
Merge pull request #9 from Tria-plc/feat/add_auth_integration
feat(freight:auth): show real user profile in top bar and fix logout
This commit is contained in:
@@ -27,7 +27,12 @@ import TrackingPage from "./pages/tracking/TrackingPage";
|
|||||||
import BillingPage from "./pages/billing/BillingPage";
|
import BillingPage from "./pages/billing/BillingPage";
|
||||||
import TrainsPage from "./pages/trains/TrainsPage";
|
import TrainsPage from "./pages/trains/TrainsPage";
|
||||||
import DashboardPage from "./pages/dashboard/DashboardPage";
|
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 CustomersPage from "./pages/customers/CustomersPage";
|
||||||
import CustomerDetailPage from "./pages/customers/CustomerDetailPage";
|
import CustomerDetailPage from "./pages/customers/CustomerDetailPage";
|
||||||
import NewCustomerPage from "./pages/customers/NewCustomerPage";
|
import NewCustomerPage from "./pages/customers/NewCustomerPage";
|
||||||
@@ -50,6 +55,7 @@ const App = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { user, loading } = useAuth();
|
const { user, loading } = useAuth();
|
||||||
|
const { logout } = useAuthUser();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
@@ -64,9 +70,19 @@ const App = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname === "/auth") {
|
const displayName = user?.name?.en || user?.username || user?.email || "User";
|
||||||
return <Navigate to="/" replace />;
|
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 (
|
return (
|
||||||
<DashboardLayout
|
<DashboardLayout
|
||||||
@@ -75,6 +91,9 @@ const App = () => {
|
|||||||
activeHref={location.pathname}
|
activeHref={location.pathname}
|
||||||
onNavigate={navigate}
|
onNavigate={navigate}
|
||||||
enableThemeToggle
|
enableThemeToggle
|
||||||
|
userName={displayName}
|
||||||
|
userEmail={userEmail}
|
||||||
|
onLogout={handleLogout}
|
||||||
>
|
>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<DashboardPage />} />
|
<Route path="/" element={<DashboardPage />} />
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ import {
|
|||||||
axiosInstance,
|
axiosInstance,
|
||||||
} from "@tria-plc/iamui-common";
|
} 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();
|
const queryClient = new QueryClient();
|
||||||
window.__IAM_CONFIG__ = {
|
window.__IAM_CONFIG__ = {
|
||||||
apiUrl: `${import.meta.env.VITE_BASE_API_URL}/api`,
|
apiUrl: `${import.meta.env.VITE_BASE_API_URL}/api`,
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export interface DashboardLayoutProps {
|
|||||||
onNavigate?: (href: string) => void;
|
onNavigate?: (href: string) => void;
|
||||||
headerRight?: ReactNode;
|
headerRight?: ReactNode;
|
||||||
enableThemeToggle?: boolean;
|
enableThemeToggle?: boolean;
|
||||||
|
userName?: string;
|
||||||
|
userEmail?: string;
|
||||||
|
userInitials?: string;
|
||||||
|
onLogout?: () => void;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,8 +46,20 @@ const DashboardLayout = ({
|
|||||||
onNavigate,
|
onNavigate,
|
||||||
headerRight,
|
headerRight,
|
||||||
enableThemeToggle = false,
|
enableThemeToggle = false,
|
||||||
|
userName = "User",
|
||||||
|
userEmail,
|
||||||
|
userInitials,
|
||||||
|
onLogout,
|
||||||
children,
|
children,
|
||||||
}: DashboardLayoutProps) => {
|
}: DashboardLayoutProps) => {
|
||||||
|
const initials =
|
||||||
|
userInitials ??
|
||||||
|
userName
|
||||||
|
.split(" ")
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 2)
|
||||||
|
.map((n) => n[0].toUpperCase())
|
||||||
|
.join("");
|
||||||
const [theme, setTheme] = useState<Theme>(() =>
|
const [theme, setTheme] = useState<Theme>(() =>
|
||||||
enableThemeToggle ? getInitialTheme() : "light",
|
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"
|
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">
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-[#33578D] text-xs font-semibold text-white">
|
||||||
JD
|
{initials}
|
||||||
</div>
|
</div>
|
||||||
<span className="hidden text-sm font-medium text-slate-700 md:block dark:text-slate-200">
|
<span className="hidden text-sm font-medium text-slate-700 md:block dark:text-slate-200">
|
||||||
John Doe
|
{userName}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`h-4 w-4 text-slate-400 transition dark:text-slate-500 ${
|
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">
|
<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">
|
<p className="text-sm font-semibold text-slate-900 dark:text-slate-100">
|
||||||
John Doe
|
{userName}
|
||||||
</p>
|
|
||||||
<p className="text-xs text-slate-500 dark:text-slate-400">
|
|
||||||
john.doe@edr.com
|
|
||||||
</p>
|
</p>
|
||||||
|
{userEmail ? (
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
{userEmail}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="#profile"
|
href="#profile"
|
||||||
@@ -182,15 +200,18 @@ const DashboardLayout = ({
|
|||||||
<User className="h-4 w-4" />
|
<User className="h-4 w-4" />
|
||||||
Profile
|
Profile
|
||||||
</a>
|
</a>
|
||||||
<a
|
<button
|
||||||
href="#logout"
|
type="button"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
onClick={() => setIsUserMenuOpen(false)}
|
onClick={() => {
|
||||||
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"
|
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 className="h-4 w-4" />
|
||||||
Logout
|
Logout
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user