로그인 페이지 완성

This commit is contained in:
2025-11-18 01:37:56 +09:00
parent 78b7cc2d3c
commit 851fec4096
5 changed files with 188 additions and 628 deletions

View File

@@ -0,0 +1,46 @@
"use client";
import React from "react";
type LoginErrorModalProps = {
open: boolean;
onClose: () => void;
};
export default function LoginErrorModal({ open, onClose }: LoginErrorModalProps) {
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<button
type="button"
aria-label="닫기"
className="absolute inset-0 bg-black/40 cursor-default"
onClick={onClose}
/>
<div
role="dialog"
aria-modal="true"
aria-labelledby="login-error-title"
className="relative bg-white box-border flex flex-col items-stretch justify-start p-6 rounded-[8px] min-w-[500px] max-w-[calc(100%-48px)]"
>
<div className="text-[18px] leading-normal font-semibold text-neutral-700 mb-8" id="login-error-title">
.
<br />
.
</div>
<div className="flex items-center justify-end gap-[8px]">
<button
type="button"
onClick={onClose}
className="h-[40px] min-w-20 px-[12px] rounded-[8px] bg-active-button text-white text-[16px] font-semibold cursor-pointer"
>
</button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,53 @@
"use client";
import React from "react";
import { useState } from "react";
type LoginOptionProps = {
onClick?: () => void;
className?: string;
loginErrorModalEnabled?: boolean;
setLoginErrorModalEnabled?: (enabled: boolean) => void;
};
export default function LoginOption({
className,
loginErrorModalEnabled,
setLoginErrorModalEnabled,
}: LoginOptionProps) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className={`fixed bottom-2 right-2 bg-red-400 cursor-pointer rounded-full w-[40px] h-[40px] shadow-xl z-100`}
>
</button>
{ isOpen && (
<div className="fixed inset-0 flex items-center justify-center z-50">
<div className="w-[500px] h-[600px] flex bg-white/80 p-10 border rounded-lg">
<ul className="flex flex-col gap-4">
<li className="flex items-center justify-between">
<p className="mr-4">login error modal</p>
<button
type="button"
aria-label="login error modal 토글"
aria-pressed={!!loginErrorModalEnabled}
onClick={() => setLoginErrorModalEnabled?.(!loginErrorModalEnabled)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${loginErrorModalEnabled ? 'bg-blue-600' : 'bg-gray-300'}`}
>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white transition ${loginErrorModalEnabled ? 'translate-x-5' : 'translate-x-1'}`}
/>
</button>
</li>
</ul>
</div>
</div>
)}
</div>
);
}

View File

