diff --git a/src/app/mypage/components/Button.module.css b/src/app/mypage/components/Button.module.css new file mode 100644 index 0000000..98e57e4 --- /dev/null +++ b/src/app/mypage/components/Button.module.css @@ -0,0 +1,14 @@ +.button { + width: 100%; + background-color: var(--violet); + border-radius: 8px; + font-size: 14px; + font-weight: 600; + line-height: 24px; + color: var(--white); +} + +.button:disabled { + background-color: var(--gray-400); + cursor: auto; +} diff --git a/src/app/mypage/components/Button.tsx b/src/app/mypage/components/Button.tsx new file mode 100644 index 0000000..cf832c9 --- /dev/null +++ b/src/app/mypage/components/Button.tsx @@ -0,0 +1,18 @@ +import { ButtonHTMLAttributes, PropsWithChildren } from 'react'; +import styles from './Button.module.css'; + +interface ButtonProps extends ButtonHTMLAttributes { + className?: string; +} + +export default function Button({ + className = '', + children, + ...props +}: PropsWithChildren) { + return ( + + ); +} diff --git a/src/app/mypage/components/FileInput.module.css b/src/app/mypage/components/FileInput.module.css new file mode 100644 index 0000000..f9a08af --- /dev/null +++ b/src/app/mypage/components/FileInput.module.css @@ -0,0 +1,32 @@ +.label { + display: flex; + justify-content: center; + align-items: center; + width: 100px; + height: 100px; + border-radius: 8px; + background-color: var(--gray-200); + cursor: pointer; + position: relative; +} + +.iconContainer { + position: relative; + width: 20px; + height: 20px; +} + +.input { + display: none; +} + +@media screen and (min-width: 768px) { + .label { + width: 182px; + height: 182px; + } + .imageContainer { + width: 30px; + height: 30px; + } +} diff --git a/src/app/mypage/components/FileInput.tsx b/src/app/mypage/components/FileInput.tsx new file mode 100644 index 0000000..2872e21 --- /dev/null +++ b/src/app/mypage/components/FileInput.tsx @@ -0,0 +1,44 @@ +import { ChangeEvent, useState } from 'react'; +import Image from 'next/image'; +import { UseFormSetValue } from 'react-hook-form'; +import { FormValues } from './ProfileForm'; +import styles from './FileInput.module.css'; + +interface FileInputProps { + name: 'imgFile'; + setValue: UseFormSetValue; +} + +export default function FileInput({ name, setValue }: FileInputProps) { + const [preview, setPreview] = useState(''); + + const handleChange = (event: ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + setPreview(URL.createObjectURL(file)); + setValue('image', file); + } + }; + + return ( + <> + + + + ); +} diff --git a/src/app/mypage/components/Input.module.css b/src/app/mypage/components/Input.module.css new file mode 100644 index 0000000..5d17657 --- /dev/null +++ b/src/app/mypage/components/Input.module.css @@ -0,0 +1,44 @@ +.container { + display: flex; + flex-direction: column; + gap: 8px; +} + +.inputWrapper { + position: relative; +} + +.input { + width: 100%; + border: 1px solid var(--gray-300); + border-radius: 8px; + font-size: 16px; + font-weight: 400; + line-height: 26px; + color: var(--black-100); + padding: 12px 16px; +} + +.input:read-only { + outline: none; +} + +.input:focus:not(:read-only) { + outline-color: var(--violet); +} + +.input::placeholder { + color: var(--gray-400); +} + +.errorFocus { + outline: 1px solid var(--red); +} + +.error { + display: block; + font-size: 14px; + font-weight: 400; + line-height: 24px; + color: var(--red); +} diff --git a/src/app/mypage/components/Input.tsx b/src/app/mypage/components/Input.tsx new file mode 100644 index 0000000..d2bc323 --- /dev/null +++ b/src/app/mypage/components/Input.tsx @@ -0,0 +1,45 @@ +import { ReactNode } from 'react'; +import { FieldError, UseFormRegisterReturn } from 'react-hook-form'; +import Label from './Label'; +import styles from './Input.module.css'; + +interface InputProps { + type: string; + name: string; + className?: string; + label?: string; + placeholder?: string; + children?: ReactNode; + register?: UseFormRegisterReturn; + error?: FieldError; + readOnly?: boolean; +} + +export default function Input({ + type, + name, + className = '', + label = '', + placeholder = '', + children, + register, + error, + readOnly = false, +}: InputProps) { + return ( +
+ +
+ + {children} + {error && {error.message}} +
+
+ ); +} diff --git a/src/app/mypage/components/Label.module.css b/src/app/mypage/components/Label.module.css new file mode 100644 index 0000000..d8a06fa --- /dev/null +++ b/src/app/mypage/components/Label.module.css @@ -0,0 +1,13 @@ +.label { + font-size: 14px; + font-weight: 400; + line-height: 24px; + color: var(--black-100); +} + +@media screen and (min-width: 768px) { + .label { + font-size: 16px; + line-height: 26px; + } +} diff --git a/src/app/mypage/components/Label.tsx b/src/app/mypage/components/Label.tsx new file mode 100644 index 0000000..95065af --- /dev/null +++ b/src/app/mypage/components/Label.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from 'react'; +import styles from './Label.module.css'; + +interface LabelProps { + htmlFor: string; + children?: ReactNode; +} + +export default function Label({ htmlFor, children }: LabelProps) { + return ( + + ); +} diff --git a/src/app/mypage/components/PasswordForm.module.css b/src/app/mypage/components/PasswordForm.module.css new file mode 100644 index 0000000..4e9c1e3 --- /dev/null +++ b/src/app/mypage/components/PasswordForm.module.css @@ -0,0 +1,28 @@ +.form h2 { + font-size: 18px; + font-weight: 700; + line-height: 26px; + color: var(--black-100); + margin-bottom: 40px; +} + +.input { + margin-bottom: 16px; +} + +.button { + margin-top: 24px; + padding: 15px 113.5px; +} + +@media screen and (min-width: 768px) { + .form h2 { + font-size: 24px; + line-height: 32px; + margin-bottom: 24px; + } + + .button { + padding: 14px 236px; + } +} diff --git a/src/app/mypage/components/PasswordForm.tsx b/src/app/mypage/components/PasswordForm.tsx new file mode 100644 index 0000000..55cbdbc --- /dev/null +++ b/src/app/mypage/components/PasswordForm.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import Input from './Input'; +import Button from './Button'; +import styles from './PasswordForm.module.css'; + +interface FormValues { + currentPassword: string; + newPassword: string; + newPasswordConfirmation: string; +} + +export default function PasswordForm() { + const { + register, + handleSubmit, + formState: { errors, isValid }, + watch, + trigger, + } = useForm({ mode: 'onChange' }); + + const watchedPassword = watch('newPassword'); + + const onSubmit = () => {}; + + useEffect(() => { + if (watchedPassword) { + trigger('newPasswordConfirmation'); + } + }, [watchedPassword, trigger]); + + return ( +
+

비밀번호 변경

+ + + + value === watchedPassword || '비밀번호가 일치하지 않습니다.', + }, + })} + error={errors.newPasswordConfirmation} + /> + +
+ ); +} diff --git a/src/app/mypage/components/ProfileForm.module.css b/src/app/mypage/components/ProfileForm.module.css new file mode 100644 index 0000000..4e9c1e3 --- /dev/null +++ b/src/app/mypage/components/ProfileForm.module.css @@ -0,0 +1,28 @@ +.form h2 { + font-size: 18px; + font-weight: 700; + line-height: 26px; + color: var(--black-100); + margin-bottom: 40px; +} + +.input { + margin-bottom: 16px; +} + +.button { + margin-top: 24px; + padding: 15px 113.5px; +} + +@media screen and (min-width: 768px) { + .form h2 { + font-size: 24px; + line-height: 32px; + margin-bottom: 24px; + } + + .button { + padding: 14px 236px; + } +} diff --git a/src/app/mypage/components/ProfileForm.tsx b/src/app/mypage/components/ProfileForm.tsx new file mode 100644 index 0000000..4e55715 --- /dev/null +++ b/src/app/mypage/components/ProfileForm.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { useForm } from 'react-hook-form'; +import Button from './Button'; +import Input from './Input'; +import styles from './ProfileForm.module.css'; +import FileInput from './FileInput'; + +export interface FormValues { + image: File; + email: string; + nickname: string; +} + +export default function ProfileForm() { + const { handleSubmit, setValue } = useForm(); + + const onSubmit = () => {}; + + return ( +
+

프로필

+ + + + + + ); +} diff --git a/src/app/mypage/layout.tsx b/src/app/mypage/layout.tsx new file mode 100644 index 0000000..e3a0137 --- /dev/null +++ b/src/app/mypage/layout.tsx @@ -0,0 +1,5 @@ +import { ReactNode } from 'react'; + +export default function Layout({ children }: { children: ReactNode }) { + return <>{children}; +} diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx new file mode 100644 index 0000000..4451f35 --- /dev/null +++ b/src/app/mypage/page.tsx @@ -0,0 +1,11 @@ +import PasswordForm from './components/PasswordForm'; +import ProfileForm from './components/ProfileForm'; + +export default function MyPage() { + return ( + <> + + + + ); +}