Added Logout
This commit is contained in:
parent
e2fc2b73c9
commit
7851a6fa24
24
payment-iq/app/api/auth/logout/route.tsx
Normal file
24
payment-iq/app/api/auth/logout/route.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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(request: Request) {
|
||||||
|
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".
|
||||||
|
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 {
|
interface ModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose?: () => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
overlayClassName?: string;
|
overlayClassName?: string;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import SearchIcon from "@mui/icons-material/Search";
|
|||||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||||
import { useSearchParams, useRouter } from "next/navigation";
|
import { useSearchParams, useRouter } from "next/navigation";
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import { ISearchLabel } from "../pages/transactions/types";
|
import { ISearchLabel } from "../DataTable/types";
|
||||||
|
|
||||||
export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
|
export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@ -39,7 +39,7 @@ export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
|
|||||||
});
|
});
|
||||||
router.push(`?${updatedParams.toString()}`);
|
router.push(`?${updatedParams.toString()}`);
|
||||||
}, 500),
|
}, 500),
|
||||||
[router],
|
[router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFieldChange = (field: string, value: string) => {
|
const handleFieldChange = (field: string, value: string) => {
|
||||||
@ -165,7 +165,7 @@ export default function AdvancedSearch({ labels }: { labels: ISearchLabel[] }) {
|
|||||||
onChange={(newValue) =>
|
onChange={(newValue) =>
|
||||||
handleFieldChange(
|
handleFieldChange(
|
||||||
field,
|
field,
|
||||||
newValue?.toISOString() || "",
|
newValue?.toISOString() || ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
interface ISearchLabel {
|
export interface ISearchLabel {
|
||||||
label: string;
|
label: string;
|
||||||
field: string;
|
field: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
@ -7,10 +7,17 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
ListItemIcon,
|
ListItemIcon,
|
||||||
Typography,
|
Typography,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import LogoutIcon from "@mui/icons-material/Logout";
|
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, RootState } from "@/app/redux/types";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function AccountMenu() {
|
export default function AccountMenu() {
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
@ -24,6 +31,38 @@ export default function AccountMenu() {
|
|||||||
setAnchorEl(null);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton onClick={handleClick} color="inherit">
|
<IconButton onClick={handleClick} color="inherit">
|
||||||
@ -56,7 +95,19 @@ export default function AccountMenu() {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<LogoutIcon fontSize="small" />
|
<LogoutIcon fontSize="small" />
|
||||||
</ListItemIcon>
|
</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>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,79 +1,67 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import LoginModal from "../features/Auth/LoginModal"; // Your LoginModal component
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import LoginModal from "../features/Auth/LoginModal";
|
||||||
import "./page.scss"; // Global styles for LoginModal and page
|
|
||||||
import Modal from "../components/Modal/Modal";
|
import Modal from "../components/Modal/Modal";
|
||||||
|
import { AppDispatch } from "../redux/types";
|
||||||
|
import {
|
||||||
|
selectAuthMessage,
|
||||||
|
selectIsLoggedIn,
|
||||||
|
selectStatus,
|
||||||
|
} from "../redux/auth/selectors";
|
||||||
|
import { clearAuthMessage, login } from "../redux/auth/authSlice";
|
||||||
|
import "./page.scss";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const redirectPath = searchParams.get("redirect") || "/dashboard";
|
const redirectPath = searchParams.get("redirect") || "/dashboard";
|
||||||
|
const isLoggedIn = useSelector(selectIsLoggedIn);
|
||||||
|
const status = useSelector(selectStatus);
|
||||||
|
const authMessage = useSelector(selectAuthMessage);
|
||||||
|
|
||||||
const [authMessage, setAuthMessage] = useState("");
|
const dispatch = useDispatch<AppDispatch>(); // Initialize useDispatch
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
|
||||||
|
|
||||||
|
// Effect to handle redirection after login or if already logged in
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if already logged in by trying to fetch a protected resource or checking a client-accessible flag
|
if (isLoggedIn && status === "succeeded") {
|
||||||
// For HTTP-only cookies, you can't directly read the token here.
|
router.replace(redirectPath); // Redirect to intended path on successful login
|
||||||
// Instead, you'd rely on a server-side check (e.g., in middleware or a Server Component)
|
}
|
||||||
// or a simple client-side flag if your backend also sets one (less secure for token itself).
|
}, [isLoggedIn, status, router, redirectPath]);
|
||||||
// For this example, we'll assume if they land here, they need to log in.
|
|
||||||
// A more robust check might involve a quick API call to /api/auth/status
|
|
||||||
// if the token is in an HTTP-only cookie.
|
|
||||||
const checkAuthStatus = async () => {
|
|
||||||
// In a real app, this might be a call to a /api/auth/status endpoint
|
|
||||||
// that checks the HTTP-only cookie on the server and returns a boolean.
|
|
||||||
// For now, we'll rely on the middleware to redirect if unauthenticated.
|
|
||||||
// If the user somehow lands on /login with a valid cookie, the middleware
|
|
||||||
// should have redirected them already.
|
|
||||||
};
|
|
||||||
checkAuthStatus();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
// Function to handle login attempt, now dispatches Redux thunk
|
||||||
const handleLogin = async (email: string, password: string) => {
|
const handleLogin = async (email: string, password: string) => {
|
||||||
setAuthMessage("Attempting login...");
|
// Dispatch the login async thunk
|
||||||
try {
|
const resultAction = await dispatch(login({ email, password }));
|
||||||
const response = await fetch("/api/auth/login", {
|
|
||||||
// <--- CALLING YOUR INTERNAL ROUTE HANDLER
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ email, password }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
// Check if login was successful based on the thunk's result
|
||||||
|
if (login.fulfilled.match(resultAction)) {
|
||||||
if (response.ok) {
|
return true; // Login successful
|
||||||
// Check if the response status is 2xx
|
|
||||||
// Backend has successfully set the HTTP-only cookie
|
|
||||||
setAuthMessage("Login successful!");
|
|
||||||
setIsLoggedIn(true);
|
|
||||||
// Redirect to the intended path after successful login
|
|
||||||
router.replace(redirectPath);
|
|
||||||
} else {
|
} else {
|
||||||
// Handle login errors (e.g., invalid credentials)
|
return false; // Login failed
|
||||||
setAuthMessage(data.message || "Login failed. Please try again.");
|
|
||||||
setIsLoggedIn(false);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("Login failed:", error);
|
|
||||||
setAuthMessage("An error occurred during login. Please try again later.");
|
|
||||||
setIsLoggedIn(false);
|
|
||||||
}
|
|
||||||
return isLoggedIn; // Return the current login status
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearAuthMessage = () => setAuthMessage("");
|
// Function to clear authentication message, now dispatches Redux action
|
||||||
|
const handleClearAuthMessage = () => {
|
||||||
|
dispatch(clearAuthMessage());
|
||||||
|
};
|
||||||
|
|
||||||
// If user is already logged in (e.g., redirected by middleware to dashboard),
|
// If user is already logged in, show a redirecting message
|
||||||
// this page shouldn't be visible. The middleware should handle the primary redirect.
|
if (isLoggedIn) {
|
||||||
// This `isLoggedIn` state here is more for internal page logic if the user somehow
|
return (
|
||||||
// bypasses middleware or lands on /login with a valid session.
|
<div className="page-container">
|
||||||
// For a robust setup, the middleware is key.
|
<div className="page-container__content">
|
||||||
|
<h1 className="page-container__title">Payment Backoffice</h1>
|
||||||
|
<p className="page-container__message--logged-in">
|
||||||
|
You are logged in. Redirecting to dashboard...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
@ -84,18 +72,11 @@ export default function LoginPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Always show the modal on the login page */}
|
<Modal open={true} title="Login to Payment Cashier">
|
||||||
<Modal
|
|
||||||
open={true} // Always open on the login page
|
|
||||||
onClose={() => {
|
|
||||||
/* No direct close for login modal, user must log in */
|
|
||||||
}}
|
|
||||||
title="Login to Backoffice"
|
|
||||||
>
|
|
||||||
<LoginModal
|
<LoginModal
|
||||||
onLogin={handleLogin} // Pass the API call function
|
onLogin={handleLogin}
|
||||||
authMessage={authMessage}
|
authMessage={authMessage}
|
||||||
clearAuthMessage={clearAuthMessage}
|
clearAuthMessage={handleClearAuthMessage}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,24 @@
|
|||||||
|
// app/redux/ReduxProvider.tsx
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect } from "react"; // Import useEffect
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "./store";
|
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
|
||||||
|
|
||||||
export default function ReduxProvider({ children }: { children: React.ReactNode }) {
|
|
||||||
return <Provider store={store}>{children}</Provider>;
|
return <Provider store={store}>{children}</Provider>;
|
||||||
}
|
}
|
||||||
|
|||||||
152
payment-iq/app/redux/auth/authSlice.tsx
Normal file
152
payment-iq/app/redux/auth/authSlice.tsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
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: any) {
|
||||||
|
// Handle network errors or other unexpected issues
|
||||||
|
return rejectWithValue(error.message || "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: any) {
|
||||||
|
// Handle network errors
|
||||||
|
return rejectWithValue(error.message || "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
payment-iq/app/redux/auth/selectors.ts
Normal file
8
payment-iq/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 { configureStore } from "@reduxjs/toolkit";
|
||||||
import advancedSearchReducer from "./advanedSearch/advancedSearchSlice";
|
import advancedSearchReducer from "./advanedSearch/advancedSearchSlice";
|
||||||
|
import authReducer from "./auth/authSlice";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
advancedSearch: advancedSearchReducer,
|
advancedSearch: advancedSearchReducer,
|
||||||
|
authSlice: authReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user