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

[FE][관리자 페이지] 내 정보 페이지 ui 추가 (#287) #294

Merged
merged 6 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions frontend/manage/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import MyProject from "./components/pages/MyProject";
import NewProject from "./components/pages/NewProject";
import ProjectDetail from "./components/pages/ProjectDetail";
import ScriptPublishingPage from "./components/pages/ScriptPublishing";
import UserProfile from "./components/pages/UserProfile";
import { ROUTE } from "./constants";
import { useUser } from "./hooks";

Expand All @@ -16,9 +17,11 @@ const App = () => {
return (
<Router>
<Nav user={user} logout={logout} />

<Switch>
<Route exact path={ROUTE.HOME} component={Home} />
<ConditionalRoute path={ROUTE.LOGIN} component={Login} condition={!user} redirectPath={ROUTE.MY_PROJECT} />
<ConditionalRoute path={ROUTE.USER_PROFILE} component={UserProfile} condition={!!user || isLoading} />
<ConditionalRoute
path={ROUTE.SCRIPT_PUBLISHING}
component={ScriptPublishingPage}
Expand Down
12 changes: 12 additions & 0 deletions frontend/manage/src/assets/svg/camera.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion frontend/manage/src/components/organisms/Nav/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const Nav = ({ user, logout }: Props) => {
<UserAvatarOptionWrapper>
{user ? (
<UserAvatarOption user={user}>
<Link to="#">내 정보</Link>
<Link to={ROUTE.USER_PROFILE}>내 정보</Link>
<Link to={ROUTE.HOME} onClick={logout}>
로그아웃
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled from "styled-components";
import { PALETTE } from "../../../styles/palette";
import { contentBoxCSS } from "../../../styles/css";

export const Container = styled.div`
display: flex;
Expand All @@ -8,10 +8,5 @@ export const Container = styled.div`

export const MainContent = styled.main`
width: 50rem;
display: flex;
flex-direction: column;
padding: 2rem 2rem;
background-color: ${PALETTE.WHITE};
border-radius: 20px;
box-shadow: rgba(0, 0, 0, 0.24) 0 3px 8px;
${contentBoxCSS}
`;
66 changes: 66 additions & 0 deletions frontend/manage/src/components/pages/UserProfile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ChangeEvent, FormEventHandler, useEffect, useState } from "react";
import cameraIcon from "../../../assets/svg/camera.svg";
import { useEditUser, useInput, useUser } from "../../../hooks";
import ScreenContainer from "../../../styles/ScreenContainer";
import SubmitButton from "../../atoms/Buttons/SubmitButton";
import { CameraIcon, Container, FileLabel, Form, InfoWrapper, Input, Label, Title, UserProfileImage } from "./styles";

const UserProfile = () => {
const { user } = useUser();
const { editUser } = useEditUser();
const { value: userName, setValue: setUserName, onChange: onChangeUserName } = useInput("");

const [profileImage, setProfileImage] = useState<string>();

const onChangeFile = (event: ChangeEvent<HTMLInputElement>) => {
const target = event.target;
const files = target?.files || [];

setProfileImage(URL.createObjectURL(files[0]));
};

const onSubmit: FormEventHandler<HTMLFormElement> = async event => {
event.preventDefault();

try {
await editUser({ nickName: userName });
} catch (error) {
alert(error.response.data.message);
console.error(error.response.data.message);
}
};

useEffect(() => {
if (user) {
setUserName(user.nickName);
setProfileImage(user.profileImageUrl);
}
}, [user]);

return (
<ScreenContainer>
<Container>
<Form onSubmit={onSubmit}>
<Title>프로필</Title>

<InfoWrapper>
<FileLabel>
<CameraIcon src={cameraIcon} />
<UserProfileImage imageURL={profileImage} size="LG" />
<Input type="file" accept="image/*" onChange={onChangeFile} />
</FileLabel>
</InfoWrapper>

<InfoWrapper>
<Label>별명</Label>
<Input value={userName} onChange={onChangeUserName} />
</InfoWrapper>

<SubmitButton>수정</SubmitButton>
</Form>
</Container>
</ScreenContainer>
);
};

export default UserProfile;
59 changes: 59 additions & 0 deletions frontend/manage/src/components/pages/UserProfile/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import styled from "styled-components";
import { contentBoxCSS, inputCSS, labelCSS, titleCSS } from "../../../styles/css";
import Avatar from "../../atoms/Avatar";

export const Container = styled.div`
width: 40rem;
${contentBoxCSS}
`;

export const Title = styled.h2`
${titleCSS}
margin-bottom: 5rem;
align-self: flex-start;
`;

export const Form = styled.form`
display: flex;
flex-direction: column;
align-items: flex-end;
`;

export const InfoWrapper = styled.div`
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 4rem;
`;

export const UserProfileImage = styled(Avatar)`
width: 100%;
height: 100%;
`;

export const Label = styled.label`
${labelCSS}
`;

export const CameraIcon = styled.img`
width: 30px;
height: 30px;
cursor: pointer;
position: absolute;
right: 0;
bottom: 0;
`;

export const FileLabel = styled(Label)`
position: relative;
transform: scale(1.8);
margin: 0 auto;
> input {
display: none;
}
`;

export const Input = styled.input`
${inputCSS}
`;
1 change: 1 addition & 0 deletions frontend/manage/src/constants/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const ROUTE = {
HOME: "/",
LOGIN: "/login",
USER_PROFILE: "/user",
MY_PROJECT: "/projects",
PROJECT_DETAIL: "/projects/:id",
SCRIPT_PUBLISHING: "/projects/:id/guide",
Expand Down
2 changes: 2 additions & 0 deletions frontend/manage/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export { useGetProject } from "./useGetProject";
export { useCreateProject } from "./useCreateProject";
export { useCopyButton } from "./useCopyButton";
export { useEditProject } from "./useEditProject";
export { useEditUser } from "./useEditUser";
export { useDeleteUser } from "./useDeleteUser";
35 changes: 35 additions & 0 deletions frontend/manage/src/hooks/useDeleteUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMutation, useQueryClient } from "react-query";
import { QUERY, REACT_QUERY_KEY } from "../constants";
import { User } from "../types/user";
import { request } from "../utils/request";

const _deleteUser = async () => {
try {
const response = await request.delete(QUERY.USER);

return response.data;
} catch (error) {
throw new Error(error.response.data.message);
}
};

export const useDeleteUser = () => {
const queryClient = useQueryClient();

const deleteMutation = useMutation<User, Error>(() => _deleteUser(), {
onSuccess: () => {
queryClient.setQueryData<User | undefined>(REACT_QUERY_KEY.USER, user => {
return undefined;
});
}
});

const isLoading = deleteMutation.isLoading;
const error = deleteMutation.error;

const deleteUser = async () => {
await deleteMutation.mutateAsync();
};

return { deleteUser, isLoading, error };
};
35 changes: 35 additions & 0 deletions frontend/manage/src/hooks/useEditUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMutation, useQueryClient } from "react-query";
import { QUERY, REACT_QUERY_KEY } from "../constants";
import { EditUserRequest, User } from "../types/user";
import { request } from "../utils/request";

const _editUser = async ({ nickName }: EditUserRequest) => {
try {
const response = await request.patch(QUERY.USER, { nickName });

return response.data;
} catch (error) {
throw new Error(error.response.data.message);
}
};

export const useEditUser = () => {
const queryClient = useQueryClient();

const editMutation = useMutation<User, Error, EditUserRequest>(({ nickName }) => _editUser({ nickName }), {
onSuccess: () => {
queryClient.invalidateQueries(REACT_QUERY_KEY.USER);
}
});

const isLoading = editMutation.isLoading;
const error = editMutation.error;

const editUser = async ({ nickName }: EditUserRequest) => {
const user = await editMutation.mutateAsync({ nickName });

return user;
};

return { editUser, isLoading, error };
};
9 changes: 9 additions & 0 deletions frontend/manage/src/styles/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,12 @@ export const inputCSS = css`
box-shadow: 0 0 0 2px ${PALETTE.BLACK_700};
}
`;

export const contentBoxCSS = css`
display: flex;
flex-direction: column;
padding: 2rem 2rem;
background-color: ${PALETTE.WHITE};
border-radius: 20px;
box-shadow: rgba(0, 0, 0, 0.24) 0 3px 8px;
`;
2 changes: 2 additions & 0 deletions frontend/manage/src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export interface User {
createdDate: string;
modifiedDate: string;
}

export type EditUserRequest = Pick<User, "nickName">;