-
+
+ {!isAdmin &&
}
))
)}
-
-
-
-
-
-
-
+ {isAdmin && (
+ <>
+
+
+
+
+
+
+
+ >
+ )}
);
}
diff --git a/src/app/intro/components/basicblock.tsx b/src/app/intro/components/basicblock.tsx
index 1fa997da..98cc5b80 100644
--- a/src/app/intro/components/basicblock.tsx
+++ b/src/app/intro/components/basicblock.tsx
@@ -31,6 +31,7 @@ interface Block {
dragStart: (position: number) => void;
dragEnter: (position: number) => void;
drop: () => void;
+ isAdmin: boolean;
}
export default function BasicBlock({
id,
@@ -52,6 +53,7 @@ export default function BasicBlock({
dragStart,
dragEnter,
drop,
+ isAdmin,
}: Block) {
const [isOpen, setIsOpen] = useState(false);
@@ -104,7 +106,7 @@ export default function BasicBlock({
case 1:
return
;
case 2:
- return
;
+ return
;
case 3:
return (
@@ -112,11 +114,13 @@ export default function BasicBlock({
case 4:
return
;
case 5:
- return
;
+ return (
+
+ );
case 6:
return
;
case 7:
- return
;
+ return
;
default:
return <>>;
}
@@ -163,38 +167,43 @@ export default function BasicBlock({
onDragOver={(e) => e.preventDefault()}
>
-
-
-
-
-
-
-
-
-
+ {isAdmin && (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
@@ -209,36 +218,40 @@ export default function BasicBlock({
{setTitle(type)}
-
-
-
-
+ {isAdmin ? (
+
+
+
+
+ ) : (
+ <>>
+ )}
{renderComponent(type)}
diff --git a/src/app/join/components/animated-text.tsx b/src/app/join/components/animated-text.tsx
new file mode 100644
index 00000000..833c34f3
--- /dev/null
+++ b/src/app/join/components/animated-text.tsx
@@ -0,0 +1,38 @@
+import { useEffect, useState } from "react";
+import { twMerge } from "tailwind-merge";
+
+function AnimatedText({
+ isVisible,
+ children,
+}: {
+ isVisible: boolean;
+ children: React.ReactNode;
+}) {
+ const [isRemoving, setIsRemoving] = useState(false);
+ const [showText, setShowText] = useState(isVisible);
+
+ useEffect(() => {
+ if (isVisible) {
+ setShowText(true);
+ setIsRemoving(false);
+ } else if (showText) {
+ setIsRemoving(true);
+ setTimeout(() => setShowText(false), 600); // 애니메이션 시간 후 DOM에서 제거
+ }
+ }, [isVisible, showText]);
+
+ if (!showText) return null;
+
+ return (
+
+ {children}
+
+ );
+}
+
+export default AnimatedText;
diff --git a/src/app/join/page.tsx b/src/app/join/page.tsx
index f647127b..5e6c3e10 100644
--- a/src/app/join/page.tsx
+++ b/src/app/join/page.tsx
@@ -1,14 +1,251 @@
-import { Metadata } from "next";
+"use client";
-//metadata
-export const metadata: Metadata = {
- title: "Join",
-};
+import FormInput from "@app/admin/(block)/components/form-input";
+import { ClientRoute } from "@config/route";
+import Image from "next/image";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { twMerge } from "tailwind-merge";
+import AnimatedText from "./components/animated-text";
export default function Join() {
+ const [userId, setUserId] = useState("");
+ const [name, setName] = useState("");
+ const [password, setPassword] = useState("");
+ const [passwordConfirm, setPasswordConfirm] = useState("");
+ const [email, setEmail] = useState("");
+
+ const [isUserIdFocused, setIsUserIdFocused] = useState(false);
+ const [isNameFocused, setIsNameFocused] = useState(false);
+ const [isPasswordFocused, setIsPasswordFocused] = useState(false);
+ const [isPasswordConfirmFocused, setIsPasswordConfirmFocused] =
+ useState(false);
+ const [isEmailFocused, setIsEmailFocused] = useState(false);
+
+ const [isUserIdTouched, setIsUserIdTouched] = useState(false);
+ const [isNameTouched, setIsNameTouched] = useState(false);
+ const [isPasswordTouched, setIsPasswordTouched] = useState(false);
+ const [isPasswordConfirmTouched, setIsPasswordConfirmTouched] =
+ useState(false);
+ const [isEmailTouched, setIsEmailTouched] = useState(false);
+
+ const isDisabled =
+ !userId ||
+ !name ||
+ !password ||
+ !passwordConfirm ||
+ !email ||
+ password !== passwordConfirm;
+
+ const router = useRouter();
+
+ async function handleJoin(e: React.FormEvent
) {
+ e.preventDefault();
+ if (isDisabled || password !== passwordConfirm) return;
+
+ try {
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_URL}/api/user/add`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ name: name,
+ userId: userId,
+ password: password,
+ email: email,
+ }),
+ },
+ );
+
+ const infor = await response.json();
+ if (response.ok) {
+ alert("회원가입 성공!");
+ router.push(ClientRoute.LOGIN as string);
+ } else {
+ alert(
+ "회원가입 실패: " +
+ (infor.message || "알 수 없는 오류가 발생했습니다."),
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ alert("회원가입 중 오류가 발생했습니다. 다시 시도해 주세요.");
+ }
+ }
+
return (
-
-
회원가입 페이지
+
+
+
IN MY LINK 회원가입 페이지입니다!
+
필수 정보를 입력해 주세요.
+
+
+
+
+
IN MY LINK 회원가입
+
+ 이미 가입하셨나요?
+
+ 로그인 하기
+
+
+
+
+
);
}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index 372ea7f9..a34f43ea 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -1,16 +1,26 @@
"use client";
-import { useState } from "react";
-import { useRouter } from "next/navigation";
+import FormInput from "@app/admin/(block)/components/form-input";
import { ClientRoute } from "@config/route";
+import Image from "next/image";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { twMerge } from "tailwind-merge";
export default function Login() {
const [userId, setUserId] = useState("");
const [password, setPassword] = useState("");
+ const [isUserIdFocused, setIsUserIdFocused] = useState(false);
+ const [isPasswordFocused, setIsPasswordFocused] = useState(false);
+
const router = useRouter();
+ const isDisabled = !userId || !password;
+
async function handleLogin(e: React.FormEvent
) {
e.preventDefault();
+ if (isDisabled) return;
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/login`,
@@ -42,27 +52,58 @@ export default function Login() {
}
return (
-