Skip to content

Commit

Permalink
feat: 28기 신입모집 최신화 (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
2yunseong authored Aug 30, 2024
2 parents 95b2b19 + 8edc3a9 commit f4bb863
Show file tree
Hide file tree
Showing 13 changed files with 337 additions and 125 deletions.
8 changes: 4 additions & 4 deletions app/recruit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
"use client";

import { RECRUIT } from "@/src/constants/recruit/recruit.ko";
import { Footer } from "@/src/components/common/Footer";
import { Faq } from "@/src/components/recruit/Faq";
import { Fileds } from "@/src/components/recruit/Fields";
import { Recruit } from "@/src/components/recruit/Recruit";
import { Subscription } from "@/src/components/recruit/Subscription";
import { Waiting } from "@/src/components/recruit/Waiting";
import { useInsertionEffect, useRef, useState } from "react";
import useRecruitStatus from "../../src/hooks/useRecruitStatus";

const RecruitPage = () => {
const [emailInputValue, setEmailInputValue] = useState("");
const recruitRef = useRef<HTMLDivElement>(null);

const { recruitStatus } = useRecruitStatus();
const scrollToRecruit = () =>
window.scrollTo({
top: recruitRef.current?.offsetTop,
Expand All @@ -33,9 +32,10 @@ const RecruitPage = () => {
return (
<>
<div className="m-auto max-w-[1920px] px-24">
{!RECRUIT.IS_ON && (
{recruitStatus !== "OPEN" && (
<section>
<Waiting
recruitStatus={recruitStatus}
inputValue={emailInputValue}
onInputChange={(e) => setEmailInputValue(e.target.value)}
scrollToRecruit={scrollToRecruit}
Expand Down
54 changes: 49 additions & 5 deletions docs/Recruit.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,67 @@ const FAQ = {
string의 값들을 관리하기 위해서 모아두었습니다.
안에 소개 글을 바꾸고 싶다면 여기 있는 내용을 바꾸면 됩니다.

IS_ON은 전역적으로 영향을 받으며 true는 리크루트를 하고 있는 상태, false는 리크루트를 하지 않는 상태입니다.
true일때는 메인 페이지 하단에 날자가 쓰여집니다. false일때 리크루트 구독 페이지가 활성화 됩니다.
### IS_ON (Deprecated 됨)

~~IS_ON은 전역적으로 영향을 받으며 true는 리크루트를 하고 있는 상태, false는 리크루트를 하지 않는 상태입니다.
true일때는 메인 페이지 하단에 날자가 쓰여집니다. false일때 리크루트 구독 페이지가 활성화 됩니다.~~

IS_ON 속성은 Deprecated 되었습니다. 그 이유는 다음과 같습니다.

- 관리자가 수동으로 상태를 변경해주어야 합니다. 그러므로 번거롭습니다.
- 아직 신입모집을 하고 있는 상태는 아니지만 준비된 상태를 나타내기 힘듭니다. 즉, READY | OPEN | CLOSED 의 상태가 필요합니다.

### START_DATE, END_DATE

위의 `IS_ON`이 해결하지 못하는 문제를 해결하기 위해 두가지 속성과 한 가지 훅으로 관리합니다.
이 섹션에서는 두가지 속성에 대해 설명합니다.

**START_DATE**

신입모집을 모집을 시작하는 UTC 시간을 나타냅니다.

**END_DATE**

신입모집의 마감하는 UTC 시간을 나타냅니다.

> ⚠️ 반드시 신입모집 시작 일주일 전에는 해당 값과 기수 값을 바꿔주어야 합니다.
우리는 이제 두 값만 설정해둔다면, 쉽게 상태를 얻을 수 있습니다. 이는 `useRecruitStatus()`훅을 통해 구현합니다.

### useRecruitStatus()

hook을 사용하기 전에, 이를 가정합니다.

- `START_DATE` 는 항상 `END_DATE`보다 이른 시간이어야 합니다.
- 두 속성은 항상 UTC로 정의 되어야 합니다.

훅은 다음과 같이 동작합나다.

- 만약 현재 시간이 `START_DATE`보다 이전이면, 훅은 `READY`를 반환합니다.
- 만약 현재 시간이 `START_DATE``END_DATE` 사이 이면, 상태는 `OPEN`을 반환합니다.
- 만약 현재 시간이 `END_DATE`이후면, 훅은 `CLOSED`를 반환합니다.

#### how to use

```ts
const { recruitStatus } = useRecruitStatus();
```

GENERATION은 해당 진행되는 기수입니다. 2023년도 1학기 기준 25기를 모집하였습니다.

```ts
const RECRUIT = {
TITLE: "recruit", // recruit string 입니다.
CONTENT: string, // recruit에 대한 소개입니다.
IS_ON: boolean, // 리크루트의 진행 상황에 대해 나타냅니다.
IS_ON: boolean, // DEPRECATED: 리크루트의 진행 상황에 대해 나타냅니다.
START_DATE: Date, // 신입모집 시작 시간을 UTC로 표현합니다.
END_DATE: Date, // 신입모집 서류 마감 시간을 UTC로 표현합니다.
GENERATION: number, // 해당 기수에 대해서 입니다.
SCHEDULE: { // 4개면 가장 좋습니다.
TEXT: string, // 스케줄에 대해서 설명하는 string 입니다.
DATE: string, //스케쥴이 언제까지 인지 설명하는 string 입니다. 추후 RECRUIT_FLOAT도 변경해야 합니다.
}[],
WATING: { // IS_ON이 false일 때 활성화 되는 곳입니다.
WATING: { // RecruitStatus가 OPEN이 아닐 때 나타나는 정보들 입니다.
TITLE: "comming soon", // warting string 입니다.
CONTENT: string, // 마감되었다는 글입니다. \n으로 띄어쓰기를 할 수 있습니다.
EMAIL_ALERT: string, // email로 받을 때 소개 글입니다. "입력한 정보의 보유기간은 모집 알림 전송시까지 보관됩니다.",
Expand Down Expand Up @@ -76,6 +121,5 @@ const RECRUIT_FLOAT = { // 메인페이지에 뜨는 부분입니다.
HOUR: "hour",
MINUTE: "minute",
SECOND: "second",
RECRUIT_START_DATE: "2023-08-04", // 시작하는 날짜 입니다. "yyyy-mm-dd" 형태를 유지해 주세요.
};
```
3 changes: 1 addition & 2 deletions src/components/main/Intro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import econovationBlackLogo from "/public/images/econovation-black.svg";
import { InfinityAutoScroll } from "../common/InfinityAutoScroll";
import { ABOUT, JOBS } from "@/src/constants/main.ko";
import { Fragment } from "react";
import { RECRUIT } from "@/src/constants/recruit/recruit.ko";
import { RecruitFloat } from "./RecruitFloat";
import { cn } from "@/src/functions/util";

export const Intro = () => {
return (
<>
{RECRUIT.IS_ON && <RecruitFloat />}
<RecruitFloat />
<h1>
<Image
className="w-full"
Expand Down
197 changes: 98 additions & 99 deletions src/components/main/RecruitFloat.tsx
Original file line number Diff line number Diff line change
@@ -1,116 +1,115 @@
"use client";

import { RECRUIT_FLOAT } from "@/src/constants/recruit/recruit.ko";
import gsap from "gsap";
import {
RECRUIT,
RECRUIT_FLOAT,
type RecruitStatus,
} from "@/src/constants/recruit/recruit.ko";
import Image from "next/image";
import { useRef } from "react";
import { LinkTo } from "../common/LinkTo";
import { useTimeDiffer } from "@/src/hooks/useTimeDiffer";
import useFloatAnimation from "../../hooks/useFloatAnimation";
import useRecruitStatus from "../../hooks/useRecruitStatus";
import HoverText from "./RecruitFloat/HoverText";
import RecruitTimer from "./RecruitFloat/RecruitTimer";
import { exhaustiveError } from "../../functions/util";

export const RecruitFloat = () => {
const recruitDate = new Date(RECRUIT_FLOAT.RECRUIT_START_DATE);
const floatRef = useRef<HTMLDivElement>(null);
const floatDetailRef = useRef<HTMLDivElement>(null);
const { days, hours, minutes, seconds } = useTimeDiffer(recruitDate);

const showDetail = () => {
gsap.to(floatDetailRef.current, {
duration: 0.5,
bottom: "-1rem",
ease: "back.out",
});
gsap.to(floatRef.current, {
duration: 0.5,
bottom: "-150%",
ease: "back.in",
});
};
const renderwordByRecruitStatus = (recruitStatus: RecruitStatus) => {
switch (recruitStatus) {
case "READY":
return (
<span>
<span className="text-white">시작</span>까지
</span>
);
case "OPEN":
return (
<span>
<span className="text-white">마감</span>까지
</span>
);
case "CLOSED":
return <></>;
default:
exhaustiveError(recruitStatus);
}
};

const closeDetail = () => {
gsap.to(floatDetailRef.current, {
duration: 0.5,
bottom: "-200%",
ease: "back.in",
});
gsap.to(floatRef.current, {
duration: 0.5,
bottom: "-1rem",
ease: "back.out",
});
};
export const RecruitFloat = () => {
const recruitStartDate = new Date(RECRUIT.START_DATE);
const recruitEndDate = new Date(RECRUIT.END_DATE);
const { floatDetailRef, floatRef, showDetail, closeDetail } =
useFloatAnimation();
const { days, hours, minutes, seconds } = useTimeDiffer(recruitStartDate);
const {
days: endDays,
hours: endHours,
minutes: endMinutes,
seconds: endSeconds,
} = useTimeDiffer(recruitEndDate);
const { recruitStatus } = useRecruitStatus();

return (
<div
className="fixed bottom-0 h-16 w-full max-w-[1600px] z-[200]"
onMouseEnter={showDetail}
onMouseLeave={closeDetail}
>
<div
className="bg-black absolute -bottom-4 rounded-t-3xl text-white pt-4 pb-8 px-8 text-2xl uppercase group font-bold"
ref={floatRef}
>
{RECRUIT_FLOAT.ECONO_IS_RECRUITING}
</div>
if (recruitStatus !== "CLOSED") {
return (
<div
className="absolute -bottom-[200%] pb-8 pt-4 px-8 w-[calc(100%-6rem)] bg-black text-white rounded-t-3xl font-semibold"
ref={floatDetailRef}
className="fixed bottom-0 z-[200] h-16 w-full max-w-[1600px]"
onMouseEnter={showDetail}
onMouseLeave={closeDetail}
>
<div className="pb-2 border-b-white border-b-2 flex justify-between px-4">
<div className="flex gap-4 items-baseline">
<div className="uppercase text-3xl">
{RECRUIT_FLOAT.ECONO_GENERTAION_RECRUIT_EN}
</div>
<div>{RECRUIT_FLOAT.ECONO_GENERTAION_RECRUIT_KR}</div>
</div>
<div className="flex gap-8">
<div className="flex gap-4">
<div className="flex flex-col items-center mr-4">
<div className="text-3xl">{days}</div>
<div className="text-[0.5rem] uppercase">
{RECRUIT_FLOAT.DAY}
</div>
</div>
<div className="flex flex-col items-center">
<div className="text-3xl opacity-90">{hours}</div>
<div className="text-[0.5rem] uppercase">
{RECRUIT_FLOAT.HOUR}
</div>
</div>
<Image
className="mb-4"
src={require("/public/icons/colon.svg").default}
alt="colon"
/>
<div className="flex flex-col items-center">
<div className="text-3xl opacity-90">{minutes}</div>
<div className="text-[0.5rem] uppercase">
{RECRUIT_FLOAT.MINUTE}
</div>
<div
className="group absolute -bottom-4 rounded-t-3xl bg-black px-8 pb-8 pt-4 text-2xl font-bold uppercase text-white"
ref={floatRef}
>
<HoverText status={recruitStatus} days={days} />
</div>
<div
className="absolute -bottom-[200%] w-[calc(100%-6rem)] rounded-t-3xl bg-black px-8 pb-8 pt-4 font-semibold text-white"
ref={floatDetailRef}
>
<div className="flex justify-between border-b-2 border-b-white px-4 pb-2">
<div className="flex items-baseline gap-4">
<div className="text-3xl uppercase">
{RECRUIT_FLOAT.ECONO_GENERTAION_RECRUIT_EN}
</div>
<Image
className="mb-4"
src={require("/public/icons/colon.svg").default}
alt="colon"
/>
<div className="flex flex-col items-center">
<div className="text-3xl opacity-90">{seconds}</div>
<div className="text-[0.5rem] uppercase">
{RECRUIT_FLOAT.SECOND}
</div>
<div className="text-neutral-300">
<span>에코노베이션</span>
<span className="text-white">` {RECRUIT.GENERTAION}기 `</span>
<span>신입 모집&nbsp;</span>
{renderwordByRecruitStatus(recruitStatus)}
</div>
</div>
<LinkTo
link="RECRUIT"
className="bg-white rounded-full h-10 w-10 flex justify-center items-center"
>
<Image
src={require("/public/icons/right-arrow.svg").default}
alt="right-arrow"
/>
</LinkTo>
<div className="flex gap-8">
{recruitStatus === "OPEN" && (
<RecruitTimer
days={endDays}
hours={endHours}
minutes={endMinutes}
seconds={endSeconds}
/>
)}
{recruitStatus === "READY" && (
<RecruitTimer
days={days}
hours={hours}
minutes={minutes}
seconds={seconds}
/>
)}

<LinkTo
link="RECRUIT"
className="flex h-10 w-10 items-center justify-center rounded-full bg-white"
>
<Image
src={require("/public/icons/right-arrow.svg").default}
alt="right-arrow"
/>
</LinkTo>
</div>
</div>
</div>
</div>
</div>
);
);
}
return <></>;
};
24 changes: 24 additions & 0 deletions src/components/main/RecruitFloat/HoverText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
RECRUIT_FLOAT,
type RecruitStatus,
} from "../../../constants/recruit/recruit.ko";

interface HoverTextProps {
status: RecruitStatus;
days?: number;
}

const HoverText = ({ status, days }: HoverTextProps) => {
const hoverComponents = {
READY: (
<span>
{RECRUIT_FLOAT.ECONO_READY_FOR_RECRUIT} D-{days}
</span>
),
OPEN: <span>{RECRUIT_FLOAT.ECONO_IS_RECRUITING}</span>,
CLOSED: <span />,
};
return hoverComponents[status] ?? <span />;
};

export default HoverText;
Loading

0 comments on commit f4bb863

Please sign in to comment.