Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"nanoid": "^5.0.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.2",
"react-router-dom": "^6.27.0",
"socket.io-client": "^4.8.1",
"styled-components": "^6.1.13",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/apis/queries/host/useUpdateHost.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HostInfo, updateHost } from '@apis/updateHost';
import { updateHost } from '@apis/updateHost';
import { useMutation, UseMutationResult } from '@tanstack/react-query';
import { HostInfo } from '@type/hostInfo';

type Params = {
onSuccess?: (data: HostInfo) => void;
Expand Down
9 changes: 1 addition & 8 deletions frontend/src/apis/updateHost.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { AxiosResponse } from 'axios';
import { fetchInstance } from '.';

export interface HostInfo {
userId: string;
liveTitle: string;
defaultThumbnailImageUrl: string;
category: string;
tags: string[];
}
import { HostInfo } from '@type/hostInfo';

export const updateHost = async (hostInfo: HostInfo): Promise<HostInfo> => {
const response: AxiosResponse<HostInfo> = await fetchInstance().post('/host/update', hostInfo);
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/components/host/Form/CategoryField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Controller, useFormContext } from 'react-hook-form';
import { FormCell, Input, Label } from './style';
import { FormValues } from '@type/hostInfo';

export default function CategoryField() {
const { control } = useFormContext<FormValues>();

return (
<FormCell>
<Label>카테고리</Label>
<Controller
name="category"
control={control}
render={({ field }) => <Input {...field} type="text" placeholder="카테고리 검색" />}
/>
</FormCell>
);
}
22 changes: 22 additions & 0 deletions frontend/src/components/host/Form/HostNameField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Controller, useFormContext } from 'react-hook-form';
import { FormCell, Input, Label, Required } from './style';
import { FormValues } from '@type/hostInfo';

export default function HostNameField() {
const { control } = useFormContext<FormValues>();

return (
<FormCell>
<Label>
호스트 이름<Required>*</Required>
</Label>
<Controller
name="hostName"
control={control}
render={({ field }) => (
<Input {...field} type="text" placeholder="호스트 이름을 입력해 주세요." maxLength={100} />
)}
/>
</FormCell>
);
}
84 changes: 84 additions & 0 deletions frontend/src/components/host/Form/ImageField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useCallback, useRef } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { FormValues } from '@type/hostInfo';
import { convertToBase64 } from '@utils/convertToBase64';
import {
FileInput,
FileInputLabel,
FormCell,
ImageActionButtons,
ImageButton,
ImageContainer,
ImageUpload,
Label,
PlaceholderImage,
PreviewImage,
UploadIcon,
UploadText
} from './style';

export default function ImageField() {
const fileInputRef = useRef<HTMLInputElement>(null);
const { control, setValue } = useFormContext<FormValues>();
const previewImage = useWatch({
control,
name: 'previewImage',
defaultValue: null
});

const handleImageChange = useCallback(
async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
const files = e.target.files;
if (!files) return;

const base64 = await convertToBase64(files[0]);
setValue('previewImage', base64);
},
[setValue]
);

const handleImageDelete = useCallback(() => {
setValue('previewImage', null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}, [setValue]);

return (
<FormCell>
<Label>미리보기 이미지</Label>
<ImageUpload>
{previewImage ? (
<ImageContainer>
<PreviewImage src={previewImage} alt="미리보기 이미지" />
<ImageActionButtons>
<ImageButton as="label" htmlFor="preview-image-input">
수정
</ImageButton>
<ImageButton onClick={handleImageDelete} type="button">
삭제
</ImageButton>
</ImageActionButtons>
</ImageContainer>
) : (
<FileInputLabel htmlFor="preview-image-input">
<PlaceholderImage>
<UploadIcon xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<path d="M1 7H13" stroke="#AEB4C2" strokeWidth="1.5" strokeLinecap="round" />
<path d="M7 1L7 13" stroke="#AEB4C2" strokeWidth="1.5" strokeLinecap="round" />
</UploadIcon>
<UploadText>업로드 (1280x720)</UploadText>
</PlaceholderImage>
</FileInputLabel>
)}
<FileInput
ref={fileInputRef}
id="preview-image-input"
type="file"
accept="image/*"
onChange={handleImageChange}
/>
</ImageUpload>
</FormCell>
);
}
27 changes: 27 additions & 0 deletions frontend/src/components/host/Form/LiveTitleField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Controller, useFormContext } from 'react-hook-form';
import { Bold, CharCount, FormCell, Input, Label, Required } from './style';
import { FormValues } from '@type/hostInfo';

