-
Notifications
You must be signed in to change notification settings - Fork 6
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
Changes from 32 commits
318efec
31640bc
7ac89b3
0a61b39
25095ce
02ae6e1
9267607
635b181
05d2881
a5e02bc
c519710
2a37f5e
4978612
c8f5411
1b321a1
0c7d9f8
d565b61
10152fe
1fa5392
4207f32
506c0c0
cc05321
9398888
b6fbb91
9166f8d
95bad42
aa8cc07
ba9658c
e7631a7
96dd30e
6756fc4
da6637f
0f71c85
ad7f2d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
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; |
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; |
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> | ||
</> | ||
); | ||
} |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 서영님 혹시 이렇게 각기 다른 속성이 한개씩만 있는 경우에는 공백없이 작성하는 것도 좋을 것 같은데 서영님 생각은 어떠신가요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', | ||
}); |
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> | ||
); | ||
} |
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, | ||
}, | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
뿌듯하네요😊 ㅋㅋㅋㅋ