Adding login and logout
This commit is contained in:
parent
5779dd762f
commit
adcde37071
28
app/api/auth/logout/route.tsx
Normal file
28
app/api/auth/logout/route.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
// app/api/auth/logout/route.ts
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
// This is your DELETE handler for the logout endpoint
|
||||
export async function DELETE() {
|
||||
try {
|
||||
// Clear the authentication cookie.
|
||||
// This MUST match the name of the cookie set during login.
|
||||
// In your login handler, the cookie is named "auth_token".
|
||||
(await
|
||||
// Clear the authentication cookie.
|
||||
// This MUST match the name of the cookie set during login.
|
||||
// In your login handler, the cookie is named "auth_token".
|
||||
cookies()).delete("auth_token");
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: true, message: "Logged out successfully" },
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Logout API error:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Internal server error during logout" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import "./Modal.scss";
|
||||
|
||||
interface ModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
overlayClassName?: string;
|
||||
|
||||
@ -39,7 +39,7 @@ export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
|
||||
});
|
||||
router.push(`?${updatedParams.toString()}`);
|
||||
}, 500),
|
||||
[router],
|
||||
[router]
|
||||
);
|
||||
|
||||
const handleFieldChange = (field: string, value: string) => {
|
||||
@ -165,7 +165,7 @@ export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
|
||||
onChange={(newValue) =>
|
||||
handleFieldChange(
|
||||
field,
|
||||
newValue?.toISOString() || "",
|
||||
newValue?.toISOString() || ""
|
||||
)
|
||||
}
|
||||
slotProps={{
|
||||
|
||||
@ -15,6 +15,7 @@ export default function LoginModal({
|
||||
const [password, setPassword] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
console.log("LoginModal rendered"); // Debugging log to check if the component renders
|
||||
|
||||
// Effect to clear authentication messages when email or password inputs change
|
||||
useEffect(() => {
|
||||
|
||||
@ -7,10 +7,17 @@ import {
|
||||
IconButton,
|
||||
ListItemIcon,
|
||||
Typography,
|
||||
Button,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import LogoutIcon from "@mui/icons-material/Logout";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { selectIsLoggedIn, selectStatus } from "@/app/redux/auth/selectors";
|
||||
import { logout } from "@/app/redux/auth/authSlice";
|
||||
import { AppDispatch } from "@/app/redux/types";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function AccountMenu() {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
@ -24,6 +31,38 @@ export default function AccountMenu() {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const router = useRouter();
|
||||
|
||||
// Select relevant state from your auth slice
|
||||
const isLoggedIn = useSelector(selectIsLoggedIn);
|
||||
const authStatus = useSelector(selectStatus);
|
||||
|
||||
// Determine if we're currently in the process of logging out
|
||||
const isLoggingOut = authStatus === "loading";
|
||||
|
||||
const handleLogout = async () => {
|
||||
// Dispatch the logout thunk
|
||||
const resultAction = await dispatch(logout());
|
||||
|
||||
// Check if logout was successful based on the action result
|
||||
if (logout.fulfilled.match(resultAction)) {
|
||||
console.log("Logout successful, redirecting...");
|
||||
router.push("/login"); // Redirect to your login page
|
||||
} else {
|
||||
// Handle logout failure (e.g., show an error message)
|
||||
console.error("Logout failed:", resultAction.payload);
|
||||
// You might want to display a toast or alert here
|
||||
}
|
||||
};
|
||||
|
||||
console.log("[isLoggedin]", isLoggedIn);
|
||||
|
||||
// Only show the logout button if the user is logged in
|
||||
if (!isLoggedIn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton onClick={handleClick} color="inherit">
|
||||
@ -56,7 +95,19 @@ export default function AccountMenu() {
|
||||
<ListItemIcon>
|
||||
<LogoutIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit">Sign out</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={handleLogout}
|
||||
disabled={isLoggingOut}
|
||||
startIcon={
|
||||
isLoggingOut ? (
|
||||
<CircularProgress size={20} color="inherit" />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{isLoggingOut ? "Logging Out..." : "Logout"}
|
||||
</Button>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import ThemeRegistry from "@/config/ThemeRegistry";
|
||||
import type { Metadata } from "next";
|
||||
import ReduxProvider from "./redux/ReduxProvider";
|
||||
import "../styles/globals.scss";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@ -15,7 +16,9 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<ThemeRegistry>{children}</ThemeRegistry>
|
||||
<ReduxProvider>
|
||||
<ThemeRegistry>{children}</ThemeRegistry>
|
||||
</ReduxProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@ -13,9 +13,9 @@ function LoginPageContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const redirectPath = searchParams.get("redirect") || "/dashboard";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
|
||||
console.log("LoginPageContent rendered", isLoggedIn); // Debugging log to check if the component renders
|
||||
useEffect(() => {
|
||||
const checkAuthStatus = async () => {
|
||||
// Optionally implement
|
||||
|
||||
24
app/redux/ReduxProvider.tsx
Normal file
24
app/redux/ReduxProvider.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
// app/redux/ReduxProvider.tsx
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react"; // Import useEffect
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./store";
|
||||
import { initializeAuth } from "./auth/authSlice";
|
||||
|
||||
export default function ReduxProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
// Get the dispatch function directly from the store for initial dispatch
|
||||
const dispatch = store.dispatch;
|
||||
|
||||
useEffect(() => {
|
||||
// Dispatch initializeAuth when the ReduxProvider component mounts on the client.
|
||||
// This ensures your Redux isLoggedIn state is synced with localStorage after a page refresh.
|
||||
dispatch(initializeAuth());
|
||||
}, [dispatch]); // Dependency array ensures it runs only once on mount
|
||||
|
||||
return <Provider store={store}>{children}</Provider>;
|
||||
}
|
||||
158
app/redux/auth/authSlice.tsx
Normal file
158
app/redux/auth/authSlice.tsx
Normal file
@ -0,0 +1,158 @@
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
// Define the initial state for the authentication slice
|
||||
interface AuthState {
|
||||
isLoggedIn: boolean;
|
||||
authMessage: string;
|
||||
status: "idle" | "loading" | "succeeded" | "failed";
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
isLoggedIn: false,
|
||||
authMessage: "",
|
||||
status: "idle",
|
||||
error: null,
|
||||
};
|
||||
|
||||
// Async Thunk for Login
|
||||
// This handles the API call to your Next.js login Route Handler
|
||||
export const login = createAsyncThunk(
|
||||
"auth/login",
|
||||
async (
|
||||
{ email, password }: { email: string; password: string },
|
||||
{ rejectWithValue }
|
||||
) => {
|
||||
try {
|
||||
const response = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
// If the server responded with an error status (e.g., 401, 400, 500)
|
||||
return rejectWithValue(data.message || "Login failed");
|
||||
}
|
||||
|
||||
// On successful login, the backend sets the HTTP-only cookie.
|
||||
// We'll set a client-side flag (like localStorage) for immediate UI updates,
|
||||
// though the primary source of truth for auth is the HTTP-only cookie.
|
||||
if (typeof window !== "undefined") {
|
||||
// Ensure localStorage access is client-side
|
||||
localStorage.setItem("userToken", "mock-authenticated"); // For client-side state sync
|
||||
}
|
||||
return data.message || "Login successful";
|
||||
} catch (error: unknown) {
|
||||
// Handle network errors or other unexpected issues
|
||||
if (error instanceof Error) {
|
||||
return rejectWithValue(error.message || "Network error during login");
|
||||
}
|
||||
return rejectWithValue("Network error during login");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Async Thunk for Logout
|
||||
// This handles the API call to your Next.js logout Route Handler
|
||||
export const logout = createAsyncThunk(
|
||||
"auth/logout",
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await fetch("/api/auth/logout", {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
// If the server responded with an error status
|
||||
return rejectWithValue(data.message || "Logout failed");
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
// Ensure localStorage access is client-side
|
||||
localStorage.removeItem("userToken"); // Clear client-side flag
|
||||
}
|
||||
return data.message || "Logged out successfully";
|
||||
} catch (error: unknown) {
|
||||
// Handle network errors
|
||||
if (error instanceof Error) {
|
||||
return rejectWithValue(error.message || "Network error during logout");
|
||||
}
|
||||
return rejectWithValue("Network error during logout");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Create the authentication slice
|
||||
const authSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState,
|
||||
reducers: {
|
||||
// Reducer to set an authentication message (e.g., from UI actions)
|
||||
setAuthMessage: (state, action: PayloadAction<string>) => {
|
||||
state.authMessage = action.payload;
|
||||
},
|
||||
// Reducer to clear the authentication message
|
||||
clearAuthMessage: (state) => {
|
||||
state.authMessage = "";
|
||||
},
|
||||
// Reducer to initialize login status from client-side storage (e.g., on app load)
|
||||
// This is useful for cases where middleware might not redirect immediately,
|
||||
// or for client-side rendering of protected content based on initial state.
|
||||
initializeAuth: (state) => {
|
||||
if (typeof window !== "undefined") {
|
||||
// Ensure this runs only on the client
|
||||
const userToken = localStorage.getItem("userToken");
|
||||
state.isLoggedIn = userToken === "mock-authenticated";
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
// Login Thunk Reducers
|
||||
.addCase(login.pending, (state) => {
|
||||
state.status = "loading";
|
||||
state.error = null;
|
||||
state.authMessage = "Attempting login...";
|
||||
})
|
||||
.addCase(login.fulfilled, (state, action) => {
|
||||
state.status = "succeeded";
|
||||
state.isLoggedIn = true;
|
||||
state.authMessage = action.payload;
|
||||
})
|
||||
.addCase(login.rejected, (state, action) => {
|
||||
state.status = "failed";
|
||||
state.isLoggedIn = false;
|
||||
state.error = action.payload as string;
|
||||
state.authMessage = action.payload as string; // Display error message
|
||||
})
|
||||
// Logout Thunk Reducers
|
||||
.addCase(logout.pending, (state) => {
|
||||
state.status = "loading";
|
||||
state.error = null;
|
||||
state.authMessage = "Logging out...";
|
||||
})
|
||||
.addCase(logout.fulfilled, (state, action) => {
|
||||
state.status = "succeeded";
|
||||
state.isLoggedIn = false;
|
||||
state.authMessage = action.payload;
|
||||
})
|
||||
.addCase(logout.rejected, (state, action) => {
|
||||
state.status = "failed";
|
||||
state.isLoggedIn = true; // Stay logged in if logout failed
|
||||
state.error = action.payload as string;
|
||||
state.authMessage = action.payload as string; // Display error message
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { setAuthMessage, clearAuthMessage, initializeAuth } =
|
||||
authSlice.actions;
|
||||
|
||||
export default authSlice.reducer;
|
||||
8
app/redux/auth/selectors.ts
Normal file
8
app/redux/auth/selectors.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { RootState } from "../types";
|
||||
|
||||
export const selectIsLoggedIn = (state: RootState) =>
|
||||
state.authSlice.isLoggedIn;
|
||||
export const selectStatus = (state: RootState) => state.authSlice?.status;
|
||||
export const selectError = (state: RootState) => state.authSlice?.error;
|
||||
export const selectAuthMessage = (state: RootState) =>
|
||||
state.authSlice?.authMessage;
|
||||
@ -1,8 +1,10 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import advancedSearchReducer from "./advanedSearch/advancedSearchSlice";
|
||||
import authReducer from "./auth/authSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
advancedSearch: advancedSearchReducer,
|
||||
authSlice: authReducer,
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user