Added Edit Uder in Admin
This commit is contained in:
parent
2864bf8cdc
commit
fc4be718f7
66
payment-iq/app/api/dashboard/admin/users/route.ts
Normal file
66
payment-iq/app/api/dashboard/admin/users/route.ts
Normal file
@ -0,0 +1,66 @@
|
||||
// app/api/user/route.ts
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json([
|
||||
{
|
||||
merchantId: 100987998,
|
||||
id: "bc6a8a55-13bc-4538-8255-cd0cec3bb4e9",
|
||||
mame: "Jacob",
|
||||
username: "lspaddy",
|
||||
firstName: "Paddy",
|
||||
lastName: "Man",
|
||||
email: "patrick@omegasys.eu",
|
||||
phone: "",
|
||||
jobTitle: "",
|
||||
enabled: true,
|
||||
authorities: [
|
||||
"ROLE_IIN",
|
||||
"ROLE_FIRST_APPROVER",
|
||||
"ROLE_RULES_ADMIN",
|
||||
"ROLE_TRANSACTION_VIEWER",
|
||||
"ROLE_IIN_ADMIN",
|
||||
"ROLE_USER_PSP_ACCOUNT",
|
||||
],
|
||||
allowedMerchantIds: [100987998],
|
||||
created: "2025-05-04T15:32:48.432Z",
|
||||
disabledBy: null,
|
||||
disabledDate: null,
|
||||
disabledReason: null,
|
||||
incidentNotes: false,
|
||||
lastLogin: "",
|
||||
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
|
||||
marketingNewsletter: false,
|
||||
releaseNotes: false,
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
|
||||
twoFactorCondition: "required",
|
||||
twoFactorCredentials: [],
|
||||
},
|
||||
{
|
||||
merchantId: 100987998,
|
||||
mame: "Jacob",
|
||||
id: "382eed15-1e21-41fa-b1f3-0c1adb3af714",
|
||||
username: "lsterence",
|
||||
firstName: "Terence",
|
||||
lastName: "User",
|
||||
email: "terence@omegasys.eu",
|
||||
phone: "",
|
||||
jobTitle: "",
|
||||
enabled: true,
|
||||
authorities: ["ROLE_IIN", "ROLE_FIRST_APPROVER", "ROLE_RULES_ADMIN"],
|
||||
allowedMerchantIds: [100987998],
|
||||
created: "2025-05-04T15:32:48.432Z",
|
||||
disabledBy: null,
|
||||
disabledDate: null,
|
||||
disabledReason: null,
|
||||
incidentNotes: false,
|
||||
lastLogin: "",
|
||||
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
|
||||
marketingNewsletter: false,
|
||||
releaseNotes: false,
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
|
||||
twoFactorCondition: "required",
|
||||
twoFactorCredentials: [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
@ -1,23 +1,23 @@
|
||||
import { transactionDummyData } from '@/app/features/Pages/transactions/mockData';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { transactionDummyData } from "@/app/components/test/test2";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
const state = searchParams.get('state');
|
||||
const user = searchParams.get('user');
|
||||
const state = searchParams.get("state");
|
||||
const user = searchParams.get("user");
|
||||
|
||||
let filteredTransactions = [...transactionDummyData];
|
||||
|
||||
if (user) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.user.toString() === user
|
||||
(tx) => tx.user.toString() === user
|
||||
);
|
||||
}
|
||||
|
||||
if (state) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.state.toLowerCase() === state.toLowerCase()
|
||||
(tx) => tx.state.toLowerCase() === state.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,28 +1,62 @@
|
||||
// app/transactions/page.tsx
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState } from "react";
|
||||
|
||||
// mocks/transactionData.ts
|
||||
export const transactionDummyData = [
|
||||
{
|
||||
id: 1,
|
||||
merchandId: 100987998,
|
||||
transactionID: 1049131973,
|
||||
user: 1,
|
||||
created: "2025-06-18 10:10:30",
|
||||
state: "FAILED",
|
||||
statusDescription: "ERR_ABOVE_LIMIT",
|
||||
pspStatusCode: 100501,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
merchandId: 100987998,
|
||||
transactionID: 1049131973,
|
||||
user: 2,
|
||||
created: "2025-06-18 10:10:30",
|
||||
state: "FAILED",
|
||||
statusDescription: "ERR_ABOVE_LIMIT",
|
||||
pspStatusCode: 100501,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
merchandId: 100987998,
|
||||
transactionID: 1049131973,
|
||||
user: 3,
|
||||
created: "2025-06-18 10:10:30",
|
||||
state: "FAILED",
|
||||
statusDescription: "ERR_ABOVE_LIMIT",
|
||||
pspStatusCode: 100501,
|
||||
},
|
||||
];
|
||||
|
||||
export default function TransactionsPage() {
|
||||
const [userId, setUserId] = useState('');
|
||||
const [state, setState] = useState('');
|
||||
const [statusCode, setStatusCode] = useState('');
|
||||
const [userId, setUserId] = useState("");
|
||||
const [state, setState] = useState("");
|
||||
const [statusCode, setStatusCode] = useState("");
|
||||
const [transactions, setTransactions] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchTransactions = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const url = new URL('https://api.example.com/transactions');
|
||||
if (userId) url.searchParams.append('userId', userId);
|
||||
if (state) url.searchParams.append('state', state);
|
||||
if (statusCode) url.searchParams.append('statusCode', statusCode);
|
||||
const url = new URL("https://api.example.com/transactions");
|
||||
if (userId) url.searchParams.append("userId", userId);
|
||||
if (state) url.searchParams.append("state", state);
|
||||
if (statusCode) url.searchParams.append("statusCode", statusCode);
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
const data = await response.json();
|
||||
setTransactions(data.transactions);
|
||||
} catch (error) {
|
||||
console.error('Error fetching transactions:', error);
|
||||
console.error("Error fetching transactions:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -72,7 +106,7 @@ export default function TransactionsPage() {
|
||||
disabled={loading}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded text-sm"
|
||||
>
|
||||
{loading ? 'Loading...' : 'Search'}
|
||||
{loading ? "Loading..." : "Search"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -104,86 +138,49 @@ export default function TransactionsPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-4 text-sm">
|
||||
{loading ? 'Loading transactions...' : 'No transactions found'}
|
||||
{loading ? "Loading transactions..." : "No transactions found"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// mocks/handlers.ts
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { transactionDummyData } from './transactionData';
|
||||
import { http, HttpResponse } from "msw";
|
||||
|
||||
export const handlers = [
|
||||
http.get('https://api.example.com/transactions', ({ request }) => {
|
||||
http.get("https://api.example.com/transactions", ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Get query parameters
|
||||
const userId = url.searchParams.get('userId');
|
||||
const state = url.searchParams.get('state');
|
||||
const statusCode = url.searchParams.get('statusCode');
|
||||
const userId = url.searchParams.get("userId");
|
||||
const state = url.searchParams.get("state");
|
||||
const statusCode = url.searchParams.get("statusCode");
|
||||
|
||||
// Filter transactions based on query parameters
|
||||
let filteredTransactions = [...transactionDummyData];
|
||||
|
||||
if (userId) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.user.toString() === userId
|
||||
(tx) => tx.user.toString() === userId
|
||||
);
|
||||
}
|
||||
|
||||
if (state) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.state.toLowerCase() === state.toLowerCase()
|
||||
(tx) => tx.state.toLowerCase() === state.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
if (statusCode) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.pspStatusCode.toString() === statusCode
|
||||
(tx) => tx.pspStatusCode.toString() === statusCode
|
||||
);
|
||||
}
|
||||
|
||||
return HttpResponse.json({
|
||||
transactions: filteredTransactions,
|
||||
count: filteredTransactions.length
|
||||
count: filteredTransactions.length,
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
// mocks/transactionData.ts
|
||||
export const transactionDummyData = [
|
||||
{
|
||||
id: 1,
|
||||
merchandId: 100987998,
|
||||
transactionID: 1049131973,
|
||||
user: 1,
|
||||
created: "2025-06-18 10:10:30",
|
||||
state: "FAILED",
|
||||
statusDescription: "ERR_ABOVE_LIMIT",
|
||||
pspStatusCode: 100501,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
merchandId: 100987998,
|
||||
transactionID: 1049131973,
|
||||
user: 2,
|
||||
created: "2025-06-18 10:10:30",
|
||||
state: "FAILED",
|
||||
statusDescription: "ERR_ABOVE_LIMIT",
|
||||
pspStatusCode: 100501,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
merchandId: 100987998,
|
||||
transactionID: 1049131973,
|
||||
user: 3,
|
||||
created: "2025-06-18 10:10:30",
|
||||
state: "FAILED",
|
||||
statusDescription: "ERR_ABOVE_LIMIT",
|
||||
pspStatusCode: 100501,
|
||||
}
|
||||
];
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
// This ensures this component is rendered only on the client side
|
||||
"use client";
|
||||
|
||||
import Users from "@/app/features/Pages/Admin/Users/users";
|
||||
|
||||
export default function BackOfficeUsersPage() {
|
||||
export default async function BackOfficeUsersPage() {
|
||||
const res = await fetch("http://localhost:3000/api/dashboard/admin/users", {
|
||||
cache: "no-store", // 👈 disables caching for SSR freshness
|
||||
});
|
||||
const users = await res.json();
|
||||
|
||||
console.log("[USERS]", users);
|
||||
return (
|
||||
<div>
|
||||
<Users />
|
||||
<Users users={users} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,92 +1,17 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Card, CardContent, Typography, Chip, Stack } from "@mui/material";
|
||||
import React from "react";
|
||||
import { Card, CardContent, Typography, Stack } from "@mui/material";
|
||||
import { IUser } from "./interfaces";
|
||||
import UserRoleCard from "@/app/features/UserRoles/userRoleCard";
|
||||
import UserRoleCard from "@/app/features/UserRoles/UserRoleCard";
|
||||
|
||||
const Users = () => {
|
||||
const [data, setData] = useState([
|
||||
{
|
||||
merchantId: 100987998,
|
||||
id: "bc6a8a55-13bc-4538-8255-cd0cec3bb4e9",
|
||||
mame: "Jacob",
|
||||
username: "lspaddy",
|
||||
firstName: "Paddy",
|
||||
lastName: "Man",
|
||||
email: "patrick@omegasys.eu",
|
||||
phone: "",
|
||||
jobTitle: "",
|
||||
enabled: true,
|
||||
authorities: [
|
||||
"ROLE_IIN",
|
||||
"ROLE_FIRST_APPROVER",
|
||||
"ROLE_RULES_ADMIN",
|
||||
"ROLE_TRANSACTION_VIEWER",
|
||||
"ROLE_IIN_ADMIN",
|
||||
"ROLE_USER_PSP_ACCOUNT",
|
||||
],
|
||||
allowedMerchantIds: [100987998],
|
||||
created: "2025-05-04T15:32:48.432Z",
|
||||
disabledBy: null,
|
||||
disabledDate: null,
|
||||
disabledReason: null,
|
||||
incidentNotes: false,
|
||||
lastLogin: "",
|
||||
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
|
||||
marketingNewsletter: false,
|
||||
releaseNotes: false,
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
|
||||
twoFactorCondition: "required",
|
||||
twoFactorCredentials: [],
|
||||
},
|
||||
{
|
||||
merchantId: 100987998,
|
||||
mame: "Jacob",
|
||||
id: "382eed15-1e21-41fa-b1f3-0c1adb3af714",
|
||||
username: "lsterence",
|
||||
firstName: "Terence",
|
||||
lastName: "User",
|
||||
email: "terence@omegasys.eu",
|
||||
phone: "",
|
||||
jobTitle: "",
|
||||
enabled: true,
|
||||
authorities: ["ROLE_IIN", "ROLE_FIRST_APPROVER", "ROLE_RULES_ADMIN"],
|
||||
allowedMerchantIds: [100987998],
|
||||
created: "2025-05-04T15:32:48.432Z",
|
||||
disabledBy: null,
|
||||
disabledDate: null,
|
||||
disabledReason: null,
|
||||
incidentNotes: false,
|
||||
lastLogin: "",
|
||||
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
|
||||
marketingNewsletter: false,
|
||||
releaseNotes: false,
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
|
||||
twoFactorCondition: "required",
|
||||
twoFactorCredentials: [],
|
||||
},
|
||||
// Add more users if needed
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// Only run MSW in the browser environment
|
||||
if (typeof window !== "undefined") {
|
||||
const fetchData = async () => {
|
||||
const response = await fetch(
|
||||
"https://test-bo.paymentiq.io/paymentiq/backoffice/api/v2/users/?includeSubMids=false&size=20&page=0&merchantId=100987998"
|
||||
); // This would be intercepted by MSW in the browser
|
||||
const data = await response.json();
|
||||
console.log("[DATA]", data);
|
||||
// setData(data);
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
interface UsersProps {
|
||||
users: IUser[];
|
||||
}
|
||||
|
||||
const Users: React.FC<UsersProps> = ({ users }) => {
|
||||
return (
|
||||
<div>
|
||||
{data.map((user: IUser) => (
|
||||
{users.map((user: IUser) => (
|
||||
<Card key={user.id} sx={{ mb: 2 }}>
|
||||
<CardContent>
|
||||
<Typography variant="h6">{user.username}</Typography>
|
||||
@ -103,7 +28,7 @@ const Users = () => {
|
||||
isAdmin={true}
|
||||
lastLogin="small"
|
||||
roles={user.authorities}
|
||||
// merchants={Numberuser.allowedMerchantIds}
|
||||
merchants={[]} // merchants={Numberuser.allowedMerchantIds}
|
||||
/>
|
||||
{/* Add more chips or UI elements for other data */}
|
||||
</Stack>
|
||||
@ -113,19 +38,4 @@ const Users = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Fetch data server-side using getServerSideProps
|
||||
// export async function getServerSideProps() {
|
||||
// // Replace this with your actual API call
|
||||
// const res = await fetch("https://api.example.com/users");
|
||||
// const data = await res.json();
|
||||
|
||||
// // Return the fetched data as props
|
||||
// return {
|
||||
// props: {
|
||||
// result: data, // Assuming data is an array of users
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
|
||||
export default Users;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
"use client";
|
||||
import { Box } from "@mui/material";
|
||||
import { GeneralHealthCard } from "../../GeneralHealthCard/GeneralHealthCard";
|
||||
import { TransactionsWaitingApproval } from "../../TransactionsWaitingApproval/TransactionsWaitingApproval";
|
||||
@ -6,53 +7,8 @@ import { Documentation } from "../../Documentation/Documentation";
|
||||
import { AccountIQ } from "../../AccountIQ/AccountIQ";
|
||||
import { WhatsNew } from "../../WhatsNew/WhatsNew";
|
||||
import { TransactionsOverView } from "../../TransactionsOverview/TransactionsOverview";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const DashboardHomePage = () => {
|
||||
// useEffect to fetch data when the component mounts
|
||||
useEffect(() => {
|
||||
// Construct the URL with query parameters
|
||||
const url =
|
||||
"https://test-bo.paymentiq.io/paymentiq/backoffice/api/v2/metrics/txsummary?merchantId=100987998&fromDate=2025-06-29+19%3A10&toDate=2025-07-01+19%3A09";
|
||||
|
||||
// Perform the fetch request inside useEffect
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
// Start loading
|
||||
|
||||
// Make the API request
|
||||
const response = await fetch(url, {
|
||||
method: "GET", // You can change this to 'POST' if necessary
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
// Add any other headers you need here
|
||||
},
|
||||
});
|
||||
|
||||
// Check if the response is OK (status code 200-299)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch: ${response.status}`);
|
||||
}
|
||||
|
||||
// Parse the JSON response
|
||||
const data = await response.json();
|
||||
console.log("[DATA]", data);
|
||||
|
||||
// Update the state with the fetched data
|
||||
// setTransactions(data.result || []);
|
||||
} catch (err) {
|
||||
// Handle any errors that occurred during the fetch
|
||||
// setError("Failed to load data, please try again.");
|
||||
console.error("Fetch error:", err);
|
||||
} finally {
|
||||
// Set loading to false when the request is complete
|
||||
// setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Call the fetch function
|
||||
fetchData();
|
||||
}, []); // Empty dependency
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ p: 2 }}>
|
||||
|
||||
@ -28,7 +28,6 @@ import SearchFilters from "@/app/components/searchFilter/SearchFilters";
|
||||
const paginationModel = { page: 0, pageSize: 50 };
|
||||
|
||||
export default function TransactionTable() {
|
||||
|
||||
const [form, setForm] = useState({
|
||||
keyword: "",
|
||||
transactionID: "",
|
||||
@ -80,7 +79,9 @@ export default function TransactionTable() {
|
||||
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
|
||||
|
||||
const handleExport = () => {
|
||||
const exportRows = onlyCurrentTable ? transactions.slice(0, 5) : transactions;
|
||||
const exportRows = onlyCurrentTable
|
||||
? transactions.slice(0, 5)
|
||||
: transactions;
|
||||
const exportData = [
|
||||
columns.map((col) => col.headerName),
|
||||
...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")),
|
||||
@ -103,7 +104,6 @@ export default function TransactionTable() {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
|
||||
const [transactions, setTransactions] = useState<any[]>([]);
|
||||
|
||||
const fetchTransactions = async () => {
|
||||
@ -113,33 +113,27 @@ export default function TransactionTable() {
|
||||
const data = await res.json();
|
||||
setTransactions(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching transactions:', error);
|
||||
console.error("Error fetching transactions:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchTransactions();
|
||||
}, [form]);
|
||||
|
||||
const handleDeleteFilter = (key) => {
|
||||
setForm((prev) => ({ ...prev, [key]: '' }));
|
||||
setForm((prev) => ({ ...prev, [key]: "" }));
|
||||
};
|
||||
|
||||
const handleClearAll = () => {
|
||||
resetForm()
|
||||
fetchTransactions()
|
||||
resetForm();
|
||||
fetchTransactions();
|
||||
};
|
||||
|
||||
|
||||
const handleClickField = (field: string, value: any) => {
|
||||
setForm((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<StyledPaper>
|
||||
<Stack
|
||||
@ -157,7 +151,11 @@ export default function TransactionTable() {
|
||||
sx={{ width: 300 }}
|
||||
/>
|
||||
<AdvancedSearch form={form} resetForm={resetForm} setForm={setForm} />
|
||||
<SearchFilters filters={form} onDeleteFilter={handleDeleteFilter} onClearAll={handleClearAll} />
|
||||
<SearchFilters
|
||||
filters={form}
|
||||
onDeleteFilter={handleDeleteFilter}
|
||||
onClearAll={handleClearAll}
|
||||
/>
|
||||
{/* <RightTemporaryDrawer /> */}
|
||||
{/* <SearchFilterForm /> */}
|
||||
<Button
|
||||
@ -174,15 +172,14 @@ export default function TransactionTable() {
|
||||
columns={columns}
|
||||
initialState={{ pagination: { paginationModel } }}
|
||||
pageSizeOptions={[50, 100]}
|
||||
sx={{ border: 0, cursor: 'pointer' }}
|
||||
|
||||
sx={{ border: 0, cursor: "pointer" }}
|
||||
onCellClick={(params, event) => {
|
||||
// Check if the click is on a specific column
|
||||
// Do something when this specific column is clicked
|
||||
handleClickField(params.field, params.value)
|
||||
console.log('Clicked cell value:', params.value); // The cell's value
|
||||
console.log('Column field:', params.field); // The column's field name (from your columns definition)
|
||||
console.log('Column header:', params.colDef.headerName); // The column's display name // Your custom logic here
|
||||
handleClickField(params.field, params.value);
|
||||
console.log("Clicked cell value:", params.value); // The cell's value
|
||||
console.log("Column field:", params.field); // The column's field name (from your columns definition)
|
||||
console.log("Column header:", params.colDef.headerName); // The column's display name // Your custom logic here
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
46
payment-iq/app/features/UserRoles/AddUser/AddUser.scss
Normal file
46
payment-iq/app/features/UserRoles/AddUser/AddUser.scss
Normal file
@ -0,0 +1,46 @@
|
||||
.edit-user {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
|
||||
input {
|
||||
flex: 1 1 20%;
|
||||
min-width: 150px;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
outline: none;
|
||||
transition: border-color 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: #0070f3;
|
||||
}
|
||||
}
|
||||
&__button-container {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
flex-basis: 100%;
|
||||
margin-top: 16px;
|
||||
padding: 10px 0;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
width: 100px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:first-child {
|
||||
color: var(--button-primary);
|
||||
border-color: var(--button-primary);
|
||||
}
|
||||
button:last-child {
|
||||
color: var(--button-secondary);
|
||||
border-color: var(--button-secondary);
|
||||
margin-left: 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
payment-iq/app/features/UserRoles/EditUser/EditUser.scss
Normal file
46
payment-iq/app/features/UserRoles/EditUser/EditUser.scss
Normal file
@ -0,0 +1,46 @@
|
||||
.edit-user {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
|
||||
input {
|
||||
flex: 1 1 20%;
|
||||
min-width: 150px;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
outline: none;
|
||||
transition: border-color 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: #0070f3;
|
||||
}
|
||||
}
|
||||
&__button-container {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
flex-basis: 100%;
|
||||
margin-top: 16px;
|
||||
padding: 10px 0;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
width: 100px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:first-child {
|
||||
color: var(--button-primary);
|
||||
border-color: var(--button-primary);
|
||||
}
|
||||
button:last-child {
|
||||
color: var(--button-secondary);
|
||||
border-color: var(--button-secondary);
|
||||
margin-left: 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
payment-iq/app/features/UserRoles/EditUser/EditUser.tsx
Normal file
107
payment-iq/app/features/UserRoles/EditUser/EditUser.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import React from "react";
|
||||
import "./editUser.scss";
|
||||
|
||||
// Union type for form field names
|
||||
export type EditUserField =
|
||||
| "firstName"
|
||||
| "lastName"
|
||||
| "email"
|
||||
| "role"
|
||||
| "phone";
|
||||
|
||||
interface IEditUserForm {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
role: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
const EditUser = () => {
|
||||
const [form, setForm] = React.useState<IEditUserForm>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
role: "",
|
||||
phone: "",
|
||||
});
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const name = e.target.name as EditUserField;
|
||||
const value = e.target.value;
|
||||
if (name === "phone") {
|
||||
const filtered = value.replace(/[^0-9+\-\s()]/g, "");
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
phone: filtered,
|
||||
}));
|
||||
} else {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetForm = () => {
|
||||
setForm({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
role: "",
|
||||
phone: "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="edit-user">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="First Name"
|
||||
name="firstName"
|
||||
value={form.firstName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Last Name"
|
||||
name="lastName"
|
||||
value={form.lastName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Role"
|
||||
name="role"
|
||||
value={form.role}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
type="tel"
|
||||
placeholder="Phone"
|
||||
name="phone"
|
||||
value={form.phone}
|
||||
maxLength={15}
|
||||
pattern="[0-9+\-\s()]*"
|
||||
onChange={handleChange}
|
||||
inputMode="tel"
|
||||
autoComplete="tel"
|
||||
/>
|
||||
<div className="edit-user__button-container">
|
||||
<button type="submit">Save</button>
|
||||
<button type="button" onClick={handleResetForm}>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditUser;
|
||||
6
payment-iq/app/features/UserRoles/User.scss
Normal file
6
payment-iq/app/features/UserRoles/User.scss
Normal file
@ -0,0 +1,6 @@
|
||||
.edit-user {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
@ -18,6 +18,8 @@ import {
|
||||
AdminPanelSettings,
|
||||
History,
|
||||
} from "@mui/icons-material";
|
||||
import { useState } from "react";
|
||||
import EditUser from "./EditUser/EditUser";
|
||||
|
||||
interface Props {
|
||||
username: string;
|
||||
@ -40,12 +42,18 @@ export default function UserRoleCard({
|
||||
roles,
|
||||
extraRolesCount,
|
||||
}: Props) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const handleEditClick = () => {
|
||||
setIsEditing(!isEditing);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card sx={{ mb: 2, minWidth: "100%" }}>
|
||||
<CardContent>
|
||||
{/* Header */}
|
||||
<Stack direction="row" alignItems="center" spacing={2}>
|
||||
<Avatar>{username.slice(0, 2).toUpperCase()}</Avatar>
|
||||
<Avatar>{username?.slice(0, 2).toUpperCase()}</Avatar>
|
||||
<Box flexGrow={1}>
|
||||
<Typography fontWeight="bold">{username}</Typography>
|
||||
<Typography variant="body2">{name}</Typography>
|
||||
@ -58,7 +66,7 @@ export default function UserRoleCard({
|
||||
<History />
|
||||
</IconButton>
|
||||
<Tooltip title="Edit">
|
||||
<IconButton>
|
||||
<IconButton onClick={handleEditClick}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@ -105,13 +113,13 @@ export default function UserRoleCard({
|
||||
{extraRolesCount && <Chip label={`+${extraRolesCount}`} />}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{isEditing && <EditUser />}
|
||||
{/* Footer */}
|
||||
<Box mt={2}>
|
||||
{/* <Box mt={2}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{lastLogin}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
import { useEffect } from 'react';
|
||||
"use client";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function MSWProvider({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
|
||||
const { worker } = require('../../mock/browser.ts');
|
||||
worker.start();
|
||||
if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") {
|
||||
import("../../mock/browser").then(({ worker }) => {
|
||||
// worker.start();
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@ -1,39 +1,43 @@
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
|
||||
const palette = {
|
||||
primary: {
|
||||
main: '#1976d2',
|
||||
main: "#1976d2",
|
||||
},
|
||||
secondary: {
|
||||
main: '#d32f2f',
|
||||
main: "#d32f2f",
|
||||
},
|
||||
background: {
|
||||
default: '#fafafa',
|
||||
paper: '#ffffff',
|
||||
primary: 'rgb(69, 190, 171)',
|
||||
default: "#fafafa",
|
||||
paper: "#ffffff",
|
||||
primary: "rgb(69, 190, 171)",
|
||||
},
|
||||
text: {
|
||||
primary: '#000000',
|
||||
secondary: '#555555',
|
||||
tertiary: '#fff',
|
||||
primary: "#000000",
|
||||
secondary: "#555555",
|
||||
tertiary: "#fff",
|
||||
},
|
||||
button: {
|
||||
primary: "#0070f3",
|
||||
secondary: "##FF00FF",
|
||||
},
|
||||
action: {
|
||||
hover: 'rgba(0, 0, 0, 0.08)',
|
||||
hover: "rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
};
|
||||
|
||||
const typography = {
|
||||
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
|
||||
h1: {
|
||||
fontSize: '3rem',
|
||||
fontSize: "3rem",
|
||||
fontWeight: 700,
|
||||
},
|
||||
h2: {
|
||||
fontSize: '2.5rem',
|
||||
fontSize: "2.5rem",
|
||||
fontWeight: 700,
|
||||
},
|
||||
body1: {
|
||||
fontSize: '1rem',
|
||||
fontSize: "1rem",
|
||||
fontWeight: 400,
|
||||
},
|
||||
};
|
||||
@ -41,8 +45,8 @@ const typography = {
|
||||
// Create the theme based on the light or dark mode preference
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'light', // Change this to 'dark' for dark mode
|
||||
...palette
|
||||
mode: "light", // Change this to 'dark' for dark mode
|
||||
...palette,
|
||||
},
|
||||
// typography,
|
||||
breakpoints: {
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
// // mocks/browser.ts import { setupWorker } from "msw/browser";
|
||||
// import { handlers } from "./handlers";
|
||||
//
|
||||
// export const worker = setupWorker(...handlers);
|
||||
|
||||
import { setupWorker } from 'msw/browser';
|
||||
import { handlers } from './handlers';
|
||||
import { setupWorker } from "msw/browser";
|
||||
import { handlers } from "./handlers";
|
||||
|
||||
export const worker = setupWorker(...handlers);
|
||||
|
||||
61
payment-iq/mock/constants.ts
Normal file
61
payment-iq/mock/constants.ts
Normal file
@ -0,0 +1,61 @@
|
||||
export const users = [
|
||||
{
|
||||
merchantId: 100987998,
|
||||
id: "bc6a8a55-13bc-4538-8255-cd0cec3bb4e9",
|
||||
mame: "Jacob",
|
||||
username: "lspaddy",
|
||||
firstName: "Paddy",
|
||||
lastName: "Man",
|
||||
email: "patrick@omegasys.eu",
|
||||
phone: "",
|
||||
jobTitle: "",
|
||||
enabled: true,
|
||||
authorities: [
|
||||
"ROLE_IIN",
|
||||
"ROLE_FIRST_APPROVER",
|
||||
"ROLE_RULES_ADMIN",
|
||||
"ROLE_TRANSACTION_VIEWER",
|
||||
"ROLE_IIN_ADMIN",
|
||||
"ROLE_USER_PSP_ACCOUNT",
|
||||
],
|
||||
allowedMerchantIds: [100987998],
|
||||
created: "2025-05-04T15:32:48.432Z",
|
||||
disabledBy: null,
|
||||
disabledDate: null,
|
||||
disabledReason: null,
|
||||
incidentNotes: false,
|
||||
lastLogin: "",
|
||||
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
|
||||
marketingNewsletter: false,
|
||||
releaseNotes: false,
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
|
||||
twoFactorCondition: "required",
|
||||
twoFactorCredentials: [],
|
||||
},
|
||||
{
|
||||
merchantId: 100987998,
|
||||
mame: "Jacob",
|
||||
id: "382eed15-1e21-41fa-b1f3-0c1adb3af714",
|
||||
username: "lsterence",
|
||||
firstName: "Terence",
|
||||
lastName: "User",
|
||||
email: "terence@omegasys.eu",
|
||||
phone: "",
|
||||
jobTitle: "",
|
||||
enabled: true,
|
||||
authorities: ["ROLE_IIN", "ROLE_FIRST_APPROVER", "ROLE_RULES_ADMIN"],
|
||||
allowedMerchantIds: [100987998],
|
||||
created: "2025-05-04T15:32:48.432Z",
|
||||
disabledBy: null,
|
||||
disabledDate: null,
|
||||
disabledReason: null,
|
||||
incidentNotes: false,
|
||||
lastLogin: "",
|
||||
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
|
||||
marketingNewsletter: false,
|
||||
releaseNotes: false,
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
|
||||
twoFactorCondition: "required",
|
||||
twoFactorCredentials: [],
|
||||
},
|
||||
];
|
||||
@ -1,72 +1,49 @@
|
||||
// import { http, HttpResponse } from "msw";
|
||||
//
|
||||
// export const handlers = [
|
||||
// http.get(
|
||||
// "https://test-bo.paymentiq.io/paymentiq/backoffice/api/v2/metrics/txsummary",
|
||||
// (req, _res, _ctx) => {
|
||||
// const merchantId = req.url.searchParams.get("merchantId");
|
||||
// const fromDate = req.url.searchParams.get("fromDate");
|
||||
// const toDate = req.url.searchParams.get("toDate");
|
||||
//
|
||||
// console.log(merchantId, fromDate, toDate);
|
||||
//
|
||||
// return HttpResponse.json({
|
||||
// result: {
|
||||
// txCount: { total: 0, successful: 0 },
|
||||
// amount: { value: "0", currency: "EUR" },
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// ),
|
||||
// ];
|
||||
|
||||
|
||||
import { transactionDummyData } from '@/app/features/Pages/transactions/mockData';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { http, HttpResponse } from "msw";
|
||||
|
||||
export const handlers = [
|
||||
// Simple GET endpoint
|
||||
http.get('https://api.example.com/user', () => {
|
||||
return HttpResponse.json({
|
||||
id: 'usr_123',
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
});
|
||||
http.get("https://api.example.com/user", () => {
|
||||
return HttpResponse.json([
|
||||
{
|
||||
id: "usr_123",
|
||||
name: "John Doe",
|
||||
email: "john@example.com",
|
||||
},
|
||||
]);
|
||||
}),
|
||||
|
||||
// POST endpoint with request validation
|
||||
http.post('https://api.example.com/login', async ({ request }) => {
|
||||
const { username, password } = await request.json() as { username: string; password: string };
|
||||
http.post("https://api.example.com/login", async ({ request }) => {
|
||||
const { username, password } = (await request.json()) as {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
if (username === 'admin' && password === 'password123') {
|
||||
if (username === "admin" && password === "password123") {
|
||||
return HttpResponse.json({
|
||||
token: 'mock-jwt-token',
|
||||
user: { id: 'usr_123', name: 'Admin User' }
|
||||
token: "mock-jwt-token",
|
||||
user: { id: "usr_123", name: "Admin User" },
|
||||
});
|
||||
}
|
||||
|
||||
return HttpResponse.json(
|
||||
{ error: 'Invalid credentials' },
|
||||
{ status: 401 }
|
||||
);
|
||||
return HttpResponse.json({ error: "Invalid credentials" }, { status: 401 });
|
||||
}),
|
||||
|
||||
|
||||
// Example with query parameters
|
||||
http.get('https://api.example.com/products', ({ request }) => {
|
||||
http.get("https://api.example.com/products", ({ request }) => {
|
||||
// Parse the URL to access query parameters
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Get query parameters
|
||||
const category = url.searchParams.get('category');
|
||||
const sort = url.searchParams.get('sort') || 'price';
|
||||
const page = url.searchParams.get('page') || '1';
|
||||
const limit = url.searchParams.get('limit') || '10';
|
||||
const category = url.searchParams.get("category");
|
||||
const sort = url.searchParams.get("sort") || "price";
|
||||
const page = url.searchParams.get("page") || "1";
|
||||
const limit = url.searchParams.get("limit") || "10";
|
||||
|
||||
// Validate parameters
|
||||
if (limit && parseInt(limit) > 100) {
|
||||
return HttpResponse.json(
|
||||
{ error: 'Limit cannot exceed 100' },
|
||||
{ error: "Limit cannot exceed 100" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
@ -74,15 +51,15 @@ export const handlers = [
|
||||
// Generate mock response based on parameters
|
||||
const mockProducts = Array.from({ length: parseInt(limit) }, (_, i) => ({
|
||||
id: i + 1,
|
||||
name: `Product ${i + 1}${category ? ` in ${category}` : ''}`,
|
||||
name: `Product ${i + 1}${category ? ` in ${category}` : ""}`,
|
||||
price: Math.floor(Math.random() * 100),
|
||||
category: category || 'general',
|
||||
category: category || "general",
|
||||
}));
|
||||
|
||||
// Sort products if sort parameter provided
|
||||
if (sort === 'price') {
|
||||
if (sort === "price") {
|
||||
mockProducts.sort((a, b) => a.price - b.price);
|
||||
} else if (sort === 'name') {
|
||||
} else if (sort === "name") {
|
||||
mockProducts.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
@ -94,38 +71,38 @@ export const handlers = [
|
||||
sortBy: sort,
|
||||
});
|
||||
}),
|
||||
http.get('https://api.example.com/transactions', ({ request }) => {
|
||||
http.get("https://api.example.com/transactions", ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Get query parameters
|
||||
const userId = url.searchParams.get('userId');
|
||||
const state = url.searchParams.get('state');
|
||||
const statusCode = url.searchParams.get('statusCode');
|
||||
const userId = url.searchParams.get("userId");
|
||||
const state = url.searchParams.get("state");
|
||||
const statusCode = url.searchParams.get("statusCode");
|
||||
|
||||
// Filter transactions based on query parameters
|
||||
let filteredTransactions = [...transactionDummyData];
|
||||
|
||||
if (userId) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.user.toString() === userId
|
||||
(tx) => tx.user.toString() === userId
|
||||
);
|
||||
}
|
||||
|
||||
if (state) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.state.toLowerCase() === state.toLowerCase()
|
||||
(tx) => tx.state.toLowerCase() === state.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
if (statusCode) {
|
||||
filteredTransactions = filteredTransactions.filter(
|
||||
tx => tx.pspStatusCode.toString() === statusCode
|
||||
(tx) => tx.pspStatusCode.toString() === statusCode
|
||||
);
|
||||
}
|
||||
|
||||
return HttpResponse.json({
|
||||
transactions: filteredTransactions,
|
||||
count: filteredTransactions.length
|
||||
count: filteredTransactions.length,
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
@ -3,8 +3,8 @@ import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
webpack: (config) => {
|
||||
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
|
||||
config.resolve.alias['@mswjs/interceptors'] = false;
|
||||
if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") {
|
||||
config.resolve.alias["@mswjs/interceptors"] = false;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
--text-tertiary: #{$text-tertiary};
|
||||
--hover-color: #{$hover-color};
|
||||
--font-family-base: #{$font-family-base};
|
||||
--button-primary: #{$button-primary};
|
||||
--button-secondary: #{$button-secondary};
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
@ -5,6 +5,8 @@ $background-primary: rgb(69, 190, 171);
|
||||
$text-primary: #000000;
|
||||
$text-secondary: #555555;
|
||||
$text-tertiary: #ffffff;
|
||||
$button-primary: #0070f3;
|
||||
$button-secondary: #ff00ff;
|
||||
$hover-color: rgba(0, 0, 0, 0.08);
|
||||
|
||||
$font-family-base: "Roboto", "Helvetica", "Arial", sans-serif;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user