Adding login
This commit is contained in:
parent
36b85ddf1a
commit
f389f8604d
53
payment-iq/app/api/auth/login/route.tsx
Normal file
53
payment-iq/app/api/auth/login/route.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
// This is your POST handler for the login endpoint
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { email, password } = await request.json();
|
||||
|
||||
// --- Replace with your ACTUAL authentication logic ---
|
||||
// In a real application, you would:
|
||||
// 1. Query your database for the user by email.
|
||||
// 2. Hash the provided password and compare it to the stored hashed password.
|
||||
// 3. If credentials match, generate a secure JWT (JSON Web Token) or session ID.
|
||||
// 4. Store the token/session ID securely (e.g., in a database or Redis).
|
||||
|
||||
// Mock authentication for demonstration purposes:
|
||||
if (email === "admin@example.com" && password === "password123") {
|
||||
const authToken = "mock-jwt-token-12345"; // Replace with a real, securely generated token
|
||||
|
||||
// Set the authentication token as an HTTP-only cookie
|
||||
// HTTP-only cookies are crucial for security as they cannot be accessed by client-side JavaScript,
|
||||
// which mitigates XSS attacks.
|
||||
(
|
||||
await // Set the authentication token as an HTTP-only cookie
|
||||
// HTTP-only cookies are crucial for security as they cannot be accessed by client-side JavaScript,
|
||||
// which mitigates XSS attacks.
|
||||
cookies()
|
||||
).set("auth_token", authToken, {
|
||||
httpOnly: true, // IMPORTANT: Makes the cookie inaccessible to client-side scripts
|
||||
secure: process.env.NODE_ENV === "production", // Use secure in production (HTTPS)
|
||||
maxAge: 60 * 60 * 24 * 7, // 1 week
|
||||
path: "/", // Available across the entire site
|
||||
sameSite: "lax", // Protects against CSRF
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: true, message: "Login successful" },
|
||||
{ status: 200 }
|
||||
);
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Invalid credentials" },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login API error:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,33 @@
|
||||
// app/components/PageLinks/PageLinks.tsx
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { ISidebarLink } from "@/app/features/dashboard/sidebar/SidebarLink.interfaces";
|
||||
import { ISidebarLink } from "@/app/features/dashboard/sidebar/SidebarLink.interfaces"; // Keep this import
|
||||
import clsx from "clsx"; // Utility to merge class names
|
||||
import "./PageLinks.scss";
|
||||
import "./PageLinks.scss"; // Keep this import
|
||||
|
||||
// Define the props interface for your PageLinks component
|
||||
// It now extends ISidebarLink and includes isShowIcon
|
||||
interface IPageLinksProps extends ISidebarLink {
|
||||
isShowIcon?: boolean;
|
||||
}
|
||||
|
||||
// PageLinks component
|
||||
export default function PageLinks({
|
||||
title,
|
||||
path,
|
||||
icon: Icon,
|
||||
}: IPageLinksProps) {
|
||||
icon: Icon, // Destructure icon as Icon
|
||||
}: // isShowIcon, // If you plan to use this prop, uncomment it and add logic
|
||||
IPageLinksProps) {
|
||||
return (
|
||||
<Link href={path} passHref legacyBehavior className="page-link">
|
||||
<a className={clsx("page-link__container")}>
|
||||
{Icon && <Icon />}
|
||||
<span className="page-link__text">{title}</span>
|
||||
</a>
|
||||
// Corrected Link usage for Next.js 13/14 App Router:
|
||||
// - Removed `passHref` and `legacyBehavior`
|
||||
// - Applied `className` directly to the Link component
|
||||
// - Removed the nested `<a>` tag
|
||||
<Link href={path} className={clsx("page-link", "page-link__container")}>
|
||||
{/* Conditionally render Icon if it exists */}
|
||||
{Icon && <Icon />}
|
||||
<span className="page-link__text">{title}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ export default async function BackOfficeUsersPage() {
|
||||
process.env.NEXT_PUBLIC_BASE_URL || process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}`
|
||||
: "http://localhost:3000";
|
||||
const res = await fetch(`${baseUrl}ss/api/dashboard/admin/users`, {
|
||||
const res = await fetch(`${baseUrl}/api/dashboard/admin/users`, {
|
||||
cache: "no-store", // 👈 disables caching for SSR freshness
|
||||
});
|
||||
const users = await res.json();
|
||||
|
||||
197
payment-iq/app/features/Auth/LoginModal.scss
Normal file
197
payment-iq/app/features/Auth/LoginModal.scss
Normal file
@ -0,0 +1,197 @@
|
||||
/* app/styles/LoginModal.scss (BEM Methodology) */
|
||||
|
||||
// Variables for consistent styling
|
||||
$primary-color: #2563eb; // Blue-600 equivalent
|
||||
$primary-hover-color: #1d4ed8; // Blue-700 equivalent
|
||||
$success-color: #16a34a; // Green-600 equivalent
|
||||
$error-color: #dc2626; // Red-600 equivalent
|
||||
$text-color-dark: #1f2937; // Gray-800 equivalent
|
||||
$text-color-medium: #4b5563; // Gray-700 equivalent
|
||||
$text-color-light: #6b7280; // Gray-600 equivalent
|
||||
$border-color: #d1d5db; // Gray-300 equivalent
|
||||
$bg-color-light: #f3f4f6; // Gray-100 equivalent
|
||||
$bg-color-white: #ffffff;
|
||||
|
||||
/* --- Login Modal Block (.login-modal) --- */
|
||||
.login-modal__overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(17, 24, 39, 0.75); // Gray-900 75% opacity
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
padding: 1rem; // p-4
|
||||
}
|
||||
|
||||
.login-modal__content {
|
||||
background-color: $bg-color-white;
|
||||
border-radius: 0.75rem; // rounded-xl
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04); // shadow-2xl
|
||||
padding: 2rem; // p-8
|
||||
width: 100%;
|
||||
max-width: 28rem; // max-w-md
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease-in-out; // transition-all duration-300
|
||||
}
|
||||
|
||||
.login-modal__title {
|
||||
font-size: 1.875rem; // text-3xl
|
||||
font-weight: 700; // font-bold
|
||||
color: $text-color-dark;
|
||||
margin-bottom: 1.5rem; // mb-6
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* --- Login Form Block (.login-form) --- */
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem; // space-y-6
|
||||
}
|
||||
|
||||
.login-form__group {
|
||||
// No specific styles needed here, just a container for label/input
|
||||
}
|
||||
|
||||
.login-form__label {
|
||||
display: block;
|
||||
font-size: 0.875rem; // text-sm
|
||||
font-weight: 500; // font-medium
|
||||
color: $text-color-medium;
|
||||
margin-bottom: 0.25rem; // mb-1
|
||||
}
|
||||
|
||||
.login-form__input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem; // px-4 py-2
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.5rem; // rounded-lg
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // shadow-sm
|
||||
font-size: 0.875rem; // sm:text-sm
|
||||
&:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
border-color: $primary-color; // focus:border-blue-500
|
||||
box-shadow: 0 0 0 1px $primary-color, 0 0 0 3px rgba($primary-color, 0.5); // focus:ring-blue-500
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form__message {
|
||||
text-align: center;
|
||||
font-size: 0.875rem; // text-sm
|
||||
font-weight: 500; // font-medium
|
||||
}
|
||||
|
||||
.login-form__message--success {
|
||||
color: $success-color;
|
||||
}
|
||||
|
||||
.login-form__message--error {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
.login-form__button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0.75rem 1rem; // py-3 px-4
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.5rem; // rounded-lg
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // shadow-sm
|
||||
font-size: 1.125rem; // text-lg
|
||||
font-weight: 600; // font-semibold
|
||||
color: $bg-color-white;
|
||||
background-color: $primary-color;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out; // transition duration-300 ease-in-out
|
||||
&:hover {
|
||||
background-color: darken($primary-color, 5%); // blue-700 equivalent
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5),
|
||||
0 0 0 4px rgba($primary-color, 0.5); // focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form__spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
height: 1.25rem; // h-5
|
||||
width: 1.25rem; // w-5
|
||||
color: white;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Page Container Block (.page-container) --- */
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: $bg-color-light;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: "Inter", sans-serif; // Assuming Inter font is used
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.page-container__content {
|
||||
width: 100%;
|
||||
max-width: 56rem; // max-w-4xl
|
||||
background-color: $bg-color-white;
|
||||
border-radius: 0.75rem; // rounded-xl
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05); // shadow-lg
|
||||
padding: 2rem; // p-8
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-container__title {
|
||||
font-size: 2.25rem; // text-4xl
|
||||
font-weight: 700; // font-bold
|
||||
color: $text-color-dark;
|
||||
margin-bottom: 1.5rem; // mb-6
|
||||
}
|
||||
|
||||
.page-container__message--logged-in {
|
||||
font-size: 1.25rem; // text-xl
|
||||
color: $success-color;
|
||||
margin-bottom: 1rem; // mb-4
|
||||
}
|
||||
|
||||
.page-container__text {
|
||||
color: $text-color-medium;
|
||||
margin-bottom: 1.5rem; // mb-6
|
||||
}
|
||||
|
||||
.page-container__button--logout {
|
||||
padding: 0.75rem 1.5rem; // px-6 py-3
|
||||
background-color: $error-color;
|
||||
color: $bg-color-white;
|
||||
font-weight: 600; // font-semibold
|
||||
border-radius: 0.5rem; // rounded-lg
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // shadow-md
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
112
payment-iq/app/features/Auth/LoginModal.tsx
Normal file
112
payment-iq/app/features/Auth/LoginModal.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
"use client"; // This MUST be the very first line of the file
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "./LoginModal.scss"; // Adjust path based on your actual structure
|
||||
|
||||
// Define the props interface for LoginModal
|
||||
type LoginModalProps = {
|
||||
onLogin: (email: string, password: string) => Promise<boolean>;
|
||||
authMessage: string;
|
||||
clearAuthMessage: () => void;
|
||||
};
|
||||
// LoginModal component
|
||||
export default function LoginModal({
|
||||
onLogin,
|
||||
authMessage,
|
||||
clearAuthMessage,
|
||||
}: LoginModalProps) {
|
||||
const [email, setEmail] = useState("");
|
||||
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(() => {
|
||||
// clearAuthMessage();
|
||||
}, [
|
||||
email,
|
||||
password,
|
||||
// clearAuthMessage
|
||||
]); // Dependency array ensures effect runs when these change
|
||||
|
||||
// Handler for form submission
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault(); // Prevent default form submission behavior
|
||||
setIsLoading(true); // Set loading state to true
|
||||
await onLogin(email, password); // Call the passed onLogin function (now uncommented)
|
||||
setIsLoading(false); // Set loading state back to false after login attempt
|
||||
};
|
||||
return (
|
||||
// The content of the login modal, without the modal overlay/wrapper
|
||||
<form onSubmit={handleSubmit} className="login-form">
|
||||
{/* Email input field */}
|
||||
<div className="login-form__group">
|
||||
<label htmlFor="email" className="login-form__label">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
className="login-form__input"
|
||||
placeholder="admin@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Password input field */}
|
||||
<div className="login-form__group">
|
||||
<label htmlFor="password" className="login-form__label">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className="login-form__input"
|
||||
placeholder="password123"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="login-form__button"
|
||||
disabled={isLoading} // Disable button while loading
|
||||
>
|
||||
{isLoading ? (
|
||||
// SVG spinner for loading state
|
||||
<svg
|
||||
className="login-form__spinner"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
{" "}
|
||||
{/* BEM class name */}
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
) : (
|
||||
"Login" // Button text for normal state
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@ -11,6 +11,7 @@ import { TransactionsOverView } from "../../TransactionsOverview/TransactionsOve
|
||||
export const DashboardHomePage = () => {
|
||||
return (
|
||||
<>
|
||||
{/* Conditional rendering of the Generic Modal, passing LoginModal as children */}
|
||||
<Box sx={{ p: 2 }}>
|
||||
<GeneralHealthCard />
|
||||
</Box>
|
||||
|
||||
64
payment-iq/app/login/page.scss
Normal file
64
payment-iq/app/login/page.scss
Normal file
@ -0,0 +1,64 @@
|
||||
// Variables for consistent styling
|
||||
$primary-color: #2563eb; // Blue-600 equivalent
|
||||
$primary-hover-color: #1d4ed8; // Blue-700 equivalent
|
||||
$success-color: #16a34a; // Green-600 equivalent
|
||||
$error-color: #dc2626; // Red-600 equivalent
|
||||
$text-color-dark: #1f2937; // Gray-800 equivalent
|
||||
$text-color-medium: #4b5563; // Gray-700 equivalent
|
||||
$text-color-light: #6b7280; // Gray-600 equivalent
|
||||
$border-color: #d1d5db; // Gray-300 equivalent
|
||||
$bg-color-light: #f3f4f6; // Gray-100 equivalent
|
||||
$bg-color-white: #ffffff;
|
||||
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: $bg-color-light;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: "Inter", sans-serif; // Assuming Inter font is used
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.page-container__content {
|
||||
width: 100%;
|
||||
max-width: 56rem; // max-w-4xl
|
||||
background-color: $bg-color-white;
|
||||
border-radius: 0.75rem; // rounded-xl
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05); // shadow-lg
|
||||
padding: 2rem; // p-8
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-container__title {
|
||||
font-size: 2.25rem; // text-4xl
|
||||
font-weight: 700; // font-bold
|
||||
color: $text-color-dark;
|
||||
margin-bottom: 1.5rem; // mb-6
|
||||
}
|
||||
|
||||
.page-container__message--logged-in {
|
||||
font-size: 1.25rem; // text-xl
|
||||
color: $success-color;
|
||||
margin-bottom: 1rem; // mb-4
|
||||
}
|
||||
|
||||
.page-container__text {
|
||||
color: $text-color-medium;
|
||||
margin-bottom: 1.5rem; // mb-6
|
||||
}
|
||||
|
||||
.page-container__button--logout {
|
||||
padding: 0.75rem 1.5rem; // px-6 py-3
|
||||
background-color: $error-color;
|
||||
color: $bg-color-white;
|
||||
font-weight: 600; // font-semibold
|
||||
border-radius: 0.5rem; // rounded-lg
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); // shadow-md
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
&:hover {
|
||||
background-color: darken($error-color, 5%); // red-700 equivalent
|
||||
}
|
||||
}
|
||||
103
payment-iq/app/login/page.tsx
Normal file
103
payment-iq/app/login/page.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import LoginModal from "../features/Auth/LoginModal"; // Your LoginModal component
|
||||
|
||||
import "./page.scss"; // Global styles for LoginModal and page
|
||||
import Modal from "../components/Modal/Modal";
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const redirectPath = searchParams.get("redirect") || "/dashboard";
|
||||
|
||||
const [authMessage, setAuthMessage] = useState("");
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if already logged in by trying to fetch a protected resource or checking a client-accessible flag
|
||||
// For HTTP-only cookies, you can't directly read the token here.
|
||||
// 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).
|
||||
// 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();
|
||||
}, []);
|
||||
|
||||
const handleLogin = async (email: string, password: string) => {
|
||||
setAuthMessage("Attempting login...");
|
||||
try {
|
||||
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();
|
||||
|
||||
if (response.ok) {
|
||||
// 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 {
|
||||
// Handle login errors (e.g., invalid credentials)
|
||||
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("");
|
||||
|
||||
// If user is already logged in (e.g., redirected by middleware to dashboard),
|
||||
// this page shouldn't be visible. The middleware should handle the primary redirect.
|
||||
// This `isLoggedIn` state here is more for internal page logic if the user somehow
|
||||
// bypasses middleware or lands on /login with a valid session.
|
||||
// For a robust setup, the middleware is key.
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<div className="page-container__content">
|
||||
<h1 className="page-container__title">Payment Backoffice</h1>
|
||||
<p className="page-container__text">
|
||||
Please log in to access the backoffice.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Always show the modal on the login page */}
|
||||
<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
|
||||
onLogin={handleLogin} // Pass the API call function
|
||||
authMessage={authMessage}
|
||||
clearAuthMessage={clearAuthMessage}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { DashboardHomePage } from "./features/pages/DashboardHomePage/DashboardHomePage";
|
||||
import { DashboardHomePage } from "./features/Pages/DashboardHomePage/DashboardHomePage";
|
||||
|
||||
const DashboardPage = () => {
|
||||
return <DashboardHomePage />;
|
||||
|
||||
30
payment-iq/middleware.ts
Normal file
30
payment-iq/middleware.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// middleware.ts
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const token = request.cookies.get("auth_token")?.value; // Get token from cookie
|
||||
|
||||
// Define protected paths
|
||||
const protectedPaths = ["/dashboard", "/settings", "/admin"];
|
||||
const isProtected = protectedPaths.some((path) =>
|
||||
request.nextUrl.pathname.startsWith(path)
|
||||
);
|
||||
|
||||
// If accessing a protected path and no token
|
||||
if (isProtected && !token) {
|
||||
// Redirect to login page
|
||||
const loginUrl = new URL("/login", request.url);
|
||||
// Optional: Add a redirect query param to return to original page after login
|
||||
loginUrl.searchParams.set("redirect", request.nextUrl.pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
// Allow the request to proceed if not protected or token exists
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Configure matcher to run middleware on specific paths
|
||||
export const config = {
|
||||
matcher: ["/dashboard/:path*", "/settings/:path*", "/admin/:path*"], // Apply to dashboard and its sub-paths
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user