From 24f377e048379b0ec321d29f8dd2732b32892d12 Mon Sep 17 00:00:00 2001 From: ljpark Date: Thu, 16 Apr 2026 18:17:55 +0900 Subject: [PATCH] =?UTF-8?q?wr:=20=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/auth/login/ui/OtpModal.tsx | 156 --- .../features/auth/login/ui/SignupModal.tsx | 701 -------------- .../src/features/auth/login/ui/TwoFAModal.tsx | 203 ---- .../auth/login/ui/UserSignupModal.tsx | 901 ------------------ .../features/auth/login/ui/signup-modal.css | 277 ------ .../auth/login/ui/user-signup-modal.css | 436 --------- 6 files changed, 2674 deletions(-) delete mode 100644 apps/wr/src/features/auth/login/ui/OtpModal.tsx delete mode 100644 apps/wr/src/features/auth/login/ui/SignupModal.tsx delete mode 100644 apps/wr/src/features/auth/login/ui/TwoFAModal.tsx delete mode 100644 apps/wr/src/features/auth/login/ui/UserSignupModal.tsx delete mode 100644 apps/wr/src/features/auth/login/ui/signup-modal.css delete mode 100644 apps/wr/src/features/auth/login/ui/user-signup-modal.css diff --git a/apps/wr/src/features/auth/login/ui/OtpModal.tsx b/apps/wr/src/features/auth/login/ui/OtpModal.tsx deleted file mode 100644 index e9db92308..000000000 --- a/apps/wr/src/features/auth/login/ui/OtpModal.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { useState } from "react"; -import { ACCESS_TOKEN, AUTH_USER_ID, REFRESH_TOKEN, CLAIM_NAME, SITE_ID } from "@/src/shared/config"; -import { tokenManager } from "@/src/entities/auth/lib/token-manager"; -import { - Button, - Dialog, - DialogContent, -} from "@common/components"; - -interface OtpModalProps { - onClose: () => void; - onSuccess: () => void; - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export default function OtpModal({ onClose, onSuccess, open, onOpenChange }: OtpModalProps) { - const [otp, setOtp] = useState(""); - const [error, setError] = useState(""); - const [isLoading, setIsLoading] = useState(false); - - const handleSubmit = async () => { - try { - setIsLoading(true); - setError(""); - - const tempToken = localStorage.getItem("tempToken"); - console.log("tempToken from localStorage:", tempToken); - - if (!tempToken) { - setError("임시 토큰이 없습니다. 로그인을 다시 시도해주세요."); - return; - } - - // Proxy를 통해 OTP 인증 처리 - const response = await fetch(`/api/proxy/user-service/api/v1/auth/2fa/verify`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Site-Id': SITE_ID, - }, - body: JSON.stringify({ - otpCode: otp, - tempToken - }), - }); - - if (!response.ok) { - const errorData = await response.json(); - let errorMsg = errorData.message || 'OTP 인증 실패'; - if (Array.isArray(errorData.errors) && errorData.errors.length > 0) { - errorMsg = errorData.errors.map((e: { message: string }) => e.message).join(', '); - } - throw new Error(errorMsg); - } - - const data = await response.json(); - console.log("OTP verification response:", data); - console.log("Response headers:", Object.fromEntries(response.headers.entries())); - console.log("Response data keys:", Object.keys(data)); - console.log("Full response data:", JSON.stringify(data, null, 2)); - - // 2FA 응답에서는 access-token만 있고 token-id는 없을 수 있음 - const accessToken = data[ACCESS_TOKEN] || data.accessToken || data.access_token || data.token || data['ss-token']; - - console.log("Extracted accessToken:", !!accessToken); - console.log("Token value - accessToken:", accessToken?.substring(0, 20) + "..."); - - // access-token이 있는 경우 저장 - if (accessToken) { - console.log("Saving access token from OTP verification"); - tokenManager.setToken(CLAIM_NAME, accessToken); // Authorization으로 저장 - - // 리프레시 토큰도 확인 - const refreshToken = data[REFRESH_TOKEN] || data.refreshToken || data.refresh_token; - if (refreshToken) { - console.log("Saving refresh token"); - tokenManager.setToken(REFRESH_TOKEN, refreshToken); - } - - // userId는 원래 로그인 응답에서 가져온 값 사용 - const originalUserId = data.userId; - if (originalUserId) { - console.log("Saving userId from original login response"); - tokenManager.setToken(AUTH_USER_ID, originalUserId); - } - - console.log("All tokens saved successfully"); - } else { - console.log("No access token in OTP response"); - console.log("Available data keys:", Object.keys(data)); - console.log("Data values:", Object.entries(data).map(([k, v]) => `${k}: ${typeof v === 'string' ? v.substring(0, 20) + '...' : v}`)); - } - - onSuccess(); - } catch (error: unknown) { - console.error("OTP verification error:", error); - if (error instanceof Error) { - setError(error.message); - } else { - setError("OTP 인증 실패"); - } - } finally { - setIsLoading(false); - } - }; - - const handleClose = () => { - onOpenChange(false); - onClose(); - }; - - return ( - - -
-

OTP 인증

- -
- setOtp(e.target.value)} - disabled={isLoading} - /> - - {error && ( -
- {error} -
- )} - -
- - -
-
-
-
-
- ); -} diff --git a/apps/wr/src/features/auth/login/ui/SignupModal.tsx b/apps/wr/src/features/auth/login/ui/SignupModal.tsx deleted file mode 100644 index 00a244e13..000000000 --- a/apps/wr/src/features/auth/login/ui/SignupModal.tsx +++ /dev/null @@ -1,701 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import type { UserSaveData } from "@/src/entities/user"; -import { ENDPOINT, BASE_URL } from "@/src/shared/config"; -import { SignupClient } from "@/src/shared/api/signup-client"; -import "./signup-modal.css"; - -interface SignupModalProps { - open: boolean; - onOpenChange: (open: boolean) => void; - onClose: () => void; - onSuccess: () => void; -} - -function SignupModal({ open, onClose, onSuccess }: SignupModalProps) { - const [currentStep, setCurrentStep] = useState<'agreement' | 'signup'>('agreement'); - const [agreed, setAgreed] = useState(false); - const [formData, setFormData] = useState({ - userId: "", - userName: "", - email: "", - password: "", - confirmPassword: "", - pswdHnt: "", - pswdAns: "", - mphn: "", - telno: "", - zip: "", - addr: "", - daddr: "", - laeSeCd: "", - ogdpSeCd: "", - expryYmd: "", - instSeCd: "", - dtlInstCd: "", - enoCd: "", - }); - - const [errors, setErrors] = useState>({}); - const [isLoading, setIsLoading] = useState(false); - const [isCheckingId, setIsCheckingId] = useState(false); - const [isCheckingEmail, setIsCheckingEmail] = useState(false); - const [idAvailable, setIdAvailable] = useState(null); - const [emailAvailable, setEmailAvailable] = useState(null); - - // 모달이 닫힐 때 초기화 - useEffect(() => { - if (!open) { - setCurrentStep('agreement'); - setAgreed(false); - setFormData({ - userId: "", - userName: "", - email: "", - password: "", - confirmPassword: "", - pswdHnt: "", - pswdAns: "", - mphn: "", - telno: "", - zip: "", - addr: "", - daddr: "", - laeSeCd: "", - ogdpSeCd: "", - expryYmd: "", - instSeCd: "", - dtlInstCd: "", - enoCd: "", - }); - setErrors({}); - setIdAvailable(null); - setEmailAvailable(null); - } - }, [open]); - - const handleAgreementChange = (e: React.ChangeEvent) => { - setAgreed(e.target.checked); - }; - - const handleNextStep = () => { - if (agreed) { - setCurrentStep('signup'); - } - }; - - const handleInputChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - - // 에러 초기화 - if (errors[name]) { - setErrors(prev => ({ ...prev, [name]: "" })); - } - }; - - const validateForm = () => { - const newErrors: Record = {}; - - // 필수 필드 검증 - if (!formData.userId) newErrors.userId = "아이디를 입력해주세요."; - if (!formData.userName) newErrors.userName = "이름을 입력해주세요."; - if (!formData.password) newErrors.password = "비밀번호를 입력해주세요."; - if (!formData.confirmPassword) newErrors.confirmPassword = "비밀번호 확인을 입력해주세요."; - if (!formData.pswdHnt) newErrors.pswdHnt = "비밀번호 힌트를 입력해주세요."; - if (!formData.laeSeCd) newErrors.laeSeCd = "법인구분을 선택해주세요."; - if (!formData.ogdpSeCd) newErrors.ogdpSeCd = "기관구분을 선택해주세요."; - - // 비밀번호 확인 - if (formData.password && formData.confirmPassword && formData.password !== formData.confirmPassword) { - newErrors.confirmPassword = "비밀번호가 일치하지 않습니다."; - } - - // 비밀번호 정규식 검증 - const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,20}$/; - if (formData.password && !passwordRegex.test(formData.password)) { - newErrors.password = "비밀번호는 8~20자, 영문/숫자/특수기호를 모두 포함해야 합니다."; - } - - // 이메일 형식 검증 - if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { - newErrors.email = "올바른 이메일 형식을 입력해주세요."; - } - - // 중복 체크 검증 - if (idAvailable === false) newErrors.userId = "이미 사용 중인 아이디입니다."; - if (emailAvailable === false) newErrors.email = "이미 사용 중인 이메일입니다."; - - setErrors(newErrors); - const isValid = Object.keys(newErrors).length === 0; - console.log("폼 검증 결과:", { isValid, errors: newErrors, formData }); - return isValid; - }; - - const checkUserIdDuplicate = async () => { - if (!formData.userId) { - setErrors(prev => ({ ...prev, userId: "아이디를 입력해주세요." })); - return; - } - - setIsCheckingId(true); - try { - // SignupClient를 통해 중복 체크 API 호출 - const signupClient = SignupClient.getInstance({ - baseURL: BASE_URL, - }); - const response = await signupClient.post( - `${ENDPOINT.USER_SERVICE.USERS}/existsUserId`, - { userId: formData.userId } - ); - - const exists = response.data; - setIdAvailable(!exists); - if (exists) { - setErrors(prev => ({ ...prev, userId: "이미 사용 중인 아이디입니다." })); - } else { - setErrors(prev => ({ ...prev, userId: "" })); - } - } catch (error) { - console.error("아이디 중복 체크 오류:", error); - setErrors(prev => ({ ...prev, userId: "아이디 중복 체크 중 오류가 발생했습니다." })); - } finally { - setIsCheckingId(false); - } - }; - - const checkEmailDuplicate = async () => { - if (!formData.email) { - setErrors(prev => ({ ...prev, email: "이메일을 입력해주세요." })); - return; - } - - setIsCheckingEmail(true); - try { - // SignupClient를 통해 중복 체크 API 호출 - const signupClient = SignupClient.getInstance({ - baseURL: BASE_URL, - }); - const response = await signupClient.post( - `${ENDPOINT.USER_SERVICE.USERS}/exists`, - { email: formData.email } - ); - - const exists = response.data; - setEmailAvailable(!exists); - if (exists) { - setErrors(prev => ({ ...prev, email: "이미 사용 중인 이메일입니다." })); - } else { - setErrors(prev => ({ ...prev, email: "" })); - } - } catch (error) { - console.error("이메일 중복 체크 오류:", error); - setErrors(prev => ({ ...prev, email: "이메일 중복 체크 중 오류가 발생했습니다." })); - } finally { - setIsCheckingEmail(false); - } - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - console.log("회원가입 버튼 클릭됨"); - - if (!validateForm()) { - console.log("폼 검증 실패"); - return; - } - - setIsLoading(true); - try { - const signupData: UserSaveData = { - userId: formData.userId, - userName: formData.userName, - email: formData.email || undefined, - password: formData.password, - pswdHnt: formData.pswdHnt, - pswdAns: formData.pswdAns || undefined, - mphn: formData.mphn || undefined, - telno: formData.telno || undefined, - zip: formData.zip || undefined, - addr: formData.addr || undefined, - daddr: formData.daddr || undefined, - laeSeCd: formData.laeSeCd, - ogdpSeCd: formData.ogdpSeCd, - expryYmd: "", - instSeCd: formData.instSeCd || undefined, - dtlInstCd: formData.dtlInstCd || undefined, - enoCd: formData.enoCd || undefined, - }; - - // SignupClient를 통해 익명사용자 회원가입 API 호출 - const signupClient = SignupClient.getInstance({ - baseURL: BASE_URL - }); - const response = await signupClient.post( - `${ENDPOINT.USER_SERVICE.USERS}/anonymouse/users/join`, - signupData - ); - - console.log("회원가입 성공:", response.data); - - // 성공 시 모달 닫기 및 콜백 호출 - onSuccess(); - onClose(); - - // 폼 초기화 - setFormData({ - userId: "", - userName: "", - email: "", - password: "", - confirmPassword: "", - pswdHnt: "", - pswdAns: "", - mphn: "", - telno: "", - zip: "", - addr: "", - daddr: "", - laeSeCd: "", - ogdpSeCd: "", - expryYmd: "", - instSeCd: "", - dtlInstCd: "", - enoCd: "", - }); - setErrors({}); - setIdAvailable(null); - setEmailAvailable(null); - - } catch (error: unknown) { - console.error("회원가입 오류:", error); - if (error instanceof Error) { - setErrors(prev => ({ ...prev, submit: error.message })); - } else { - setErrors(prev => ({ ...prev, submit: "회원가입에 실패했습니다." })); - } - } finally { - setIsLoading(false); - } - }; - - if (!open) return null; - - return ( -
-
e.stopPropagation()}> -
-

{currentStep === 'agreement' ? '개인정보제공동의' : '회원가입'}

- -
- - {currentStep === 'agreement' ? ( -
-
-

개인정보 수집 및 이용 동의

-

회원가입을 위해 다음과 같이 개인정보를 수집 및 이용합니다.

- -

1. 수집하는 개인정보 항목

-
    -
  • 필수항목: 아이디, 이름, 비밀번호, 비밀번호 힌트, 법인구분, 기관구분
  • -
  • 선택항목: 이메일, 휴대폰번호, 전화번호, 주소, 기관코드, 상세기관코드, 사업자등록번호
  • -
- -

2. 개인정보의 수집 및 이용목적

-
    -
  • 회원가입 및 관리
  • -
  • 서비스 제공 및 운영
  • -
  • 고객상담 및 문의응답
  • -
- -

3. 개인정보의 보유 및 이용기간

-

회원탈퇴 시까지 (단, 관련 법령에 따라 보존이 필요한 경우 해당 기간까지)

- -

4. 동의 거부권 및 거부 시 불이익

-

개인정보 수집 및 이용에 대한 동의를 거부할 수 있으며, 동의 거부 시 회원가입이 제한됩니다.

-
- -
- -
- -
- - -
-
- ) : ( -
-
-

기본 정보

- -
-
- -
- - -
- {idAvailable === true && 사용 가능한 아이디입니다.} - {errors.userId && {errors.userId}} -
-
- -
-
- - - {errors.userName && {errors.userName}} -
-
- -
-
- -
- - -
- {emailAvailable === true && 사용 가능한 이메일입니다.} - {errors.email && {errors.email}} -
-
- -
-
- - - {errors.password && {errors.password}} -
-
- -
-
- - - {errors.confirmPassword && {errors.confirmPassword}} -
-
- -
-
- - - {errors.pswdHnt && {errors.pswdHnt}} -
-
- -
-
- - - {errors.pswdAns && {errors.pswdAns}} -
-
-
- -
-

연락처 정보

- -
-
- - - {errors.mphn && {errors.mphn}} -
-
- -
-
- - - {errors.telno && {errors.telno}} -
-
-
- -
-

주소 정보

- -
-
- - - {errors.zip && {errors.zip}} -
-
- -
-
- - - {errors.addr && {errors.addr}} -
-
- -
-
- - - {errors.daddr && {errors.daddr}} -
-
-
- -
-

기관 정보

- -
-
- - - {errors.laeSeCd && {errors.laeSeCd}} -
-
- -
-
- - - {errors.ogdpSeCd && {errors.ogdpSeCd}} -
-
- -
-
- - - {errors.instSeCd && {errors.instSeCd}} -
-
- -
-
- - - {errors.dtlInstCd && {errors.dtlInstCd}} -
-
-
- - {errors.submit && ( -
-
{errors.submit}
-
- )} - -
- - - -
-
- )} -
-
- ); -} - -export default SignupModal; diff --git a/apps/wr/src/features/auth/login/ui/TwoFAModal.tsx b/apps/wr/src/features/auth/login/ui/TwoFAModal.tsx deleted file mode 100644 index 4fb63931a..000000000 --- a/apps/wr/src/features/auth/login/ui/TwoFAModal.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { useEffect, useState } from "react"; -import Image from "next/image"; -import { - Button, - Dialog, - DialogContent, -} from "@common/components"; -import { SITE_ID } from "@/src/shared/config"; - -interface TwoFAModalProps { - onClose: () => void; - onSuccess: () => void; - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export default function TwoFAModal({ onClose, onSuccess, open, onOpenChange }: TwoFAModalProps) { - const [qrImage, setQrImage] = useState(""); - const [secret, setSecret] = useState(""); - const [otp, setOtp] = useState(""); - const [error, setError] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [isSetupLoading, setIsSetupLoading] = useState(true); - - useEffect(() => { - const fetchQR = async () => { - try { - setIsSetupLoading(true); - setError(""); - - const tempToken = localStorage.getItem("tempToken"); - if (!tempToken) { - setError("임시 토큰이 없습니다"); - return; - } - - // Proxy를 통해 2FA setup 처리 - const response = await fetch(`/api/proxy/user-service/api/v1/auth/2fa/setup`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Site-Id': SITE_ID, - }, - body: JSON.stringify({ tempToken }), - }); - - if (!response.ok) { - const errorData = await response.json(); - let errorMsg = errorData.message || 'QR코드 발급 실패'; - if (Array.isArray(errorData.errors) && errorData.errors.length > 0) { - errorMsg = errorData.errors.map((e: { message: string }) => e.message).join(', '); - } - throw new Error(errorMsg); - } - - const data = await response.json(); - console.log("2FA setup response:", data); - console.log("Response headers:", Object.fromEntries(response.headers.entries())); - - // Proxy 응답에서 QR 데이터 추출 - setQrImage(data.qrImage); - setSecret(data.secret); - } catch (error: unknown) { - console.error("2FA setup error:", error); - if (error instanceof Error) { - setError(error.message); - } else { - setError("QR코드 발급 실패"); - } - } finally { - setIsSetupLoading(false); - } - }; - - if (open) { - fetchQR(); - } - }, [open]); - - const handleEnable2FA = async () => { - try { - setIsLoading(true); - setError(""); - - const tempToken = localStorage.getItem("tempToken"); - if (!tempToken) { - setError("임시 토큰이 없습니다"); - return; - } - - // Proxy를 통해 2FA enable 처리 - const response = await fetch(`/api/proxy/user-service/api/v1/auth/2fa/enable`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': tempToken, - 'X-Site-Id': SITE_ID, - }, - body: JSON.stringify({ - otpCode: otp, - tempToken: tempToken, - secret: secret, - }), - }); - - if (!response.ok) { - const errorData = await response.json(); - let errorMsg = errorData.message || '2FA 등록 실패'; - if (Array.isArray(errorData.errors) && errorData.errors.length > 0) { - errorMsg = errorData.errors.map((e: { message: string }) => e.message).join(', '); - } - throw new Error(errorMsg); - } - - const data = await response.json(); - console.log("2FA enable response:", data); - console.log("Response headers:", Object.fromEntries(response.headers.entries())); - console.log("Response data keys:", Object.keys(data)); - - // 2FA 설정 완료 후에는 토큰을 저장하지 않음 (실제 로그인은 OTP에서 처리) - console.log("2FA setup completed successfully"); - - onSuccess(); - } catch (error: unknown) { - console.error("2FA enable error:", error); - if (error instanceof Error) { - setError(error.message); - } else { - setError("2FA 등록 실패"); - } - } finally { - setIsLoading(false); - } - }; - - const handleClose = () => { - onOpenChange(false); - onClose(); - }; - - return ( - - -
-

2FA 등록

- - {isSetupLoading ? ( -
-
QR코드 로딩 중...
-
- ) : ( - <> - {qrImage && ( -
- QR코드 -
- )} - -
- setOtp(e.target.value)} - disabled={isLoading} - /> - - {error && ( -
- {error} -
- )} - -
- - -
-
- - )} -
-
-
- ); -} diff --git a/apps/wr/src/features/auth/login/ui/UserSignupModal.tsx b/apps/wr/src/features/auth/login/ui/UserSignupModal.tsx deleted file mode 100644 index 8d3220e8e..000000000 --- a/apps/wr/src/features/auth/login/ui/UserSignupModal.tsx +++ /dev/null @@ -1,901 +0,0 @@ -"use client"; - -import React, { useState, useEffect } from "react"; -import { SignupClient } from "@/src/shared/api/signup-client"; -import { BASE_URL, ENDPOINT } from "@/src/shared/config"; -import "./user-signup-modal.css"; - -interface UserSignupModalProps { - open: boolean; - onClose: () => void; - onSuccess: () => void; -} - -interface UserSignupData { - userId: string; - userName: string; - email?: string; - password: string; - pswdHnt: string; - pswdAns?: string; - mphn?: string; - telno?: string; - zip?: string; - addr?: string; - daddr?: string; - laeSeCd: string; - ogdpSeCd: string; - instSeCd?: string; - dtlInstCd?: string; - enoCd?: string; -} - -// 익명 사용자용 CodeSelect 컴포넌트 -interface AnonymousCodeSelectProps { - upCd: string; - value: string; - onValueChange: (value: string) => void; - disabled?: boolean; - placeholder?: string; -} - -function AnonymousCodeSelect({ - upCd, - value, - onValueChange, - disabled, - placeholder = "선택", -}: AnonymousCodeSelectProps) { - const [codes, setCodes] = useState< - Array<{ lwpoCd: string; lwpoCdNm: string }> - >([]); - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - const fetchCodes = async () => { - if (!upCd) return; - - setIsLoading(true); - try { - const signupClient = SignupClient.getInstance({ - baseURL: BASE_URL, - }); - - const response = await signupClient.get( - `${ENDPOINT.CMS_SERVICE.CODE}/${upCd}`, - ); - - if (response.data && Array.isArray(response.data)) { - setCodes(response.data); - } else { - setCodes([]); - } - } catch (error) { - console.error(`공통 코드 로드 실패 (${upCd}):`, error); - setCodes([]); - } finally { - setIsLoading(false); - } - }; - - fetchCodes(); - }, [upCd]); - - return ( - - ); -} - -function UserSignupModal({ open, onClose, onSuccess }: UserSignupModalProps) { - const [currentStep, setCurrentStep] = useState<"agreement" | "signup">( - "agreement", - ); - const [agreed, setAgreed] = useState(false); - const [formData, setFormData] = useState({ - userId: "", - userName: "", - email: "", - password: "", - confirmPassword: "", - pswdHnt: "", - pswdAns: "", - mphn: "", - telno: "", - zip: "", - addr: "", - daddr: "", - laeSeCd: "", - ogdpSeCd: "", - instSeCd: "", - dtlInstCd: "", - enoCd: "", - }); - - const [errors, setErrors] = useState>({}); - const [isLoading, setIsLoading] = useState(false); - const [isCheckingId, setIsCheckingId] = useState(false); - const [isCheckingEmail, setIsCheckingEmail] = useState(false); - const [idAvailable, setIdAvailable] = useState(null); - const [emailAvailable, setEmailAvailable] = useState(null); - - // 모달이 닫힐 때 초기화 - useEffect(() => { - if (!open) { - setCurrentStep("agreement"); - setAgreed(false); - setFormData({ - userId: "", - userName: "", - email: "", - password: "", - confirmPassword: "", - pswdHnt: "", - pswdAns: "", - mphn: "", - telno: "", - zip: "", - addr: "", - daddr: "", - laeSeCd: "", - ogdpSeCd: "", - instSeCd: "", - dtlInstCd: "", - enoCd: "", - }); - setErrors({}); - setIdAvailable(null); - setEmailAvailable(null); - } - }, [open]); - - const handleAgreementChange = (e: React.ChangeEvent) => { - setAgreed(e.target.checked); - }; - - const handleNextStep = () => { - if (agreed) { - setCurrentStep("signup"); - } - }; - - const handleInputChange = ( - e: React.ChangeEvent, - ) => { - const { name, value } = e.target; - setFormData((prev) => ({ ...prev, [name]: value })); - - // 에러 초기화 - if (errors[name]) { - setErrors((prev) => ({ ...prev, [name]: "" })); - } - }; - - const validateForm = () => { - const newErrors: Record = {}; - - console.log("폼 검증 시작:", formData); - - // 필수 필드 검증 - if (!formData.userId) newErrors.userId = "아이디를 입력해주세요."; - if (!formData.userName) newErrors.userName = "이름을 입력해주세요."; - if (!formData.password) newErrors.password = "비밀번호를 입력해주세요."; - if (!formData.confirmPassword) - newErrors.confirmPassword = "비밀번호 확인을 입력해주세요."; - if (!formData.pswdHnt) newErrors.pswdHnt = "비밀번호 힌트를 선택해주세요."; - if (!formData.laeSeCd) newErrors.laeSeCd = "법인구분을 선택해주세요."; - if (!formData.ogdpSeCd) newErrors.ogdpSeCd = "기관구분을 선택해주세요."; - - // 비밀번호 확인 - if ( - formData.password && - formData.confirmPassword && - formData.password !== formData.confirmPassword - ) { - newErrors.confirmPassword = "비밀번호가 일치하지 않습니다."; - } - - // 비밀번호 정규식 검증 - const passwordRegex = - /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,20}$/; - if (formData.password && !passwordRegex.test(formData.password)) { - newErrors.password = - "비밀번호는 8~20자, 영문/숫자/특수기호를 모두 포함해야 합니다."; - } - - // 이메일 형식 검증 - if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { - newErrors.email = "올바른 이메일 형식을 입력해주세요."; - } - - // 중복 체크 검증 - if (idAvailable === false) - newErrors.userId = "이미 사용 중인 아이디입니다."; - if (emailAvailable === false) - newErrors.email = "이미 사용 중인 이메일입니다."; - - setErrors(newErrors); - const isValid = Object.keys(newErrors).length === 0; - console.log("폼 검증 결과:", { isValid, errors: newErrors, formData }); - return isValid; - }; - - const checkUserIdDuplicate = async () => { - if (!formData.userId) { - setErrors((prev) => ({ ...prev, userId: "아이디를 입력해주세요." })); - return; - } - - setIsCheckingId(true); - try { - const signupClient = SignupClient.getInstance({ - baseURL: BASE_URL, - }); - const response = await signupClient.post( - `${ENDPOINT.USER_SERVICE.USERS}/existsUserId`, - { userId: formData.userId }, - ); - - const exists = response.data; - setIdAvailable(!exists); - if (exists) { - setErrors((prev) => ({ - ...prev, - userId: "이미 사용 중인 아이디입니다.", - })); - } else { - setErrors((prev) => ({ ...prev, userId: "" })); - } - } catch (error) { - console.error("아이디 중복 체크 오류:", error); - setErrors((prev) => ({ - ...prev, - userId: "아이디 중복 체크 중 오류가 발생했습니다.", - })); - } finally { - setIsCheckingId(false); - } - }; - - const checkEmailDuplicate = async () => { - if (!formData.email) { - setErrors((prev) => ({ ...prev, email: "이메일을 입력해주세요." })); - return; - } - - setIsCheckingEmail(true); - try { - const signupClient = SignupClient.getInstance({ - baseURL: BASE_URL, - }); - const response = await signupClient.post( - `${ENDPOINT.USER_SERVICE.USERS}/exists`, - { email: formData.email }, - ); - - const exists = response.data; - setEmailAvailable(!exists); - if (exists) { - setErrors((prev) => ({ - ...prev, - email: "이미 사용 중인 이메일입니다.", - })); - } else { - setErrors((prev) => ({ ...prev, email: "" })); - } - } catch (error) { - console.error("이메일 중복 체크 오류:", error); - setErrors((prev) => ({ - ...prev, - email: "이메일 중복 체크 중 오류가 발생했습니다.", - })); - } finally { - setIsCheckingEmail(false); - } - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - console.log("회원가입 버튼 클릭됨 - handleSubmit"); - console.log("현재 폼 데이터:", formData); - - if (!validateForm()) { - console.log("폼 검증 실패"); - return; - } - - console.log("폼 검증 성공, 회원가입 API 호출 시작"); - setIsLoading(true); - try { - const signupData: UserSignupData = { - userId: formData.userId, - userName: formData.userName, - email: formData.email || undefined, - password: formData.password, - pswdHnt: formData.pswdHnt, - pswdAns: formData.pswdAns || undefined, - mphn: formData.mphn || undefined, - telno: formData.telno || undefined, - zip: formData.zip || undefined, - addr: formData.addr || undefined, - daddr: formData.daddr || undefined, - laeSeCd: formData.laeSeCd, - ogdpSeCd: formData.ogdpSeCd, - instSeCd: formData.instSeCd || undefined, - dtlInstCd: formData.dtlInstCd || undefined, - enoCd: formData.enoCd || undefined, - }; - - // SignupClient를 통해 익명사용자 회원가입 API 호출 - console.log("SignupClient 생성 및 API 호출:", { - baseURL: BASE_URL, - endpoint: ENDPOINT.USER_SERVICE.USER_JOIN, - data: signupData, - }); - - const signupClient = SignupClient.getInstance({ - baseURL: BASE_URL, - }); - const response = await signupClient.post( - ENDPOINT.USER_SERVICE.USER_JOIN, - signupData, - ); - - console.log("회원가입 성공:", response.data); - - // 성공 시 모달 닫기 및 콜백 호출 - onSuccess(); - onClose(); - - // 폼 초기화 - setFormData({ - userId: "", - userName: "", - email: "", - password: "", - confirmPassword: "", - pswdHnt: "", - pswdAns: "", - mphn: "", - telno: "", - zip: "", - addr: "", - daddr: "", - laeSeCd: "", - ogdpSeCd: "", - instSeCd: "", - dtlInstCd: "", - enoCd: "", - }); - setErrors({}); - setIdAvailable(null); - setEmailAvailable(null); - } catch (error: unknown) { - console.error("회원가입 오류:", error); - if (error instanceof Error) { - setErrors((prev) => ({ ...prev, submit: error.message })); - } else { - setErrors((prev) => ({ ...prev, submit: "회원가입에 실패했습니다." })); - } - } finally { - setIsLoading(false); - } - }; - - if (!open) return null; - - return ( -
-
-
-

- {currentStep === "agreement" ? "개인정보제공동의" : "회원가입"} -

- -
- - {currentStep === "agreement" ? ( -
-
-

개인정보 수집 및 이용 동의

-

회원가입을 위해 다음과 같이 개인정보를 수집 및 이용합니다.

- -

1. 수집하는 개인정보 항목

-
    -
  • - 필수항목: 아이디, 이름, 비밀번호, 비밀번호 힌트, 법인구분, - 기관구분 -
  • -
  • - 선택항목: 이메일, 휴대폰번호, 전화번호, 주소, 기관코드, - 상세기관코드, 사업자등록번호 -
  • -
- -

2. 개인정보의 수집 및 이용목적

-
    -
  • 회원가입 및 관리
  • -
  • 서비스 제공 및 운영
  • -
  • 고객상담 및 문의응답
  • -
- -

3. 개인정보의 보유 및 이용기간

-

- 회원탈퇴 시까지 (단, 관련 법령에 따라 보존이 필요한 경우 해당 - 기간까지) -

- -

4. 동의 거부권 및 거부 시 불이익

-

- 개인정보 수집 및 이용에 대한 동의를 거부할 수 있으며, 동의 거부 - 시 회원가입이 제한됩니다. -

-
- -
- -
- -
- - -
-
- ) : ( -
-
-

기본 정보

- -
-
- -
- - -
- {idAvailable === true && ( - - 사용 가능한 아이디입니다. - - )} - {errors.userId && ( - - {errors.userId} - - )} -
-
- -
-
- - - {errors.userName && ( - - {errors.userName} - - )} -
-
- -
-
- -
- - -
- {emailAvailable === true && ( - - 사용 가능한 이메일입니다. - - )} - {errors.email && ( - - {errors.email} - - )} -
-
- -
-
- - - {errors.password && ( - - {errors.password} - - )} -
-
- -
-
- - - {errors.confirmPassword && ( - - {errors.confirmPassword} - - )} -
-
- -
-
- - - setFormData((prev) => ({ ...prev, pswdHnt: value })) - } - disabled={isLoading} - placeholder="비밀번호 힌트를 선택하세요" - /> - {errors.pswdHnt && ( - - {errors.pswdHnt} - - )} -
-
- -
-
- - - {errors.pswdAns && ( - - {errors.pswdAns} - - )} -
-
-
- -
-

연락처 정보

- -
-
- - - {errors.mphn && ( - - {errors.mphn} - - )} -
-
- -
-
- - - {errors.telno && ( - - {errors.telno} - - )} -
-
-
- -
-

주소 정보

- -
-
- - - {errors.zip && ( - - {errors.zip} - - )} -
-
- -
-
- - - {errors.addr && ( - - {errors.addr} - - )} -
-
- -
-
- - - {errors.daddr && ( - - {errors.daddr} - - )} -
-
-
- -
-

기관 정보

- -
-
- - - setFormData((prev) => ({ ...prev, laeSeCd: value })) - } - disabled={isLoading} - placeholder="법인구분을 선택하세요" - /> - {errors.laeSeCd && ( - - {errors.laeSeCd} - - )} -
-
- -
-
- - - setFormData((prev) => ({ ...prev, ogdpSeCd: value })) - } - disabled={isLoading} - placeholder="기관구분을 선택하세요" - /> - {errors.ogdpSeCd && ( - - {errors.ogdpSeCd} - - )} -
-
- -
-
- - - {errors.instSeCd && ( - - {errors.instSeCd} - - )} -
-
- -
-
- - - {errors.dtlInstCd && ( - - {errors.dtlInstCd} - - )} -
-
-
- - {errors.submit && ( -
-
- {errors.submit} -
-
- )} - -
- - - -
-
- )} -
-
- ); -} - -export default UserSignupModal; diff --git a/apps/wr/src/features/auth/login/ui/signup-modal.css b/apps/wr/src/features/auth/login/ui/signup-modal.css deleted file mode 100644 index 0fb1585c9..000000000 --- a/apps/wr/src/features/auth/login/ui/signup-modal.css +++ /dev/null @@ -1,277 +0,0 @@ -/* 모달 오버레이 */ -.modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - padding: 20px; -} - -/* 모달 컨텐츠 */ -.modal-content { - background: white; - border-radius: 8px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); - max-width: 800px; - width: 100%; - max-height: 90vh; - overflow-y: auto; - position: relative; -} - -/* 모달 헤더 */ -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 20px 24px; - border-bottom: 1px solid #e5e7eb; - background-color: #f9fafb; - border-radius: 8px 8px 0 0; -} - -.modal-header h2 { - margin: 0; - font-size: 1.8rem; - font-weight: 600; - color: #1f2937; -} - -.close-button { - background: none; - border: none; - font-size: 1.5rem; - cursor: pointer; - color: #6b7280; - padding: 4px; - border-radius: 4px; - transition: color 0.2s; -} - -.close-button:hover { - color: #374151; -} - -/* 폼 스타일 */ -.signup-form { - padding: 24px; -} - -.form-section { - margin-bottom: 32px; -} - -.form-section h3 { - margin: 0 0 16px 0; - font-size: 1.4rem; - font-weight: 600; - color: #374151; - padding-bottom: 8px; - border-bottom: 2px solid #e5e7eb; -} - -.form-row { - margin-bottom: 16px; -} - -.form-group { - display: flex; - flex-direction: column; -} - -.form-group label { - margin-bottom: 6px; - font-weight: 500; - color: #374151; - font-size: 1.1rem; -} - -.form-group input, -.form-group select { - padding: 12px 14px; - border: 1px solid #d1d5db; - border-radius: 6px; - font-size: 1.1rem; - transition: border-color 0.2s, box-shadow 0.2s; - background-color: white; -} - -.form-group input:focus, -.form-group select:focus { - outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); -} - -.form-group input:disabled, -.form-group select:disabled { - background-color: #f3f4f6; - color: #6b7280; - cursor: not-allowed; -} - -/* 입력 필드와 버튼이 함께 있는 경우 */ -.input-with-button { - display: flex; - gap: 8px; - align-items: flex-end; -} - -.input-with-button input { - flex: 1; -} - -.check-button { - padding: 10px 16px; - background-color: #6b7280; - color: white; - border: none; - border-radius: 6px; - font-size: 0.8rem; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s; - white-space: nowrap; - min-width: 80px; -} - -.check-button:hover:not(:disabled) { - background-color: #4b5563; -} - -.check-button:disabled { - background-color: #d1d5db; - cursor: not-allowed; -} - -/* 메시지 스타일 */ -.error-message { - color: #dc2626; - font-size: 0.8rem; - margin-top: 4px; - display: block; -} - -.success-message { - color: #059669; - font-size: 0.8rem; - margin-top: 4px; - display: block; -} - -.submit-error { - background-color: #fef2f2; - border: 1px solid #fecaca; - color: #dc2626; - padding: 12px; - border-radius: 6px; - margin-bottom: 16px; - text-align: center; -} - -/* 폼 액션 버튼 */ -.form-actions { - display: flex; - gap: 12px; - justify-content: flex-end; - padding-top: 20px; - border-top: 1px solid #e5e7eb; - margin-top: 24px; -} - -.cancel-button, -.submit-button { - padding: 12px 24px; - border-radius: 6px; - font-weight: 500; - font-size: 0.9rem; - cursor: pointer; - transition: all 0.2s; - border: none; - min-width: 100px; -} - -.cancel-button { - background-color: #f3f4f6; - color: #374151; - border: 1px solid #d1d5db; -} - -.cancel-button:hover:not(:disabled) { - background-color: #e5e7eb; -} - -.submit-button { - background-color: #3b82f6; - color: white; -} - -.submit-button:hover:not(:disabled) { - background-color: #2563eb; -} - -.cancel-button:disabled, -.submit-button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* 반응형 디자인 */ -@media (max-width: 768px) { - .modal-content { - margin: 10px; - max-height: 95vh; - } - - .modal-header { - padding: 16px 20px; - } - - .signup-form { - padding: 20px; - } - - .form-actions { - flex-direction: column; - } - - .cancel-button, - .submit-button { - width: 100%; - } - - .input-with-button { - flex-direction: column; - align-items: stretch; - } - - .check-button { - width: 100%; - margin-top: 8px; - } -} - -/* 스크롤바 스타일 */ -.modal-content::-webkit-scrollbar { - width: 6px; -} - -.modal-content::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 3px; -} - -.modal-content::-webkit-scrollbar-thumb { - background: #c1c1c1; - border-radius: 3px; -} - -.modal-content::-webkit-scrollbar-thumb:hover { - background: #a8a8a8; -} diff --git a/apps/wr/src/features/auth/login/ui/user-signup-modal.css b/apps/wr/src/features/auth/login/ui/user-signup-modal.css deleted file mode 100644 index 241929a2a..000000000 --- a/apps/wr/src/features/auth/login/ui/user-signup-modal.css +++ /dev/null @@ -1,436 +0,0 @@ -/* 신규 사용자 가입 모달 오버레이 */ -.user-signup-modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - padding: 20px; -} - -/* 신규 사용자 가입 모달 컨텐츠 */ -.user-signup-modal-content { - background: white; - border-radius: 8px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); - max-width: 800px; - width: 100%; - max-height: 90vh; - overflow-y: auto; - position: relative; -} - -/* 신규 사용자 가입 모달 헤더 */ -.user-signup-modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 20px 24px; - border-bottom: 1px solid #e5e7eb; - background-color: #f9fafb; - border-radius: 8px 8px 0 0; -} - -.user-signup-modal-header h2 { - margin: 0; - font-size: 1.8rem; - font-weight: 600; - color: #1f2937; -} - -.user-signup-close-button { - background: none; - border: none; - font-size: 1.5rem; - cursor: pointer; - color: #6b7280; - padding: 4px; - border-radius: 4px; - transition: color 0.2s; -} - -.user-signup-close-button:hover { - color: #374151; -} - -/* 신규 사용자 가입 폼 스타일 */ -.user-signup-form { - padding: 24px; -} - -.user-signup-form-section { - margin-bottom: 32px; -} - -.user-signup-form-section h3 { - margin: 0 0 16px 0; - font-size: 1.4rem; - font-weight: 600; - color: #374151; - padding-bottom: 8px; - border-bottom: 2px solid #e5e7eb; -} - -.user-signup-form-row { - margin-bottom: 16px; -} - -.user-signup-form-group { - display: flex; - flex-direction: column; -} - -.user-signup-form-group label { - margin-bottom: 6px; - font-weight: 500; - color: #374151; - font-size: 1.1rem; -} - -.user-signup-form-group input, -.user-signup-form-group select { - padding: 12px 14px; - border: 1px solid #d1d5db; - border-radius: 6px; - font-size: 1.1rem; - transition: border-color 0.2s, box-shadow 0.2s; - background-color: white; -} - -/* CodeSelect 컴포넌트 스타일링 */ -.user-signup-form-group .CodeSelect { - width: 100%; -} - -.user-signup-form-group .CodeSelect select { - width: 100%; - padding: 12px 14px; - border: 1px solid #d1d5db; - border-radius: 6px; - font-size: 1.1rem; - transition: border-color 0.2s, box-shadow 0.2s; - background-color: white; -} - -.user-signup-form-group .CodeSelect select:focus { - outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); -} - -.user-signup-form-group .CodeSelect select:disabled { - background-color: #f3f4f6; - color: #6b7280; - cursor: not-allowed; -} - -/* Select 요소 스타일링 */ -.user-signup-select { - width: 100%; - padding: 12px 14px; - border: 1px solid #d1d5db; - border-radius: 6px; - font-size: 1.1rem; - transition: border-color 0.2s, box-shadow 0.2s; - background-color: white; - cursor: pointer; -} - -.user-signup-select:focus { - outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); -} - -.user-signup-select:disabled { - background-color: #f3f4f6; - color: #6b7280; - cursor: not-allowed; -} - -.user-signup-select option { - padding: 8px; - font-size: 1rem; -} - -.user-signup-form-group input:focus, -.user-signup-form-group select:focus { - outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); -} - -.user-signup-form-group input:disabled, -.user-signup-form-group select:disabled { - background-color: #f3f4f6; - color: #6b7280; - cursor: not-allowed; -} - -/* 입력 필드와 버튼이 함께 있는 경우 */ -.user-signup-input-with-button { - display: flex; - gap: 8px; - align-items: flex-end; -} - -.user-signup-input-with-button input { - flex: 1; -} - -.user-signup-check-button { - padding: 10px 16px; - background-color: #6b7280; - color: white; - border: none; - border-radius: 6px; - font-size: 0.8rem; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s; - white-space: nowrap; - min-width: 80px; -} - -.user-signup-check-button:hover:not(:disabled) { - background-color: #4b5563; -} - -.user-signup-check-button:disabled { - background-color: #d1d5db; - cursor: not-allowed; -} - -/* 메시지 스타일 */ -.user-signup-error-message { - color: #dc2626; - font-size: 0.8rem; - margin-top: 4px; - display: block; -} - -.user-signup-success-message { - color: #059669; - font-size: 0.8rem; - margin-top: 4px; - display: block; -} - -.user-signup-submit-error { - background-color: #fef2f2; - border: 1px solid #fecaca; - color: #dc2626; - padding: 12px; - border-radius: 6px; - margin-bottom: 16px; - text-align: center; -} - -/* 폼 액션 버튼 */ -.user-signup-form-actions { - display: flex; - gap: 12px; - justify-content: flex-end; - padding-top: 20px; - border-top: 1px solid #e5e7eb; - margin-top: 24px; -} - -.user-signup-cancel-button, -.user-signup-submit-button, -.user-signup-back-button { - padding: 12px 24px; - border-radius: 6px; - font-weight: 500; - font-size: 0.9rem; - cursor: pointer; - transition: all 0.2s; - border: none; - min-width: 100px; -} - -.user-signup-cancel-button { - background-color: #f3f4f6; - color: #374151; - border: 1px solid #d1d5db; -} - -.user-signup-cancel-button:hover:not(:disabled) { - background-color: #e5e7eb; -} - -.user-signup-submit-button { - background-color: #3b82f6; - color: white; -} - -.user-signup-submit-button:hover:not(:disabled) { - background-color: #2563eb; -} - -.user-signup-back-button { - background-color: #6b7280; - color: white; -} - -.user-signup-back-button:hover:not(:disabled) { - background-color: #4b5563; -} - -.user-signup-cancel-button:disabled, -.user-signup-submit-button:disabled, -.user-signup-back-button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* 개인정보제공동의 스타일 */ -.user-signup-agreement-content { - padding: 24px; -} - -.user-signup-agreement-text { - margin-bottom: 24px; -} - -.user-signup-agreement-text h3 { - margin: 0 0 16px 0; - font-size: 1.4rem; - font-weight: 600; - color: #374151; -} - -.user-signup-agreement-text h4 { - margin: 20px 0 8px 0; - font-size: 1.1rem; - font-weight: 600; - color: #374151; -} - -.user-signup-agreement-text p { - margin: 8px 0; - color: #6b7280; - line-height: 1.5; -} - -.user-signup-agreement-text ul { - margin: 8px 0; - padding-left: 20px; - color: #6b7280; -} - -.user-signup-agreement-text li { - margin: 4px 0; - line-height: 1.5; -} - -.user-signup-agreement-checkbox { - margin-bottom: 24px; - padding: 16px; - background-color: #f9fafb; - border-radius: 6px; - border: 1px solid #e5e7eb; -} - -.user-signup-agreement-checkbox label { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-weight: 500; - color: #374151; -} - -.user-signup-agreement-checkbox input[type="checkbox"] { - width: 18px; - height: 18px; - margin: 0; -} - -.user-signup-agreement-actions { - display: flex; - gap: 12px; - justify-content: flex-end; -} - -.user-signup-next-button { - padding: 12px 24px; - background-color: #3b82f6; - color: white; - border: none; - border-radius: 6px; - font-weight: 500; - font-size: 0.9rem; - cursor: pointer; - transition: background-color 0.2s; - min-width: 100px; -} - -.user-signup-next-button:hover:not(:disabled) { - background-color: #2563eb; -} - -.user-signup-next-button:disabled { - background-color: #d1d5db; - cursor: not-allowed; -} - -/* 반응형 디자인 */ -@media (max-width: 768px) { - .user-signup-modal-content { - margin: 10px; - max-height: 95vh; - } - - .user-signup-modal-header { - padding: 16px 20px; - } - - .user-signup-form { - padding: 20px; - } - - .user-signup-form-actions { - flex-direction: column; - } - - .user-signup-cancel-button, - .user-signup-submit-button, - .user-signup-back-button { - width: 100%; - } - - .user-signup-input-with-button { - flex-direction: column; - align-items: stretch; - } - - .user-signup-check-button { - width: 100%; - margin-top: 8px; - } -} - -/* 스크롤바 스타일 */ -.user-signup-modal-content::-webkit-scrollbar { - width: 6px; -} - -.user-signup-modal-content::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 3px; -} - -.user-signup-modal-content::-webkit-scrollbar-thumb { - background: #c1c1c1; - border-radius: 3px; -} - -.user-signup-modal-content::-webkit-scrollbar-thumb:hover { - background: #a8a8a8; -}