feat: Create login page for admin.
wip: Admin user Configuration
This commit is contained in:
@@ -19,5 +19,18 @@ For database:
|
|||||||
- password : kmobile
|
- password : kmobile
|
||||||
- SELECT * FROM users;
|
- SELECT * FROM users;
|
||||||
- \x
|
- \x
|
||||||
|
- \d -- all tables details
|
||||||
- \d users; -- see the data type of column
|
- \d users; -- see the data type of column
|
||||||
- \c kmobile_banking kmobile_app_rw -- alter the user
|
- \c kmobile_banking kmobile_app_rw -- alter the user
|
||||||
|
- grant select,insert,update,delete on table admin to kmobile_app_rw; -- give read write access to a table
|
||||||
|
|
||||||
|
______________________________________________________________________
|
||||||
|
|
||||||
|
How to connect through linux machine?
|
||||||
|
- run the bat file.
|
||||||
|
- shh username@localhost
|
||||||
|
- give your password
|
||||||
|
|
||||||
|
Copy local machine to linux machine :
|
||||||
|
- scp -P 9022 Smsservice/smsserviceapplication.jar <username>@localhost:/home/<username>
|
||||||
|
- ssh nabanita@localhost -p 9022
|
@@ -11,6 +11,7 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Radio,
|
Radio,
|
||||||
Text,
|
Text,
|
||||||
|
PasswordInput,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
@@ -339,9 +340,9 @@ const AddBeneficiaryOthers: React.FC = () => {
|
|||||||
// disabled={isVisibilityLocked}
|
// disabled={isVisibilityLocked}
|
||||||
readOnly={isVisibilityLocked}
|
readOnly={isVisibilityLocked}
|
||||||
required
|
required
|
||||||
// onCopy={(e) => e.preventDefault()}
|
onCopy={(e) => e.preventDefault()}
|
||||||
// onPaste={(e) => e.preventDefault()}
|
onPaste={(e) => e.preventDefault()}
|
||||||
// onCut={(e) => e.preventDefault()}
|
onCut={(e) => e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{validationStatus === "error" && (
|
{validationStatus === "error" && (
|
||||||
@@ -380,7 +381,7 @@ const AddBeneficiaryOthers: React.FC = () => {
|
|||||||
{otpSent && (
|
{otpSent && (
|
||||||
<>
|
<>
|
||||||
<Grid.Col span={3}>
|
<Grid.Col span={3}>
|
||||||
<TextInput
|
<PasswordInput
|
||||||
label="Enter OTP"
|
label="Enter OTP"
|
||||||
placeholder="Enter OTP"
|
placeholder="Enter OTP"
|
||||||
value={otp}
|
value={otp}
|
||||||
|
@@ -11,6 +11,7 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Radio,
|
Radio,
|
||||||
Text,
|
Text,
|
||||||
|
PasswordInput,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import AddBeneficiaryOthers from './addBeneficiaryOthers';
|
import AddBeneficiaryOthers from './addBeneficiaryOthers';
|
||||||
@@ -253,9 +254,9 @@ const AddBeneficiary: React.FC = () => {
|
|||||||
// disabled={isVisibilityLocked}
|
// disabled={isVisibilityLocked}
|
||||||
readOnly={isVisibilityLocked}
|
readOnly={isVisibilityLocked}
|
||||||
required
|
required
|
||||||
// onCopy={(e) => e.preventDefault()}
|
onCopy={(e) => e.preventDefault()}
|
||||||
// onPaste={(e) => e.preventDefault()}
|
onPaste={(e) => e.preventDefault()}
|
||||||
// onCut={(e) => e.preventDefault()}
|
onCut={(e) => e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{validationStatus === "error" && (
|
{validationStatus === "error" && (
|
||||||
@@ -294,7 +295,7 @@ const AddBeneficiary: React.FC = () => {
|
|||||||
{otpSent && (
|
{otpSent && (
|
||||||
<>
|
<>
|
||||||
<Grid.Col span={3}>
|
<Grid.Col span={3}>
|
||||||
<TextInput
|
<PasswordInput
|
||||||
label="Enter OTP"
|
label="Enter OTP"
|
||||||
placeholder="Enter OTP"
|
placeholder="Enter OTP"
|
||||||
value={otp}
|
value={otp}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Button, Group, Modal, Paper, Radio, ScrollArea, Select, Stack, Text, TextInput, Title } from "@mantine/core";
|
import { Button, Group, Modal, Paper, PasswordInput, Radio, ScrollArea, Select, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { generateOTP } from '@/app/OTPGenerator';
|
import { generateOTP } from '@/app/OTPGenerator';
|
||||||
@@ -361,9 +361,9 @@ export default function QuickPay() {
|
|||||||
setConfirmBeneficiaryAcc(value);
|
setConfirmBeneficiaryAcc(value);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
// onCopy={(e) => e.preventDefault()}
|
onCopy={(e) => e.preventDefault()}
|
||||||
// onPaste={(e) => e.preventDefault()}
|
onPaste={(e) => e.preventDefault()}
|
||||||
// onCut={(e) => e.preventDefault()}
|
onCut={(e) => e.preventDefault()}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
readOnly={isVisibilityLocked}
|
readOnly={isVisibilityLocked}
|
||||||
/>
|
/>
|
||||||
@@ -418,7 +418,7 @@ export default function QuickPay() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
{showOtpField && (
|
{showOtpField && (
|
||||||
<TextInput
|
<PasswordInput
|
||||||
label="OTP"
|
label="OTP"
|
||||||
placeholder="Enter OTP"
|
placeholder="Enter OTP"
|
||||||
type="otp"
|
type="otp"
|
||||||
|
@@ -14,12 +14,44 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||||
const [userLastLoginDetails, setUserLastLoginDetails] = useState(null);
|
const [userLastLoginDetails, setUserLastLoginDetails] = useState(null);
|
||||||
|
const [custname, setCustname] = useState<string | null>(null);
|
||||||
|
|
||||||
async function handleLogout(e: React.FormEvent) {
|
async function handleLogout(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
localStorage.removeItem("access_token");
|
localStorage.removeItem("access_token");
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleFetchUserName() {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem("access_token");
|
||||||
|
const response = await fetch('api/customer', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok && Array.isArray(data)) {
|
||||||
|
if (data.length > 0) {
|
||||||
|
const name = data[0].custname;
|
||||||
|
setCustname(name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Please try again later",
|
||||||
|
message: "Unable to Fetch, Please try again later",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem("access_token");
|
const token = localStorage.getItem("access_token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -28,6 +60,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SetAuthorized(true);
|
SetAuthorized(true);
|
||||||
|
handleFetchUserName();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -44,8 +77,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
else if(response.status ===401 ||data.message === 'invalid or expired token'){
|
else if (response.status === 401 || data.message === 'invalid or expired token') {
|
||||||
// console.log(data);
|
// console.log(data);
|
||||||
localStorage.removeItem("access_token");
|
localStorage.removeItem("access_token");
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
@@ -125,7 +158,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
>
|
>
|
||||||
<Stack gap={0} align="flex-start">
|
<Stack gap={0} align="flex-start">
|
||||||
<Title order={4} style={{ fontFamily: "inter", fontSize: '22px' }}>
|
<Title order={4} style={{ fontFamily: "inter", fontSize: '22px' }}>
|
||||||
Welcome, Rajat Kumar Maharana
|
Welcome, {custname ?? null}
|
||||||
</Title>
|
</Title>
|
||||||
<Text size="xs" c="gray" style={{ fontFamily: "inter", fontSize: '13px' }}>
|
<Text size="xs" c="gray" style={{ fontFamily: "inter", fontSize: '13px' }}>
|
||||||
Last logged in at {userLastLoginDetails ? new Date(userLastLoginDetails).toLocaleString() : "N/A"}
|
Last logged in at {userLastLoginDetails ? new Date(userLastLoginDetails).toLocaleString() : "N/A"}
|
||||||
|
266
src/app/ForgetPassword/page.tsx
Normal file
266
src/app/ForgetPassword/page.tsx
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Text, Button, TextInput, PasswordInput, Title, Card, Box, Image } from "@mantine/core";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { Providers } from "@/app/providers";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import NextImage from "next/image";
|
||||||
|
import logo from '@/app/image/logo.jpg';
|
||||||
|
import changePwdImage from '@/app/image/changepw.png';
|
||||||
|
// import CaptchaImage from './CaptchaImage';
|
||||||
|
import { IconEye, IconEyeOff, IconLock, IconLogout } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
export default function ForgetLoginPwd() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||||
|
const [captcha, setCaptcha] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
const [captchaInput, setCaptchaInput] = useState('');
|
||||||
|
const [captchaError, setCaptchaError] = useState('');
|
||||||
|
const [confirmVisible, setConfirmVisible] = useState(false);
|
||||||
|
const toggleConfirmVisibility = () => setConfirmVisible((v) => !v);
|
||||||
|
const icon = <IconLock size={18} stroke={1.5} />;
|
||||||
|
|
||||||
|
async function handleLogout(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
localStorage.removeItem("access_token");
|
||||||
|
router.push("/login")
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
generateCaptcha();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const generateCaptcha = () => {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
setCaptcha(result);
|
||||||
|
setCaptchaInput('');
|
||||||
|
setCaptchaError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleSetLoginPassword(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
const pwdRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
|
||||||
|
if (!password || !confirmPassword) {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Both password fields are required.",
|
||||||
|
message: "Both password fields are required.",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
// alert("Both password fields are required.");
|
||||||
|
} else if (password !== confirmPassword) {
|
||||||
|
// alert("Passwords do not match.");
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Passwords do not match.",
|
||||||
|
message: "Passwords do not match.",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!pwdRegex.test(password)) {
|
||||||
|
// alert("Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.");
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.",
|
||||||
|
message: "Password must contain at least 1 capital letter, 1 number, 1 special character, and be at least 8 characters long.",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (captchaInput !== captcha) {
|
||||||
|
setCaptchaError("Incorrect CAPTCHA.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const token = localStorage.getItem("access_token");
|
||||||
|
const response = await fetch('api/auth/login_password', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
login_password: password,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
console.log(data);
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "green",
|
||||||
|
title: "Login Password has been set",
|
||||||
|
message: "Login Password has been set",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
router.push("/SetTxn");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Please try again later ",
|
||||||
|
message: "Please try again later ",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem("access_token");
|
||||||
|
if (!token) {
|
||||||
|
SetAuthorized(false);
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SetAuthorized(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (authorized) {
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "auto", paddingTop: "5%" }}>
|
||||||
|
<Box style={{
|
||||||
|
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)"
|
||||||
|
}}>
|
||||||
|
<Image
|
||||||
|
// radius="md"
|
||||||
|
fit="cover"
|
||||||
|
src={logo}
|
||||||
|
component={NextImage}
|
||||||
|
alt="ebanking"
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
/>
|
||||||
|
<Button style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '90%',
|
||||||
|
color: 'white',
|
||||||
|
textShadow: '1px 1px 2px black',
|
||||||
|
fontSize: "20px"
|
||||||
|
}}
|
||||||
|
leftSection={<IconLogout color='white' />} variant="subtle" onClick={handleLogout}>Logout</Button>
|
||||||
|
</Box>
|
||||||
|
<div>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "center", alignItems: "center", columnGap: "5rem" }} bg="#c1e0f0">
|
||||||
|
<Image h="85vh" fit="contain" component={NextImage} src={changePwdImage} alt="Change Password Image" />
|
||||||
|
<Box h="100%" style={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
|
||||||
|
<Card p="xl" w="40vw" h='82vh'>
|
||||||
|
<Title order={3}
|
||||||
|
// @ts-ignore
|
||||||
|
align="center" mb="md">Set Login Password</Title>
|
||||||
|
|
||||||
|
<form onSubmit={handleSetLoginPassword}>
|
||||||
|
<PasswordInput
|
||||||
|
label="Login Password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
id="loginPassword"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
onCopy={(e) => e.preventDefault()}
|
||||||
|
onPaste={(e) => e.preventDefault()}
|
||||||
|
onCut={(e) => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PasswordInput
|
||||||
|
label="Confirm Login Password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
rightSection={icon}
|
||||||
|
id="confirmPassword"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.currentTarget.value)}
|
||||||
|
type={confirmVisible ? 'text' : 'password'}
|
||||||
|
// rightSection={
|
||||||
|
// <button
|
||||||
|
// type="button"
|
||||||
|
// onClick={toggleConfirmVisibility}
|
||||||
|
// style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'grey' }}
|
||||||
|
// >
|
||||||
|
// {confirmVisible ? <IconEyeOff size={18} /> : <IconEye size={18} />}
|
||||||
|
// </button>
|
||||||
|
// }
|
||||||
|
onCopy={(e) => e.preventDefault()}
|
||||||
|
onPaste={(e) => e.preventDefault()}
|
||||||
|
onCut={(e) => e.preventDefault()}
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* CAPTCHA */}
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<label style={{ fontWeight: 600 }}>Enter CAPTCHA *</label>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 5 }}>
|
||||||
|
{/* <CaptchaImage text={captcha} /> */}
|
||||||
|
<Button size="xs" variant="outline" onClick={generateCaptcha}>Refresh</Button>
|
||||||
|
</div>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Enter above text"
|
||||||
|
value={captchaInput}
|
||||||
|
onChange={(e) => setCaptchaInput(e.currentTarget.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{captchaError && <p style={{ color: 'red',fontSize:'12px' }}>{captchaError}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
mt="sm"
|
||||||
|
color="blue"
|
||||||
|
>
|
||||||
|
Set
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
<br></br>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
borderLeft: '1px solid #ccc',
|
||||||
|
paddingLeft: 16,
|
||||||
|
minHeight: 90,
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="sm">
|
||||||
|
<strong>Note:</strong> Password will contains minimum one alphabet, one digit, one special symbol and total 8 charecters.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
flexShrink: 0,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginTop: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text c="dimmed" size="xs">
|
||||||
|
© 2025 Kangra Central Co-Operative Bank
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Providers >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
243
src/app/administrator/home/UserConfiguration.tsx
Normal file
243
src/app/administrator/home/UserConfiguration.tsx
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { TextInput, Button, Title, Stack, Radio, Group, Text, Divider, LoadingOverlay, Box, Modal, Card, Checkbox } from '@mantine/core';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
|
export default function UserConfiguration() {
|
||||||
|
const [CIF, setCIF] = useState('');
|
||||||
|
const [userDetails, setUserDetails] = useState<any>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [savingsAccount, setSavingsAccount] = useState<string | null>(null);
|
||||||
|
const [accountError, setAccountError] = useState<string>('');
|
||||||
|
const [detailsExpanded, setDetailsExpanded] = useState(true);
|
||||||
|
const [showPreviewModal, setShowPreviewModal] = useState(false);
|
||||||
|
const [confirmedPreview, setConfirmedPreview] = useState(false);
|
||||||
|
const [ibEnabled, setIbEnabled] = useState(false);
|
||||||
|
const [ibAccess,setIbAccess] =useState<"read"|"transaction"|"">("");
|
||||||
|
const [mbEnabled, setMbEnabled] = useState(false);
|
||||||
|
const [mbAccess,setMbAccess] =useState<"read"|"transaction"|"">("");
|
||||||
|
const isValidated = !!userDetails;
|
||||||
|
const canSubmit = isValidated && !!savingsAccount && confirmedPreview;
|
||||||
|
|
||||||
|
const handleValidate = async () => {
|
||||||
|
if (!CIF) {
|
||||||
|
notifications.show({
|
||||||
|
color: 'red',
|
||||||
|
title: 'CIF Missing',
|
||||||
|
message: 'Please enter a CIF number to proceed.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
setAccountError('');
|
||||||
|
setSavingsAccount(null);
|
||||||
|
setUserDetails(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem("admin_access_token");
|
||||||
|
const response = await fetch(`/api/auth/admin/fetch/customer_details?CIF=${CIF}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok && Array.isArray(data) && data.length > 0) {
|
||||||
|
const saAccount = data.find(acc => acc.stAccountType === 'SA');
|
||||||
|
console.log(saAccount);
|
||||||
|
if (saAccount) {
|
||||||
|
setSavingsAccount(saAccount.stAccountNo);
|
||||||
|
} else {
|
||||||
|
setAccountError('At least one savings account is required for requesting.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = data[0];
|
||||||
|
setUserDetails({
|
||||||
|
name: user.custname,
|
||||||
|
userId: user.id,
|
||||||
|
mobile: user.mobileno,
|
||||||
|
address: user.custaddress,
|
||||||
|
activeAccount: user.activeAccounts,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('User not found or data format incorrect');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
notifications.show({
|
||||||
|
color: 'red',
|
||||||
|
title: 'Validation Failed',
|
||||||
|
message: 'User not found',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!canSubmit) {
|
||||||
|
notifications.show({
|
||||||
|
color: 'red',
|
||||||
|
title: 'Required',
|
||||||
|
message: 'One savings account is required for enable rights',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const rightsData = {
|
||||||
|
CIF,
|
||||||
|
// internetBanking,
|
||||||
|
// mobileBanking,
|
||||||
|
};
|
||||||
|
console.log('Submitting rights:', rightsData);
|
||||||
|
notifications.show({
|
||||||
|
color: 'blue',
|
||||||
|
title: 'Submitted',
|
||||||
|
message: `User -${CIF} rights submitted successfully.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSavingsAccount(null);
|
||||||
|
setUserDetails(null);
|
||||||
|
setCIF('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMainAction = () => {
|
||||||
|
if (!isValidated) {
|
||||||
|
handleValidate();
|
||||||
|
} else if (!confirmedPreview) {
|
||||||
|
setShowPreviewModal(true);
|
||||||
|
} else if (isValidated && confirmedPreview) {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} />
|
||||||
|
<Title order={4}>User Configuration</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Enter CIF No"
|
||||||
|
placeholder="Enter your CIF No"
|
||||||
|
value={CIF}
|
||||||
|
onChange={(e) => {
|
||||||
|
const input = e.currentTarget.value.replace(/\D/g, '');
|
||||||
|
if (input.length <= 11) setCIF(input);
|
||||||
|
}}
|
||||||
|
disabled={isValidated}
|
||||||
|
withAsterisk />
|
||||||
|
|
||||||
|
{isValidated && (
|
||||||
|
<>
|
||||||
|
<Divider size="xs" label="User Info" />
|
||||||
|
<Box style={{ overflowX: 'auto', border: '1px solid #eee', borderRadius: 8, padding: 12 }}>
|
||||||
|
<Group p="apart">
|
||||||
|
<Text fw={500}>Preview User Details</Text>
|
||||||
|
<Button variant="subtle" size="xs" onClick={() => setDetailsExpanded((v) => !v)}>
|
||||||
|
{detailsExpanded ? '▲' : '▼'}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{detailsExpanded && (
|
||||||
|
<>
|
||||||
|
<Group grow>
|
||||||
|
<TextInput label="User ID" value={userDetails.userId} readOnly disabled />
|
||||||
|
<TextInput label="Name" value={userDetails.name} readOnly disabled />
|
||||||
|
<TextInput label="Mobile" value={userDetails.mobile} readOnly disabled />
|
||||||
|
</Group>
|
||||||
|
<Group grow>
|
||||||
|
<TextInput label="Address" value={userDetails.address} readOnly disabled />
|
||||||
|
<TextInput label="Savings Account" value={savingsAccount || ''} readOnly disabled />
|
||||||
|
<TextInput label="Active Accounts" value={userDetails.activeAccount} readOnly disabled />
|
||||||
|
</Group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider label='Rights' size="xs" />
|
||||||
|
<Stack>
|
||||||
|
<Group grow>
|
||||||
|
<Box>
|
||||||
|
<Text fw={500}>Internet Banking</Text>
|
||||||
|
<Group mt="xs">
|
||||||
|
<Checkbox
|
||||||
|
label="Enabled"
|
||||||
|
checked={ibEnabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
setIbEnabled(e.currentTarget.checked);
|
||||||
|
if (!e.currentTarget.checked) setIbAccess(""); // reset if disabled
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="Read"
|
||||||
|
disabled={!ibEnabled}
|
||||||
|
checked={ibAccess === "read"}
|
||||||
|
onChange={() => setIbAccess("read")}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="Transaction"
|
||||||
|
disabled={!ibEnabled}
|
||||||
|
checked={ibAccess === "transaction"}
|
||||||
|
onChange={() => setIbAccess("transaction")}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Mobile Banking */}
|
||||||
|
<Box>
|
||||||
|
<Text fw={500}>Mobile Banking</Text>
|
||||||
|
<Group mt="xs">
|
||||||
|
<Checkbox
|
||||||
|
label="Enabled"
|
||||||
|
checked={mbEnabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
setMbEnabled(e.currentTarget.checked);
|
||||||
|
if (!e.currentTarget.checked) setMbAccess(""); // reset if disabled
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="Read"
|
||||||
|
disabled={!mbEnabled}
|
||||||
|
checked={mbAccess === "read"}
|
||||||
|
onChange={() => setMbAccess("read")}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="Transaction"
|
||||||
|
disabled={!mbEnabled}
|
||||||
|
checked={mbAccess === "transaction"}
|
||||||
|
onChange={() => setMbAccess("transaction")}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
{accountError && (
|
||||||
|
<Text color="red" size="sm" mt="xs">{accountError}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button mt="lg" onClick={handleMainAction} disabled={loading}>
|
||||||
|
{!isValidated ? 'Validate' : !confirmedPreview ? 'Preview' : 'Submit'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
opened={showPreviewModal}
|
||||||
|
onClose={() => setShowPreviewModal(false)}
|
||||||
|
title="Confirm User Rights"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<TextInput label="CIF" value={CIF} readOnly disabled />
|
||||||
|
{/* <TextInput label="Internet Banking" value={internetBanking} readOnly disabled />
|
||||||
|
<TextInput label="Mobile Banking" value={mobileBanking} readOnly disabled /> */}
|
||||||
|
</Stack>
|
||||||
|
<Group p="right" mt="md">
|
||||||
|
<Button variant="default" onClick={() => setShowPreviewModal(false)}>Cancel</Button>
|
||||||
|
<Button onClick={() => { setConfirmedPreview(true); setShowPreviewModal(false); }}>OK</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
162
src/app/administrator/home/page.tsx
Normal file
162
src/app/administrator/home/page.tsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Text, Box, Image, Button, Stack, Divider, Title } from "@mantine/core";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { Providers } from "@/app/providers";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import NextImage from "next/image";
|
||||||
|
import logo from '@/app/image/logo.jpg';
|
||||||
|
import { IconLogout, IconUsers, IconUserScreen } from "@tabler/icons-react";
|
||||||
|
import UserConfiguration from "./UserConfiguration";
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||||
|
const [view, setView] = useState<'userConf' | 'view' | null>(null);
|
||||||
|
const [lastLoginDetails, setLastLoginDetails] = useState(null);
|
||||||
|
const [name, setName] = useState(null);
|
||||||
|
|
||||||
|
async function handleLogout(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
localStorage.removeItem("admin_access_token");
|
||||||
|
router.push("/administrator/login");
|
||||||
|
}
|
||||||
|
async function handleFetchUserDetails(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
const token = localStorage.getItem("admin_access_token");
|
||||||
|
const response = await fetch('/api/auth/admin/admin_details', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
// console.log(data)
|
||||||
|
if (response.ok) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
else if (response.status === 401 || data.message === 'invalid or expired token') {
|
||||||
|
localStorage.removeItem("admin_access_token");
|
||||||
|
router.push('/administrator/login');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Please try again later",
|
||||||
|
message: "Unable to fetch timestamp, please try again later",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem("admin_access_token");
|
||||||
|
if (!token) {
|
||||||
|
SetAuthorized(false);
|
||||||
|
router.push("/administrator/login/");
|
||||||
|
} else {
|
||||||
|
SetAuthorized(true);
|
||||||
|
const fetchLoginTime = async () => {
|
||||||
|
const result = await handleFetchUserDetails({ preventDefault: () => { } } as React.FormEvent);
|
||||||
|
if (result) {
|
||||||
|
setLastLoginDetails(result.last_login);
|
||||||
|
setName(result.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchLoginTime();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClick = (type: 'UserConf' | 'ViewUser' | 'logout') => {
|
||||||
|
if (type === 'UserConf') {
|
||||||
|
setView('userConf');
|
||||||
|
} else if (type === 'ViewUser') {
|
||||||
|
setView('view');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!authorized) return null;
|
||||||
|
|
||||||
|
if (authorized) {
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "100%", paddingTop: "5%" }}>
|
||||||
|
{/* Header */}
|
||||||
|
<Box style={{
|
||||||
|
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||||
|
// border: "1px solid black"
|
||||||
|
}}>
|
||||||
|
<Image
|
||||||
|
fit="cover"
|
||||||
|
src={logo}
|
||||||
|
component={NextImage}
|
||||||
|
alt="ebanking"
|
||||||
|
style={{ width: "40%", height: "100%", objectFit: "contain", marginLeft: 0 }}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '64%',
|
||||||
|
color: 'white',
|
||||||
|
textShadow: '1px 1px 2px blue',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <IconBuildingBank/> */}
|
||||||
|
Head Office : Dharmshala, District: Kangra(H.P), Pincode: 176215
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
{/* layout and */}
|
||||||
|
<Box style={{ display: 'flex', height: '88vh' }}>
|
||||||
|
{/* Sidebar manually placed under header */}
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
width: 220,
|
||||||
|
background:"#ebf5f5ff",
|
||||||
|
// background: "linear-gradient(90deg,rgba(42, 123, 155, 1) 0%, rgba(87, 199, 133, 1) 30%)",
|
||||||
|
padding: '1rem',
|
||||||
|
borderRight: '1px solid #ccc',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Title order={3} style={{textAlign:'center'}}>Admin Portal</Title>
|
||||||
|
<Divider my="xs" label="Menu" labelPosition="center" />
|
||||||
|
<Stack>
|
||||||
|
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={() => handleClick('UserConf')}>
|
||||||
|
<IconUsers /> User Configuration
|
||||||
|
</Text>
|
||||||
|
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={() => handleClick('ViewUser')}>
|
||||||
|
<IconUserScreen /> View Users
|
||||||
|
</Text>
|
||||||
|
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={handleLogout}>
|
||||||
|
<IconLogout /> Logout
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Main Content Area */}
|
||||||
|
<Stack style={{ flex: 1, padding: '1rem' }}>
|
||||||
|
<Box>
|
||||||
|
<Text c="Blue" size="lg" fs='italic'>Welcome ,{name} </Text>
|
||||||
|
<Text size="xs" c="gray" style={{ fontFamily: "inter", fontSize: '13px' }}>
|
||||||
|
Last logged in at {lastLoginDetails ? new Date(lastLoginDetails).toLocaleString() : "N/A"}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
{view === 'userConf' && <UserConfiguration />}
|
||||||
|
{view === 'view' && <Text size="xl">view</Text>}
|
||||||
|
{!view &&
|
||||||
|
<Text size="xl">Welcome To The Admin Portal</Text>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
207
src/app/administrator/login/page.tsx
Normal file
207
src/app/administrator/login/page.tsx
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Text, Button, TextInput, PasswordInput, Title, Card, Group, Box, Image } from "@mantine/core";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { Providers } from "@/app/providers";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import NextImage from "next/image";
|
||||||
|
import logo from '@/app/image/logo.jpg';
|
||||||
|
import frontPage from '@/app/image/admin_login.jpg';
|
||||||
|
import { generateCaptcha } from '@/app/captcha';
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [userName, setUserName] = useState("");
|
||||||
|
const [psw, SetPsw] = useState("");
|
||||||
|
const [captcha, setCaptcha] = useState("");
|
||||||
|
const [inputCaptcha, setInputCaptcha] = useState("");
|
||||||
|
const [isLogging, setIsLogging] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCaptcha = async () => {
|
||||||
|
const newCaptcha = await generateCaptcha();
|
||||||
|
setCaptcha(newCaptcha);
|
||||||
|
};
|
||||||
|
loadCaptcha();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const regenerateCaptcha = () => {
|
||||||
|
// setCaptcha(generateCaptcha());
|
||||||
|
const loadCaptcha = async () => {
|
||||||
|
const newCaptcha = await generateCaptcha();
|
||||||
|
setCaptcha(newCaptcha);
|
||||||
|
};
|
||||||
|
loadCaptcha();
|
||||||
|
setInputCaptcha("");
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleLogin(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!userName || !psw || !captcha) {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Field Blank",
|
||||||
|
message: "Please enter all mandatory details",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (inputCaptcha !== captcha) {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Captcha Error",
|
||||||
|
message: "Please enter the correct captcha",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
regenerateCaptcha();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await fetch('/api/auth/admin/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
userName: userName,
|
||||||
|
password: psw,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
setIsLogging(true);
|
||||||
|
if (response.ok) {
|
||||||
|
console.log(data);
|
||||||
|
const token = data.token;
|
||||||
|
localStorage.setItem("admin_access_token", token);
|
||||||
|
router.push("/administrator/home");
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setIsLogging(false);
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Wrong User Id or Password",
|
||||||
|
message: "Wrong User Id or Password",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "100%", paddingTop: "5%" }}>
|
||||||
|
{/* Header */}
|
||||||
|
<Box style={{
|
||||||
|
position: 'fixed', width: '100%', height: '12%', top: 0, left: 0, zIndex: 100,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
background: "linear-gradient(15deg,rgba(2, 163, 85, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||||
|
// border: "1px solid black"
|
||||||
|
}}>
|
||||||
|
<Image
|
||||||
|
fit="cover"
|
||||||
|
src={logo}
|
||||||
|
component={NextImage}
|
||||||
|
alt="ebanking"
|
||||||
|
style={{ width: "40%", height: "100%", objectFit: "contain", marginLeft: 0 }}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '64%',
|
||||||
|
color: 'white',
|
||||||
|
textShadow: '1px 1px 2px blue',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <IconBuildingBank/> */}
|
||||||
|
Head Office : Dharmshala, District: Kangra(H.P), Pincode: 176215
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<div>
|
||||||
|
{/* Main */}
|
||||||
|
<div style={{
|
||||||
|
display: "flex", height: "84vh", overflow: "hidden", position: "relative",
|
||||||
|
background: 'linear-gradient(to right, #48ac64ff, #199444ff)'
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: 1, backgroundColor: "#c1e0f0", position: "relative" }}>
|
||||||
|
<Image
|
||||||
|
fit="cover"
|
||||||
|
src={frontPage}
|
||||||
|
component={NextImage}
|
||||||
|
alt="ebanking"
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Box w={{ base: "100%", md: "50%" }} p="lg">
|
||||||
|
<Card shadow="md" padding="xl" radius="md" style={{ maxWidth: 500, margin: "0 auto", height: '70vh', backdropFilter: 'blur(8px)' }}>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Title order={3} align='center'>Admin Portal</Title>
|
||||||
|
<form onSubmit={handleLogin}>
|
||||||
|
<TextInput
|
||||||
|
label="User Name"
|
||||||
|
placeholder="Enter your user name"
|
||||||
|
value={userName}
|
||||||
|
onChange={(e) => setUserName(e.currentTarget.value)}
|
||||||
|
error={error}
|
||||||
|
withAsterisk
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
label="Password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={psw}
|
||||||
|
onChange={(e) => SetPsw(e.currentTarget.value)}
|
||||||
|
withAsterisk
|
||||||
|
mt="sm"
|
||||||
|
/>
|
||||||
|
{/* <Box style={{ textAlign: "right"}}>
|
||||||
|
<Anchor
|
||||||
|
style={{ fontSize: "14px", color: "#1c7ed6", cursor: "pointer" }}
|
||||||
|
// onClick={() => router.push("/ValidateUser")}
|
||||||
|
>
|
||||||
|
Forgot Password?
|
||||||
|
</Anchor>
|
||||||
|
</Box> */}
|
||||||
|
<Group mt="sm" align="center">
|
||||||
|
<Box style={{ backgroundColor: "#fff", fontSize: "18px", textDecoration: "line-through", padding: "4px 8px", fontFamily: "cursive" }}>{captcha}</Box>
|
||||||
|
<Button size="xs" variant="light" onClick={regenerateCaptcha}>Refresh</Button>
|
||||||
|
|
||||||
|
</Group>
|
||||||
|
<TextInput
|
||||||
|
label="Enter CAPTCHA"
|
||||||
|
placeholder="Enter above text"
|
||||||
|
value={inputCaptcha}
|
||||||
|
onChange={(e) => setInputCaptcha(e.currentTarget.value)}
|
||||||
|
withAsterisk
|
||||||
|
mt="sm"
|
||||||
|
/>
|
||||||
|
<Button type="submit" fullWidth mt="md" disabled={isLogging}>
|
||||||
|
{isLogging ? "Logging..." : "Login"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<Box
|
||||||
|
component="footer"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text c='dimmed'>
|
||||||
|
© 2025 KCC Bank. All rights reserved.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
BIN
src/app/image/admin_login.jpg
Normal file
BIN
src/app/image/admin_login.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
BIN
src/app/image/ib_admin_front_page.jpg
Normal file
BIN
src/app/image/ib_admin_front_page.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
@@ -52,6 +52,7 @@ export default function Login() {
|
|||||||
message: "Please enter the correct captcha",
|
message: "Please enter the correct captcha",
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
});
|
});
|
||||||
|
regenerateCaptcha();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const response = await fetch('api/auth/login', {
|
const response = await fetch('api/auth/login', {
|
||||||
@@ -198,7 +199,7 @@ export default function Login() {
|
|||||||
<Box style={{ textAlign: "right"}}>
|
<Box style={{ textAlign: "right"}}>
|
||||||
<Anchor
|
<Anchor
|
||||||
style={{ fontSize: "14px", color: "#1c7ed6", cursor: "pointer" }}
|
style={{ fontSize: "14px", color: "#1c7ed6", cursor: "pointer" }}
|
||||||
onClick={() => router.push("/validate_user")}
|
onClick={() => router.push("/ValidateUser")}
|
||||||
>
|
>
|
||||||
Forgot Password?
|
Forgot Password?
|
||||||
</Anchor>
|
</Anchor>
|
||||||
|
Reference in New Issue
Block a user