로그인 페이지 완성

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

@@ -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>
);
}