From 490b6bd15050fd768eaf0291eb8e152927232925 Mon Sep 17 00:00:00 2001 From: okojin Date: Tue, 18 Mar 2025 15:41:05 +0900 Subject: [PATCH 01/60] chore --- pnpm-lock.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05b7404..82e9353 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1214,12 +1214,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} -<<<<<<< HEAD react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} -======= ->>>>>>> origin/develop react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -2478,11 +2475,8 @@ snapshots: react-is@16.13.1: {} -<<<<<<< HEAD react-is@18.3.1: {} -======= ->>>>>>> origin/develop react-refresh@0.14.2: {} react-router-dom@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): From ea4eaf8d9f35bcfe595d8865adaa9ffc810ad408 Mon Sep 17 00:00:00 2001 From: okojin Date: Tue, 18 Mar 2025 15:41:21 +0900 Subject: [PATCH 02/60] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/pages/register/Page.tsx | 16 ++++++++++++++++ src/router.tsx | 5 +++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/pages/register/Page.tsx diff --git a/src/pages/register/Page.tsx b/src/pages/register/Page.tsx new file mode 100644 index 0000000..c56c4d3 --- /dev/null +++ b/src/pages/register/Page.tsx @@ -0,0 +1,16 @@ +import Card from '@/components/common/card/Card'; +import styles from './page.module.scss'; +import { Link } from 'react-router-dom'; + +export default function RegisterPage() { + return ( +
+

JobForeigner

+

회원가입

+

+ 이미 계정이 있으신가요? 로그인 +

+ 회원가입 내용 +
+ ); +} diff --git a/src/router.tsx b/src/router.tsx index 92896df..24cb2b1 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -20,7 +20,7 @@ const CommunityPage = lazy(() => import('./pages/community/Page')); const CompaniesPage = lazy(() => import('./pages/companies/Page')); const DetailCompanyPage = lazy(() => import('./pages/companies/DetailPage')); const NotFoundPage = lazy(() => import('./pages/notFound/Page')); - +const RegisterPage = lazy(() => import('./pages/register/Page')); // 각 페이지를 Suspense가 적용된 HOC로 감싸기 const SuspensedMainPage = withSuspense(MainPage); const SuspensedProfilePage = withSuspense(ProfilePage); @@ -30,7 +30,7 @@ const SuspensedCommunityPage = withSuspense(CommunityPage); const SuspensedCompaniesPage = withSuspense(CompaniesPage); const SuspensedDetailCompanyPage = withSuspense(DetailCompanyPage); const SuspensedNotFoundPage = withSuspense(NotFoundPage); - +const SuspensedRegisterPage = withSuspense(RegisterPage); export const router = createBrowserRouter( createRoutesFromElements( <> @@ -40,6 +40,7 @@ export const router = createBrowserRouter( } /> } /> } /> + } /> {/* Sidebar가 포함된 라우트 */} From d029abec9838f3f27965fde8bce691c8f3039b2e Mon Sep 17 00:00:00 2001 From: okojin Date: Tue, 18 Mar 2025 15:41:35 +0900 Subject: [PATCH 03/60] =?UTF-8?q?feat:=20=EB=B0=B0=EA=B2=BD=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=EB=A7=81=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/pages/register/page.module.scss | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/pages/register/page.module.scss diff --git a/src/pages/register/page.module.scss b/src/pages/register/page.module.scss new file mode 100644 index 0000000..91c089a --- /dev/null +++ b/src/pages/register/page.module.scss @@ -0,0 +1,32 @@ +.page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + background-color: var(--color-gray-50); + + h1 { + font-size: 2.5rem; + color: var(--color-purple-500); + font-weight: 700; + margin-bottom: 2.5rem; + } + + h2 { + font-size: 1.875rem; + font-weight: 700; + margin-bottom: 1.125rem; + } + + p { + font-size: 1.125rem; + font-weight: 400; + margin-bottom: 4rem; + } + + a { + color: var(--color-purple-500); + text-decoration: none; + } +} From eeb578aafbd4da23dc8d40d28de81a228247602c Mon Sep 17 00:00:00 2001 From: okojin Date: Tue, 18 Mar 2025 15:41:49 +0900 Subject: [PATCH 04/60] =?UTF-8?q?feat:=20=EC=B9=B4=EB=93=9C=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/card/Card.tsx | 13 +++++++++++++ src/components/common/card/card.module.scss | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 src/components/common/card/Card.tsx create mode 100644 src/components/common/card/card.module.scss diff --git a/src/components/common/card/Card.tsx b/src/components/common/card/Card.tsx new file mode 100644 index 0000000..a3ba126 --- /dev/null +++ b/src/components/common/card/Card.tsx @@ -0,0 +1,13 @@ +import styles from './card.module.scss'; + +interface CardProps extends React.HTMLAttributes { + children: React.ReactNode; +} + +export default function Card({ children, ...props }: CardProps) { + return ( +
+ {children} +
+ ); +} diff --git a/src/components/common/card/card.module.scss b/src/components/common/card/card.module.scss new file mode 100644 index 0000000..1fc0a96 --- /dev/null +++ b/src/components/common/card/card.module.scss @@ -0,0 +1,7 @@ +.card { + border: 1px solid var(--color-gray-200); + border-radius: 12px; + background-color: #fff; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); + padding: 24px; +} From 4333cf69763e95727f6c47fa17c140c286dcfa06 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 12:15:35 +0900 Subject: [PATCH 05/60] =?UTF-8?q?fix:=20=EB=B0=94=20=EB=86=92=EC=9D=B4=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/progress/progress.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/progress/progress.module.scss b/src/components/common/progress/progress.module.scss index d14f91f..8b8cf6b 100644 --- a/src/components/common/progress/progress.module.scss +++ b/src/components/common/progress/progress.module.scss @@ -1,7 +1,7 @@ .progress { position: relative; width: 100%; - height: 0.5rem; + height: 0.75rem; background-color: var(--color-gray-200); border-radius: 9999px; overflow: hidden; From fe341e24ec206fe427d7b02f2ccc65a720927afe Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:36:41 +0900 Subject: [PATCH 06/60] =?UTF-8?q?fix:=20=EB=A7=89=EB=8C=80=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=EC=A1=B0=EC=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/progress/progress.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/progress/progress.module.scss b/src/components/common/progress/progress.module.scss index 8b8cf6b..ee4de46 100644 --- a/src/components/common/progress/progress.module.scss +++ b/src/components/common/progress/progress.module.scss @@ -12,7 +12,7 @@ left: 0; top: 0; bottom: 0; - background-color: var(--color-indigo-500); + background-color: var(--color-purple-500); border-radius: inherit; transition: width 0.3s ease; width: 0%; From 7d4894645e1ab22b7af92b83dd42c73f2eae9c65 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:40:50 +0900 Subject: [PATCH 07/60] =?UTF-8?q?docs:=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=EB=B2=95=20JSD?= =?UTF-8?q?oc=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/button/Button.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/common/button/Button.tsx b/src/components/common/button/Button.tsx index 317005e..e6eee37 100644 --- a/src/components/common/button/Button.tsx +++ b/src/components/common/button/Button.tsx @@ -11,8 +11,15 @@ interface ButtonProps extends React.ButtonHTMLAttributes { } /** - * - variant: "default" | "outline" - * - 나머지 props: (onClick, children 등) ButtonHTMLAttributes + * - variant: 버튼 버전 "default" | "outline" + * - default: 기본 버튼 + * - outline: 테두리 버튼 + * - size: 버튼 사이즈 설정 "small" | "medium" | "large" + * - small: 작은 버튼 + * - medium: 중간 버튼 + * - large: 큰 버튼 + * - children: 버튼 내용 + * - 나머지 props: (onClick, children 등) ButtonHTMLAttributes(button 태그의 속성) */ export default function Button({ variant = 'default', From 4d8d9fa6ab8bb06fe457e99c8a5ed16c2b7cd25e Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:41:49 +0900 Subject: [PATCH 08/60] =?UTF-8?q?docs:=20=EC=B9=B4=EB=93=9C=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=EB=B2=95=20JSD?= =?UTF-8?q?oc=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/card/Card.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/common/card/Card.tsx b/src/components/common/card/Card.tsx index a3ba126..b01583c 100644 --- a/src/components/common/card/Card.tsx +++ b/src/components/common/card/Card.tsx @@ -4,6 +4,12 @@ interface CardProps extends React.HTMLAttributes { children: React.ReactNode; } +/** + * - 카드 컴포넌트 + * - 카드 모양으로 특정 컴포넌트를 감쌀 때 사용 + * - children: 카드 내용 + * - 나머지 props: (onClick, children 등) HTMLAttributes(div 태그의 속성) + */ export default function Card({ children, ...props }: CardProps) { return (
From b28b3cc63675318e41f26443a5cf1b72d290e3d3 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:44:13 +0900 Subject: [PATCH 09/60] =?UTF-8?q?docs:=20InputField=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=EB=B2=95=20JSDoc=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/field/InputField.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/common/field/InputField.tsx b/src/components/common/field/InputField.tsx index c597504..1a3a861 100644 --- a/src/components/common/field/InputField.tsx +++ b/src/components/common/field/InputField.tsx @@ -19,6 +19,24 @@ type InputFieldProps = { required?: boolean; } & React.InputHTMLAttributes; +/** + * - React Hook Form 일반 입력 필드 컴포넌트 + * - 텍스트, 전화번호, 숫자, 날짜, 시간 입력 필드 타입 지원 + * - 전화번호 입력 시 자동 포맷팅 지원 + * - 필수 입력 칸 입니다. 메시지 표시 지원 + * - control: 폼 제어 객체 + * - name: 필드 이름 + * - label: 필드 레이블 + * - type: 필드 타입 "text" | "phone" | "number" | "date" | "datetime-local" + * - text: 텍스트 입력 필드 + * - phone: 전화번호 입력 필드 + * - number: 숫자 입력 필드 + * - date: 날짜 입력 필드 + * - datetime-local: 날짜 및 시간 입력 필드 + * - placeholder: 필드 플레이스홀더 + * - required: 필수 입력 여부 + * - 나머지 props: (onChange, onKeyDown 등) InputHTMLAttributes(input 태그의 속성) + */ const InputField = ({ control, name, From 7f2ec10ed34ced67064cdbe7c56f84a9bbf9726d Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:46:05 +0900 Subject: [PATCH 10/60] =?UTF-8?q?docs:=20SelectField=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=EB=B2=95=20JSDoc=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/field/SelectField.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/common/field/SelectField.tsx b/src/components/common/field/SelectField.tsx index 41b0332..9da4cbc 100644 --- a/src/components/common/field/SelectField.tsx +++ b/src/components/common/field/SelectField.tsx @@ -15,6 +15,18 @@ type Props = { }[]; }; +/** + * - React Hook Form 선택 필드 컴포넌트 + * - 선택 필드 타입 지원 + * - 필수 입력 칸 입니다. 메시지 표시 지원 + * - control: 폼 제어 객체 + * - name: 필드 이름 + * - label: 필드 레이블 + * - required: 필수 입력 여부 + * - options: 선택 필드 옵션 배열({value: string, label: string}[]) + * - value: 옵션 값 + * - label: 옵션 레이블 + */ export default function SelectField({ control, name, From 0921a88aaf68fd2dbdd63d14d9d717930a4fb284 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:46:47 +0900 Subject: [PATCH 11/60] =?UTF-8?q?docs:=20TextareaField=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=EB=B2=95=20JSD?= =?UTF-8?q?oc=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/field/TextareaField.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/common/field/TextareaField.tsx b/src/components/common/field/TextareaField.tsx index af9ce6e..bf226de 100644 --- a/src/components/common/field/TextareaField.tsx +++ b/src/components/common/field/TextareaField.tsx @@ -13,6 +13,17 @@ type TextareaFieldProps = { required?: boolean; } & React.InputHTMLAttributes; +/** + * - React Hook Form 텍스트 필드 컴포넌트 + * - 텍스트 필드 타입 지원 + * - 필수 입력 칸 입니다. 메시지 표시 지원 + * - control: 폼 제어 객체 + * - name: 필드 이름 + * - label: 필드 레이블 + * - placeholder: 필드 플레이스홀더 + * - required: 필수 입력 여부 + * - 나머지 props: (onChange, onKeyDown 등) InputHTMLAttributes(input 태그의 속성) + */ const TextareaField = ({ control, name, From bac074c63cf794637ef808773dc37a11e394fe87 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:48:05 +0900 Subject: [PATCH 12/60] =?UTF-8?q?docs:=20URLInputField=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=EB=B2=95=20JSD?= =?UTF-8?q?oc=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/field/URLInputField.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/common/field/URLInputField.tsx b/src/components/common/field/URLInputField.tsx index 5479528..98e497e 100644 --- a/src/components/common/field/URLInputField.tsx +++ b/src/components/common/field/URLInputField.tsx @@ -1,4 +1,3 @@ -import { formatPhoneNumber } from '@/lib/utils/formatters'; import FormField from '../form/FormField'; import FormItem from '../form/FormItem'; import FormLabel from '../form/FormLabel'; @@ -14,11 +13,25 @@ type InputFieldProps = { control: Control; name: string; label: string; - type?: 'text' | 'phone' | 'number' | 'date' | 'datetime-local' | 'url'; + type?: 'text' | 'url'; placeholder?: string; required?: boolean; } & React.InputHTMLAttributes; +/** + * - React Hook Form URL 입력 필드 컴포넌트 + * - URL 입력 필드 타입 지원 + * - 필수 입력 칸 입니다. 메시지 표시 지원 + * - control: 폼 제어 객체 + * - name: 필드 이름 + * - label: 필드 레이블 + * - type: 필드 타입 "text" | "phone" | "number" | "date" | "datetime-local" | "url" + * - text: 텍스트 입력 필드 + * - url: URL 입력 필드 + * - placeholder: 필드 플레이스홀더 + * - required: 필수 입력 여부 + * - 나머지 props: (onChange, onKeyDown 등) InputHTMLAttributes(input 태그의 속성) + */ const URLInputField = ({ control, name, @@ -33,12 +46,6 @@ const URLInputField = ({ ) => { const target = e.currentTarget; - if (type === 'phone') { - target.value = formatPhoneNumber(target.value); - field.onChange(target.value); - return; - } - field.onChange(target.value); }; From 297a9efe11b19440ee66dfe5310559a9b084939a Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:50:14 +0900 Subject: [PATCH 13/60] =?UTF-8?q?docs:=20Progress=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=EB=B2=95=20JSDoc=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/progress/Progress.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/common/progress/Progress.tsx b/src/components/common/progress/Progress.tsx index e9d6e4c..1cc1bc7 100644 --- a/src/components/common/progress/Progress.tsx +++ b/src/components/common/progress/Progress.tsx @@ -6,6 +6,12 @@ interface ProgressProps { className?: string; } +/** + * - 진행 바 컴포넌트 + * - 진행 바 모양으로 특정 컴포넌트를 감쌀 때 사용 + * - value: 진행 바 값 + * - className: 진행 바 클래스 이름 + */ export default function Progress({ value, className }: ProgressProps) { const safeValue = Math.min(Math.max(value, 0), 100); From a775994b3a9f1bb83801f09ab94c252d235f36d1 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 14:51:15 +0900 Subject: [PATCH 14/60] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80,=20=EC=A0=95=EA=B7=9C=EC=8B=9D=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/lib/schemas/error.ts | 18 ++++++++++++++++++ src/lib/schemas/regex.ts | 3 +++ src/lib/schemas/resumeSchema.ts | 21 ++------------------- 3 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 src/lib/schemas/error.ts create mode 100644 src/lib/schemas/regex.ts diff --git a/src/lib/schemas/error.ts b/src/lib/schemas/error.ts new file mode 100644 index 0000000..dd7861d --- /dev/null +++ b/src/lib/schemas/error.ts @@ -0,0 +1,18 @@ +export const ERROR_MSG = { + required: '필수 입력 항목입니다.', + numberRequired: '1 이상의 숫자를 입력해주세요.', + password: { + min: '8자 이상으로 입력해주세요.', + confirm: '비밀번호가 일치하지 않습니다.', + }, + exceed: { + ten: '10자 이내로 입력해주세요.', + thirty: '30자 이내로 입력해주세요.', + fifty: '50자 이내로 입력해주세요.', + hundred: '100자 이내로 입력해주세요.', + thousand: '1000자 이내로 입력해주세요.', + twoThousand: '2000자 이내로 입력해주세요.', + fiveThousand: '5000자 이내로 입력해주세요.', + }, + phoneNumber: '올바른 전화번호를 입력해주세요.', +}; diff --git a/src/lib/schemas/regex.ts b/src/lib/schemas/regex.ts new file mode 100644 index 0000000..c0da3cd --- /dev/null +++ b/src/lib/schemas/regex.ts @@ -0,0 +1,3 @@ +export const REGEX = { + phoneNumber: /^\d{2,3}-\d{3,4}-\d{4}$/, +}; diff --git a/src/lib/schemas/resumeSchema.ts b/src/lib/schemas/resumeSchema.ts index e958d9f..3d91e01 100644 --- a/src/lib/schemas/resumeSchema.ts +++ b/src/lib/schemas/resumeSchema.ts @@ -1,23 +1,6 @@ import { z } from 'zod'; - -const REGEX = { - phoneNumber: /^\d{2,3}-\d{3,4}-\d{4}$/, -}; - -const ERROR_MSG = { - required: '필수 입력 항목입니다.', - numberRequired: '1 이상의 숫자를 입력해주세요.', - exceed: { - ten: '10자 이내로 입력해주세요.', - thirty: '30자 이내로 입력해주세요.', - fifty: '50자 이내로 입력해주세요.', - hundred: '100자 이내로 입력해주세요.', - thousand: '1000자 이내로 입력해주세요.', - twoThousand: '2000자 이내로 입력해주세요.', - fiveThousand: '5000자 이내로 입력해주세요.', - }, - phoneNumber: '올바른 전화번호를 입력해주세요.', -}; +import { ERROR_MSG } from './error'; +import { REGEX } from './regex'; export const resumeSchema = z.object({ title: z.string().min(1, ERROR_MSG.required).max(50, ERROR_MSG.exceed.fifty), From 4b9d543f499ed8e1c859d6e8a970528e92f04891 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 15:24:59 +0900 Subject: [PATCH 15/60] =?UTF-8?q?feat:=20radix=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=EB=B0=95=EC=8A=A4=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- package.json | 1 + pnpm-lock.yaml | 205 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/package.json b/package.json index 9a74f6e..9b7e048 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@hookform/resolvers": "^4.1.3", + "@radix-ui/react-checkbox": "^1.1.4", "clsx": "^2.1.1", "lucide-react": "^0.471.1", "path": "^0.12.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82e9353..d1cc659 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@hookform/resolvers': specifier: ^4.1.3 version: 4.1.3(react-hook-form@7.54.2(react@18.3.1)) + '@radix-ui/react-checkbox': + specifier: ^1.1.4 + version: 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -492,6 +495,120 @@ packages: resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} engines: {node: '>= 10.0.0'} + '@radix-ui/primitive@1.1.1': + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + + '@radix-ui/react-checkbox@1.1.4': + resolution: {integrity: sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.2': + resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.2': + resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.0': + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rollup/rollup-android-arm-eabi@4.29.2': resolution: {integrity: sha512-s/8RiF4bdmGnc/J0N7lHAr5ZFJj+NdJqJ/Hj29K+c4lEdoVlukzvWXB9XpWZCdakVT0YAw8iyIqUP2iFRz5/jA==} cpu: [arm] @@ -1781,6 +1898,94 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.0 optional: true + '@radix-ui/primitive@1.1.1': {} + + '@radix-ui/react-checkbox@1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@radix-ui/react-context@1.1.1(@types/react@18.3.18)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + + '@radix-ui/react-primitive@2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + + '@radix-ui/react-slot@1.1.2(@types/react@18.3.18)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.18)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.18)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.18)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.18)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.18)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + '@rollup/rollup-android-arm-eabi@4.29.2': optional: true From cb89da81cdde00508a882d12e8a8f6a93c88f847 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Wed, 19 Mar 2025 15:25:17 +0900 Subject: [PATCH 16/60] =?UTF-8?q?feat:=20FormMessage=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/field/InputField.tsx | 5 ++++- src/components/common/field/SelectField.tsx | 2 ++ src/components/common/field/TextareaField.tsx | 5 ++++- src/components/common/field/URLInputField.tsx | 5 ++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/common/field/InputField.tsx b/src/components/common/field/InputField.tsx index 1a3a861..ce51fb1 100644 --- a/src/components/common/field/InputField.tsx +++ b/src/components/common/field/InputField.tsx @@ -69,7 +69,10 @@ const InputField = ({ {label}  {' '} {required && ( - + * )} diff --git a/src/components/common/field/SelectField.tsx b/src/components/common/field/SelectField.tsx index 9da4cbc..1198b56 100644 --- a/src/components/common/field/SelectField.tsx +++ b/src/components/common/field/SelectField.tsx @@ -3,6 +3,7 @@ import FormField from '../form/FormField'; import FormItem from '../form/FormItem'; import FormLabel from '../form/FormLabel'; import Select from '../select/Select'; +import FormMessage from '../form/FormMessage'; type Props = { control: Control; @@ -48,6 +49,7 @@ export default function SelectField({ )} handleInput(e, field)} - onKeyDown={e => e.key === 'Enter' && e.preventDefault()} - type={type} - icon={icon} - /> - - - )} - /> - ); -}; + return ( + ( + + + {label}  {' '} + {required && ( + + * + + )} + + handleInput(e, field)} + onKeyDown={e => e.key === 'Enter' && e.preventDefault()} + type={type} + icon={icon} + ref={ref} + /> + + + )} + /> + ); + }, +); export default InputField; From 16538aa8594e445739b4490ce5c0308af4089728 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Thu, 20 Mar 2025 11:04:02 +0900 Subject: [PATCH 36/60] =?UTF-8?q?fix:=20=EC=B9=B4=EB=93=9C=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=20relative=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/card/card.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/common/card/card.module.scss b/src/components/common/card/card.module.scss index 1fc0a96..f749152 100644 --- a/src/components/common/card/card.module.scss +++ b/src/components/common/card/card.module.scss @@ -1,4 +1,5 @@ .card { + position: relative; border: 1px solid var(--color-gray-200); border-radius: 12px; background-color: #fff; From 33858b3865d9d00afccdbd521c5a69703c7c16fa Mon Sep 17 00:00:00 2001 From: oko_jin Date: Thu, 20 Mar 2025 11:09:24 +0900 Subject: [PATCH 37/60] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/login/LoginSection.tsx | 65 +++++++++++++++++++ src/components/login/loginSection.module.scss | 36 ++++++++++ src/lib/schemas/loginSchema.ts | 15 +++++ src/pages/login/Page.tsx | 46 +++++++++++++ src/pages/login/page.module.scss | 44 +++++++++++++ src/router.tsx | 5 ++ 6 files changed, 211 insertions(+) create mode 100644 src/components/login/LoginSection.tsx create mode 100644 src/components/login/loginSection.module.scss create mode 100644 src/lib/schemas/loginSchema.ts create mode 100644 src/pages/login/Page.tsx create mode 100644 src/pages/login/page.module.scss diff --git a/src/components/login/LoginSection.tsx b/src/components/login/LoginSection.tsx new file mode 100644 index 0000000..67d8d96 --- /dev/null +++ b/src/components/login/LoginSection.tsx @@ -0,0 +1,65 @@ +import { useFormContext } from 'react-hook-form'; +import styles from './loginSection.module.scss'; +import InputField from '../common/field/InputField'; +import Button from '../common/button/Button'; +import { Link } from 'react-router-dom'; +import { useRef, useState } from 'react'; +import { Eye, EyeOff } from 'lucide-react'; + +export default function LoginSection() { + const { control } = useFormContext(); + const passwordRef = useRef(null); + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + + const togglePasswordVisibility = () => { + setIsPasswordVisible(!isPasswordVisible); + + setTimeout(() => { + if (passwordRef.current) { + passwordRef.current.focus(); + const length = passwordRef.current.value.length; + passwordRef.current.setSelectionRange(length, length); + } + }, 0); + }; + + return ( +
+ +
+ 비밀번호 찾기 +
+ + {isPasswordVisible ? ( + + ) : ( + + )} +
+ +
+
+ ); +} diff --git a/src/components/login/loginSection.module.scss b/src/components/login/loginSection.module.scss new file mode 100644 index 0000000..ccee2f4 --- /dev/null +++ b/src/components/login/loginSection.module.scss @@ -0,0 +1,36 @@ +.container { + text-align: left; + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.searchPasswordLink { + position: absolute; + right: 2em; + top: 9.5em; + font-size: 0.875rem; + + a { + color: var(--color-purple-500); + text-decoration: none; + + &:hover { + color: var(--color-purple-400); + } + } +} + +.passwordIcon { + position: absolute; + right: 3em; + top: 12.875em; + cursor: pointer; + color: var(--color-gray-500); +} + +.buttonContainer { + display: flex; + flex-direction: column; + margin-top: 2rem; +} diff --git a/src/lib/schemas/loginSchema.ts b/src/lib/schemas/loginSchema.ts new file mode 100644 index 0000000..eacf50c --- /dev/null +++ b/src/lib/schemas/loginSchema.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; +import { ERROR_MSG } from './error'; + +export const loginSchema = z.object({ + email: z.string().email(ERROR_MSG.email).max(50, ERROR_MSG.exceed.fifty), + password: z.string().min(8, ERROR_MSG.password.min), +}); + +export type LoginValues = z.infer; + +export const validateLogin = (formData: FormData) => { + const formValues = Object.fromEntries(formData.entries()); + + return loginSchema.safeParse(formValues); +}; diff --git a/src/pages/login/Page.tsx b/src/pages/login/Page.tsx new file mode 100644 index 0000000..5638e9d --- /dev/null +++ b/src/pages/login/Page.tsx @@ -0,0 +1,46 @@ +import { Link } from 'react-router-dom'; +import styles from './page.module.scss'; +import { FormProvider, useForm } from 'react-hook-form'; +import Card from '@/components/common/card/Card'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { loginSchema, LoginValues } from '@/lib/schemas/loginSchema'; +import LoginSection from '@/components/login/LoginSection'; + +const defaultValues = { + email: '', + password: '', +}; + +export default function LoginPage() { + const formState = useForm({ + defaultValues, + resolver: zodResolver(loginSchema), + }); + + const onSubmit = (data: LoginValues) => { + console.log(data); + }; + + const onError = (error: unknown) => { + console.error(error); + }; + + return ( +
+
+

JobForeigner

+

로그인

+

+ 아직 계정이 없으신가요? 회원가입 +

+ + +
+ + +
+
+
+
+ ); +} diff --git a/src/pages/login/page.module.scss b/src/pages/login/page.module.scss new file mode 100644 index 0000000..df9dd43 --- /dev/null +++ b/src/pages/login/page.module.scss @@ -0,0 +1,44 @@ +.page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + height: 100%; + background-color: var(--color-gray-50); + text-align: center; + padding: 4rem 0; +} + +.container { + width: 420px; + + > h1 { + font-size: 2.5rem; + color: var(--color-purple-500); + font-weight: 700; + margin-bottom: 2rem; + } + + > h2 { + font-size: 1.875rem; + font-weight: 700; + margin-bottom: 1.125rem; + } + + > p { + font-size: 1rem; + font-weight: 400; + margin-bottom: 3rem; + color: var(--color-gray-500); + + > a { + color: var(--color-purple-500); + text-decoration: none; + + &:hover { + color: var(--color-purple-400); + } + } + } +} diff --git a/src/router.tsx b/src/router.tsx index 24cb2b1..8ffd466 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -21,6 +21,8 @@ const CompaniesPage = lazy(() => import('./pages/companies/Page')); const DetailCompanyPage = lazy(() => import('./pages/companies/DetailPage')); const NotFoundPage = lazy(() => import('./pages/notFound/Page')); const RegisterPage = lazy(() => import('./pages/register/Page')); +const LoginPage = lazy(() => import('./pages/login/Page')); + // 각 페이지를 Suspense가 적용된 HOC로 감싸기 const SuspensedMainPage = withSuspense(MainPage); const SuspensedProfilePage = withSuspense(ProfilePage); @@ -31,6 +33,8 @@ const SuspensedCompaniesPage = withSuspense(CompaniesPage); const SuspensedDetailCompanyPage = withSuspense(DetailCompanyPage); const SuspensedNotFoundPage = withSuspense(NotFoundPage); const SuspensedRegisterPage = withSuspense(RegisterPage); +const SuspensedLoginPage = withSuspense(LoginPage); + export const router = createBrowserRouter( createRoutesFromElements( <> @@ -41,6 +45,7 @@ export const router = createBrowserRouter( } /> } /> } /> + } /> {/* Sidebar가 포함된 라우트 */} From f9fe53dd7710c820e0cb299e73069606ce36314d Mon Sep 17 00:00:00 2001 From: oko_jin Date: Thu, 20 Mar 2025 11:27:38 +0900 Subject: [PATCH 38/60] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A7=81=ED=81=AC=20register=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #20 --- src/components/common/header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index 91266ac..474bd38 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -32,7 +32,7 @@ export default function Header() { - +
From 235ca8492920a4c587d371a171ab84ed4a5af570 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Thu, 20 Mar 2025 14:51:46 +0900 Subject: [PATCH 39/60] =?UTF-8?q?feat:=20=EC=A7=80=EC=9B=90=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=ED=8D=BC?= =?UTF-8?q?=EB=B8=94=EB=A6=AC=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #23 --- .../profile/applications/ApplicationInfo.tsx | 89 ++++++++ .../profile/applications/ApplicationsTabs.tsx | 85 ++++++++ .../profile/applications/StatusBox.tsx | 32 +++ .../applications/applicationInfo.module.scss | 131 ++++++++++++ .../applications/applicationsTabs.module.scss | 14 ++ .../applications/statusBox.module.scss | 50 +++++ src/lib/type/profile/application.ts | 10 + src/pages/profile/applications/Page.tsx | 198 ++++++++++++++++++ .../profile/applications/page.module.scss | 48 +++++ src/router.tsx | 8 + 10 files changed, 665 insertions(+) create mode 100644 src/components/profile/applications/ApplicationInfo.tsx create mode 100644 src/components/profile/applications/ApplicationsTabs.tsx create mode 100644 src/components/profile/applications/StatusBox.tsx create mode 100644 src/components/profile/applications/applicationInfo.module.scss create mode 100644 src/components/profile/applications/applicationsTabs.module.scss create mode 100644 src/components/profile/applications/statusBox.module.scss create mode 100644 src/lib/type/profile/application.ts create mode 100644 src/pages/profile/applications/Page.tsx create mode 100644 src/pages/profile/applications/page.module.scss diff --git a/src/components/profile/applications/ApplicationInfo.tsx b/src/components/profile/applications/ApplicationInfo.tsx new file mode 100644 index 0000000..ae9e8a3 --- /dev/null +++ b/src/components/profile/applications/ApplicationInfo.tsx @@ -0,0 +1,89 @@ +import Card from '@/components/common/card/Card'; +import styles from './applicationInfo.module.scss'; +import { Application } from '@/lib/type/profile/application'; +import { Ban, Building2, Calendar, ChevronRight, FileText } from 'lucide-react'; +import clsx from 'clsx'; +import Button from '@/components/common/button/Button'; + +const parseStatus = (status: string) => { + if (status === 'reviewing') { + return '서류 검토중'; + } + if (status === 'interview') { + return '면접 예정'; + } + if (status === 'accepted') { + return '채용 완료'; + } + if (status === 'rejected') { + return '불합격'; + } + return status; +}; + +interface ApplicationInfoProps { + application: Application; + icon: React.ReactNode; +} + +export default function ApplicationInfo({ + application, + icon, +}: ApplicationInfoProps) { + return ( + +
+
+
+ {application.company} +
+
+

