Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"plugins": ["prettier"],
"rules": {
"prettier/prettier": ["error"],
"no-unused-vars": "warn",
"@typescript-eslint/no-unused-vars": "warn"
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 λ‘˜μ΄ 같이 있으면 μΆ©λŒλ‚˜μ„œ κ²½κ³  밑쀄이 μ΄μƒν•˜κ²Œ λœ¨λ”λΌκ³ μš”
저희 μ–΄μ°¨ν”Ό typescript만 μ‚¬μš©ν•˜λ‹ˆκΉŒ μœ„μ—κ±΄ μ§€μ› μŠ΅λ‹ˆλ‹€.

}
}
14 changes: 0 additions & 14 deletions src/app/mypage/components/Button.module.css

This file was deleted.

18 changes: 0 additions & 18 deletions src/app/mypage/components/Button.tsx

This file was deleted.

27 changes: 19 additions & 8 deletions src/app/mypage/components/FileInput.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
import { useEffect } from 'react';
import { ChangeEvent, useState } from 'react';
import Image from 'next/image';
import { UseFormSetValue } from 'react-hook-form';
import { FormValues } from './ProfileForm';
import { UseFormSetValue, FieldValues, Path, PathValue } from 'react-hook-form';
import styles from './FileInput.module.css';

interface FileInputProps {
name: 'imgFile';
setValue: UseFormSetValue<FormValues>;
interface FileInputProps<T extends FieldValues> {
name: Path<T>;
setValue: UseFormSetValue<T>;
url?: string | null;
}

export default function FileInput({ name, setValue }: FileInputProps) {
const [preview, setPreview] = useState('');
export default function FileInput<T extends FieldValues>({
name,
setValue,
url,
}: FileInputProps<T>) {
const [preview, setPreview] = useState<string | null>(null);

const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setPreview(URL.createObjectURL(file));
setValue('image', file);
setValue(name, file as PathValue<T, Path<T>>);
}
};

useEffect(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use client μ΅œμƒμœ„μ— μ„ μ–Έ μ•ˆν•΄μ€˜λ„ λ™μž‘ν•˜λ‚˜λ³΄κ΅°μš”?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

λΆ€λͺ¨μ»΄ν¬λ„ŒνŠΈκ°€ 'use client' 둜 λ˜μ–΄μžˆμœΌλ©΄ ν•„μš” μ—†κΈ΄ ν•œλ°...
곡용으둜 λΉΌλ €λ©΄ κ³ λ €ν•΄λ³Ό μ‚¬ν•­μ΄λ„€μš”..!

if (url) {
setPreview(url);
}
}, [url]);

return (
<>
<label className={styles.label} htmlFor={name}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
}

.button {
line-height: 24px;
margin-top: 24px;
padding: 15px 113.5px;
}
Expand Down
1 change: 1 addition & 0 deletions src/app/mypage/components/Input.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
}

.input:read-only {
color: var(--gray-400);
outline: none;
}

Expand Down
28 changes: 17 additions & 11 deletions src/app/mypage/components/PasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'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';
import Button from '@/components/Button';
import { ERROR_MESSAGES } from '../constants/message';
import styles from './Form.module.css';
import { updatePassword } from '../lib/authHelper';

