diff --git a/public/assets/images/crew-sample/index.tsx b/public/assets/images/crew-sample/index.tsx index 0eac6777..9e530eed 100644 --- a/public/assets/images/crew-sample/index.tsx +++ b/public/assets/images/crew-sample/index.tsx @@ -3,5 +3,10 @@ import ImgCrewSample02 from './crew-sample-2.jpg'; import ImgCrewSample03 from './crew-sample-3.jpg'; const ImgCrewSamples = [ImgCrewSample01, ImgCrewSample02, ImgCrewSample03]; +const ImgCrewSampleUrls = [ + 'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/0e05d971-15a8-4a32-bf03-80d12cae392e', + 'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/eb35c35d-829a-402b-8019-29e42f91589f', + 'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/471b3b3b-b23c-48e8-8e6b-9a7ec31e1917', +]; -export default ImgCrewSamples; +export default ImgCrewSampleUrls; diff --git a/public/assets/images/gathering-sample/index.tsx b/public/assets/images/gathering-sample/index.tsx index 6bba3587..116d1637 100644 --- a/public/assets/images/gathering-sample/index.tsx +++ b/public/assets/images/gathering-sample/index.tsx @@ -3,5 +3,10 @@ import ImgGatheringSample02 from './gathering-sample-2.jpg'; import ImgGatheringSample03 from './gathering-sample-3.jpg'; const ImgGatheringSamples = [ImgGatheringSample01, ImgGatheringSample02, ImgGatheringSample03]; +const ImgGatheringSampleUrls = [ + 'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/1ff61110-e238-4fd6-8736-e6f03483f4df', + 'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/85ad6a48-a1cb-4c8f-b713-94b9e18b023a', + 'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/60bc093c-cb3a-4628-93b9-fbf19e47a1ac', +]; -export default ImgGatheringSamples; +export default ImgGatheringSampleUrls; diff --git a/src/_apis/crew/crew.ts b/src/_apis/crew/crew.ts new file mode 100644 index 00000000..f8fb8d20 --- /dev/null +++ b/src/_apis/crew/crew.ts @@ -0,0 +1,25 @@ +import { fetchApi } from '@/src/utils/api'; +import { CreateCrewRequestTypes, CreateCrewResponseTypes } from '@/src/types/create-crew'; + +export async function createCrew(data: CreateCrewRequestTypes) { + try { + const response: { data: CreateCrewResponseTypes; status: number } = await fetchApi( + `/api/crews`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', // Include authentication credentials + body: JSON.stringify(data), + }, + ); + if (!response.data) { + throw new Error('Failed to create crew: No data received'); + } + + return response.data; + } catch (error) { + throw error; + } +} diff --git a/src/_apis/image/get-image-url.ts b/src/_apis/image/get-image-url.ts new file mode 100644 index 00000000..253c7e56 --- /dev/null +++ b/src/_apis/image/get-image-url.ts @@ -0,0 +1,36 @@ +import { fetchApi } from '@/src/utils/api'; +import validateFile from '@/src/utils/validate-file'; +import Toast from '@/src/components/common/toast'; +import { GetImageUrlResponseTypes } from '@/src/types/create-crew'; + +export async function getImageUrl( + file: File | string | null, + type: 'MEMBER' | 'CREW' | 'GATHERING', +) { + const formData = new FormData(); + if (file instanceof File) { + try { + if (!validateFile(file)) { + throw new Error('Invalid file'); + } + formData.append('file', file); + + const response: { data: GetImageUrlResponseTypes } = await fetchApi( + `/api/images?type=${type}`, + { + method: 'POST', + body: formData, + }, + ); + if (!response.data) { + throw new Error('Failed to get image: No data received'); + } + + return response.data; + } catch (error) { + // eslint-disable-next-line no-console + throw error; + } + } + return null; +} diff --git a/src/_queries/crew/crew-list-queries.tsx b/src/_queries/crew/crew-list-queries.tsx index 056b8dbd..545700ca 100644 --- a/src/_queries/crew/crew-list-queries.tsx +++ b/src/_queries/crew/crew-list-queries.tsx @@ -11,6 +11,7 @@ export function useGetCrewListQuery({ const { size, sort = ['string'] } = pageable; return { queryKey: [ + 'crewLists', condition.keyword, condition.mainLocation, condition.mainCategory, diff --git a/src/app/(crew)/crew/_components/create-crew-form/create-crew-form.stories.tsx b/src/app/(crew)/crew/_components/create-crew-form/create-crew-form.stories.tsx index 1f2a9113..fb8cefe9 100644 --- a/src/app/(crew)/crew/_components/create-crew-form/create-crew-form.stories.tsx +++ b/src/app/(crew)/crew/_components/create-crew-form/create-crew-form.stories.tsx @@ -1,15 +1,16 @@ import { Meta, StoryFn } from '@storybook/react'; -import { CreateCrewRequestTypes } from '@/src/types/create-crew'; -import CreateCrewForm, { CreateCrewFormTypes } from '.'; +import { CreateCrewFormTypes, CreateCrewRequestTypes } from '@/src/types/create-crew'; +import CreateCrewForm from '.'; const initialValue: CreateCrewRequestTypes = { title: '', mainCategory: '', subCategory: '', - imageUrl: null, + imageUrl: '', mainLocation: '', subLocation: '', totalCount: 0, + introduce: '', }; export default { diff --git a/src/app/(crew)/crew/_components/create-crew-form/index.tsx b/src/app/(crew)/crew/_components/create-crew-form/index.tsx index f197b632..a55d0ee3 100644 --- a/src/app/(crew)/crew/_components/create-crew-form/index.tsx +++ b/src/app/(crew)/crew/_components/create-crew-form/index.tsx @@ -10,14 +10,15 @@ import Button from '@/src/components/common/input/button'; import DropDown from '@/src/components/common/input/drop-down'; import FileInputWrap from '@/src/components/common/input/file-input-wrap'; import TextInput from '@/src/components/common/input/text-input'; -import { CreateCrewRequestTypes } from '@/src/types/create-crew'; -import ImgCrewSamples from '@/public/assets/images/crew-sample'; +import Textarea from '@/src/components/common/input/textarea'; +import { CreateCrewFormTypes } from '@/src/types/create-crew'; +import ImgCrewSampleUrls from '@/public/assets/images/crew-sample'; -export interface CreateCrewFormTypes { - data: CreateCrewRequestTypes; +export interface CreateCrewFormProps { + data: CreateCrewFormTypes; isEdit?: boolean; - onEdit?: (data: CreateCrewRequestTypes) => void; - onSubmit?: (data: CreateCrewRequestTypes) => void; + onEdit?: (data: CreateCrewFormTypes) => void; + onSubmit?: (data: CreateCrewFormTypes) => void; } export default function CreateCrewForm({ @@ -25,7 +26,7 @@ export default function CreateCrewForm({ onEdit = () => {}, onSubmit = () => {}, data, -}: CreateCrewFormTypes) { +}: CreateCrewFormProps) { const router = useRouter(); const { control, @@ -34,7 +35,7 @@ export default function CreateCrewForm({ trigger, clearErrors, formState: { errors, isValid, isSubmitting }, - } = useForm({ + } = useForm({ defaultValues: data, mode: 'onBlur', }); @@ -43,24 +44,24 @@ export default function CreateCrewForm({ const [regionIndex, setRegionIndex] = useState(0); const title = useWatch({ control, name: 'title' }); - // mainCategory와 mainLocation 값의 변화를 감지하여 인덱스를 설정 const mainCategory = useWatch({ control, name: 'mainCategory' }); const mainLocation = useWatch({ control, name: 'mainLocation' }); + const introduce = useWatch({ control, name: 'introduce' }); const handleMainCategoryChange = (newValue: string | null) => { - setValue('mainCategory' as const, newValue as CreateCrewRequestTypes['mainCategory']); - setValue('subCategory' as const, null as CreateCrewRequestTypes['subCategory']); + setValue('mainCategory', newValue || ''); + setValue('subCategory', null); clearErrors('subCategory'); }; const handleMainLocationChange = (newValue: string | null) => { - setValue('mainLocation' as const, newValue as CreateCrewRequestTypes['mainLocation']); - setValue('subLocation' as const, null as CreateCrewRequestTypes['subLocation']); + setValue('mainLocation', newValue || ''); + setValue('subLocation', null); clearErrors('subLocation'); }; useEffect(() => { - setCategoryIndex(categoryData.findIndex((category) => category.title.value === mainCategory)); - setRegionIndex(regionData.findIndex((region) => region.main.value === mainLocation)); + setCategoryIndex(categoryData.findIndex((category) => category.title.label === mainCategory)); + setRegionIndex(regionData.findIndex((region) => region.main.label === mainLocation)); }, [mainCategory, mainLocation]); return ( @@ -162,24 +163,24 @@ export default function CreateCrewForm({ required: '이미지를 선택해주세요.', validate: { fileSize: (file) => - file && file instanceof File && file.size <= 5242880 - ? true - : '파일 크기는 5MB 이하여야 합니다.', + file && file instanceof File + ? file.size <= 5242880 || '파일 크기는 5MB 이하여야 합니다.' + : true, // 문자열인 경우 크기 검사를 건너뜁니다. fileType: (file) => - file && - file instanceof File && - ['image/jpeg', 'image/jpg', 'image/png'].includes(file.type) - ? true - : 'JPG, PNG 파일만 업로드 가능합니다.', + file && file instanceof File + ? ['image/jpeg', 'image/jpg', 'image/png'].includes(file.type) || + 'JPG, PNG 파일만 업로드 가능합니다.' + : true, // 문자열인 경우 파일 타입 검사를 건너뜁니다. }, }} render={({ field }) => ( { field.onChange(newValue); - if (newValue instanceof File) trigger('imageUrl'); + trigger('imageUrl'); }} /> )} @@ -264,7 +265,28 @@ export default function CreateCrewForm({ )} /> - +
+
+ + + {introduce.length}/100 + +
+ ( +