feat : screen for user create and update

feat : unlocked users.
This commit is contained in:
2025-11-04 13:05:00 +05:30
parent 96b505a38c
commit 26b9eff409
6 changed files with 982 additions and 404 deletions

View File

@@ -14,8 +14,8 @@ interface SendOtpPayload {
}
function getStoredMobileNumber(): string {
const mobileNumber = localStorage.getItem('remitter_mobile_no');
// const mobileNumber = "7890544527";
// const mobileNumber = localStorage.getItem('remitter_mobile_no');
const mobileNumber = "7890544527";
if (!mobileNumber) throw new Error('Mobile number not found.');
return mobileNumber;
}

View File

@@ -0,0 +1,265 @@
"use client";
import React, { useState, useEffect } from "react";
import {
TextInput,
Button,
Title,
Stack,
Group,
Text,
Paper,
LoadingOverlay,
Divider,
Select,
Center,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { useRouter } from "next/navigation";
export default function UserUnlockPage() {
const router = useRouter();
const [CIF, setCIF] = useState("");
const [username, setUsername] = useState("");
const [userDetails, setUserDetails] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Redirect if not logged in
useEffect(() => {
const token = localStorage.getItem("admin_access_token");
if (!token) {
localStorage.clear();
sessionStorage.clear();
router.push("/administrator/login");
}
}, [router]);
const checkAuth = () => {
const token = localStorage.getItem("admin_access_token");
if (!token) {
localStorage.clear();
sessionStorage.clear();
router.push("/administrator/login");
return null;
}
return token;
};
// Fetch user info
const handleSearch = async () => {
setError(null);
setUserDetails(null);
if (!CIF && !username) {
notifications.show({
color: "red",
title: "Missing Input",
message: "Please enter either CIF number or username.",
});
return;
}
setLoading(true);
const token = checkAuth();
if (!token) return;
try {
const query = CIF ? CIF : username;
const response = await fetch(`/api/auth/admin/user/rights?CIF=${query}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (!response.ok || !data?.customer_no) {
setError("User not found or invalid CIF/username.");
notifications.show({
color: "red",
title: "User Not Found",
message: "Invalid CIF or username.",
});
return;
}
setUserDetails(data);
notifications.show({
color: data.locked ? "red" : "green",
title: "User Found",
message: `User ${data.customer_no} is currently ${data.locked ? "Locked 🔒" : "Unlocked 🔓"
}`,
});
} catch (err) {
console.error(err);
notifications.show({
color: "red",
title: "Error",
message: "Failed to fetch user details.",
});
} finally {
setLoading(false);
}
};
// 🔄 Unlock or lock user
const handleStatusChange = async (value: string) => {
if (!userDetails?.customer_no) return;
const isUnlock = value === "unlocked";
const token = checkAuth();
if (!token) return;
setLoading(true);
try {
const response = await fetch("/api/auth/admin/user/unlock", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
user: userDetails.customer_no, // can also be username if API supports
action: !isUnlock, // false => unlock, true => lock
}),
});
const data = await response.json();
if (response.ok) {
setUserDetails({ ...userDetails, locked: !isUnlock });
notifications.show({
color: "green",
title: "Success",
message: `User ${userDetails.customer_no} ${isUnlock ? "unlocked" : "locked"
} successfully.`,
});
} else {
notifications.show({
color: "red",
title: "Failed",
message: data.message || "Unable to update lock status.",
});
}
} catch (err) {
console.error(err);
notifications.show({
color: "red",
title: "Error",
message: "Something went wrong while updating user status.",
});
} finally {
setLoading(false);
}
};
const handleReset = () => {
setCIF("");
setUsername("");
setUserDetails(null);
setError(null);
};
return (
<>
<Title order={4}>User Unlock Management</Title>
<Paper p="md" mt="md" radius="md" withBorder>
<LoadingOverlay visible={loading} zIndex={1000} />
<Group grow>
<TextInput
label="CIF Number"
placeholder="Enter CIF Number"
value={CIF}
onChange={(e) => {
const input = e.currentTarget.value.replace(/\D/g, "");
if (input.length <= 11) setCIF(input);
setError(null);
}}
disabled={!!username}
/>
<Text c="dimmed" ta="center">
OR
</Text>
<TextInput
label="Username"
placeholder="Enter Username"
value={username}
onChange={(e) => {
const sanitized = e.currentTarget.value.replace(/[^a-zA-Z0-9@_]/g, "");
if (sanitized.length <= 15) setUsername(sanitized);
setError(null);
}}
disabled={!!CIF}
/>
</Group>
{error && (
<Center mt="xs">
<Text c="red" size="sm" fw={500}>
{error}
</Text>
</Center>
)}
<Group mt="md">
<Button onClick={handleSearch} disabled={loading}>
Search
</Button>
<Button variant="light" color="red" onClick={handleReset}>
Reset
</Button>
</Group>
</Paper>
{userDetails && (
<Paper withBorder p="md" mt="xl" radius="md">
<Stack gap="xs">
<Divider label="User Details" labelPosition="center" />
<Text>
<strong>Customer No:</strong> {userDetails.customer_no}
</Text>
<Text>
<strong>Registration Status:</strong>{" "}
{userDetails.last_login === null ? (
<Text span c="red" fw={600}>
Not Registered
</Text>
) : (
<Text span c="green" fw={600}>
Registered
</Text>
)}
</Text>
<Select
label="Locked Status"
value={userDetails.locked ? "locked" : "unlocked"}
data={[
{ value: "locked", label: "Locked 🔒" },
{ value: "unlocked", label: "Unlocked 🔓" },
]}
onChange={(value) => {
if (!value) return;
handleStatusChange(value);
}}
mt="md"
disabled={!userDetails.locked} // only allow change if locked
/>
</Stack>
</Paper>
)}
</>
);
}

View File

@@ -15,10 +15,11 @@ import {
Paper,
Tabs,
ScrollArea,
Center,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { useRouter } from "next/navigation";
import { IconGavel, IconUser } from "@tabler/icons-react";
import { IconGavel, IconHome, IconUser } from "@tabler/icons-react";
export default function UserConfiguration() {
const router = useRouter();
@@ -32,11 +33,15 @@ export default function UserConfiguration() {
const [ibAccess, setIbAccess] = useState<"2" | "1" | null>(null);
const [mbEnabled, setMbEnabled] = useState(false);
const [mbAccess, setMbAccess] = useState<"2" | "1" | null>(null);
const [ibLimit, setIbLimit] = useState("");
const [mbLimit, setMbLimit] = useState("");
const [error, setError] = useState<string | null>(null);
const isValidated = !!userDetails;
const canSubmit =
isValidated && savingsAccount && userDetails?.mobile && confirmedPreview;
// 🔹 Fetch both user details and rights
const handleValidate = async () => {
if (!CIF) {
notifications.show({
@@ -60,10 +65,14 @@ export default function UserConfiguration() {
},
}
);
const data = await response.json();
if (response.ok && Array.isArray(data) && data.length > 0) {
const saAccount = data.find((acc) => acc.stAccountType === "SA");
if (!response.ok || !Array.isArray(data) || data.length === 0) {
setError("User not present or invalid CIF number.");
}
const saAccount = data.find(
(acc: { stAccountType: string; }) => acc.stAccountType === "SA" || acc.stAccountType === "SB"
);
setSavingsAccount(saAccount?.stAccountNo ?? null);
const user = data[0];
@@ -73,30 +82,52 @@ export default function UserConfiguration() {
mobile: user.mobileno ?? null,
address: user.custaddress,
activeAccount: user.activeAccounts,
branchNo: user.stBranchNo,
dateOfBirth: user.custdob,
pinCode: user.pincode,
});
} else {
notifications.show({
color: "red",
title: "Validation Failed",
message: "User not found",
});
// Fetch existing rights if any
const rightsRes = await fetch(
`/api/auth/admin/user/rights?CIF=${CIF}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
Authorization: `Bearer ${token}`,
},
}
} catch (err) {
);
if (rightsRes.ok) {
const rights = await rightsRes.json();
if (rights?.ib_access_level || rights?.mb_access_level) {
// populate existing rights
if (rights.ib_access_level) {
setIbEnabled(true);
setIbAccess(rights.ib_access_level as "1" | "2");
setIbLimit(rights.inb_limit_amount?.toString() || "");
}
if (rights.mb_access_level) {
setMbEnabled(true);
setMbAccess(rights.mb_access_level as "1" | "2");
setMbLimit(rights.mobile_limit_amount?.toString() || "");
}
}
}
}
catch (err) {
console.error(err);
notifications.show({
color: "red",
title: "Validation Failed",
message: "User not found",
});
setError("User not present or invalid CIF number.");
} finally {
setLoading(false);
}
};
// Preview before submit
const handlePreview = () => {
if (!savingsAccount || !userDetails?.mobile) {
return;
}
if (!savingsAccount || !userDetails?.mobile) return;
if (!ibAccess && !mbAccess) {
notifications.show({
title: "Rights Required",
@@ -106,12 +137,20 @@ export default function UserConfiguration() {
});
return;
}
if ((ibEnabled && !ibLimit) || (mbEnabled && !mbLimit)) {
notifications.show({
title: "Limit Required",
message: "Please enter fund transfer limits for enabled services.",
color: "red",
});
return;
}
setShowPreviewModal(true);
};
// Submit or update rights
const handleSubmit = async () => {
if (!canSubmit) return;
try {
const token = localStorage.getItem("admin_access_token");
const response = await fetch("/api/auth/admin/user/rights", {
@@ -125,52 +164,58 @@ export default function UserConfiguration() {
CIF,
ib_access_level: ibAccess,
mb_access_level: mbAccess,
ib_limit: ibLimit ? parseInt(ibLimit) : 0,
mb_limit: mbLimit ? parseInt(mbLimit) : 0,
}),
});
const data = await response.json();
if (response.ok) {
if (data?.otp) {
// Registration case
await fetch('/api/otp/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json',"X-Login-Type": "Admin", },
await fetch("/api/otp/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
},
body: JSON.stringify({
mobileNumber: userDetails.mobile,
type: 'REGISTRATION',
type: "REGISTRATION",
userOtp: data.otp,
}),
});
notifications.show({
color: 'blue',
title: 'Submitted',
color: "blue",
title: "Submitted",
message: `User ${CIF} rights submitted successfully. OTP sent to the user.`,
});
}
else if (data?.message) {
// Rights update case
await fetch('/api/otp/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json',"X-Login-Type": "Admin", },
} else if (data?.message) {
await fetch("/api/otp/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
},
body: JSON.stringify({
mobileNumber: userDetails.mobile,
type: 'RIGHT_UPDATE',
type: "RIGHT_UPDATE",
}),
});
notifications.show({
color: 'blue',
title: 'Submitted',
color: "blue",
title: "Updated",
message: `User ${CIF} rights updated successfully. Confirmation sent to the user.`,
});
}
}
else if (response.status === 401 || data.message === "invalid or expired token") {
} else if (
response.status === 401 ||
data.message === "invalid or expired token"
) {
localStorage.clear();
sessionStorage.clear();
router.push("/administrator/login");
}
}
catch (err) {
} catch (err) {
console.error(err);
notifications.show({
color: "red",
@@ -190,20 +235,19 @@ export default function UserConfiguration() {
setConfirmedPreview(false);
setIbEnabled(false);
setIbAccess(null);
setIbLimit("");
setMbEnabled(false);
setMbAccess(null);
setMbLimit("");
};
const handleMainAction = () => {
if (!isValidated) {
handleValidate();
} else if (!confirmedPreview) {
handlePreview();
} else {
handleSubmit();
}
if (!isValidated) handleValidate();
else if (!confirmedPreview) handlePreview();
else handleSubmit();
};
return (
<>
<Title order={4}>User Configuration</Title>
@@ -223,16 +267,26 @@ export default function UserConfiguration() {
value={CIF}
onChange={(e) => {
const input = e.currentTarget.value.replace(/\D/g, "");
if (input.length <= 11) setCIF(input);
if (input.length <= 11)
setCIF(input);
setError(null);
}}
disabled={isValidated}
withAsterisk
/>
{error && (
<Center mt="xl">
<Text c="red" fw={500} size="md">
{error}
</Text>
</Center>
)}
{isValidated && (
<Tabs defaultValue="details" mt="md" variant="outline">
<Tabs.List>
<Tabs.Tab value="details" leftSection={<IconUser size={12} />}>User Details</Tabs.Tab>
<Tabs.Tab value="address" leftSection={<IconHome size={12} />}>Address</Tabs.Tab>
<Tabs.Tab value="rights" leftSection={<IconGavel size={12} />}>Rights</Tabs.Tab>
</Tabs.List>
@@ -241,24 +295,32 @@ export default function UserConfiguration() {
<Paper withBorder p="md" radius="md">
<Stack>
<Group grow>
<Text fw={700} >User ID:</Text>
<Text c="dimmed">{userDetails?.userId ?? "N/A"}</Text>
<Text fw={700} >ID:</Text>
<Text>{userDetails?.userId ?? "N/A"}</Text>
</Group>
<Group grow>
<Text fw={700} >User Name:</Text>
<Text fw={700} >Branch:</Text>
<Text>{userDetails?.branchNo ?? "N/A"}</Text>
</Group>
<Group grow>
<Text fw={700} >Name:</Text>
<Text>{userDetails?.name ?? "N/A"}</Text>
</Group>
<Group grow>
<Text fw={700} >Mobile Number:</Text>
<Text c="dimmed">{userDetails?.mobile ?? "N/A"}</Text>
</Group>
<Group grow>
<Text fw={700} >User Address:</Text>
<Text>{userDetails?.address ?? "N/A"}</Text>
<Text fw={700} >Date of Birth:</Text>
<Text>
{userDetails?.dateOfBirth
? `${userDetails.dateOfBirth.slice(0, 2)}/${userDetails.dateOfBirth.slice(2, 4)}/${userDetails.dateOfBirth.slice(4)}`
: "N/A"}
</Text>
</Group>
<Group grow>
<Text fw={700} >Savings Account:</Text>
<Text c="blue">{savingsAccount ?? "N/A"}</Text>
<Text c="blue">
{Array.isArray(savingsAccount)
? savingsAccount.join(", ")
: savingsAccount ?? "N/A"}
</Text>
</Group>
<Group grow>
<Text fw={700}>Active Accounts:</Text>
@@ -269,9 +331,31 @@ export default function UserConfiguration() {
</ScrollArea>
</Tabs.Panel>
<Tabs.Panel value="address" pt="xs">
<ScrollArea h={250} type="auto" offsetScrollbars>
<Paper withBorder p="md" radius="md">
<Stack>
<Group grow>
<Text fw={700} >Address:</Text>
<Text>{userDetails?.address ?? "N/A"}</Text>
</Group>
<Group grow>
<Text fw={700} >Pincode:</Text>
<Text>{userDetails?.pinCode ?? "N/A"}</Text>
</Group>
<Group grow>
<Text fw={700} >Mobile Number:</Text>
<Text>{userDetails?.mobile ?? "N/A"}</Text>
</Group>
</Stack>
</Paper>
</ScrollArea>
</Tabs.Panel>
<Tabs.Panel value="rights" pt="xs">
<Stack gap="sm">
<Group gap="xl" align="center">
<Stack gap="md">
{/* Internet Banking Section */}
<Group>
<Box w={150}>
<Text fw={700}>Internet Banking</Text>
</Box>
@@ -281,7 +365,10 @@ export default function UserConfiguration() {
checked={ibEnabled}
onChange={(e) => {
setIbEnabled(e.currentTarget.checked);
if (!e.currentTarget.checked) setIbAccess(null);
if (!e.currentTarget.checked) {
setIbAccess(null);
setIbLimit("");
}
}}
/>
</Box>
@@ -302,8 +389,25 @@ export default function UserConfiguration() {
/>
</Box>
</Group>
<Group align="center" gap="sm">
<Text fw={700}>
Fund Transfer Limit for Internet Banking
</Text>
<TextInput
placeholder="Enter Amount"
value={ibLimit}
onChange={(e) => {
const val = e.currentTarget.value;
if (val === "" || (/^\d{0,7}$/.test(val))) {
setIbLimit(val);
}
}}
/>
</Group>
<Group gap="xl" align="center">
<Divider my="sm" labelPosition="center" />
<Group >
<Box w={150}>
<Text fw={700}>Mobile Banking</Text>
</Box>
@@ -313,7 +417,10 @@ export default function UserConfiguration() {
checked={mbEnabled}
onChange={(e) => {
setMbEnabled(e.currentTarget.checked);
if (!e.currentTarget.checked) setMbAccess(null);
if (!e.currentTarget.checked) {
setMbAccess(null);
setMbLimit("");
}
}}
/>
</Box>
@@ -334,6 +441,21 @@ export default function UserConfiguration() {
/>
</Box>
</Group>
<Group align="center" gap="sm">
<Text fw={700}>
Fund Transfer Limit for Mobile Banking
</Text>
<TextInput
placeholder="Enter Amount"
value={mbLimit}
onChange={(e) => {
const val = e.currentTarget.value;
if (val === "" || (/^\d{0,7}$/.test(val))) {
setMbLimit(val);
}
}}
/>
</Group>
</Stack>
</Tabs.Panel>
</Tabs>
@@ -355,9 +477,12 @@ export default function UserConfiguration() {
<Text>
<strong>Mobile Number:</strong> {userDetails?.mobile ?? "N/A"}
</Text>
<Divider label="Rights" size="xs" />
{/* Internet Banking Section */}
<TextInput
label="Internet Banking"
label="Internet Banking Access"
value={
ibAccess === "1"
? "Transaction"
@@ -367,8 +492,16 @@ export default function UserConfiguration() {
}
readOnly
/>
<TextInput
label="Mobile Banking"
label="Internet Banking Limit"
value={ibLimit || "N/A"}
readOnly
/>
{/* Mobile Banking Section */}
<TextInput
label="Mobile Banking Access"
value={
mbAccess === "1"
? "Transaction"
@@ -378,7 +511,14 @@ export default function UserConfiguration() {
}
readOnly
/>
<TextInput
label="Mobile Banking Limit"
value={mbLimit || "N/A"}
readOnly
/>
</Stack>
<Group justify="right" mt="md">
<Button variant="default" onClick={() => setShowPreviewModal(false)}>
Cancel

View File

@@ -11,6 +11,7 @@ import {
LoadingOverlay,
Box,
Paper,
Center,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
@@ -27,35 +28,34 @@ export default function ViewUserRights() {
const [CIFNo, setCIFNo] = useState("");
const [loading, setLoading] = useState(false);
const [userData, setUserData] = useState<UserRights | null>(null);
const [detailsExpanded, setDetailsExpanded] = useState(true);
const [error, setError] = useState<string | null>(null);
const handleSearch = async () => {
setError(null);
if (!CIFNo) {
notifications.show({
color: "red",
title: "Required",
message: "Please enter CIF Number.",
title: "CIF Missing",
message: "Please enter a CIF number to proceed.",
});
setUserData(null);
return;
}
setLoading(true);
setUserData(null);
try {
const token = localStorage.getItem("admin_access_token");
const response = await fetch(
`/api/auth/admin/user/rights?CIF=${CIFNo}`,
{
const response = await fetch(`/api/auth/admin/user/rights?CIF=${CIFNo}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Login-Type": "Admin",
Authorization: `Bearer ${token}`,
},
}
);
});
const data = await response.json();
if (response.ok && data) {
setUserData({
customer_no: data.customer_no,
@@ -66,18 +66,10 @@ export default function ViewUserRights() {
mbAccess: data.mb_access_level,
});
} else {
notifications.show({
color: "red",
title: "Not Found",
message: "No user rights found for this identifier.",
});
setError("User not present or invalid CIF number.");
}
} catch (err) {
notifications.show({
color: "red",
title: "Failed",
message: "Error fetching user rights",
});
setError("Error fetching user rights. Please try again.");
} finally {
setLoading(false);
}
@@ -89,15 +81,15 @@ export default function ViewUserRights() {
<Title order={4}>View User Rights</Title>
<Group mt="sm">
<TextInput
// label="Enter CIF No"
placeholder="Enter CIF Number"
value={CIFNo}
onInput={(e) => {
const input = e.currentTarget.value.replace(/\D/g, "");
if (input.length <= 11) {
setCIFNo(input);
setUserData(null)
};
setUserData(null);
setError(null);
}
}}
style={{ flex: 1 }}
/>
@@ -106,6 +98,15 @@ export default function ViewUserRights() {
</Button>
</Group>
{/* Error message in center */}
{error && (
<Center mt="xl">
<Text c="red" fw={500} size="md">
{error}
</Text>
</Center>
)}
{userData && (
<>
<Divider size="xs" label="User Info" mt="md" />
@@ -119,35 +120,57 @@ export default function ViewUserRights() {
>
<Stack gap="sm">
<Group justify="space-between">
<Text size="sm" fw={500} c="dimmed" w={150}>CIF Number:</Text>
<Text size="sm" fw={500} c="dimmed" w={150}>
CIF Number:
</Text>
<Text size="md">{userData.customer_no}</Text>
</Group>
<Group justify="space-between">
<Text size="sm" fw={500} c="dimmed">User Created :</Text>
<Text size="md">{new Date(userData.created_at).toLocaleString()}</Text>
<Text size="sm" fw={500} c="dimmed">
User Created :
</Text>
<Text size="md">
{new Date(userData.created_at).toLocaleString()}
</Text>
</Group>
<Group justify="space-between">
<Text size="sm" fw={500} c="dimmed">User Status:</Text>
<Text size="md" c="green">Active</Text>
<Text size="sm" fw={500} c="dimmed">
User Status:
</Text>
<Text size="md" c="green">
Active
</Text>
</Group>
<Group justify="space-between">
<Text size="sm" fw={500} c="dimmed">User Registered Status:</Text>
<Text size="md" c={userData.is_first_login ? "orange" : "green"}>
{userData.is_first_login ? "Not Registered" : "Registered"}
<Text size="sm" fw={500} c="dimmed">
User Registered Status:
</Text>
<Text
size="md"
c={userData.is_first_login ? "orange" : "green"}
>
{userData.is_first_login
? "Not Registered"
: "Registered"}
</Text>
</Group>
</Stack>
</Paper>
<Divider label="Rights" size="xs" mt="md" />
<Paper style={{
<Paper
style={{
border: "1px solid #47C44D",
borderRadius: 8,
padding: 12,
marginTop: 8,
}}>
}}
>
<Stack gap="sm" mt="sm">
<Group justify="space-between">
<Text size="sm" fw={500} c="dimmed" w={150}>Internet Banking Rights:</Text>
<Text size="sm" fw={500} c="dimmed" w={150}>
Internet Banking Rights:
</Text>
<Text size="md">
{userData.ibAccess === "1"
? "Transaction Mode"
@@ -157,7 +180,9 @@ export default function ViewUserRights() {
</Text>
</Group>
<Group justify="space-between">
<Text size="sm" fw={500} c="dimmed" w={150}>Mobile Banking Rights:</Text>
<Text size="sm" fw={500} c="dimmed" w={150}>
Mobile Banking Rights:
</Text>
<Text size="md">
{userData.mbAccess === "1"
? "Transaction Mode"
@@ -169,8 +194,7 @@ export default function ViewUserRights() {
</Stack>
</Paper>
</>
)
}
)}
</Box>
);
}

View File

@@ -1,50 +1,67 @@
"use client";
import React, { useState, useEffect } from "react";
import { Text, Box, Image, Button, Stack, Divider, Title } from "@mantine/core";
import {
Text,
Box,
Image,
Stack,
Divider,
Title,
Collapse,
Group,
UnstyledButton,
} 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/logo1.jpg';
import { IconEye, IconLogout, IconPhoneFilled, IconUsers, IconUserScreen } from "@tabler/icons-react";
import logo from "@/app/image/logo1.jpg";
import {
IconLogout,
IconPhoneFilled,
IconUsers,
IconChevronDown,
IconChevronUp,
IconSettings,
} from "@tabler/icons-react";
import UserConfiguration from "./UserConfiguration";
import ViewUserConfiguration from "./ViewUserConfiguration";
import UnlockedUsers from "./UnlockedUsers";
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);
const [view, setView] = useState<string | null>(null);
const [name, setName] = useState<string | null>(null);
const [lastLoginDetails, setLastLoginDetails] = useState<string | null>(null);
const [userMenuOpen, setUserMenuOpen] = useState(true);
const [configMenuOpen, setConfigMenuOpen] = useState(false);
async function handleLogout(e: React.FormEvent) {
e.preventDefault();
localStorage.removeItem("admin_access_token");
localStorage.clear();
sessionStorage.clear();
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',
const response = await fetch("/api/auth/admin/admin_details", {
method: "GET",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
"X-Login-Type": "Admin",
'Authorization': `Bearer ${token}`
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') {
} else if (response.status === 401 || data.message === "invalid or expired token") {
localStorage.removeItem("admin_access_token");
router.push('/administrator/login');
}
else {
router.push("/administrator/login");
} else {
notifications.show({
withBorder: true,
color: "red",
@@ -63,7 +80,9 @@ export default function Login() {
} else {
SetAuthorized(true);
const fetchLoginTime = async () => {
const result = await handleFetchUserDetails({ preventDefault: () => { } } as React.FormEvent);
const result = await handleFetchUserDetails({
preventDefault: () => { },
} as React.FormEvent);
if (result) {
setLastLoginDetails(result.last_login);
setName(result.name);
@@ -73,29 +92,21 @@ export default function Login() {
}
}, []);
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%", }}>
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "100%" }}>
{/* Header */}
<Box
style={{
height: "60px",
position: 'relative',
width: '100%',
position: "relative",
width: "100%",
display: "flex",
justifyContent: "flex-start",
background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
background:
"linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
}}
>
<Image
@@ -108,76 +119,214 @@ export default function Login() {
<Title
order={2}
style={{
fontFamily: 'Roboto',
position: 'absolute',
top: '30%',
left: '6%',
color: 'White',
transition: "opacity 0.5s ease-in-out",
fontFamily: "Roboto",
position: "absolute",
top: "30%",
left: "6%",
color: "White",
}}
>
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title>
<Text
style={{
position: 'absolute',
top: '50%',
left: '80%',
color: 'white',
textShadow: '1px 1px 2px black',
position: "absolute",
top: "50%",
left: "80%",
color: "white",
textShadow: "1px 1px 2px black",
}}
>
<IconPhoneFilled size={20} /> Toll Free No : 1800-180-8008
<IconPhoneFilled size={18} /> Toll Free No : 1800-180-8008
</Text>
</Box>
{/* layout and */}
<Box style={{ display: 'flex', height: '90vh' }}>
{/* Sidebar manually placed under header */}
{/* Layout */}
<Box style={{ display: "flex", height: "90vh" }}>
{/* Sidebar */}
<Box
style={{
width: 250,
background: "#ebf5f5ff",
// background: "linear-gradient(90deg,rgba(42, 123, 155, 1) 0%, rgba(87, 199, 133, 1) 30%)",
padding: '1rem',
borderRight: '1px solid #ccc',
width: 240,
background: "#02a355",
padding: "0.75rem",
borderRight: "1px solid #ccc",
color: "white",
fontSize: "13px",
}}
>
<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 size="15" /> User Configuration
<Title order={4} c="white" style={{ textAlign: "center", marginBottom: "6px" }}>
Admin Portal
</Title>
<Divider my="xs" color="rgba(255,255,255,0.3)" />
{/* User Maintenance */}
<UnstyledButton
onClick={() => setUserMenuOpen(!userMenuOpen)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
padding: "5px 6px",
borderRadius: "4px",
backgroundColor: "rgba(255,255,255,0.1)",
color: "white",
fontSize: "13px",
}}
>
<Group gap="xs">
<IconUsers size={15} />
<Text fw={600} size="sm">
User Maintenance
</Text>
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={() => handleClick('ViewUser')}>
<IconEye size="15" /> View User Configuration
</Group>
{userMenuOpen ? <IconChevronUp size={15} /> : <IconChevronDown size={15} />}
</UnstyledButton>
<Collapse in={userMenuOpen}>
<Stack gap={2} pl="md" mt={4}>
<Box
onClick={() => setView("userConf")}
style={{
cursor: "pointer",
color: view === "userConf" ? "#02a355" : "white",
backgroundColor: view === "userConf" ? "white" : "transparent",
borderRadius: "3px",
padding: "3px 6px",
transition: "0.2s",
}}
>
User Create / Update
</Box>
<Box
onClick={() => setView("viewUser")}
style={{
cursor: "pointer",
color: view === "viewUser" ? "#02a355" : "white",
backgroundColor: view === "viewUser" ? "white" : "transparent",
borderRadius: "3px",
padding: "3px 6px",
transition: "0.2s",
}}
>
Users Status
</Box>
<Box
onClick={() => setView("unlockUser")}
style={{
cursor: "pointer",
color: view === "unlockUser" ? "#02a355" : "white",
backgroundColor: view === "unlockUser" ? "white" : "transparent",
borderRadius: "3px",
padding: "3px 6px",
transition: "0.2s",
}}
>
User Unlock
</Box>
</Stack>
</Collapse>
{/* Configuration */}
<UnstyledButton
onClick={() => setConfigMenuOpen(!configMenuOpen)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
padding: "5px 6px",
marginTop: "8px",
borderRadius: "4px",
backgroundColor: "rgba(255,255,255,0.1)",
color: "white",
fontSize: "13px",
}}
>
<Group gap="xs">
<IconSettings size={15} />
<Text fw={600} size="sm">
Configuration
</Text>
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={handleLogout}>
<IconLogout size="15" /> Logout
</Group>
{configMenuOpen ? <IconChevronUp size={15} /> : <IconChevronDown size={15} />}
</UnstyledButton>
<Collapse in={configMenuOpen}>
<Stack gap={2} pl="md" mt={4}>
<Box
onClick={() => setView("ifscConfig")}
style={{
cursor: "pointer",
color: view === "ifscConfig" ? "#02a355" : "white",
backgroundColor: view === "ifscConfig" ? "white" : "transparent",
borderRadius: "3px",
padding: "3px 6px",
transition: "0.2s",
}}
>
IFSC Configuration
</Box>
</Stack>
</Collapse>
<Divider my="xs" color="rgba(255,255,255,0.3)" />
<Text
onClick={handleLogout}
style={{
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: "4px",
color: "white",
marginTop: "8px",
fontSize: "13px",
}}
>
<IconLogout size={14} /> Logout
</Text>
</Box>
{/* Main Content */}
<Stack style={{ flex: 1, padding: "1rem" }}>
<Box>
<Text c="Blue" size="sm" fs="italic">
Welcome, {name}
</Text>
<Text size="xs" c="gray" style={{ fontFamily: "inter" }}>
Last logged in at{" "}
{lastLoginDetails
? new Date(lastLoginDetails).toLocaleString()
: "N/A"}
</Text>
</Box>
<Box>
{view === "userConf" && <UserConfiguration />}
{view === "viewUser" && <ViewUserConfiguration />}
{view === "unlockUser" && <UnlockedUsers />}
{view === "ifscConfig" && (
<Text size="sm" c="gray">
IFSC Configuration Page Coming Soon
</Text>
)}
{!view && (
<>
<Text size="xl" c="gray">
Welcome To The Internet Banking Admin Portal
</Text>
<Text size="sm" c="black">
Choose the service from the menu.
</Text>
</>
)}
</Box>
</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"}
<Divider size="xs" color="#99c2ff" />
<Text size="xs" style={{ textAlign: "center" }}>
© 2025 The Kangra Central Co-Operative Bank
</Text>
</Box>
<Box>
{view === 'userConf' && <UserConfiguration />}
{view === 'view' && <Text size="xl"><ViewUserConfiguration /></Text>}
{!view &&
<Text size="xl">Welcome To The Admin Portal</Text>
}
</Box>
</Stack>
</Box>
<Divider size="xs" color='#99c2ff' />
<Text size="sm" style={{ textAlign: "center" }}>© 2025 The Kangra Central Co-Operative Bank</Text>
</div>
</Providers>
);
}
}

View File

@@ -45,8 +45,8 @@ export default function Login() {
}
try {
await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: mobile });
// await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "7890544527" });
// await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: mobile });
await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "7890544527" });
notifications.show({
color: 'orange',
title: 'OTP Required',
@@ -67,8 +67,8 @@ export default function Login() {
async function handleVerifyOtp(mobile?: string) {
try {
if (mobile) {
await verifyLoginOtp(otp, mobile);
// await verifyLoginOtp(otp, '7890544527');
// await verifyLoginOtp(otp, mobile);
await verifyLoginOtp(otp, '7890544527');
return true;
}
}