Skip to content

Commit

Permalink
Merge pull request #273 from gloddy-dev/feature/264-create-manage-app…
Browse files Browse the repository at this point in the history
…ly-page

Feature : 모임 지원서 관리 페이지 구현
  • Loading branch information
kangju2000 authored Aug 28, 2023
2 parents 766aee8 + 7fb525e commit 2507eda
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 17 deletions.
10 changes: 10 additions & 0 deletions public/icons/16/application.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions public/icons/16/gloddy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions public/icons/24/application.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reject_character.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/apis/groups/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export interface Article {
commentCount: number;
isCertifiedStudent: boolean;
isCaptain: boolean;
isWriter: boolean;
isWriterCaptain: boolean;
isWriterCertifiedStudent: boolean;
writerReliabilityLevel: string;
images: string[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Header } from '@/components/Header';
import { Flex } from '@/components/Layout';
import { useNumberParams } from '@/hooks/useNumberParams';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/navigation';

export default function GroupingHeader() {
Expand All @@ -28,9 +29,11 @@ export default function GroupingHeader() {
<Header.Right>
<Flex align="center">
{isCaptain && (
<IconButton size="large" onClick={() => console.log('수정')}>
<Image src="/icons/24/application.svg" alt="application" width={24} height={24} />
</IconButton>
<Link href={`/grouping/${groupId}/manage`}>
<IconButton size="large">
<Image src="/icons/24/application.svg" alt="application" width={24} height={24} />
</IconButton>
</Link>
)}
<IconButton size="large" onClick={() => console.log('더보기')}>
<Image src="/icons/24/more.svg" alt="more" width={24} height={24} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use client';

import ManageModal from './ManageModal';
import { Avatar } from '@/components/Avatar';
import { Button, ButtonGroup, IconButton } from '@/components/Button';
import { Spacing } from '@/components/common/Spacing';
import { Flex } from '@/components/Layout';
import { TextField } from '@/components/TextField';
import { useModal } from '@/hooks/useModal';
import Image from 'next/image';

const DUMMY_DATA = {
userImageUrl: '/images/dummy_avatar.png',
isCertifiedStudent: true,
name: 'Glow',
reliabilityLevel: 'GLODDY',
introduce: '안녕하세요! 저는 글로우입니다. 잘 부탁드립니다.',
reason: '저는 이 모임에 가입하고 싶습니다.',
};

export default function ApplyCard() {
const { userImageUrl, isCertifiedStudent, name, reliabilityLevel, introduce, reason } =
DUMMY_DATA;

const { open, close } = useModal();

const handleAcceptClick = () => {};
const handleRejectClick = () => {};

return (
<div className="w-[90%] shrink-0 rounded-8 bg-white p-16 shadow-card-ui">
<Flex justify="between" align="center" className="my-4 gap-12">
<Avatar
imageUrl={userImageUrl}
size="small"
iconVariant={isCertifiedStudent ? 'education' : 'none'}
/>
<div className="grow">
<p className="text-paragraph-1">{name}</p>
<Flex align="center" className="gap-2">
<Image
src={`/icons/16/${reliabilityLevel.toLowerCase()}.svg`}
alt="medal"
width={16}
height={16}
/>
<p className="text-caption text-sign-tertiary">{reliabilityLevel}</p>
</Flex>
</div>
<IconButton>
<Image src="/icons/24/navigate-next.svg" alt="navigate-next" width={24} height={24} />
</IconButton>
</Flex>
<Spacing size={16} />
<p className="px-4 text-sign-secondary">나는 이런 사람이에요!</p>
<Spacing size={4} />
<TextField as="textarea" value={introduce} readOnly />
<Spacing size={16} />
<p className="px-4 text-sign-secondary">모임에 함께하고 싶은 이유</p>
<TextField as="textarea" value={reason} readOnly />
<Spacing size={16} />
<ButtonGroup position="contents" hasDivider={false}>
<Button
variant="solid-warning"
onClick={() =>
open(<ManageModal type="reject" onOkClick={handleRejectClick} onCancelClick={close} />)
}
>
거절
</Button>
<Button
onClick={() =>
open(<ManageModal type="accept" onOkClick={handleAcceptClick} onCancelClick={close} />)
}
>
승인
</Button>
</ButtonGroup>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import ApplyCard from './ApplyCard.client';
import { Spacing } from '@/components/common/Spacing';
import { Flex } from '@/components/Layout';
import Image from 'next/image';
import { useState } from 'react';

export default function ManageDetail() {
const [currentApplication, setCurrentApplication] = useState(1);

return (
<div>
<Spacing size={32} />
<Flex justify="between" align="end" className="px-20">
<p className="text-h4 text-sign-cto">
모임에 가입하고 싶은 멤버의
<br />
지원서를 확인해주세요
</p>
<Flex align="center">
<Image src="/icons/16/application.svg" alt="application" width={16} height={16} />
<p className="text-caption text-sign-sub">
{currentApplication}/{4}
</p>
</Flex>
</Flex>
{/* TODO: Swiper로 변경 예정 */}
<Flex className="gap-8 overflow-x-scroll p-20">
{Array.from({ length: 3 }).map((_, index) => (
<ApplyCard key={index} />
))}
</Flex>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';
import { IconButton } from '@/components/Button';
import { Header } from '@/components/Header';
import Image from 'next/image';
import { useRouter } from 'next/navigation';

export default function ManageHeader() {
const router = useRouter();

return (
<Header className="px-4">
<Header.Left>
<IconButton size="large" onClick={() => router.back()}>
<Image src="/icons/24/arrow_back.svg" alt="back" width={24} height={24} />
</IconButton>
<p>모임 지원서 관리</p>
</Header.Left>
</Header>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Spacing } from '@/components/common/Spacing';
import { Modal } from '@/components/Modal';
import Image from 'next/image';

const modalText = {
accept: {
description: '서로에 대한 존중으로\n건전한 모임 문화를 만들어요!',
content: '지원서를 승인하시겠어요?',
variant: 'success',
},
reject: {
description: '성급한 거절로\n적합한 지원자를 놓칠 수 있어요',
content: '지원서를 거절하시겠어요?',
variant: 'warning',
},
} as const;

interface ManageModalProps {
type: 'accept' | 'reject';
onOkClick: () => void;
onCancelClick: () => void;
}

export default function ManageModal({ type, onOkClick, onCancelClick }: ManageModalProps) {
const { description, content, variant } = modalText[type];

return (
<Modal variant={variant} onOkClick={onOkClick} onCancelClick={onCancelClick}>
<Spacing size={32} />
<Image src="/images/reject_character.png" alt="refuse_character" width={130} height={130} />
<Spacing size={22} />
<p className="text-paragraph-1 text-sign-tertiary">{description}</p>
<Spacing size={12} />
<p>{content}</p>
<Spacing size={16} />
</Modal>
);
}
14 changes: 14 additions & 0 deletions src/app/(main)/grouping/[groupId]/manage/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import ManageDetail from './components/ManageDetail.client';
import ManageHeader from './components/ManageHeader.client';
import { Suspense } from 'react';

export default function GroupingManagePage() {
return (
<>
<ManageHeader />
<Suspense fallback={null}>
<ManageDetail />
</Suspense>
</>
);
}
22 changes: 14 additions & 8 deletions src/components/Button/ButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ const renderElements = (
},
props[index].className
),
variant: cn(
{
variant:
props[index].variant ||
cn({
'solid-default': index === 0,
'solid-primary': index !== 0,
},
props[index].variant
),
}),
});
})}
</div>
Expand All @@ -45,11 +44,16 @@ interface ButtonGroupProps {
* 공백 여부를 설정합니다. (default: true)
*/
isSpacing?: boolean;
/**
* 버튼 사이에 구분선을 추가합니다. (default: true)
*/
hasDivider?: boolean;
}

export default function ButtonGroup({
position = 'bottom',
isSpacing = true,
hasDivider = true,
children,
}: StrictPropsWithChildren<ButtonGroupProps>) {
const validChildren = Children.toArray(children).filter((child) =>
Expand All @@ -73,10 +77,12 @@ export default function ButtonGroup({

return (
<>
{isSpacing && <Spacing size={buttonHeight + 28} />}
{position === 'bottom' && isSpacing && <Spacing size={buttonHeight + 28} />}
<div
className={cn('mx-auto border-t-1 border-divider bg-white p-20 pt-7', {
'fixed inset-x-0 bottom-0 z-50 max-w-450': position === 'bottom',
className={cn({
'fixed inset-x-0 bottom-0 z-50 mx-auto max-w-450 bg-white p-20 pt-7':
position === 'bottom',
'border-t-1 border-divider': hasDivider,
})}
>
{renderElements(validChildren, props)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function IconButton({
}: StrictPropsWithChildren<IconButtonProps>) {
return (
<div
className={cn('flex items-center justify-center', {
className={cn('flex cursor-pointer items-center justify-center', {
'h-24 w-24': size === 'small',
'h-40 w-40': size === 'medium',
'h-48 w-48': size === 'large',
Expand Down
1 change: 1 addition & 0 deletions src/style/theme/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const colors = {
// Status Colors
'warning-light': PALETTE.red['200'],
warning: PALETTE.red['500'],
'warning-dark': PALETTE.red['600'],
success: {
cto: PALETTE.blue['500'],
text: PALETTE.blue['600'],
Expand Down

0 comments on commit 2507eda

Please sign in to comment.