Skip to content

Commit 7153ffb

Browse files
authored
Merge pull request #96 from bssm-portfolio/develop
v1.2.0
2 parents 7d9c62c + aa78bfc commit 7153ffb

22 files changed

+346
-123
lines changed

Diff for: apis/httpClient.ts

+22-7
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,6 @@ export class HttpClient {
3434
});
3535
}
3636

37-
search(data: unknown, requestConfig?: AxiosRequestConfig) {
38-
return this.api.post("/search", data, {
39-
...HttpClient.clientConfig,
40-
...requestConfig,
41-
});
42-
}
43-
4437
post(data: unknown, requestConfig?: AxiosRequestConfig) {
4538
return this.api.post("", data, {
4639
...HttpClient.clientConfig,
@@ -62,6 +55,13 @@ export class HttpClient {
6255
});
6356
}
6457

58+
search(data: unknown, requestConfig?: AxiosRequestConfig) {
59+
return this.api.post("/search", data, {
60+
...HttpClient.clientConfig,
61+
...requestConfig,
62+
});
63+
}
64+
6565
delete(requestConfig?: AxiosRequestConfig) {
6666
return this.api.delete("", {
6767
...HttpClient.clientConfig,
@@ -97,6 +97,20 @@ export class HttpClient {
9797
});
9898
}
9999

100+
video(data: unknown, requestConfig?: AxiosRequestConfig) {
101+
return this.api.post("/video", data, {
102+
...HttpClient.clientConfig,
103+
...requestConfig,
104+
});
105+
}
106+
107+
image(data: unknown, requestConfig?: AxiosRequestConfig) {
108+
return this.api.post("/image", data, {
109+
...HttpClient.clientConfig,
110+
...requestConfig,
111+
});
112+
}
113+
100114
private setting() {
101115
HttpClient.setCommonInterceptors(this.api);
102116
}
@@ -136,6 +150,7 @@ export default {
136150
memberName: new HttpClient("/api/member/name", axiosConfig),
137151
comment: new HttpClient("/api/comment", axiosConfig),
138152
file: new HttpClient("/api/file", axiosConfig),
153+
fileUpload: new HttpClient("/api/file/upload", axiosConfig),
139154
follow: new HttpClient("/api/follow", axiosConfig),
140155
refreshToken: new HttpClient("/api/refresh-token", axiosConfig),
141156
revalidatePortfolio: new HttpClient("/api/revalidate-portfolio", axiosConfig),

Diff for: components/app/PortfolioPlayer.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import useWindow from "@/hooks/useWindow";
12
import { PortfolioType } from "@/types/portfolio.interface";
23
import classNames from "classnames";
34
import { useEffect, useState } from "react";
45
import Button from "../atoms/Button";
6+
import Video from "../atoms/Video";
57

68
interface PortfolioProps {
79
type: PortfolioType;
@@ -17,6 +19,8 @@ export default function PortfolioPlayer({
1719
const [selected, setSelected] = useState<"web" | "video">(
1820
type === "URL" ? "web" : "video",
1921
);
22+
const { isWindow } = useWindow();
23+
2024
useEffect(() => {
2125
setSelected(type === "URL" ? "web" : "video");
2226
}, [type]);
@@ -60,10 +64,8 @@ export default function PortfolioPlayer({
6064
title="portfolio"
6165
/>
6266
)}
63-
{selected === "video" && (
64-
<video src={videoUrl} controls className="w-full">
65-
<track default kind="captions" srcLang="ko" src={videoUrl} />
66-
</video>
67+
{selected === "video" && isWindow && (
68+
<Video width="100%" height="auto" src={videoUrl} controls />
6769
)}
6870
</div>
6971
);

Diff for: components/app/UploadModal/FileUploadView.tsx

+22-71
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,34 @@
1-
import FileUploader from "@/components/atoms/FileUploader";
1+
import DropFileUploader from "@/components/atoms/DropFileUploader";
22
import Input from "@/components/atoms/Input";
33
import { PortfolioForm } from "@/types/portfolio.interface";
44
import classNames from "classnames";
5-
import Image from "next/image";
65
import { Dispatch, SetStateAction } from "react";
76
import { UseFormRegister } from "react-hook-form";
87

8+
interface FileUploadViewProps {
9+
register: UseFormRegister<PortfolioForm>;
10+
className?: string;
11+
setVideoFileList: Dispatch<SetStateAction<File[]>>;
12+
setThumbnailFileList: Dispatch<SetStateAction<File[]>>;
13+
}
14+
915
export default function FileUploadView({
1016
register,
1117
className,
12-
videoFile,
13-
setVideoFile,
14-
setThumbnailFile,
15-
thumbnailFile,
18+
setVideoFileList,
19+
setThumbnailFileList,
1620
...props
17-
}: {
18-
register: UseFormRegister<PortfolioForm>;
19-
className?: string;
20-
videoFile: File | undefined;
21-
setVideoFile: Dispatch<SetStateAction<File | undefined>>;
22-
thumbnailFile: File | undefined;
23-
setThumbnailFile: Dispatch<SetStateAction<File | undefined>>;
24-
}) {
25-
const getFileUploaderBorderCss = () => {
26-
return `
27-
relative
28-
border
29-
mb-2.5
30-
flex
31-
flex-col
32-
justify-center
33-
items-center
34-
border-primary-border_gray
35-
gap-2.5
36-
rounded-lg`;
37-
};
38-
21+
}: FileUploadViewProps) {
3922
return (
4023
<div className={classNames("flex flex-col gap-8", className)} {...props}>
4124
<div>
4225
<h2 className="mb-2">동영상</h2>
43-
<div
44-
className={classNames("w-full h-[11rem]", getFileUploaderBorderCss())}
45-
>
46-
<FileUploader
47-
id="video-uploader"
48-
label="동영상 업로드"
49-
onChange={(event) => {
50-
if (event.target.files) setVideoFile(event.target.files[0]);
51-
}}
52-
/>
53-
{videoFile && (
54-
<video
55-
src={URL.createObjectURL(videoFile)}
56-
className="w-full h-44 -z-10 absolute object-cover rounded-lg opacity-30"
57-
>
58-
<track kind="captions" src={URL.createObjectURL(videoFile)} />
59-
</video>
60-
)}
61-
</div>
62-
26+
<DropFileUploader
27+
id="video"
28+
setFileList={setVideoFileList}
29+
label="동영상 드래그 앤 드롭으로 업로드"
30+
accept="video"
31+
/>
6332
<div>
6433
<h2 className="mb-2">웹 주소</h2>
6534
<div className="w-full flex flex-col items-center border-primary-border_gray gap-2.5 rounded-lg">
@@ -72,29 +41,11 @@ export default function FileUploadView({
7241
</div>
7342

7443
<h2 className="mt-5 mb-2">썸네일</h2>
75-
<div
76-
className={classNames(
77-
"w-[20rem] h-[11.25rem]",
78-
getFileUploaderBorderCss(),
79-
)}
80-
>
81-
<FileUploader
82-
id="thumbnail-uploader"
83-
label="썸네일 업로드"
84-
onChange={(event) => {
85-
if (event.target.files) setThumbnailFile(event.target.files[0]);
86-
}}
87-
/>
88-
{thumbnailFile && (
89-
<Image
90-
src={URL.createObjectURL(thumbnailFile)}
91-
alt="ddd"
92-
fill
93-
sizes="20rem"
94-
className="rounded-lg -z-10 opacity-30"
95-
/>
96-
)}
97-
</div>
44+
<DropFileUploader
45+
id="thumbnail"
46+
setFileList={setThumbnailFileList}
47+
label="썸네일 드래그 앤 드롭으로 업로드"
48+
/>
9849
<span>이미지 사이즈: 1280x720 (너비 640px 이상)</span>
9950
</div>
10051
</div>

Diff for: components/app/UploadModal/index.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ export default function UploadModal({ closeModal }: UploadModalProps) {
2525
const [pageIndex, setPageIndex] = useState(0);
2626
const [selectedSkills, setSelectedSkills] = useState<Skill[]>([]);
2727
const [selectedMembers, setSelectedMembers] = useState<Member[]>([]);
28-
const [thumbnailFile, setThumbnailFile] = useState<File>();
29-
const [videoFile, setVideoFile] = useState<File>();
28+
const [videoFileList, setVideoFileList] = useState<File[]>([]);
29+
const [thumbnailFileList, setThumbnailFileList] = useState<File[]>([]);
3030

3131
const { register, handleSubmit, setValue } = useForm<PortfolioForm>();
3232
const queryClient = useQueryClient();
3333
const { openToast } = useOverlay();
3434

3535
const onValid: SubmitHandler<PortfolioForm> = async (data) => {
3636
const videoFileUid =
37-
videoFile && (await getFileUidByFileUpload(videoFile, openToast));
37+
videoFileList.length &&
38+
(await getFileUidByFileUpload(videoFileList[0], openToast, "video"));
3839
const thumbnailFileUid =
39-
thumbnailFile && (await getFileUidByFileUpload(thumbnailFile, openToast));
40+
thumbnailFileList.length &&
41+
(await getFileUidByFileUpload(thumbnailFileList[0], openToast, "image"));
4042

4143
const getPortfolioType = (): PortfolioType => {
4244
if (data.portfolioUrl.length > 0 && videoFileUid) {
@@ -92,10 +94,8 @@ export default function UploadModal({ closeModal }: UploadModalProps) {
9294
<FileUploadView
9395
className={classNames({ hidden: pageIndex !== 0 })}
9496
register={register}
95-
videoFile={videoFile}
96-
setVideoFile={setVideoFile}
97-
thumbnailFile={thumbnailFile}
98-
setThumbnailFile={setThumbnailFile}
97+
setThumbnailFileList={setThumbnailFileList}
98+
setVideoFileList={setVideoFileList}
9999
/>
100100
<FormView
101101
className={classNames({ hidden: pageIndex !== 1 })}

Diff for: components/atoms/DropFileUploader.tsx

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import useFileDrop from "@/hooks/useFileDrop";
2+
import classNames from "classnames";
3+
import Image from "next/image";
4+
import {
5+
Dispatch,
6+
ReactNode,
7+
SetStateAction,
8+
useEffect,
9+
useState,
10+
} from "react";
11+
12+
type AcceptType = "image" | "video";
13+
interface DropFileUploaderProps {
14+
id?: string;
15+
className?: string;
16+
label?: string | ReactNode;
17+
isSingleFile?: boolean;
18+
accept?: AcceptType;
19+
setFileList: Dispatch<SetStateAction<File[]>>;
20+
}
21+
22+
export default function DropFileUploader({
23+
id = "",
24+
className = "",
25+
label = "",
26+
isSingleFile = true,
27+
accept = "image",
28+
setFileList,
29+
}: DropFileUploaderProps) {
30+
const [previewFile, setPreviewFile] = useState<File>();
31+
const { files, inputRef, isDragActive, labelRef } = useFileDrop({
32+
isSingleFile,
33+
accept: accept === "video" ? "video/*" : "image/*",
34+
});
35+
36+
const getFileUploaderBorderCss = () => {
37+
return `
38+
relative
39+
border
40+
mb-2.5
41+
flex
42+
flex-col
43+
justify-center
44+
items-center
45+
gap-2.5
46+
rounded-lg
47+
cursor-pointer
48+
`;
49+
};
50+
51+
useEffect(() => {
52+
setFileList(files);
53+
setPreviewFile(files[0]);
54+
}, [files, setFileList]);
55+
56+
return (
57+
<label
58+
htmlFor={id}
59+
className={classNames(
60+
className,
61+
getFileUploaderBorderCss(),
62+
"relative w-full h-[11rem]",
63+
{
64+
"!border-2 !border-blue border-dotted": isDragActive,
65+
},
66+
)}
67+
ref={labelRef}
68+
>
69+
<input ref={inputRef} type="file" className="hidden" id={id} />
70+
71+
{label}
72+
73+
{previewFile && accept === "image" && (
74+
<Image
75+
src={URL.createObjectURL(previewFile)}
76+
alt="썸네일"
77+
fill
78+
sizes="20rem"
79+
className="rounded-lg -z-10 opacity-30"
80+
/>
81+
)}
82+
83+
{previewFile && accept === "video" && (
84+
<video
85+
src={URL.createObjectURL(previewFile)}
86+
className="w-full h-44 -z-10 absolute object-cover rounded-lg opacity-30"
87+
>
88+
<track kind="captions" src={URL.createObjectURL(previewFile)} />
89+
</video>
90+
)}
91+
</label>
92+
);
93+
}

Diff for: components/atoms/FileUploader.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import classNames from "classnames";
2-
import { ChangeEventHandler, InputHTMLAttributes } from "react";
2+
import { ChangeEventHandler, InputHTMLAttributes, RefObject } from "react";
33

44
type ButtonVarient = "primary" | "secondary";
55
interface FileUploaderProps extends InputHTMLAttributes<HTMLInputElement> {
@@ -8,6 +8,7 @@ interface FileUploaderProps extends InputHTMLAttributes<HTMLInputElement> {
88
label?: string;
99
varient?: ButtonVarient;
1010
id?: string;
11+
inputRef?: RefObject<HTMLInputElement>;
1112
}
1213

1314
const getFileUploaderCss = (varient: ButtonVarient) => {
@@ -30,6 +31,7 @@ export default function FileUploader({
3031
label,
3132
className = "",
3233
id = "file-uploader",
34+
inputRef,
3335
}: FileUploaderProps) {
3436
return (
3537
<>
@@ -40,9 +42,10 @@ export default function FileUploader({
4042
{label}
4143
</label>
4244
<input
45+
ref={inputRef}
4346
type="file"
4447
id={id}
45-
className={classNames("hidden")}
48+
className="hidden"
4649
onChange={onChange}
4750
/>
4851
</>

Diff for: components/atoms/Radio.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import classNames from "classnames";
2+
import { InputHTMLAttributes } from "react";
23
import { UseFormRegisterReturn } from "react-hook-form";
34

4-
interface RadioProps {
5+
interface RadioProps extends InputHTMLAttributes<HTMLInputElement> {
56
id: string;
67
label: string;
78
description?: string;

Diff for: components/atoms/Video.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import useWindow from "@/hooks/useWindow";
2+
import { ReactPlayerProps } from "react-player";
3+
import ReactPlayer from "react-player/lazy";
4+
5+
interface VideoProps extends ReactPlayerProps {
6+
src?: string | string[];
7+
}
8+
9+
export default function Video({ src, ...props }: VideoProps) {
10+
const { isWindow } = useWindow();
11+
if (!isWindow) return <div />;
12+
13+
return <ReactPlayer width="100%" url={src} {...props} />;
14+
}

0 commit comments

Comments
 (0)