Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/api/activityLog.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ApiAllInquiries } from '../models/admin/mainPreview';
import type { ApiMyComments, ApiMyInquiries } from './../models/activityLog';
import { httpClient } from './http.api';

Expand Down
48 changes: 48 additions & 0 deletions src/api/admin/user.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ApiUserApplicantsData } from '../../models/admin/userDetail/userDetail.applicants';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type

import { ApiUserProjectDataResponse } from '../../models/admin/userDetail/userProjectData';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type

import { httpClient } from '../http.api';

export const postBanUser = async (id: string) => {
try {
await httpClient.post(`/ban/${id}`);
} catch (e) {
console.error(e);
throw e;
}
};

export const postWarningUser = async (id: string) => {
try {
await httpClient.post(`/warning/${id}`);
} catch (e) {
console.error(e);
throw e;
}
};

export const getUserApplicants = async (
projectId: number,
applicantId: number
) => {
try {
const response = await httpClient.get<ApiUserApplicantsData>(
`/admin/project/${projectId}/full?applicantId=${applicantId}`
);
return response.data;
} catch (e) {
console.error(e);
throw e;
}
};

export const getUserProjectData = async (userId: number) => {
try {
const response = await httpClient.get<ApiUserProjectDataResponse>(
`/users/${userId}/projects`
);
return response.data.data.appliedProjects;
} catch (e) {
console.error(e);
throw e;
}
};
14 changes: 14 additions & 0 deletions src/api/admin/userActivity.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { httpClient } from '../http.api';
import { ApiUserActivityResponse } from '../../models/admin/userDetail/userActivity';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type


export const getUserActivityData = async (userId: number) => {
try {
const response = await httpClient.get<ApiUserActivityResponse>(
`/users/${userId}/activities`
);
return response.data;
} catch (e) {
console.error(e);
throw e;
}
};
2 changes: 1 addition & 1 deletion src/api/mypage.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const getMyAppliedStatusList = async () => {
'/user/applications'
);

