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

Guild Member Setting 페이지 #275

Merged
merged 32 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
61d829f
refactor: guild dialog를 Intercepting Routes을 이용해 수정
sumi-0011 Jan 5, 2025
fc9c338
refactor: components로 파일 정리
sumi-0011 Jan 5, 2025
3d8d475
refactor: guild detail page refactor
sumi-0011 Jan 5, 2025
27068ff
feat: guild create init
sumi-0011 Jan 5, 2025
7f2a686
feat: get guild icons API
sumi-0011 Jan 5, 2025
c67f5a6
feat: get background API
sumi-0011 Jan 5, 2025
205cdab
feat: 내가 가입한 길드 페이지 추가
sumi-0011 Jan 7, 2025
ee1e625
feat: guild create 후 페이지 이동
sumi-0011 Jan 7, 2025
90ee5ba
feat: guild type img 추가
sumi-0011 Jan 7, 2025
76fddb8
feat: dropdown menu 커스텀
sumi-0011 Jan 7, 2025
0aa3543
feat: guild 더보기 버튼 추가
sumi-0011 Jan 7, 2025
cb9b5dd
Merge remote-tracking branch 'origin/feat/guild-create' into feat/gui…
sumi-0011 Jan 8, 2025
5e2799e
refactor: state -> form 방식으로 변경 진행중
sumi-0011 Jan 8, 2025
9bb8712
feat: 선택 가능한 폼 데이터 추가
sumi-0011 Jan 8, 2025
40a80f6
feat: guild form 제작
sumi-0011 Jan 8, 2025
9a3cfb9
feat: update guild 추가
sumi-0011 Jan 8, 2025
315b3fe
feat: 길드 설정 메뉴 접근 제어
sumi-0011 Jan 9, 2025
7c7c3e1
feat: setting 접근 제어
sumi-0011 Jan 9, 2025
b2fd77d
feat: Intercepting용 Dialog 추가
sumi-0011 Jan 9, 2025
f47351f
feat: update setting dialog 추가
sumi-0011 Jan 9, 2025
caa8ca0
feat: component 구현중
sumi-0011 Jan 9, 2025
f98c850
fix: member setting ssr error fix
sumi-0011 Jan 10, 2025
c6fe182
feat: kick member form 추가
sumi-0011 Jan 10, 2025
724160f
feat: 길드 가입 수락
sumi-0011 Jan 10, 2025
9ad6531
feat: 길드 가입, 거절 API 연결
sumi-0011 Jan 10, 2025
104b2b0
refactor: member card 파일 이동
sumi-0011 Jan 10, 2025
f205dbf
feat: 가입 수락, 제거
sumi-0011 Jan 10, 2025
4749ba6
feat: member page min height
sumi-0011 Jan 10, 2025
7cecdf2
refactor: layout 하나로 정의
sumi-0011 Jan 10, 2025
0cac774
feat: back trigger 추가
sumi-0011 Jan 10, 2025
91ca9e0
Merge branch 'guild' into feat/guild-member
sumi-0011 Jan 11, 2025
911b1b8
fix: max length 추가
sumi-0011 Jan 11, 2025
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
4 changes: 1 addition & 3 deletions apps/web/src/app/[locale]/error.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import { useEffect } from 'react';
import { signOut } from 'next-auth/react';

import { sendMessageToErrorChannel } from '@/apis/slack/sendMessage';
import { ErrorPage } from '@/components/Error/ErrorPage';
Expand Down Expand Up @@ -35,9 +34,8 @@ Error Stack: ${error.stack}
const router = useRouter();

const onClickRetry = () => {
signOut();
reset();
router.push('/');
router.refresh();
};

