Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions src/api/admin/customerService/inquiry.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export const getAllInquiries = async (
}
};

export const getInquiriesPreview = async () => {
const response = await httpClient.get<ApiAdminInquiry>(`/inquiry/preview`);
return response.data.data;
};

export const getInquiryDetail = async (id: string) => {
try {
const response = await httpClient.get<ApiAdminInquiryDetail>(
Expand Down
2 changes: 1 addition & 1 deletion src/api/admin/tag.api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ApiCommonBasicType } from '../../models/apiCommon';
import type { TagFormType } from '../../models/tags';

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

export const postSkillTag = async (formData: FormData) => {
Expand Down
3 changes: 1 addition & 2 deletions src/api/http.api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import axios, { AxiosRequestConfig } from 'axios';
import useAuthStore from '../store/authStore';
import { postRefresh } from './auth.api';

export const BASE_URL = `${import.meta.env.VITE_APP_API_BASE_URL}`;
const DEFAULT_TIMEOUT = 15000;

export const createClient = (config?: AxiosRequestConfig) => {
const { login, logout } = useAuthStore.getState();
const { logout } = useAuthStore.getState();

const axiosInstance = axios.create({
baseURL: BASE_URL,
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/adminInquiry/AdminInquiryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { AdminInquiryChangeSearchParams } from '../../../models/inquiry';

export type SearchParamsInquiryKeyType = keyof AdminInquiryChangeSearchParams;
export default function AdminInquiryList() {
const [searchParams, setSearchParams] = useSearchParams();
const [searchParams] = useSearchParams();
const userId = searchParams.get('userId') || '';
const startDate = searchParams.get('startDate') || '';
const endDate = searchParams.get('endDate') || '';
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/adminTags/AdminTagsBasic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useAdminSkillTag } from '../../../hooks/admin/useAdminTag';
import { useLocation } from 'react-router-dom';
import AdminTagCRUD from './AdminTagCRUD';
import AdminSkillTagItems from './skills/AdminSkillTagItems';
import type { TagFormType } from '../../../models/tags';

import AdminPositionItems from './positions/AdminPositionItems';

export type TWitchTag = 'skill' | 'position';
Expand Down
1 change: 0 additions & 1 deletion src/components/admin/adminUserReport/AdminReportDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import * as S from './AdminReportDetail.styled';
import AdminTitle from '../../common/admin/title/AdminTitle';
import Avatar from '../../common/avatar/Avatar';
Expand Down
5 changes: 0 additions & 5 deletions src/components/admin/mainCard/graphCard/GraphCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import * as S from './GraphCard.styled';
import { Line } from 'react-chartjs-2';
import 'chart.js/auto';
Expand Down Expand Up @@ -58,7 +57,6 @@ const options: ChartOptions<'line'> = {
x: {
grid: {
display: true,
borderDash: [5, 5],
color: 'rgba(0,0,0,0.1)',
},
ticks: {
Expand All @@ -71,9 +69,6 @@ const options: ChartOptions<'line'> = {
y: {
grid: {
display: true,
drawBorder: false,

borderDash: [5, 5],
color: 'rgba(0,0,0,0.1)',
},
ticks: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import * as S from './AllUserPreview.styled';
import Avatar from '../../../common/avatar/Avatar';
import { ADMIN_ROUTE } from '../../../../constants/routes';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import * as S from './InquiresPreview.styled';
import { useGetAllInquiries } from '../../../../hooks/admin/useGetAllInquiries';
import Avatar from '../../../common/avatar/Avatar';
import { ADMIN_ROUTE } from '../../../../constants/routes';
import arrow_right from '../../../../assets/ArrowRight.svg';
import Spinner from '../../../user/mypage/Spinner';
import { AdminInquiryChangeSearchParams } from '../../../../models/inquiry';
import { useGetInquiriesPreview } from '../../../../hooks/admin/useGetInquiriesPreview';

const InquiresPreview = () => {
const childSearchParams: AdminInquiryChangeSearchParams = {
userId: '',
startDate: '',
endDate: '',
};
const { allInquiriesData, isLoading, isFetching } =
useGetAllInquiries(childSearchParams);
const { allInquiriesPreviewData, isLoading, isFetching } =
useGetInquiriesPreview();

if (isLoading || isFetching) {
return (
Expand All @@ -23,13 +17,13 @@ const InquiresPreview = () => {
);
}

if (!allInquiriesData || allInquiriesData.length === 0) {
if (!allInquiriesPreviewData || allInquiriesPreviewData.length === 0) {
return <S.Container>등록된 문의가 없습니다.</S.Container>;
}

return (
<S.Container>
{allInquiriesData?.map((inquiry) => (
{allInquiriesPreviewData.map((inquiry) => (
<S.Wrapper key={inquiry.id}>
<S.Content>
{/* <Link to={`${ADMIN_ROUTE.}`} */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const NoticePreview = () => {
}

if (!noticeData) {
return <S.Container>공지사힝이 없습니다.</S.Container>;
return <S.Container>공지사항이 없습니다.</S.Container>;
}

return (
Expand Down
1 change: 0 additions & 1 deletion src/components/admin/userCard/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import * as S from './UserCard.styled';
import Avatar from '../../common/avatar/Avatar';
import type { AllUser } from '../../../models/auth';
Expand Down
1 change: 0 additions & 1 deletion src/components/user/comment/DropDownItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ const DropDownItem = ({
targetId={recommentId ? recommentId : commentId}
type={recommentId ? 'recomment' : 'comment'}
onClose={handleCloseReportModal}
route={projectId}
/>
)}
</>
Expand Down
1 change: 0 additions & 1 deletion src/components/user/mypage/ContentTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ 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 Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useState } from 'react';
import type { MyInquiries } from '../../../../../../models/activityLog';
import * as S from './Inquiry.styled';
import { My_INQUIRIES_MESSAGE } from '../../../../../../constants/user/customerService';
Expand All @@ -22,14 +22,14 @@ export default function Inquiry({ list, no }: InquiryProps) {
url: '',
});
const answer = list.answer || '';
const answerRef = useRef<HTMLTextAreaElement>(null);
// const answerRef = useRef<HTMLTextAreaElement>(null);

const handleChangeAnswerRef = () => {
if (answerRef && answerRef.current) {
answerRef.current.style.height = 'auto';
answerRef.current.style.height = `${answerRef.current.scrollHeight}px`;
}
};
// const handleChangeAnswerRef = () => {
// if (answerRef && answerRef.current) {
// answerRef.current.style.height = 'auto';
// answerRef.current.style.height = `${answerRef.current.scrollHeight}px`;
// }
// };

return (
<S.Container>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { ROUTES } from '../../../../../constants/routes';
import { useModal } from '../../../../../hooks/useModal';
import Modal from '../../../../common/modal/Modal';
import { MODAL_MESSAGE } from '../../../../../constants/user/modalMessage';
import { useGithubLink } from '../../../../../hooks/user/useMyInfo';

export default function ProfileGithubSuccess() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,34 @@ export const InputContainer = styled.div`
flex-direction: column;
`;

export const InputStyle = styled.input<{ type?: string }>`
export const InputWrapper = styled.div`
display: flex;
align-items: center;
gap: 2px;
`;

export const UnitText = styled.span`
font-size: ${({ theme }) => theme.heading.xsSmall.fontSize};
color: ${({ theme }) => theme.color.deepGrey};
white-space: nowrap;
`;

export const InputStyle = styled.input<{ type?: string; name?: string }>`
padding: 10px;
border: 1px solid ${({ theme }) => theme.color.border};
border-radius: ${({ theme }) => theme.borderRadius.primary};
font-size: ${({ theme }) => theme.heading.small.fontSize};

${({ type }) => {
${({ type, name }) => {
switch (type) {
case 'text':
if (name === 'maxVolunteers' || name === 'duration') {
return css`
width: 60px;
text-align: right;
border-radius: 13px;
`;
}
return css`
width: 100%;
border-radius: 13px;
Expand All @@ -40,19 +59,20 @@ export const InputStyle = styled.input<{ type?: string }>`
}}
`;

export const InputInfoStyle = styled.input<{ type?: string }>`
export const InputInfoStyle = styled.input<{ type?: string; name?: string }>`
font-size: ${({ theme }) => theme.heading.small.fontSize};
border: none;

${({ type }) => {
${({ type, name }) => {
switch (type) {
case 'text':
return css`
width: 220px;
text-align: left;
padding-left: 1px;
`;
case 'number':
if (name === 'maxVolunteers' || name === 'duration') {
return css`
width: 30px;
text-align: right;
padding-left: 1px;
`;
}
return css`
width: 220px;
text-align: left;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import * as S from './InputComponent.styled';
import MdEditorInput from '../editor/MarkdownEditor';

type InputProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
control: Control<any>;
name: string;
placeholder?: string;
errors?: FieldErrors;
type?: 'text' | 'date' | 'textarea' | 'number' | string;
indexInfo?: string;
unit?: string;
min?: number;
};

const Input = ({
Expand All @@ -18,25 +21,70 @@ const Input = ({
placeholder,
type,
indexInfo,
unit,
min = 0,
}: InputProps) => {
const { field } = useController({
control,
name,
});
const hasError = Boolean(errors?.[name]);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;

// 숫자 입력 필드인 경우 숫자만 허용
if (name === 'maxVolunteers' || name === 'duration') {
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

스키마와 입력 검증의 일관성을 확인해주세요

maxVolunteersduration 필드가 텍스트 입력으로 변경되었는데, 프로젝트 상수 파일의 검증 스키마가 이 변경사항과 일치하는지 확인이 필요합니다.

다음 스크립트를 실행하여 스키마 일관성을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# 스키마에서 maxVolunteers와 duration 필드의 타입 확인
rg -A 10 "maxVolunteers.*z\." src/constants/user/projectConstants.ts
rg -A 10 "duration.*z\." src/constants/user/projectConstants.ts

Length of output: 136


다음 스크립트로 projectConstants.ts 내 검증 스키마 정의와 maxVolunteers·duration 타입을 다시 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Zod 스키마 정의 위치 확인
rg -n "z.object" -A 20 src/constants/user/projectConstants.ts || true

# 각 필드 타입 확인
rg -n "maxVolunteers" -A 5 src/constants/user/projectConstants.ts || true
rg -n "duration" -A 5 src/constants/user/projectConstants.ts || true

Length of output: 2145


duration 필드 스키마 타입 불일치 보강 필요

프로젝트 상수(src/constants/user/projectConstants.ts)의 createProjectScheme에서:

  • maxVolunteers
    • z.number — UI에서 text 입력 후 숫자로 변환 → 검증 일치
  • duration
    • z.string — UI에서 text 입력만 검증(refine) → 반환 타입은 string

두 필드를 모두 숫자형으로 다룰 예정이라면 durationz.number로 변경하거나, 반대로 maxVolunteersz.string 기반 검증으로 통일해야 합니다.

수정 예시:

--- a/src/constants/user/projectConstants.ts
+++ b/src/constants/user/projectConstants.ts
@@ -95,7 +95,7 @@ export const createProjectScheme = z.object({
-  duration: z
-    .string({ message: '예상 기간을 입력해주세요.' })
-    .min(1, { message: '예상 기간을 입력해주세요.' })
-    .refine((val) => {
-      const num = Number(val);
-      return !isNaN(num) && num > 0;
-    }, { message: '유효한 기간을 입력해주세요.' }),
+  duration: z
+    .number({ message: '예상 기간을 입력해주세요.' })
+    .min(1, { message: '예상 기간을 입력해주세요.' }),

위와 같이 duration을 z.number로 변경해 일관성을 맞춰주세요.

📝 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
if (name === 'maxVolunteers' || name === 'duration') {
export const createProjectScheme = z.object({
// ... other schema fields ...
- duration: z
- .string({ message: '예상 기간을 입력해주세요.' })
- .min(1, { message: '예상 기간을 입력해주세요.' })
- .refine((val) => {
- const num = Number(val);
- return !isNaN(num) && num > 0;
- }, { message: '유효한 기간을 입력해주세요.' }),
+ duration: z
+ .number({ message: '예상 기간을 입력해주세요.' })
+ .min(1, { message: '예상 기간을 입력해주세요.' }),
maxVolunteers: z
.number({ message: '모집 인원을 입력해주세요.' })
.min(1, { message: '모집 인원은 1명이상이어야 합니다.' }),
// ... other schema fields ...
});
🤖 Prompt for AI Agents
In src/components/user/projectFormComponents/inputComponent/InputComponent.tsx
at line 37, the condition checks for 'duration' and 'maxVolunteers' fields, but
the schema type for 'duration' in src/constants/user/projectConstants.ts's
createProjectScheme is z.string while 'maxVolunteers' is z.number. To maintain
consistency and handle both as numbers, update the createProjectScheme to change
the 'duration' field type from z.string to z.number, ensuring the validation and
UI handling align with numeric types for both fields.

// 숫자가 아닌 문자 제거
const numericValue = value.replace(/[^0-9]/g, '');
if (numericValue !== value) {
e.target.value = numericValue;
}

// 음수 방지 (0보다 작은 값은 허용하지 않음)
const numValue = Number(numericValue);
if (numValue < min) {
return;
}
}

field.onChange(e);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

입력 검증 로직을 개선해주세요

숫자 입력 검증 로직이 잘 구현되었지만, e.target.value를 직접 수정한 후 field.onChange(e)를 호출하는 방식은 일관성 문제를 야기할 수 있습니다.

더 안정적인 구현을 위해 다음과 같이 수정하는 것을 권장합니다:

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-  const value = e.target.value;
+  let value = e.target.value;

  // 숫자 입력 필드인 경우 숫자만 허용
  if (name === 'maxVolunteers' || name === 'duration') {
    // 숫자가 아닌 문자 제거
-    const numericValue = value.replace(/[^0-9]/g, '');
-    if (numericValue !== value) {
-      e.target.value = numericValue;
-    }
+    value = value.replace(/[^0-9]/g, '');

    // 음수 방지 (0보다 작은 값은 허용하지 않음)
-    const numValue = Number(numericValue);
+    const numValue = Number(value);
    if (numValue < min) {
      return;
    }
  }

-  field.onChange(e);
+  field.onChange(value);
};
🤖 Prompt for AI Agents
In src/components/user/projectFormComponents/inputComponent/InputComponent.tsx
around lines 33 to 52, avoid directly modifying e.target.value before calling
field.onChange(e) as it can cause inconsistencies. Instead, extract and sanitize
the input value first, then call field.onChange with a synthetic event or
directly pass the sanitized value to update the form state. This ensures
consistent and reliable input handling without mutating the original event
object.


const renderInput = () => {
if (indexInfo) {
return (
<S.InputInfoStyle {...field} type={type} placeholder={placeholder} />
<S.InputWrapper>
<S.InputInfoStyle
{...field}
type={type}
placeholder={placeholder}
onChange={handleInputChange}
min={min}
name={name}
/>
{unit && <S.UnitText>{unit}</S.UnitText>}
</S.InputWrapper>
);
}

if (name === 'markdownEditor') {
return <MdEditorInput field={{ ...field }} />;
}

return <S.InputStyle {...field} type={type} placeholder={placeholder} />;
return (
<S.InputWrapper>
<S.InputStyle
{...field}
type={type}
placeholder={placeholder}
onChange={handleInputChange}
min={min}
name={name}
/>
{unit && <S.UnitText>{unit}</S.UnitText>}
</S.InputWrapper>
);
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export const InfoLabel = styled.label`
color: #333;
`;

export const LabelDescription = styled.span`
font-size: ${({ theme }) => theme.heading.xsSmall.fontSize};
color: ${({ theme }) => theme.color.grey};
font-weight: normal;
margin-left: 8px;
`;

export const welcomeSprout = styled.p`
font-size: 1rem;
font-weight: bold;
Expand Down
Loading