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

[FEATURE] Banner Admin Base Branch #131

Draft
wants to merge 16 commits into
base: dev
Choose a base branch
from
Draft
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
29,957 changes: 29,957 additions & 0 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file added .yarn/install-state.gz
Binary file not shown.
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,18 @@
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"jsdom": "^24.0.0",
"msw": "^2.6.4",
"openapi-typescript": "^7.4.3",
"prettier": "^3.2.5",
"storybook": "8.0.4",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1",
"webpack": "^5.95.0"
},
"packageManager": "[email protected]"
"packageManager": "[email protected]",
"msw": {
"workerDirectory": [
"public"
]
}
}
44 changes: 44 additions & 0 deletions src/components/bannerAdmin/BannerList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Button, Tab } from '@sopt-makers/ui';
import { useEffect, useState } from 'react';

import { getBannerList } from '@/services/api/banner';
import { useGetBannerList } from '@/services/api/banner/query';

import CreateBannerModal from './components/BannerModal/BannerModal';
import ListItem from './components/ListItem/ListItem';
import { bannerListCss, bannerListHeaderCss } from './style';

const BannerList = () => {
const [selectedTab, setSelectedTab] = useState<TabOption>('all');
const [isOpen, setIsOpen] = useState(false);

const { data: bannerList } = useGetBannerList(
selectedTab as BannerList['status'],
'status',
);

console.log(bannerList);

return (
<>
<Tab
style="primary"
size="lg"
tabItems={['전체', '진행 예정', '진행 중', '진행 종료']}
onChange={(value) => setSelectedTab(value as TabOption)}
/>
<div css={bannerListHeaderCss}>ddd</div>
<Button size="sm" onClick={() => setIsOpen(true)}>
모달 열기
</Button>
<ul css={bannerListCss}>
{bannerList?.banners.map((banner) => (
<ListItem key={banner.id} {...banner} />
))}
</ul>
<CreateBannerModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
</>
);
};

export default BannerList;
54 changes: 54 additions & 0 deletions src/components/bannerAdmin/components/BannerModal/BannerModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { FieldBox, Radio, TextField } from '@sopt-makers/ui';

import Modal from '@/components/common/modal';
import ModalHeader from '@/components/common/modal/ModalHeader';

import { modalContentCss } from './style';

interface CreateBannerModalProps {
isOpen: boolean;
onClose: () => void;
}

const CreateBannerModal = (props: CreateBannerModalProps) => {
const { isOpen, onClose } = props;

if (!isOpen) return null;

return (
<Modal>
<ModalHeader title="신규 배너 등록" onClose={onClose} />
<section css={modalContentCss}>
<TextField labelText="배너 제목" required />
<FieldBox
topAddon={
<FieldBox.TopAddon
leftAddon={<FieldBox.Label label="콘텐츠 유형" required />}
/>
}>
<div className="radio-group">
<Radio label="프로덕트 홍보" size="lg" />
<Radio label="기타 홍보" size="lg" />
<Radio label="생일 광고" size="lg" />
</div>
</FieldBox>
<FieldBox
topAddon={
<FieldBox.TopAddon
leftAddon={<FieldBox.Label label="노출 위치" required />}
/>
}>
<div className="radio-group">
<Radio label="프로덕트 홍보" size="lg" />
<Radio label="기타 홍보" size="lg" />
<Radio label="생일 광고" size="lg" />
<Radio label="생일 광고" size="lg" />
</div>
</FieldBox>
<TextField labelText="노출 일정" required />
</section>
</Modal>
);
};

export default CreateBannerModal;
15 changes: 15 additions & 0 deletions src/components/bannerAdmin/components/BannerModal/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { css } from '@emotion/react';

export const modalContentCss = css({
display: 'flex',
flexDirection: 'column',
gap: '3.2rem',

padding: '2.6rem 3rem',

'& .radio-group': {
display: 'flex',
flexDirection: 'row',
gap: '1.6rem',
},
});
70 changes: 70 additions & 0 deletions src/components/bannerAdmin/components/ListItem/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { IconEdit, IconTrash } from '@sopt-makers/icons';
import { Tag } from '@sopt-makers/ui';
import { CSSProperties } from 'react';

import {
listItemCss,
progressColorVariant,
progressCss,
tagColorVariant,
tagCss,
} from './style';

export interface ListItemProp extends BannerList {}

const ProgressTextMap: Record<BannerList['status'], string> = {
reserved: '진행 예정',
in_progress: '진행 중',
done: '진행 종료',
};

const TagTextMap: Record<BannerList['content_type'], string> = {
product: '상품',
birthday: '생일',
sponsor: '스폰서',
event: '이벤트',
etc: '기타',
};

const ListItem = (props: ListItemProp) => {
const {
status,
location,
content_type,
title,
publisher,
start_date,
end_date,
} = props;

const progressStyle = { ...progressColorVariant[status] } as CSSProperties;
const tagStyle = { ...tagColorVariant[content_type] } as CSSProperties;

return (
<li css={listItemCss}>
<div
aria-describedby="진행 상태(임시)"
style={progressStyle}
css={progressCss}>
{ProgressTextMap[status]}
</div>
<div>
<Tag size="md" shape="pill" css={tagCss}>
{TagTextMap[content_type]}
</Tag>
<Tag size="md" shape="pill">
{TagTextMap[content_type]}
</Tag>
<span>{TagTextMap[content_type]}</span>
<span>{start_date}</span>
<span>{end_date}</span>
</div>
<div>
<IconEdit />
<IconTrash />
</div>
</li>
);
};