@@ -3,25 +3,48 @@
import { useState } from "react"; import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import MainLogo from "@/app/svgs/mainlogosvg" import MainLogo from "@/app/svgs/mainlogosvg"
import LoginCheckboxActiveSvg from "@/app/svgs/logincheckboxactivesvg";
import LoginCheckboxInactiveSvg from "@/app/svgs/logincheckboxinactivesvg";
import LoginInputSvg from "@/app/svgs/inputformx";
import LoginErrorModal from "./LoginErrorModal";
import LoginOption from "@/app/login/loginoption";
export default function LoginPage() { export default function LoginPage() {
const [userId, setUserId] = useState(""); const [userId, setUserId] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [rememberId, setRememberId] = useState(false); const [rememberId, setRememberId] = useState(false);
const [autoLogin, setAutoLogin] = useState(false); const [autoLogin, setAutoLogin] = useState(false);
const [isUserIdFocused, setIsUserIdFocused] = useState(false);
const [isPasswordFocused, setIsPasswordFocused] = useState(false);
const [isLoginErrorOpen, setIsLoginErrorOpen] = useState(false);
const [idError, setIdError] = useState("");
const [passwordError, setPasswordError] = useState("");
function handleSubmit(e: React.FormEvent<HTMLFormElement>) { function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault(); e.preventDefault();
/* todo */ // 실제 로그인 API 연동 전까지는 실패 모달을 노출합니다.
console.log({ userId, password, rememberId, autoLogin }); // API 연동 시 결과에 따라 성공/실패 분기에서 setIsLoginErrorOpen(true) 호출로 교체하세요.
// if (userId.trim().length > 0 && password.trim().length > 0) {
// setIsLoginErrorOpen(true);
// }
} }
return ( return (
<div className="min-h-screen w-full flex flex-col items-center justify-between"> <div className="min-h-screen w-full flex flex-col items-center justify-between">
<div></div>
<LoginErrorModal
open={isLoginErrorOpen}
onClose={() => setIsLoginErrorOpen(false)}
/>
<LoginOption
onClick={() => setIsLoginErrorOpen(true)}
loginErrorModalEnabled={isLoginErrorOpen}
setLoginErrorModalEnabled={setIsLoginErrorOpen}
/>
<div className="rounded-xl bg-white max-w-[560px] px-[40px] w-full"> <div className="rounded-xl bg-white max-w-[560px] px-[40px] w-full">
{/* 로고 영역 */} {/* 로고 영역 */}
<div className="my-10 flex flex-col items-center"> <div className="my-15 flex flex-col items-center">
<div className="mb-[7px]"> <div className="mb-[7px]">
<MainLogo/> <MainLogo/>
</div> </div>
@@ -31,10 +54,10 @@ export default function LoginPage() {
</div> </div>
{/* 폼 */} {/* 폼 */}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-4"> <div className="space-y-4">
{/* 아이디 */} {/* 아이디 */}
<div> <div className="relative">
<label htmlFor="userId" className="sr-only"> <label htmlFor="userId" className="sr-only">
</label> </label>
@@ -43,17 +66,33 @@ export default function LoginPage() {
name="userId" name="userId"
value={userId} value={userId}
onChange={(e) => setUserId(e.target.value)} onChange={(e) => setUserId(e.target.value)}
onFocus={() => setIsUserIdFocused(true)}
onBlur={() => setIsUserIdFocused(false)}
placeholder="아이디 (이메일)" placeholder="아이디 (이메일)"
className=" className="
h-[56px] px-[12px] py-[7px] w-full rounded-[8px] border border-neutral-40 h-[56px] px-[12px] py-[7px] w-full rounded-[8px] border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700 focus:appearance-none focus:border-neutral-700
text-[18px] text-neutral-700 font-normal leading-[150%] placeholder:text-input-placeholder-text text-[18px] text-neutral-700 font-normal leading-[150%] placeholder:text-input-placeholder-text
pr-[40px]
" "
/> />
{userId.trim().length > 0 && isUserIdFocused && (
<button
type="button"
onMouseDown={(e) => {
e.preventDefault();
setUserId("");
}}
aria-label="입력 지우기"
className="absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer"
>
<LoginInputSvg />
</button>
)}
</div> </div>
{/* 비밀번호 */} {/* 비밀번호 */}
<div> <div className="relative">
<label htmlFor="password" className="sr-only"> <label htmlFor="password" className="sr-only">
</label> </label>
@@ -63,27 +102,47 @@ export default function LoginPage() {
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
onFocus={() => setIsPasswordFocused(true)}
onBlur={() => setIsPasswordFocused(false)}
placeholder="비밀번호" placeholder="비밀번호"
className=" className="
h-[56px] px-[12px] py-[7px] rounded-[8px] w-full border border-neutral-40 h-[56px] px-[12px] py-[7px] rounded-[8px] w-full border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700 focus:appearance-none focus:border-neutral-700
text-[18px] text-neutral-700 font-normal leading-[150%] placeholder:text-input-placeholder-text text-[18px] text-neutral-700 font-normal leading-[150%] placeholder:text-input-placeholder-text
pr-[40px]
" "
/> />
{password.trim().length > 0 && isPasswordFocused && (
<button
type="button"
onMouseDown={(e) => {
e.preventDefault();
setPassword("");
}}
aria-label="입력 지우기"
className="absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer"
>
<LoginInputSvg />
</button>
)}
</div> </div>
</div> </div>
{/* 체크박스들 */} {/* 체크박스들 */}
<div className="flex items-center justify-start gap-6"> <div className="flex items-center justify-start gap-6 mb-15">
<label className="flex cursor-pointer select-none items-center gap-2 text-[15px] font-normal text-basic-text"> <label className="flex cursor-pointer select-none items-center gap-2 text-[15px] font-normal text-basic-text">
<input <input
type="checkbox" type="checkbox"
checked={rememberId} checked={rememberId}
onChange={(e) => setRememberId(e.target.checked)} onChange={(e) => setRememberId(e.target.checked)}
className="h-[18px] w-[18px] cursor-pointer rounded border className="sr-only"
accent-input-border-select border-inactive-checkbox"
/> />
{rememberId ? (
<LoginCheckboxActiveSvg />
) : (
<LoginCheckboxInactiveSvg />
)}
</label> </label>
<label className="flex cursor-pointer select-none items-center gap-2 text-[15px] font-normal text-basic-text"> <label className="flex cursor-pointer select-none items-center gap-2 text-[15px] font-normal text-basic-text">
@@ -91,9 +150,13 @@ export default function LoginPage() {
type="checkbox" type="checkbox"
checked={autoLogin} checked={autoLogin}
onChange={(e) => setAutoLogin(e.target.checked)} onChange={(e) => setAutoLogin(e.target.checked)}
className="h-[18px] w-[18px] cursor-pointer rounded border className="sr-only"
accent-input-border-select border-inactive-checkbox"
/> />
{autoLogin ? (
<LoginCheckboxActiveSvg />
) : (
<LoginCheckboxInactiveSvg />
)}
</label> </label>
</div> </div>
@@ -101,16 +164,16 @@ export default function LoginPage() {
{/* 로그인 버튼 */} {/* 로그인 버튼 */}
<button <button
type="submit" type="submit"
className="h-14 w-full rounded-lg text-[16px] font-semibold text-white transition-opacity cursor-pointer bg-inactive-button" className={`h-14 w-full rounded-lg text-[16px] font-semibold text-white transition-opacity cursor-pointer mb-3 ${userId.trim().length > 0 && password.trim().length > 0 ? "bg-active-button" : "bg-inactive-button"}`}
> >
</button> </button>
{/* 하단 링크들 */} {/* 하단 링크들 */}
<div className="flex items-center justify-between text-[15px]"> <div className="flex items-center justify-between text-[15px] leading-[150%] h-[36px]">
<Link <Link
href="/register" href="/register"
className="underline-offset-2 text-basic-text" className="underline-offset-2 text-basic-text font-bold"
> >
</Link> </Link>

View File

@@ -1,613 +0,0 @@
\"use client\";
import { useState } from \"react\";
import Link from \"next/link\";
import MainLogo from \"@/app/svgs/mainlogosvg\";
export default function RegisterPage() {
const [name, setName] = useState(\"\");
const [phone, setPhone] = useState(\"\");
const [email, setEmail] = useState(\"\");
const [password, setPassword] = useState(\"\");
const [confirmPassword, setConfirmPassword] = useState(\"\");
const [gender, setGender] = useState<\"male\" | \"female\" | \"\">(\"\");
const [birth, setBirth] = useState(\"\");
const [agreeAll, setAgreeAll] = useState(false);
const [agreeAge, setAgreeAge] = useState(false);
const [agreeTos, setAgreeTos] = useState(false);
const [agreePrivacy, setAgreePrivacy] = useState(false);
function syncAgreeAll(nextAll: boolean) {
setAgreeAll(nextAll);
setAgreeAge(nextAll);
setAgreeTos(nextAll);
setAgreePrivacy(nextAll);
}
function handleIndividualAgree(next: { age?: boolean; tos?: boolean; privacy?: boolean }) {
const nextAge = next.age ?? agreeAge;
const nextTos = next.tos ?? agreeTos;
const nextPrivacy = next.privacy ?? agreePrivacy;
setAgreeAge(nextAge);
setAgreeTos(nextTos);
setAgreePrivacy(nextPrivacy);
setAgreeAll(nextAge && nextTos && nextPrivacy);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
// TODO: 서버 연동 시 실제 제출 로직으로 교체
console.log({
name,
phone,
email,
password,
confirmPassword,
gender,
birth,
agree: { all: agreeAll, age: agreeAge, tos: agreeTos, privacy: agreePrivacy },
});
}
return (
<div className=\"min-h-screen w-full flex flex-col items-center justify-between\">
<div></div>
<div className=\"rounded-xl bg-white max-w-[480px] w-full\">
{/* 로고/타이틀 */}
<div className=\"mb-10 flex flex-col items-center\">
<div className=\"mb-[7px]\">
<MainLogo />
</div>
<div className=\"text-[24px] font-[700] leading-[150%] text-neutral-700\">회원가입</div>
</div>
<form onSubmit={handleSubmit} className=\"space-y-6\">
<div className=\"space-y-4\">
{/* 이름 */}
<div className=\"space-y-2\">
<label className=\"text-[15px] font-[600] text-[var(--color-basic-text)]\">이름</label>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder=\"이름을 입력해 주세요.\"
className=\"h-[40px] px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40 text-[16px] text-neutral-700 placeholder:text-input-placeholder-text focus:outline-none focus:border-neutral-700\"
/>
</div>
{/* 휴대폰 번호 */}
<div className=\"space-y-2\">
<label className=\"text-[15px] font-[600] text-[var(--color-basic-text)]\">휴대폰 번호</label>
<input
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder=\"-없이 입력해 주세요.\"
className=\"h-[40px] px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40 text-[16px] text-neutral-700 placeholder:text-input-placeholder-text focus:outline-none focus:border-neutral-700\"
/>
</div>
{/* 아이디(이메일) + 인증번호 전송 */}
<div className=\"space-y-2\">
<label className=\"text-[15px] font-[600] text-[var(--color-basic-text)]\">아이디 (이메일)</label>
<div className=\"flex gap-2\">
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder=\"이메일을 입력해 주세요.\"
className=\"h-[40px] px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40 text-[16px] text-neutral-700 placeholder:text-input-placeholder-text focus:outline-none focus:border-neutral-700\"
/>
<button
type=\"button\"
className=\"h-[40px] px-[12px] rounded-[8px] bg-[#f1f3f5] text-[16px] font-[600] text-[#4c5561]\"
>
인증번호 전송
</button>
</div>
</div>
{/* 비밀번호 */}
<div className=\"space-y-2\">
<label className=\"text-[15px] font-[600] text-[var(--color-basic-text)]\">비밀번호</label>
<input
type=\"password\"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder=\"8~16자의 영문/숫자/특수문자를 조합해서 입력해 주세요.\"
className=\"h-[40px] px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40 text-[16px] text-neutral-700 placeholder:text-input-placeholder-text focus:outline-none focus:border-neutral-700\"
/>
</div>
{/* 비밀번호 확인 */}
<div className=\"space-y-2\">
<label className=\"text-[15px] font-[600] text-[var(--color-basic-text)]\">비밀번호 확인</label>
<input
type=\"password\"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder=\"비밀번호를 다시 입력해 주세요.\"
className=\"h-[40px] px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40 text-[16px] text-neutral-700 placeholder:text-input-placeholder-text focus:outline-none focus:border-neutral-700\"
/>
</div>
{/* 성별 */}
<div className=\"space-y-2\">
<label className=\"text-[15px] font-[600] text-[var(--color-basic-text)]\">성별</label>
<div className=\"flex items-center gap-5 h-[40px]\">
<label className=\"flex items-center gap-2 text-[15px] text-neutral-700\">
<input
type=\"radio\"
name=\"gender\"
checked={gender === \"male\"}
onChange={() => setGender(\"male\")}
className=\"h-[18px] w-[18px] cursor-pointer\"
style={{ accentColor: \"var(--color-input-border-select)\" }}
/>
남성
</label>
<label className=\"flex items-center gap-2 text-[15px] text-neutral-700\">
<input
type=\"radio\"
name=\"gender\"
checked={gender === \"female\"}
onChange={() => setGender(\"female\")}
className=\"h-[18px] w-[18px] cursor-pointer\"
style={{ accentColor: \"var(--color-input-border-select)\" }}
/>
여성
</label>
</div>
</div>
{/* 생년월일 */}
<div className=\"space-y-2\">
<label className=\"text-[15px] font-[600] text-[var(--color-basic-text)]\">생년월일</label>
<input
type=\"date\"
value={birth}
onChange={(e) => setBirth(e.target.value)}
className=\"h-[40px] px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40 text-[16px] text-neutral-700 focus:outline-none focus:border-neutral-700\"
/>
</div>
</div>
{/* 약관 */}
<div className=\"space-y-4\">
<label className=\"flex items-center gap-2 text-[18px] font-[600] text-neutral-700 cursor-pointer\">
<input
type=\"checkbox\"
checked={agreeAll}
onChange={(e) => syncAgreeAll(e.target.checked)}
className=\"h-[20px] w-[20px] cursor-pointer rounded border\"
style={{ accentColor: \"var(--color-input-border-select)\", borderColor: \"var(--color-inactive-checkbox)\" }}
/>
모든 항목에 동의합니다.
</label>
<div className=\"h-px w-full bg-[var(--color-input-border)]\" />
<div className=\"space-y-3\">
<label className=\"flex items-center justify-between text-[15px]\">
<span className=\"flex items-center gap-2 text-neutral-700\">
<input
type=\"checkbox\"
checked={agreeAge}
onChange={(e) => handleIndividualAgree({ age: e.target.checked })}
className=\"h-[18px] w-[18px] cursor-pointer rounded border\"
style={{ accentColor: \"var(--color-input-border-select)\", borderColor: \"var(--color-inactive-checkbox)\" }}
/>
<span>
만 14세 이상입니다. <span className=\"text-[#384fbf]\">(필수)</span>
</span>
</span>
</label>
<label className=\"flex items-center justify-between text-[15px]\">
<span className=\"flex items-center gap-2 text-neutral-700\">
<input
type=\"checkbox\"
checked={agreeTos}
onChange={(e) => handleIndividualAgree({ tos: e.target.checked })}
className=\"h-[18px] w-[18px] cursor-pointer rounded border\"
style={{ accentColor: \"var(--color-input-border-select)\", borderColor: \"var(--color-inactive-checkbox)\" }}
/>
<span>
이용 약관 동의 <span className=\"text-[#384fbf]\">(필수)</span>
</span>
</span>
</label>
<label className=\"flex items-center justify-between text-[15px]\">
<span className=\"flex items-center gap-2 text-neutral-700\">
<input
type=\"checkbox\"
checked={agreePrivacy}
onChange={(e) => handleIndividualAgree({ privacy: e.target.checked })}
className=\"h-[18px] w-[18px] cursor-pointer rounded border\"
style={{ accentColor: \"var(--color-input-border-select)\", borderColor: \"var(--color-inactive-checkbox)\" }}
/>
<span>
개인정보 수집 및 이용 동의 <span className=\"text-[#384fbf]\">(필수)</span>
</span>
</span>
</label>
</div>
</div>
{/* 버튼들 */}
<div className=\"flex gap-3\">
<Link
href=\"/login\"
className=\"h-14 flex-1 rounded-[12px] bg-[#f1f3f5] text-center grid place-items-center text-[18px] font-[600] text-[#4c5561]\"
>
돌아가기
</Link>
<button
type=\"submit\"
className=\"h-14 flex-1 rounded-[12px] text-[18px] font-[600] text-white\"
style={{ backgroundColor: \"var(--color-active-button)\" }}
disabled={!(agreeAge && agreeTos && agreePrivacy)}
>
회원 가입 완료
</button>
</div>
</form>
</div>
<div></div>
<p className=\"text-center text-[15px] text-basic-text\">
Copyright ⓒ 2025 XL LMS. All rights reserved
</p>
</div>
);
}
"use client";
import { useEffect, useMemo, useState } from "react";
import Link from "next/link";
export default function RegisterPage() {
// form states
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [passwordConfirm, setPasswordConfirm] = useState("");
const [gender, setGender] = useState<"male" | "female">("male");
const [birth, setBirth] = useState("");
// agreements
const [agreeAge, setAgreeAge] = useState(false);
const [agreeTos, setAgreeTos] = useState(false);
const [agreePrivacy, setAgreePrivacy] = useState(false);
const agreeAll = useMemo(
() => agreeAge && agreeTos && agreePrivacy,
[agreeAge, agreeTos, agreePrivacy],
);
function handleToggleAll(next: boolean) {
setAgreeAge(next);
setAgreeTos(next);
setAgreePrivacy(next);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
if (!agreeAge || !agreeTos || !agreePrivacy) {
alert(" .");
return;
}
if (password.length < 8 || password.length > 16) {
alert(" 8~16 .");
return;
}
if (password !== passwordConfirm) {
alert(" .");
return;
}
// TODO: 실제 API 연동
console.log({
name,
phone,
email,
password,
gender,
birth,
agreements: { agreeAge, agreeTos, agreePrivacy },
});
alert(" . ()");
}
return (
<div className="min-h-screen w-full flex flex-col items-center justify-between">
<div></div>
<div className="rounded-xl bg-white max-w-[480px] w-full">
{/* 헤더 타이틀 */}
<div className="mb-10 flex flex-col items-center">
<div className="text-[24px] font-[700] leading-[150%] text-neutral-700">
회원가입
</div>
</div>
{/* 폼 */}
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
{/* 이름 */}
<div className="space-y-2">
<label
htmlFor="name"
className="text-[15px] font-[600]"
style={{ color: "var(--color-basic-text)" }}
>
이름
</label>
<input
id="name"
name="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder=" ."
className="
h-10 px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700
text-[16px] text-neutral-700 font-[400] leading-[150%] placeholder:text-input-placeholder-text
"
/>
</div>
{/* 휴대폰 번호 */}
<div className="space-y-2">
<label
htmlFor="phone"
className="text-[15px] font-[600]"
style={{ color: "var(--color-basic-text)" }}
>
휴대폰 번호
</label>
<input
id="phone"
name="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="- ."
className="
h-10 px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700
text-[16px] text-neutral-700 font-[400] leading-[150%] placeholder:text-input-placeholder-text
"
/>
</div>
{/* 아이디(이메일) + 인증번호 전송 */}
<div className="space-y-2">
<label
htmlFor="email"
className="text-[15px] font-[600]"
style={{ color: "var(--color-basic-text)" }}
>
아이디 (이메일)
</label>
<div className="flex items-center gap-2">
<input
id="email"
name="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder=" ."
className="
h-10 px-[12px] py-[8px] rounded-[8px] w-full border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700
text-[16px] text-neutral-700 font-[400] leading-[150%] placeholder:text-input-placeholder-text
"
/>
<button
type="button"
className="h-10 w-[136px] rounded-[8px] text-[16px] font-[600] bg-[#f9fafb] text-[#b1b8c0]"
>
인증번호 전송
</button>
</div>
</div>
{/* 비밀번호 */}
<div className="space-y-2">
<label
htmlFor="password"
className="text-[15px] font-[600]"
style={{ color: "var(--color-basic-text)" }}
>
비밀번호
</label>
<input
id="password"
name="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="8~16 // ."
className="
h-10 px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700
text-[16px] text-neutral-700 font-[400] leading-[150%] placeholder:text-input-placeholder-text
"
/>
</div>
{/* 비밀번호 확인 */}
<div className="space-y-2">
<label
htmlFor="passwordConfirm"
className="text-[15px] font-[600]"
style={{ color: "var(--color-basic-text)" }}
>
비밀번호 확인
</label>
<input
id="passwordConfirm"
name="passwordConfirm"
type="password"
value={passwordConfirm}
onChange={(e) => setPasswordConfirm(e.target.value)}
placeholder=" ."
className="
h-10 px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700
text-[16px] text-neutral-700 font-[400] leading-[150%] placeholder:text-input-placeholder-text
"
/>
</div>
{/* 성별 */}
<div className="space-y-2">
div className="text-[15px] font-[600]" style={{ color: "var(--color-basic-text)" }}>
성별
</div>
<div className="flex items-center gap-5 h-10">
<label className="flex items-center gap-2 text-[15px] text-neutral-700">
<input
type="radio"
name="gender"
checked={gender === "male"}
onChange={() => setGender("male")}
className="h-[18px] w-[18px] rounded-full"
style={{ accentColor: "var(--color-active-button)" }}
/>
남성
</label>
<label className="flex items-center gap-2 text-[15px] text-neutral-700">
<input
type="radio"
name="gender"
checked={gender === "female"}
onChange={() => setGender("female")}
className="h-[18px] w-[18px] rounded-full"
style={{ accentColor: "var(--color-input-border)" }}
/>
여성
</label>
</div>
</div>
{/* 생년월일 */}
<div className="space-y-2">
<label
htmlFor="birth"
className="text-[15px] font-[600]"
style={{ color: "var(--color-basic-text)" }}
>
생년월일
</label>
<input
id="birth"
name="birth"
type="date"
value={birth}
onChange={(e) => setBirth(e.target.value)}
className="
h-10 px-[12px] py-[8px] w-full rounded-[8px] border border-neutral-40
focus:outline-none focus:ring-0 focus:ring-offset-0 focus:shadow-none
focus:appearance-none focus:border-neutral-700
text-[16px] text-neutral-700 font-[400] leading-[150%]
"
/>
</div>
</div>
{/* 약관 동의 */}
<div className="space-y-4">
<label className="flex cursor-pointer select-none items-center gap-2">
<input
type="checkbox"
checked={agreeAll}
onChange={(e) => handleToggleAll(e.target.checked)}
className="h-[20px] w-[20px] cursor-pointer rounded border"
style={{
accentColor: "var(--color-input-border-select)",
borderColor: "var(--color-inactive-checkbox)",
}}
/>
<span className="text-[18px] font-[600] text-neutral-700">모든 항목에 동의합니다.</span>
</label>
<div className="h-px w-full bg-neutral-200" />
<div className="space-y-3">
<label className="flex cursor-pointer select-none items-center justify-between">
<span className="flex items-center gap-2 text-[15px]" style={{ color: "var(--color-basic-text)" }}>
<input
type="checkbox"
checked={agreeAge}
onChange={(e) => setAgreeAge(e.target.checked)}
className="h-[20px] w-[20px] cursor-pointer rounded border"
style={{
accentColor: "var(--color-input-border-select)",
borderColor: "var(--color-inactive-checkbox)",
}}
/>
만 14세 이상입니다. <span className="text-[#384fbf]">(필수)</span>
</span>
</label>
<label className="flex cursor-pointer select-none items-center justify-between">
<span className="flex items-center gap-2 text-[15px]" style={{ color: "var(--color-basic-text)" }}>
<input
type="checkbox"
checked={agreeTos}
onChange={(e) => setAgreeTos(e.target.checked)}
className="h-[20px] w-[20px] cursor-pointer rounded border"
style={{
accentColor: "var(--color-input-border-select)",
borderColor: "var(--color-inactive-checkbox)",
}}
/>
이용 약관 동의 <span className="text-[#384fbf]">(필수)</span>
</span>
</label>
<label className="flex cursor-pointer select-none items-center justify-between">
<span className="flex items-center gap-2 text-[15px]" style={{ color: "var(--color-basic-text)" }}>
<input
type="checkbox"
checked={agreePrivacy}
onChange={(e) => setAgreePrivacy(e.target.checked)}
className="h-[20px] w-[20px] cursor-pointer rounded border"
style={{
accentColor: "var(--color-input-border-select)",
borderColor: "var(--color-inactive-checkbox)",
}}
/>
개인정보 수집 및 이용 동의 <span className="text-[#384fbf]">(필수)</span>
</span>
</label>
</div>
</div>
{/* 하단 버튼 */}
<div className="grid grid-cols-2 gap-3">
<Link
href="/login"
className="h-14 rounded-[12px] bg-[#f1f3f5] text-[18px] font-[600] text-center flex items-center justify-center"
style={{ color: "var(--color-basic-text)" }}
>
돌아가기
</Link>
<button
type="submit"
className="h-14 rounded-[12px] text-[18px] font-[600] text-white"
style={{ backgroundColor: "var(--color-inactive-button)" }}
>
회원 가입 완료
</button>
</div>
</form>
</div>
<div></div>
<p className="text-center text-[15px] text-basic-text">
Copyright 2025 XL LMS. All rights reserved
</p>
</div>
);
}

View File

@@ -0,0 +1,11 @@
"use client";
export default function LoginInputSvg() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path fillRule="evenodd" clipRule="evenodd" d="M10 17.5C5.8575 17.5 2.5 14.1425 2.5 10C2.5 5.8575 5.8575 2.5 10 2.5C14.1425 2.5 17.5 5.8575 17.5 10C17.5 14.1425 14.1425 17.5 10 17.5Z" fill="#6C7682" stroke="#6C7682" strokeWidth="1.875" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M12.3583 7.6416L7.6416 12.3583" stroke="white" strokeWidth="1.875" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M12.3583 12.3583L7.6416 7.6416" stroke="white" strokeWidth="1.875" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}