fix(roles): handle errors properly in edit role API
This commit is contained in:
26
node_modules/.vite/deps/_metadata.json
generated
vendored
26
node_modules/.vite/deps/_metadata.json
generated
vendored
@@ -1,59 +1,65 @@
|
||||
{
|
||||
"hash": "ff6fda40",
|
||||
"browserHash": "b50068b3",
|
||||
"browserHash": "c03c03c2",
|
||||
"optimized": {
|
||||
"react/jsx-runtime": {
|
||||
"src": "../../react/jsx-runtime.js",
|
||||
"file": "react_jsx-runtime.js",
|
||||
"fileHash": "ddd613d5",
|
||||
"fileHash": "e8b04feb",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react/jsx-dev-runtime": {
|
||||
"src": "../../react/jsx-dev-runtime.js",
|
||||
"file": "react_jsx-dev-runtime.js",
|
||||
"fileHash": "db7123d4",
|
||||
"fileHash": "778d2d19",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react": {
|
||||
"src": "../../react/index.js",
|
||||
"file": "react.js",
|
||||
"fileHash": "f0784960",
|
||||
"fileHash": "9085912a",
|
||||
"needsInterop": true
|
||||
},
|
||||
"axios": {
|
||||
"src": "../../axios/index.js",
|
||||
"file": "axios.js",
|
||||
"fileHash": "b9f81ab8",
|
||||
"fileHash": "387f77ed",
|
||||
"needsInterop": false
|
||||
},
|
||||
"lucide-react": {
|
||||
"src": "../../lucide-react/dist/esm/lucide-react.mjs",
|
||||
"file": "lucide-react.js",
|
||||
"fileHash": "a5656c81",
|
||||
"fileHash": "63623648",
|
||||
"needsInterop": false
|
||||
},
|
||||
"react-dom/client": {
|
||||
"src": "../../react-dom/client.js",
|
||||
"file": "react-dom_client.js",
|
||||
"fileHash": "ae58904b",
|
||||
"fileHash": "993544a9",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-router-dom": {
|
||||
"src": "../../react-router-dom/dist/index.js",
|
||||
"file": "react-router-dom.js",
|
||||
"fileHash": "420348ee",
|
||||
"fileHash": "d3f984dc",
|
||||
"needsInterop": false
|
||||
},
|
||||
"recharts": {
|
||||
"src": "../../recharts/es6/index.js",
|
||||
"file": "recharts.js",
|
||||
"fileHash": "dafddd35",
|
||||
"fileHash": "e1665e3e",
|
||||
"needsInterop": false
|
||||
},
|
||||
"zustand": {
|
||||
"src": "../../zustand/esm/index.mjs",
|
||||
"file": "zustand.js",
|
||||
"fileHash": "f2ee525b",
|
||||
"fileHash": "8be28924",
|
||||
"needsInterop": false
|
||||
},
|
||||
"zustand/middleware": {
|
||||
"src": "../../zustand/esm/middleware.mjs",
|
||||
"file": "zustand_middleware.js",
|
||||
"fileHash": "936c3138",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
|
||||
479
node_modules/.vite/deps/zustand_middleware.js
generated
vendored
Normal file
479
node_modules/.vite/deps/zustand_middleware.js
generated
vendored
Normal file
@@ -0,0 +1,479 @@
|
||||
import "./chunk-5WWUZCGV.js";
|
||||
|
||||
// node_modules/zustand/esm/middleware.mjs
|
||||
var reduxImpl = (reducer, initial) => (set, _get, api) => {
|
||||
api.dispatch = (action) => {
|
||||
set((state) => reducer(state, action), false, action);
|
||||
return action;
|
||||
};
|
||||
api.dispatchFromDevtools = true;
|
||||
return { dispatch: (...args) => api.dispatch(...args), ...initial };
|
||||
};
|
||||
var redux = reduxImpl;
|
||||
var trackedConnections = /* @__PURE__ */ new Map();
|
||||
var getTrackedConnectionState = (name) => {
|
||||
const api = trackedConnections.get(name);
|
||||
if (!api)
|
||||
return {};
|
||||
return Object.fromEntries(
|
||||
Object.entries(api.stores).map(([key, api2]) => [key, api2.getState()])
|
||||
);
|
||||
};
|
||||
var extractConnectionInformation = (store, extensionConnector, options) => {
|
||||
if (store === void 0) {
|
||||
return {
|
||||
type: "untracked",
|
||||
connection: extensionConnector.connect(options)
|
||||
};
|
||||
}
|
||||
const existingConnection = trackedConnections.get(options.name);
|
||||
if (existingConnection) {
|
||||
return { type: "tracked", store, ...existingConnection };
|
||||
}
|
||||
const newConnection = {
|
||||
connection: extensionConnector.connect(options),
|
||||
stores: {}
|
||||
};
|
||||
trackedConnections.set(options.name, newConnection);
|
||||
return { type: "tracked", store, ...newConnection };
|
||||
};
|
||||
var removeStoreFromTrackedConnections = (name, store) => {
|
||||
if (store === void 0)
|
||||
return;
|
||||
const connectionInfo = trackedConnections.get(name);
|
||||
if (!connectionInfo)
|
||||
return;
|
||||
delete connectionInfo.stores[store];
|
||||
if (Object.keys(connectionInfo.stores).length === 0) {
|
||||
trackedConnections.delete(name);
|
||||
}
|
||||
};
|
||||
var findCallerName = (stack) => {
|
||||
var _a, _b;
|
||||
if (!stack)
|
||||
return void 0;
|
||||
const traceLines = stack.split("\n");
|
||||
const apiSetStateLineIndex = traceLines.findIndex(
|
||||
(traceLine) => traceLine.includes("api.setState")
|
||||
);
|
||||
if (apiSetStateLineIndex < 0)
|
||||
return void 0;
|
||||
const callerLine = ((_a = traceLines[apiSetStateLineIndex + 1]) == null ? void 0 : _a.trim()) || "";
|
||||
return (_b = /.+ (.+) .+/.exec(callerLine)) == null ? void 0 : _b[1];
|
||||
};
|
||||
var devtoolsImpl = (fn, devtoolsOptions = {}) => (set, get, api) => {
|
||||
const { enabled, anonymousActionType, store, ...options } = devtoolsOptions;
|
||||
let extensionConnector;
|
||||
try {
|
||||
extensionConnector = (enabled != null ? enabled : (import.meta.env ? import.meta.env.MODE : void 0) !== "production") && window.__REDUX_DEVTOOLS_EXTENSION__;
|
||||
} catch (e) {
|
||||
}
|
||||
if (!extensionConnector) {
|
||||
return fn(set, get, api);
|
||||
}
|
||||
const { connection, ...connectionInformation } = extractConnectionInformation(store, extensionConnector, options);
|
||||
let isRecording = true;
|
||||
api.setState = (state, replace, nameOrAction) => {
|
||||
const r = set(state, replace);
|
||||
if (!isRecording)
|
||||
return r;
|
||||
const action = nameOrAction === void 0 ? {
|
||||
type: anonymousActionType || findCallerName(new Error().stack) || "anonymous"
|
||||
} : typeof nameOrAction === "string" ? { type: nameOrAction } : nameOrAction;
|
||||
if (store === void 0) {
|
||||
connection == null ? void 0 : connection.send(action, get());
|
||||
return r;
|
||||
}
|
||||
connection == null ? void 0 : connection.send(
|
||||
{
|
||||
...action,
|
||||
type: `${store}/${action.type}`
|
||||
},
|
||||
{
|
||||
...getTrackedConnectionState(options.name),
|
||||
[store]: api.getState()
|
||||
}
|
||||
);
|
||||
return r;
|
||||
};
|
||||
api.devtools = {
|
||||
cleanup: () => {
|
||||
if (connection && typeof connection.unsubscribe === "function") {
|
||||
connection.unsubscribe();
|
||||
}
|
||||
removeStoreFromTrackedConnections(options.name, store);
|
||||
}
|
||||
};
|
||||
const setStateFromDevtools = (...a) => {
|
||||
const originalIsRecording = isRecording;
|
||||
isRecording = false;
|
||||
set(...a);
|
||||
isRecording = originalIsRecording;
|
||||
};
|
||||
const initialState = fn(api.setState, get, api);
|
||||
if (connectionInformation.type === "untracked") {
|
||||
connection == null ? void 0 : connection.init(initialState);
|
||||
} else {
|
||||
connectionInformation.stores[connectionInformation.store] = api;
|
||||
connection == null ? void 0 : connection.init(
|
||||
Object.fromEntries(
|
||||
Object.entries(connectionInformation.stores).map(([key, store2]) => [
|
||||
key,
|
||||
key === connectionInformation.store ? initialState : store2.getState()
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
if (api.dispatchFromDevtools && typeof api.dispatch === "function") {
|
||||
let didWarnAboutReservedActionType = false;
|
||||
const originalDispatch = api.dispatch;
|
||||
api.dispatch = (...args) => {
|
||||
if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && args[0].type === "__setState" && !didWarnAboutReservedActionType) {
|
||||
console.warn(
|
||||
'[zustand devtools middleware] "__setState" action type is reserved to set state from the devtools. Avoid using it.'
|
||||
);
|
||||
didWarnAboutReservedActionType = true;
|
||||
}
|
||||
originalDispatch(...args);
|
||||
};
|
||||
}
|
||||
connection.subscribe((message) => {
|
||||
var _a;
|
||||
switch (message.type) {
|
||||
case "ACTION":
|
||||
if (typeof message.payload !== "string") {
|
||||
console.error(
|
||||
"[zustand devtools middleware] Unsupported action format"
|
||||
);
|
||||
return;
|
||||
}
|
||||
return parseJsonThen(
|
||||
message.payload,
|
||||
(action) => {
|
||||
if (action.type === "__setState") {
|
||||
if (store === void 0) {
|
||||
setStateFromDevtools(action.state);
|
||||
return;
|
||||
}
|
||||
if (Object.keys(action.state).length !== 1) {
|
||||
console.error(
|
||||
`
|
||||
[zustand devtools middleware] Unsupported __setState action format.
|
||||
When using 'store' option in devtools(), the 'state' should have only one key, which is a value of 'store' that was passed in devtools(),
|
||||
and value of this only key should be a state object. Example: { "type": "__setState", "state": { "abc123Store": { "foo": "bar" } } }
|
||||
`
|
||||
);
|
||||
}
|
||||
const stateFromDevtools = action.state[store];
|
||||
if (stateFromDevtools === void 0 || stateFromDevtools === null) {
|
||||
return;
|
||||
}
|
||||
if (JSON.stringify(api.getState()) !== JSON.stringify(stateFromDevtools)) {
|
||||
setStateFromDevtools(stateFromDevtools);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!api.dispatchFromDevtools)
|
||||
return;
|
||||
if (typeof api.dispatch !== "function")
|
||||
return;
|
||||
api.dispatch(action);
|
||||
}
|
||||
);
|
||||
case "DISPATCH":
|
||||
switch (message.payload.type) {
|
||||
case "RESET":
|
||||
setStateFromDevtools(initialState);
|
||||
if (store === void 0) {
|
||||
return connection == null ? void 0 : connection.init(api.getState());
|
||||
}
|
||||
return connection == null ? void 0 : connection.init(getTrackedConnectionState(options.name));
|
||||
case "COMMIT":
|
||||
if (store === void 0) {
|
||||
connection == null ? void 0 : connection.init(api.getState());
|
||||
return;
|
||||
}
|
||||
return connection == null ? void 0 : connection.init(getTrackedConnectionState(options.name));
|
||||
case "ROLLBACK":
|
||||
return parseJsonThen(message.state, (state) => {
|
||||
if (store === void 0) {
|
||||
setStateFromDevtools(state);
|
||||
connection == null ? void 0 : connection.init(api.getState());
|
||||
return;
|
||||
}
|
||||
setStateFromDevtools(state[store]);
|
||||
connection == null ? void 0 : connection.init(getTrackedConnectionState(options.name));
|
||||
});
|
||||
case "JUMP_TO_STATE":
|
||||
case "JUMP_TO_ACTION":
|
||||
return parseJsonThen(message.state, (state) => {
|
||||
if (store === void 0) {
|
||||
setStateFromDevtools(state);
|
||||
return;
|
||||
}
|
||||
if (JSON.stringify(api.getState()) !== JSON.stringify(state[store])) {
|
||||
setStateFromDevtools(state[store]);
|
||||
}
|
||||
});
|
||||
case "IMPORT_STATE": {
|
||||
const { nextLiftedState } = message.payload;
|
||||
const lastComputedState = (_a = nextLiftedState.computedStates.slice(-1)[0]) == null ? void 0 : _a.state;
|
||||
if (!lastComputedState)
|
||||
return;
|
||||
if (store === void 0) {
|
||||
setStateFromDevtools(lastComputedState);
|
||||
} else {
|
||||
setStateFromDevtools(lastComputedState[store]);
|
||||
}
|
||||
connection == null ? void 0 : connection.send(
|
||||
null,
|
||||
// FIXME no-any
|
||||
nextLiftedState
|
||||
);
|
||||
return;
|
||||
}
|
||||
case "PAUSE_RECORDING":
|
||||
return isRecording = !isRecording;
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
return initialState;
|
||||
};
|
||||
var devtools = devtoolsImpl;
|
||||
var parseJsonThen = (stringified, fn) => {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(stringified);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"[zustand devtools middleware] Could not parse the received json",
|
||||
e
|
||||
);
|
||||
}
|
||||
if (parsed !== void 0)
|
||||
fn(parsed);
|
||||
};
|
||||
var subscribeWithSelectorImpl = (fn) => (set, get, api) => {
|
||||
const origSubscribe = api.subscribe;
|
||||
api.subscribe = (selector, optListener, options) => {
|
||||
let listener = selector;
|
||||
if (optListener) {
|
||||
const equalityFn = (options == null ? void 0 : options.equalityFn) || Object.is;
|
||||
let currentSlice = selector(api.getState());
|
||||
listener = (state) => {
|
||||
const nextSlice = selector(state);
|
||||
if (!equalityFn(currentSlice, nextSlice)) {
|
||||
const previousSlice = currentSlice;
|
||||
optListener(currentSlice = nextSlice, previousSlice);
|
||||
}
|
||||
};
|
||||
if (options == null ? void 0 : options.fireImmediately) {
|
||||
optListener(currentSlice, currentSlice);
|
||||
}
|
||||
}
|
||||
return origSubscribe(listener);
|
||||
};
|
||||
const initialState = fn(set, get, api);
|
||||
return initialState;
|
||||
};
|
||||
var subscribeWithSelector = subscribeWithSelectorImpl;
|
||||
function combine(initialState, create) {
|
||||
return (...args) => Object.assign({}, initialState, create(...args));
|
||||
}
|
||||
function createJSONStorage(getStorage, options) {
|
||||
let storage;
|
||||
try {
|
||||
storage = getStorage();
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
const persistStorage = {
|
||||
getItem: (name) => {
|
||||
var _a;
|
||||
const parse = (str2) => {
|
||||
if (str2 === null) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(str2, options == null ? void 0 : options.reviver);
|
||||
};
|
||||
const str = (_a = storage.getItem(name)) != null ? _a : null;
|
||||
if (str instanceof Promise) {
|
||||
return str.then(parse);
|
||||
}
|
||||
return parse(str);
|
||||
},
|
||||
setItem: (name, newValue) => storage.setItem(name, JSON.stringify(newValue, options == null ? void 0 : options.replacer)),
|
||||
removeItem: (name) => storage.removeItem(name)
|
||||
};
|
||||
return persistStorage;
|
||||
}
|
||||
var toThenable = (fn) => (input) => {
|
||||
try {
|
||||
const result = fn(input);
|
||||
if (result instanceof Promise) {
|
||||
return result;
|
||||
}
|
||||
return {
|
||||
then(onFulfilled) {
|
||||
return toThenable(onFulfilled)(result);
|
||||
},
|
||||
catch(_onRejected) {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
then(_onFulfilled) {
|
||||
return this;
|
||||
},
|
||||
catch(onRejected) {
|
||||
return toThenable(onRejected)(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
var persistImpl = (config, baseOptions) => (set, get, api) => {
|
||||
let options = {
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => state,
|
||||
version: 0,
|
||||
merge: (persistedState, currentState) => ({
|
||||
...currentState,
|
||||
...persistedState
|
||||
}),
|
||||
...baseOptions
|
||||
};
|
||||
let hasHydrated = false;
|
||||
const hydrationListeners = /* @__PURE__ */ new Set();
|
||||
const finishHydrationListeners = /* @__PURE__ */ new Set();
|
||||
let storage = options.storage;
|
||||
if (!storage) {
|
||||
return config(
|
||||
(...args) => {
|
||||
console.warn(
|
||||
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
|
||||
);
|
||||
set(...args);
|
||||
},
|
||||
get,
|
||||
api
|
||||
);
|
||||
}
|
||||
const setItem = () => {
|
||||
const state = options.partialize({ ...get() });
|
||||
return storage.setItem(options.name, {
|
||||
state,
|
||||
version: options.version
|
||||
});
|
||||
};
|
||||
const savedSetState = api.setState;
|
||||
api.setState = (state, replace) => {
|
||||
savedSetState(state, replace);
|
||||
return setItem();
|
||||
};
|
||||
const configResult = config(
|
||||
(...args) => {
|
||||
set(...args);
|
||||
return setItem();
|
||||
},
|
||||
get,
|
||||
api
|
||||
);
|
||||
api.getInitialState = () => configResult;
|
||||
let stateFromStorage;
|
||||
const hydrate = () => {
|
||||
var _a, _b;
|
||||
if (!storage)
|
||||
return;
|
||||
hasHydrated = false;
|
||||
hydrationListeners.forEach((cb) => {
|
||||
var _a2;
|
||||
return cb((_a2 = get()) != null ? _a2 : configResult);
|
||||
});
|
||||
const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0;
|
||||
return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
|
||||
if (deserializedStorageValue) {
|
||||
if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
|
||||
if (options.migrate) {
|
||||
const migration = options.migrate(
|
||||
deserializedStorageValue.state,
|
||||
deserializedStorageValue.version
|
||||
);
|
||||
if (migration instanceof Promise) {
|
||||
return migration.then((result) => [true, result]);
|
||||
}
|
||||
return [true, migration];
|
||||
}
|
||||
console.error(
|
||||
`State loaded from storage couldn't be migrated since no migrate function was provided`
|
||||
);
|
||||
} else {
|
||||
return [false, deserializedStorageValue.state];
|
||||
}
|
||||
}
|
||||
return [false, void 0];
|
||||
}).then((migrationResult) => {
|
||||
var _a2;
|
||||
const [migrated, migratedState] = migrationResult;
|
||||
stateFromStorage = options.merge(
|
||||
migratedState,
|
||||
(_a2 = get()) != null ? _a2 : configResult
|
||||
);
|
||||
set(stateFromStorage, true);
|
||||
if (migrated) {
|
||||
return setItem();
|
||||
}
|
||||
}).then(() => {
|
||||
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
|
||||
stateFromStorage = get();
|
||||
hasHydrated = true;
|
||||
finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
|
||||
}).catch((e) => {
|
||||
postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
|
||||
});
|
||||
};
|
||||
api.persist = {
|
||||
setOptions: (newOptions) => {
|
||||
options = {
|
||||
...options,
|
||||
...newOptions
|
||||
};
|
||||
if (newOptions.storage) {
|
||||
storage = newOptions.storage;
|
||||
}
|
||||
},
|
||||
clearStorage: () => {
|
||||
storage == null ? void 0 : storage.removeItem(options.name);
|
||||
},
|
||||
getOptions: () => options,
|
||||
rehydrate: () => hydrate(),
|
||||
hasHydrated: () => hasHydrated,
|
||||
onHydrate: (cb) => {
|
||||
hydrationListeners.add(cb);
|
||||
return () => {
|
||||
hydrationListeners.delete(cb);
|
||||
};
|
||||
},
|
||||
onFinishHydration: (cb) => {
|
||||
finishHydrationListeners.add(cb);
|
||||
return () => {
|
||||
finishHydrationListeners.delete(cb);
|
||||
};
|
||||
}
|
||||
};
|
||||
if (!options.skipHydration) {
|
||||
hydrate();
|
||||
}
|
||||
return stateFromStorage || configResult;
|
||||
};
|
||||
var persist = persistImpl;
|
||||
export {
|
||||
combine,
|
||||
createJSONStorage,
|
||||
devtools,
|
||||
persist,
|
||||
redux,
|
||||
subscribeWithSelector
|
||||
};
|
||||
//# sourceMappingURL=zustand_middleware.js.map
|
||||
7
node_modules/.vite/deps/zustand_middleware.js.map
generated
vendored
Normal file
7
node_modules/.vite/deps/zustand_middleware.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@ import {
|
||||
Settings,
|
||||
Menu,
|
||||
X,
|
||||
Users,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
|
||||
@@ -14,6 +15,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
||||
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
|
||||
{ name: 'Transactions', href: '/transactions', icon: CreditCard },
|
||||
{ name: 'Roles', href: '/roles', icon: Shield },
|
||||
{ name: 'Users', href: '/users', icon: Users },
|
||||
{ name: 'Settings', href: '/settings', icon: Settings },
|
||||
];
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ const Login = () => {
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Payment Admin Panel
|
||||
Khalij pay Admin Panel
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">
|
||||
Sign in to your account
|
||||
@@ -136,9 +136,7 @@ const Login = () => {
|
||||
<div className="w-full border-t border-gray-300 dark:border-gray-600" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400">
|
||||
Demo Credentials
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import DataTable from '../components/DataTable';
|
||||
import { rolesAPI } from '../services/api';
|
||||
import { rolesAPI, listPermissions } from '../services/api';
|
||||
import { Plus, Trash2, Search, Pencil } from 'lucide-react';
|
||||
|
||||
const Roles = () => {
|
||||
@@ -9,25 +9,13 @@ const Roles = () => {
|
||||
const [error, setError] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
const [permissionsInput, setPermissionsInput] = useState('');
|
||||
const [userType, setUserType] = useState('');
|
||||
const [nameFilter, setNameFilter] = useState('');
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [editingRole, setEditingRole] = useState(null);
|
||||
const [selectedPermissions, setSelectedPermissions] = useState([]);
|
||||
|
||||
const permissionOptions = [
|
||||
'Administrator',
|
||||
'UserManagement',
|
||||
'AddUser',
|
||||
'EditUser',
|
||||
'UserPasswordManagement',
|
||||
'UserRoleManagement',
|
||||
'RoleManagement',
|
||||
'AddRole',
|
||||
'EditRole',
|
||||
'DeleteRole',
|
||||
];
|
||||
const [permissionOptions, setPermissionOptions] = useState([]);
|
||||
|
||||
const fetchRoles = async (q = '') => {
|
||||
try {
|
||||
@@ -43,6 +31,14 @@ const Roles = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchRoles();
|
||||
(async () => {
|
||||
try {
|
||||
const perms = await listPermissions();
|
||||
setPermissionOptions(Array.isArray(perms) ? perms : []);
|
||||
} catch (_) {
|
||||
setPermissionOptions([]);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const onAddRole = async (e) => {
|
||||
@@ -52,10 +48,9 @@ const Roles = () => {
|
||||
? selectedPermissions
|
||||
: permissionsInput.split(',').map(p => p.trim()).filter(Boolean);
|
||||
try {
|
||||
await rolesAPI.create({ name: name.trim(), permissions: perms, userType: userType.trim() });
|
||||
await rolesAPI.create({ name: name.trim(), permissions: perms });
|
||||
setName('');
|
||||
setPermissionsInput('');
|
||||
setUserType('');
|
||||
setSelectedPermissions([]);
|
||||
await fetchRoles(nameFilter);
|
||||
setIsModalOpen(false);
|
||||
@@ -72,7 +67,6 @@ const Roles = () => {
|
||||
const openEdit = (role) => {
|
||||
setEditingRole(role);
|
||||
setName(role.name || '');
|
||||
setUserType(role.userType || '');
|
||||
setSelectedPermissions(Array.isArray(role.permissions) ? role.permissions : []);
|
||||
setPermissionsInput('');
|
||||
setIsEditModalOpen(true);
|
||||
@@ -85,13 +79,12 @@ const Roles = () => {
|
||||
? selectedPermissions
|
||||
: permissionsInput.split(',').map(p => p.trim()).filter(Boolean);
|
||||
try {
|
||||
await rolesAPI.update(editingRole.id, { name: name.trim(), permissions: perms, userType: userType.trim() });
|
||||
await rolesAPI.update(editingRole.id, { name: name.trim(), permissions: perms });
|
||||
await fetchRoles(nameFilter);
|
||||
setIsEditModalOpen(false);
|
||||
setEditingRole(null);
|
||||
setName('');
|
||||
setPermissionsInput('');
|
||||
setUserType('');
|
||||
setSelectedPermissions([]);
|
||||
} catch (_) {
|
||||
setError('Failed to update role');
|
||||
@@ -103,7 +96,7 @@ const Roles = () => {
|
||||
{ key: 'permissions', header: 'Permissions', render: (val) => Array.isArray(val) ? val.join(', ') : '' },
|
||||
{ key: 'userType', header: 'User Type' },
|
||||
{ key: 'actions', header: 'Actions', render: (_val, row) => (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-x-2 ">
|
||||
<button
|
||||
onClick={() => openEdit(row)}
|
||||
className="inline-flex items-center px-3 py-1 text-sm rounded-md bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 hover:opacity-90"
|
||||
@@ -146,7 +139,7 @@ const Roles = () => {
|
||||
<div className="w-full max-w-lg card p-6 relative">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Add Role</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Create a new role with permissions and user type</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Create a new role with permissions</p>
|
||||
</div>
|
||||
<form className="space-y-4" onSubmit={onAddRole}>
|
||||
<div>
|
||||
@@ -162,22 +155,29 @@ const Roles = () => {
|
||||
<label className="block text-sm mb-2 text-gray-700 dark:text-gray-300">Permissions</label>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{permissionOptions.map((perm) => {
|
||||
const checked = selectedPermissions.includes(perm);
|
||||
const permName = typeof perm === 'string' ? perm : perm?.name;
|
||||
const permDesc = typeof perm === 'object' ? perm?.description : undefined;
|
||||
const checked = selectedPermissions.includes(permName);
|
||||
return (
|
||||
<label key={perm} className="inline-flex items-center space-x-2 space-x-reverse justify-start">
|
||||
<label key={permName} className="inline-flex items-center gap-x-2 space-x-reverse justify-start">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-primary-600 border-gray-300 rounded"
|
||||
checked={checked}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedPermissions((prev) => [...prev, perm]);
|
||||
setSelectedPermissions((prev) => [...prev, permName]);
|
||||
} else {
|
||||
setSelectedPermissions((prev) => prev.filter(p => p !== perm));
|
||||
setSelectedPermissions((prev) => prev.filter(p => p !== permName));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">{perm}</span>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300 mx-2">
|
||||
{permName}
|
||||
{permDesc ? (
|
||||
<span className="ml-2 text-xs text-gray-500 dark:text-gray-400 block">{permDesc}</span>
|
||||
) : null}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
@@ -186,7 +186,7 @@ const Roles = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedPermissions([])}
|
||||
className="text-xs text-gray-600 dark:text-gray-300 hover:underline"
|
||||
className="text-xs text-blue-600 dark:text-gray-300 hover:underline"
|
||||
>
|
||||
Clear selection
|
||||
</button>
|
||||
@@ -201,16 +201,8 @@ const Roles = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm mb-1 text-gray-700 dark:text-gray-300">User Type</label>
|
||||
<input
|
||||
value={userType}
|
||||
onChange={(e) => setUserType(e.target.value)}
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="e.g., internal or external"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
@@ -239,7 +231,7 @@ const Roles = () => {
|
||||
<div className="w-full max-w-lg card p-6 relative">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Edit Role</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Update role name, permissions and user type</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Update role name and permissions</p>
|
||||
</div>
|
||||
<form className="space-y-4" onSubmit={onUpdateRole}>
|
||||
<div>
|
||||
@@ -255,22 +247,29 @@ const Roles = () => {
|
||||
<label className="block text-sm mb-2 text-gray-700 dark:text-gray-300">Permissions</label>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{permissionOptions.map((perm) => {
|
||||
const checked = selectedPermissions.includes(perm);
|
||||
const permName = typeof perm === 'string' ? perm : perm?.name;
|
||||
const permDesc = typeof perm === 'object' ? perm?.description : undefined;
|
||||
const checked = selectedPermissions.includes(permName);
|
||||
return (
|
||||
<label key={perm} className="inline-flex items-center space-x-2 space-x-reverse justify-start">
|
||||
<label key={permName} className="inline-flex items-center space-x-reverse justify-start">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-primary-600 border-gray-300 rounded"
|
||||
checked={checked}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedPermissions((prev) => [...prev, perm]);
|
||||
setSelectedPermissions((prev) => [...prev, permName]);
|
||||
} else {
|
||||
setSelectedPermissions((prev) => prev.filter(p => p !== perm));
|
||||
setSelectedPermissions((prev) => prev.filter(p => p !== permName));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">{perm}</span>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300 mx-2">
|
||||
{permName}
|
||||
{permDesc ? (
|
||||
<span className="ml-2 text-xs text-gray-500 dark:text-gray-400 block">{permDesc}</span>
|
||||
) : null}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
@@ -279,7 +278,7 @@ const Roles = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedPermissions([])}
|
||||
className="text-xs text-gray-600 dark:text-gray-300 hover:underline"
|
||||
className="text-xs text-blue-600 dark:text-gray-300 hover:underline"
|
||||
>
|
||||
Clear selection
|
||||
</button>
|
||||
@@ -294,16 +293,8 @@ const Roles = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm mb-1 text-gray-700 dark:text-gray-300">User Type</label>
|
||||
<input
|
||||
value={userType}
|
||||
onChange={(e) => setUserType(e.target.value)}
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="e.g., internal or external"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsEditModalOpen(false)}
|
||||
|
||||
7
src/pages/Users.jsx
Normal file
7
src/pages/Users.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Users() {
|
||||
return (
|
||||
<div>Users</div>
|
||||
)
|
||||
}
|
||||
@@ -216,9 +216,15 @@ export const rolesAPI = {
|
||||
// prevent global 401 redirect for optional fetch
|
||||
skipAuthRedirect: true,
|
||||
});
|
||||
const data = res?.data;
|
||||
// Try common shapes: { items: Role[], total: number } or Role[]
|
||||
const items = Array.isArray(data) ? data : (Array.isArray(data?.items) ? data.items : []);
|
||||
const raw = res?.data;
|
||||
// Backend shape example:
|
||||
// { data: { filterSummary: {...}, data: Role[] }, isSuccess: true, ... }
|
||||
const apiItems = Array.isArray(raw?.data?.data) ? raw.data.data : undefined;
|
||||
// Fallbacks for other shapes we may encounter
|
||||
const items = apiItems
|
||||
|| (Array.isArray(raw) ? raw : undefined)
|
||||
|| (Array.isArray(raw?.items) ? raw.items : [])
|
||||
|| [];
|
||||
// Sync a snapshot to local storage for offline UX
|
||||
writeRolesToStorage(items.map(r => ({ id: r.id || crypto.randomUUID(), ...r })));
|
||||
return items;
|
||||
@@ -236,7 +242,7 @@ export const rolesAPI = {
|
||||
const payload = {
|
||||
name: String(role?.name || '').trim(),
|
||||
permissions: Array.isArray(role?.permissions) ? role.permissions : [],
|
||||
userType: String(role?.userType || '').trim(),
|
||||
// userType: String(role?.userType || '').trim(),
|
||||
};
|
||||
let created = null;
|
||||
try {
|
||||
@@ -269,16 +275,31 @@ export const rolesAPI = {
|
||||
},
|
||||
|
||||
async update(id, role) {
|
||||
const existingLocal = readRolesFromStorage().find(r => r.id === id) || {};
|
||||
const payload = {
|
||||
name: String(role?.name || '').trim(),
|
||||
permissions: Array.isArray(role?.permissions) ? role.permissions : [],
|
||||
userType: String(role?.userType || '').trim(),
|
||||
...(role?.userType !== undefined
|
||||
? { userType: String(role?.userType || '').trim() }
|
||||
: (existingLocal.userType ? { userType: existingLocal.userType } : {})),
|
||||
};
|
||||
|
||||
try {
|
||||
await api.put(`/api/v1/Role/${encodeURIComponent(id)}`, payload, { skipAuthRedirect: true });
|
||||
} catch (_) {
|
||||
// ignore; apply optimistic local update
|
||||
} catch (err) {
|
||||
const status = err?.statusCode || err?.status || err?.response?.status;
|
||||
if (status === 404 || status === 405 || status === 400) {
|
||||
try {
|
||||
await api.put('/api/v1/Role', { id, ...payload }, { skipAuthRedirect: true });
|
||||
} catch (e2) {
|
||||
const s2 = e2?.statusCode || e2?.status || e2?.response?.status;
|
||||
if (s2 === 401 || s2 === 403) {
|
||||
throw { message: 'Unauthorized to edit roles (401/403). Check permissions/login.', status: s2 };
|
||||
}
|
||||
}
|
||||
} else if (status === 401 || status === 403) {
|
||||
throw { message: 'Unauthorized to edit roles (401/403). Check permissions/login.', status };
|
||||
}
|
||||
}
|
||||
|
||||
const roles = readRolesFromStorage();
|
||||
@@ -296,3 +317,46 @@ export const rolesAPI = {
|
||||
return updated;
|
||||
},
|
||||
};
|
||||
|
||||
// -----------------------------
|
||||
// Permissions API
|
||||
// -----------------------------
|
||||
export async function listPermissions() {
|
||||
try {
|
||||
const res = await api.get('/api/v1/General/Permission', { skipAuthRedirect: true });
|
||||
const raw = res?.data;
|
||||
// Expected shape: { data: Permission[] }
|
||||
const items = Array.isArray(raw?.data) ? raw.data : [];
|
||||
// Prefer returning structured objects with name and description
|
||||
const objs = items
|
||||
.map((p) => {
|
||||
const name = typeof p?.name === 'string' ? p.name : (typeof p?.displayName === 'string' ? p.displayName : null);
|
||||
if (!name) return null;
|
||||
const description = typeof p?.description === 'string' && p.description
|
||||
? p.description
|
||||
: (typeof p?.displayName === 'string' ? p.displayName : name);
|
||||
return { name, description };
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (objs.length > 0) return objs;
|
||||
// Fallback to simple names if server returned unexpected shape
|
||||
const names = items
|
||||
.map((p) => (typeof p?.name === 'string' ? p.name : (typeof p?.displayName === 'string' ? p.displayName : null)))
|
||||
.filter(Boolean);
|
||||
return names.map((n) => ({ name: n, description: n }));
|
||||
} catch (_) {
|
||||
// Fallback to a minimal static list to keep UI usable
|
||||
return [
|
||||
{ name: 'Administrator', description: 'Full access to administrative features' },
|
||||
{ name: 'UserManagement', description: 'Manage users and their profiles' },
|
||||
{ name: 'AddUser', description: 'Create new user accounts' },
|
||||
{ name: 'EditUser', description: 'Edit existing user accounts' },
|
||||
{ name: 'UserPasswordManagement', description: 'Reset or change user passwords' },
|
||||
{ name: 'UserRoleManagement', description: 'Assign or modify user roles' },
|
||||
{ name: 'RoleManagement', description: 'Manage roles and permissions' },
|
||||
{ name: 'AddRole', description: 'Create new roles' },
|
||||
{ name: 'EditRole', description: 'Edit existing roles' },
|
||||
{ name: 'DeleteRole', description: 'Remove roles from the system' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
export const useAuthStore = create((set) => ({
|
||||
isLoggedIn: false,
|
||||
loading: false,
|
||||
setLoggedIn: (status) => set({ isLoggedIn: status }),
|
||||
setLoading: (status) => set({ loading: status }),
|
||||
}));
|
||||
export const useAuthStore = create(
|
||||
persist(
|
||||
(set) => ({
|
||||
isLoggedIn: false,
|
||||
loading: false,
|
||||
setLoggedIn: (status) => set({ isLoggedIn: status }),
|
||||
setLoading: (status) => set({ loading: status }),
|
||||
}),
|
||||
{
|
||||
name: "auth_store_v1",
|
||||
partialize: (state) => ({ isLoggedIn: state.isLoggedIn }),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user