Skip to content

Commit

Permalink
프로필 수정기능 개발 (#61)
Browse files Browse the repository at this point in the history
* Minor : Icon 추가

* New : 유저정보 변경 뮤테이션 추가

* New : 파일 삭제 뮤테이션 추가

* Refactor : 쿼리 인벨리데이션과정 추가

* New : 정보 수정 서버 URL 추가

* Refactor : 앱바, 드로워 컴포넌트 분리

* New : 프로필 수정기능 적용
  • Loading branch information
jobkaeHenry authored Nov 29, 2023
1 parent 266bbae commit 769547e
Show file tree
Hide file tree
Showing 14 changed files with 407 additions and 66 deletions.
10 changes: 5 additions & 5 deletions client/src/app/(protectedRoute)/new-post/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Box, Container, Paper, Tooltip } from "@mui/material";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import HOME from "@/const/clientPath";
import CameraIcon from "@/assets/icons/CameraIcon.svg";
import PictureIcon from "@/assets/icons/PictureIcon.svg";
import PinIcon from "@/assets/icons/PinIcon.svg";
import { useGlobalLoadingStore } from "@/store/useGlobalLoadingStore";
import useNewPostMutation from "@/queries/newPost/useNewPostMutation";
Expand Down Expand Up @@ -90,9 +90,9 @@ export default function NewpostPage() {
{/* 최상단 앱바 */}
<CustomAppbar
title="포스팅"
buttonComponent="공유"
disableButton={isSuccess}
onClickButton={submitHandler}
appendButton="공유"
disableAppend={isSuccess}
onClickAppend={submitHandler}
/>

<Container sx={{ p: { xs: 0, sm: 4 } }} maxWidth={"lg"}>
Expand Down Expand Up @@ -125,7 +125,7 @@ export default function NewpostPage() {
<Tooltip title="사진 첨부">
<SquareIconButton
component={"label"}
iconComponent={<CameraIcon />}
iconComponent={<PictureIcon />}
>
<input
name="image"
Expand Down
6 changes: 4 additions & 2 deletions client/src/app/user/[userId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import CustomAppbar from "@/components/CustomAppbar";
import UserInfoEditingDrawer from "@/components/user/info/drawer/UserInfoEditingDrawer";
import { SETTING_PAGE } from "@/const/clientPath";
import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery";
import UserPageContext from "@/store/user/UserPageContext";
Expand All @@ -24,8 +25,8 @@ const UserInfoPageLayout = ({ children, params }: Props) => {
return (
<UserPageContext.Provider value={{ isEditing, setIsEditing }}>
<CustomAppbar
buttonComponent={isMyProfile ? "설정" : undefined}
onClickButton={() => {
appendButton={isMyProfile ? "설정" : undefined}
onClickAppend={() => {
if (!isMyProfile) {
return;
}
Expand All @@ -42,6 +43,7 @@ const UserInfoPageLayout = ({ children, params }: Props) => {
p: 2,
}}
>
<UserInfoEditingDrawer />
{children}
</Paper>
</Container>
Expand Down
File renamed without changes
3 changes: 3 additions & 0 deletions client/src/assets/icons/badge/CameraIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 42 additions & 17 deletions client/src/components/CustomAppbar.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,73 @@
"use client";
import { AppBar, Button, IconButton, Toolbar, Typography } from "@mui/material";
import {
AppBar,
Button,
IconButton,
Toolbar,
Typography,
styled,
} from "@mui/material";
import GoBackIcon from "@/assets/icons/GoBackIcon.svg";
import { MouseEventHandler, ReactNode, memo } from "react";
import { useRouter } from "next/navigation";

interface CustomAppbarInterface {
title?: string;
buttonComponent?: ReactNode;
disableButton?: boolean;
onClickButton?: MouseEventHandler<HTMLButtonElement>;
prependButton?: ReactNode;
onClickPrepend?: MouseEventHandler<HTMLButtonElement>;

appendButton?: ReactNode;
disableAppend?: boolean;
onClickAppend?: MouseEventHandler<HTMLButtonElement>;
}

const CustomAppbar = ({
title,
buttonComponent,
disableButton,
onClickButton,
appendButton,
prependButton,
onClickPrepend,
disableAppend,
onClickAppend,
}: CustomAppbarInterface) => {
const router = useRouter();

return (
<AppBar position={"static"}>
<Toolbar sx={{ display: "flex", justifyContent: "space-between" }}>
<IconButton onClick={() => router.back()}>
<GoBackIcon />
</IconButton>
{/* 프리팬드 버튼 */}
{prependButton ? (
<AppbarButton variant="text" onClick={onClickPrepend}>
{prependButton}
</AppbarButton>
) : (
<IconButton onClick={() => router.back()}>
<GoBackIcon />
</IconButton>
)}
{/* 타이틀 */}
<Typography component="h1" variant="subtitle2" fontWeight={"bold"}>
{title}
</Typography>
{buttonComponent ? (
<Button
disabled={disableButton}
onClick={onClickButton}
{/* 어팬드 버튼 */}
{appendButton ? (
<AppbarButton
disabled={disableAppend}
onClick={onClickAppend}
variant="text"
sx={{ minWidth: 40, fontWeight: "medium" }}
>
{buttonComponent}
</Button>
{appendButton}
</AppbarButton>
) : (
<div style={{ width: "40px" }} />
)}
</Toolbar>
</AppBar>
);
};
const AppbarButton = styled(Button)(() => ({
minWidth: 40,
fontWeight: "medium",
fontSize:'18px'
}));

export default memo(CustomAppbar);
49 changes: 49 additions & 0 deletions client/src/components/CustomSwipeableDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
Box,
SwipeableDrawer,
SwipeableDrawerProps,
styled,
} from "@mui/material";
import React from "react";

interface Props extends SwipeableDrawerProps {}

const CustomSwipeableDrawer = ({ open, onOpen, onClose, children }: Props) => {
const pullerBleed = 24;

return (
<SwipeableDrawer
open={open}
onOpen={onOpen}
onClose={onClose}
anchor="bottom"
disableSwipeToOpen
PaperProps={{
sx: {
p: 2,
borderTopLeftRadius: pullerBleed,
borderTopRightRadius: pullerBleed,
overFlow: "hidden",
},
}}
ModalProps={{
keepMounted: false,
}}
>
<Puller />
<Box sx={{ mt: `${pullerBleed + 8}px` }}>{children}</Box>
</SwipeableDrawer>
);
};

const Puller = styled(Box)(() => ({
width: 56,
height: 4,
backgroundColor: "#F6EAFB",
borderRadius: 3,
position: "absolute",
top: 8,
left: "calc(50% - 28px)",
}));

export default CustomSwipeableDrawer;
20 changes: 20 additions & 0 deletions client/src/components/user/info/drawer/UserInfoEditingDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import CustomSwipeableDrawer from "@/components/CustomSwipeableDrawer";
import UserPageContext from "@/store/user/UserPageContext";
import { useContext } from "react";
import UserInfoEditingForm from "./UserInfoEditingForm";

const UserInfoEditingDrawer = () => {
const { isEditing, setIsEditing } = useContext(UserPageContext);

return (
<CustomSwipeableDrawer
open={isEditing}
onClose={() => setIsEditing(false)}
onOpen={() => setIsEditing(true)}
>
<UserInfoEditingForm />
</CustomSwipeableDrawer>
);
};

export default UserInfoEditingDrawer;
154 changes: 154 additions & 0 deletions client/src/components/user/info/drawer/UserInfoEditingForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"use client";
import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery";
import {
Stack,
Avatar,
Typography,
TextField,
Button,
Badge,
} from "@mui/material";
import CameraIcon from "@/assets/icons/badge/CameraIcon.svg";
import { useCallback, useContext, useEffect, useState } from "react";
import useNewAttachMutation from "@/queries/attach/useNewAttachMutation";
import useDeleteAttachMutation from "@/queries/attach/useDeleteAttachMutation";
import UserPageContext from "@/store/user/UserPageContext";
import CustomAppbar from "@/components/CustomAppbar";
import { useGlobalLoadingStore } from "./../../../../store/useGlobalLoadingStore";
import usePatchUserInfoMutation from "@/queries/user/usePatchUserInfoMutation";

const UserInfoEditingForm = () => {
const { setIsEditing } = useContext(UserPageContext);

const { isLoading, setLoading } = useGlobalLoadingStore();

const { data } = useMyInfoQuery();

const [introduction, setIntroduction] = useState(data?.introduction);
const [file, setFile] = useState<File>();
const [fileUrl, setFileUrl] = useState<string | ArrayBuffer | null>(null);

const { mutateAsync: attachFile } = useNewAttachMutation();
const { mutateAsync: removeFile } = useDeleteAttachMutation();
const { mutateAsync: patchUserInfo } = usePatchUserInfoMutation();

const submitFileHandler = useCallback(
async (file: File) => {
if (!data) {
return;
}
await attachFile({
file: file,
url: { type: "PROFILE", pk: Number(data?.userNo) },
});
},
[data]
);

const submitHandler = async () => {
setLoading(true);
await patchUserInfo({ introduction });
if (file) {
data?.profileImages.forEach(async (profile) => {
await removeFile(String(profile.attachNo));
});
await submitFileHandler(file);
}
setIsEditing(false);
setLoading(false);
};

useEffect(() => {
if (!file) {
return;
}
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => setFileUrl(reader.result);
}, [file]);

return (
<>
<CustomAppbar
title={"프로필 수정"}
prependButton={"취소"}
onClickPrepend={() => setIsEditing(false)}
onClickAppend={submitHandler}
appendButton={"저장"}
disableAppend={isLoading}
/>
<Stack
component="form"
alignItems="center"
gap={6}
width="100%"
minHeight="70vh"
>
<label style={{ cursor: "pointer" }}>
<Badge
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
color="primary"
badgeContent={<CameraIcon />}
>
<Avatar
src={(fileUrl as string) ?? data?.profileImages?.[0]?.attachUrl}
sx={{ width: 80, height: 80, border: "1px solid #ccc" }}
/>
</Badge>
<input
type="file"
accept="image/*"
onChange={(e) => {
if (e.target.files) {
setFile(e.target.files[0]);
}
}}
style={{ display: "none" }}
/>
</label>
<Stack gap={2} width="100%">
<Stack direction="row" width="100%">
<Typography fontWeight="bold" width={120}>
{"닉네임"}
</Typography>
<TextField
disabled
value={data?.nickname ?? ""}
helperText={"닉네임은 수정할 수 없어요"}
size="small"
fullWidth
autoComplete="off"
/>
</Stack>
<Stack direction="row" width="100%">
<Typography fontWeight="bold" width={120}>
{"자기소개"}
</Typography>
<TextField
value={introduction ?? ""}
onChange={({ target }) => {
setIntroduction(target.value);
}}
helperText={
"나에 대해 소개해보세요, 좋아하는 술, 술의 맛, 음식 뭐든 좋아요"
}
size="small"
fullWidth
autoComplete="off"
multiline
rows={3}
/>
</Stack>
</Stack>
<Button disabled={isLoading} fullWidth onClick={submitHandler}>
프로필 저장하기
</Button>
</Stack>
</>
);
};

export default UserInfoEditingForm;
4 changes: 2 additions & 2 deletions client/src/components/wiki/WikiAppbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const WikiAppbar = () => {
return (
<CustomAppbar
title="술백과"
buttonComponent={<SearchIcon />}
onClickButton={() => setIsSearching(true)}
appendButton={<SearchIcon />}
onClickAppend={() => setIsSearching(true)}
/>
);
};
Expand Down
Loading

0 comments on commit 769547e

Please sign in to comment.