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

mypage 퍼블리싱 및 기능 구현 #228

Merged
merged 12 commits into from
Feb 21, 2024
Merged
14 changes: 11 additions & 3 deletions src/apis/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,21 @@ class Fetch {
body: JSON.stringify(body),
});

const data = await response.json();

if (!response.ok) {
const data = await response.json();
throw new ResponseError(data);
}

return data as TData;
// 응답 본문이 있는지 확인
const text = await response.text();
if (text) {
// 응답 본문이 있으면 JSON으로 파싱
const data = JSON.parse(text);
return data as TData;
} else {
// 응답 본문이 없으면 null 반환
return null;
}
}

async delete<T>(path: string, body?: object): Promise<T> {
Expand Down
34 changes: 23 additions & 11 deletions src/apis/profile/useProfile.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { useMutation } from '@tanstack/react-query';

import { PresignedURLResponse, ProfileResponse } from '@interfaces/api/profile';
import {
ModifyProfileRequestDTO,
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');
};
Expand All @@ -35,6 +30,13 @@ const updateProfileImgURL = (profileImgURL: string) => {
});
};

const modifyProfile = (req: ModifyProfileRequestDTO) => {
return client.put({
path: `/members/profile/information`,
body: req,
});
};

const deleteProfileImg = () => {
return client.delete(`/members/profile/image`);
};
Expand All @@ -51,4 +53,14 @@ const useDeleteProfileImg = () => {
return useMutation({ mutationFn: () => deleteProfileImg() });
};

export { getProfile, useGetPresignedURL, useUpdateProfileImgURL, useDeleteProfileImg };
const useModifyProfile = () => {
return useMutation({ mutationFn: modifyProfile });
};

export {
getProfile,
useGetPresignedURL,
useUpdateProfileImgURL,
useDeleteProfileImg,
useModifyProfile,
};
4 changes: 3 additions & 1 deletion src/components/commons/Modal/ActionModalButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';

import { colors } from '@styles/theme';

import { Row } from '../Flex/Flex';
import Text from '../Text/Text';

Expand All @@ -14,7 +16,7 @@ const ActionModalButton = ({ handleClick, Icon, label }: ActionModalButtonProps)
<button onClick={handleClick}>
<Row alignItems={'center'} gap={14}>
<Icon />
<Text size={16} weight={500}>
<Text size={16} weight={500} color={colors.black}>
{label}
</Text>
</Row>
Expand Down
11 changes: 7 additions & 4 deletions src/components/commons/SelectInput/SelectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ const StyledSelect = styled.select<{ selected: boolean }>`
font-size: 1.4rem;
font-weight: 600;
line-height: 1.4;
color: ${({ theme }) => theme.colors.purple};
color: ${({ selected, theme }) => (selected ? theme.colors.white : theme.colors.purple_60)};
appearance: none;
background-color: #342b52;
background-color: ${({ theme }) => theme.colors.navy2_40};
border: none;
border-radius: 10px;
opacity: 0.6;

&:focus {
outline: none;
Expand All @@ -61,7 +60,11 @@ const SelectInput = (props: SelectInputProps) => {
<SelectLabel htmlFor={id}>
<DownChevronIcon />
</SelectLabel>
<StyledSelect {...register(id, options)} selected={watch(id) !== undefined} id={id}>
<StyledSelect
{...register(id, options)}
selected={watch(id) !== undefined && watch(id) !== ''}
id={id}
>
<option value="" disabled selected style={{ display: 'none' }}>
{placeholder}
</option>
Expand Down
2 changes: 1 addition & 1 deletion src/components/commons/TextInput/TextInput.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TextInputTheme } from './theme';

const StyledInput = styled.input<{ inputTheme: TextInputTheme }>`
width: 100%;
font-size: ${({ inputTheme }) => inputTheme.placeholderSize};
font-size: ${({ inputTheme }) => inputTheme.fontSize};
font-weight: ${({ inputTheme }) => inputTheme.fontWeight};
line-height: 1.4;
color: ${({ theme }) => theme.colors.white};
Expand Down
4 changes: 3 additions & 1 deletion src/components/commons/TextInput/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const theme1: TextInputTheme = {
fontSize: '1.4rem',
fontWeight: 600,
backgroundColor: colors.navy2_40,
placeholderColor: colors.purple,
placeholderColor: colors.purple_60,
placeholderSize: '1.4rem',
};

