Added Edit Uder in Admin

This commit is contained in:
Mitchell Magro 2025-07-09 14:41:51 +02:00
parent 2864bf8cdc
commit fc4be718f7
21 changed files with 521 additions and 337 deletions

View 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: [],
},
]);
}

View File

@ -1,25 +1,25 @@
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
);
}
if (user) {
filteredTransactions = filteredTransactions.filter(
(tx) => tx.user.toString() === user
);
}
if (state) {
filteredTransactions = filteredTransactions.filter(
tx => tx.state.toLowerCase() === state.toLowerCase()
);
}
if (state) {
filteredTransactions = filteredTransactions.filter(
(tx) => tx.state.toLowerCase() === state.toLowerCase()
);
}
return NextResponse.json(filteredTransactions);
}

View File

@ -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,
}
];

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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 }}>

View File

@ -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
}}
/>

View 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;
}
}
}

View 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;
}
}
}

View 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;

View File

@ -0,0 +1,6 @@
.edit-user {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}

View File

@ -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>
);

View File

@ -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();
});
}
}, []);

View File

@ -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: {

View File

@ -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);

View 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: [],
},
];

View File

@ -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,
});
}),
];

View File

@ -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;
},

View File

@ -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) {

View File

@ -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;