-
Notifications
You must be signed in to change notification settings - Fork 7
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
Guild Member Setting 페이지 #275
Changes from all commits
61d829f
fc9c338
3d8d475
27068ff
7f2a686
c67f5a6
205cdab
ee1e625
90ee5ba
76fddb8
0aa3543
cb9b5dd
5e2799e
9bb8712
40a80f6
9a3cfb9
315b3fe
7c7c3e1
b2fd77d
f47351f
caa8ca0
f98c850
c6fe182
724160f
9ad6531
104b2b0
f205dbf
4749ba6
7cecdf2
0cac774
91ca9e0
911b1b8
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 |
---|---|---|
@@ -1,26 +1,7 @@ | ||
import type { PropsWithChildren } from 'react'; | ||
import { css } from '_panda/css'; | ||
import { Flex } from '_panda/jsx'; | ||
import { dialogTitleStyle } from '@gitanimals/ui-panda'; | ||
|
||
import { GuildModalPageLayout } from '../../_components/GuildModalPageLayout'; | ||
|
||
export default function GuildCreatePage({ children }: PropsWithChildren) { | ||
return ( | ||
<div className={css({ minH: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' })}> | ||
<div | ||
className={css({ | ||
maxWidth: '880px', | ||
mx: 'auto', | ||
background: 'gray.gray_150', | ||
p: '40px', | ||
borderRadius: '16px', | ||
color: 'white.white', | ||
})} | ||
> | ||
<h2 className={dialogTitleStyle}>Guild Setting</h2> | ||
<Flex flexDirection="column" gap="24px"> | ||
{children} | ||
</Flex> | ||
</div> | ||
</div> | ||
); | ||
return <GuildModalPageLayout>{children}</GuildModalPageLayout>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* eslint-disable @next/next/no-img-element */ | ||
import type { ReactNode } from 'react'; | ||
import { css } from '_panda/css'; | ||
import { Flex } from '_panda/jsx'; | ||
|
||
export function BannerGuildMember({ | ||
image, | ||
name, | ||
count, | ||
bottomElement, | ||
}: { | ||
image: string; | ||
name: string; | ||
count: string; | ||
bottomElement: ReactNode; | ||
}) { | ||
return ( | ||
<div className={bannerStyle}> | ||
<img src={image} alt={name} width={80} height={80} /> | ||
<p className={nameStyle}>{name}</p> | ||
Comment on lines
+19
to
+20
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. 🛠️ Refactor suggestion 이미지 처리 및 접근성 개선 필요
- <img src={image} alt={name} width={80} height={80} />
+ <img
+ src={image}
+ alt={t('guild.member_avatar_alt', { name })}
+ width={80}
+ height={80}
+ loading="lazy"
+ onError={(e) => {
+ e.currentTarget.src = '/images/default-avatar.png';
+ }}
+ />
|
||
<p className={countStyle}>{count}</p> | ||
<Flex gap="2">{bottomElement}</Flex> | ||
</div> | ||
); | ||
} | ||
|
||
const bannerStyle = css({ | ||
display: 'inline-flex', | ||
padding: '8px 16px 16px 16px', | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
gap: '12px', | ||
backgroundColor: 'white.white_10', | ||
borderRadius: '8px', | ||
}); | ||
|
||
const nameStyle = css({ | ||
textStyle: 'glyph16.regular', | ||
color: 'white', | ||
}); | ||
|
||
const countStyle = css({ | ||
textStyle: 'glyph14.regular', | ||
color: 'white.white_50', | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||||||||||
'use client'; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
import type { GuildMember } from '@gitanimals/api'; | ||||||||||||||||||||||||||||||||||||
import { kickMemberFromGuild } from '@gitanimals/api'; | ||||||||||||||||||||||||||||||||||||
import { Button } from '@gitanimals/ui-panda'; | ||||||||||||||||||||||||||||||||||||
import { toast } from 'sonner'; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
import { useRouter } from '@/i18n/routing'; | ||||||||||||||||||||||||||||||||||||
import { getPersonaImage } from '@/utils/image'; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
import { BannerGuildMember } from './BannerGuildMember'; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
export function MemberCard({ member, guildId }: { member: GuildMember; guildId: string }) { | ||||||||||||||||||||||||||||||||||||
const router = useRouter(); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
const kickMember = async () => { | ||||||||||||||||||||||||||||||||||||
await kickMemberFromGuild({ guildId, userId: member.userId }); | ||||||||||||||||||||||||||||||||||||
toast.success('Kicked member'); | ||||||||||||||||||||||||||||||||||||
router.refresh(); | ||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||
Comment on lines
+16
to
+20
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. 멤버 추방 기능 보완 필요
const kickMember = async () => {
+ if (!window.confirm(t('guild.kick_member_confirm'))) return;
+
+ try {
await kickMemberFromGuild({ guildId, userId: member.userId });
toast.success('Kicked member');
router.refresh();
+ } catch (error) {
+ console.error('Failed to kick member:', error);
+ toast.error(t('guild.kick_member_error'));
+ }
}; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||
<BannerGuildMember | ||||||||||||||||||||||||||||||||||||
key={member.id} | ||||||||||||||||||||||||||||||||||||
image={getPersonaImage(member.personaType)} | ||||||||||||||||||||||||||||||||||||
name={member.name} | ||||||||||||||||||||||||||||||||||||
count={member.contributions} | ||||||||||||||||||||||||||||||||||||
bottomElement={<Button onClick={kickMember}>Remove</Button>} | ||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
'use client'; | ||
|
||
import { Flex } from '_panda/jsx'; | ||
import { acceptJoinGuild, denyJoinGuild, type GuildMember } from '@gitanimals/api'; | ||
import { Button } from '@gitanimals/ui-panda'; | ||
import { toast } from 'sonner'; | ||
|
||
import { useRouter } from '@/i18n/routing'; | ||
import { getPersonaImage } from '@/utils/image'; | ||
|
||
import { BannerGuildMember } from './BannerGuildMember'; | ||
|
||
export function WaitMemberCard({ member, guildId }: { member: GuildMember; guildId: string }) { | ||
const router = useRouter(); | ||
|
||
const acceptMember = async () => { | ||
await acceptJoinGuild({ guildId: guildId, userId: member.userId }); | ||
toast.success('Accepted member'); | ||
router.refresh(); | ||
}; | ||
Comment on lines
+16
to
+20
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. 에러 처리 및 로딩 상태 추가 필요 API 호출 시 에러 처리와 로딩 상태가 누락되어 있습니다. 다음과 같은 개선을 제안합니다: + const [isLoading, setIsLoading] = useState(false);
const acceptMember = async () => {
+ setIsLoading(true);
try {
await acceptJoinGuild({ guildId: guildId, userId: member.userId });
toast.success('Accepted member');
router.refresh();
+ } catch (error) {
+ toast.error('Failed to accept member');
+ } finally {
+ setIsLoading(false);
+ }
};
|
||
|
||
const denyMember = async () => { | ||
await denyJoinGuild({ guildId: guildId, userId: member.userId }); | ||
toast.success('Denied member'); | ||
router.refresh(); | ||
}; | ||
|
||
return ( | ||
<BannerGuildMember | ||
key={member.id} | ||
image={getPersonaImage(member.personaType)} | ||
name={member.name} | ||
count={member.contributions} | ||
bottomElement={ | ||
<Flex gap="2"> | ||
<Button variant="secondary" onClick={denyMember}> | ||
Deny | ||
</Button> | ||
<Button onClick={acceptMember}>Accept</Button> | ||
</Flex> | ||
Comment on lines
+36
to
+40
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. 🛠️ Refactor suggestion 버튼 상태 처리 개선 필요 버튼의 로딩 및 비활성화 상태 처리가 필요합니다. - <Button variant="secondary" onClick={denyMember}>
+ <Button variant="secondary" onClick={denyMember} disabled={isLoading}>
Deny
</Button>
- <Button onClick={acceptMember}>Accept</Button>
+ <Button onClick={acceptMember} disabled={isLoading}>
+ {isLoading ? 'Processing...' : 'Accept'}
+ </Button>
|
||
} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { css } from '_panda/css'; | ||
import { Flex } from '_panda/jsx'; | ||
import { getGuildById } from '@gitanimals/api'; | ||
|
||
import { GuildModalPageTitle } from '../../../_components/GuildModalPageLayout'; | ||
|
||
import { MemberCard } from './MemberCard'; | ||
import { WaitMemberCard } from './WaitMemberCard'; | ||
|
||
export default async function GuildMemberSetting({ params }: { params: { id: string } }) { | ||
const guild = await getGuildById({ guildId: params.id }); | ||
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. API 에러 처리 및 로딩 상태 관리 필요 getGuildById API 호출 시 에러 처리와 로딩 상태 관리가 필요합니다. + import { Suspense } from 'react';
+ import { ErrorBoundary } from '@gitanimals/ui-panda';
+ import { LoadingSpinner } from '@/components/LoadingSpinner';
export default async function GuildMemberSetting({ params }: { params: { id: string } }) {
+ return (
+ <ErrorBoundary fallback={<div>길드 정보를 불러오는 중 오류가 발생했습니다.</div>}>
+ <Suspense fallback={<LoadingSpinner />}>
+ <GuildMemberContent params={params} />
+ </Suspense>
+ </ErrorBoundary>
+ );
}
|
||
|
||
return ( | ||
<div className={css({ minH: 'calc(100vh - 300px)' })}> | ||
<GuildModalPageTitle className={css({ mb: '40px' })}>Manage members</GuildModalPageTitle> | ||
|
||
{guild?.waitMembers.length === 0 ? null : ( | ||
<> | ||
<h3 className={subHeadingStyle}>Request</h3> | ||
<Flex gap="1" flexWrap="wrap" mb="24px"> | ||
{guild.waitMembers.map((member) => ( | ||
<WaitMemberCard member={member} guildId={guild.id} key={member.id} /> | ||
))} | ||
</Flex> | ||
</> | ||
)} | ||
|
||
{guild?.members.length === 0 ? ( | ||
<p className={css({ textAlign: 'center', py: '40px', color: 'white.white_50' })}>No members</p> | ||
) : ( | ||
<> | ||
<h3 className={subHeadingStyle}>Members</h3> | ||
<Flex gap="1" flexWrap="wrap"> | ||
{guild.members.map((member) => ( | ||
<MemberCard member={member} key={member.id} guildId={guild.id} /> | ||
))} | ||
</Flex> | ||
</> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
const subHeadingStyle = css({ | ||
textStyle: 'glyph14.regular', | ||
color: 'white', | ||
mb: '8px', | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,6 +2,8 @@ import { checkIsLeader, getGuildBackgrounds, getGuildById, getGuildIcons } from | |||||
|
||||||
import { redirect } from '@/i18n/routing'; | ||||||
|
||||||
import { GuildModalPageTitle } from '../../_components/GuildModalPageLayout'; | ||||||
|
||||||
import { GuildSetting } from './GuildSetting'; | ||||||
|
||||||
export default async function GuildSettingPage({ params }: { params: { id: string } }) { | ||||||
|
@@ -15,5 +17,10 @@ export default async function GuildSettingPage({ params }: { params: { id: strin | |||||
redirect(`/guild/${params.id}`); | ||||||
} | ||||||
|
||||||
return <GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={data} />; | ||||||
return ( | ||||||
<> | ||||||
<GuildModalPageTitle>Guild Setting</GuildModalPageTitle> | ||||||
<GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={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. JSX 요소에서 불필요한 세미콜론을 제거해주세요. JSX 렌더링 코드에 불필요한 세미콜론이 있습니다. - <GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={data} />;
+ <GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={data} /> 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (1.9.4)[error] 22-23: There is a suspicious semicolon in the JSX element. This is usually the result of a typo or some refactor gone wrong. (lint/suspicious/noSuspiciousSemicolonInJsx) |
||||||
</> | ||||||
); | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { PropsWithChildren } from 'react'; | ||
import { css, cx } from '_panda/css'; | ||
import { Flex } from '_panda/jsx'; | ||
import { dialogTitleStyle } from '@gitanimals/ui-panda'; | ||
import { XIcon } from 'lucide-react'; | ||
|
||
import { BackTrigger } from '@/components/Trigger'; | ||
|
||
export function GuildModalPageLayout({ children }: PropsWithChildren) { | ||
return ( | ||
<div className={css({ minH: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' })}> | ||
<div | ||
className={css({ | ||
maxWidth: '880px', | ||
mx: 'auto', | ||
background: 'gray.gray_150', | ||
p: '40px', | ||
borderRadius: '16px', | ||
color: 'white.white', | ||
w: '100%', | ||
position: 'relative', | ||
})} | ||
> | ||
<BackTrigger className={css({ position: 'absolute', top: '16px', right: '16px' })}> | ||
<XIcon /> | ||
</BackTrigger> | ||
<Flex flexDirection="column" gap="24px"> | ||
{children} | ||
</Flex> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export const GuildModalPageTitle = ({ children, className }: PropsWithChildren<{ className?: string }>) => ( | ||
<h2 className={cx(dialogTitleStyle, className)}>{children}</h2> | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,16 @@ | ||
import { css } from '_panda/css'; | ||
import { Flex } from '_panda/jsx'; | ||
import { dialogTitleStyle } from '@gitanimals/ui-panda'; | ||
|
||
import GuildCreate from '../(components)/GuildCreate'; | ||
import GuildCreate from '../_components/GuildCreate'; | ||
import { GuildModalPageLayout } from '../_components/GuildModalPageLayout'; | ||
|
||
export default function GuildCreatePage() { | ||
return ( | ||
<div className={css({ minH: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' })}> | ||
<div | ||
className={css({ | ||
maxWidth: '880px', | ||
mx: 'auto', | ||
background: 'gray.gray_150', | ||
p: '40px', | ||
borderRadius: '16px', | ||
color: 'white.white', | ||
})} | ||
> | ||
<h2 className={dialogTitleStyle}>Create Guild</h2> | ||
<Flex flexDirection="column" gap="24px"> | ||
<GuildCreate /> | ||
</Flex> | ||
</div> | ||
</div> | ||
<GuildModalPageLayout> | ||
<h2 className={dialogTitleStyle}>Create Guild</h2> | ||
<Flex flexDirection="column" gap="24px"> | ||
<GuildCreate /> | ||
</Flex> | ||
</GuildModalPageLayout> | ||
); | ||
} |
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.
🛠️ Refactor suggestion
컴포넌트 이름과 경로가 일치하지 않습니다
현재 컴포넌트가 setting 폴더에 위치해 있지만 이름이
GuildCreatePage
로 되어있습니다. 경로와 일관성을 위해GuildSettingLayout
으로 변경하는 것이 좋겠습니다.