export default ListItem;
88 changes: 88 additions & 0 deletions src/components/bannerAdmin/components/ListItem/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { css } from '@emotion/react';
import { colors } from '@sopt-makers/colors';
import { fontsObject } from '@sopt-makers/fonts';

import { Progress, TagType } from '../../types';

export const listItemCss = css({
display: 'grid',
gridTemplateColumns: '1.3fr 10fr 0.5fr',
alignItems: 'center',

width: '100%',
height: '9rem',

padding: '2rem 3rem 2rem 1.5rem',

border: `0.1rem solid ${colors.gray800}`,
borderRadius: '1rem',

color: colors.gray10,

'&:hover': {
backgroundColor: colors.gray900,
},
'& > div:first-of-type': {
...fontsObject.TITLE_6_16_SB,
},

'& > div:nth-of-type(2)': {
...fontsObject.BODY_3_14_M,
display: 'grid',
gridTemplateColumns: 'repeat(5, 1fr)',
alignItems: 'center',
gap: '0.8rem',
flexGrow: 1,

padding: '0 2rem 0 7.1rem',

'& > *': {
width: 'fit-content',
},
},

'& > div:last-of-type': {
display: 'flex',
alignItems: 'center',
gap: '1.2rem',
justifyContent: 'flex-end',

'& > svg': {
width: '2.4rem',

cursor: 'pointer',
},
},
});

export const progressColor = '--operation-progress-color';
export const tagBackgroundColor = '--operation-tag-background-color';

interface ProgressColorVariant {
[progressColor]: string;
}

interface TagColorVariant {
[tagBackgroundColor]: string;
}

export const progressColorVariant: Record<Progress, ProgressColorVariant> = {
reserved: { [progressColor]: colors.secondary },
finished: { [progressColor]: colors.error },
'in-progress': { [progressColor]: colors.success },
};

export const tagColorVariant: Record<TagType, TagColorVariant> = {
'pg-community': { [tagBackgroundColor]: '#58CF0580' },
'cr-main': { [tagBackgroundColor]: '#00AEFF80' },
'cr-feed': { [tagBackgroundColor]: '#FA73E380' },
org: { [tagBackgroundColor]: colors.orangeAlpha500 },
};

export const progressCss = css({
color: `var(${progressColor})`,
});

export const tagCss = css({
backgroundColor: `var(${tagBackgroundColor})`,
});
1 change: 1 addition & 0 deletions src/components/bannerAdmin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './BannerList';
19 changes: 19 additions & 0 deletions src/components/bannerAdmin/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { css } from '@emotion/react';
import { colors } from '@sopt-makers/colors';

export const bannerListHeaderCss = css({
display: 'flex',

width: '100%',
padding: '4.9rem 0 2.4rem 0',

color: colors.white,
});

export const bannerListCss = css({
display: 'flex',
flexDirection: 'column',
gap: '1rem',

paddingTop: '4.9rem',
});
22 changes: 22 additions & 0 deletions src/components/common/PageWrapper/PageWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { HTMLAttributes, ReactNode } from 'react';

import { pageWrapperCss } from './style';

export interface PageWrapperProps
extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
title: ReactNode;
subTitle?: ReactNode; // TODO: 서브타이틀이 들어가는 스펙이 생길 경우 스타일 정의하기
}

const PageWrapper = (props: PageWrapperProps) => {
const { title, children, ...restProps } = props;

return (
<main css={pageWrapperCss} {...restProps}>
<h1>{title}</h1>
{children}
</main>
);
};

export default PageWrapper;
12 changes: 12 additions & 0 deletions src/components/common/PageWrapper/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { css } from '@emotion/react';
import { colors } from '@sopt-makers/colors';
import { fontsObject } from '@sopt-makers/fonts';

export const pageWrapperCss = css({
'& > h1': {
color: colors.gray10,
...fontsObject.TITLE_1_32_SB,

paddingBottom: '4.9rem',
},
});
2 changes: 1 addition & 1 deletion src/components/common/modal/ModalHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StModalHeader } from './style';

interface Props {
title: string;
desc: string;
desc?: string;
onClose: () => void;
}

Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './bannerAdmin';
10 changes: 10 additions & 0 deletions src/mocks/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { http, HttpResponse } from 'msw';

export const handler = [
http.get('http://localhost:3000/test', () => {
console.log('msw set successfully !');
return HttpResponse.json({
name: 'test',
});
}),
];
12 changes: 12 additions & 0 deletions src/pages/bannerAdmin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import BannerList from '@/components/bannerAdmin/BannerList';
import PageWrapper from '@/components/common/PageWrapper/PageWrapper';

const BannerAdminPage = () => {
return (
<PageWrapper title={'광고 배너 관리'}>
<BannerList />
</PageWrapper>
);
};

export default BannerAdminPage;
10 changes: 10 additions & 0 deletions src/services/api/banner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { client } from '../client';

export const getBannerList = async (
status: BannerList['status'],
sort: BannerListSort,
) => {
const res = await client.get(`/banners?status=${status}&sort=${sort}`);

return res.data.data;
};
Loading