diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..e0970ed
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,9 @@
+stages:
+ -build
+
+build:
+ stage: build
+ image: node:25.1.0
+ script:
+ - npm run build
+ - ls
diff --git a/package.json b/package.json
index 4ea66b0..27c6795 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
- "build": "tsc -b && vite build",
+ "build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
diff --git a/src/data/form/emailVerification.schema.ts b/src/data/form/emailVerification.schema.ts
new file mode 100644
index 0000000..29993bb
--- /dev/null
+++ b/src/data/form/emailVerification.schema.ts
@@ -0,0 +1,9 @@
+import * as z from 'zod';
+
+export const EmailVerificationSchema = z.object({
+ email: z
+ .email()
+ , verificationCode: z
+ .string()
+ .length(6, "이메일 인증 번호 6자리를 입력해주십시오.")
+});
\ No newline at end of file
diff --git a/src/data/form/index.ts b/src/data/form/index.ts
index 334dd49..02e4965 100644
--- a/src/data/form/index.ts
+++ b/src/data/form/index.ts
@@ -1,3 +1,4 @@
export { SignUpSchema } from './signup.schema';
export { LoginSchema } from './login.schema';
-export { ResetPasswordSchema } from './resetPassword.schema';
\ No newline at end of file
+export { ResetPasswordSchema } from './resetPassword.schema';
+export { EmailVerificationSchema } from './emailVerification.schema';
\ No newline at end of file
diff --git a/src/data/form/login.schema.ts b/src/data/form/login.schema.ts
index d380697..87c73f3 100644
--- a/src/data/form/login.schema.ts
+++ b/src/data/form/login.schema.ts
@@ -1,13 +1,11 @@
import * as z from 'zod';
-import { zodResolver } from '@/hookform/resolvers/zod';
-import { useForm } from 'react-hook-form';
-const LoginSchema = z.object({
+export const LoginSchema = z.object({
email: z
.email()
, password: z
- .string()
+ .string()
.min(8, "비밀번호는 8-12 자리여야 합니다.")
.max(12, "비밀번호는 8-12 자리여야 합니다.")
- .regex(\^[a-z](?=.*[0-9])(?=.*[!@#$]).*$\)
+ .regex(/^[a-z](?=.*[0-9])(?=.*[!@#$]).*$/, "비밀번호는 영소문자로 시작하여 숫자, 특수문자(!@#$)를 한 개 이상 포함하여야 합니다.")
});
\ No newline at end of file
diff --git a/src/data/form/resetPassword.schema.ts b/src/data/form/resetPassword.schema.ts
index ad100e5..73360b3 100644
--- a/src/data/form/resetPassword.schema.ts
+++ b/src/data/form/resetPassword.schema.ts
@@ -1,8 +1,6 @@
import * as z from 'zod';
-import { zodResolver } from '@/hookform/resolvers/zod';
-import { useForm } from 'react-hook-form';
-const ResetPasswordSchema = z.object({
+export const ResetPasswordSchema = z.object({
email: z
.email()
});
\ No newline at end of file
diff --git a/src/data/form/signup.schema.ts b/src/data/form/signup.schema.ts
index 66c1819..b3fb6df 100644
--- a/src/data/form/signup.schema.ts
+++ b/src/data/form/signup.schema.ts
@@ -1,19 +1,20 @@
import * as z from 'zod';
-import { zodResolver } from '@/hookform/resolvers/zod';
-import { useForm } from 'react-hook-form';
-const SignUpSchema = z.object({
+export const SignUpSchema = z.object({
email: z
- .email()
+ .string()
+ .min(1, "이메일을 입력해주십시오.")
, password: z
- .string()
+ .string()
.min(8, "비밀번호는 8-12 자리여야 합니다.")
.max(12, "비밀번호는 8-12 자리여야 합니다.")
- .regex(\^[a-z](?=.*[0-9])(?=.*[!@#$]).*$\, "영문 소문자로 시작하고 숫자와 특수문자(!@#$)를 포함해야 합니다.")
+ .regex(/^[a-z](?=.*[0-9])(?=.*[!@#$]).*$/, "영문 소문자로 시작하고 숫자와 특수문자(!@#$)를 포함해야 합니다.")
, name: z
.string()
+ .min(1, "이름을 입력해주시십시오.")
, nickname: z
.string()
+ .min(1, "닉네임을 입력해주십시오.")
, passwordConfirm: z
.string()
})
diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx
new file mode 100644
index 0000000..be275c9
--- /dev/null
+++ b/src/hooks/useModal.tsx
@@ -0,0 +1,40 @@
+import { useState, type ReactNode } from "react";
+import { Dialog, DialogTrigger, DialogTitle, DialogContent, DialogHeader, DialogFooter} from "@/components/ui/dialog";
+import { type DialogProps } from "@radix-ui/react-dialog";
+
+export function useModal() {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const open = () => setIsOpen(true);
+ const close = () => setIsOpen(false);
+
+ type ModalProps = DialogProps & {
+ children: ReactNode;
+ trigger: ReactNode;
+ title: string;
+ footer: ReactNode;
+ };
+
+ const Modal = ({ children, trigger, title, footer, ...props }: ModalProps) => (
+
+ )
+
+ return { isOpen, open, close, Modal };
+}
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index 0e53474..c913384 100644
--- a/src/index.css
+++ b/src/index.css
@@ -122,3 +122,15 @@
html, body, #root {
height: 100%;
}
+
+/* Chrome, Safari, Edge */
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+/* Firefox */
+input[type="number"] {
+ -moz-appearance: textfield;
+}
\ No newline at end of file
diff --git a/src/ui/component/modal/EmailVerificationModal.tsx b/src/ui/component/modal/EmailVerificationModal.tsx
new file mode 100644
index 0000000..35d3cd0
--- /dev/null
+++ b/src/ui/component/modal/EmailVerificationModal.tsx
@@ -0,0 +1,98 @@
+import { Button } from "@/components/ui/button";
+import { useModal } from "@/hooks/useModal";
+import { useState, type ReactNode } from "react";
+import { EmailVerificationSchema } from "@/data/form";
+import { Controller, useForm } from "react-hook-form";
+import z from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
+import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
+
+type EmailVerificationModalProps = {
+ trigger: ReactNode;
+ email: string;
+ handler: () => void;
+}
+export default function EmailVerificationModal(
+ { trigger, email, handler } : EmailVerificationModalProps
+) {
+ const [isVerifying, setIsVerifying] = useState(false);
+ const { open, close, Modal } = useModal();
+
+ const emailVerificationForm = useForm>({
+ resolver: zodResolver(EmailVerificationSchema),
+ defaultValues: {
+ email: email,
+ verificationCode: ""
+ }
+ });
+
+ const handleOnOpenChange = (isOpen: boolean) => {
+ if (!isVerifying) {
+ if (isOpen) {
+ open();
+ } else {
+ emailVerificationForm.clearErrors();
+ close();
+ }
+ }
+ }
+
+ const verifyCode = () => {
+ console.log(emailVerificationForm.getValues("verificationCode"));
+ }
+
+ const handleOnOTPComplete = (otp: string) => {
+ setIsVerifying(true);
+ emailVerificationForm.setValue("verificationCode", otp, { shouldValidate: true });
+ emailVerificationForm.handleSubmit(verifyCode)();
+ }
+
+ return (
+
+ (
+
+
+ 인증 번호
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ />
+
+
+ }
+ trigger={trigger}
+ title="이메일 인증"
+ footer={null}
+ />);
+}
\ No newline at end of file
diff --git a/src/ui/page/login/LoginPage.tsx b/src/ui/page/login/LoginPage.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/ui/page/signup/SignUpPage.tsx b/src/ui/page/signup/SignUpPage.tsx
index 8277097..e05dd5b 100644
--- a/src/ui/page/signup/SignUpPage.tsx
+++ b/src/ui/page/signup/SignUpPage.tsx
@@ -5,23 +5,165 @@ import { Input } from '@/components/ui/input';
import {
Card,
CardContent,
- CardDescription,
CardHeader,
- CardTitle
+ CardFooter
} from '@/components/ui/card';
-import { Form, FormField } from '@/components/ui/form';
+import { Field, FieldError, FieldGroup, FieldLabel, FieldLegend } from '@/components/ui/field';
+import { SignUpSchema } from '@/data/form';
+import { Controller, useForm } from 'react-hook-form';
+import * as z from 'zod';
+import { zodResolver } from '@hookform/resolvers/zod';
+import EmailVerificationModal from '@/ui/component/modal/EmailVerificationModal';
export default function SignUpPage() {
- const [open, setOpen] = useState(false);
+ const [isCheckedEmailDuplication, setIsCheckdEmailDupliation] = useState(false);
+ const [isEmailVerificated, setIsEmailVerificated] = useState(false);
+ const [duplicationCheckedEmail, setDuplicationCheckedEmail] = useState("");
+
+ const signUpForm = useForm>({
+ resolver: zodResolver(SignUpSchema),
+ defaultValues: {
+ email: "",
+ password: "",
+ passwordConfirm: "",
+ name: "",
+ nickname: "",
+ }
+ });
+
+ const handleOnChangeEmail = (e: React.ChangeEvent) => {
+ setIsCheckdEmailDupliation(
+ e.currentTarget.value === duplicationCheckedEmail
+ );
+ // setIsEmailVerificated(
+ // e.currentTarget.value === duplicationCheckedEmail
+ // );
+ }
+
+ const handleEmailDuplicationCheckButtonClick = () => {
+ console.log(signUpForm.getValues("email"));
+ setDuplicationCheckedEmail(signUpForm.getValues("email"));
+ setIsCheckdEmailDupliation(true);
+ }
+
+ const handleOnSubmitSignUpForm = () => {
+ if (!isCheckedEmailDuplication) {
+ signUpForm.setError("email", { message: "이메일 중복 확인이 필요합니다." });
+ }
+ // if (!isEmailVerificated) {
+ // signUpForm.setError("email", { message: "이메일 인증이 완료되지 않았습니다." });
+ // }
+ }
+
return (
-
-
- 로그인
-
+
+ 회원가입
-
+
+
+
+ 회원가입
+
+ }
+ email={duplicationCheckedEmail}
+ handler={() => {}}
+ />
+
+
);