diff --git a/Makefile b/Makefile index 37171ce..379ab95 100644 --- a/Makefile +++ b/Makefile @@ -55,16 +55,23 @@ clean: ## Clean node_modules, dist and Docker artifacts # Docker Setup setup: ## Setup Docker buildx for multi-platform builds - docker buildx create --name nice-builder --driver docker-container --use || docker buildx use nice-builder - docker buildx inspect --bootstrap + @docker buildx use nice-builder 2>/dev/null || docker buildx create --name nice-builder --driver docker-container --use + @docker buildx inspect --bootstrap # Docker Build targets -docker-build: setup ## Build multi-platform Docker image locally +# docker-build: setup ## Build Docker image locally (single platform) +# docker buildx build --platform linux/amd64 \ +# -f docker/Dockerfile \ +# -t $(DOCKER_IMAGE):$(DOCKER_TAG) \ +# -t $(DOCKER_IMAGE):latest \ +# --load . + +docker-build: setup ## Build multi-platform Docker image (no local load) docker buildx build --platform $(PLATFORMS) \ -f docker/Dockerfile \ -t $(DOCKER_IMAGE):$(DOCKER_TAG) \ -t $(DOCKER_IMAGE):latest \ - --load . + --output type=image,name=$(DOCKER_IMAGE):$(DOCKER_TAG),push=false . docker-push: setup ## Build and push multi-platform image to registry docker buildx build --platform $(PLATFORMS) \ diff --git a/docker/Dockerfile b/docker/Dockerfile index 3294c7f..5caf1b0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ # Multi-stage build for React Vite application # Multi-platform support for AMD64 and ARM64 -FROM --platform=$BUILDPLATFORM node:20-alpine3.18 AS builder +FROM --platform=$BUILDPLATFORM node:20-alpine AS builder # Install git (required for pnpm) RUN apk add --no-cache git diff --git a/src/entities/account/model/types.ts b/src/entities/account/model/types.ts index dd6e81e..f7f28c6 100644 --- a/src/entities/account/model/types.ts +++ b/src/entities/account/model/types.ts @@ -1,3 +1,5 @@ +import { UserMenuPermissionData } from '@/entities/user/model/types'; + export enum AccountTabKeys { UserManage = 'UserManage', PasswordManage = 'PasswordManage', @@ -11,40 +13,53 @@ export enum AccountUserTabKeys { }; export interface AccountUserTabProps { activeTab: AccountUserTabKeys; - tid: string; mid?: string; usrid?: string; + idCl?: string; + status?: string; }; -export interface AuthItem { - useYn?: boolean; - authName?: string; - tid?: string; -}; + export interface UserManageAuthListProps { - userItems: Array; + userItems: Array; mid: string; }; -export interface UserManageAuthItemProps extends AuthItem { +export interface UserManageAuthItemProps { usrid?: string; mid?: string; + idCl?: string; + status?: string; }; export interface UserLoginAuthInfoWrapProps { mid: string; usrid: string; }; export interface UserAccountAuthWrapProps { - tid: string; + mid: string; + usrid: string; + idCl: string; + status: string; }; export interface PermItem { menuId?: string; - permName?: string; + menuName?: string; + subMenu?: Array; }; export interface UserAccountAuthPermListProps { - tid: string; - permItems: Array; + mid: string; + usrid: string; + idCl: string; + status: string; + menuItems: Array; + menuGrants: Array; }; export interface UserAccountAuthPermItemProps extends PermItem { - tid: string; + mid: string; + usrid: string; + idCl: string; + status: string; + menuName: string; + subMenu: Array; + menuGrants: Array; }; export interface VerificationItem { diff --git a/src/entities/account/ui/account-user-tab.tsx b/src/entities/account/ui/account-user-tab.tsx index 8cd9f97..a6c84dd 100644 --- a/src/entities/account/ui/account-user-tab.tsx +++ b/src/entities/account/ui/account-user-tab.tsx @@ -7,9 +7,10 @@ import { export const AccountUserTab = ({ activeTab, - tid, mid, - usrid + usrid, + idCl, + status, }: AccountUserTabProps) => { const { navigate } = useNavigate(); @@ -18,18 +19,20 @@ export const AccountUserTab = ({ if(tab === AccountUserTabKeys.LoginAuthInfo){ navigate(PATHS.account.user.loginAuthInfo, { state: { - tid: tid, mid: mid, - usrid: usrid + usrid: usrid, + idCl: idCl, + status: status } }); } else if(tab === AccountUserTabKeys.AccountAuth){ navigate(PATHS.account.user.accountAuth, { state: { - tid: tid, mid: mid, - usrid: usrid + usrid: usrid, + idCl: idCl, + status: status } }); } diff --git a/src/entities/account/ui/user-account-auth-perm-item.tsx b/src/entities/account/ui/user-account-auth-perm-item.tsx index b211287..89511ff 100644 --- a/src/entities/account/ui/user-account-auth-perm-item.tsx +++ b/src/entities/account/ui/user-account-auth-perm-item.tsx @@ -3,17 +3,28 @@ import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { UserAccountAuthPermItemProps } from '../model/types'; export const UserAccountAuthPermItem = ({ - tid, + mid, + usrid, + idCl, + status, menuId, - permName + menuName, + subMenu, + menuGrants, }: UserAccountAuthPermItemProps) => { const { navigate } = useNavigate(); const onClickToNavigation = () => { navigate(PATHS.account.user.menuAuth, { state: { - tid: tid, - menuId: menuId + mid: mid, + usrid: usrid, + idCl: idCl, + status: status, + menuId: menuId, + menuName: menuName, + subMenu: subMenu, + menuGrants: menuGrants, } }) }; @@ -23,7 +34,7 @@ export const UserAccountAuthPermItem = ({ className="perm-item" onClick={ () => onClickToNavigation() } > - { permName } + { menuName } diff --git a/src/entities/account/ui/user-account-auth-perm-list.tsx b/src/entities/account/ui/user-account-auth-perm-list.tsx index f13c840..28da230 100644 --- a/src/entities/account/ui/user-account-auth-perm-list.tsx +++ b/src/entities/account/ui/user-account-auth-perm-list.tsx @@ -1,30 +1,37 @@ import { UserAccountAuthPermListProps } from '../model/types'; import { UserAccountAuthPermItem } from './user-account-auth-perm-item'; + export const UserAccountAuthPermList = ({ - tid, - permItems + mid, + usrid, + idCl, + status, + menuItems, + menuGrants, }: UserAccountAuthPermListProps) => { - - const getPermItems = () => { - let rs = []; - for(let i=0;i - ); - } - return rs; - }; - return ( - <> -
- { getPermItems() } -
- +
+ {menuItems.map((item, index) => { + // 해당 메뉴와 서브메뉴에 대한 권한만 필터링 + const subMenuIds = (item.subMenu ?? []).map(sub => Number(sub.menuId)); + const relevantGrants = (menuGrants ?? []).filter(grant => + subMenuIds.includes(grant.menuId) + ); + + return ( + + ); + })} +
); }; \ No newline at end of file diff --git a/src/entities/account/ui/user-account-auth-wrap.tsx b/src/entities/account/ui/user-account-auth-wrap.tsx index a320f2d..d6c8795 100644 --- a/src/entities/account/ui/user-account-auth-wrap.tsx +++ b/src/entities/account/ui/user-account-auth-wrap.tsx @@ -1,19 +1,112 @@ +import { useState, useEffect } from 'react'; import { UserAccountAuthWrapProps } from '../model/types'; import { UserAccountAuthPermList } from './user-account-auth-perm-list'; +import { useUserMenuPermissionsMutation } from '@/entities/user/api/use-user-menu-permission-mutation'; +import { useUserUpdatePermissionsMutation } from '@/entities/user/api/use-user-update-permission-mutation'; +import { UserMenuPermissionData } from '@/entities/user/model/types'; export const UserAccountAuthWrap = ({ - tid + mid, + usrid, + idCl, + status, }: UserAccountAuthWrapProps) => { + const [currentStatus, setCurrentStatus] = useState(status); + const [currentIdCl, setCurrentIdCl] = useState(idCl); + const [menuGrants, setMenuGrants] = useState>([]); + const [hasChanges, setHasChanges] = useState(false); + console.log('mid : ', mid); + console.log('usrid : ', usrid); + console.log('idCl : ', idCl); + console.log('status : ', status); + const { mutateAsync: userMenuPermissions } = useUserMenuPermissionsMutation(); + const updatePermissionsMutation = useUserUpdatePermissionsMutation(); + useEffect(() => { + if (mid && usrid) { + console.log('userMenuPermissions'); + userMenuPermissions({mid: mid, usrid: usrid}).then((res) => { + console.log('res : ', res); + setMenuGrants(res?.data || res || []); + }).catch((error) => { + console.error('Failed to fetch menu permissions:', error); + setMenuGrants([]); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mid, usrid]); + + useEffect(() => { + console.log('menuGrants : ', menuGrants); + }, [menuGrants]); + + // 변경 사항 감지 + useEffect(() => { + const statusChanged = currentStatus !== status; + const idClChanged = currentIdCl !== idCl; + setHasChanges(statusChanged || idClChanged); + }, [currentStatus, currentIdCl, status, idCl]); + let menuItems = [ - {menuId: 'menu1', permName: '거래조회'}, - {menuId: 'menu2', permName: '정산조회'}, - {menuId: 'menu3', permName: '가맹점 관리'}, - {menuId: 'menu4', permName: '결제 관리'}, - {menuId: 'menu5', permName: '계정 관리'}, - {menuId: 'menu6', permName: '부가세 신고 자료'}, - {menuId: 'menu7', permName: '부가서비스'}, - {menuId: 'menu8', permName: '고객지원'}, - ]; + {menuId: '30', parent: '30', menuName: '거래조회', subMenu: + [ + {menuId: '31', parent: '30', menuName: '거래내역조회'}, + {menuId: '32', parent: '30', menuName: '현금영수증 발행'}, + {menuId: '33', parent: '30', menuName: '에스크로'}, + {menuId: '34', parent: '30', menuName: '빌링'} + ] + }, + {menuId: '35', parent: '35', menuName: '정산조회', subMenu: + [ + {menuId: '36', parent: '35', menuName: '정산달력'}, + {menuId: '37', parent: '35', menuName: '정산내역'}, + ] + }, + {menuId: '38', parent: '38', menuName: '가맹점 관리', subMenu: + [ + {menuId: '39', parent: '38', menuName: '가맹점 정보'}, + {menuId: '40', parent: '38', menuName: '등록 현황'}, + ] + }, + {menuId: '41', parent: '41', menuName: '결제 관리', subMenu: + [ + {menuId: '42', parent: '41', menuName: '결제 정보'}, + {menuId: '43', parent: '41', menuName: '결제데이터통보'}, + ] + }, + {menuId: '44', parent: '44', menuName: '계정관리', subMenu: + [ + {menuId: '45', parent: '44', menuName: '사용자관리'}, + {menuId: '46', parent: '44', menuName: '비밀번호관리'}, + ] + }, + {menuId: '47', parent: '47', menuName: '부가세신고자료', subMenu: + [ + {menuId: '48', parent: '47', menuName: '부가세신고자료'}, + {menuId: '49', parent: '47', menuName: '부가세참고'}, + ] + }, + {menuId: '50', parent: '50', menuName: '부가서비스', subMenu: + [ + {menuId: '51', parent: '50', menuName: '부가서비스소개'}, + {menuId: '52', parent: '50', menuName: '신용카드ARS카드결제'}, + {menuId: '53', parent: '50', menuName: '계좌이체ARS카드결제'}, + {menuId: '54', parent: '50', menuName: '가상계좌ARS카드결제'}, + {menuId: '55', parent: '50', menuName: '휴대폰ARS카드결제'}, + {menuId: '56', parent: '50', menuName: '계좌간편결제ARS카드결제'}, + {menuId: '57', parent: '50', menuName: 'SSG머니ARS카드결제'}, + {menuId: '58', parent: '50', menuName: 'SSG은행계좌ARS카드결제'}, + {menuId: '59', parent: '50', menuName: '문화상품권ARS카드결제'}, + {menuId: '60', parent: '50', menuName: '티머니페이ARS카드결제'}, + ] + }, + {menuId: '61', parent: '61', menuName: '고객지원', subMenu: + [ + {menuId: '62', parent: '61', menuName: '공지사항'}, + {menuId: '63', parent: '61', menuName: '자주묻는질문'}, + {menuId: '64', parent: '61', menuName: '1:1문의'}, + ] + }, + ] return ( <>
@@ -21,19 +114,18 @@ export const UserAccountAuthWrap = ({
계정 상태
- setCurrentStatus(e.target.value)}> + +
로그인 범위
- setCurrentIdCl(e.target.value)}> + +
@@ -42,16 +134,34 @@ export const UserAccountAuthWrap = ({
메뉴별 권한 설정
- + type="button" + disabled={!hasChanges || updatePermissionsMutation.isPending} + onClick={() => { + console.log('updatePermissionMutation'); + updatePermissionsMutation.mutate( + { + mid: mid, + usrid: usrid, + idCl: currentIdCl, + status: currentStatus + }); + }} + > + {updatePermissionsMutation.isPending ? '저장 중...' : '저장'} +
+
); diff --git a/src/entities/account/ui/user-login-auth-info-wrap.tsx b/src/entities/account/ui/user-login-auth-info-wrap.tsx index 61d2631..b3032f1 100644 --- a/src/entities/account/ui/user-login-auth-info-wrap.tsx +++ b/src/entities/account/ui/user-login-auth-info-wrap.tsx @@ -1,13 +1,17 @@ -import { UserFindAuthMethodParams, UserAuthMethodData } from '@/entities/user/model/types'; +import { UserFindAuthMethodParams, UserAuthMethodData, AuthMethodModifyItem } from '@/entities/user/model/types'; import { useUserFindAuthMethodMutation } from '@/entities/user/api/use-user-find-authmethod-mutation'; import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constant'; import { useEffect, useState } from 'react'; +import { useUserModifyAuthMethodMutation } from '@/entities/user/api/use-user-modify-authmethod-mutation'; export const UserLoginAuthInfoWrap = ({ mid, usrid, + idCl, + status, }: UserFindAuthMethodParams) => { const { mutateAsync: userFindAuthMethod } = useUserFindAuthMethodMutation(); + const { mutateAsync: userModifyAuthMethod } = useUserModifyAuthMethodMutation(); const [pageParam] = useState(DEFAULT_PAGE_PARAM); const [authMethodData, setAuthMethodData] = useState(); const [initialData, setInitialData] = useState(); @@ -18,6 +22,8 @@ export const UserLoginAuthInfoWrap = ({ const [readOnlyEmails, setReadOnlyEmails] = useState>(new Set()); const [readOnlyPhones, setReadOnlyPhones] = useState>(new Set()); + console.log("UserLoginAuthInfoWrap", mid, usrid, idCl, status); + const handleRemoveExistingEmail = (index: number) => { if (authMethodData?.emails) { const updatedEmails = authMethodData.emails.filter((_, i) => i !== index); @@ -36,6 +42,8 @@ export const UserLoginAuthInfoWrap = ({ let params: UserFindAuthMethodParams = { mid: mid, usrid: usrid, + idCl: idCl, + status: status, page: pageParam }; userFindAuthMethod(params).then((rs: any) => { @@ -260,6 +268,107 @@ export const UserLoginAuthInfoWrap = ({ return hasChanges; }; + const handleSave = async () => { + try { + const addMethods: AuthMethodModifyItem[] = []; + const removeMethods: AuthMethodModifyItem[] = []; + + // 삭제된 이메일 항목 수집 + if (initialData?.emails) { + initialData.emails.forEach((email) => { + const stillExists = authMethodData?.emails?.some( + e => e.content === email.content && e.sequence === email.sequence + ); + if (!stillExists) { + removeMethods.push({ + usrid: usrid, + systemAdminClassId: mid, + idCl: "MID", + authMethodType: "EMAIL", + sequence: email.sequence, + content: email.content + }); + } + }); + } + + // 삭제된 전화번호 항목 수집 + if (initialData?.phones) { + initialData.phones.forEach((phone) => { + const stillExists = authMethodData?.phones?.some( + p => p.content === phone.content && p.sequence === phone.sequence + ); + if (!stillExists) { + removeMethods.push({ + usrid: usrid, + systemAdminClassId: mid, + idCl: "MID", + authMethodType: "PHONE", + sequence: phone.sequence, + content: phone.content + }); + } + }); + } + + // 새로 추가된 이메일 항목 수집 + const validNewEmails = newEmails.filter(e => e.trim()); + validNewEmails.forEach((email, index) => { + const existingEmailCount = authMethodData?.emails?.length || 0; + addMethods.push({ + usrid: usrid, + systemAdminClassId: mid, + idCl: "MID", + authMethodType: "EMAIL", + sequence: existingEmailCount + index + 1, + content: email + }); + }); + + // 새로 추가된 전화번호 항목 수집 + const validNewPhones = newPhones.filter(p => p.trim()); + validNewPhones.forEach((phone, index) => { + const existingPhoneCount = authMethodData?.phones?.length || 0; + addMethods.push({ + usrid: usrid, + systemAdminClassId: mid, + idCl: "MID", + authMethodType: "PHONE", + sequence: existingPhoneCount + index + 1, + content: phone + }); + }); + + const requestBody = { + addMethods: addMethods.length > 0 ? addMethods : undefined, + removeMethods: removeMethods.length > 0 ? removeMethods : undefined + }; + + // 빈 배열 제거 + const finalRequestBody = Object.fromEntries( + Object.entries(requestBody).filter(([_, value]) => value !== undefined) + ); + + console.log("Save request body:", finalRequestBody); + + // API 호출 + await userModifyAuthMethod(finalRequestBody); + + // 성공 후 데이터 새로고침 + callUserFindAuthMethod(mid, usrid); + + // 새로 추가한 항목들 초기화 + setNewEmails([]); + setNewPhones([]); + setEditableEmailIndex(-1); + setEditablePhoneIndex(-1); + setReadOnlyEmails(new Set()); + setReadOnlyPhones(new Set()); + } catch (error) { + console.error("Failed to save auth methods:", error); + } + }; + console.log("Rendering with authMethodData: ", authMethodData); console.log("Emails: ", authMethodData?.emails); console.log("Phones: ", authMethodData?.phones); @@ -369,6 +478,7 @@ export const UserLoginAuthInfoWrap = ({ diff --git a/src/entities/account/ui/user-manage-auth-item.tsx b/src/entities/account/ui/user-manage-auth-item.tsx index ed1247b..da4d039 100644 --- a/src/entities/account/ui/user-manage-auth-item.tsx +++ b/src/entities/account/ui/user-manage-auth-item.tsx @@ -4,33 +4,25 @@ import { UserManageAuthItemProps } from '../model/types'; export const UserManageAuthItem = ({ usrid, - tid, mid, + idCl, + status, }: UserManageAuthItemProps) => { + console.log("UserManageAuthItem", usrid, mid, idCl, status); const { navigate } = useNavigate(); - const onClickToNavigation = () => { - // state를 통해 데이터 전달 + const handleClick = () => { navigate(PATHS.account.user.loginAuthInfo, { - state: { - tid: tid, - mid: mid, - usrid: usrid - } + state: { mid, usrid, idCl, status } }); }; + return ( - <> -
onClickToNavigation() } - > -
- {/* { (!!useYn)? '사용': '미사용' } */} - { usrid } -
- +
+
+ {usrid}
- + +
); }; \ No newline at end of file diff --git a/src/entities/account/ui/user-manage-auth-list.tsx b/src/entities/account/ui/user-manage-auth-list.tsx index 3ae275d..0afa033 100644 --- a/src/entities/account/ui/user-manage-auth-list.tsx +++ b/src/entities/account/ui/user-manage-auth-list.tsx @@ -5,26 +5,18 @@ export const UserManageAuthList = ({ userItems, mid }: UserManageAuthListProps) => { - - const getUserManageAuthItems = () => { - let rs = []; - for(let i=0;i - ); - } - return rs; - } + console.log("UserManageAuthList", userItems, mid); return ( - <> -
- { getUserManageAuthItems() } -
- +
+ {userItems.map((item) => ( + + ))} +
); }; \ No newline at end of file diff --git a/src/entities/account/ui/user-manage-wrap.tsx b/src/entities/account/ui/user-manage-wrap.tsx index d344b24..a1f6a61 100644 --- a/src/entities/account/ui/user-manage-wrap.tsx +++ b/src/entities/account/ui/user-manage-wrap.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { PATHS } from '@/shared/constants/paths'; import { useNavigate } from '@/shared/lib/hooks/use-navigate'; -import { AuthItem } from '../model/types'; import { DEFAULT_PAGE_PARAM } from '@/entities/common/model/constant'; import { UserManageAuthList } from './user-manage-auth-list'; import { useUserFindMutation } from '@/entities/user/api/use-user-find-mutation'; @@ -23,12 +22,18 @@ export const UserManageWrap = () => { const callList = (mid: string) => { setPageParam(pageParam); userFind({ mid: mid, page: pageParam }).then((rs) => { + console.log('API Response:', rs); + console.log('Content:', rs.content); setUserItems(rs.content || []); }); }; const onClickToNavigation = () => { - navigate(PATHS.account.user.addAccount); + navigate(PATHS.account.user.addAccount, { + state: { + mid: mid, + } + }); }; useEffect(() => { diff --git a/src/entities/common/model/types.ts b/src/entities/common/model/types.ts index 26fcc7a..43608a4 100644 --- a/src/entities/common/model/types.ts +++ b/src/entities/common/model/types.ts @@ -133,7 +133,8 @@ export interface CodesGroupByCodeClParams { codeCl: string; }; export interface CodesGroupByCodeClResponse extends CodeGroupItem { - + status: boolean; + error?: ErrorResponse; } export interface CodesCacheStatusResponse { additionalProp1: string; @@ -141,13 +142,15 @@ export interface CodesCacheStatusResponse { additionalProp3: string; }; export interface CodesCacheRefreshResponse { - + status: boolean; + error?: ErrorResponse; }; export interface CodesCacheRefreshByCodeClParams { codeCl: string; }; export interface CodesCacheRefreshByCodeClResponse { - + status: boolean; + error?: ErrorResponse; }; export interface EmptyTokenVerifyCodeParams { usrid: string; @@ -177,7 +180,8 @@ export interface EmptyTokenChangeParams { newConfirmPassword: string; }; export interface EmptyTokenChangeResponse { - + status: boolean; + error?: ErrorResponse; }; export interface EmptyTokenAddSendCodeParams { usrid: string; @@ -196,3 +200,12 @@ export interface SectionArrowProps { export interface BannerInfo { HomneBottomBanner: boolean; }; + +export interface ErrorResponse { + root: string; + errKey: string; + code: string; + message: string; + timestamp: string; + details: Record; +} \ No newline at end of file diff --git a/src/entities/user/api/use-user-change-password-mutation.ts b/src/entities/user/api/use-user-change-password-mutation.ts new file mode 100644 index 0000000..ec0c9b9 --- /dev/null +++ b/src/entities/user/api/use-user-change-password-mutation.ts @@ -0,0 +1,29 @@ +import axios from 'axios'; +import { API_URL_USER } from '@/shared/api/api-url-user'; +import { resultify } from '@/shared/lib/resultify'; +import { CBDCAxiosError } from '@/shared/@types/error'; +import { + ChangePasswordParams, + ChangePasswordResponse +} from '../model/types'; +import { + useMutation, + UseMutationOptions +} from '@tanstack/react-query'; + +export const userChangePassword = (params: ChangePasswordParams) => { + return resultify( + axios.post(API_URL_USER.changePassword(), params), + ); +}; + +export const useUserChangePasswordMutation = (options?: UseMutationOptions) => { + const mutation = useMutation({ + ...options, + mutationFn: (params: ChangePasswordParams) => userChangePassword(params), + }); + + return { + ...mutation, + }; +}; diff --git a/src/entities/user/api/use-user-menu-permission-mutation.ts b/src/entities/user/api/use-user-menu-permission-mutation.ts new file mode 100644 index 0000000..c8021cb --- /dev/null +++ b/src/entities/user/api/use-user-menu-permission-mutation.ts @@ -0,0 +1,28 @@ +import axios from 'axios'; +import { API_URL_USER } from '@/shared/api/api-url-user'; +import { resultify } from '@/shared/lib/resultify'; +import { CBDCAxiosError } from '@/shared/@types/error'; +import { + UserMenuPermissionsParams, + UserMenuPermissionsResponse +} from '../model/types'; +import { + useMutation, + UseMutationOptions +} from '@tanstack/react-query'; + +export const userMenuPermissions = (params: UserMenuPermissionsParams) => { + return resultify( + axios.post(API_URL_USER.findMenuPermissions(), params), + ); +}; + +export const useUserMenuPermissionsMutation = (options?: UseMutationOptions) => { + const mutation = useMutation({ + ...options, + mutationFn: (params: UserMenuPermissionsParams) => userMenuPermissions(params), + }); + return { + ...mutation, + }; +}; diff --git a/src/entities/user/api/use-user-menu-permission-save-mutation.ts b/src/entities/user/api/use-user-menu-permission-save-mutation.ts new file mode 100644 index 0000000..32456e6 --- /dev/null +++ b/src/entities/user/api/use-user-menu-permission-save-mutation.ts @@ -0,0 +1,29 @@ +import axios from 'axios'; +import { API_URL_USER } from '@/shared/api/api-url-user'; +import { resultify } from '@/shared/lib/resultify'; +import { CBDCAxiosError } from '@/shared/@types/error'; +import { + UserMenuPermissionsSaveParams, + UserMenuPermissionsSaveResponse +} from '../model/types'; +import { + useMutation, + UseMutationOptions +} from '@tanstack/react-query'; + +export const userMenuPermissionsSave = (params: UserMenuPermissionsSaveParams) => { + return resultify( + axios.post(API_URL_USER.saveMenuPermissions(), params), + ); +}; + +export const useUserMenuPermissionsSaveMutation = (options?: UseMutationOptions) => { + const mutation = useMutation({ + ...options, + mutationFn: (params: UserMenuPermissionsSaveParams) => userMenuPermissionsSave(params), + }); + + return { + ...mutation, + }; +}; diff --git a/src/entities/user/api/use-user-modify-authmethod-mutation.ts b/src/entities/user/api/use-user-modify-authmethod-mutation.ts new file mode 100644 index 0000000..1a5d745 --- /dev/null +++ b/src/entities/user/api/use-user-modify-authmethod-mutation.ts @@ -0,0 +1,29 @@ +import axios from 'axios'; +import { API_URL_USER } from '@/shared/api/api-url-user'; +import { resultify } from '@/shared/lib/resultify'; +import { CBDCAxiosError } from '@/shared/@types/error'; +import { + UserModifyAuthMethodParams, + UserModifyAuthMethodResponse +} from '../model/types'; +import { + useMutation, + UseMutationOptions +} from '@tanstack/react-query'; + +export const userModifyAuthMethod = (params: UserModifyAuthMethodParams) => { + return resultify( + axios.post(API_URL_USER.modifyAuthMethod(), params), + ); +}; + +export const useUserModifyAuthMethodMutation = (options?: UseMutationOptions) => { + const mutation = useMutation({ + ...options, + mutationFn: (params: UserModifyAuthMethodParams) => userModifyAuthMethod(params), + }); + + return { + ...mutation, + }; +}; diff --git a/src/entities/user/api/use-user-update-permission-mutation.ts b/src/entities/user/api/use-user-update-permission-mutation.ts new file mode 100644 index 0000000..b931094 --- /dev/null +++ b/src/entities/user/api/use-user-update-permission-mutation.ts @@ -0,0 +1,29 @@ +import axios from 'axios'; +import { API_URL_USER } from '@/shared/api/api-url-user'; +import { resultify } from '@/shared/lib/resultify'; +import { CBDCAxiosError } from '@/shared/@types/error'; +import { + UserUpdatePermissionsParams, + UserUpdatePermissionsResponse +} from '../model/types'; +import { + useMutation, + UseMutationOptions +} from '@tanstack/react-query'; + +export const userUpdatePermissions = (params: UserUpdatePermissionsParams) => { + return resultify( + axios.post(API_URL_USER.updatePermissions(), params), + ); +}; + +export const useUserUpdatePermissionsMutation = (options?: UseMutationOptions) => { + const mutation = useMutation({ + ...options, + mutationFn: (params: UserUpdatePermissionsParams) => userUpdatePermissions(params), + }); + + return { + ...mutation, + }; +}; diff --git a/src/entities/user/model/types.ts b/src/entities/user/model/types.ts index 5a1e924..b52e717 100644 --- a/src/entities/user/model/types.ts +++ b/src/entities/user/model/types.ts @@ -1,6 +1,7 @@ import { DefaulResponsePagination, - DefaultRequestPagination + DefaultRequestPagination, + ErrorResponse, } from '@/entities/common/model/types'; export interface LoginParams { @@ -28,16 +29,19 @@ export interface LoginResponse { }; export interface UserInfo extends LoginResponse { - + status: boolean; + error?: ErrorResponse; } export interface UserParams { - + status: boolean; + error?: ErrorResponse; }; export interface UserListItem { usrid: string; idCl: string; status: string; + tid: string; } export interface UserFindResponse extends DefaulResponsePagination { @@ -56,6 +60,8 @@ export interface UserExistsUseridParams { export interface UserFindAuthMethodParams { mid: string; usrid: string; + idCl: string; + status: string; page?: DefaultRequestPagination; }; @@ -68,16 +74,86 @@ export interface UserExistsUseridResponse { exists: boolean; }; +export interface AuthMethodModifyItem { + usrid: string; + systemAdminClassId: string; + idCl: string; + authMethodType: string; + sequence: number; + content: string; +} + +export interface UserModifyAuthMethodParams { + addMethods?: Array; + removeMethods?: Array; +}; + +export interface UserModifyAuthMethodResponse { + status: boolean; + error?: ErrorResponse; +}; + +export interface UserUpdatePermissionsParams { + mid: string; + usrid: string; + idCl: string; + status: string; +} + +export interface UserUpdatePermissionsResponse { + status: boolean; + error?: ErrorResponse; +} + +export interface UserMenuPermissionsParams { + mid: string; + usrid: string; +} + +export interface UserMenuPermissionsResponse { + status: boolean; + data: Array; +} + +export interface UserMenuPermissionsSaveParams { + mid: string; + namsUserMenuAccess: Array; +} + +export interface UserMenuPermissionsSaveResponse { + status: boolean; + error?: ErrorResponse; +} + + +export interface UserMenuPermissionData { + menuId: number; + usrid: string; + grant: number; +} + +export interface ChangePasswordParams { + mid: string; + usrid: string; + password: string; +} + +export interface ChangePasswordResponse { + status: boolean; + error?: ErrorResponse; +} + export interface VerificationsItem { type: string; contact: string; }; export interface UserCreateParams { - userId: string; + mid: string; + usrid: string; password: string; loginRange: string; - verification: Array + verifications: Array }; export interface UserData { diff --git a/src/pages/account/password/modify-login-password-page.tsx b/src/pages/account/password/modify-login-password-page.tsx index 0ee3a7f..ed982ca 100644 --- a/src/pages/account/password/modify-login-password-page.tsx +++ b/src/pages/account/password/modify-login-password-page.tsx @@ -1,15 +1,44 @@ +import { useState } from 'react'; import { PATHS } from '@/shared/constants/paths'; import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { HeaderType } from '@/entities/common/model/types'; -import { +import { useSetHeaderTitle, useSetHeaderType, useSetFooterMode, useSetOnBack } from '@/widgets/sub-layout/use-sub-layout'; +import { useUserChangePasswordMutation } from '@/entities/user/api/use-user-change-password-mutation'; +import { snackBar } from '@/shared/lib/toast'; export const PasswordModifyLoginPasswordPage = () => { const { navigate } = useNavigate(); + const [mid, setMid] = useState('nictest00m'); + const [currentPassword, setCurrentPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [usrid] = useState(''); // TODO: Get actual user ID from context/session + + const changePasswordMutation = useUserChangePasswordMutation({ + onSuccess: () => { + snackBar('비밀번호가 성공적으로 변경되었습니다.'); + // Clear form + setCurrentPassword(''); + setNewPassword(''); + setConfirmPassword(''); + // Navigate back + navigate(PATHS.account.password.manage); + }, + onError: (error) => { + snackBar(error?.response?.data?.message || '비밀번호 변경에 실패했습니다.'); + } + }); + + const midList = [ + { value: 'nictest00m', label: 'nictest00m' }, + { value: 'nictest01m', label: 'nictest01m' }, + { value: 'nictest02m', label: 'nictest02m' }, + ]; useSetHeaderTitle('로그인 비밀번호 변경'); useSetHeaderType(HeaderType.LeftArrow); @@ -17,6 +46,29 @@ export const PasswordModifyLoginPasswordPage = () => { useSetOnBack(() => { navigate(PATHS.account.password.manage); }); + + // 저장 버튼 활성화 조건 체크 + const isFormValid = () => { + return ( + currentPassword.length >= 8 && + newPassword.length >= 8 && + confirmPassword.length >= 8 && + newPassword === confirmPassword + ); + }; + + // 저장 버튼 클릭 핸들러 + const handleSave = () => { + if (!isFormValid()) return; + + // TODO: Validate current password before submitting + changePasswordMutation.mutate({ + mid, + usrid: usrid || 'test', // TODO: Get actual user ID + password: newPassword + }); + }; + return ( <>
@@ -26,42 +78,54 @@ export const PasswordModifyLoginPasswordPage = () => {
가맹점 *
- setMid(e.target.value)}> + {midList.map((item) => ( + + ))}
기존 비밀번호 *
- setCurrentPassword(e.target.value)} />
변경 비밀번호 *
- setNewPassword(e.target.value)} />
-
입력 정보 불일치
변경 비밀번호 재입력 *
- setConfirmPassword(e.target.value)} />
+ {confirmPassword && newPassword !== confirmPassword && ( +
입력 정보 불일치
+ )}
- + disabled={!isFormValid() || changePasswordMutation.isPending} + onClick={handleSave} + >{changePasswordMutation.isPending ? '처리중...' : '저장'}
diff --git a/src/pages/account/user/account-auth-page.tsx b/src/pages/account/user/account-auth-page.tsx index e905ea1..d1271d0 100644 --- a/src/pages/account/user/account-auth-page.tsx +++ b/src/pages/account/user/account-auth-page.tsx @@ -16,9 +16,9 @@ import { export const UserAccountAuthPage = () => { const { navigate } = useNavigate(); const location = useLocation(); - const { tid, mid, usrid } = location.state || {}; + const { tid, mid, usrid, idCl, status } = location.state || {}; - const [activeTab, setActiveTab] = useState(AccountUserTabKeys.AccountAuth); + const [activeTab, ] = useState(AccountUserTabKeys.AccountAuth); useSetHeaderTitle('사용자 설정'); useSetHeaderType(HeaderType.LeftArrow); useSetFooterMode(true); @@ -37,12 +37,16 @@ export const UserAccountAuthPage = () => {
diff --git a/src/pages/account/user/add-account-page.tsx b/src/pages/account/user/add-account-page.tsx index 643d143..c951497 100644 --- a/src/pages/account/user/add-account-page.tsx +++ b/src/pages/account/user/add-account-page.tsx @@ -11,8 +11,12 @@ import { useState, useEffect, useRef } from 'react'; import { VerificationItem } from '@/entities/account/model/types'; import { useUserCreateMutation } from '@/entities/user/api/use-user-create-mutation'; import { useUserExistsUseridQuery } from '@/entities/user/api/use-user-exists-userid-query'; +import { useLocation } from 'react-router'; + export const UserAddAccountPage = () => { const { navigate } = useNavigate(); + const location = useLocation(); + const { mid } = location.state || {}; const { mutateAsync: userCreate, isPending } = useUserCreateMutation(); // 폼 상태 관리 @@ -143,6 +147,16 @@ export const UserAddAccountPage = () => { setNewPhones(updated); }; + // 비밀번호 검증 함수 + const validatePassword = (password: string) => { + if (!password.trim()) { + return '비밀번호를 입력해 주세요'; + } else if (password.length < 8) { + return '8자리 이상 입력해 주세요'; + } + return ''; + }; + // 폼 입력 핸들러 const handleInputChange = (field: string, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); @@ -174,6 +188,14 @@ export const UserAddAccountPage = () => { } }; + // 비밀번호 blur 핸들러 + const handlePasswordBlur = () => { + const passwordError = validatePassword(formData.password); + if (passwordError) { + setErrors(prev => ({ ...prev, password: passwordError })); + } + }; + // 컴포넌트 언마운트 시 타이머 정리 useEffect(() => { return () => { @@ -241,6 +263,44 @@ export const UserAddAccountPage = () => { return totalCount > 1; }; + // 저장 버튼 활성화 조건 체크 + const isSaveButtonEnabled = () => { + // 1. 사용자 ID가 입력되고 중복되지 않아야 함 + if (!formData.usrid.trim()) return false; + if (errors.usrid) return false; + if (isCheckingUsrid || isUserExistsLoading) return false; + + // 2. 비밀번호가 8자리 이상 + if (!formData.password.trim() || formData.password.length < 8) return false; + + // 3. 입력된 모든 이메일과 휴대폰 번호가 유효한 형식이어야 함 + const nonEmptyEmails = newEmails.filter(email => email.trim()); + const nonEmptyPhones = newPhones.filter(phone => phone.trim()); + + // 입력된 이메일이 있으면 모두 유효한 형식이어야 함 + if (nonEmptyEmails.length > 0) { + const invalidEmails = nonEmptyEmails.filter(email => !isValidEmail(email)); + if (invalidEmails.length > 0) return false; + } + + // 입력된 휴대폰 번호가 있으면 모두 유효한 형식이어야 함 + if (nonEmptyPhones.length > 0) { + const invalidPhones = nonEmptyPhones.filter(phone => !isValidPhone(phone)); + if (invalidPhones.length > 0) return false; + } + + // 4. 유효한 이메일 또는 휴대폰 번호가 최소 1개 이상 + const validEmails = nonEmptyEmails.filter(email => isValidEmail(email)); + const validPhones = nonEmptyPhones.filter(phone => isValidPhone(phone)); + + if (validEmails.length === 0 && validPhones.length === 0) return false; + + // 5. 중복이 없어야 함 + if (hasDuplicateEmail() || hasDuplicatePhone()) return false; + + return true; + }; + // 폼 검증 const validateForm = () => { const newErrors = { usrid: '', password: '' }; @@ -302,10 +362,11 @@ export const UserAddAccountPage = () => { }); const request = { - userId: formData.usrid, + mid: mid, + usrid: formData.usrid, password: formData.password, loginRange: formData.loginRange, - verification: verifications + verifications: verifications }; const response = await userCreate(request); @@ -365,7 +426,7 @@ export const UserAddAccountPage = () => { )} - {errors.usrid &&
{errors.usrid}
} + {errors.usrid &&
{errors.usrid}
} {!errors.usrid && formData.usrid && userExistsData && !userExistsData.exists && (
사용 가능한 ID입니다.
)} @@ -378,9 +439,10 @@ export const UserAddAccountPage = () => { placeholder="8자리 이상 입력해 주세요" value={formData.password} onChange={(e) => handleInputChange('password', e.target.value)} + onBlur={handlePasswordBlur} /> - {errors.password &&
{errors.password}
} + {errors.password &&
{errors.password}
}
로그인 범위
@@ -390,7 +452,7 @@ export const UserAddAccountPage = () => { onChange={(e) => handleInputChange('loginRange', e.target.value)} > - +
@@ -471,7 +533,7 @@ export const UserAddAccountPage = () => { className="btn-50 btn-blue flex-1" type="button" onClick={handleSave} - disabled={isPending} + disabled={!isSaveButtonEnabled() || isPending} > {isPending ? '저장 중...' : '저장'} diff --git a/src/pages/account/user/login-auth-info-page.tsx b/src/pages/account/user/login-auth-info-page.tsx index 5e376ea..1990ab3 100644 --- a/src/pages/account/user/login-auth-info-page.tsx +++ b/src/pages/account/user/login-auth-info-page.tsx @@ -15,10 +15,10 @@ import { export const UserLoginAuthInfoPage = () => { const location = useLocation(); - const { mid, usrid, tid } = location.state || {}; + const { mid, usrid, idCl, status } = location.state || {}; const { navigate } = useNavigate(); - const [activeTab, setActiveTab] = useState(AccountUserTabKeys.LoginAuthInfo); + const [activeTab, ] = useState(AccountUserTabKeys.LoginAuthInfo); useSetHeaderTitle('사용자 설정'); useSetHeaderType(HeaderType.LeftArrow); useSetFooterMode(true); @@ -26,22 +26,6 @@ export const UserLoginAuthInfoPage = () => { navigate(PATHS.account.user.manage); }); - // const { mutateAsync: userFindAuthMethod } = useUserFindAuthMethodMutation(); - - // const callUserFindAuthMethod = (mid: string, usrid: string) => { - // let parms: UserFindAuthMethodParams = { - // mid: mid, - // usrid: usrid - // } - // userFindAuthMethod(params).then((rs: UserFindAuthMethodResponse) => { - // console.log("User Find Auth Method: ", rs) - // }); - // } - - // useEffect(() => { - // callUserFindAuthMethod(mid, usrid); - // }, [mid, usrid]); - return ( <>
@@ -49,13 +33,16 @@ export const UserLoginAuthInfoPage = () => {
diff --git a/src/pages/account/user/manage-page.tsx b/src/pages/account/user/manage-page.tsx index f39c8ed..6c1bf40 100644 --- a/src/pages/account/user/manage-page.tsx +++ b/src/pages/account/user/manage-page.tsx @@ -17,7 +17,7 @@ import { export const UserManagePage = () => { const { navigate } = useNavigate(); - const [activeTab, setActiveTab] = useState(AccountTabKeys.UserManage); + const [activeTab, ] = useState(AccountTabKeys.UserManage); useSetHeaderTitle('계정 관리'); useSetHeaderType(HeaderType.LeftArrow); useSetFooterMode(true); diff --git a/src/pages/account/user/menu-auth-page.tsx b/src/pages/account/user/menu-auth-page.tsx index 1d45858..427ad45 100644 --- a/src/pages/account/user/menu-auth-page.tsx +++ b/src/pages/account/user/menu-auth-page.tsx @@ -1,28 +1,169 @@ import { useEffect, useState } from 'react'; -import { useLocation } from 'react-router'; import { PATHS } from '@/shared/constants/paths'; import { useNavigate } from '@/shared/lib/hooks/use-navigate'; import { HeaderType } from '@/entities/common/model/types'; -import { +import { useSetHeaderTitle, useSetHeaderType, useSetFooterMode, useSetOnBack } from '@/widgets/sub-layout/use-sub-layout'; +import { useLocation } from 'react-router'; +import { useUserMenuPermissionsSaveMutation } from '@/entities/user/api/use-user-menu-permission-save-mutation'; +// import { useUserMenuPermissionsMutation } from '@/entities/user/api/use-user-menu-permission-mutation'; +import { UserMenuPermissionData } from '@/entities/user/model/types'; + +// 권한 비트 플래그 (실제 API 데이터 기준) +const PERMISSION = { + READ: 1, // 조회 + SAVE: 2, // 저장 + EXECUTE: 4, // 실행 + DOWNLOAD: 8 // 다운로드 +}; export const UserMenuAuthPage = () => { const { navigate } = useNavigate(); + const location = useLocation(); + const { mid, usrid, menuName, subMenu, menuGrants } = location.state || {}; - useSetHeaderTitle('사용자 설정'); + // 메뉴별 권한 상태 관리 + const [permissions, setPermissions] = useState>({}); + const [initialPermissions, setInitialPermissions] = useState>({}); + const [hasChanges, setHasChanges] = useState(false); + const savePermissionsMutation = useUserMenuPermissionsSaveMutation(); + // const getPermissionsMutation = useUserMenuPermissionsMutation(); + + useSetHeaderTitle(menuName); useSetHeaderType(HeaderType.LeftArrow); useSetFooterMode(true); useSetOnBack(() => { - navigate(PATHS.account.user.accountAuth); + navigate(PATHS.account.user.accountAuth, { + state: { + mid, + usrid, + idCl: location.state?.idCl, + status: location.state?.status + } + }); }); useEffect(() => { + console.log('menuGrants : ', menuGrants); + }, [menuGrants]); - }); + // // 메뉴 권한 조회 함수 + // const loadPermissions = useCallback(() => { + // if (mid && usrid) { + // getPermissionsMutation.mutate( + // { mid, usrid }, + // { + // onSuccess: (response) => { + // if (response.data) { + // const perms: Record = {}; + // response.data.forEach((item: UserMenuPermissionData) => { + // perms[item.menuId] = item.grant; + // }); + // setPermissions(perms); + // } + // } + // } + // ); + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [mid, usrid]); + + // menuGrants를 권한 상태로 초기화 또는 API에서 권한 조회 + useEffect(() => { + if (menuGrants && Array.isArray(menuGrants) && menuGrants.length > 0) { + // menuGrants 데이터가 있으면 사용 + const initial: Record = {}; + menuGrants.forEach((grant: { menuId: number; grant: number }) => { + initial[grant.menuId] = grant.grant || 0; + }); + setPermissions(initial); + setInitialPermissions(initial); + } else { + // menuGrants가 없거나 빈 배열이면 API에서 권한 조회 + // loadPermissions(); + } + }, [menuGrants]); + + // 권한 변경 감지 + useEffect(() => { + const hasAnyChange = Object.keys(permissions).some(key => { + const menuId = Number(key); + return permissions[menuId] !== (initialPermissions[menuId] || 0); + }) || Object.keys(initialPermissions).some(key => { + const menuId = Number(key); + return (permissions[menuId] || 0) !== initialPermissions[menuId]; + }); + setHasChanges(hasAnyChange); + }, [permissions, initialPermissions]); + + // 특정 메뉴의 권한 체크 + const hasPermission = (menuId: number, flag: number): boolean => { + const grant = permissions[menuId] || 0; + return (grant & flag) === flag; + }; + + // 권한 토글 처리 + const togglePermission = (menuId: number, flag: number) => { + setPermissions(prev => { + const currentGrant = prev[menuId] || 0; + const newGrant = currentGrant ^ flag; + return { + ...prev, + [menuId]: newGrant + }; + }); + }; + + // 메인 토글 처리 (VIEW 권한) + const toggleMainPermission = (menuId: number) => { + setPermissions(prev => { + const currentGrant = prev[menuId] || 0; + if (currentGrant > 0) { + // 권한이 있으면 모두 제거 + return { + ...prev, + [menuId]: 0 + }; + } else { + // 권한이 없으면 VIEW 권한만 부여 + return { + ...prev, + [menuId]: PERMISSION.READ + }; + } + }); + }; + + // 권한 저장 + const handleSave = () => { + const namsUserMenuAccess: UserMenuPermissionData[] = Object.entries(permissions).map( + ([menuId, grant]) => ({ + menuId: Number(menuId), + usrid: usrid, + grant: grant + }) + ); + + savePermissionsMutation.mutate( + { mid, namsUserMenuAccess }, + { + onSuccess: () => { + alert('권한이 저장되었습니다.'); + // 저장 성공 후 초기값 업데이트 + setInitialPermissions({...permissions}); + setHasChanges(false); + }, + onError: (error) => { + alert('권한 저장에 실패했습니다.'); + console.error(error); + } + } + ); + }; return ( <> @@ -33,89 +174,83 @@ export const UserMenuAuthPage = () => {
메뉴별 사용 권한을 설정해 주세요.
선택한 권한에 따라 기능 이용이 제한됩니다.
-
-
-
거래내역 조회
- -
-
-
- 등록 - -
-
- 수정 - -
-
- 삭제 - -
-
- 다운로드 - -
-
-
-
-
-
현금영수증 발행
- + {subMenu && subMenu.map((menu: { menuId: number; menuName: string }) => { + const menuGrant = permissions[menu.menuId] || 0; + const hasAccess = menuGrant > 0; + + return ( +
+
+
+
{menu.menuName}
+ +
+
+
+
+ 저장 + +
+
+ 실행 + +
+
+ 다운로드 + +
+
+
+
-
-
에스크로
- -
-
-
빌링
- -
-
+ ); + })}
- + type="button" + onClick={handleSave} + disabled={!hasChanges || savePermissionsMutation.isPending} + > + {savePermissionsMutation.isPending ? '저장 중...' : '저장'} +
diff --git a/src/shared/api/api-url-user.ts b/src/shared/api/api-url-user.ts index 572860b..afb6891 100644 --- a/src/shared/api/api-url-user.ts +++ b/src/shared/api/api-url-user.ts @@ -29,15 +29,16 @@ export const API_URL_USER = { modifyAuthMethod: () => { return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/tfa/modify`; }, - // allUserList: () => { - // return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/all/users`; - // }, - - // updateUser: () => { - // return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/update`; - // }, - // userDetail: () => { - // return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/detail`; - // }, - + updatePermissions: () => { + return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/permissions/update`; + }, + findMenuPermissions: () => { + return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/menu/permissions`; + }, + saveMenuPermissions: () => { + return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/menu/permissions/save`; + }, + changePassword: () => { + return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/password`; + }, } \ No newline at end of file