return response.data;
return response.data.data;
} catch (error) {
console.error('내가 지원한 프로젝트 리스트: ', error);
throw error;
Expand Down
34 changes: 14 additions & 20 deletions src/components/admin/adminUserDetail/AdminUserDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ import useGetUserInfo from '../../../hooks/admin/useGetUserInfo';
import Spinner from '../../user/mypage/Spinner';
import Sidebar from '../../common/sidebar/Sidebar';
import ScrollPreventor from '../../common/modal/ScrollPreventor';

type TabKey = 'basic' | 'log' | 'inquiry' | 'joined' | 'created' | 'applied';
import useGetUserProjectData from '../../../hooks/admin/useGetUserProjectData';
import { TabKey } from '../../../models/admin/userDetail/routing';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type


const AdminUserDetail = () => {
const { userId } = useParams();
const { userData, isLoading, isFetching } = useGetUserInfo(Number(userId));
const {
userData: projectData,
isLoading: projectLoading,
isFetching: projectFetching,
} = useGetUserProjectData(Number(userId));

if (isLoading || isFetching) {
if (isLoading || isFetching || projectLoading || projectFetching) {
return (
<S.Spinner>
<Spinner />
Expand All @@ -34,9 +39,9 @@ const AdminUserDetail = () => {
icon: React.ReactNode;
}[] = [
{
key: 'basic',
key: 'profile',
label: '기본 정보',
path: `/admin/users/${userId}/${ADMIN_ROUTE.basic}`,
path: `/admin/users/${userId}`,
icon: <InformationCircleIcon width='17px' height='17px' />,
},
{
Expand All @@ -46,23 +51,11 @@ const AdminUserDetail = () => {
icon: <ClipboardDocumentListIcon width='17px' height='17px' />,
},
{
key: 'joined',
label: '참여 프로젝트',
path: `/admin/users/${userId}/${ADMIN_ROUTE.joinedProject}`,
icon: <UserGroupIcon width='17px' height='17px' />,
},
{
key: 'created',
label: '기획 프로젝트',
path: `/admin/users/${userId}/${ADMIN_ROUTE.createdProject}`,
key: 'projects',
label: '지원/참여/기획한 프로젝트',
path: `/admin/users/${userId}/${ADMIN_ROUTE.projects}`,
icon: <UserGroupIcon width='17px' height='17px' />,
},
{
key: 'applied',
label: '지원한 프로젝트',
path: `/admin/users/${userId}/${ADMIN_ROUTE.appliedProject}`,
icon: <ClipboardDocumentListIcon width='17px' height='17px' />,
},
];

return (
Expand All @@ -88,6 +81,7 @@ const AdminUserDetail = () => {
<Outlet
context={{
userInfoData: userData,
projectData,
}}
/>
</S.DetailContent>
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/sidebar/Sidebar.styled.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from 'styled-components';

export const Container = styled.div<{ $isAdmin: boolean }>`
export const Container = styled.div<{ $isAdmin: boolean | undefined }>`
display: flex;
flex-direction: column;
border: 2px solid #f0f0f0;
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ interface SidebarProps {
const Sidebar = ({ menuItems, profileImage, nickname }: SidebarProps) => {
const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
const location = useLocation();
const isAdmin = useAuthStore().userData?.admin;
const isUserPage = location.pathname.includes('/user');
const isManagePage = location.pathname.includes('/manage');
const isAdmin = location.pathname.includes('/admin');

const isMyProfile = isLoggedIn && !isUserPage && !isManagePage;
const getActiveIndex = useCallback(() => {
Expand Down
26 changes: 17 additions & 9 deletions src/components/user/mypage/ContentTab.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useEffect, useState } from 'react';
import * as S from './ContentTab.styled';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { ROUTES } from '../../../constants/routes';
import ScrollWrapper from './ScrollWrapper';
import MovedInquiredLink from '../customerService/MoveInquiredLink';
import useAuthStore from '../../../store/authStore';
import { ADMIN_ROUTE, ROUTES } from '../../../constants/routes';

interface Filter {
title: string;
Expand All @@ -19,29 +20,35 @@ interface ContentProps {

export default function ContentTab({ filter, $justifyContent }: ContentProps) {
const { pathname } = useLocation();
const isAdmin = useAuthStore().userData?.admin;
const [filterId, setFilterId] = useState<number>();
const isAdmin = pathname.includes('/admin');

function handleChangeId(id: number) {
setFilterId(id);
}
useEffect(() => {
if (
pathname.includes(ROUTES.notificationsAppliedProjects) ||
pathname.includes(ROUTES.activityInquiries)
pathname.includes(ROUTES.activityInquiries) ||
pathname.includes(ADMIN_ROUTE.appliedProject)
) {
return setFilterId(1);
} else if (pathname.includes(ROUTES.notificationsCheckedApplicants)) {
} else if (
pathname.includes(ROUTES.notificationsCheckedApplicants) ||
pathname.includes(ADMIN_ROUTE.joinedProject)
) {
return setFilterId(2);
} else if (
pathname.includes(`${ROUTES.myPageNotifications}/${ROUTES.comments}`)
pathname.includes(`${ROUTES.myPageNotifications}/${ROUTES.comments}`) ||
pathname.includes(ADMIN_ROUTE.createdProject)
) {
return setFilterId(3);
} else {
return setFilterId(0);
}
}, [setFilterId, pathname]);

function handleChangeId(id: number) {
setFilterId(id);
}

return (
<S.Container>
<S.FilterWrapper $justifyContent={$justifyContent}>
Expand All @@ -51,6 +58,7 @@ export default function ContentTab({ filter, $justifyContent }: ContentProps) {
to={filter.url}
onClick={() => handleChangeId(filter.id as number)}
>
{' '}
<S.WrapperTitle $selected={filter?.id === filterId}>
<S.FilterTitle>{filter.title}</S.FilterTitle>
</S.WrapperTitle>
Expand All @@ -64,7 +72,7 @@ export default function ContentTab({ filter, $justifyContent }: ContentProps) {
</S.WrapperButton>
<ScrollWrapper $height='10%'>
<S.FilterContainer>
<Outlet />
<Outlet context={{ filterId }} />
</S.FilterContainer>
</ScrollWrapper>
</>
Expand Down
5 changes: 2 additions & 3 deletions src/components/user/mypage/activityLog/ActivityLog.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useLocation } from 'react-router-dom';
import {
ACTIVITY_FILTER,
ACTIVITY_FILTER_ADMIN,
} from '../../../../constants/user/myPageFilter';
import ContentTab from '../ContentTab';
import useAuthStore from '../../../../store/authStore';

export default function ActivityLog() {
const { pathname } = useLocation();
const isAdmin = pathname.includes('/admin');
const isAdmin = useAuthStore().userData?.admin;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게하면 admin일때는 사용자단의 mypage 접근이 안되지 않나요................


return (
<ContentTab
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ import Spinner from '../../Spinner';
import CommentActivity from './commentActivity/CommentActivity';
import * as S from './CommentsActivity.styled';
import NoContent from '../../../../common/noContent/NoContent';
import { useGetMyComments } from '../../../../../hooks/user/useGetMyComments';
import useGetUserActivity from '../../../../../hooks/admin/useGetAllUserActivity';
import { useParams } from 'react-router-dom';
import { MyComments } from '../../../../../models/activityLog';
import { UserComment } from '../../../../../models/admin/userDetail/userActivity';

export default function CommentsActivity() {
const { myCommentsData, isLoading } = useGetMyComments();
const { userId } = useParams();

const { userActivityData, isLoading } = useGetUserActivity(
Number(userId),
'comments'
);

if (isLoading) {
return (
Expand All @@ -16,21 +24,27 @@ export default function CommentsActivity() {
);
}

if (!myCommentsData || myCommentsData.length === 0) {
if (
!userActivityData ||
!Array.isArray(userActivityData) ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 데이터 없어도 [] 빈배열로 들어오지않나요? 그러면 이거는 항상 true 조건 아닌가요? 그래서 length로 했던것같은데

userActivityData.length === 0
) {
return (
<S.WrapperNoContentAppliedProjects data-type='noContent'>
<NoContent type='comment' />
</S.WrapperNoContentAppliedProjects>
);
}

const commentsData = userActivityData as MyComments[] | UserComment[];

return (
<S.Container>
<S.CommentsWrapper>
{myCommentsData.map((list, idx: number) => (
{commentsData.map((list: MyComments | UserComment, idx: number) => (
<Fragment key={list.id}>
<CommentActivity list={list} />
{idx !== myCommentsData.length - 1 && (
{idx !== commentsData.length - 1 && (
<S.CommentBorder></S.CommentBorder>
)}
</Fragment>
Expand Down
14 changes: 11 additions & 3 deletions src/components/user/mypage/activityLog/inquiries/Inquiries.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { useGetMyInquiries } from '../../../../../hooks/user/useGetMyInquiries';
import { useParams } from 'react-router-dom';
import useGetUserActivity from '../../../../../hooks/admin/useGetAllUserActivity';
import ContentBorder from '../../../../common/contentBorder/ContentBorder';
import NoContent from '../../../../common/noContent/NoContent';
import Spinner from '../../Spinner';
import * as S from './Inquiries.styled';
import Inquiry from './inquiry/Inquiry';
import { MyInquiries } from '../../../../../models/activityLog';

export default function Inquiries() {
const { myInquiriesData, isLoading } = useGetMyInquiries();
const { userId } = useParams();
const { userActivityData, isLoading } = useGetUserActivity(
Number(userId),
'inquiries'
);
Comment on lines +11 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

URL 파라미터 검증 누락

userId 파라미터가 유효한지 검증하지 않고 바로 Number(userId)로 변환하고 있습니다. 잘못된 파라미터가 전달될 경우 문제가 발생할 수 있습니다.

export default function Inquiries() {
  const { userId } = useParams();
+ 
+ if (!userId || isNaN(Number(userId))) {
+   return (
+     <S.WrapperNoContentAppliedProjects data-type='noContent'>
+       <NoContent type='error' />
+     </S.WrapperNoContentAppliedProjects>
+   );
+ }
+ 
  const { userActivityData, isLoading } = useGetUserActivity(
    Number(userId),
    'inquiries'
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { userId } = useParams();
const { userActivityData, isLoading } = useGetUserActivity(
Number(userId),
'inquiries'
);
export default function Inquiries() {
const { userId } = useParams();
if (!userId || isNaN(Number(userId))) {
return (
<S.WrapperNoContentAppliedProjects data-type='noContent'>
<NoContent type='error' />
</S.WrapperNoContentAppliedProjects>
);
}
const { userActivityData, isLoading } = useGetUserActivity(
Number(userId),
'inquiries'
);
// ...rest of the component
}
🤖 Prompt for AI Agents
In src/components/user/mypage/activityLog/inquiries/Inquiries.tsx around lines
12 to 16, the userId URL parameter is converted to a number without validation,
which can cause issues if the parameter is invalid. Add validation to check if
userId exists and is a valid number before converting it. Handle cases where
userId is missing or invalid by showing an error message or redirecting
appropriately to prevent runtime errors.


if (isLoading) {
return (
Expand All @@ -16,13 +22,15 @@ export default function Inquiries() {
);
}

if (!myInquiriesData || myInquiriesData?.length === 0)
if (!userActivityData || userActivityData?.length === 0)
return (
<S.WrapperNoContentAppliedProjects data-type='noContent'>
<NoContent type='inquiries' />
</S.WrapperNoContentAppliedProjects>
);

const myInquiriesData = userActivityData as MyInquiries[];

return (
<S.container>
<S.InquiriesContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import { useRef, useState } from 'react';
import type { MyInquiries } from '../../../../../../models/activityLog';
import * as S from './Inquiry.styled';
import { My_INQUIRIES_MESSAGE } from '../../../../../../constants/user/customerService';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Link } from 'react-router-dom';
import { Link, useParams } from 'react-router-dom';
import * as S from './AppliedProjects.styled';
import Spinner from '../../Spinner';
import AppliedProjectsStatus from './appliedProjectsStatus/AppliedProjectsStatus';
import NoContent from '../../../../common/noContent/NoContent';
import { useMyAppliedStatusList } from '../../../../../hooks/user/useMyInfo';
import { ROUTES } from '../../../../../constants/routes';
import useGetUserProjectData from '../../../../../hooks/admin/useGetUserProjectData';

export default function AppliedProjects() {
const { myAppliedStatusListData, isLoading } = useMyAppliedStatusList();
const { userId } = useParams();

const { userData: userProjectData, isLoading } = useGetUserProjectData(
Number(userId)
);

if (isLoading) {
return (
Expand All @@ -17,7 +21,7 @@ export default function AppliedProjects() {
);
}

if (!myAppliedStatusListData || myAppliedStatusListData.length === 0) {
if (!userProjectData || userProjectData.length === 0) {
return (
<S.WrapperNoContentAppliedProjects data-type='noContent'>
<NoContent type='projects' />
Expand All @@ -28,7 +32,7 @@ export default function AppliedProjects() {
return (
<S.container>
<S.Wrapper>
{myAppliedStatusListData.map((data) => (
{userProjectData.map((data) => (
<Link key={data.id} to={`${ROUTES.projectDetail}/${data.id}`}>
<AppliedProjectsStatus list={data} />
</Link>
Expand Down
Loading