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

View File

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

View File

@@ -1,50 +1,67 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; 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 { notifications } from "@mantine/notifications";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import NextImage from "next/image"; import NextImage from "next/image";
import logo from '@/app/image/logo1.jpg'; import logo from "@/app/image/logo1.jpg";
import { IconEye, IconLogout, IconPhoneFilled, IconUsers, IconUserScreen } from "@tabler/icons-react"; import {
IconLogout,
IconPhoneFilled,
IconUsers,
IconChevronDown,
IconChevronUp,
IconSettings,
} from "@tabler/icons-react";
import UserConfiguration from "./UserConfiguration"; import UserConfiguration from "./UserConfiguration";
import ViewUserConfiguration from "./ViewUserConfiguration"; import ViewUserConfiguration from "./ViewUserConfiguration";
import UnlockedUsers from "./UnlockedUsers";
export default function Login() { export default function Login() {
const router = useRouter(); const router = useRouter();
const [authorized, SetAuthorized] = useState<boolean | null>(null); const [authorized, SetAuthorized] = useState<boolean | null>(null);
const [view, setView] = useState<'userConf' | 'view' | null>(null); const [view, setView] = useState<string | null>(null);
const [lastLoginDetails, setLastLoginDetails] = useState(null); const [name, setName] = useState<string | null>(null);
const [name, setName] = useState(null); const [lastLoginDetails, setLastLoginDetails] = useState<string | null>(null);
const [userMenuOpen, setUserMenuOpen] = useState(true);
const [configMenuOpen, setConfigMenuOpen] = useState(false);
async function handleLogout(e: React.FormEvent) { async function handleLogout(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
localStorage.removeItem("admin_access_token");
localStorage.clear(); localStorage.clear();
sessionStorage.clear(); sessionStorage.clear();
router.push("/administrator/login"); router.push("/administrator/login");
} }
async function handleFetchUserDetails(e: React.FormEvent) { async function handleFetchUserDetails(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
const token = localStorage.getItem("admin_access_token"); const token = localStorage.getItem("admin_access_token");
const response = await fetch('/api/auth/admin/admin_details', { const response = await fetch("/api/auth/admin/admin_details", {
method: 'GET', method: "GET",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
"X-Login-Type": "Admin", "X-Login-Type": "Admin",
'Authorization': `Bearer ${token}` Authorization: `Bearer ${token}`,
}, },
}); });
const data = await response.json(); const data = await response.json();
// console.log(data)
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') {
localStorage.removeItem("admin_access_token"); localStorage.removeItem("admin_access_token");
router.push('/administrator/login'); router.push("/administrator/login");
} } else {
else {
notifications.show({ notifications.show({
withBorder: true, withBorder: true,
color: "red", color: "red",
@@ -63,7 +80,9 @@ export default function Login() {
} else { } else {
SetAuthorized(true); SetAuthorized(true);
const fetchLoginTime = async () => { const fetchLoginTime = async () => {
const result = await handleFetchUserDetails({ preventDefault: () => { } } as React.FormEvent); const result = await handleFetchUserDetails({
preventDefault: () => { },
} as React.FormEvent);
if (result) { if (result) {
setLastLoginDetails(result.last_login); setLastLoginDetails(result.last_login);
setName(result.name); 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 null;
if (authorized) {
return ( return (
<Providers> <Providers>
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "100%", }}> <div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "100%" }}>
{/* Header */} {/* Header */}
<Box <Box
style={{ style={{
height: "60px", height: "60px",
position: 'relative', position: "relative",
width: '100%', width: "100%",
display: "flex", display: "flex",
justifyContent: "flex-start", 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 <Image
@@ -108,76 +119,214 @@ export default function Login() {
<Title <Title
order={2} order={2}
style={{ style={{
fontFamily: 'Roboto', fontFamily: "Roboto",
position: 'absolute', position: "absolute",
top: '30%', top: "30%",
left: '6%', left: "6%",
color: 'White', color: "White",
transition: "opacity 0.5s ease-in-out",
}} }}
> >
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD. THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
</Title> </Title>
<Text <Text
style={{ style={{
position: 'absolute', position: "absolute",
top: '50%', top: "50%",
left: '80%', left: "80%",
color: 'white', color: "white",
textShadow: '1px 1px 2px black', textShadow: "1px 1px 2px black",
}} }}
> >
<IconPhoneFilled size={20} /> Toll Free No : 1800-180-8008 <IconPhoneFilled size={18} /> Toll Free No : 1800-180-8008
</Text> </Text>
</Box> </Box>
{/* layout and */}
<Box style={{ display: 'flex', height: '90vh' }}> {/* Layout */}
{/* Sidebar manually placed under header */} <Box style={{ display: "flex", height: "90vh" }}>
{/* Sidebar */}
<Box <Box
style={{ style={{
width: 250, width: 240,
background: "#ebf5f5ff", background: "#02a355",
// background: "linear-gradient(90deg,rgba(42, 123, 155, 1) 0%, rgba(87, 199, 133, 1) 30%)", padding: "0.75rem",
padding: '1rem', borderRight: "1px solid #ccc",
borderRight: '1px solid #ccc', color: "white",
fontSize: "13px",
}} }}
> >
<Title order={3} style={{ textAlign: 'center' }}>Admin Portal</Title> <Title order={4} c="white" style={{ textAlign: "center", marginBottom: "6px" }}>
<Divider my="xs" label="Menu" labelPosition="center" /> Admin Portal
<Stack> </Title>
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={() => handleClick('UserConf')}> <Divider my="xs" color="rgba(255,255,255,0.3)" />
<IconUsers size="15" /> User Configuration
{/* 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>
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={() => handleClick('ViewUser')}> </Group>
<IconEye size="15" /> View User Configuration {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>
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={handleLogout}> </Group>
<IconLogout size="15" /> Logout {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> </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> </Stack>
</Box> </Box>
{/* Main Content Area */} <Divider size="xs" color="#99c2ff" />
<Stack style={{ flex: 1, padding: '1rem' }}> <Text size="xs" style={{ textAlign: "center" }}>
<Box> © 2025 The Kangra Central Co-Operative Bank
<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> </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> </div>
</Providers> </Providers>
); );
} }
}

View File

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