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

Feat: 프로필 설정 페이지 구현 및 공용 폰트 업데이트 #30

Merged
merged 34 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
318efec
Refactor: 매직넘버 상수화
seoyoung-min Feb 11, 2024
31640bc
Refactor: 아이템 추가 코멘트 글자수 괄호 제거
seoyoung-min Feb 11, 2024
7ac89b3
Fix: 아이템 추가 타이틀 input박스 가로 길이 늘어나는 오류 수정
seoyoung-min Feb 11, 2024
0a61b39
Fix: padding 속성 none 값 0으로 수정
seoyoung-min Feb 11, 2024
25095ce
Feat: 프로필 이미지 컴포넌트 생성
seoyoung-min Feb 11, 2024
02ae6e1
Feat: 프로필 설정 페이지 퍼블리싱
seoyoung-min Feb 11, 2024
9267607
Chore: type 파스카 케이스로 수정
seoyoung-min Feb 13, 2024
635b181
Chore: 임시 파일 제거
seoyoung-min Feb 14, 2024
05d2881
Feat: debounce 유틸 함수 추가
seoyoung-min Feb 14, 2024
a5e02bc
Feat: input규칙, 플레이스홀더, 토스트 메시지, 타입 상수 추가
seoyoung-min Feb 14, 2024
c519710
Fix: 카카오톡 관련 경고 해결 위한 속성 추가
seoyoung-min Feb 14, 2024
2a37f5e
Refactor: 불필요 파일 삭제
seoyoung-min Feb 14, 2024
4978612
Feat: 이미지 파일미리보기 위한 fileToBase64 유틸 함수 추가
seoyoung-min Feb 14, 2024
c8f5411
Refactor: 객체 key 이름에 value형식 유추가능하도록 변경
seoyoung-min Feb 14, 2024
1b321a1
Feat: 닉네임 중복 검사 api 추가
seoyoung-min Feb 14, 2024
0c7d9f8
Feat: 프로필 수정 및 이미지 업로드 api 추가
seoyoung-min Feb 14, 2024
d565b61
Feat: 이미지 압축 라이브러리 추가 및 유틸 함수 추가
seoyoung-min Feb 14, 2024
10152fe
Feat: 배경 및 프로필 이미지 미리보기 컴포넌트 구현
seoyoung-min Feb 14, 2024
1fa5392
Feat: 프로필 수정 페이지 구현
seoyoung-min Feb 14, 2024
4207f32
Fix: import 파일 잘못된 이름 수정
seoyoung-min Feb 14, 2024
506c0c0
Feat: 파란 버튼 공용 컴포넌트 생성
seoyoung-min Feb 14, 2024
cc05321
Feat: 프로필 설정 페이지 헤더 추가
seoyoung-min Feb 14, 2024
9398888
Fix: autoComplete false에서 off로 변경
seoyoung-min Feb 14, 2024
b6fbb91
Chore: 공용함수, 훅, 컴포넌트 설명 주석 추가
seoyoung-min Feb 14, 2024
9166f8d
Feat: 임시 공용 폰트 추가(미완성)
seoyoung-min Feb 14, 2024
95bad42
Feat: 프로필 수정 후 조회 다시 하기 및 Dev-CD 파일 삭제
seoyoung-min Feb 14, 2024
aa8cc07
Design: 공용 폰트 업데이트된 스타일로 수정
seoyoung-min Feb 14, 2024
ba9658c
Chore: Type 첫글자 대문자로 수정
seoyoung-min Feb 14, 2024
e7631a7
Refactor: 불필요 타입 제거 및 코드리뷰 피드백 반영하여 updateProfile 코드 개선
seoyoung-min Feb 14, 2024
96dd30e
Refactor: 토스트 메시지 상수 그룹화 방식 다국어 기능에 맞추어 변경
seoyoung-min Feb 14, 2024
6756fc4
Refactor: 코드리뷰 반영하여 타입, 컨벤션, 순서, 코드 리팩토링
seoyoung-min Feb 14, 2024
da6637f
Fix: 잘 못된 import 방식 수정
seoyoung-min Feb 14, 2024
0f71c85
Refactor: 디바운스 함수 훅으로 재구현 및 JSDoc 수정
seoyoung-min Feb 15, 2024
ad7f2d1
Fix: dev와의 충돌 해결
seoyoung-min Feb 15, 2024
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
31 changes: 0 additions & 31 deletions .github/workflows/Dev-CD.yml

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@vanilla-extract/next-plugin": "^2.3.2",
"@yaireo/tagify": "^4.19.0",
"axios": "^1.6.5",
"browser-image-compression": "^2.0.2",
"cheerio": "^1.0.0-rc.12",
"copy-to-clipboard": "^3.3.3",
"html-to-image": "^1.11.11",
Expand Down
5 changes: 5 additions & 0 deletions public/icons/camera.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/error_x.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions src/app/_api/list/uploadItemImages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import axiosInstance from '@/lib/axios/axiosInstance';
import { ItemImagesType, PresignedUrlListType } from '@/lib/types/listType';
import axios from 'axios';

