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

merge develop #88

Merged
merged 2 commits into from
Sep 13, 2024
Merged
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
Binary file added public/easteregg/모두박수.png
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/fonts/nanum.ttf
Binary file not shown.
35 changes: 35 additions & 0 deletions src/container/SearchDialog/EasterEgg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { motion } from 'framer-motion';

export function EasterEgg() {
return (
<div
className="relative w-full h-[500px] font-[NanumDaheng] text-[80px] flex justify-center items-center"
style={{ overflow: 'hidden' }}>
<div className="relative flex gap-[20px] items-center">
디프만 15기 수고 많으셨습니다!
<motion.img
initial={{ scale: 3, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 5.5 }}
src="/easteregg/모두박수.png"
style={{ height: '120px' }}
/>
<motion.div
className="absolute"
initial={{ x: '-10%' }}
animate={{ x: '120%' }}
transition={{ delay: 0.5, duration: 8 }}
style={{ top: 0, left: 0, height: '100%', width: '110%', display: 'flex' }}>
<div
style={{
background: 'linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1))',
width: '30px',
height: '100%',
}}
/>
<div style={{ flex: 1, backgroundColor: 'white' }} />
</motion.div>
</div>
</div>
);
}
137 changes: 137 additions & 0 deletions src/container/SearchDialog/SearchBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { TouchButton } from '@/components/TouchButton';
import { Icon } from '@/system/components';
import { color } from '@/system/token/color';
import { Spacing } from '@/system/utils/Spacing';
import { TagType } from '@/container/SearchDialog/types';
import clsx from 'clsx';
import { Tag } from './Tag';
import { useState } from 'react';
import { If } from '@/system/utils/If';
import { useGetTags } from './apis/useGetTags';
import { useGetSearchCardTagHistory } from './apis/useGetSearchCardTagHistory';

interface Props {
hideTagHistory: boolean;
onSearchSubmit: (tagIdList: number[]) => void;
onShowEaster: () => void;
}

export function SearchBox({ hideTagHistory, onSearchSubmit, onShowEaster }: Props) {
const [dropdownOpened, setDropdownOpened] = useState(false);
const [tagList, setTagList] = useState<TagType[]>([]);
const activatedButtonClassName = tagList.length === 0 ? 'text-neutral-30 bg-neutral-5' : 'text-white bg-neutral-95';

const tags = useGetTags().data;
const tagHistory = useGetSearchCardTagHistory().data;

const 역량태그리스트 = tags.filter((tag) => tag.type === '역량');
const 인성태그리스트 = tags.filter((tag) => tag.type === '인성');

const onTagClick = (text: TagType) => {
setTagList([...tagList, text]);
};

const onRefreshClick = () => setTagList([]);

return (
<div>
<div
onClick={() => setDropdownOpened(!dropdownOpened)}
className="relative w-full px-[16px] py-[12px] border-[1px] border-neutral-40 rounded-[8px] flex justify-between items-center cursor-pointer">
<div className="flex items-center">
<Icon name="search" size={24} color={color.neutral95} />
<Spacing size={16} direction="row" />
{tagList.length === 0 ? (
<span className="text-neutral-30 text-label1 font-semibold">이곳을 클릭해 검색할 태그를 선택해주세요.</span>
) : (
<div className="flex items-center gap-[8px] flex-wrap">
{tagList.map((tag) => (
<Tag key={tag.name} variant={tag.type}>
{tag.name}
</Tag>
))}
</div>
)}
</div>
<div className="flex items-center gap-[8px]">
<button
className="p-[6px] rounded-[6px] border-[1px] border-neutral-10 m-0"
onClick={(event) => {
event.stopPropagation();
onRefreshClick();
}}>
<Icon name="refresh" size={20} />
</button>
<If condition={tagList.length !== 0}></If>
<TouchButton
className={clsx(activatedButtonClassName, 'px-[16px] py-[8px] rounded-[6px] text-caption1')}
onClick={(event) => {
event.stopPropagation();
setDropdownOpened(false);
if (tagList.length >= 3) {
onShowEaster();
return;
}
onSearchSubmit(tagList.map(({ id }) => id));
}}>
검색하기
</TouchButton>
</div>
<If condition={dropdownOpened}>
<div
onClick={(event) => event.stopPropagation()}
className="absolute translate-y-[calc(100%+16px)] left-0 bottom-0 w-[calc(100vw-160px-80px)] mt-[16px] p-[32px] bg-white border-[1px] border-neutral-5 rounded-[12px] shadow-[0px_2px_12px_0px_rgba(0,0,0,0.08)]">
<span className="text-neutral-75 text-label1 font-semibold">직무역량 태그</span>
<Spacing size={16} />
<div className="flex gap-[16px] flex-wrap">
{역량태그리스트.map(({ type, name, id }) => (
<Tag
key={name}
variant={type}
highlighted={tagList.find((tag) => tag.name === name) != null}
onClick={() => onTagClick({ id, type, name })}>
{name}
</Tag>
))}
</div>

<Spacing size={32} />

<span className="text-neutral-75 text-label1 font-semibold">기본역량 태그</span>
<Spacing size={16} />
<div className="flex gap-[16px] flex-wrap">
{인성태그리스트.map(({ type, name, id }) => (
<Tag
key={name}
variant={type}
highlighted={tagList.find((tag) => tag.name === name) != null}
onClick={() => onTagClick({ type, name, id })}>
{name}
</Tag>
))}
</div>
</div>
</If>
</div>

<Spacing size={32} />
<If condition={!hideTagHistory}>
<span className="text-body1 text-neutral-95 font-semibold">최근 검색한 태그</span>
<Spacing size={16} />
<If condition={tagHistory.length === 0}>
<div className="text-label1 font-semibold text-neutral-30 text-center">최근 검색한 태그가 없어요</div>
</If>
<If condition={tagHistory.length !== 0}>
<div className="flex gap-[12px]">
{tagHistory.map(({ type, name, id }) => (
<Tag key={name} variant={type} onClick={() => onTagClick({ id, type, name })}>
{name}
</Tag>
))}
</div>
</If>
</If>
</div>
);
}
66 changes: 66 additions & 0 deletions src/container/SearchDialog/SearchDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { TouchButton } from '@/components/TouchButton';
import { Icon } from '@/system/components';
import { color } from '@/system/token/color';
import { Spacing } from '@/system/utils/Spacing';
import * as RadixDialog from '@radix-ui/react-dialog';
import { SearchBox } from './SearchBox';
import { useGetSearchCards } from './apis/useGetSearchCards';
import { SearchedCard } from './SearchedCard';
import { EasterEgg } from './EasterEgg';
import { useState } from 'react';
import { AsyncBoundaryWithQuery } from '@/lib';
import { If } from '@/system/utils/If';