+ {application.title} +

+

+ {application.company} +

+

+ + + {application.location} + + + + + 지원일: {application.appliedAt} + + + + + {application.resumeTitle} + +

+
+
+
+ {icon} + {parseStatus(application.status)} +
+
+
+
+
+ + +
+
+
+ ); +} diff --git a/src/components/profile/applications/ApplicationsTabs.tsx b/src/components/profile/applications/ApplicationsTabs.tsx new file mode 100644 index 0000000..cd3d10f --- /dev/null +++ b/src/components/profile/applications/ApplicationsTabs.tsx @@ -0,0 +1,85 @@ +import { Application } from '@/lib/type/profile/application'; +import clsx from 'clsx'; +import styles from './applicationsTabs.module.scss'; + +interface ApplicationsTabsProps { + applications: Application[]; + reviewing: Application[]; + interviewing: Application[]; + accepted: Application[]; + selectedApplications: { status: string; applications: Application[] }; + setSelectedApplications: (applications: { + status: string; + applications: Application[]; + }) => void; +} + +export default function ApplicationsTabs({ + applications, + reviewing, + interviewing, + accepted, + selectedApplications, + setSelectedApplications, +}: ApplicationsTabsProps) { + return ( + <> + + + + + + ); +} diff --git a/src/components/profile/applications/StatusBox.tsx b/src/components/profile/applications/StatusBox.tsx new file mode 100644 index 0000000..66c89aa --- /dev/null +++ b/src/components/profile/applications/StatusBox.tsx @@ -0,0 +1,32 @@ +import Card from '@/components/common/card/Card'; +import styles from './statusBox.module.scss'; +import clsx from 'clsx'; + +const statusClassName: Record = { + '전체 지원': 'all', + '서류 검토중': 'reviewing', + '면접 예정': 'interview', + 완료: 'accepted', +}; + +type StatusBoxProps = { + icon: React.ReactNode; + title: string; + number: number; +}; + +export default function StatusBox({ icon, title, number }: StatusBoxProps) { + return ( + +
+
+
{title}
+
{number}
+
+
+ {icon} +
+
+
+ ); +} diff --git a/src/components/profile/applications/applicationInfo.module.scss b/src/components/profile/applications/applicationInfo.module.scss new file mode 100644 index 0000000..a576147 --- /dev/null +++ b/src/components/profile/applications/applicationInfo.module.scss @@ -0,0 +1,131 @@ +.applicationInfo { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.applicationInfoHeader { + display: flex; + gap: 1rem; +} + +.applicationInfoImage { + width: 4rem; + height: 4rem; + background-color: var(--color-gray-100); + border-radius: 0.5rem; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.applicationInfoHeaderText { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.applicationInfoHeaderTitle { + font-size: 1.375rem; + font-weight: 600; + color: var(--color-gray-900); +} + +.applicationInfoHeaderCompany { + font-size: 1.0625rem; + color: var(--color-gray-500); +} + +.applicationInfoHeaderDescription { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1rem; + color: var(--color-gray-500); + + span { + display: flex; + align-items: center; + gap: 0.25rem; + } + + svg { + width: 1rem; + height: 1rem; + color: var(--color-gray-500); + } +} + +.applicationInfoStatus { + display: flex; + align-items: flex-start; + gap: 0.5rem; +} + +.tag { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.25rem 0.5rem; + border-radius: 9999px; + background-color: var(--color-gray-100); + font-size: 0.875rem; + font-weight: 700; + + svg { + width: 1rem; + height: 1rem; + } + + &.reviewing { + background-color: var(--color-blue-100); + color: var(--color-blue-800); + } + + &.interview { + background-color: var(--color-yellow-100); + color: var(--color-yellow-800); + } + + &.accepted { + background-color: var(--color-green-100); + color: var(--color-green-800); + } + + &.rejected { + background-color: var(--color-red-100); + color: var(--color-red-800); + } +} + +.applicationInfoDivider { + border: none; + border-top: 1px solid var(--color-gray-200); +} + +.applicationInfoActions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; + align-items: center; +} + +.buttonItem { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1rem; + + &.cancel { + color: var(--color-red-600); + } + + svg { + width: 1.375rem; + height: 1.375rem; + } +} diff --git a/src/components/profile/applications/applicationsTabs.module.scss b/src/components/profile/applications/applicationsTabs.module.scss new file mode 100644 index 0000000..35f1b24 --- /dev/null +++ b/src/components/profile/applications/applicationsTabs.module.scss @@ -0,0 +1,14 @@ +.tab { + border: none; + border-radius: 0.5rem; + background-color: none; + padding: 0.5rem 0.625rem; + font-size: 1.0625rem; + font-weight: 500; + color: var(--color-gray-900); + cursor: pointer; + + &.active { + background-color: #fff; + } +} diff --git a/src/components/profile/applications/statusBox.module.scss b/src/components/profile/applications/statusBox.module.scss new file mode 100644 index 0000000..9269759 --- /dev/null +++ b/src/components/profile/applications/statusBox.module.scss @@ -0,0 +1,50 @@ +.container { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; +} + +.left { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.title { + font-size: 1rem; + color: var(--color-gray-500); +} + +.number { + font-size: 2rem; + font-weight: 600; +} + +.right { + display: flex; + align-items: center; + justify-content: center; + border-radius: 9999px; + padding: 0.5rem; + + &.all { + background-color: var(--color-purple-50); + color: var(--color-purple-600); + } + + &.reviewing { + background-color: var(--color-blue-50); + color: var(--color-blue-600); + } + + &.interview { + background-color: var(--color-green-50); + color: var(--color-green-600); + } + + &.accepted { + background-color: var(--color-green-50); + color: var(--color-green-600); + } +} diff --git a/src/lib/type/profile/application.ts b/src/lib/type/profile/application.ts new file mode 100644 index 0000000..e675e23 --- /dev/null +++ b/src/lib/type/profile/application.ts @@ -0,0 +1,10 @@ +export type Application = { + id: number; + company: string; + title: string; + logo: string; + location: string; + appliedAt: string; + status: string; + resumeTitle: string; +}; diff --git a/src/pages/profile/applications/Page.tsx b/src/pages/profile/applications/Page.tsx new file mode 100644 index 0000000..3e0ce79 --- /dev/null +++ b/src/pages/profile/applications/Page.tsx @@ -0,0 +1,198 @@ +import { useState } from 'react'; +import styles from './page.module.scss'; +import StatusBox from '@/components/profile/applications/StatusBox'; +import { Application } from '@/lib/type/profile/application'; +import ApplicationsTabs from '@/components/profile/applications/ApplicationsTabs'; +import ApplicationInfo from '@/components/profile/applications/ApplicationInfo'; +import { + FileTextIcon, + CalendarClock, + CheckCircle2, + Clock, + XCircle, +} from 'lucide-react'; + +function getIcon(title: string) { + if (title === '전체 지원' || title === 'all') { + return ; + } + if (title === '서류 검토중' || title === 'reviewing') { + return ; + } + if (title === '면접 예정' || title === 'interview') { + return ; + } + if (title === '완료' || title === 'accepted') { + return ; + } + if (title === '완료' || title === 'rejected') { + return ; + } + return null; +} + +const applications: Application[] = [ + { + id: 1, + company: '토스', + title: '프론트엔드 개발자', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '서울 강남구', + appliedAt: '2021-08-01', + status: 'reviewing', + resumeTitle: '프론트엔드 개발자 이력서', + }, + { + id: 2, + company: '당근마켓', + title: '백엔드 개발자', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '서울 강남구', + appliedAt: '2021-08-01', + status: 'interview', + resumeTitle: '백엔드 개발자 이력서', + }, + { + id: 3, + company: '네이버', + title: '디자이너', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '경기 성남', + appliedAt: '2021-08-01', + status: 'rejected', + resumeTitle: '디자이너 이력서', + }, + { + id: 4, + company: '카카오', + title: '프론트엔드 개발자', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '서울 강남구', + appliedAt: '2021-08-01', + status: 'accepted', + resumeTitle: '프론트엔드 개발자 이력서', + }, + { + id: 5, + company: '라인', + title: '프론트엔드 개발자', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '도쿄 신주쿠', + appliedAt: '2021-08-01', + status: 'interview', + resumeTitle: '프론트엔드 개발자 이력서', + }, + { + id: 6, + company: '우아한 형제들', + title: '프론트엔드 개발자', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '경기 성남', + appliedAt: '2021-08-01', + status: 'reviewing', + resumeTitle: '프론트엔드 개발자 이력서', + }, + { + id: 7, + company: '쿠팡', + title: '프론트엔드 개발자', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '서울 강남구', + appliedAt: '2021-08-01', + status: 'interview', + resumeTitle: '프론트엔드 개발자 이력서', + }, + { + id: 8, + company: '다쏘시스템', + title: '프론트엔드 개발자', + logo: 'https://toss.im/assets/images/toss-logo.png', + location: '대구 중구', + appliedAt: '2021-08-01', + status: 'reviewing', + resumeTitle: '프론트엔드 개발자 이력서', + }, +]; + +export default function ApplicationsPage() { + const [selectedApplications, setSelectedApplications] = useState<{ + status: string; + applications: Application[]; + }>({ + status: 'all', + applications: applications, + }); + + const reviewing = applications.filter( + application => application.status === 'reviewing', + ); + const interviewing = applications.filter( + application => application.status === 'interview', + ); + const accepted = applications.filter( + application => + application.status === 'accepted' || application.status === 'rejected', + ); + + const statusBoxes = [ + { + id: 1, + title: '전체 지원', + number: applications.length, + }, + { + id: 2, + title: '서류 검토중', + number: reviewing.length, + }, + { + id: 3, + title: '면접 예정', + number: interviewing.length, + }, + { + id: 4, + title: '완료', + number: accepted.length, + }, + ]; + + return ( +
+
+

지원 내역

+

+ 지원한 채용 공고와 진행 상태를 확인할 수 있습니다. +

+
+ {statusBoxes.map(statusBox => ( + + ))} +
+
+ +
+
+ {selectedApplications.applications.map(application => ( + + ))} +
+
+
+ ); +} diff --git a/src/pages/profile/applications/page.module.scss b/src/pages/profile/applications/page.module.scss new file mode 100644 index 0000000..e102c1c --- /dev/null +++ b/src/pages/profile/applications/page.module.scss @@ -0,0 +1,48 @@ +.container { + background-color: var(--color-gray-50); + flex: 1; + padding: 1rem; +} + +.page { + max-width: 1024px; + margin: 0 auto; + margin-top: 3rem; + margin-bottom: 6rem; +} + +.pageTitle { + font-size: 1.5rem; + line-height: 2rem; + font-weight: 700; + color: var(--color-gray-900); +} + +.pageDescription { + margin-top: 0.25rem; + font-size: 0.875rem; + color: var(--color-gray-500); + margin-bottom: 1.5rem; +} + +.statusBoxes { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1rem; +} + +.tabs { + display: inline-flex; + gap: 0.25rem; + margin-top: 2rem; + margin-bottom: 1rem; + border-radius: 0.5rem; + background-color: var(--color-indigo-50); + padding: 0.25rem; +} + +.applications { + display: flex; + flex-direction: column; + gap: 1rem; +} diff --git a/src/router.tsx b/src/router.tsx index 8ffd466..40d1c33 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -16,6 +16,9 @@ const ResumeListPage = lazy(() => import('./pages/profile/resume/Page')); const CreateResumePage = lazy( () => import('./pages/profile/resume/create/Page'), ); +const ApplicationsPage = lazy( + () => import('./pages/profile/applications/Page'), +); const CommunityPage = lazy(() => import('./pages/community/Page')); const CompaniesPage = lazy(() => import('./pages/companies/Page')); const DetailCompanyPage = lazy(() => import('./pages/companies/DetailPage')); @@ -34,6 +37,7 @@ const SuspensedDetailCompanyPage = withSuspense(DetailCompanyPage); const SuspensedNotFoundPage = withSuspense(NotFoundPage); const SuspensedRegisterPage = withSuspense(RegisterPage); const SuspensedLoginPage = withSuspense(LoginPage); +const SuspensedApplicationsPage = withSuspense(ApplicationsPage); export const router = createBrowserRouter( createRoutesFromElements( @@ -56,6 +60,10 @@ export const router = createBrowserRouter( path='/profile/resume/create' element={} /> + } + /> {/* Layout이 적용되지 않는 라우트 */} From bf45eeecc9f67363b6233c473f9afacc3b6b2f03 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Thu, 20 Mar 2025 16:19:19 +0900 Subject: [PATCH 40/60] =?UTF-8?q?fix:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #23 --- src/components/profile/applications/ApplicationsTabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/profile/applications/ApplicationsTabs.tsx b/src/components/profile/applications/ApplicationsTabs.tsx index cd3d10f..a594166 100644 --- a/src/components/profile/applications/ApplicationsTabs.tsx +++ b/src/components/profile/applications/ApplicationsTabs.tsx @@ -55,7 +55,7 @@ export default function ApplicationsTabs({ +
+ + {isShow && } +
); } diff --git a/src/components/common/header/LanguageSwitcher.tsx b/src/components/common/header/LanguageSwitcher.tsx new file mode 100644 index 0000000..4198a9e --- /dev/null +++ b/src/components/common/header/LanguageSwitcher.tsx @@ -0,0 +1,39 @@ +import { useTranslation } from 'react-i18next'; +import styles from './languageSwitcher.module.scss'; +import { Dispatch, SetStateAction } from 'react'; + +const options = [ + { + label: '한국어', + value: 'ko', + }, + { + label: 'English', + value: 'en', + }, +]; + +const LanguageSwitcher = ({ + setIsShow, +}: { + setIsShow: Dispatch>; +}) => { + const { i18n } = useTranslation(); + + const handleChange = (value: string) => { + i18n.changeLanguage(value); + setIsShow(false); + }; + + return ( +
+ {options.map((option: { label: string; value: string }) => ( +
handleChange(option.value)}> + {option.label} +
+ ))} +
+ ); +}; + +export default LanguageSwitcher; diff --git a/src/components/common/header/languageButton.module.scss b/src/components/common/header/languageButton.module.scss index 6952db1..b0e0385 100644 --- a/src/components/common/header/languageButton.module.scss +++ b/src/components/common/header/languageButton.module.scss @@ -1,3 +1,7 @@ +.wrapper { + position: relative; +} + .languageButton { background: none; border: none; diff --git a/src/components/common/header/languageSwitcher.module.scss b/src/components/common/header/languageSwitcher.module.scss new file mode 100644 index 0000000..60ccc3f --- /dev/null +++ b/src/components/common/header/languageSwitcher.module.scss @@ -0,0 +1,20 @@ +.wrapper { + position: absolute; + width: 80px; + left: -30px; + background-color: #fff; + border: 1px solid var(--color-gray-200); + border-radius: 5px; +} + +.item { + width: 100%; + padding: 0.125rem; + font-size: 1rem; + text-align: center; + cursor: pointer; + + &:hover { + background-color: var(--color-gray-50); + } +} diff --git a/src/components/main/Banner.tsx b/src/components/main/Banner.tsx index a9d7c07..f754486 100644 --- a/src/components/main/Banner.tsx +++ b/src/components/main/Banner.tsx @@ -1,28 +1,28 @@ import { Link } from 'react-router-dom'; import styles from './banner.module.scss'; import { ChevronRight } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; export default function Banner() { + const { t } = useTranslation('common'); + return (

- 외국인을 위한{' '} - 취업 플랫폼 + {t('banner_title1')}{' '} + {t('banner_title2')}

-

- 한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 - 서비스를 제공합니다. -

+

{t('banner_subtitle')}

- + 채용정보 보기 회원가입 @@ -31,8 +31,8 @@ export default function Banner() {
외국인 취업 지원
diff --git a/src/main.tsx b/src/main.tsx index 444c88e..f1cc26c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,8 +3,10 @@ import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import './global.css'; +import '../i18n/i18n.ts'; + createRoot(document.getElementById('root')!).render( - + , ); diff --git a/translations/translations.csv b/translations/translations.csv index d081e7f..89f49ba 100644 --- a/translations/translations.csv +++ b/translations/translations.csv @@ -1,10 +1,10 @@ key,ko,en -for foreigners,외국인을 위한,for foreigners -recruitment platform,채용 플랫폼,recruitment platform -It provides customized employment information and services for foreigners looking for a job in Korea,한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 서비스를 제공합니다,It provides customized employment information and services for foreigners looking for a job in Korea -View Recruitment,채용정보 보기,View Recruitment -Sign up,회원가입,Sign up -Login,로그인,Login, -Search for job postings,채용 공고 검색하기,Search for job postings -Recruitment,채용정보,recruitment -Companies,기업정보,Companies \ No newline at end of file +banner_title1,외국인을 위한,for foreigners +banner_title2,채용 플랫폼,recruitment platform +banner_subtitle,한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 서비스를 제공합니다,It provides customized employment information and services for foreigners looking for a job in Korea +viewRecruitment,채용정보 보기,View Recruitment +signUp,회원가입,Sign up +login,로그인,Login +searchRecruitment,채용 공고 검색하기,Search for job postings +recruitmentInfo,채용정보,recruitment +companiesInfo,기업정보,Companies \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 689287a..1056cba 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -5,6 +5,8 @@ "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", + "esModuleInterop": true, + "resolveJsonModule": true, "skipLibCheck": true, /* Bundler mode */ From 88f84e76a60bc645a861c6fb7aad83bdcf35f461 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Fri, 28 Mar 2025 18:54:57 +0900 Subject: [PATCH 54/60] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A7=81=ED=81=AC=20/register=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index 91266ac..474bd38 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -32,7 +32,7 @@ export default function Header() { - +
From 89c824e175f8e51b6d41d7fa50706fb7cc676d3c Mon Sep 17 00:00:00 2001 From: oko_jin Date: Fri, 28 Mar 2025 19:09:43 +0900 Subject: [PATCH 55/60] =?UTF-8?q?feat:=20=ED=97=A4=EB=8D=94=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B0=B0=EB=84=88=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #25 --- locales/en/common.json | 4 +++- locales/ko/common.json | 4 +++- src/components/common/header/Header.tsx | 9 ++++++--- src/components/common/header/SearchForm.tsx | 9 ++++++--- src/components/main/Banner.tsx | 4 ++-- src/lib/constants/navItems.ts | 8 ++++---- translations/translations.csv | 4 +++- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/locales/en/common.json b/locales/en/common.json index 57819ea..3cab4ca 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -7,5 +7,7 @@ "login": "Login", "searchRecruitment": "Search for job postings", "recruitmentInfo": "recruitment", - "companiesInfo": "Companies" + "companiesInfo": "Companies", + "nearBy": "Surrounding", + "community": "Community" } \ No newline at end of file diff --git a/locales/ko/common.json b/locales/ko/common.json index 1d0aea7..a7357e2 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -7,5 +7,7 @@ "login": "로그인", "searchRecruitment": "채용 공고 검색하기", "recruitmentInfo": "채용정보", - "companiesInfo": "기업정보" + "companiesInfo": "기업정보", + "nearBy": "주변기업찾기", + "community": "커뮤니티" } \ No newline at end of file diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index 474bd38..add2470 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -5,8 +5,11 @@ import SearchForm from './SearchForm'; import LanguageButton from './LanguageButton'; import Button from '../button/Button'; import { title as Logo } from '@/lib/constants/serviceName'; +import { useTranslation } from 'react-i18next'; export default function Header() { + const { t } = useTranslation('common'); + return (
@@ -20,7 +23,7 @@ export default function Header() { to={link} className={({ isActive }) => (isActive ? styles.active : '')} > - {name} + {t(name)} ))} @@ -30,10 +33,10 @@ export default function Header() { - + - +
diff --git a/src/components/common/header/SearchForm.tsx b/src/components/common/header/SearchForm.tsx index f628fa6..e31477b 100644 --- a/src/components/common/header/SearchForm.tsx +++ b/src/components/common/header/SearchForm.tsx @@ -1,13 +1,16 @@ import { Search } from 'lucide-react'; import styles from './searchForm.module.scss'; +import { useTranslation } from 'react-i18next'; export default function SearchForm() { + const { t } = useTranslation('common'); + return (
- - +
); } diff --git a/src/components/main/Banner.tsx b/src/components/main/Banner.tsx index f754486..6484935 100644 --- a/src/components/main/Banner.tsx +++ b/src/components/main/Banner.tsx @@ -18,14 +18,14 @@ export default function Banner() {

{t('banner_subtitle')}

- 채용정보 보기 + {t('viewRecruitment')} - 회원가입 + {t('signUp')}
diff --git a/src/lib/constants/navItems.ts b/src/lib/constants/navItems.ts index 974ae86..a07505f 100644 --- a/src/lib/constants/navItems.ts +++ b/src/lib/constants/navItems.ts @@ -1,22 +1,22 @@ export const navItems = [ { id: 1, - name: '채용정보', + name: 'recruitmentInfo', link: './jobs', }, { id: 2, - name: '기업정보', + name: 'companiesInfo', link: './companies', }, { id: 3, - name: '주변기업찾기', + name: 'nearBy', link: './nearby-companies', }, { id: 4, - name: '커뮤니티', + name: 'community', link: './community', }, ]; diff --git a/translations/translations.csv b/translations/translations.csv index 89f49ba..d9661f9 100644 --- a/translations/translations.csv +++ b/translations/translations.csv @@ -7,4 +7,6 @@ signUp,회원가입,Sign up login,로그인,Login searchRecruitment,채용 공고 검색하기,Search for job postings recruitmentInfo,채용정보,recruitment -companiesInfo,기업정보,Companies \ No newline at end of file +companiesInfo,기업정보,Companies +nearBy,주변기업찾기,Surrounding +community,커뮤니티,Community \ No newline at end of file From 0e65ebddacafb2e8ad021403305e688011d2cb2d Mon Sep 17 00:00:00 2001 From: oko_jin Date: Fri, 28 Mar 2025 19:14:04 +0900 Subject: [PATCH 56/60] =?UTF-8?q?fix:=20locales=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #25 --- .github/workflows/update-translations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-translations.yml b/.github/workflows/update-translations.yml index 60ed56f..7522537 100644 --- a/.github/workflows/update-translations.yml +++ b/.github/workflows/update-translations.yml @@ -31,5 +31,5 @@ jobs: run: | git config --global user.name "github-actions" git config --global user.email "github-actions@github.com" - git add public/locales + git add locales git diff --cached --quiet || (git commit -m "chore: update translations" && git push) From 206ee9619c1064a0dd7876cd40c7221dd67473e6 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Fri, 28 Mar 2025 19:17:01 +0900 Subject: [PATCH 57/60] =?UTF-8?q?fix:=20=EB=A9=94=EC=9D=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20csv=20key=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #25 --- src/components/main/Banner.tsx | 6 +++--- translations/translations.csv | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/main/Banner.tsx b/src/components/main/Banner.tsx index 6484935..c8efb8d 100644 --- a/src/components/main/Banner.tsx +++ b/src/components/main/Banner.tsx @@ -12,10 +12,10 @@ export default function Banner() {

- {t('banner_title1')}{' '} - {t('banner_title2')} + {t('mainBannerTitle1')}{' '} + {t('mainBannerTitle2')}

-

{t('banner_subtitle')}

+

{t('mainBannerSubtitle')}

{t('viewRecruitment')} diff --git a/translations/translations.csv b/translations/translations.csv index d9661f9..a32564c 100644 --- a/translations/translations.csv +++ b/translations/translations.csv @@ -1,12 +1,12 @@ key,ko,en -banner_title1,외국인을 위한,for foreigners -banner_title2,채용 플랫폼,recruitment platform -banner_subtitle,한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 서비스를 제공합니다,It provides customized employment information and services for foreigners looking for a job in Korea -viewRecruitment,채용정보 보기,View Recruitment signUp,회원가입,Sign up login,로그인,Login searchRecruitment,채용 공고 검색하기,Search for job postings recruitmentInfo,채용정보,recruitment companiesInfo,기업정보,Companies nearBy,주변기업찾기,Surrounding -community,커뮤니티,Community \ No newline at end of file +community,커뮤니티,Community +mainBannerTitle1,외국인을 위한,for foreigners +mainBannerTitle2,채용 플랫폼,recruitment platform +mainBannerSubtitle,한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 서비스를 제공합니다,It provides customized employment information and services for foreigners looking for a job in Korea +viewRecruitment,채용정보 보기,View Recruitment \ No newline at end of file From 2f484ae49fc90471d0613ad96b8499ac814d84cb Mon Sep 17 00:00:00 2001 From: oko_jin Date: Fri, 28 Mar 2025 19:21:03 +0900 Subject: [PATCH 58/60] =?UTF-8?q?fix:=20=EA=B9=83=ED=97=88=EB=B8=8C=20?= =?UTF-8?q?=EB=B4=87=20=EA=B6=8C=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #25 --- .github/workflows/update-translations.yml | 2 ++ translations/translations.csv | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-translations.yml b/.github/workflows/update-translations.yml index 7522537..05a4742 100644 --- a/.github/workflows/update-translations.yml +++ b/.github/workflows/update-translations.yml @@ -9,6 +9,8 @@ on: jobs: update-translations: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/translations/translations.csv b/translations/translations.csv index a32564c..4e4d170 100644 --- a/translations/translations.csv +++ b/translations/translations.csv @@ -6,7 +6,7 @@ recruitmentInfo,채용정보,recruitment companiesInfo,기업정보,Companies nearBy,주변기업찾기,Surrounding community,커뮤니티,Community -mainBannerTitle1,외국인을 위한,for foreigners +mainBannerTitle1,외국인을 위한,For foreigners mainBannerTitle2,채용 플랫폼,recruitment platform mainBannerSubtitle,한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 서비스를 제공합니다,It provides customized employment information and services for foreigners looking for a job in Korea viewRecruitment,채용정보 보기,View Recruitment \ No newline at end of file From 2a8d0d5da81797be28947adbfe3dbb414b4176f9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 28 Mar 2025 10:21:23 +0000 Subject: [PATCH 59/60] chore: update translations --- locales/en/common.json | 10 +++++----- locales/ko/common.json | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/en/common.json b/locales/en/common.json index 3cab4ca..1314673 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -1,13 +1,13 @@ { - "banner_title1": "for foreigners", - "banner_title2": "recruitment platform", - "banner_subtitle": "It provides customized employment information and services for foreigners looking for a job in Korea", - "viewRecruitment": "View Recruitment", "signUp": "Sign up", "login": "Login", "searchRecruitment": "Search for job postings", "recruitmentInfo": "recruitment", "companiesInfo": "Companies", "nearBy": "Surrounding", - "community": "Community" + "community": "Community", + "mainBannerTitle1": "For foreigners", + "mainBannerTitle2": "recruitment platform", + "mainBannerSubtitle": "It provides customized employment information and services for foreigners looking for a job in Korea", + "viewRecruitment": "View Recruitment" } \ No newline at end of file diff --git a/locales/ko/common.json b/locales/ko/common.json index a7357e2..29da76e 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -1,13 +1,13 @@ { - "banner_title1": "외국인을 위한", - "banner_title2": "채용 플랫폼", - "banner_subtitle": "한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 서비스를 제공합니다", - "viewRecruitment": "채용정보 보기", "signUp": "회원가입", "login": "로그인", "searchRecruitment": "채용 공고 검색하기", "recruitmentInfo": "채용정보", "companiesInfo": "기업정보", "nearBy": "주변기업찾기", - "community": "커뮤니티" + "community": "커뮤니티", + "mainBannerTitle1": "외국인을 위한", + "mainBannerTitle2": "채용 플랫폼", + "mainBannerSubtitle": "한국에서 일자리를 찾고 있는 외국인을 위한 맞춤형 취업 정보와 서비스를 제공합니다", + "viewRecruitment": "채용정보 보기" } \ No newline at end of file From 9795e47e05364c89cf1531eefe2bdaeb4c8732a2 Mon Sep 17 00:00:00 2001 From: oko_jin Date: Fri, 28 Mar 2025 22:03:20 +0900 Subject: [PATCH 60/60] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=B2=84=ED=8A=BC=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index add2470..01bc34a 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -36,7 +36,7 @@ export default function Header() { - +