fix logout
This commit is contained in:
parent
a935417a31
commit
9e2345c0f7
5 changed files with 12351 additions and 846 deletions
11376
package-lock.json
generated
Normal file
11376
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
18
package.json
18
package.json
|
@ -10,10 +10,10 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "4.0.3",
|
||||
"@astrojs/node": "^9.0.0",
|
||||
"@astrojs/react": "^4.2.0",
|
||||
"@astrojs/tailwind": "5.1.4",
|
||||
"@astrojs/mdx": "^4.2.3",
|
||||
"@astrojs/node": "^9.1.3",
|
||||
"@astrojs/react": "^4.2.3",
|
||||
"@astrojs/tailwind": "^6.0.2",
|
||||
"@heroui/react": "^2.7.5",
|
||||
"@iconify-json/heroicons": "^1.2.2",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
|
@ -21,9 +21,9 @@
|
|||
"@types/highlight.js": "^10.1.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash": "^4.17.15",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"astro": "5.1.1",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.1",
|
||||
"astro": "^5.5.6",
|
||||
"astro-expressive-code": "^0.40.2",
|
||||
"astro-icon": "^1.1.5",
|
||||
"chart.js": "^4.4.7",
|
||||
|
@ -37,9 +37,9 @@
|
|||
"next": "^15.1.2",
|
||||
"pocketbase": "^0.25.1",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^19.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-icons": "^5.4.0",
|
||||
"rehype-expressive-code": "^0.40.2",
|
||||
|
|
|
@ -59,16 +59,7 @@ export default function AccountSecuritySettings({
|
|||
checkAuth();
|
||||
}, []);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await logger.send('logout', 'auth', 'User manually logged out from settings page');
|
||||
await auth.logout();
|
||||
window.location.href = '/';
|
||||
} catch (error) {
|
||||
console.error('Error during logout:', error);
|
||||
toast.error('Failed to log out. Please try again.');
|
||||
}
|
||||
};
|
||||
// No logout functions needed here as logout is handled in the dashboard menu
|
||||
|
||||
const detectBrowser = (userAgent: string): string => {
|
||||
if (userAgent.indexOf('Chrome') > -1) return 'Chrome';
|
||||
|
@ -179,17 +170,13 @@ export default function AccountSecuritySettings({
|
|||
<h4 className="font-semibold text-lg mb-2">Account Actions</h4>
|
||||
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="btn btn-error btn-outline w-full md:w-auto"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
|
||||
<p className="text-sm text-warning p-3 bg-warning bg-opacity-10 rounded-lg">
|
||||
If you need to delete your account or have other account-related issues,
|
||||
please contact an IEEE UCSD administrator.
|
||||
</p>
|
||||
<p className="text-sm text-info p-3 bg-info bg-opacity-10 rounded-lg">
|
||||
To log out of your account, use the Logout option in the dashboard menu.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
79
src/pages/api/logout.ts
Normal file
79
src/pages/api/logout.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import type { APIRoute } from "astro";
|
||||
|
||||
// Mark this endpoint as server-rendered, not static
|
||||
export const prerender = false;
|
||||
|
||||
export const GET: APIRoute = async ({ request, redirect }) => {
|
||||
try {
|
||||
// Get the Logto endpoint and client ID from environment variables
|
||||
const logtoEndpoint = import.meta.env.LOGTO_ENDPOINT;
|
||||
const clientId = import.meta.env.LOGTO_POCKETBASE_APP_ID;
|
||||
|
||||
if (!logtoEndpoint) {
|
||||
throw new Error("LOGTO_ENDPOINT environment variable is not set");
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
throw new Error(
|
||||
"LOGTO_POCKETBASE_APP_ID environment variable is not set",
|
||||
);
|
||||
}
|
||||
|
||||
// Get the current origin to use as the redirect URL
|
||||
const url = new URL(request.url);
|
||||
const origin = url.origin;
|
||||
|
||||
// Construct the redirect URL (back to dashboard)
|
||||
const redirectUrl = `${origin}/dashboard`;
|
||||
|
||||
// Log the redirect URL for debugging
|
||||
console.log(`Setting post-logout redirect to: ${redirectUrl}`);
|
||||
console.log(`Using client ID: ${clientId}`);
|
||||
|
||||
// Make a POST request to the Logto session end endpoint with the redirect in the body
|
||||
const logoutUrl = `${logtoEndpoint}/oidc/session/end`;
|
||||
|
||||
try {
|
||||
// Try to make a POST request with the redirect in the body and client ID
|
||||
const response = await fetch(logoutUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
post_logout_redirect_uri: redirectUrl,
|
||||
client_id: clientId,
|
||||
}),
|
||||
redirect: "manual", // Don't automatically follow redirects
|
||||
});
|
||||
|
||||
// If we get a redirect response, follow it
|
||||
if (response.status >= 300 && response.status < 400) {
|
||||
const location = response.headers.get("Location");
|
||||
if (location) {
|
||||
console.log(`Received redirect to: ${location}`);
|
||||
return redirect(location);
|
||||
}
|
||||
}
|
||||
|
||||
// If POST doesn't work, fall back to the query parameter approach
|
||||
console.log(
|
||||
"POST request didn't result in expected redirect, falling back to GET",
|
||||
);
|
||||
return redirect(
|
||||
`${logoutUrl}?post_logout_redirect_uri=${encodeURIComponent(redirectUrl)}&client_id=${encodeURIComponent(clientId)}`,
|
||||
);
|
||||
} catch (fetchError) {
|
||||
console.error("Error making POST request to Logto:", fetchError);
|
||||
// Fall back to the query parameter approach
|
||||
return redirect(
|
||||
`${logoutUrl}?post_logout_redirect_uri=${encodeURIComponent(redirectUrl)}&client_id=${encodeURIComponent(clientId)}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in logout API:", error);
|
||||
|
||||
// If there's an error, redirect to dashboard anyway
|
||||
return redirect("/dashboard");
|
||||
}
|
||||
};
|
|
@ -32,8 +32,8 @@ const components = Object.fromEntries(
|
|||
);
|
||||
// console.log(`Loaded component: ${section.component}`); // Debug log
|
||||
return [section.component, component.default];
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// console.log("Available components:", Object.keys(components)); // Debug log
|
||||
|
@ -52,10 +52,7 @@ const components = Object.fromEntries(
|
|||
</head>
|
||||
<body class="bg-base-200">
|
||||
<!-- First Time Login Manager - This handles the onboarding popup for new users -->
|
||||
<FirstTimeLoginManager
|
||||
client:load
|
||||
logtoApiEndpoint={logtoApiEndpoint}
|
||||
/>
|
||||
<FirstTimeLoginManager client:load logtoApiEndpoint={logtoApiEndpoint} />
|
||||
|
||||
<div class="flex h-screen">
|
||||
<!-- Sidebar -->
|
||||
|
@ -75,33 +72,19 @@ const components = Object.fromEntries(
|
|||
<!-- User Profile -->
|
||||
<div class="p-6 border-b border-base-200">
|
||||
<!-- Loading State -->
|
||||
<div
|
||||
id="userProfileSkeleton"
|
||||
class="flex items-center gap-4"
|
||||
>
|
||||
<div id="userProfileSkeleton" class="flex items-center gap-4">
|
||||
<div class="avatar flex items-center justify-center">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl bg-base-300 animate-pulse"
|
||||
>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-xl bg-base-300 animate-pulse"></div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div
|
||||
class="h-6 w-32 bg-base-300 animate-pulse rounded mb-2"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="h-5 w-20 bg-base-300 animate-pulse rounded"
|
||||
>
|
||||
<div class="h-6 w-32 bg-base-300 animate-pulse rounded mb-2">
|
||||
</div>
|
||||
<div class="h-5 w-20 bg-base-300 animate-pulse rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Signed Out State -->
|
||||
<div
|
||||
id="userProfileSignedOut"
|
||||
class="flex items-center gap-4 hidden"
|
||||
>
|
||||
<div id="userProfileSignedOut" class="flex items-center gap-4 hidden">
|
||||
<div class="avatar flex items-center justify-center">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl bg-base-300 text-base-content/30 flex items-center justify-center"
|
||||
|
@ -110,22 +93,15 @@ const components = Object.fromEntries(
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="font-medium text-lg text-base-content/70"
|
||||
>
|
||||
<h3 class="font-medium text-lg text-base-content/70">
|
||||
Signed Out
|
||||
</h3>
|
||||
<div class="badge badge-outline mt-1 opacity-50">
|
||||
Guest
|
||||
</div>
|
||||
<div class="badge badge-outline mt-1 opacity-50">Guest</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actual Profile -->
|
||||
<div
|
||||
id="userProfileSummary"
|
||||
class="flex items-center gap-4 hidden"
|
||||
>
|
||||
<div id="userProfileSummary" class="flex items-center gap-4 hidden">
|
||||
<div class="avatar flex items-center justify-center">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl bg-[#06659d] text-white ring ring-base-200 ring-offset-base-100 ring-offset-2 inline-flex items-center justify-center"
|
||||
|
@ -137,9 +113,7 @@ const components = Object.fromEntries(
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-medium text-lg" id="userName">
|
||||
Loading...
|
||||
</h3>
|
||||
<h3 class="font-medium text-lg" id="userName">Loading...</h3>
|
||||
<div
|
||||
class="badge badge-outline mt-1 border-[#06659d] text-[#06659d]"
|
||||
id="userRole"
|
||||
|
@ -178,64 +152,41 @@ const components = Object.fromEntries(
|
|||
<div id="actualMenu" class="hidden">
|
||||
{
|
||||
Object.entries(dashboardConfig.categories).map(
|
||||
([categoryKey, category]: [
|
||||
string,
|
||||
any,
|
||||
]) => (
|
||||
([categoryKey, category]: [string, any]) => (
|
||||
<>
|
||||
<li
|
||||
class={`menu-title font-medium opacity-70 ${
|
||||
category.role &&
|
||||
category.role !== "none"
|
||||
category.role && category.role !== "none"
|
||||
? "hidden"
|
||||
: ""
|
||||
}`}
|
||||
data-role-required={
|
||||
category.role || "none"
|
||||
}
|
||||
data-role-required={category.role || "none"}
|
||||
>
|
||||
<span>{category.title}</span>
|
||||
</li>
|
||||
{category.sections.map(
|
||||
(sectionKey: string) => {
|
||||
const section =
|
||||
dashboardConfig
|
||||
.sections[
|
||||
sectionKey
|
||||
];
|
||||
{category.sections.map((sectionKey: string) => {
|
||||
const section = dashboardConfig.sections[sectionKey];
|
||||
return (
|
||||
<li
|
||||
class={
|
||||
section.role &&
|
||||
section.role !==
|
||||
"none"
|
||||
section.role && section.role !== "none"
|
||||
? "hidden"
|
||||
: ""
|
||||
}
|
||||
data-role-required={
|
||||
section.role
|
||||
}
|
||||
data-role-required={section.role}
|
||||
>
|
||||
<button
|
||||
class={`dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 ${section.class || ""}`}
|
||||
data-section={
|
||||
sectionKey
|
||||
}
|
||||
data-section={sectionKey}
|
||||
>
|
||||
<Icon
|
||||
name={
|
||||
section.icon
|
||||
}
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<Icon name={section.icon} class="h-5 w-5" />
|
||||
{section.title}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
)}
|
||||
})}
|
||||
</>
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
@ -248,15 +199,10 @@ const components = Object.fromEntries(
|
|||
class="flex-1 overflow-x-hidden overflow-y-auto bg-base-200 w-full xl:w-[calc(100%-20rem)]"
|
||||
>
|
||||
<!-- Mobile Header -->
|
||||
<header
|
||||
class="bg-base-100 p-4 shadow-md xl:hidden sticky top-0 z-40"
|
||||
>
|
||||
<header class="bg-base-100 p-4 shadow-md xl:hidden sticky top-0 z-40">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
id="mobileSidebarToggle"
|
||||
class="btn btn-square btn-ghost"
|
||||
>
|
||||
<button id="mobileSidebarToggle" class="btn btn-square btn-ghost">
|
||||
<Icon name="heroicons:bars-3" class="h-6 w-6" />
|
||||
</button>
|
||||
<h1 class="text-xl font-bold">IEEE UCSD</h1>
|
||||
|
@ -268,11 +214,8 @@ const components = Object.fromEntries(
|
|||
<div class="p-4 md:p-6 max-w-[1600px] mx-auto">
|
||||
<!-- Loading State -->
|
||||
<div id="pageLoadingState" class="w-full">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-4 sm:p-8"
|
||||
>
|
||||
<div class="loading loading-spinner loading-lg">
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center p-4 sm:p-8">
|
||||
<div class="loading loading-spinner loading-lg"></div>
|
||||
<p class="mt-4 opacity-70">Loading dashboard...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -298,9 +241,7 @@ const components = Object.fromEntries(
|
|||
<!-- Not Authenticated State -->
|
||||
<div id="notAuthenticatedState" class="hidden w-full">
|
||||
<div class="card bg-base-100 shadow-xl mx-2 sm:mx-0">
|
||||
<div
|
||||
class="card-body items-center text-center p-4 sm:p-8"
|
||||
>
|
||||
<div class="card-body items-center text-center p-4 sm:p-8">
|
||||
<div class="mb-4 sm:mb-6">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -317,11 +258,9 @@ const components = Object.fromEntries(
|
|||
<h2 class="card-title text-xl sm:text-2xl mb-2">
|
||||
Sign in to Access Dashboard
|
||||
</h2>
|
||||
<p
|
||||
class="opacity-70 mb-4 sm:mb-6 text-sm sm:text-base"
|
||||
>
|
||||
Please sign in with your IEEE UCSD account
|
||||
to access the dashboard.
|
||||
<p class="opacity-70 mb-4 sm:mb-6 text-sm sm:text-base">
|
||||
Please sign in with your IEEE UCSD account to access the
|
||||
dashboard.
|
||||
</p>
|
||||
<button
|
||||
class="login-button btn btn-primary btn-lg gap-2 w-full sm:w-auto"
|
||||
|
@ -353,14 +292,12 @@ const components = Object.fromEntries(
|
|||
// Skip if no component is defined
|
||||
if (!section.component) return null;
|
||||
|
||||
const Component =
|
||||
components[section.component];
|
||||
const Component = components[section.component];
|
||||
return (
|
||||
<div
|
||||
id={`${sectionKey}Section`}
|
||||
class={`dashboard-section hidden ${
|
||||
section.role &&
|
||||
section.role !== "none"
|
||||
section.role && section.role !== "none"
|
||||
? "role-restricted"
|
||||
: ""
|
||||
}`}
|
||||
|
@ -369,7 +306,7 @@ const components = Object.fromEntries(
|
|||
<Component />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
@ -398,11 +335,10 @@ const components = Object.fromEntries(
|
|||
}
|
||||
|
||||
// Initialize page state
|
||||
const pageLoadingState =
|
||||
document.getElementById("pageLoadingState");
|
||||
const pageLoadingState = document.getElementById("pageLoadingState");
|
||||
const pageErrorState = document.getElementById("pageErrorState");
|
||||
const notAuthenticatedState = document.getElementById(
|
||||
"notAuthenticatedState"
|
||||
"notAuthenticatedState",
|
||||
);
|
||||
const mainContent = document.getElementById("mainContent");
|
||||
const sidebar = document.querySelector("aside");
|
||||
|
@ -433,11 +369,9 @@ const components = Object.fromEntries(
|
|||
}
|
||||
|
||||
// For non-sponsor roles, handle normally
|
||||
document
|
||||
.querySelectorAll("[data-role-required]")
|
||||
.forEach((element) => {
|
||||
document.querySelectorAll("[data-role-required]").forEach((element) => {
|
||||
const requiredRole = element.getAttribute(
|
||||
"data-role-required"
|
||||
"data-role-required",
|
||||
) as OfficerStatus;
|
||||
|
||||
// Skip elements that don't have a role requirement
|
||||
|
@ -447,22 +381,185 @@ const components = Object.fromEntries(
|
|||
}
|
||||
|
||||
// Check if user has permission for this role
|
||||
const hasPermission = hasAccess(
|
||||
officerStatus,
|
||||
requiredRole
|
||||
);
|
||||
const hasPermission = hasAccess(officerStatus, requiredRole);
|
||||
|
||||
// Only show elements if user has permission
|
||||
element.classList.toggle("hidden", !hasPermission);
|
||||
});
|
||||
};
|
||||
|
||||
// Function to delete all cookies (to handle Logto logout)
|
||||
const deleteAllCookies = () => {
|
||||
// Get all cookies
|
||||
const cookies = document.cookie.split(";");
|
||||
|
||||
// Common paths that might have cookies
|
||||
const paths = ["/", "/dashboard", "/auth", "/api"];
|
||||
|
||||
// Domains to target
|
||||
const domains = [
|
||||
"", // current domain
|
||||
"auth.ieeeucsd.org",
|
||||
".auth.ieeeucsd.org",
|
||||
"ieeeucsd.org",
|
||||
".ieeeucsd.org",
|
||||
"dev.ieeeucsd.org",
|
||||
".dev.ieeeucsd.org",
|
||||
];
|
||||
|
||||
// Delete each cookie with all combinations of paths and domains
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i];
|
||||
const eqPos = cookie.indexOf("=");
|
||||
const name =
|
||||
eqPos > -1 ? cookie.substring(0, eqPos).trim() : cookie.trim();
|
||||
|
||||
if (!name) continue; // Skip empty cookie names
|
||||
|
||||
// Try all combinations of paths and domains
|
||||
for (const path of paths) {
|
||||
// Delete from current domain (no domain specified)
|
||||
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=${path}`;
|
||||
|
||||
// Try with specific domains
|
||||
for (const domain of domains) {
|
||||
if (domain) {
|
||||
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=${path};domain=${domain}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Specifically target known Logto cookies
|
||||
const logtoSpecificCookies = [
|
||||
"logto",
|
||||
"logto.signin",
|
||||
"logto.session",
|
||||
"logto.callback",
|
||||
];
|
||||
|
||||
for (const cookieName of logtoSpecificCookies) {
|
||||
for (const path of paths) {
|
||||
document.cookie = `${cookieName}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=${path}`;
|
||||
|
||||
for (const domain of domains) {
|
||||
if (domain) {
|
||||
document.cookie = `${cookieName}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=${path};domain=${domain}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function to create and show a logout confirmation modal
|
||||
const showLogoutConfirmation = () => {
|
||||
// Create modal if it doesn't exist
|
||||
let modal = document.getElementById("logoutConfirmModal");
|
||||
if (!modal) {
|
||||
modal = document.createElement("dialog");
|
||||
modal.id = "logoutConfirmModal";
|
||||
modal.className = "modal modal-bottom sm:modal-middle";
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">Confirm Logout</h3>
|
||||
<p class="py-4">Are you sure you want to log out of your account?</p>
|
||||
<div class="modal-action">
|
||||
<button id="cancelLogout" class="btn btn-outline">Cancel</button>
|
||||
<button id="confirmLogout" class="btn btn-error">
|
||||
<span id="logoutSpinner" class="loading loading-spinner loading-sm hidden"></span>
|
||||
<span id="logoutText">Log Out</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>Close</button>
|
||||
</form>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add event listeners
|
||||
document
|
||||
.getElementById("cancelLogout")
|
||||
?.addEventListener("click", () => {
|
||||
(modal as HTMLDialogElement).close();
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("confirmLogout")
|
||||
?.addEventListener("click", async () => {
|
||||
// Show loading state
|
||||
const spinner = document.getElementById("logoutSpinner");
|
||||
const text = document.getElementById("logoutText");
|
||||
const confirmBtn = document.getElementById("confirmLogout");
|
||||
const cancelBtn = document.getElementById("cancelLogout");
|
||||
|
||||
if (spinner) spinner.classList.remove("hidden");
|
||||
if (text) text.textContent = "Logging out...";
|
||||
if (confirmBtn) confirmBtn.setAttribute("disabled", "true");
|
||||
if (cancelBtn) cancelBtn.setAttribute("disabled", "true");
|
||||
|
||||
try {
|
||||
// Log the logout action
|
||||
await logger.send(
|
||||
"logout",
|
||||
"auth",
|
||||
"User logged out from dashboard menu",
|
||||
);
|
||||
|
||||
// Log out from PocketBase using the Authentication class
|
||||
await auth.logout();
|
||||
|
||||
// For extra safety, also directly clear the PocketBase auth store
|
||||
const pb = auth.getPocketBase();
|
||||
pb.authStore.clear();
|
||||
|
||||
// Delete all cookies to ensure Logto is logged out
|
||||
deleteAllCookies();
|
||||
|
||||
// Specifically target auth.ieeeucsd.org cookies with different approaches
|
||||
document.cookie =
|
||||
"logto=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=auth.ieeeucsd.org";
|
||||
document.cookie =
|
||||
"logto=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=.ieeeucsd.org";
|
||||
document.cookie =
|
||||
"logto=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
|
||||
|
||||
// Direct logout from Logto by redirecting to our API endpoint
|
||||
// This will handle the Logto session end with proper redirect back to dashboard
|
||||
window.location.href = "/api/logout";
|
||||
return; // Stop execution here as we're redirecting
|
||||
} catch (error) {
|
||||
console.error("Error during logout:", error);
|
||||
|
||||
// Show error message if toast is available
|
||||
if (window.toast && typeof window.toast === "function") {
|
||||
window.toast("Failed to log out. Please try again.", {
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
||||
// Reset button state
|
||||
if (spinner) spinner.classList.add("hidden");
|
||||
if (text) text.textContent = "Log Out";
|
||||
if (confirmBtn) confirmBtn.removeAttribute("disabled");
|
||||
if (cancelBtn) cancelBtn.removeAttribute("disabled");
|
||||
|
||||
// Close the modal
|
||||
if (modal) (modal as HTMLDialogElement).close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show the modal
|
||||
if (modal) (modal as HTMLDialogElement).showModal();
|
||||
};
|
||||
|
||||
// Handle navigation
|
||||
const handleNavigation = () => {
|
||||
const navButtons =
|
||||
document.querySelectorAll(".dashboard-nav-btn");
|
||||
const sections =
|
||||
document.querySelectorAll(".dashboard-section");
|
||||
const navButtons = document.querySelectorAll(".dashboard-nav-btn");
|
||||
const sections = document.querySelectorAll(".dashboard-section");
|
||||
const mainContentDiv = document.getElementById("mainContent");
|
||||
|
||||
// Ensure mainContent is visible
|
||||
|
@ -476,8 +573,7 @@ const components = Object.fromEntries(
|
|||
|
||||
// Handle logout button
|
||||
if (sectionKey === "logout") {
|
||||
auth.logout();
|
||||
window.location.reload();
|
||||
showLogoutConfirmation();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -496,8 +592,7 @@ const components = Object.fromEntries(
|
|||
|
||||
// Show selected section
|
||||
const sectionId = `${sectionKey}Section`;
|
||||
const targetSection =
|
||||
document.getElementById(sectionId);
|
||||
const targetSection = document.getElementById(sectionId);
|
||||
if (targetSection) {
|
||||
targetSection.classList.remove("hidden");
|
||||
// console.log(`Showing section: ${sectionId}`); // Debug log
|
||||
|
@ -507,8 +602,7 @@ const components = Object.fromEntries(
|
|||
if (window.innerWidth < 1024 && sidebar) {
|
||||
sidebar.classList.add("-translate-x-full");
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
const overlay =
|
||||
document.getElementById("sidebarOverlay");
|
||||
const overlay = document.getElementById("sidebarOverlay");
|
||||
overlay?.remove();
|
||||
}
|
||||
});
|
||||
|
@ -553,7 +647,7 @@ const components = Object.fromEntries(
|
|||
"",
|
||||
{
|
||||
fields: ["id", "type", "role"],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (officerRecords && officerRecords.items.length > 0) {
|
||||
|
@ -595,7 +689,7 @@ const components = Object.fromEntries(
|
|||
"",
|
||||
{
|
||||
fields: ["id", "company"],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (sponsorRecords && sponsorRecords.items.length > 0) {
|
||||
|
@ -629,8 +723,7 @@ const components = Object.fromEntries(
|
|||
|
||||
if (userName) userName.textContent = fallbackValues.name;
|
||||
if (userRole) userRole.textContent = fallbackValues.role;
|
||||
if (userInitials)
|
||||
userInitials.textContent = fallbackValues.initials;
|
||||
if (userInitials) userInitials.textContent = fallbackValues.initials;
|
||||
|
||||
updateSectionVisibility("" as OfficerStatus);
|
||||
}
|
||||
|
@ -638,18 +731,16 @@ const components = Object.fromEntries(
|
|||
|
||||
// Mobile sidebar toggle
|
||||
const mobileSidebarToggle = document.getElementById(
|
||||
"mobileSidebarToggle"
|
||||
"mobileSidebarToggle",
|
||||
);
|
||||
if (mobileSidebarToggle && sidebar) {
|
||||
const toggleSidebar = () => {
|
||||
const isOpen =
|
||||
!sidebar.classList.contains("-translate-x-full");
|
||||
const isOpen = !sidebar.classList.contains("-translate-x-full");
|
||||
|
||||
if (isOpen) {
|
||||
sidebar.classList.add("-translate-x-full");
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
const overlay =
|
||||
document.getElementById("sidebarOverlay");
|
||||
const overlay = document.getElementById("sidebarOverlay");
|
||||
overlay?.remove();
|
||||
} else {
|
||||
sidebar.classList.remove("-translate-x-full");
|
||||
|
@ -684,8 +775,7 @@ const components = Object.fromEntries(
|
|||
window.toast = originalToast;
|
||||
|
||||
// console.log("User not authenticated");
|
||||
if (pageLoadingState)
|
||||
pageLoadingState.classList.add("hidden");
|
||||
if (pageLoadingState) pageLoadingState.classList.add("hidden");
|
||||
if (notAuthenticatedState)
|
||||
notAuthenticatedState.classList.remove("hidden");
|
||||
return;
|
||||
|
@ -694,30 +784,28 @@ const components = Object.fromEntries(
|
|||
// Initialize auth sync for IndexedDB (for authenticated users)
|
||||
await initAuthSync();
|
||||
|
||||
if (pageLoadingState)
|
||||
pageLoadingState.classList.remove("hidden");
|
||||
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
||||
if (pageErrorState) pageErrorState.classList.add("hidden");
|
||||
if (notAuthenticatedState)
|
||||
notAuthenticatedState.classList.add("hidden");
|
||||
|
||||
// Show loading states
|
||||
const userProfileSkeleton = document.getElementById(
|
||||
"userProfileSkeleton"
|
||||
"userProfileSkeleton",
|
||||
);
|
||||
const userProfileSignedOut = document.getElementById(
|
||||
"userProfileSignedOut"
|
||||
"userProfileSignedOut",
|
||||
);
|
||||
const userProfileSummary =
|
||||
document.getElementById("userProfileSummary");
|
||||
const menuLoadingSkeleton = document.getElementById(
|
||||
"menuLoadingSkeleton"
|
||||
"menuLoadingSkeleton",
|
||||
);
|
||||
const actualMenu = document.getElementById("actualMenu");
|
||||
|
||||
if (userProfileSkeleton)
|
||||
userProfileSkeleton.classList.remove("hidden");
|
||||
if (userProfileSummary)
|
||||
userProfileSummary.classList.add("hidden");
|
||||
if (userProfileSummary) userProfileSummary.classList.add("hidden");
|
||||
if (userProfileSignedOut)
|
||||
userProfileSignedOut.classList.add("hidden");
|
||||
if (menuLoadingSkeleton)
|
||||
|
@ -728,15 +816,11 @@ const components = Object.fromEntries(
|
|||
await updateUserProfile(user);
|
||||
|
||||
// Show actual profile and hide skeleton
|
||||
if (userProfileSkeleton)
|
||||
userProfileSkeleton.classList.add("hidden");
|
||||
if (userProfileSummary)
|
||||
userProfileSummary.classList.remove("hidden");
|
||||
if (userProfileSkeleton) userProfileSkeleton.classList.add("hidden");
|
||||
if (userProfileSummary) userProfileSummary.classList.remove("hidden");
|
||||
|
||||
// Hide all sections first
|
||||
document
|
||||
.querySelectorAll(".dashboard-section")
|
||||
.forEach((section) => {
|
||||
document.querySelectorAll(".dashboard-section").forEach((section) => {
|
||||
section.classList.add("hidden");
|
||||
});
|
||||
|
||||
|
@ -753,7 +837,7 @@ const components = Object.fromEntries(
|
|||
"",
|
||||
{
|
||||
fields: ["id", "type", "role"],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (officerRecords && officerRecords.items.length > 0) {
|
||||
|
@ -791,23 +875,17 @@ const components = Object.fromEntries(
|
|||
"",
|
||||
{
|
||||
fields: ["id", "company"],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
sponsorRecords &&
|
||||
sponsorRecords.items.length > 0
|
||||
) {
|
||||
if (sponsorRecords && sponsorRecords.items.length > 0) {
|
||||
officerStatus = "sponsor";
|
||||
} else {
|
||||
officerStatus = "none";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error determining officer status:",
|
||||
error
|
||||
);
|
||||
console.error("Error determining officer status:", error);
|
||||
officerStatus = "none";
|
||||
}
|
||||
|
||||
|
@ -818,19 +896,14 @@ const components = Object.fromEntries(
|
|||
// Only sponsors get a different default view
|
||||
if (officerStatus === "sponsor") {
|
||||
// For sponsors, show the sponsor dashboard
|
||||
defaultSection = document.getElementById(
|
||||
"sponsorDashboardSection"
|
||||
);
|
||||
defaultSection = document.getElementById("sponsorDashboardSection");
|
||||
defaultButton = document.querySelector(
|
||||
'[data-section="sponsorDashboard"]'
|
||||
'[data-section="sponsorDashboard"]',
|
||||
);
|
||||
} else {
|
||||
// For all other users (including administrators), show the profile section
|
||||
defaultSection =
|
||||
document.getElementById("profileSection");
|
||||
defaultButton = document.querySelector(
|
||||
'[data-section="profile"]'
|
||||
);
|
||||
defaultSection = document.getElementById("profileSection");
|
||||
defaultButton = document.querySelector('[data-section="profile"]');
|
||||
|
||||
// Log the default section for debugging
|
||||
// console.log(`Setting default section to profile for user with role: ${officerStatus}`);
|
||||
|
@ -847,20 +920,16 @@ const components = Object.fromEntries(
|
|||
handleNavigation();
|
||||
|
||||
// Show actual menu and hide skeleton
|
||||
if (menuLoadingSkeleton)
|
||||
menuLoadingSkeleton.classList.add("hidden");
|
||||
if (menuLoadingSkeleton) menuLoadingSkeleton.classList.add("hidden");
|
||||
if (actualMenu) actualMenu.classList.remove("hidden");
|
||||
|
||||
// Show main content and hide loading
|
||||
if (mainContent) mainContent.classList.remove("hidden");
|
||||
if (pageLoadingState)
|
||||
pageLoadingState.classList.add("hidden");
|
||||
if (pageLoadingState) pageLoadingState.classList.add("hidden");
|
||||
} catch (error) {
|
||||
console.error("Error initializing dashboard:", error);
|
||||
if (pageLoadingState)
|
||||
pageLoadingState.classList.add("hidden");
|
||||
if (pageErrorState)
|
||||
pageErrorState.classList.remove("hidden");
|
||||
if (pageLoadingState) pageLoadingState.classList.add("hidden");
|
||||
if (pageErrorState) pageErrorState.classList.remove("hidden");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -872,24 +941,19 @@ const components = Object.fromEntries(
|
|||
.querySelector(".login-button")
|
||||
?.addEventListener("click", async () => {
|
||||
try {
|
||||
if (pageLoadingState)
|
||||
pageLoadingState.classList.remove("hidden");
|
||||
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
||||
if (notAuthenticatedState)
|
||||
notAuthenticatedState.classList.add("hidden");
|
||||
await auth.login();
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
if (pageLoadingState)
|
||||
pageLoadingState.classList.add("hidden");
|
||||
if (pageErrorState)
|
||||
pageErrorState.classList.remove("hidden");
|
||||
if (pageLoadingState) pageLoadingState.classList.add("hidden");
|
||||
if (pageErrorState) pageErrorState.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle logout button click
|
||||
document
|
||||
.getElementById("logoutButton")
|
||||
?.addEventListener("click", () => {
|
||||
document.getElementById("logoutButton")?.addEventListener("click", () => {
|
||||
auth.logout();
|
||||
window.location.reload();
|
||||
});
|
||||
|
@ -902,8 +966,7 @@ const components = Object.fromEntries(
|
|||
|
||||
window.addEventListener("resize", () => {
|
||||
if (window.innerWidth >= 1024) {
|
||||
const overlay =
|
||||
document.getElementById("sidebarOverlay");
|
||||
const overlay = document.getElementById("sidebarOverlay");
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
|
|
Loading…
Reference in a new issue