interface uploadItemImagesProps {
interface UploadItemImagesProps {
listId: number;
imageData: ItemImagesType;
imageFileList: File[];
}

export const uploadItemImages = async ({ listId, imageData, imageFileList }: uploadItemImagesProps) => {
const uploadItemImages = async ({ listId, imageData, imageFileList }: UploadItemImagesProps) => {
imageData.listId = listId;

//PresignedUrl 생성 요청
Expand All @@ -29,3 +29,5 @@ export const uploadItemImages = async ({ listId, imageData, imageFileList }: upl
await axiosInstance.post('/lists/upload-complete', imageData);
}
};

export default uploadItemImages;
9 changes: 9 additions & 0 deletions src/app/_api/user/checkNicknameDuplication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import axiosInstance from '@/lib/axios/axiosInstance';

const checkNicknameDuplication = async (nickname: string) => {
const result = await axiosInstance.get<boolean>(`/users/exists?nickname=${nickname}`);

return result.data; //true:중복 false:미중복
};

export default checkNicknameDuplication;
58 changes: 58 additions & 0 deletions src/app/_api/user/updateProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import axiosInstance from '@/lib/axios/axiosInstance';
import axios from 'axios';
import { UserProfileEditType } from '@/lib/types/userProfileType';
import compressFile from '@/lib/utils/compressFile';

//프로필수정 이미지업로드 타입
interface UserPresignedUrlsType {
ownerId: number;
backgroundPresignedUrl: string;
profilePresignedUrl: string;
}

interface UpdateProfileParams {
userId: Number;
data: UserProfileEditType;
}

const updateProfile = async ({ userId, data }: UpdateProfileParams) => {
const { nickname, description, backgroundImageUrl, profileImageUrl, newBackgroundFileList, newProfileFileList } =
data;

//프로필 수정
const result = await axiosInstance.patch(`/users/${userId}`, {
nickname,
description,
backgroundImageUrl,
profileImageUrl,
});

//이미지 수정 없는 경우 return
if (result.status !== 204 || (newBackgroundFileList === null && newProfileFileList === null)) return;

//1. presignedUrl 생성요청
const imageData = {
ownerId: userId,
backgroundExtension: newBackgroundFileList?.[0].type.split('/')[1],
profileExtension: newProfileFileList?.[0].type.split('/')[1],
};
const response = await axiosInstance.post<UserPresignedUrlsType>('/users/upload-url', imageData);

//2. presignedUrl에 사진 업로드
const { backgroundPresignedUrl, profilePresignedUrl } = response?.data;

if (newBackgroundFileList !== null) {
const resultFile = await compressFile(newBackgroundFileList[0]);
await axios.put(backgroundPresignedUrl, resultFile);
}

if (newProfileFileList !== null) {
const resultFile = await compressFile(newProfileFileList[0]);
await axios.put(profilePresignedUrl, resultFile);
}

//3.서버에 성공 알림
await axiosInstance.post('/users/upload-complete', imageData);
};

export default updateProfile;
12 changes: 11 additions & 1 deletion src/app/account/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
'use client';

import useMoveToPage from '@/hooks/useMoveToPage';

export default function AccountPage() {
return <div>마이페이지</div>;
const { onClickMoveToPage } = useMoveToPage();
return (
<>
<div>마이페이지</div>
<button onClick={onClickMoveToPage('account/profile')}>프로필설정</button>
Comment on lines +6 to +10
Copy link
Contributor

Choose a reason for hiding this comment

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

뿌듯하네요😊 ㅋㅋㅋㅋ

</>
);
}
48 changes: 48 additions & 0 deletions src/app/account/profile/_components/ImagePreview.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { style } from '@vanilla-extract/css';

export const backgroundImageContainer = style({
maxWidth: 400,
width: '100%',
height: 230,

display: 'flex',
alignItems: 'center',

position: 'relative',

borderRadius: '30px',

overflow: 'hidden',
Comment on lines +9 to +15
Copy link
Contributor

Choose a reason for hiding this comment

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

서영님 혹시 이렇게 각기 다른 속성이 한개씩만 있는 경우에는 공백없이 작성하는 것도 좋을 것 같은데 서영님 생각은 어떠신가요?!
css 컨벤션을 정했지만 그 이유가 가독성 측면이어서 지금처럼 4~5줄은 붙여도 될 것 같아서 의견드려봅니다..!
+. 하지만, 가독성은 개인차도 있을 것 같아서 참고로만 봐주시면 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오! 저도 순서만 지켜진다면, 각기 다른 속성 한개씩만 있는 경우 공백없이 작성하는 것 좋아보입니다!! 내일 안건으로 이야기 나눠보면 좋을 것 같아요 :)👍

});

export const transparentBox = style({
maxWidth: 400,
width: '100%',
height: 230,
padding: '0 23px',

display: 'flex',
alignItems: 'center',

position: 'absolute',

borderRadius: '30px',
});

export const profileImageContainer = style({
width: 90,
height: 90,

display: 'flex',
justifyContent: 'center',
alignItems: 'center',

position: 'relative',

backgroundColor: 'white',

borderRadius: '50%',
border: '3px solid white',

overflow: 'hidden',
});
28 changes: 28 additions & 0 deletions src/app/account/profile/_components/ImagePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Image from 'next/image';
import * as styles from './ImagePreview.css';

interface ImagePreviewProps {
backgroundImageUrl: string;
profileImageUrl: string;
}

/** TODO: 이미지 에러, 로딩 처리
* - [ ] placeholder=blur처리
* - [ ] ONERROR 처리
*/
export default function ImagePreview({ backgroundImageUrl, profileImageUrl }: ImagePreviewProps) {
return (
<div className={styles.backgroundImageContainer}>
{backgroundImageUrl && (
<>
<Image src={backgroundImageUrl} alt="배경이미지" fill style={{ objectFit: 'cover' }} priority />
<div className={styles.transparentBox}>
<div className={styles.profileImageContainer}>
<Image src={profileImageUrl} alt="프로필이미지" fill style={{ objectFit: 'cover' }} priority />
</div>
</div>
</>
)}
</div>
);
}
140 changes: 140 additions & 0 deletions src/app/account/profile/_components/ProfileForm.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { style, createVar } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';
import { labelSmall, bodyMedium, caption } from '@/styles/font.css';

export const form = style({
maxWidth: 400,
width: '100%',

display: 'flex',
flexDirection: 'column',
gap: '12px',
});

const container = style({
width: '100%',

padding: '10px 12px',

border: `1px solid ${vars.color.gray5}`,
});

export const inputContainer = style([
container,
{
display: 'flex',
flexDirection: 'column',
gap: '8px',
},
]);

export const label = style([labelSmall, { color: vars.color.gray9 }]);

export const inputText = style([bodyMedium]);

export const textarea = style([
bodyMedium,
{
border: 'none',
resize: 'none',
},
]);

export const textLength = style([
bodyMedium,
{
color: vars.color.gray9,
textAlign: 'end',
},
]);

export const inputFile = style({
display: 'none',
});

export const inputFileLabel = style({
border: `1px solid ${vars.color.black}`,
});

const option = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',

backgroundColor: vars.color.gray3 /**TODO: white로 대체예정*/,
backgroundImage: 'none' /**TODO: backgroundImage로 대체예정*/,

cursor: 'pointer',

selectors: {
'&:hover': {
border: `1px solid ${vars.color.blue}`,
},
},
});

export const backgroundOptionContainer = style([
container,
{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gridTemplateRows: '1fr 1fr',
gridColumnGap: 8,
gridRowGap: 10,
},
]);

export const backgroundOption = style([
option,
{
maxWidth: 85,
height: 47,

borderRadius: 15,
},
]);

export const profileOptionContainer = style([
container,
{
display: 'flex',
justifyContent: 'space-between',
gap: 14,

position: 'relative',
},
]);

export const profileOption = style([
option,
{
width: '100%',
minWidth: 30,

borderRadius: '50%',

selectors: {
'&::before': {
content: '',
display: 'block',
paddingBottom: '100%',
},
},
},
]);

export const error = style({
marginTop: '0.6rem',
marginLeft: '0.9rem',

display: 'flex',
alignItems: 'center',
gap: '0.45rem',
});

export const errorText = style([
caption,
{
color: vars.color.red,
},
]);
Loading
Loading