export const theme2: TextInputTheme = {
Expand All @@ -28,6 +29,7 @@ export const theme2: TextInputTheme = {
fontWeight: 500,
backgroundColor: colors.navy2_40,
placeholderColor: colors.purple,
placeholderSize: '1.6rem',
};

export const theme3: TextInputTheme = {
Expand Down
7 changes: 6 additions & 1 deletion src/interfaces/api/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ interface PresignedURLResponse {
presignedUrl: string;
}

export type { ProfileResponse, PresignedURLResponse };
interface ModifyProfileRequestDTO {
nickname: string;
job: string;
}

export type { ProfileResponse, PresignedURLResponse, ModifyProfileRequestDTO };
2 changes: 1 addition & 1 deletion src/routes/Auth/signup/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const Signup = () => {
options={CONFIG.NICKNAME.options}
placeholder={'한글, 영문, 숫자 최대 8자'}
right={() => (
<Text size={14} weight={700} color={colors.purple}>
<Text size={14} weight={700} color={colors.purple_60}>
{nicknameProgress}
</Text>
)}
Expand Down
40 changes: 40 additions & 0 deletions src/routes/MyPage/ModifyProfile/ModifyProfile.styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { color } from 'framer-motion';
import { styled } from 'styled-components';

import { colors } from '@styles/theme';

export const Container = styled.div`
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
height: 100%;
padding: 24px 20px 0;
background-color: ${(props) => props.theme.colors.navy};
`;
export const BackButton = styled.button`
width: 24px;
height: 24px;
padding: 3.6px 7.8px;
`;

export const Divider = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 1px;
background-color: ${colors.white_20};
`;

export const SubmitButton = styled.div`
position: absolute;
bottom: 48px;
left: 50%;
justify-content: center;
width: 100%;
padding: 0 20px;
transform: translateX(-50%);
`;
137 changes: 137 additions & 0 deletions src/routes/MyPage/ModifyProfile/ModifyProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { useEffect, useRef, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useLocation, useNavigate } from 'react-router-dom';

import { useModifyProfile } from '@apis/profile/useProfile';
import DefaultButton from '@components/commons/Button/DefaultButton';
import { Col, Row } from '@components/commons/Flex/Flex';
import InputField from '@components/commons/InputField/InputField';
import Layout from '@components/commons/Layout/Layout';
import SelectInput from '@components/commons/SelectInput/SelectInput';
import Text from '@components/commons/Text/Text';
import TextInput from '@components/commons/TextInput/TextInput';
import { theme1, theme2 } from '@components/commons/TextInput/theme';
import { Toast } from '@components/commons/Toast/Toast';
import { ModifyProfileRequestDTO } from '@interfaces/api/profile';

import { CONFIG, INPUT_TYPE } from '@constants/form';
import { JOBS } from '@constants/signup';

import { colors, theme } from '@styles/theme';

import { RightChevronIcon } from '@icons/index';

import { ResponseError } from '@apis/fetch';

import { BackButton, Container, Divider, SubmitButton } from './ModifyProfile.styles';

interface ModifyProfileProps {
birth: string;
gender: string;
}

const MAX_NICKNAME_LENGTH = 8;

const ModifyProfile = () => {
const navigate = useNavigate();
const location = useLocation();
const { birth, gender } = location.state as ModifyProfileProps;

const methods = useForm<ModifyProfileRequestDTO>({ mode: 'onChange' });
const modifyProfileMutation = useModifyProfile();

const nicknameProgress = methods.watch(INPUT_TYPE.NICKNAME)
? `${methods.watch(INPUT_TYPE.NICKNAME)?.length}/${MAX_NICKNAME_LENGTH}`
: '';

const handleSubmitForm = async () => {
const data = methods.getValues();
try {
const res = await modifyProfileMutation.mutateAsync({
nickname: data.nickname,
job: data.job,
});
console.log(res);
navigate(-1);
} catch (error) {
console.log(error);
if (error instanceof ResponseError) {
Toast.error(error.errorData.errorContent.message);
}
}
};

return (
<Layout
hasBottomNavigation={false}
HeaderLeft={
<BackButton onClick={() => navigate(-1)}>
<RightChevronIcon style={{ transform: 'rotate(180deg)' }} stroke={colors.white} />
</BackButton>
}
HeaderCenter={
<Text size={20} weight={600} color={colors.white}>
내 정보 수정
</Text>
}
>
<FormProvider {...methods}>
<Container>
<Col gap={24} alignItems="flex-start">
<Col gap={20} alignItems="flex-start">
<Row gap={12} alignItems="center">
<Text size={14} weight={600} color={colors.white_40}>
생년월일
</Text>
<Text size={14} weight={400} color={colors.white_60}>
{birth}
</Text>
</Row>
<Row gap={12} alignItems="center">
<Text size={14} weight={600} color={colors.white_40}>
성별
</Text>
<Text size={14} weight={400} color={colors.white_60}>
{gender}
</Text>
</Row>
</Col>
<Divider />
<Col gap={40} alignItems="flex-start" justifyContent="space-between">
<InputField label="변경할 닉네임을 입력해 주세요">
<TextInput
id={INPUT_TYPE.NICKNAME}
theme={theme1}
options={CONFIG.NICKNAME.options}
placeholder={'한글, 영문, 숫자 최대 8자'}
right={() => (
<Text size={14} weight={700} color={colors.purple_60}>
{nicknameProgress}
</Text>
)}
/>
</InputField>
<InputField label="변경할 직업을 입력해 주세요">
<SelectInput
id={INPUT_TYPE.JOB}
options={CONFIG.JOB.options}
placeholder="직업 선택하기"
selectOptions={JOBS}
/>
</InputField>
</Col>
</Col>
<SubmitButton>
<DefaultButton
title={'변경하기'}
onClick={handleSubmitForm}
disabled={!methods.formState.isValid}
/>
</SubmitButton>
</Container>
</FormProvider>
</Layout>
);
};

export default ModifyProfile;
2 changes: 1 addition & 1 deletion src/routes/MyPage/MyPage.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const BackButton = styled.button`
padding: 3.6px 7.8px;
`;

export const MyInfoUpdateButton = styled.button`
export const ModifyProfileButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
Expand Down
Loading
Loading