Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…roject/WEB1_1_ZeroOne_FE into feat/#120-chatting-api
  • Loading branch information
joarthvr committed Jan 22, 2025
2 parents 730ec03 + f52e066 commit d66afa8
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 106 deletions.
3 changes: 2 additions & 1 deletion src/features/auth/lib/form.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ export const profileFormValidation = formValidation.shape({
.matches(
/^\s*$|^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\.[a-zA-Z]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)?$/gm,
'URL 형식에 맞게 입력해주세요.',
),
)
.required('포트폴리오 URL을 입력해주세요.'),
});

export const profileFormConfig: FormConfigType<PortfolioFormValues> = {
Expand Down
5 changes: 3 additions & 2 deletions src/features/portfolio/hooks/usePortfolioList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ export const usePortfolioList = ({
},

getNextPageParam: lastPage => {
console.log(lastPage);
// 더 엄격한 조건 체크
if (!lastPage || !lastPage.content || lastPage.last) {
if (!lastPage || !lastPage.content) {
return undefined;
}
// 현재 페이지의 아이템이 size보다 적으면 마지막 페이지
if (lastPage.content.length < size) {
return undefined;
}
return lastPage.number + 1;
return lastPage.offset / size + 1;
},

initialPageParam: 0,
Expand Down
11 changes: 3 additions & 8 deletions src/features/portfolio/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,9 @@ export interface Sort {
// API Response Interface
export interface PortfolioResponse {
content: Portfolio[];
pageable: Pageable;
size: number;
number: number;
sort: Sort;
numberOfElements: number;
first: boolean;
last: boolean;
empty: boolean;
hasNext: boolean;
offset: number;
pageSize: number;
}
export type PortfolioListApiResponse = ApiResponse<PortfolioResponse>;

Expand Down
5 changes: 4 additions & 1 deletion src/features/user/model/user.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export const useUserStore = create(
});
},
clearUserData: () => {
set(initialState);
set({
userData: null,
loading: false,
});
},
load: () => {
set({ loading: true });
Expand Down
12 changes: 0 additions & 12 deletions src/pages/MyPage/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import { useNavigate } from 'react-router-dom';

import styles from './MyPage.module.scss';

import { useRoleGuard } from '@/shared/hook/useRoleGuard';
import { ContentLayout, SideTab, useMyTab } from '@/widgets';

export const MyPage = () => {
const navigate = useNavigate();
// REAL_NEWBIE면 register page로 redirect
useRoleGuard({
requiredRoles: ['JUST_NEWBIE', 'ADMIN', 'OLD_NEWBIE', 'USER'],
onAccessDenied: () => {
navigate('/register');
},
});

const { activeTabItem, isActivePath } = useMyTab();
return (
<div className={styles.myPageContainer}>
Expand Down
12 changes: 11 additions & 1 deletion src/shared/hook/useRegistAlarm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ import { useUserStore } from '@/features/user/model/user.store';

const excludePath = ['/register', '/my'];

const checkPath = (path: string, pathList: string[]) => {
for (const excludePath of pathList) {
if (path.startsWith(excludePath)) {
return false;
}
}

return true;
};

export const useRegistAlarm = () => {
const navigate = useNavigate();
const location = useLocation();
const userData = useUserStore(state => state.userData);

useEffect(() => {
if (!userData) return;
if (userData.role === 'REAL_NEWBIE' && !excludePath.includes(location.pathname)) {
if (userData.role === 'REAL_NEWBIE' && checkPath(location.pathname, excludePath)) {
void customConfirm({
title: '유저 등록',
text: '아직 등록된 유저 프로필이 없습니다!\n프로필을 등록해주세요.',
Expand Down
5 changes: 3 additions & 2 deletions src/shared/hook/useRoleGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { useShallow } from 'zustand/shallow';

import { customConfirm } from '../ui';

import type { UserDataState } from '@/features/user/model/user.store';
import { useUserStore } from '@/features/user/model/user.store';
import type { UserRole } from '@/features/user/user.dto';

interface UseRoleGuardProps {
requiredRoles: UserRole[]; // 허용된 역할 목록
onAccessDenied?: () => void; // 접근 제한 시 실행할 이벤트
onAccessDenied?: (userData: UserDataState | null) => void; // 접근 제한 시 실행할 이벤트
}

export const useRoleGuard = ({ requiredRoles, onAccessDenied }: UseRoleGuardProps) => {
Expand Down Expand Up @@ -37,7 +38,7 @@ export const useRoleGuard = ({ requiredRoles, onAccessDenied }: UseRoleGuardProp

if (!userData || !requiredRoles.includes(userData.role)) {
if (onAccessDenied) {
onAccessDenied();
onAccessDenied(userData);
} else {
void defaultDeniedHandler();
}
Expand Down
1 change: 1 addition & 0 deletions src/widgets/Layout/ui/Header/Header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
flex-basis: 1;
column-gap: 3rem;
align-items: center;
min-width: 15rem;
}
}

Expand Down
90 changes: 47 additions & 43 deletions src/widgets/Layout/ui/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export const Header = () => {
const { pathname } = useLocation();
const navigate = useNavigate();
const open = useModalStore(state => state.actions.open);
const { userData } = useUserStore(
const { userData, loading } = useUserStore(
useShallow(state => ({
userData: state.userData,
actions: state.actions,
loading: state.loading,
})),
);
const [isMobile, setIsMobile] = useState(false);
Expand Down Expand Up @@ -200,51 +200,55 @@ export const Header = () => {
))}
</ul>
</nav>
<div className={styles.userMenu}>
<FontAwesomeIcon
className={cn(styles.button, styles.search)}
icon={faSearch}
onClick={toggleSearch}
/>
<div className={cn(styles.bell, { [styles.isNewNotice]: isNewNotice })}>
{loading ? (
<div className={styles.userMenu}></div>
) : (
<div className={styles.userMenu}>
<FontAwesomeIcon
className={cn(styles.button, styles.bell)}
icon={faBell}
onClick={toggleNoti}
className={cn(styles.button, styles.search)}
icon={faSearch}
onClick={toggleSearch}
/>
</div>
<FontAwesomeIcon
className={cn(styles.button, styles.heart)}
icon={faHeart}
onClick={() => {
if (userData) navigate('/like');
else {
void customConfirm({
text: '로그인이 필요합니다.',
title: '로그인',
icon: 'info',
});
}
}}
/>
{userData ? (
<button
onClick={() => {
navigate(`/user/${userData.userId}`);
}}
>
<img alt='user-profile' className={styles.userProfile} src={userData.imageUrl} />
</button>
) : (
<Button
<div className={cn(styles.bell, { [styles.isNewNotice]: isNewNotice })}>
<FontAwesomeIcon
className={cn(styles.button, styles.bell)}
icon={faBell}
onClick={toggleNoti}
/>
</div>
<FontAwesomeIcon
className={cn(styles.button, styles.heart)}
icon={faHeart}
onClick={() => {
open('login');
if (userData) navigate('/like');
else {
void customConfirm({
text: '로그인이 필요합니다.',
title: '로그인',
icon: 'info',
});
}
}}
>
로그인
</Button>
)}
</div>{' '}
/>
{userData ? (
<button
onClick={() => {
navigate(`/user/${userData.userId}`);
}}
>
<img alt='user-profile' className={styles.userProfile} src={userData.imageUrl} />
</button>
) : (
<Button
onClick={() => {
open('login');
}}
>
로그인
</Button>
)}
</div>
)}
{isSearch && (
<div className={cn(styles.searchWrapper, { [styles.visible]: isSearch })}>
<SearchBar isSearch setIsSearch={setIsSearch} />
Expand Down
4 changes: 4 additions & 0 deletions src/widgets/PortfolioGrid/PortFolioGrid.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@
gap: 1.25rem;
width: 100%;
}

.loading {
padding: 2.5rem 0;
}
39 changes: 9 additions & 30 deletions src/widgets/PortfolioGrid/PortFolioGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PortfolioCard } from '@/features';
import { usePortfolioList } from '@/features/portfolio/hooks/usePortfolioList';
import type { PortfolioParams } from '@/features/portfolio/model/types';
import { useIntersectionObserver } from '@/shared/hook/useIntersectionObserver';
import { Loader } from '@/shared/ui';
import { TripleDot } from '@/shared/ui';

export const PortFolioGrid = ({ params }: { params: PortfolioParams }) => {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status, isError } =
Expand All @@ -23,40 +23,19 @@ export const PortFolioGrid = ({ params }: { params: PortfolioParams }) => {
if (isError) return <div>Error loading portfolios</div>;

const portfolios = data?.pages?.flatMap(page => page?.content ?? []).filter(Boolean) ?? [];
if (portfolios.length === 0) {
return (
<div>
<Loader />
검색된 내용이 없습니다.
</div>
);
}

return (
<div className={styles.container}>
<div className={styles.list}>
{portfolios.map(portfolio => (
<PortfolioCard key={portfolio.portFolioId} {...portfolio} />
))}
{portfolios.length > 0 ? (
portfolios.map(portfolio => <PortfolioCard key={portfolio.portFolioId} {...portfolio} />)
) : (
<div>검색 결과가 없습니다.</div>
)}
</div>
<div
ref={ref}
style={{
width: '100%',
height: '100px',
marginTop: '20px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{isFetchingNextPage ? (
<div>
<Loader />
</div>
) : hasNextPage ? (
<div style={{ visibility: 'hidden' }}>Intersection Observer Trigger</div>
) : null}

<div className={styles.loading} ref={ref}>
{isFetchingNextPage && <TripleDot />}
</div>
</div>
);
Expand Down
25 changes: 21 additions & 4 deletions src/widgets/SettingUser/ContentLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,33 @@ interface ContentLayoutProps {
export const ContentLayout = ({ activeTab }: ContentLayoutProps) => {
// 로그인한 유저만 접근 가능
const { userData } = useRoleGuard({
requiredRoles: ['ADMIN', 'JUST_NEWBIE', 'OLD_NEWBIE', 'REAL_NEWBIE', 'USER'],
onAccessDenied: () => {
requiredRoles: ['ADMIN', 'JUST_NEWBIE', 'OLD_NEWBIE', 'USER'],
onAccessDenied: userData => {
if (!userData) {
void customConfirm({
title: '잘못된 접근',
text: '유저 정보를 확인할 수 없습니다.\n로그인하고 다시 시도해주세요.',
icon: 'warning',
showCancelButton: false,
allowOutsideClick: false,
}).then(result => {
if (result.isConfirmed) {
navigate('/');
return;
}
});
return;
}

void customConfirm({
title: '잘못된 접근',
text: '유저 정보를 확인할 수 없습니다.\n로그인하고 다시 시도해주세요.',
text: '유저 정보를 확인할 수 없습니다.\n회원 정보를 먼저 등록해주세요.',
icon: 'warning',
showCancelButton: false,
allowOutsideClick: false,
}).then(result => {
if (result.isConfirmed) {
navigate('/');
navigate('/register');
return;
}
});
Expand Down
4 changes: 2 additions & 2 deletions src/widgets/SettingUser/SetProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export const SetProfile = ({ userData }: SetProfileProps) => {
const { socials, majorJobGroup, minorJobGroup, ...rest } = data;

const majorOption = {
value: majorJobGroup,
value: majorJobGroup || '',
label:
JOB_CATEGORIES.find(majorCatergory => majorCatergory.value === majorJobGroup)?.label ??
'알 수 없음',
};

const minorOption = {
value: minorJobGroup,
value: minorJobGroup || '',
label:
JOB_SUB_CATEGORY.find(minorCategory => minorCategory.value === minorJobGroup)?.label ??
'알 수 없음',
Expand Down

0 comments on commit d66afa8

Please sign in to comment.