취소 비밀번호 변경 기능 추가 및 사용자 계정 관리 개선
🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -5,7 +5,7 @@ NODE_VERSION = 20
|
||||
DOCKER_REGISTRY = harbor.jongyeob.com
|
||||
DOCKER_PROJECT = nicepay
|
||||
DOCKER_IMAGE = nice-app-web
|
||||
DOCKER_TAG = latest
|
||||
DOCKER_TAG = 1.0.0
|
||||
DOCKER_FULL_IMAGE = $(DOCKER_REGISTRY)/$(DOCKER_PROJECT)/$(DOCKER_IMAGE)
|
||||
CONTAINER_NAME = nice-app-web
|
||||
PORT = 80
|
||||
|
||||
@@ -4,9 +4,6 @@ import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
export const PasswordManageWrap = () => {
|
||||
const { navigate } = useNavigate();
|
||||
|
||||
const onClickTonavigation = () => {
|
||||
navigate(PATHS.account.password.modifyLoginPassword);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className="ing-list">
|
||||
@@ -15,11 +12,12 @@ export const PasswordManageWrap = () => {
|
||||
<button
|
||||
className="btn-44 btn-white pwd-btn"
|
||||
type="button"
|
||||
onClick={ () => onClickTonavigation() }
|
||||
onClick={ () => navigate(PATHS.account.password.modifyLoginPassword) }
|
||||
>로그인 비밀번호 변경</button>
|
||||
<button
|
||||
className="btn-44 btn-white pwd-btn"
|
||||
type="button"
|
||||
onClick={ () => navigate(PATHS.account.password.modifyCancelPassword) }
|
||||
>거래취소 비밀번호 변경</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,9 @@ import { useUserFindAuthMethodMutation } from '@/entities/user/api/use-user-find
|
||||
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';
|
||||
import { snackBar } from '@/shared/lib/toast';
|
||||
import { PATHS } from '@/shared/constants/paths';
|
||||
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
||||
|
||||
export const UserLoginAuthInfoWrap = ({
|
||||
mid,
|
||||
@@ -10,8 +13,7 @@ export const UserLoginAuthInfoWrap = ({
|
||||
idCl,
|
||||
status,
|
||||
}: UserFindAuthMethodParams) => {
|
||||
const { mutateAsync: userFindAuthMethod } = useUserFindAuthMethodMutation();
|
||||
const { mutateAsync: userModifyAuthMethod } = useUserModifyAuthMethodMutation();
|
||||
const { navigate } = useNavigate();
|
||||
const [pageParam] = useState(DEFAULT_PAGE_PARAM);
|
||||
const [authMethodData, setAuthMethodData] = useState<UserAuthMethodData>();
|
||||
const [initialData, setInitialData] = useState<UserAuthMethodData>();
|
||||
@@ -21,6 +23,20 @@ export const UserLoginAuthInfoWrap = ({
|
||||
const [editablePhoneIndex, setEditablePhoneIndex] = useState<number>(-1);
|
||||
const [readOnlyEmails, setReadOnlyEmails] = useState<Set<number>>(new Set());
|
||||
const [readOnlyPhones, setReadOnlyPhones] = useState<Set<number>>(new Set());
|
||||
const { mutateAsync: userFindAuthMethod } = useUserFindAuthMethodMutation();
|
||||
const { mutateAsync: userModifyAuthMethod } = useUserModifyAuthMethodMutation({
|
||||
onSuccess: () => {
|
||||
snackBar('사용자 정보가 성공적으로 저장되었습니다.');
|
||||
navigate(PATHS.account.user.manage, {
|
||||
state: {
|
||||
mid: mid,
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
snackBar(error?.response?.data?.message || '사용자 정보 저장에 실패했습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
console.log("UserLoginAuthInfoWrap", mid, usrid, idCl, status);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
ChangeCancelPasswordParams,
|
||||
ChangeCancelPasswordResponse
|
||||
} from '../model/types';
|
||||
import {
|
||||
useMutation,
|
||||
UseMutationOptions
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
export const userChangeCancelPassword = (params: ChangeCancelPasswordParams) => {
|
||||
return resultify(
|
||||
axios.post<ChangeCancelPasswordResponse>(API_URL_USER.userChangeCancelPassword(), params),
|
||||
);
|
||||
};
|
||||
|
||||
export const useUserChangeCancelPasswordMutation = (options?: UseMutationOptions<ChangeCancelPasswordResponse, CBDCAxiosError, ChangeCancelPasswordParams>) => {
|
||||
const mutation = useMutation<ChangeCancelPasswordResponse, CBDCAxiosError, ChangeCancelPasswordParams>({
|
||||
...options,
|
||||
mutationFn: (params: ChangeCancelPasswordParams) => userChangeCancelPassword(params),
|
||||
});
|
||||
|
||||
return {
|
||||
...mutation,
|
||||
};
|
||||
};
|
||||
@@ -133,9 +133,9 @@ export interface UserMenuPermissionData {
|
||||
}
|
||||
|
||||
export interface ChangePasswordParams {
|
||||
mid: string;
|
||||
usrid: string;
|
||||
password: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export interface ChangePasswordResponse {
|
||||
@@ -143,6 +143,16 @@ export interface ChangePasswordResponse {
|
||||
error?: ErrorResponse;
|
||||
}
|
||||
|
||||
export interface ChangeCancelPasswordParams {
|
||||
mid: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface ChangeCancelPasswordResponse {
|
||||
status: boolean;
|
||||
error?: ErrorResponse;
|
||||
}
|
||||
|
||||
export interface VerificationsItem {
|
||||
type: string;
|
||||
contact: string;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { UserMenuAuthPage } from './user/menu-auth-page';
|
||||
import { UserAddAccountPage } from './user/add-account-page';
|
||||
import { PasswordManagePage } from './password/manage-page';
|
||||
import { PasswordModifyLoginPasswordPage } from './password/modify-login-password-page';
|
||||
import { PasswordModifyCancelPasswordPage } from './password/modify-cancel-password-page';
|
||||
|
||||
export const AccountPages = () => {
|
||||
|
||||
@@ -24,6 +25,7 @@ export const AccountPages = () => {
|
||||
<Route path={ROUTE_NAMES.account.password.base}>
|
||||
<Route path={ROUTE_NAMES.account.password.manage} element={<PasswordManagePage />} />
|
||||
<Route path={ROUTE_NAMES.account.password.modifyLoginPassword} element={<PasswordModifyLoginPasswordPage />} />
|
||||
<Route path={ROUTE_NAMES.account.password.modifyCancelPassword} element={<PasswordModifyCancelPasswordPage />} />
|
||||
</Route>
|
||||
</SentryRoutes>
|
||||
</>
|
||||
|
||||
121
src/pages/account/password/modify-cancel-password-page.tsx
Normal file
121
src/pages/account/password/modify-cancel-password-page.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
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 {
|
||||
useSetHeaderTitle,
|
||||
useSetHeaderType,
|
||||
useSetFooterMode,
|
||||
useSetOnBack
|
||||
} from '@/widgets/sub-layout/use-sub-layout';
|
||||
import { useUserChangeCancelPasswordMutation } from '@/entities/user/api/use-user-change-cancel-password-mutation';
|
||||
import { snackBar } from '@/shared/lib/toast';
|
||||
|
||||
export const PasswordModifyCancelPasswordPage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const [mid, setMid] = useState<string>('nictest00m');
|
||||
const [newPassword, setNewPassword] = useState<string>('');
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>('');
|
||||
|
||||
const changeCancelPasswordMutation = useUserChangeCancelPasswordMutation({
|
||||
onSuccess: () => {
|
||||
snackBar('비밀번호가 성공적으로 변경되었습니다.');
|
||||
// Clear form
|
||||
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);
|
||||
useSetFooterMode(false);
|
||||
useSetOnBack(() => {
|
||||
navigate(PATHS.account.password.manage);
|
||||
});
|
||||
|
||||
// 저장 버튼 활성화 조건 체크
|
||||
const isFormValid = () => {
|
||||
return (
|
||||
newPassword.length >= 8 &&
|
||||
confirmPassword.length >= 8 &&
|
||||
newPassword === confirmPassword
|
||||
);
|
||||
};
|
||||
|
||||
// 저장 버튼 클릭 핸들러
|
||||
const handleSave = () => {
|
||||
if (!isFormValid()) return;
|
||||
|
||||
// TODO: Validate current password before submitting
|
||||
changeCancelPasswordMutation.mutate({
|
||||
mid,
|
||||
password: newPassword
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<main>
|
||||
<div className="tab-content">
|
||||
<div className="tab-pane sub active">
|
||||
<div className="ing-list add">
|
||||
<div className="user-add">
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">가맹점 <span className="red">*</span></div>
|
||||
<select className="wid-100" value={mid} onChange={(e) => setMid(e.target.value)}>
|
||||
{midList.map((item) => (
|
||||
<option key={item.value} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">변경 비밀번호 <span className="red">*</span></div>
|
||||
<input
|
||||
className={`wid-100 ${confirmPassword && newPassword !== confirmPassword ? 'error' : ''}`}
|
||||
type="password"
|
||||
placeholder=""
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">변경 비밀번호 재입력 <span className="red">*</span></div>
|
||||
<input
|
||||
className={`wid-100 ${confirmPassword && newPassword !== confirmPassword ? 'error' : ''}`}
|
||||
type="password"
|
||||
placeholder=""
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{confirmPassword && newPassword !== confirmPassword && (
|
||||
<div className="ua-help error">입력 정보 불일치</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="apply-row bottom-padding">
|
||||
<button
|
||||
className="btn-50 btn-blue flex-1"
|
||||
type="button"
|
||||
disabled={!isFormValid() || changeCancelPasswordMutation.isPending}
|
||||
onClick={handleSave}
|
||||
>{changeCancelPasswordMutation.isPending ? '처리중...' : '저장'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -13,11 +13,10 @@ import { snackBar } from '@/shared/lib/toast';
|
||||
|
||||
export const PasswordModifyLoginPasswordPage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const [mid, setMid] = useState<string>('nictest00m');
|
||||
const [currentPassword, setCurrentPassword] = useState<string>('');
|
||||
const [newPassword, setNewPassword] = useState<string>('');
|
||||
const [confirmPassword, setConfirmPassword] = useState<string>('');
|
||||
const [usrid] = useState<string>(''); // TODO: Get actual user ID from context/session
|
||||
const [usrid, ] = useState<string>('nictest00');
|
||||
|
||||
const changePasswordMutation = useUserChangePasswordMutation({
|
||||
onSuccess: () => {
|
||||
@@ -34,12 +33,6 @@ export const PasswordModifyLoginPasswordPage = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const midList = [
|
||||
{ value: 'nictest00m', label: 'nictest00m' },
|
||||
{ value: 'nictest01m', label: 'nictest01m' },
|
||||
{ value: 'nictest02m', label: 'nictest02m' },
|
||||
];
|
||||
|
||||
useSetHeaderTitle('로그인 비밀번호 변경');
|
||||
useSetHeaderType(HeaderType.LeftArrow);
|
||||
useSetFooterMode(false);
|
||||
@@ -63,9 +56,9 @@ export const PasswordModifyLoginPasswordPage = () => {
|
||||
|
||||
// TODO: Validate current password before submitting
|
||||
changePasswordMutation.mutate({
|
||||
mid,
|
||||
usrid: usrid || 'test', // TODO: Get actual user ID
|
||||
password: newPassword
|
||||
usrid: usrid,
|
||||
password: newPassword,
|
||||
newPassword: newPassword,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -76,14 +69,6 @@ export const PasswordModifyLoginPasswordPage = () => {
|
||||
<div className="tab-pane sub active">
|
||||
<div className="ing-list add">
|
||||
<div className="user-add">
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">가맹점 <span className="red">*</span></div>
|
||||
<select className="wid-100" value={mid} onChange={(e) => setMid(e.target.value)}>
|
||||
{midList.map((item) => (
|
||||
<option key={item.value} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="ua-row">
|
||||
<div className="ua-label">기존 비밀번호 <span className="red">*</span></div>
|
||||
<input
|
||||
|
||||
@@ -12,12 +12,21 @@ 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';
|
||||
import { snackBar } from '@/shared/lib/toast';
|
||||
|
||||
export const UserAddAccountPage = () => {
|
||||
const { navigate } = useNavigate();
|
||||
const location = useLocation();
|
||||
const { mid } = location.state || {};
|
||||
const { mutateAsync: userCreate, isPending } = useUserCreateMutation();
|
||||
|
||||
const { mutateAsync: userCreate, isPending } = useUserCreateMutation({
|
||||
onSuccess: () => {
|
||||
snackBar('사용자가 성공적으로 추가되었습니다.');
|
||||
},
|
||||
onError: (error) => {
|
||||
snackBar(error?.response?.data?.message || '사용자 추가에 실패했습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
// 폼 상태 관리
|
||||
const [formData, setFormData] = useState({
|
||||
@@ -373,6 +382,7 @@ export const UserAddAccountPage = () => {
|
||||
|
||||
if (response.status) {
|
||||
// 성공 시 사용자 관리 페이지로 이동
|
||||
snackBar('사용자가 성공적으로 추가되었습니다.');
|
||||
navigate(PATHS.account.user.manage);
|
||||
} else if (response.error) {
|
||||
// 에러 처리
|
||||
|
||||
@@ -12,6 +12,7 @@ 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';
|
||||
import { snackBar } from '@/shared/lib/toast';
|
||||
|
||||
// 권한 비트 플래그 (실제 API 데이터 기준)
|
||||
const PERMISSION = {
|
||||
@@ -30,8 +31,15 @@ export const UserMenuAuthPage = () => {
|
||||
const [permissions, setPermissions] = useState<Record<number, number>>({});
|
||||
const [initialPermissions, setInitialPermissions] = useState<Record<number, number>>({});
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const savePermissionsMutation = useUserMenuPermissionsSaveMutation();
|
||||
// const getPermissionsMutation = useUserMenuPermissionsMutation();
|
||||
const savePermissionsMutation = useUserMenuPermissionsSaveMutation({
|
||||
onSuccess: () => {
|
||||
snackBar('권한이 성공적으로 저장되었습니다.');
|
||||
navigate(PATHS.account.password.manage);
|
||||
},
|
||||
onError: (error) => {
|
||||
snackBar(error?.response?.data?.message || '권한 저장에 실패했습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
useSetHeaderTitle(menuName);
|
||||
useSetHeaderType(HeaderType.LeftArrow);
|
||||
|
||||
@@ -41,4 +41,7 @@ export const API_URL_USER = {
|
||||
changePassword: () => {
|
||||
return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/password`;
|
||||
},
|
||||
userChangeCancelPassword: () => {
|
||||
return `${API_BASE_URL}/api/v1/${API_URL_KEY}/user/cancel/change`;
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,10 @@ export const PATHS: RouteNamesType = {
|
||||
`${ROUTE_NAMES.account.base}${ROUTE_NAMES.account.password.base}`,
|
||||
ROUTE_NAMES.account.password.modifyLoginPassword,
|
||||
),
|
||||
modifyCancelPassword: generatePath(
|
||||
`${ROUTE_NAMES.account.base}${ROUTE_NAMES.account.password.base}`,
|
||||
ROUTE_NAMES.account.password.modifyCancelPassword,
|
||||
),
|
||||
}
|
||||
},
|
||||
vatReturn: {
|
||||
|
||||
@@ -55,8 +55,9 @@ export const ROUTE_NAMES = {
|
||||
password: {
|
||||
base: '/password/*',
|
||||
manage: 'manage',
|
||||
modifyLoginPassword: 'modifyLoginPassword'
|
||||
}
|
||||
modifyLoginPassword: 'change-login-password',
|
||||
modifyCancelPassword: 'change-cancel-password'
|
||||
},
|
||||
},
|
||||
vatReturn: {
|
||||
base: '/vat-return/*',
|
||||
|
||||
Reference in New Issue
Block a user