interface SearchDialogProps {
open: boolean;
onClose: () => void;
}

function Content({ onClose }: SearchDialogProps) {
const { data, mutate } = useGetSearchCards();
const [showEaster, setShowEaster] = useState(false);

return (
<div className="z-[10000] fixed w-[calc(100vw-160px)] h-[calc(100vh-160px)] top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] rounded-[20px] bg-white p-[40px] pb-0">
<div className="flex justify-between">
<span className="text-heading1 text-neutral-95 font-bold">태그 검색</span>
<TouchButton onClick={onClose}>
<Icon name="close" size={24} color={color.neutral40} />
</TouchButton>
</div>
<Spacing size={24} />
<SearchBox
hideTagHistory={data?.data.length !== 0}
onSearchSubmit={(tagIdList: number[]) => mutate(tagIdList)}
onShowEaster={() => setShowEaster(true)}
/>
<If condition={!showEaster}>
<div className="overflow-scroll grid flex-wrap" style={{ gap: 20, gridTemplateColumns: '1fr 1fr 1fr' }}>
{data?.data.map((info) => <SearchedCard key={info.id} {...info} />)}
</div>
</If>
{showEaster && <EasterEgg />}
</div>
);
}

export function SearchDialog({ open, onClose }: SearchDialogProps) {
if (open == false) {
return null;
}

return (
<RadixDialog.Root>
<RadixDialog.Portal forceMount>
<RadixDialog.Overlay forceMount onClick={onClose}>
<div className="z-[10000] hihi fixed top-0 left-0 w-full h-full bg-[rgba(39,40,44,0.50)]" />
</RadixDialog.Overlay>
<RadixDialog.Content forceMount>
<AsyncBoundaryWithQuery>
<Content open={open} onClose={onClose} />
</AsyncBoundaryWithQuery>
</RadixDialog.Content>
</RadixDialog.Portal>
</RadixDialog.Root>
);
}
65 changes: 65 additions & 0 deletions src/container/SearchDialog/SearchedCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { format } from 'date-fns';
import { generateText } from '@tiptap/core';
import Text from '@tiptap/extension-text';
import StarterKit from '@tiptap/starter-kit';
import { SearchedCardType } from './types';
import { Spacing } from '@/system/utils/Spacing';
import { Tag } from './Tag';
import { If } from '@/system/utils/If';
import { SwitchCase } from '@/system/utils/SwitchCase';