return (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/[locale]/guild/@modal/(.)create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GuildCreate from '../../(components)/GuildCreate';
import GuildCreate from '../../_components/GuildCreate';
import GuildModal from '../GuildModal';

export default function GuildCreateModal() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from '@/i18n/routing';
import { joinGuildAction } from '@/serverActions/guild';

import { GuildJoinPetSelectDialog } from '../../../../(components)/GuildPetSelectDialog';
import { GuildJoinPetSelectDialog } from '../../../../_components/GuildPetSelectDialog';
import GuildModal from '../../../GuildModal';

export default function GuildJoinModal({ params }: { params: { id: string } }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button } from '@gitanimals/ui-panda';

import { Link } from '@/i18n/routing';

import { GuildDetail } from '../../../(components)/GuildDetail';
import { GuildDetail } from '../../../_components/GuildDetail';
import GuildModal from '../../GuildModal';

export default function GuildDetailModal({ params }: { params: { id: string } }) {
Expand Down
10 changes: 6 additions & 4 deletions apps/web/src/app/[locale]/guild/[id]/MoreMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ export async function MoreMenu({ guildId }: { guildId: string }) {
<CatIcon color="#FFFFFF80" size={18} />
Edit profile pet
</DropdownMenuItem>
<DropdownMenuItem>
<UsersRoundIcon color="#FFFFFF80" size={18} />
Manage members
</DropdownMenuItem>
<Link href={`/guild/${guildId}/setting/member`}>
<DropdownMenuItem>
<UsersRoundIcon color="#FFFFFF80" size={18} />
Manage members
</DropdownMenuItem>
</Link>
<DropdownMenuItem>
<LinkIcon color="#FFFFFF80" size={18} />
Send invite message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { Guild } from '@gitanimals/api';
import { useRouter } from '@/i18n/routing';
import { updateGuildAction } from '@/serverActions/guild';

import { GuildInfoFormClient, GuildInfoFormSubmitButton } from '../../(components)/GuidlInfoFormClient';
import { GuildInfoFormClient, GuildInfoFormSubmitButton } from '../../_components/GuidlInfoFormClient';

export interface FormState {
message: string;
Expand Down
25 changes: 3 additions & 22 deletions apps/web/src/app/[locale]/guild/[id]/setting/layout.tsx
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) {
Copy link

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으로 변경하는 것이 좋겠습니다.

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

이미지 처리 및 접근성 개선 필요

  1. 이미지 로딩 상태 처리가 누락되어 있습니다.
  2. 이미지에 대한 의미있는 alt 텍스트가 필요합니다.
-      <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';
+        }}
+      />

Committable suggestion skipped: line range outside the PR's diff.

<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',
});
31 changes: 31 additions & 0 deletions apps/web/src/app/[locale]/guild/[id]/setting/member/MemberCard.tsx
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

멤버 추방 기능 보완 필요

  1. 멤버 추방 전 확인 다이얼로그가 필요합니다.
  2. 에러 처리가 누락되어 있습니다.
   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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const kickMember = async () => {
await kickMemberFromGuild({ guildId, userId: member.userId });
toast.success('Kicked member');
router.refresh();
};
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'));
}
};


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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

에러 처리 및 로딩 상태 추가 필요

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);
+   }
  };

Committable suggestion skipped: line range outside the PR's diff.


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
Copy link

Choose a reason for hiding this comment

The 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>

Committable suggestion skipped: line range outside the PR's diff.

}
/>
);
}
48 changes: 48 additions & 0 deletions apps/web/src/app/[locale]/guild/[id]/setting/member/page.tsx
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 });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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>
+   );
  }

Committable suggestion skipped: line range outside the PR's diff.


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',
});
9 changes: 8 additions & 1 deletion apps/web/src/app/[locale]/guild/[id]/setting/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }) {
Expand All @@ -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} />;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

JSX 요소에서 불필요한 세미콜론을 제거해주세요.

JSX 렌더링 코드에 불필요한 세미콜론이 있습니다.

-      <GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={data} />;
+      <GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={data} />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={data} />;
<GuildSetting icons={icons} backgrounds={backgrounds} guildId={params.id} initialData={data} />
🧰 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.
Remove the semicolon, or move it inside a JSX element.

(lint/suspicious/noSuspiciousSemicolonInJsx)

</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ export function GuildInfoFormClient(props: GuildInfoFormProps) {
className={css({ mb: '6px' })}
defaultValue={props.initialData?.title}
/>
<TextArea placeholder="Type guild description" name="description" defaultValue={props.initialData?.body} />
<TextArea
placeholder="Type guild description"
maxLength={50}
name="description"
defaultValue={props.initialData?.body}
/>
</div>
<div>
<p className={headingStyle}>Guild thumbnail</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ export function GuildCreateForm({ formData, onDataChange }: GuildCreateFormProps
className={css({ mb: '6px' })}
onChange={(e) => onDataChange('title', e.target.value)}
/>
<TextArea placeholder="Type guild description" onChange={(e) => onDataChange('body', e.target.value)} />
<TextArea
placeholder="Type guild description"
maxLength={50}
onChange={(e) => onDataChange('body', e.target.value)}
/>
</div>
<div>
<p className={headingStyle}>Guild thumbnail</p>
Expand Down
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>
);
27 changes: 8 additions & 19 deletions apps/web/src/app/[locale]/guild/create/page.tsx
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>
);
}
Loading
Loading