Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

마이페이지 ui 및 기능 추가 #205

Merged
merged 16 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat: 프로필 이미지 s3 업로드 및 프로필 이미지 변경 api 연동
  • Loading branch information
chaeyoung103 committed Feb 14, 2024
commit 90fc26b1c9beba8e997ff4ed2e46e944ca0d4a80
20 changes: 20 additions & 0 deletions src/apis/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ class Fetch {
return data as TData;
}

async put<TData>({ path, headers, body }: { path: string; headers?: HeadersInit; body: object }) {
const response = await fetch(`${this.baseURL}${path}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
...(this.accessToken && { Authorization: `Bearer ${this.accessToken}` }),
...headers,
},
body: JSON.stringify(body),
});

const data = await response.json();

if (!response.ok) {
throw new ResponseError(data);
}

return data as TData;
}

async delete<T>(path: string, body?: object): Promise<T> {
const response = await fetch(`${this.baseURL}${path}`, {
method: 'DELETE',
Expand Down
46 changes: 46 additions & 0 deletions src/apis/profile/useProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useMutation } from '@tanstack/react-query';

import { PresignedURLResponse, ProfileResponse } from '@interfaces/api/profile';

import client from '@apis/fetch';

// export interface profileRequestDTO {
// status?: 'VOTING' | 'CLOSED';
// keyword_id?: number;
// page?: number;
// size?: number;
// sort?: string;
// side?: 'TOPIC_A' | 'TOPIC_B';
// }

const getProfile = () => {
return client.get<ProfileResponse>('/members/profile');
};

const getPresignedURL = (fileName: string) => {
return client.post<PresignedURLResponse>({
path: `/images/profile`,
body: {
fileName: fileName,
},
});
};

const updateProfileImgURL = (profileImgURL: string) => {
return client.put({
path: `/members/profile/image`,
body: {
imageUrl: profileImgURL,
},
});
};

const useGetPresignedURL = (fileName: string) => {
return useMutation({ mutationFn: () => getPresignedURL(fileName) });
};

const useUpdateProfileImgURL = (profileImgURL: string) => {
return useMutation({ mutationFn: () => updateProfileImgURL(profileImgURL) });
};

export { getProfile, useGetPresignedURL, useUpdateProfileImgURL };
11 changes: 0 additions & 11 deletions src/constants/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,3 @@ export const TOPIC_DEADLINES: TopicDeadline[] = Array.from({ length: 24 }, (_, i
value: i + 1,
};
});

// export const TOPIC_DEADLINES: TopicDeadline[] = Array.from({ length: 24 }, (_, i) => {
// const hoursToAdd = i + 1;
// const date = new Date();
// date.setHours(date.getHours() + hoursToAdd);

// return {
// label: `${hoursToAdd}시간`,
// value: Math.floor(date.getTime() / 1000),
// };
// });
13 changes: 13 additions & 0 deletions src/interfaces/api/profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface ProfileResponse {
profileImageUrl: string;
nickname: string;
birth: string;
gender: string;
job: string;
}

interface PresignedURLResponse {
presignedUrl: string;
}

export type { ProfileResponse, PresignedURLResponse };
81 changes: 74 additions & 7 deletions src/routes/MyPage/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';

import { getProfile, useGetPresignedURL, useUpdateProfileImgURL } from '@apis/profile/useProfile';
import { Col, Row } from '@components/commons/Flex/Flex';
import Layout from '@components/commons/Layout/Layout';
import ActionModalButton from '@components/commons/Modal/ActionModalButton';
Expand Down Expand Up @@ -28,25 +29,69 @@ const MyPage = () => {

const profileImageInputRef = useRef<HTMLInputElement>(null);

const [profileImgUrl, setProfileImgUrl] = useState<string | null>(null);
const [profileImg, setProfileImgUrl] = useState<string | null>(null);
const [nickName, setNickName] = useState<string>('');
const [fileName, setFileName] = useState<string>('');
const [file, setFile] = useState<File>();
const [presignedURL, setpresignedURL] = useState<string>('');

const updateProfileImgMutation = useGetPresignedURL('.' + fileName);
const updateProfileImgURLMutation = useUpdateProfileImgURL(presignedURL);

const { Modal, toggleModal } = useModal('action');

const handleSelectFromAlbum = () => {
if (profileImageInputRef.current) {
profileImageInputRef.current.click();
const removeQueryString = (url: string): string => {
const urlObj = new URL(url);
return `${urlObj.origin}${urlObj.pathname}`;
};

const uploadFile = async () => {
if (!fileName || !file) {
return;
}
try {
const presignedURLResponse = await updateProfileImgMutation.mutateAsync();
const imageUrl = removeQueryString(presignedURLResponse.presignedUrl);

console.log(imageUrl);
setpresignedURL(imageUrl);

const result = await fetch(presignedURLResponse.presignedUrl, {
method: 'PUT',
headers: {
'Content-Type': 'image/' + fileName,
},
body: file,
});

if (result.ok) {
console.log('Upload successful');
const res = await updateProfileImgURLMutation.mutateAsync();
console.log('result', res);
} else {
console.error('Upload failed');
}
} catch (error) {
// 오류 처리 로직
}
};

const handleSelectFromAlbum = () => {
profileImageInputRef.current?.click();
};

const handleProfileImgFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const fileObj = event.target.files && event.target.files[0];
const fileObj = event.target.files?.[0];
if (!fileObj) {
return;
}

const reader = new FileReader();
reader.onloadend = () => {
setProfileImgUrl(reader.result as string);
const type = fileObj.name.split('.');
setFileName(type[type.length - 1].toLowerCase());
setFile(fileObj);
};
reader.readAsDataURL(fileObj);
toggleModal();
Expand All @@ -58,6 +103,28 @@ const MyPage = () => {
toggleModal();
};

useEffect(() => {
const fetchProfile = async () => {
try {
const response = await getProfile();
if (response.profileImageUrl) {
setProfileImgUrl(response.profileImageUrl);
}
setNickName(response.nickname);
} catch (error) {
console.error(error);
}
};

fetchProfile();
}, []);

useEffect(() => {
if (fileName && file) {
uploadFile();
}
}, [fileName, file]);

return (
<Layout
hasBottomNavigation={true}
Expand All @@ -76,13 +143,13 @@ const MyPage = () => {
<Col gap={100} alignItems="center">
<Col gap={30} alignItems="center">
<ProfileImgContainer>
<ProfileImg url={profileImgUrl} size={102} rounded={true}></ProfileImg>
<ProfileImg url={profileImg} size={102} rounded={true}></ProfileImg>
<PhotoButton onClick={handleOnClickPhotoButton}>
<CameraIcon />
</PhotoButton>
</ProfileImgContainer>
<Text size={22} weight={600} color={colors.white}>
사용자 이름
{nickName}
</Text>
</Col>
<Col gap={32} alignItems="flex-start">
Expand Down