diff --git a/app/(admin)/_components/header.tsx b/app/(admin)/_components/header.tsx
index ef68227..a5f5976 100644
--- a/app/(admin)/_components/header.tsx
+++ b/app/(admin)/_components/header.tsx
@@ -17,6 +17,8 @@ import {
} from "lucide-react";
import { useAppTheme } from "@/components/ThemeProvider";
import { ThemeToggle } from "@/components/ThemeToggle";
+import { useAuth } from "@/hooks/useAuth";
+import { toast } from "sonner";
export function Header() {
const pathname = usePathname();
@@ -25,6 +27,14 @@ export function Header() {
const [userMenuOpen, setUserMenuOpen] = useState(false);
const { theme } = useAppTheme();
const isDark = theme === "dark";
+ const { logout } = useAuth();
+
+ const handleLogout = () => {
+ setUserMenuOpen(false);
+ logout();
+ toast.success("Logged out successfully");
+ router.push("/");
+ };
// Mock notifications data
const notifications = [
@@ -209,10 +219,7 @@ export function Header() {
+
+ {/* Logout Button - Only show when authenticated */}
+ {isAuthenticated && (
+
+
+
+ Logout
+
+
+ )}
>
)}
diff --git a/components/Navbar.tsx b/components/Navbar.tsx
index a3a55a2..dcd863a 100644
--- a/components/Navbar.tsx
+++ b/components/Navbar.tsx
@@ -2,13 +2,15 @@
import { motion, AnimatePresence } from "framer-motion";
import { Button } from "@/components/ui/button";
-import { Heart, Menu, X } from "lucide-react";
+import { Heart, Menu, X, LogOut } from "lucide-react";
import { ThemeToggle } from "@/components/ThemeToggle";
import { useEffect, useState } from "react";
import { LoginDialog } from "@/components/LoginDialog";
import { useRouter, usePathname } from "next/navigation";
import Link from "next/link";
import { useAppTheme } from "@/components/ThemeProvider";
+import { useAuth } from "@/hooks/useAuth";
+import { toast } from "sonner";
export function Navbar() {
const { theme } = useAppTheme();
@@ -18,6 +20,9 @@ export function Navbar() {
const router = useRouter();
const pathname = usePathname();
const isUserDashboard = pathname?.startsWith("/user/dashboard");
+ const isUserSettings = pathname?.startsWith("/user/settings");
+ const isUserRoute = pathname?.startsWith("/user/");
+ const { isAuthenticated, logout } = useAuth();
const scrollToSection = (id: string) => {
const element = document.getElementById(id);
@@ -33,6 +38,13 @@ export function Navbar() {
setMobileMenuOpen(false);
};
+ const handleLogout = () => {
+ logout();
+ toast.success("Logged out successfully");
+ setMobileMenuOpen(false);
+ router.push("/");
+ };
+
// Close mobile menu when clicking outside
useEffect(() => {
if (mobileMenuOpen) {
@@ -73,7 +85,7 @@ export function Navbar() {
{/* Desktop Navigation */}
- {!isUserDashboard && (
+ {!isUserRoute && (
scrollToSection("about")}
@@ -98,7 +110,7 @@ export function Navbar() {
{/* Desktop Actions */}
- {!isUserDashboard && (
+ {!isAuthenticated && !isUserDashboard && (
)}
-
- Book Now
-
+ {!isUserDashboard && (
+
+ Book-Now
+
+ )}
+ {isAuthenticated && (
+
+
+ Logout
+
+ )}
{/* Mobile Actions */}
@@ -161,7 +195,7 @@ export function Navbar() {
>
{/* Mobile Navigation Links */}
- {!isUserDashboard && (
+ {!isUserRoute && (
<>
scrollToSection("about")}
@@ -185,7 +219,7 @@ export function Navbar() {
)}
- {!isUserDashboard && (
+ {!isAuthenticated && !isUserDashboard && (
)}
-
- setMobileMenuOpen(false)}>
- Book Now
+ {!isUserDashboard && (
+ setMobileMenuOpen(false)}
+ className={`text-left text-sm sm:text-base font-medium py-2.5 sm:py-3 px-3 sm:px-4 rounded-lg transition-colors ${isDark ? 'text-gray-300 hover:bg-gray-800' : 'text-gray-700 hover:bg-gray-100'}`}
+ >
+ Book-Now
-
+ )}
+ {isAuthenticated && (
+ {
+ handleLogout();
+ }}
+ >
+
+ Logout
+
+ )}
diff --git a/hooks/useAuth.ts b/hooks/useAuth.ts
index 160e42c..cabc484 100644
--- a/hooks/useAuth.ts
+++ b/hooks/useAuth.ts
@@ -43,8 +43,14 @@ export function useAuth() {
// Check if user is authenticated
const isAuthenticated = !!user && !!getStoredTokens().access;
- // Check if user is admin (check both is_admin and isAdmin)
- const isAdmin = user?.is_admin === true || (user as any)?.isAdmin === true;
+ // Check if user is admin (check multiple possible field names)
+ const isAdmin =
+ user?.is_admin === true ||
+ (user as any)?.isAdmin === true ||
+ (user as any)?.is_staff === true ||
+ (user as any)?.isStaff === true ||
+ (user as any)?.is_superuser === true ||
+ (user as any)?.isSuperuser === true;
// Login mutation
const loginMutation = useMutation({
@@ -109,8 +115,8 @@ export function useAuth() {
const logout = useCallback(() => {
clearAuthData();
queryClient.clear();
- router.push("/login");
- }, [queryClient, router]);
+ // Don't redirect here - let components handle redirect as needed
+ }, [queryClient]);
// Login function
const login = useCallback(
diff --git a/lib/actions/auth.ts b/lib/actions/auth.ts
index a97aa4f..93a0bf2 100644
--- a/lib/actions/auth.ts
+++ b/lib/actions/auth.ts
@@ -76,6 +76,28 @@ async function handleResponse(response: Response): Promise {
return data as T;
}
+// Helper function to normalize auth response
+function normalizeAuthResponse(data: AuthResponse): AuthResponse {
+ // Normalize tokens: if tokens are at root level, move them to tokens object
+ if (data.access && data.refresh && !data.tokens) {
+ data.tokens = {
+ access: data.access,
+ refresh: data.refresh,
+ };
+ }
+
+ // Normalize user: only map isVerified to is_verified if needed
+ if (data.user) {
+ const user = data.user as any;
+ if (user.isVerified !== undefined && user.is_verified === undefined) {
+ user.is_verified = user.isVerified;
+ }
+ data.user = user;
+ }
+
+ return data;
+}
+
// Register a new user
export async function registerUser(input: RegisterInput): Promise {
const response = await fetch(API_ENDPOINTS.auth.register, {
@@ -86,6 +108,29 @@ export async function registerUser(input: RegisterInput): Promise
body: JSON.stringify(input),
});
+ // Handle response - check if it's a 500 error that might indicate OTP sending failure
+ // but user registration might have succeeded
+ if (!response.ok && response.status === 500) {
+ try {
+ const data = await response.json();
+ // If the error message mentions OTP or email sending, it might be a partial success
+ const errorMessage = extractErrorMessage(data);
+ if (errorMessage.toLowerCase().includes("otp") ||
+ errorMessage.toLowerCase().includes("email") ||
+ errorMessage.toLowerCase().includes("send") ||
+ errorMessage.toLowerCase().includes("ssl") ||
+ errorMessage.toLowerCase().includes("certificate")) {
+ // Return a partial success response - user might be created, allow OTP resend
+ // This allows the user to proceed to OTP verification and use resend OTP
+ return {
+ message: "User registered, but OTP email could not be sent. Please use resend OTP.",
+ } as AuthResponse;
+ }
+ } catch {
+ // If we can't parse the error, continue to normal error handling
+ }
+ }
+
return handleResponse(response);
}
@@ -100,23 +145,7 @@ export async function verifyOtp(input: VerifyOtpInput): Promise {
});
const data = await handleResponse(response);
-
- // Normalize response: if tokens are at root level, move them to tokens object
- if (data.access && data.refresh && !data.tokens) {
- data.tokens = {
- access: data.access,
- refresh: data.refresh,
- };
- }
-
- // Normalize user: map isVerified to is_verified if needed
- if (data.user) {
- if (data.user.isVerified !== undefined && data.user.is_verified === undefined) {
- data.user.is_verified = data.user.isVerified;
- }
- }
-
- return data;
+ return normalizeAuthResponse(data);
}
// Login user
@@ -130,23 +159,7 @@ export async function loginUser(input: LoginInput): Promise {
});
const data = await handleResponse(response);
-
- // Normalize response: if tokens are at root level, move them to tokens object
- if (data.access && data.refresh && !data.tokens) {
- data.tokens = {
- access: data.access,
- refresh: data.refresh,
- };
- }
-
- // Normalize user: map isVerified to is_verified if needed
- if (data.user) {
- if (data.user.isVerified !== undefined && data.user.is_verified === undefined) {
- data.user.is_verified = data.user.isVerified;
- }
- }
-
- return data;
+ return normalizeAuthResponse(data);
}
// Resend OTP
@@ -245,9 +258,7 @@ export function storeUser(user: User): void {
if (typeof window === "undefined") return;
localStorage.setItem("auth_user", JSON.stringify(user));
-
- // Also set cookie for middleware
- document.cookie = `auth_user=${JSON.stringify(user)}; path=/; max-age=${7 * 24 * 60 * 60}; SameSite=Lax`;
+ document.cookie = `auth_user=${encodeURIComponent(JSON.stringify(user))}; path=/; max-age=${7 * 24 * 60 * 60}; SameSite=Lax`;
}
// Get stored user
diff --git a/lib/models/auth.ts b/lib/models/auth.ts
index 6c7921e..2af43b9 100644
--- a/lib/models/auth.ts
+++ b/lib/models/auth.ts
@@ -11,9 +11,17 @@ export interface User {
last_name: string;
phone_number?: string;
is_admin?: boolean;
+ isAdmin?: boolean; // API uses camelCase
+ is_staff?: boolean;
+ isStaff?: boolean; // API uses camelCase
+ is_superuser?: boolean;
+ isSuperuser?: boolean; // API uses camelCase
is_verified?: boolean;
isVerified?: boolean; // API uses camelCase
+ is_active?: boolean;
+ isActive?: boolean; // API uses camelCase
date_joined?: string;
+ last_login?: string;
created_at?: string;
updated_at?: string;
}
diff --git a/middleware.ts b/middleware.ts
index 4c2bfa4..630a3ac 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -13,16 +13,31 @@ export function middleware(request: NextRequest) {
if (userStr) {
try {
- const user = JSON.parse(userStr);
- isAdmin = user.is_admin === true;
+ // Decode the user string if it's URL encoded
+ const decodedUserStr = decodeURIComponent(userStr);
+ const user = JSON.parse(decodedUserStr);
+ // Check for admin status using multiple possible field names
+ // Admin users must be verified (is_verified or isVerified must be true)
+ const isVerified = user.is_verified === true || user.isVerified === true;
+ const hasAdminRole =
+ user.is_admin === true ||
+ user.isAdmin === true ||
+ user.is_staff === true ||
+ user.isStaff === true ||
+ user.is_superuser === true ||
+ user.isSuperuser === true;
+
+ // User is admin only if they have admin role AND are verified
+ isAdmin = hasAdminRole && isVerified;
} catch {
- // Invalid user data
+ // Invalid user data - silently fail and treat as non-admin
}
}
// Protected routes
const isProtectedRoute = pathname.startsWith("/user") || pathname.startsWith("/admin");
const isAdminRoute = pathname.startsWith("/admin");
+ const isUserRoute = pathname.startsWith("/user");
const isAuthRoute = pathname.startsWith("/login") || pathname.startsWith("/signup");
// Redirect unauthenticated users away from protected routes
@@ -34,12 +49,19 @@ export function middleware(request: NextRequest) {
// Redirect authenticated users away from auth routes
if (isAuthRoute && isAuthenticated) {
+ // Redirect based on user role
+ const redirectPath = isAdmin ? "/admin/dashboard" : "/user/dashboard";
+ return NextResponse.redirect(new URL(redirectPath, request.url));
+ }
+
+ // Redirect admin users away from user routes
+ if (isUserRoute && isAuthenticated && isAdmin) {
return NextResponse.redirect(new URL("/admin/dashboard", request.url));
}
// Redirect non-admin users away from admin routes
- if (isAdminRoute && (!isAuthenticated || !isAdmin)) {
- return NextResponse.redirect(new URL("/admin/dashboard", request.url));
+ if (isAdminRoute && isAuthenticated && !isAdmin) {
+ return NextResponse.redirect(new URL("/user/dashboard", request.url));
}
return NextResponse.next();