작업중

This commit is contained in:
focp212@naver.com
2025-09-29 16:59:26 +09:00
parent 74ab29f80a
commit 0a747469a6
18 changed files with 313 additions and 111 deletions

View File

@@ -33,4 +33,66 @@ export const BottomSheetMotionVaiants = {
};
export const BottomSheetMotionDuration = {
duration: 0.3
};
};
export const MenuItems = [
{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문의'},
]
},
]

View File

@@ -7,18 +7,22 @@ export interface BannerInfoState {
setBannerInfo: (update: SetStateAction<Partial<BannerInfo>>) => void;
};
const initialState = {
const initialBannerInfoState = {
bannerInfo: {} as BannerInfo,
} as BannerInfoState;
export const createBannerInfoStore = lens<BannerInfoState>((set, get) => ({
...initialState,
...initialBannerInfoState,
setBannerInfo: (update) => {
set((state: BannerInfoState) => {
const newBannerInfo = typeof update === 'function' ? update(state.bannerInfo) : update;
const newBannerInfo = (typeof update === 'function')
? update(state.bannerInfo): update;
return {
...state,
bannerInfo: { ...state.bannerInfo, ...newBannerInfo },
bannerInfo: {
...state.bannerInfo,
...newBannerInfo
},
};
});
},

View File

@@ -69,11 +69,13 @@ export interface HeaderNavigationProps {
menuOn: boolean;
headerType: HeaderType;
setMenuOn: (menuOn: boolean) => void;
favoriteEdit?: boolean;
};
export interface FooterProps {
setMenuOn: (menuOn: boolean) => void;
footerCurrentPage?: string | null;
setFavoriteEdit: (favoriteEdit: boolean) => void;
};
export enum FooterItemActiveKey {
Home = 'Home',

View File

@@ -3,10 +3,6 @@ import {
NoticeListResponse
} from '@/entities/support/model/types';
export interface FavoriteItemProps {
img?: string,
text?: string
};
export interface HomeBottomBannerProps {
setBottomBannerOn: (bottomBannerOn: boolean) => void;
bottomBannerOn: boolean;

View File

@@ -1,17 +0,0 @@
import { FavoriteItemProps } from '../model/types';
export const FavoriteItem = ({
img,
text
}: FavoriteItemProps) => {
return (
<>
<div className="swiper-item">
<div className="swiper-icon coin-icon">
<img src={ img } alt={ text } />
</div>
<span className="swiper-text">{ text }</span>
</div>
</>
);
};

View File

@@ -1,56 +1,98 @@
import { useEffect, useState } from 'react';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { FavoriteItem } from './favorite-item';
import { FavoriteItemProps } from '../model/types'
import { UserFavorite } from '@/entities/user/model/types';
import { useStore } from '@/shared/model/store';
/*
const items: Array<UserFavorite> = [
{img: IMAGE_ROOT + '/ico_menu_01.svg', title: '지급대행'},
{img: IMAGE_ROOT + '/ico_menu_02.svg', title: '거래내역조회'},
{img: IMAGE_ROOT + '/ico_menu_03.svg', title: '정산달력'},
{img: IMAGE_ROOT + '/ico_menu_02.svg', title: '거래내역조회'},
{img: IMAGE_ROOT + '/ico_menu_03.svg', title: '정산달력'}
];
*/
export interface FavoriteWrapperProps {
usingType: 'home' | 'menu'
};
export const FavoriteWrapper = () => {
const items: Array<FavoriteItemProps> = [
{img: IMAGE_ROOT + '/ico_menu_01.svg', text: '지급대행'},
{img: IMAGE_ROOT + '/ico_menu_02.svg', text: '거래내역조회'},
{img: IMAGE_ROOT + '/ico_menu_03.svg', text: '정산달력'},
{img: IMAGE_ROOT + '/ico_menu_02.svg', text: '거래내역조회'},
{img: IMAGE_ROOT + '/ico_menu_03.svg', text: '정산달력'}
];
export const FavoriteWrapper = ({
usingType
}: FavoriteWrapperProps) => {
const { navigate } = useNavigate();
const itemAdd = {
const [edit, setEdit] = useState<boolean>(false);
const [favoriteItems, setFavoriteItems] = useState<Array<UserFavorite>>([]);
const itemAdd: UserFavorite = {
img: IMAGE_ROOT + '/ico_menu_plus.svg',
text: '편집하기'
title: '편집하기'
};
const onClickToFavoriteEdit = () => {
setEdit(true);
};
const getItems = () => {
//useSetFavoriteEdit(true);
//useSetMenuOn(true);
const onClickToNavigate = (path?: string) => {
if(!!path){
navigate(path);
}
};
const getFavoriteItems = () => {
let rs = [];
for(let i=0;i<items.length;i++){
for(let i=0;i<favoriteItems.length;i++){
rs.push(
<SwiperSlide
key={ 'slide-key-'+i }
>
<FavoriteItem
img={ items[i]?.img }
text={ items[i]?.text }
></FavoriteItem>
<SwiperSlide key={ `favorite-slide-key-${i}` }>
<div
className="swiper-item"
onClick={ () => onClickToNavigate(favoriteItems[i]?.path) }
>
<div className="swiper-icon coin-icon">
<img
src={ favoriteItems[i]?.img }
alt={ favoriteItems[i]?.title }
/>
</div>
<span className="swiper-text">{ favoriteItems[i]?.title }</span>
</div>
</SwiperSlide>
);
}
rs.push(
<SwiperSlide key={ `favorite-item-add-slide-key` }>
<div
className="swiper-item"
onClick={ onClickToFavoriteEdit }
>
<div className="swiper-icon coin-icon">
<img
src={ itemAdd.img }
alt={ itemAdd.title }
/>
</div>
<span className="swiper-text">{ itemAdd.title }</span>
</div>
</SwiperSlide>
);
return rs;
};
return (
<>
<Swiper
spaceBetween={9}
slidesPerView={4}
>
{ getItems() }
<SwiperSlide>
<FavoriteItem
img={ itemAdd.img }
text={ itemAdd.text }
></FavoriteItem>
</SwiperSlide>
</Swiper>
spaceBetween={ 9 }
slidesPerView={ 4 }
>{ getFavoriteItems() }</Swiper>
</>
);
};

View File

@@ -3,8 +3,10 @@ export interface MenuCategoryItem {
path: string;
};
export interface MenuCategoryProps {
key: string;
category: string;
categoryIcon?: string;
items: Array<MenuCategoryItem>;
setMenuOn: (menuOn: boolean) => void;
favoriteEdit?: boolean;
};

View File

@@ -1,35 +1,70 @@
import { PATHS } from '@/shared/constants/paths';
import { IMAGE_ROOT } from '@/shared/constants/common';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { MenuCategoryProps } from '../model/types';
import { useStore } from '@/shared/model/store';
import { IMAGE_ROOT } from '@/shared/constants/common';
export const MenuCategory = ({
category,
categoryIcon,
items,
setMenuOn
setMenuOn,
favoriteEdit
}: MenuCategoryProps) => {
const { navigate } = useNavigate();
const onClickToNavigate = (path?: string) => {
if(!!path){
if(!!path && !favoriteEdit){
setMenuOn(false);
navigate(path);
}
};
const favoriteSetting = (
checked: boolean,
title?: string,
path?: string
) => {
useStore.getState().UserStore.setUserFavorite([{
title: title,
img: IMAGE_ROOT + '/ico_menu_01.svg',
path: path,
}]);
};
const getMenuItems = () => {
let rs = [];
for(let i=0;i<items.length;i++){
let title = items[i]?.title;
let path = items[i]?.path;
let key = 'menu-item-key-'+i;
rs.push(
<li
key={ key }
onClick={ () => onClickToNavigate(path) }
>{ title }</li>
);
if(!!favoriteEdit){
rs.push(
<li
key={ `menu-item-key-${category}-${i}` }
onClick={ () => onClickToNavigate(items[i]?.path) }
>
<span>{ items[i]?.title }</span>
<div className="check_box_scrap">
<input
id={ `menu-item-checkbox-${category}-${i}` }
className="checkbox"
type="checkbox"
onChange={ (e) => favoriteSetting(e.target.checked, items[i]?.title, items[i]?.path) }
/>
<label
className="gtr"
htmlFor={ `menu-item-checkbox-${category}-${i}` }
></label>
</div>
</li>
);
}
else{
rs.push(
<li
key={ `menu-item-key-${i}` }
onClick={ () => onClickToNavigate(items[i]?.path) }
>{ items[i]?.title }</li>
);
}
}
return rs;
};

View File

@@ -1,26 +1,33 @@
import { lens } from '@dhmk/zustand-lens';
import { SetStateAction } from 'react';
import { UserInfo } from './types';
import { UserFavorite, UserInfo } from './types';
import { StorageKeys } from '@/shared/constants/local-storage';
export interface UserInfoState {
userInfo: UserInfo;
setUserInfo: (update: SetStateAction<Partial<UserInfo>>) => void;
resetUserInfo: () => void;
userFavorite: Array<UserFavorite>;
setUserFavorite: (update: SetStateAction<Array<UserFavorite>>) => void;
};
const initialState = {
const initialUserInfoState = {
userInfo: {} as UserInfo,
userFavorite: [] as Array<UserFavorite>
} as UserInfoState;
export const createUserInfoStore = lens<UserInfoState>((set, get) => ({
...initialState,
...initialUserInfoState,
setUserInfo: (update) => {
set((state: UserInfoState) => {
const newUserInfo = typeof update === 'function' ? update(state.userInfo) : update;
const newUserInfo = (typeof update === 'function')
? update(state.userInfo): update;
return {
...state,
userInfo: { ...state.userInfo, ...newUserInfo },
userInfo: {
...state.userInfo,
...newUserInfo
},
};
});
},
@@ -34,6 +41,19 @@ export const createUserInfoStore = lens<UserInfoState>((set, get) => ({
window.localStorage.removeItem(StorageKeys.Usrid);
// window.localStorage.removeItem(StorageKeys.ClientAddressIP);
// window.localStorage.removeItem(StorageKeys.Requires2FA);
set(initialState);
set(initialUserInfoState);
},
setUserFavorite: (update) => {
set((state: UserInfoState) => {
const newUserFavorite = (typeof update === 'function')
? update(state.userFavorite): update;
return {
...state,
userFavorite: {
...state.userFavorite,
...newUserFavorite
},
};
});
},
}));

View File

@@ -28,6 +28,11 @@ export interface LoginResponse {
requires2FA?: boolean;
};
export interface UserFavorite {
title?: string;
img?: string;
path?: string;
};
export interface UserInfo extends LoginResponse {
status: boolean;
error?: ErrorResponse;

View File

@@ -15,16 +15,25 @@ import {
useSetHeaderTitle,
useSetHeaderType,
useSetFooterMode,
useSetFooterCurrentPage
useSetFooterCurrentPage,
useSetFavoriteEdit,
useSetMenuOn
} from '@/widgets/sub-layout/use-sub-layout';
export const HomePage = () => {
const { callLogin } = useUserInfo();
const { isNativeEnvironment, openBiometricRegistrationPopup, requestToken, logout } = useAppBridge();
const {
isNativeEnvironment,
openBiometricRegistrationPopup,
requestToken,
logout
} = useAppBridge();
useSetHeaderTitle('');
useSetHeaderType(HeaderType.Home);
useSetFooterMode(true);
useSetFooterCurrentPage(FooterItemActiveKey.Home);
//useSetFavoriteEdit(true);
//useSetMenuOn(true);
const today = moment().format('YYYYMMDD').toString();
let bannerToday = getLocalStorage(StorageKeys.BottomBannerClose);
@@ -189,7 +198,9 @@ export const HomePage = () => {
<div className="tab-pane dashboard active">
{ loginSuccess &&
<>
<FavoriteWrapper></FavoriteWrapper>
<FavoriteWrapper
usingType='home'
></FavoriteWrapper>
<DayStatusBox></DayStatusBox>
</>
}

View File

@@ -305,6 +305,13 @@ export const PATHS: RouteNamesType = {
ROUTE_NAMES.additionalService.payout.request,
),
},
faceAuth: {
base: generatePath(`${ROUTE_NAMES.additionalService.base}${ROUTE_NAMES.additionalService.faceAuth.base}`),
list: generatePath(
`${ROUTE_NAMES.additionalService.base}${ROUTE_NAMES.additionalService.faceAuth.base}`,
ROUTE_NAMES.additionalService.faceAuth.list,
),
}
},
support: {
base: generatePath(ROUTE_NAMES.support.base),

View File

@@ -131,6 +131,10 @@ export const ROUTE_NAMES = {
detail: 'detail',
request: 'request',
},
faceAuth: {
base: '/face-auth/*',
list: 'list',
}
},
support: {

View File

@@ -10,10 +10,12 @@ import { FilterMotionDuration, FilterMotionStyle, FilterMotionVariants } from '@
export interface MenuProps {
menuOn: boolean;
setMenuOn: (menuOn: boolean) => void;
favoriteEdit?: boolean;
};
export const Menu = ({
menuOn,
setMenuOn
setMenuOn,
favoriteEdit
}: MenuProps) => {
const { navigate } = useNavigate();
const userInfo = useStore((state) => state.UserStore.userInfo);
@@ -24,8 +26,7 @@ export const Menu = ({
};
const onClickToMenuClose = () => {
setMenuOn(false);
};
};
const menuCategoryItems = {
transaction: {
@@ -93,6 +94,7 @@ export const Menu = ({
{title: '자금이체', path: PATHS.additionalService.fundAccount.transferList},
{title: '정산대행', path: PATHS.additionalService.settlementAgency.manage},
{title: '지급대행', path: PATHS.additionalService.payout.list},
{title: '안면인증', path: PATHS.additionalService.faceAuth.list}
]
},
support: {
@@ -105,12 +107,7 @@ export const Menu = ({
]
},
}
const variants = {
hidden: { x: '100%' },
visible: { x: '0%' },
};
const getMenuCategory = () => {
let rs = [];
for (const [key, value] of Object.entries(menuCategoryItems)) {
@@ -121,6 +118,7 @@ export const Menu = ({
categoryIcon={ value.categoryIcon }
items={ value.items }
setMenuOn={ setMenuOn }
favoriteEdit={ favoriteEdit }
/>
);
}
@@ -139,25 +137,38 @@ export const Menu = ({
>
<div className="full-menu-container">
<div className="full-menu-header">
<div className="full-menu-title">{ 'nictest001m' } <span>(madzoneviper)</span></div>
<div className="full-menu-title">
{ 'nictest001m' }
<span style={{marginLeft: '4px'}}>(madzoneviper)</span>
</div>
<div className="full-menu-actions">
<button
className="full-menu-settings"
onClick={ () => onClickToNavigate(PATHS.setting) }
>
<img src={ IMAGE_ROOT + '/ico_set.svg' } alt="설정" />
<img
src={ IMAGE_ROOT + '/ico_set.svg' }
alt="설정"
/>
</button>
<button
className="full-menu-close"
onClick={ () => onClickToMenuClose() }
>
<img src={ IMAGE_ROOT + '/ico_close.svg' } alt="설정" />
<img
src={ IMAGE_ROOT + '/ico_close.svg' }
alt="설정"
/>
</button>
</div>
</div>
<div className="full-menu-top-nav">
{ <FavoriteWrapper></FavoriteWrapper> }
{
<FavoriteWrapper
usingType='menu'
></FavoriteWrapper>
}
</div>
<div>

View File

@@ -9,7 +9,8 @@ import { useEffect, useState } from 'react';
export const FooterNavigation = ({
setMenuOn,
footerCurrentPage
footerCurrentPage,
setFavoriteEdit
}: FooterProps) => {
const { navigate } = useNavigate();
const [isFooterOn, setIsFooterOn] = useState<boolean>(false);
@@ -20,6 +21,7 @@ export const FooterNavigation = ({
}
};
const onClickToOpenMenu = () => {
setFavoriteEdit(false);
setMenuOn(true);
};

View File

@@ -12,7 +12,8 @@ export const HeaderNavigation = ({
headerTitle,
menuOn,
headerType,
setMenuOn
setMenuOn,
favoriteEdit
}: HeaderNavigationProps) => {
const {
navigate,
@@ -49,6 +50,7 @@ export const HeaderNavigation = ({
<Menu
menuOn={ menuOn }
setMenuOn={ setMenuOn }
favoriteEdit={ favoriteEdit }
></Menu>
}
{

View File

@@ -9,14 +9,16 @@ import { PullToRefresh } from '@/widgets/pull-to-refresh/pull-to-refresh';
import { useNavigate } from '@/shared/lib/hooks';
import { useScrollToTop } from '@/shared/lib/hooks/use-scroll-to-top';
import { HeaderType } from '@/entities/common/model/types';
export interface ContextType {
setOnBack: (onBack: () => void) => void;
setHeaderTitle: (title: string) => void;
setIsPullToRefreshEnabled: (enabled: boolean) => void;
setMenuOn:(on: boolean) => void;
setMenuOn: (menuOn: boolean) => void;
setHeaderType: (headerType: HeaderType) => void;
setFooterMode:(footMode: boolean) => void;
setFooterCurrentPage:(currentPage?: string | null) => void;
setFooterMode: (footerMode: boolean) => void;
setFooterCurrentPage: (footerCurrentPage?: string | null) => void;
setFavoriteEdit: (favoriteEdit?: boolean) => void;
};
export const SubLayout = () => {
@@ -29,7 +31,8 @@ export const SubLayout = () => {
const [headerType, setHeaderType] = useState<HeaderType>(HeaderType.NoHeader);
const [footerMode, setFooterMode] = useState<boolean>(false);
const [footerCurrentPage, setFooterCurrentPage] = useState<undefined | string | null>(undefined);
const [favoriteEdit, setFavoriteEdit] = useState<boolean>(false);
const wrapperClassName = 'wrapper';
return (
@@ -45,6 +48,7 @@ export const SubLayout = () => {
menuOn={ menuOn }
setMenuOn={ setMenuOn }
headerType={ headerType }
favoriteEdit={ favoriteEdit }
/>
<Outlet
context={{
@@ -54,7 +58,8 @@ export const SubLayout = () => {
setMenuOn,
setHeaderType,
setFooterMode,
setFooterCurrentPage
setFooterCurrentPage,
setFavoriteEdit
}}
/>
{
@@ -62,6 +67,7 @@ export const SubLayout = () => {
<FooterNavigation
setMenuOn={ setMenuOn }
footerCurrentPage={ footerCurrentPage }
setFavoriteEdit={ setFavoriteEdit }
></FooterNavigation>
}

View File

@@ -1,9 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { ReactNode, useEffect } from 'react';
import { useEffect } from 'react';
import { useOutletContext } from 'react-router';
import { ContextType } from '.';
import { FooterItemActiveKey } from '@/entities/common/model/types';
import { HeaderType } from '@/entities/common/model/types';
import { ContextType } from '.';
export const useSubLayoutContext = () => {
return useOutletContext<ContextType>();
@@ -25,7 +24,7 @@ export const useSetHeaderTitle = (title: string) => {
useEffect(() => {
setHeaderTitle(title);
return () => setHeaderTitle('');
}, [setHeaderTitle]);
}, [title, setHeaderTitle]);
return { setHeaderTitle };
};
@@ -34,16 +33,16 @@ export const useSetIsPullToRefreshEnabled = (enabled: boolean) => {
useEffect(() => {
setIsPullToRefreshEnabled(enabled);
return () => setIsPullToRefreshEnabled(false);
}, [setIsPullToRefreshEnabled, enabled]);
}, [enabled, setIsPullToRefreshEnabled]);
return { setIsPullToRefreshEnabled };
};
export const useSetMenuOn = (on: boolean) => {
export const useSetMenuOn = (menuOn: boolean) => {
const { setMenuOn } = useSubLayoutContext();
useEffect(() => {
setMenuOn(on);
setMenuOn(menuOn);
return () => setMenuOn(false);
}, [setMenuOn, on]);
}, [menuOn, setMenuOn]);
return { setMenuOn };
};
@@ -52,16 +51,25 @@ export const useSetHeaderType = (headerType: HeaderType) => {
useEffect(() => {
setHeaderType(headerType);
return () => setHeaderType(HeaderType.NoHeader);
}, [setHeaderType]);
}, [headerType, setHeaderType]);
return { setHeaderType };
};
export const useSetFavoriteEdit = (favoriteEdit: boolean) => {
const { setFavoriteEdit } = useSubLayoutContext();
useEffect(() => {
setFavoriteEdit(favoriteEdit);
return () => setFavoriteEdit(false);
}, [favoriteEdit, setFavoriteEdit]);
return { setFavoriteEdit }
};
export const useSetFooterMode = (footerMode: boolean) => {
const { setFooterMode } = useSubLayoutContext();
useEffect(() => {
setFooterMode(footerMode);
return () => setFooterMode(false);
}, [setFooterMode, footerMode]);
}, [footerMode, setFooterMode]);
return { setFooterMode };
};
@@ -70,6 +78,6 @@ export const useSetFooterCurrentPage = (footerCurrentPage?: FooterItemActiveKey
useEffect(() => {
setFooterCurrentPage(footerCurrentPage);
return () => setFooterCurrentPage(undefined);
}, [setFooterCurrentPage, footerCurrentPage]);
}, [footerCurrentPage, setFooterCurrentPage]);
return { setFooterCurrentPage };
};