-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/29 admin user management #30
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| import styled from 'styled-components'; | ||
| import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; | ||
|
|
||
| interface UserCardProps { | ||
| nickname: string; | ||
| email: string; | ||
| createdAt: string; | ||
| icon: string | null; | ||
| isOpen: boolean; | ||
| } | ||
|
|
||
| // Styled Components | ||
| const CardContainer = styled.div<{ isOpen: boolean }>` | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| padding: 16px; | ||
| background-color: white; | ||
| border: 1px solid #ddd; | ||
| border-radius: ${(props) => (props.isOpen ? '8px 8px 0 0' : '8px')}; | ||
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | ||
| cursor: pointer; | ||
| transition: all 0.3s ease; | ||
|
|
||
| &:hover { | ||
| background-color: #f7f7f7; | ||
| } | ||
| `; | ||
|
|
||
| const UserInfo = styled.div` | ||
| display: flex; | ||
| align-items: center; | ||
| `; | ||
|
|
||
| const Avatar = styled.img` | ||
| width: 50px; | ||
| height: 50px; | ||
| border-radius: 50%; | ||
| margin-right: 12px; | ||
| `; | ||
|
|
||
| const TextContainer = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| `; | ||
|
|
||
| const Name = styled.p` | ||
| font-weight: bold; | ||
| `; | ||
|
|
||
| const IconWrapper = styled.div` | ||
| width: 24px; | ||
| height: 24px; | ||
|
|
||
| svg { | ||
| width: 100%; | ||
| height: 100%; | ||
| } | ||
| `; | ||
|
|
||
| const UserCard = ({ nickname, icon, isOpen }: UserCardProps) => { | ||
| return ( | ||
| <CardContainer isOpen={isOpen}> | ||
| <UserInfo> | ||
| <Avatar | ||
| src={icon || 'https://via.placeholder.com/50'} | ||
| alt='User Icon' | ||
| /> | ||
| <TextContainer> | ||
| <Name>{nickname}</Name> | ||
| </TextContainer> | ||
| </UserInfo> | ||
|
|
||
| <IconWrapper> | ||
| {isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />} | ||
| </IconWrapper> | ||
| </CardContainer> | ||
| ); | ||
| }; | ||
|
|
||
| export default UserCard; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import { useEffect, useState } from 'react'; | ||
| import { AdminData } from '@/types/admindata'; | ||
| import UserCard from './UserCard'; | ||
| import styled from 'styled-components'; | ||
| import { fetchAdminData } from '@/apis/admin.api'; | ||
|
|
||
| // Styled Components | ||
| const UserListContainer = styled.div` | ||
| width: 600px; | ||
| margin: 0; | ||
| padding-left: 10px; | ||
| max-height: 500px; | ||
| overflow-y: auto; | ||
| border: 1px solid #ddd; | ||
| border-radius: 8px; | ||
| `; | ||
|
|
||
| const ListItem = styled.div` | ||
| margin-bottom: 12px; /* 리스트 아이템 간 간격 추가 */ | ||
| margin-top: 12px; | ||
| margin-right: 12px; | ||
| `; | ||
|
|
||
| const DropdownContent = styled.div` | ||
| background-color: #f9f9f9; | ||
| padding: 16px; | ||
| border: 1px solid #ddd; | ||
| border-top: none; | ||
| border-radius: 0 0 8px 8px; | ||
| box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.1); | ||
| `; | ||
|
|
||
| const DeleteButton = styled.button` | ||
| background-color: #ff4d4f; | ||
| color: white; | ||
| padding: 8px 12px; | ||
| border: none; | ||
| border-radius: 4px; | ||
| cursor: pointer; | ||
| font-weight: bold; | ||
| margin-top: 10px; | ||
| transition: background-color 0.3s; | ||
|
|
||
| &:hover { | ||
| background-color: #e33e3e; | ||
| } | ||
| `; | ||
|
|
||
| const UserList = () => { | ||
| const [users, setUsers] = useState<AdminData[]>([]); | ||
| const [openUserId, setOpenUserId] = useState<number | null>(null); | ||
|
|
||
| const toggleDropdown = (id: number) => { | ||
| setOpenUserId((prev) => (prev === id ? null : id)); | ||
| }; | ||
|
|
||
| const handleDeleteUser = (id: number) => { | ||
| alert(`회원탈퇴: 유저 ID ${id}`); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| const loadUsers = async () => { | ||
| try { | ||
| const data = await fetchAdminData(); | ||
| setUsers(data); | ||
| } catch (error) { | ||
| console.error('Failed to load users:', error); | ||
| } | ||
| }; | ||
|
|
||
| loadUsers(); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <UserListContainer> | ||
| {users.map((user) => ( | ||
| <ListItem key={user.id}> | ||
| <div onClick={() => toggleDropdown(user.id)}> | ||
| <UserCard | ||
| nickname={user.nickname} | ||
| email={user.email} | ||
| createdAt={user.created_at} | ||
| icon={user.icon} | ||
| isOpen={openUserId === user.id} | ||
| /> | ||
| </div> | ||
|
|
||
| {openUserId === user.id && ( | ||
| <DropdownContent> | ||
| <p>유저 ID: {user.id}</p> | ||
| <p>닉네임: {user.nickname}</p> | ||
| <p>이메일: {user.email}</p> | ||
| <p>가입 일자: {user.created_at}</p> | ||
| <DeleteButton onClick={() => handleDeleteUser(user.id)}> | ||
| 회원탈퇴 | ||
| </DeleteButton> | ||
| </DropdownContent> | ||
| )} | ||
| </ListItem> | ||
| ))} | ||
| </UserListContainer> | ||
| ); | ||
| }; | ||
|
|
||
| export default UserList; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { AdminData } from '@/types/admindata'; | ||
| import { httpClient } from './http.api'; | ||
|
|
||
| export const fetchAdminData = async () => { | ||
| try { | ||
| const response = await httpClient.get<AdminData[]>('/api/admin'); | ||
| console.log('응답 데이터:', response.data); | ||
|
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. 사실 console.log는 일반적으로는 지양되어야하는 코드입니다. |
||
| return response.data; | ||
| } catch (error) { | ||
| console.error('Error fetching admin data:', error); | ||
| throw error; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export interface AdminData { | ||
|
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. AdminData라는 이름은 조금 헷갈릴지도 모르겠어요. |
||
| id: number; | ||
| nickname: string; | ||
| email: string; | ||
| created_at: string; | ||
| icon: string | null; | ||
| } | ||
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.
선언된 인터페이스 AdminData과 내용이 거의 유사한데,
이를 활용할 수는 없을까요?