2025-11-18 03:27:30 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2025-11-18 06:19:26 +09:00
|
|
|
import React, { useMemo, useState } from "react";
|
2025-11-18 03:27:30 +09:00
|
|
|
import IdFindDone from "./IdFindDone";
|
|
|
|
|
import IdFindFailed from "./IdFindFailed";
|
|
|
|
|
import FindIdOption from "./FindIdOption";
|
2025-11-18 06:19:26 +09:00
|
|
|
import LoginInputSvg from "@/app/svgs/inputformx";
|
2025-11-18 03:27:30 +09:00
|
|
|
|
|
|
|
|
export default function FindIdPage() {
|
|
|
|
|
const [isDoneOpen, setIsDoneOpen] = useState(false);
|
|
|
|
|
const [isFailedOpen, setIsFailedOpen] = useState(false);
|
|
|
|
|
const [foundUserId, setFoundUserId] = useState<string | undefined>(undefined);
|
2025-11-18 06:19:26 +09:00
|
|
|
const [name, setName] = useState("");
|
|
|
|
|
const [phone, setPhone] = useState("");
|
|
|
|
|
const [focused, setFocused] = useState<Record<string, boolean>>({});
|
|
|
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
|
|
|
|
|
|
|
|
const isNameValid = useMemo(() => name.trim().length > 0, [name]);
|
|
|
|
|
const isPhoneValid = useMemo(() => /^\d{9,11}$/.test(phone), [phone]);
|
|
|
|
|
const canSubmit = useMemo(() => isNameValid && isPhoneValid, [isNameValid, isPhoneValid]);
|
|
|
|
|
|
|
|
|
|
function validateAll() {
|
|
|
|
|
const next: Record<string, string> = {};
|
|
|
|
|
if (!isNameValid) next.name = "이름을 입력해 주세요.";
|
|
|
|
|
if (!isPhoneValid) next.phone = "휴대폰 번호를 숫자만 9~11자리로 입력해 주세요.";
|
|
|
|
|
setErrors(next);
|
|
|
|
|
return Object.keys(next).length === 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-24 22:50:28 +09:00
|
|
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
2025-11-18 06:19:26 +09:00
|
|
|
e.preventDefault();
|
|
|
|
|
if (!validateAll()) return;
|
2025-11-25 12:38:11 +09:00
|
|
|
|
2025-11-24 22:50:28 +09:00
|
|
|
try {
|
|
|
|
|
const response = await fetch('https://hrdi.coconutmeet.net/auth/find-id', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
name: name,
|
|
|
|
|
phone: phone,
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
2025-11-24 22:50:42 +09:00
|
|
|
let errorMessage = `아이디 찾기 실패 (${response.status})`;
|
|
|
|
|
try {
|
|
|
|
|
const errorData = await response.json();
|
|
|
|
|
if (errorData.error) {
|
|
|
|
|
errorMessage = errorData.error;
|
|
|
|
|
} else if (errorData.message) {
|
|
|
|
|
errorMessage = errorData.message;
|
|
|
|
|
} else if (response.statusText) {
|
|
|
|
|
errorMessage = `${response.statusText} (${response.status})`;
|
|
|
|
|
}
|
|
|
|
|
} catch (parseError) {
|
|
|
|
|
if (response.statusText) {
|
|
|
|
|
errorMessage = `${response.statusText} (${response.status})`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
console.error('아이디 찾기 실패:', errorMessage);
|
2025-11-24 22:50:28 +09:00
|
|
|
setIsFailedOpen(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
setFoundUserId(data.email);
|
|
|
|
|
setIsDoneOpen(true);
|
|
|
|
|
} catch (error) {
|
2025-11-24 22:50:42 +09:00
|
|
|
const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.';
|
|
|
|
|
console.error('아이디 찾기 오류:', errorMessage);
|
2025-11-24 22:50:28 +09:00
|
|
|
setIsFailedOpen(true);
|
|
|
|
|
}
|
2025-11-18 06:19:26 +09:00
|
|
|
}
|
2025-11-18 03:27:30 +09:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="min-h-screen w-full flex flex-col items-center justify-between">
|
|
|
|
|
<IdFindDone
|
|
|
|
|
on={isDoneOpen}
|
|
|
|
|
userId={foundUserId}
|
|
|
|
|
onClose={() => setIsDoneOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
<IdFindFailed
|
|
|
|
|
on={isFailedOpen}
|
|
|
|
|
onClose={() => setIsFailedOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
|
2025-11-18 06:19:26 +09:00
|
|
|
<div className="rounded-xl bg-white max-w-[560px] px-[40px] w-full relative">
|
2025-11-25 12:38:11 +09:00
|
|
|
|
2025-11-18 06:19:26 +09:00
|
|
|
<div className="my-15 flex flex-col items-center">
|
|
|
|
|
<div className="text-[24px] font-extrabold leading-[150%] text-neutral-700">
|
|
|
|
|
아이디 찾기
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-[18px] leading-[150%] text-[#6c7682] mt-[8px] text-center">
|
|
|
|
|
가입 시 등록한 회원정보를 입력해 주세요.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<label htmlFor="name" className="text-[15px] font-semibold text-[#6c7682]">
|
|
|
|
|
이름
|
|
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<input
|
|
|
|
|
id="name"
|
|
|
|
|
name="name"
|
|
|
|
|
value={name}
|
|
|
|
|
onChange={(e) => setName(e.target.value)}
|
|
|
|
|
onFocus={() => setFocused((p) => ({ ...p, name: true }))}
|
|
|
|
|
onBlur={() => setFocused((p) => ({ ...p, name: false }))}
|
|
|
|
|
placeholder="이름을 입력해 주세요."
|
2025-11-25 12:38:11 +09:00
|
|
|
className="h-[40px] px-[12px] py-[7px] w-full rounded-[8px] mt-3 border border-neutral-40 focus:outline-none focus:border-neutral-700 text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px]"
|
2025-11-18 06:19:26 +09:00
|
|
|
/>
|
|
|
|
|
{name.trim().length > 0 && focused.name && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setName("");
|
|
|
|
|
}}
|
|
|
|
|
aria-label="입력 지우기"
|
|
|
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer">
|
|
|
|
|
<LoginInputSvg />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{errors.name && <p className="text-error text-[13px] leading-tight">{errors.name}</p>}
|
|
|
|
|
</div>
|
2025-11-18 03:27:30 +09:00
|
|
|
|
2025-11-18 06:19:26 +09:00
|
|
|
<div className="space-y-2">
|
|
|
|
|
<label htmlFor="phone" className="text-[15px] font-semibold text-[#6c7682]">
|
|
|
|
|
휴대폰 번호
|
|
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<input
|
|
|
|
|
id="phone"
|
|
|
|
|
name="phone"
|
|
|
|
|
type="tel"
|
|
|
|
|
inputMode="numeric"
|
|
|
|
|
value={phone}
|
|
|
|
|
onChange={(e) => setPhone(e.target.value.replace(/[^0-9]/g, ""))}
|
|
|
|
|
onFocus={() => setFocused((p) => ({ ...p, phone: true }))}
|
|
|
|
|
onBlur={() => setFocused((p) => ({ ...p, phone: false }))}
|
|
|
|
|
placeholder="-없이 입력해 주세요."
|
2025-11-25 12:38:11 +09:00
|
|
|
className="h-[40px] px-[12px] py-[7px] w-full rounded-[8px] border mt-3 border-neutral-40 focus:outline-none focus:border-neutral-700 text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px]"
|
2025-11-18 06:19:26 +09:00
|
|
|
/>
|
|
|
|
|
{phone.trim().length > 0 && focused.phone && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setPhone("");
|
|
|
|
|
}}
|
|
|
|
|
aria-label="입력 지우기"
|
|
|
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer">
|
|
|
|
|
<LoginInputSvg />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{errors.phone && <p className="text-error text-[13px] leading-tight">{errors.phone}</p>}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
2025-11-25 12:38:11 +09:00
|
|
|
className={`h-[56px] w-full rounded-[12px] text-[18px] mt-[30px] font-semibold text-white hover:bg-[#1F2B91] ${canSubmit ? "bg-active-button" : "bg-inactive-button"} cursor-pointer`}>
|
2025-11-18 06:19:26 +09:00
|
|
|
다음
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<FindIdOption
|
2025-11-18 03:27:30 +09:00
|
|
|
doneEnabled={isDoneOpen}
|
|
|
|
|
setDoneEnabled={setIsDoneOpen}
|
|
|
|
|
failedEnabled={isFailedOpen}
|
|
|
|
|
setFailedEnabled={setIsFailedOpen}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<p className="text-center py-[40px] text-[15px] text-basic-text">
|
|
|
|
|
Copyright ⓒ 2025 XL LMS. All rights reserved
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|