export default function LiveTitleField() {
const { control } = useFormContext<FormValues>();

return (
<FormCell>
<Label>
방송 제목<Required>*</Required>
</Label>
<Controller
name="liveTitle"
control={control}
render={({ field }) => (
<>
<Input {...field} type="text" placeholder="방송 제목을 입력해주세요." maxLength={100} />
<CharCount>
<Bold>{field.value?.length || 0}</Bold>/100
</CharCount>
</>
)}
/>
</FormCell>
);
}
18 changes: 18 additions & 0 deletions frontend/src/components/host/Form/NoticeField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Controller, useFormContext } from 'react-hook-form';
import { FormValues } from '@type/hostInfo';
import { FormCell, Input, Label } from './style';

export default function NoticeField() {
const { control } = useFormContext<FormValues>();

return (
<FormCell>
<Label>공지</Label>
<Controller
name="notice"
control={control}
render={({ field }) => <Input {...field} type="text" placeholder="공지 추가하기" />}
/>
</FormCell>
);
}
81 changes: 81 additions & 0 deletions frontend/src/components/host/Form/TagField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Controller, useFormContext } from 'react-hook-form';
import { FormValues } from '@type/hostInfo';
import {
Button,
Flex,
FormCell,
Input,
Label,
RemoveButton,
TagChipContainer,
TagContainer,
UploadText
} from './style';
import { KeyboardEvent } from 'react';

export default function TagField() {
const { control, setValue, getValues } = useFormContext<FormValues>();

const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
e.preventDefault();
onAddTag();
}
};

const onAddTag = () => {
const currentTag = getValues('tag').trim();
if (currentTag) {
const currentTags = getValues('tags');
setValue('tags', [...currentTags, currentTag]);
setValue('tag', '');
Copy link
Collaborator

Choose a reason for hiding this comment

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

setValue가 2개인데 의도한 것인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

tag, tags가 있는데 tag에서 엔터를 치면 tags 배열에 들어가고 tag는 다른 인풋을 받기 위해 빈스트링으로 설정합니다.

}
};

const onRemoveTag = (indexToRemove: number) => {
const currentTags = getValues('tags');
setValue(
'tags',
currentTags.filter((_: string, index: number) => index !== indexToRemove)
);
};

return (
<FormCell>
<Label>태그</Label>
<Flex>
<Controller
name="tag"
control={control}
render={({ field }) => <Input {...field} type="text" placeholder="태그 추가하기" onKeyDown={onKeyDown} />}
/>
<Button type="button" onClick={onAddTag}>
추가
</Button>
</Flex>
<Controller
name="tags"
control={control}
render={({ field }) => (
<>{field.value.length > 0 && <TagList tags={field.value} onRemoveTag={onRemoveTag} />}</>
)}
/>
<UploadText>공백 및 특수 문자 없이 15자까지 입력할 수 있습니다.</UploadText>
</FormCell>
);
}

const TagChip = ({ tag, onRemove }: { tag: string; onRemove: () => void }) => (
<TagChipContainer>
{tag}
<RemoveButton onClick={onRemove}>×</RemoveButton>
</TagChipContainer>
);

const TagList = ({ tags, onRemoveTag }: { tags: string[]; onRemoveTag: (index: number) => void }) => (
<TagContainer>
{tags.map((tag, index) => (
<TagChip key={tag + index} tag={tag} onRemove={() => onRemoveTag(index)} />
))}
</TagContainer>
);
6 changes: 6 additions & 0 deletions frontend/src/components/host/Form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default as LiveTitleField } from './LiveTitleField';
export { default as CategoryField } from './CategoryField';
export { default as TagField } from './TagField';
export { default as NoticeField } from './NoticeField';
export { default as ImageField } from './ImageField';
export { default as HostNameField } from './HostNameField';
Loading
Loading