interface FormValues {
export interface PasswordFormValues {
currentPassword: string;
newPassword: string;
newPasswordConfirmation: string;
Expand All @@ -16,14 +16,18 @@ export default function PasswordForm() {
const {
register,
handleSubmit,
formState: { errors, isValid },
formState: { errors },
watch,
trigger,
} = useForm<FormValues>({ mode: 'onChange' });
setError,
reset,
} = useForm<PasswordFormValues>({ mode: 'onChange' });

const customIsValid = Object.keys(errors).length === 0;
const watchedPassword = watch('newPassword');

const onSubmit = () => {};
const onSubmit = async (data: PasswordFormValues) =>
updatePassword(data, reset, setError);

useEffect(() => {
if (watchedPassword) {
Expand All @@ -40,6 +44,8 @@ export default function PasswordForm() {
name="currentPassword"
label="ν˜„μž¬ λΉ„λ°€λ²ˆν˜Έ"
placeholder="λΉ„λ°€λ²ˆν˜Έ μž…λ ₯"
register={register('currentPassword')}
error={errors.currentPassword}
/>
<Input
className={styles.input}
Expand All @@ -50,7 +56,7 @@ export default function PasswordForm() {
register={register('newPassword', {
minLength: {
value: 8,
message: 'λΉ„λ°€λ²ˆν˜Έλ₯Ό 8자 이상 μž…λ ₯ν•΄μ£Όμ„Έμš”.',
message: ERROR_MESSAGES.PASSWORD_TOO_SHORT,
},
})}
error={errors.newPassword}
Expand All @@ -63,12 +69,12 @@ export default function PasswordForm() {
register={register('newPasswordConfirmation', {
validate: {
matchesPassword: (value) =>
value === watchedPassword || 'λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.',
value === watchedPassword || ERROR_MESSAGES.PASSWORDS_MATCH,
},
})}
error={errors.newPasswordConfirmation}
/>
<Button className={styles.button} type="submit" disabled={!isValid}>
<Button className={styles.button} type="submit" disabled={!customIsValid}>
λ³€κ²½
</Button>
</form>
Expand Down
28 changes: 0 additions & 28 deletions src/app/mypage/components/ProfileForm.module.css

This file was deleted.

48 changes: 36 additions & 12 deletions src/app/mypage/components/ProfileForm.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
'use client';

import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import Button from './Button';
import Input from './Input';
import styles from './ProfileForm.module.css';
import useAuth from '../hooks/useAuth';
import FileInput from './FileInput';
import Input from './Input';
import Button from '@/components/Button';
import { updateProfile } from '../lib/authHelper';
import styles from './Form.module.css';

export interface FormValues {
image: File;
export interface ProfileFormValues {
image: File | null;
email: string;
nickname: string;
}

export default function ProfileForm() {
const { handleSubmit, setValue } = useForm<FormValues>();
const { user } = useAuth();
const {
register,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<ProfileFormValues>();

const onSubmit = () => {};
const customIsValid = Object.keys(errors).length === 0;

const onSubmit = async (data: ProfileFormValues) => updateProfile(data);

useEffect(() => {
if (user) {
const { email, nickname } = user;
reset({ email, nickname });
}
}, [reset, user]);

return (
<form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
<h2>ν”„λ‘œν•„</h2>
<FileInput name="imgFile" setValue={setValue} />
<FileInput<ProfileFormValues>
name="image"
setValue={setValue}
url={user?.profileImageUrl}
/>
<Input
className={styles.input}
type="text"
name="email"
label="이메일"
placeholder="test@email.com"
register={register('email')}
readOnly
/>
<Input
Expand All @@ -35,8 +56,11 @@ export default function ProfileForm() {
name="nickname"
label="λ‹‰λ„€μž„"
placeholder="λ‹‰λ„€μž„ μž…λ ₯"
register={register('nickname')}
/>
<Button className={styles.button}>μ €μž₯</Button>
<Button className={styles.button} type="submit" disabled={!customIsValid}>
μ €μž₯
</Button>
</form>
);
}
6 changes: 6 additions & 0 deletions src/app/mypage/constants/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const ERROR_MESSAGES = {
CURRENT_PASSWORD_INCORRECT: 'ν˜„μž¬ λΉ„λ°€λ²ˆν˜Έκ°€ ν‹€λ ΈμŠ΅λ‹ˆλ‹€.',
PASSWORDS_MATCH: 'λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.',
SAME_AS_OLD_PASSWORD: 'κΈ°μ‘΄ λΉ„λ°€λ²ˆν˜Έμ™€ λ™μΌν•©λ‹ˆλ‹€.',
PASSWORD_TOO_SHORT: 'λΉ„λ°€λ²ˆν˜Έλ₯Ό 8자 이상 μž…λ ₯ν•΄μ£Όμ„Έμš”.',
};
46 changes: 46 additions & 0 deletions src/app/mypage/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import useAuthStore from '../store/authStore';
import axios from '../lib/axios';

const useAuth = () => {
const { user, accessToken, setUser, setAccessToken } = useAuthStore();
const isAuthenticated = !!accessToken;

const getMe = async () => {
try {
const response = await axios.get('/users/me');
console.log('user:', response.data);
setUser(response.data);
} catch (error) {
throw error;
}
};

const login = async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 개발 μž„μ‹œμš©μΈκ±ΈκΉŒμš”?!

try {
const response = await axios.post('/auth/login', {
email: 'bono@example.com',
password: '12341234',
});
setAccessToken(response.data.accessToken);
} catch (error) {
throw error;
}
};

const logout = async () => {
const setAccessToken = useAuthStore.getState().setAccessToken;

setAccessToken(null);
};

return {
user,
accessToken,
isAuthenticated,
getMe,
login,
logout,
};
};

export default useAuth;
62 changes: 62 additions & 0 deletions src/app/mypage/lib/authHelper.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ—¬κΈ°μ„œ ν–‰ν•΄μ§€λŠ” λ‚΄μš©μ„λ³΄λ©΄ profileμ΄λž‘ λΉ„λ°€λ²ˆν˜Έ μ—…λ°μ΄νŠΈ ν•˜λŠ” apiν˜ΈμΆœμ„ ν•˜λŠ”κ²ƒ 같은데
파일λͺ…이 authHelperλΌμ„œ μ•½κ°„ 음... μ’€ λ‹€λ₯ΈλŠλ‚Œ?이 λ“€κΈ΄ν•©λ‹ˆλ‹€
auth라고 ν•˜λ©΄ 인증/인가 κ΄€λ ¨λœ 무언가 일것같은데 보면은 μ‚¬μš©μžμ •λ³΄ μ—…λ°μ΄νŠΈ ν•˜λŠ” λ‚΄μš©μ΄λ“€μ–΄μžˆμ–΄κ°€μ§€κ΅¬..

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파일λͺ…μ—λŒ€ν•΄ ν•œλ²ˆ 더 μƒκ°ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.. μ–΄λ ΅λ„€μš” πŸ€”

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import axios from './axios';
import { UseFormSetError } from 'react-hook-form';
import useAuthStore from '../store/authStore';
import { ERROR_MESSAGES } from '../constants/message';
import { ProfileFormValues } from '../components/ProfileForm';
import { PasswordFormValues } from '../components/PasswordForm';

export const updateProfile = async (data: ProfileFormValues) => {
const { image, nickname } = data;
const setUser = useAuthStore.getState().setUser;

let url = null;
try {
if (image) {
const formData = new FormData();
formData.append('image', image);
const response = await axios.post('/users/me/image', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
url = response.data.profileImageUrl;
}
const response = await axios.put('/users/me', {
nickname,
...(url && { profileImageUrl: url }),
});
setUser(response.data);
} catch (error) {
if (error instanceof Error) {
}
Comment on lines +30 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μš”λ‚΄μš©μ€ 아직 μ•ˆμ±„μ›Œμ§„κ±°μ£ ?γ…‹γ…‹

}
};

export const updatePassword = async (
data: PasswordFormValues,
reset: () => void,
setError: UseFormSetError<PasswordFormValues>
) => {
try {
await axios.put('/auth/password', {
password: data.currentPassword,
newPassword: data.newPassword,
});
reset();
} catch (error) {
if (error instanceof Error) {
if (error.message === ERROR_MESSAGES.CURRENT_PASSWORD_INCORRECT) {
setError('currentPassword', {
type: 'manual',
message: error.message,
});
}
if (error.message === ERROR_MESSAGES.SAME_AS_OLD_PASSWORD) {
setError('newPassword', {
type: 'manual',
message: error.message,
});
}
}
}
};
Loading