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

QA에서 나왔던 닉네임 validate 추가 #672

Merged
merged 10 commits into from
Oct 17, 2024
2 changes: 1 addition & 1 deletion frontend/src/apis/deletes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export const deleteCancelChamyo = async (moimId: number) => {
};

export const deleteMyInfo = async () => {
await ApiClient.deleteWithLastDarakbangId(`/member/delete`);
await ApiClient.deleteWithLastDarakbangId(`/auth`);
};
9 changes: 9 additions & 0 deletions frontend/src/common/assets/default_profile.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions frontend/src/common/assets/empty_profile.svg

This file was deleted.

12 changes: 7 additions & 5 deletions frontend/src/components/Input/MoimInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTheme } from '@emotion/react';

export interface LabeledInputProps<T extends string | number>
extends HTMLProps<HTMLInputElement> {
title: string;
title?: string;
validateFun?: (value: T) => boolean;
}

Expand Down Expand Up @@ -49,10 +49,12 @@ export default function LabeledInput<T extends string | number>(

return (
<label htmlFor={title} css={S.labelWrapper}>
<h3 css={S.title({ theme })}>
{title}
<span css={S.required({ theme })}>{required ? '*' : ''}</span>
</h3>
{title && (
<h3 css={S.title({ theme })}>
{title}
<span css={S.required({ theme })}>{required ? '*' : ''}</span>
</h3>
)}

<input
name={name}
Expand Down
19 changes: 15 additions & 4 deletions frontend/src/components/ProfileFrame/ProfileFrame.style.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { css } from '@emotion/react';
import EmptyProfile from '@_common/assets/empty_profile.svg?url';
import { css, Theme } from '@emotion/react';
import EmptyProfile from '@_common/assets/default_profile.svg?url';
type Size = number;

export const profileBox = () => {
Expand All @@ -8,7 +8,17 @@ export const profileBox = () => {
`;
};

export const profileFrame = (width: Size, height: Size, borderWidth: Size) => {
export const profileFrame = ({
width,
height,
borderWidth,
theme,
}: {
width: Size;
height: Size;
borderWidth: Size;
theme: Theme;
}) => {
return css`
overflow: hidden;
display: flex;
Expand All @@ -17,8 +27,9 @@ export const profileFrame = (width: Size, height: Size, borderWidth: Size) => {

width: ${width}rem;
height: ${height}rem;
background: ${theme.colorPalette.white[100]};

border: ${borderWidth}rem solid orange;
border: ${borderWidth}rem solid ${theme.colorPalette.orange[200]};
border-radius: 300rem;
`;
};
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/ProfileFrame/ProfileFrame.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as S from './ProfileFrame.style';

import Crown from '@_common/assets/crown.svg?url';
import EmptyProfile from '@_common/assets/empty_profile.svg?url';
import DefaultProfile from '@_common/assets/default_profile.svg?url';
import { ImgHTMLAttributes, useState } from 'react';
import { Role } from '@_types/index';
import { useTheme } from '@emotion/react';

interface ProfileFrameProps extends ImgHTMLAttributes<HTMLImageElement> {
role?: Role;
Expand All @@ -22,26 +23,27 @@ export default function ProfileFrame(props: ProfileFrameProps) {
src,
...args
} = props;
const theme = useTheme();
const [isLoaded, setIsLoaded] = useState(false);
const handleError = (
event: React.SyntheticEvent<HTMLImageElement, Event>,
) => {
if (onError) {
onError(event);
}
event.currentTarget.src = EmptyProfile;
event.currentTarget.src = DefaultProfile;
};

return (
<div css={S.profileBox()}>
{role === 'MOIMER' ? <img src={Crown} css={S.profileCrown(width)} /> : ''}
<div css={S.profileFrame(width, height, borderWidth)}>
<div css={S.profileFrame({ width, height, borderWidth, theme })}>
{!isLoaded && (
<img src={EmptyProfile} css={S.profileImage()} alt="Placeholder" />
<img src={DefaultProfile} css={S.profileImage()} alt="Placeholder" />
)}
<img
css={S.profileImage({ isLoaded })}
src={src || EmptyProfile}
src={src || DefaultProfile}
onLoad={() => setIsLoaded(true)}
{...args}
onError={handleError}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/UserPreview/UserPreview.style.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { css } from '@emotion/react';
import defaultProfile from '@_common/assets/empty_profile.svg?url';
import defaultProfile from '@_common/assets/default_profile.svg?url';

export const preview = ({
imageUrl,
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/constants/poclies.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

GooooooooD

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const POLICES = {

maxDarakbangName: 40,

maxNicknameLength: 10,
minNicknameLength: 1,
maxNicknameLength: 12,

entranceCodeLength: 7,
};
Expand Down
19 changes: 13 additions & 6 deletions frontend/src/pages/Mypage/MyPage.style.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { css, Theme } from '@emotion/react';

export const AccountButton = (props: { theme: Theme }) => css`
${props.theme.typography.b2}
export const AccountButton = ({
theme,
isValidMyInfo = true,
}: {
theme: Theme;
isValidMyInfo?: boolean;
}) => css`
${theme.typography.b2}
color: ${isValidMyInfo ? '' : theme.colorPalette.grey[200]};
background: none;
border: none;
`;
Expand All @@ -13,7 +20,7 @@ export const mainContainer = () => css`
align-items: center;
`;

export const editButton = (props: { theme: Theme }) => css`
export const editButton = ({ theme }: { theme: Theme }) => css`
display: flex;
gap: 1rem;
align-items: center;
Expand All @@ -22,13 +29,13 @@ export const editButton = (props: { theme: Theme }) => css`
width: 25rem;
padding: 1.6rem 5.9rem;

color: ${props.theme.colorPalette.white[100]};
color: ${theme.colorPalette.white[100]};

background-color: ${props.theme.semantic.primary};
background-color: ${theme.semantic.primary};
border: none;
border-radius: 3rem;

&:active {
background-color: ${props.theme.colorPalette.orange[900]};
background-color: ${theme.colorPalette.orange[900]};
}
`;
120 changes: 29 additions & 91 deletions frontend/src/pages/Mypage/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,108 +1,41 @@
// import { useRef } from 'react';
import { css, useTheme } from '@emotion/react';
import InformationLayout from '@_layouts/InformationLayout/InformationLayout';
import MineInfoCard from './components/MineInfoCard/MineInfoCard';
import NavigationBar from '@_components/NavigationBar/NavigationBar';
import NavigationBarWrapper from '@_layouts/components/NavigationBarWrapper/NavigationBarWrapper';
import { common } from '@_common/common.style';
import useMyInfo from '@_hooks/queries/useMyInfo';

Check failure on line 7 in frontend/src/pages/Mypage/MyPage.tsx

View workflow job for this annotation

GitHub Actions / test

'useMyInfo' is defined but never used
import * as S from './MyPage.style';
import MyInfoTabBar from './components/MyInfoTabBar/MyInfoTabBar';
import Setting from '@_common/assets/setting.svg';
import Edit from '@_common/assets/edit.svg';
import { ChangeEvent, Fragment, useEffect, useRef, useState } from 'react'; // ChangeEvent를 가져옴
import useEditMyInfo from '@_hooks/mutaions/useEditMyInfo';
import { Fragment } from 'react';
import { useNavigate } from 'react-router-dom';
import GET_ROUTES from '@_common/getRoutes';
import useMyPage from './hook/useMyPage';

export default function MyPage() {
const navigate = useNavigate();
const { myInfo } = useMyInfo();
const fileInput = useRef<HTMLInputElement | null>(null); // 타입을 명시적으로 지정
const [profile, setProfile] = useState(myInfo?.profile || '');
const [nickname, setNickname] = useState(myInfo?.nickname || '');
const [description, setDescription] = useState(myInfo?.description || '');
const [selectedFile, setSelectedFile] = useState<File | string>('');
const [isEditing, setIsEditing] = useState(false); // 편집 모드 상태
const [isReset, setIsReset] = useState('false');
const [isShownRest, setIsShownRest] = useState(false);

const theme = useTheme();
const { mutate } = useEditMyInfo();

useEffect(() => {
if (myInfo) {
setNickname(myInfo.nickname || '');
setDescription(myInfo.description || '');
setProfile(myInfo.profile || '');
setSelectedFile(myInfo.profile || '');
myInfo.profile && setIsShownRest(true);
}
}, [myInfo]); // myInfo가 업데이트될 때마다 상태 업데이트

const handleEditClick = () => {
setIsEditing((prev) => !prev); // 편집 모드 활성화
setSelectedFile('');
};

const onChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setSelectedFile(e.target.files[0]); // 선택한 파일을 상태에 저장
setIsShownRest(true);
setIsReset('false');
} else {
setProfile(myInfo?.profile ?? '');
return;
}

const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2 && typeof reader.result === 'string') {
setProfile(reader.result);
}
};
reader.readAsDataURL(e.target.files[0]);
};

const onUpload = async () => {
const formData = new FormData();

// 파일 추가

formData.append('file', selectedFile);
// 문자열 데이터 추가
formData.append('nickname', nickname ?? '');
formData.append('description', description ?? '');
formData.append('isReset', isReset);

try {
// 서버로 파일 및 데이터 전송
mutate(formData); // FormData 객체 자체를 전달
handleEditClick(); // 편집 모드 비활성화
} catch (error) {
console.error('파일 업로드 실패', error);
}
};

const handleProfileClick = () => {
fileInput.current?.click(); // ProfileFrame 클릭 시 파일 선택창 열기
};

const handleCancel = () => {
// 편집 취소시 기존 데이터 복구
setProfile(myInfo?.profile || '');
setNickname(myInfo?.nickname || '');
setDescription(myInfo?.description || '');
setIsEditing(false);
setIsReset('false');
myInfo?.profile && setIsShownRest(false);
};
const handleDefaultProfile = () => {
setProfile('');
setSelectedFile('');
setIsReset('true');
setIsShownRest(false);
};
const {
myInfo,
fileInput,
profile,
nickname,
description,
isEditing,
isShownRest,
isValidMyInfo,
setNickname,
setDescription,
handleEditClick,
onChange,
onUpload,
handleProfileClick,
handleCancel,
handleDefaultProfile,
} = useMyPage();

return (
<Fragment>
Expand Down Expand Up @@ -139,9 +72,14 @@
기본이미지로 변경
</button>
)}
<button css={S.AccountButton({ theme })} onClick={onUpload}>
저장
</button>
{isValidMyInfo && (
<button
css={S.AccountButton({ theme })}
onClick={() => onUpload()}
>
저장
</button>
)}
<button css={S.AccountButton({ theme })} onClick={handleCancel}>
취소
</button>
Expand All @@ -155,7 +93,7 @@
<MineInfoCard
myInfo={{
nickname,
profile: profile,
profile,
name: myInfo.name,
}}
onProfileClick={handleProfileClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ export const editSVG = () => css`

export const input = (props: { theme: Theme }) => css`
${props.theme.typography.h5}
max-width: 60%;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import * as S from './MineInfoCard.style';
import ProfileFrame from '@_components/ProfileFrame/ProfileFrame';
import { useTheme } from '@emotion/react';
import Edit from '@_common/assets/edit.svg?url';
import LabeledInput from '@_components/Input/MoimInput';
import { ChangeEvent } from 'react';
import { validateNickName } from '@_pages/Mypage/validate';

interface MineInfoCardProps {
myInfo: {
Expand Down Expand Up @@ -33,10 +36,14 @@ export default function MineInfoCard({
{isEditing && <img src={Edit} alt="Edit" css={S.editSVG} />}
</div>
{isEditing ? (
<input
<LabeledInput
css={S.input({ theme })}
value={nickname}
onChange={(e) => setNickname(e.target.value)}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setNickname(e.target.value)
}
placeholder="닉네임을 1자에서 12자이하로 지어주세요"
validateFun={validateNickName}
/>
) : (
<span css={theme.typography.h5}>{nickname}</span>
Expand Down
Loading
Loading