ㄱㄱ
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
|
||||
type FindIdDevOptionProps = {
|
||||
doneEnabled?: boolean;
|
||||
setDoneEnabled?: (enabled: boolean) => void;
|
||||
failedEnabled?: boolean;
|
||||
setFailedEnabled?: (enabled: boolean) => void;
|
||||
};
|
||||
|
||||
export default function FindIdDevOption({
|
||||
doneEnabled,
|
||||
setDoneEnabled,
|
||||
failedEnabled,
|
||||
setFailedEnabled,
|
||||
}: FindIdDevOptionProps) {
|
||||
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">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="옵션 닫기"
|
||||
className="absolute inset-0 bg-black/20 cursor-default"
|
||||
onClick={() => setIsOpen(false)}
|
||||
/>
|
||||
<div className="w-[500px] h-[600px] flex bg-white/80 p-10 border rounded-lg relative">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="옵션 닫기"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="absolute top-3 right-3 w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 text-gray-700 flex items-center justify-center"
|
||||
>
|
||||
<span className="text-sm">✕</span>
|
||||
</button>
|
||||
<ul className="flex flex-col gap-4">
|
||||
<li className="flex items-center justify-between">
|
||||
<p className="mr-4">find-id done overlay</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="find-id done overlay 토글"
|
||||
aria-pressed={!!doneEnabled}
|
||||
onClick={() => setDoneEnabled?.(!doneEnabled)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${doneEnabled ? "bg-blue-600" : "bg-gray-300"}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition ${doneEnabled ? "translate-x-5" : "translate-x-1"}`}
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex items-center justify-between">
|
||||
<p className="mr-4">find-id failed overlay</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="find-id failed overlay 토글"
|
||||
aria-pressed={!!failedEnabled}
|
||||
onClick={() => setFailedEnabled?.(!failedEnabled)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${failedEnabled ? "bg-blue-600" : "bg-gray-300"}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition ${failedEnabled ? "translate-x-5" : "translate-x-1"}`}
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,131 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo, useState } from "react";
|
||||
import LoginInputSvg from "@/app/svgs/inputformx";
|
||||
import Link from "next/link";
|
||||
import React, { useState } from "react";
|
||||
|
||||
type FindIdOptionProps = {
|
||||
onOpenDone: (userId?: string) => void;
|
||||
onOpenFailed: () => void;
|
||||
type FindIdDevOptionProps = {
|
||||
doneEnabled?: boolean;
|
||||
setDoneEnabled?: (enabled: boolean) => void;
|
||||
failedEnabled?: boolean;
|
||||
setFailedEnabled?: (enabled: boolean) => void;
|
||||
};
|
||||
|
||||
export default function FindIdOption({ onOpenDone, onOpenFailed }: FindIdOptionProps) {
|
||||
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;
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
if (!validateAll()) return;
|
||||
const mockUserId = `${name.trim()}@example.com`;
|
||||
onOpenDone(mockUserId);
|
||||
}
|
||||
export default function FindIdDevOption({
|
||||
doneEnabled,
|
||||
setDoneEnabled,
|
||||
failedEnabled,
|
||||
setFailedEnabled,
|
||||
}: FindIdDevOptionProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-white max-w-[560px] px-[40px] w-full relative">
|
||||
<Link
|
||||
href="/login"
|
||||
aria-label="닫기"
|
||||
className="absolute top-3 right-3 w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 text-gray-700 flex items-center justify-center"
|
||||
<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`}
|
||||
>
|
||||
<span className="text-sm">✕</span>
|
||||
</Link>
|
||||
<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="이름을 입력해 주세요."
|
||||
className="h-[40px] px-[12px] py-[7px] w-full rounded-[8px] border border-neutral-40 focus:outline-none focus:border-neutral-700 text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px]"
|
||||
/>
|
||||
{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>
|
||||
)}
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="옵션 닫기"
|
||||
className="absolute inset-0 bg-black/20 cursor-default"
|
||||
onClick={() => setIsOpen(false)}
|
||||
/>
|
||||
<div className="w-[500px] h-[600px] flex bg-white/80 p-10 border rounded-lg relative">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="옵션 닫기"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="absolute top-3 right-3 w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 text-gray-700 flex items-center justify-center"
|
||||
>
|
||||
<span className="text-sm">✕</span>
|
||||
</button>
|
||||
<ul className="flex flex-col gap-4">
|
||||
<li className="flex items-center justify-between">
|
||||
<p className="mr-4">find-id done overlay</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="find-id done overlay 토글"
|
||||
aria-pressed={!!doneEnabled}
|
||||
onClick={() => setDoneEnabled?.(!doneEnabled)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${doneEnabled ? "bg-blue-600" : "bg-gray-300"}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition ${doneEnabled ? "translate-x-5" : "translate-x-1"}`}
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex items-center justify-between">
|
||||
<p className="mr-4">find-id failed overlay</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="find-id failed overlay 토글"
|
||||
aria-pressed={!!failedEnabled}
|
||||
onClick={() => setFailedEnabled?.(!failedEnabled)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${failedEnabled ? "bg-blue-600" : "bg-gray-300"}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-5 w-5 transform rounded-full bg-white transition ${failedEnabled ? "translate-x-5" : "translate-x-1"}`}
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{errors.name && <p className="text-error text-[13px] leading-tight">{errors.name}</p>}
|
||||
</div>
|
||||
|
||||
<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="-없이 입력해 주세요."
|
||||
className="h-[40px] px-[12px] py-[7px] w-full rounded-[8px] border border-neutral-40 focus:outline-none focus:border-neutral-700 text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px]"
|
||||
/>
|
||||
{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"
|
||||
className={`h-[40px] w-full rounded-[12px] text-[18px] font-semibold text-white ${canSubmit ? "bg-active-button" : "bg-inactive-button"} cursor-pointer`}>
|
||||
다음
|
||||
</button>
|
||||
|
||||
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import IdFindDone from "./IdFindDone";
|
||||
import IdFindFailed from "./IdFindFailed";
|
||||
import FindIdOption from "./FindIdOption";
|
||||
import FindIdDevOption from "./FindIdDevOption";
|
||||
import LoginInputSvg from "@/app/svgs/inputformx";
|
||||
|
||||
export default function FindIdPage() {
|
||||
const [isDoneOpen, setIsDoneOpen] = useState(false);
|
||||
const [isFailedOpen, setIsFailedOpen] = useState(false);
|
||||
const [foundUserId, setFoundUserId] = useState<string | undefined>(undefined);
|
||||
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;
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
if (!validateAll()) return;
|
||||
const mockUserId = `${name.trim()}@example.com`;
|
||||
setFoundUserId(mockUserId);
|
||||
setIsDoneOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen w-full flex flex-col items-center justify-between">
|
||||
@@ -23,17 +47,93 @@ export default function FindIdPage() {
|
||||
onClose={() => setIsFailedOpen(false)}
|
||||
/>
|
||||
|
||||
<FindIdOption
|
||||
onOpenDone={(id) => {
|
||||
setFoundUserId(id);
|
||||
setIsDoneOpen(true);
|
||||
}}
|
||||
onOpenFailed={() => {
|
||||
setIsFailedOpen(true);
|
||||
}}
|
||||
/>
|
||||
<div className="rounded-xl bg-white max-w-[560px] px-[40px] w-full relative">
|
||||
|
||||
<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>
|
||||
|
||||
<FindIdDevOption
|
||||
<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="이름을 입력해 주세요."
|
||||
className="h-[40px] px-[12px] py-[7px] w-full rounded-[8px] border border-neutral-40 focus:outline-none focus:border-neutral-700 text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px]"
|
||||
/>
|
||||
{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>
|
||||
|
||||
<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="-없이 입력해 주세요."
|
||||
className="h-[40px] px-[12px] py-[7px] w-full rounded-[8px] border border-neutral-40 focus:outline-none focus:border-neutral-700 text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px]"
|
||||
/>
|
||||
{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"
|
||||
className={`h-[40px] w-full rounded-[12px] text-[18px] font-semibold text-white ${canSubmit ? "bg-active-button" : "bg-inactive-button"} cursor-pointer`}>
|
||||
다음
|
||||
</button>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<FindIdOption
|
||||
doneEnabled={isDoneOpen}
|
||||
setDoneEnabled={setIsDoneOpen}
|
||||
failedEnabled={isFailedOpen}
|
||||
|
||||
Reference in New Issue
Block a user