interface Props extends SearchedCardType {
onCardClick?: () => void;
}

export function SearchedCard({
id,
title,
updatedDate,
tagList,
cardTypeValueGroup,
cardTypeValue,
recruitTitle,
content,
onCardClick,
}: Props) {
const value = content === '' ? '' : generateText(JSON.parse(content), [StarterKit, Text]);

return (
<div className="rounded-[16px] border-[1px] border-neutral-5" onClick={onCardClick}>
<div className="bg-neutral-1 px-[23px] pt-[12px] pb-[10px] border-neutral-5 border-b-[1px] flex justify-between items-center">
<div>
{cardTypeValueGroup} / {cardTypeValue}
</div>
<span className="text-neutral-30 text-caption1">{format(new Date(updatedDate), 'yy.MM.dd')}</span>
</div>
<div className="px-[23px] pt-[18px] pb-[22px]">
<div>
<SwitchCase
value={recruitTitle == null ? 'spacing' : 'badge'}
caseBy={{
spacing: <Spacing size={24} />,
badge: (
<span className="text-caption1 px-[6px] py-[3px] rounded-[4px] bg-[#FFF3C2] text-[#D77B0F]">
{recruitTitle}
</span>
),
}}
/>
</div>
<Spacing size={16} />
<span className="text-neutral-95 text-body1 font-semibold text-ellipsis">{title || '제목을 입력해주세요'}</span>
<Spacing size={8} />
<span className="text-ellipsis text-caption1 text-neutral-35 line-clamp-2 h-[32px]">{value}</span>
<Spacing size={16} />
<div className="flex gap-[8px] flex-wrap">
{tagList.slice(0, 3).map((tag) => (
<Tag key={tag.id} variant={tag.type}>
{tag.name}
</Tag>
))}
</div>
</div>
</div>
);
}
23 changes: 23 additions & 0 deletions src/container/SearchDialog/Tag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import clsx from 'clsx';
import { ComponentProps } from 'react';
import { match } from 'ts-pattern';

interface Props extends ComponentProps<'div'> {
variant: '역량' | '인성';
highlighted?: boolean;
}

export function Tag({ variant, highlighted = false, children, ...restProps }: Props) {
const variantClassName = match({ variant, highlighted })
.with({ variant: '인성', highlighted: false }, () => 'text-blue-text-1 border-blue-bg-1')
.with({ variant: '역량', highlighted: false }, () => 'text-purple-text-1 border-purple-bg-1')
.with({ variant: '인성', highlighted: true }, () => 'text-blue-text-1 bg-blue-bg-1')
.with({ variant: '역량', highlighted: true }, () => 'text-purple-text-1 bg-purple-bg-1')
.exhaustive();

return (
<div className={clsx(variantClassName, 'px-[8px] border-[1px] py-[4px] rounded-[4px] text-label1')} {...restProps}>
{children}
</div>
);
}
23 changes: 23 additions & 0 deletions src/container/SearchDialog/apis/useGetSearchCardTagHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { http } from '@/apis/http';
import { useSuspenseQuery } from '@tanstack/react-query';

type Response = Array<{
id: number;
name: string;
type: '인성' | '역량';
}>;

export const GET_SEARCH_CARD_TAG_HISTORY = 'getSearchCardTagHistory';

function getSearchCardTagHistory() {
return http.get<Response>({ url: '/search/card-tag-history' });
}

export function useGetSearchCardTagHistory() {
const result = useSuspenseQuery({
queryKey: [GET_SEARCH_CARD_TAG_HISTORY],
queryFn: () => getSearchCardTagHistory(),
});

return result.data;
}
19 changes: 19 additions & 0 deletions src/container/SearchDialog/apis/useGetSearchCards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { http } from '@/apis/http';
import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
import { SearchedCardType } from '@/container/SearchDialog/types';

type Response = Array<SearchedCardType>;

export const GET_SEARCH_CARDS = 'getSearchCards';

function getSearchCards(ids: number[]) {
return http.get<Response>({
url: `/search/cards?tag-ids=${ids.join(',')}`,
});
}

export function useGetSearchCards() {
const result = useMutation({ mutationFn: (ids: number[]) => getSearchCards(ids) });

return result;
}
14 changes: 14 additions & 0 deletions src/container/SearchDialog/apis/useGetTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { http } from '@/apis/http';
import { TagType } from '@/types';
import { useSuspenseQuery } from '@tanstack/react-query';

const getTags = () =>
http.get<Array<TagType>>({
url: '/tags',
});

export const useGetTags = () =>
useSuspenseQuery({
queryKey: ['tags'],
queryFn: getTags,
}).data;
Loading
Loading