Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ffe3017
โœจ feat: ๋งˆ์ดํŽ˜์ด์ง€ ์ด๋ฆ„๋ณ€๊ฒฝ, ํŽ˜์ด์ง€ ์„œ๋ฒ„๋กœ ๋ณ€๊ฒฝ
HaeJungg Nov 14, 2024
15249d9
Merge branch 'develop' into Feat/125/MyPageProfile
HaeJungg Nov 14, 2024
a0a4aef
๐Ÿ› fix: User ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ํ• ๋‹นํ•˜๋„๋ก ์ˆ˜์ •
HaeJungg Nov 14, 2024
79105a1
โœจ feat: ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ
HaeJungg Nov 14, 2024
fe90461
โœจ feat: ํšŒ์›์ •๋ณด ์ˆ˜์ • api์ถ”๊ฐ€
HaeJungg Nov 15, 2024
7c7efbd
๐Ÿš‘ fix: getuser ์ˆ˜์ •
HaeJungg Nov 18, 2024
a54e378
๐Ÿ“ฆ chore: ์ด๋ฏธ์ง€ ํŒŒ์ผ ๋ณ€๊ฒฝ
HaeJungg Nov 18, 2024
0250543
โœจ feat: 200์ด ๋นˆ ๊ฐ์ฒด๋กœ ์˜ฌ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
HaeJungg Nov 18, 2024
f867559
๐Ÿ’„ design: ํ”„๋กœํ•„ ํ…Œ๋‘๋ฆฌ ๋””์ž์ธ ์ถ”๊ฐ€
HaeJungg Nov 18, 2024
92b1af1
โœจ feat: ์บ์‹œ๋ฅผ ์ง€์šฐ๊ณ  ์ƒˆ๋กœ ํ”„๋กœํ•„ ํŒจ์น˜๋ฅผ ํ•˜๊ธฐ ์œ„ํ•œ api ์ถ”๊ฐ€
HaeJungg Nov 18, 2024
20522d6
โœจ feat: ํ”„๋กœํ•„ ์ปดํฌ๋„ŒํŠธ ๊ฐœ์„ , edit ๊ฒฝ์šฐ ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ •
HaeJungg Nov 18, 2024
7a72219
๐Ÿ’„ design: ํ—ค๋” 360์—์„œ๋„ ๊นจ์ง€์ง€ ์•Š๋„๋ก ์ˆ˜์ •
HaeJungg Nov 18, 2024
8e373cf
โœจ feat: ํ—ค๋” ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—ฐ๊ฒฐ
HaeJungg Nov 18, 2024
8b87010
โœจ feat: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ˆ˜์ • api ์ถ”๊ฐ€, ๋“œ๋กญ๋‹ค์šด ์ถ”๊ฐ€
HaeJungg Nov 18, 2024
f92e139
๐Ÿ› fix: ์Šคํ† ๋ฆฌ๋ถ ์ˆ˜์ •
HaeJungg Nov 18, 2024
843083e
๐Ÿ› fix: ํ—ค๋” ์Šคํ† ๋ฆฌ๋ถ ์ˆ˜์ •
HaeJungg Nov 18, 2024
47b8c5f
โœจ feat: auth store์— rehydration ์ฒ˜๋ฆฌ ์ถ”๊ฐ€
HaeJungg Nov 18, 2024
6168d99
๐Ÿ› fix: rehydration ํ›„ ์ธ์ฆ ๋ฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ฒ˜๋ฆฌ ์ถ”๊ฐ€
HaeJungg Nov 18, 2024
d157a7e
Merge branch 'develop' into Feat/125/MyPageProfile
HaeJungg Nov 18, 2024
2b952f8
โœจ feat: ๊ธฐ๋ณธ ํ”„๋กœํ•„๋กœ ๋Œ์•„๊ฐ€๋Š” api ์ถ”๊ฐ€
HaeJungg Nov 19, 2024
8cd3ccd
โœจ feat: ์ดˆ๊ธฐ ์ด๋ฏธ์ง€๋กœ ๋„˜์–ด๊ฐ€๋Š” api ์—ฐ๊ฒฐ
HaeJungg Nov 19, 2024
92f9509
Merge branch 'develop' into Feat/125/MyPageProfile
HaeJungg Nov 19, 2024
edbb817
๐Ÿ› fix: lint error ์ˆ˜์ •
HaeJungg Nov 19, 2024
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
4 changes: 4 additions & 0 deletions public/assets/icons/ic-edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 0 additions & 5 deletions public/assets/icons/profile-edit.svg

This file was deleted.

31 changes: 30 additions & 1 deletion src/_apis/auth/user-apis.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import { fetchApi } from '@/src/utils/api';
import { User } from '@/src/types/auth';

export function getUser(): Promise<{ data: User }> {
export function getUser(): Promise<User> {
return fetchApi<{ data: User }>('/auths/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then((response) => response.data);
}

// ํšŒ์›์ •๋ณด ์ˆ˜์ •
export async function updateUserProfile(file: File): Promise<void> {
const url = `/auths/user`;

const formData = new FormData();
formData.append('file', file);

await fetchApi(url, {
method: 'PUT',
body: formData,
});
}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

ํŒŒ์ผ ์—…๋กœ๋“œ ๊ตฌํ˜„์— ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”:

  1. multipart/form-data Content-Type ํ—ค๋”๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  2. ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๊ฐ€ ๊ตฌํ˜„๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •์„ ์ œ์•ˆ๋“œ๋ฆฝ๋‹ˆ๋‹ค:

 export async function updateUserProfile(file: File): Promise<void> {
   const url = `/auths/user`;
 
   const formData = new FormData();
   formData.append('file', file);
 
   await fetchApi(url, {
     method: 'PUT',
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    },
     body: formData,
-  });
+  }).catch((error) => {
+    console.error('ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
+    throw error;
+  });
 }

Committable suggestion skipped: line range outside the PR's diff.


export async function fetchUpdatedUser() {
return fetchApi<{ data: User }>('/auths/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
},
})
.then((response) => {
return response.data;
})
.catch((error) => {
throw error;
});
}
Comment on lines +26 to +40
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

์ฝ”๋“œ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

getUser์™€ fetchUpdatedUser ํ•จ์ˆ˜๊ฐ€ ์ƒ๋‹น ๋ถ€๋ถ„ ์ค‘๋ณต๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณตํ†ต ๋กœ์ง์„ ์ถ”์ถœํ•˜์—ฌ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฆฌํŒฉํ† ๋ง์„ ์ œ์•ˆ๋“œ๋ฆฝ๋‹ˆ๋‹ค:

+interface GetUserOptions {
+  skipCache?: boolean;
+}
+
+async function getUserBase(options: GetUserOptions = {}): Promise<User> {
+  const headers: Record<string, string> = {
+    'Content-Type': 'application/json',
+  };
+
+  if (options.skipCache) {
+    headers['Cache-Control'] = 'no-cache';
+  }
+
+  return fetchApi<{ data: User }>('/auths/user', {
+    method: 'GET',
+    headers,
+  })
+    .then((response) => response.data)
+    .catch((error) => {
+      console.error('์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
+      throw error;
+    });
+}
+
-export function getUser(): Promise<User> {
-  return fetchApi<{ data: User }>('/auths/user', {
-    method: 'GET',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-  }).then((response) => response.data);
+export function getUser(): Promise<User> {
+  return getUserBase();
 }
 
-export async function fetchUpdatedUser() {
-  return fetchApi<{ data: User }>('/auths/user', {
-    method: 'GET',
-    headers: {
-      'Content-Type': 'application/json',
-      'Cache-Control': 'no-cache',
-    },
-  })
-    .then((response) => {
-      return response.data;
-    })
-    .catch((error) => {
-      throw error;
-    });
+export async function fetchUpdatedUser(): Promise<User> {
+  return getUserBase({ skipCache: true });
 }
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function fetchUpdatedUser() {
return fetchApi<{ data: User }>('/auths/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
},
})
.then((response) => {
return response.data;
})
.catch((error) => {
throw error;
});
}
interface GetUserOptions {
skipCache?: boolean;
}
async function getUserBase(options: GetUserOptions = {}): Promise<User> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (options.skipCache) {
headers['Cache-Control'] = 'no-cache';
}
return fetchApi<{ data: User }>('/auths/user', {
method: 'GET',
headers,
})
.then((response) => response.data)
.catch((error) => {
console.error('์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
throw error;
});
}
export function getUser(): Promise<User> {
return getUserBase();
}
export async function fetchUpdatedUser(): Promise<User> {
return getUserBase({ skipCache: true });
}

75 changes: 75 additions & 0 deletions src/app/(crew)/my-page/_components/profile-card/container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use client';

import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useRouter } from 'next/navigation';
import { fetchUpdatedUser, updateUserProfile } from '@/src/_apis/auth/user-apis';
import { useAuthStore } from '@/src/store/use-auth-store';
import { User } from '@/src/types/auth';
import ProfileCardPresenter from './presenter';

export default function ProfileCard() {
const router = useRouter();
const { isAuth, setUser } = useAuthStore();
const [user, setLocalUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [profileImageUrl, setProfileImageUrl] = useState<string>('');

useEffect(() => {
const timer = setTimeout(async () => {
if (!isAuth) {
router.push('/login');
} else {
try {
const updatedUser = await fetchUpdatedUser();
setLocalUser(updatedUser);
setUser(updatedUser);
setProfileImageUrl(updatedUser.profileImageUrl);
setIsLoading(false);
} catch (error) {
toast.error('์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}
}
}, 500);

return () => clearTimeout(timer);
}, [isAuth, router, setUser]);
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

useEffect ๋‚ด ํƒ€์ด๋จธ ์‚ฌ์šฉ๊ณผ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ์ ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ๋ถˆํ•„์š”ํ•œ 500ms ์ง€์—ฐ์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ €ํ•˜์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ๋น„๋™๊ธฐ ์ž‘์—… ์ค‘ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋  ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ๋„ˆ๋ฌด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ์ œ์•ˆ๋“œ๋ฆฝ๋‹ˆ๋‹ค:

 useEffect(() => {
-  const timer = setTimeout(async () => {
+  let isMounted = true;
+  
+  const fetchUser = async () => {
     if (!isAuth) {
       router.push('/login');
     } else {
       try {
         const updatedUser = await fetchUpdatedUser();
+        if (!isMounted) return;
         setLocalUser(updatedUser);
         setUser(updatedUser);
         setProfileImageUrl(updatedUser.profileImageUrl);
         setIsLoading(false);
       } catch (error) {
-        toast.error('์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
+        if (!isMounted) return;
+        if (error instanceof Error) {
+          toast.error(`์œ ์ € ์ •๋ณด ์กฐํšŒ ์‹คํŒจ: ${error.message}`);
+        } else {
+          toast.error('์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
+        }
       }
     }
-  }, 500);
+  };
+  
+  fetchUser();
 
-  return () => clearTimeout(timer);
+  return () => {
+    isMounted = false;
+  };
 }, [isAuth, router, setUser]);
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const timer = setTimeout(async () => {
if (!isAuth) {
router.push('/login');
} else {
try {
const updatedUser = await fetchUpdatedUser();
setLocalUser(updatedUser);
setUser(updatedUser);
setProfileImageUrl(updatedUser.profileImageUrl);
setIsLoading(false);
} catch (error) {
toast.error('์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}
}
}, 500);
return () => clearTimeout(timer);
}, [isAuth, router, setUser]);
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
if (!isAuth) {
router.push('/login');
} else {
try {
const updatedUser = await fetchUpdatedUser();
if (!isMounted) return;
setLocalUser(updatedUser);
setUser(updatedUser);
setProfileImageUrl(updatedUser.profileImageUrl);
setIsLoading(false);
} catch (error) {
if (!isMounted) return;
if (error instanceof Error) {
toast.error(`์œ ์ € ์ •๋ณด ์กฐํšŒ ์‹คํŒจ: ${error.message}`);
} else {
toast.error('์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}
}
}
};
fetchUser();
return () => {
isMounted = false;
};
}, [isAuth, router, setUser]);


if (isLoading) return <div>๋กœ๋”ฉ ์ค‘...</div>;

if (!user) return <div>์œ ์ € ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.</div>;

// ํŒŒ์ผ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ
const handleEdit = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.png,.jpg,.jpeg';
input.onchange = async (event) => {
const file = (event.target as HTMLInputElement)?.files?.[0];
if (file) {
if (file.size > 5 * 1024 * 1024) {
alert('5MB ์ดํ•˜์˜ ํŒŒ์ผ๋งŒ ์—…๋กœ๋“œ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.');
return;
}

try {
await updateUserProfile(file);

const tempUrl = URL.createObjectURL(file);
setProfileImageUrl(tempUrl);

const updatedUser = await fetchUpdatedUser();

const newProfileImageUrl = `${updatedUser.profileImageUrl}?timestamp=${new Date().getTime()}`;
setProfileImageUrl(newProfileImageUrl);
setUser({ ...updatedUser, profileImageUrl: newProfileImageUrl });
} catch (error) {
toast.error('ํŒŒ์ผ ์—…๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}
}
};
input.click();
};
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ ๋กœ์ง์˜ ๋ณด์•ˆ ๋ฐ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ˜„์žฌ ๊ตฌํ˜„์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

  1. ํŒŒ์ผ ํ˜•์‹ ๊ฒ€์ฆ ๊ฐ•ํ™”
  2. ์—…๋กœ๋“œ ์ง„ํ–‰ ์ƒํƒœ ํ‘œ์‹œ
  3. URL.createObjectURL ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ์ œ์•ˆ๋“œ๋ฆฝ๋‹ˆ๋‹ค:

+const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/jpg', 'image/png'];
+const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB

 const handleEdit = () => {
   const input = document.createElement('input');
   input.type = 'file';
   input.accept = '.png,.jpg,.jpeg';
   input.onchange = async (event) => {
     const file = (event.target as HTMLInputElement)?.files?.[0];
     if (file) {
-      if (file.size > 5 * 1024 * 1024) {
+      if (!ALLOWED_FILE_TYPES.includes(file.type)) {
+        toast.error('์ง€์›ํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ํ˜•์‹์ž…๋‹ˆ๋‹ค.');
+        return;
+      }
+      
+      if (file.size > MAX_FILE_SIZE) {
         alert('5MB ์ดํ•˜์˜ ํŒŒ์ผ๋งŒ ์—…๋กœ๋“œ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.');
         return;
       }

       try {
+        setIsLoading(true);
         await updateUserProfile(file);

         const tempUrl = URL.createObjectURL(file);
+        // Clean up the temporary URL when we're done with it
+        setTimeout(() => URL.revokeObjectURL(tempUrl), 1000);
         setProfileImageUrl(tempUrl);

         const updatedUser = await fetchUpdatedUser();

         const newProfileImageUrl = `${updatedUser.profileImageUrl}?timestamp=${new Date().getTime()}`;
         setProfileImageUrl(newProfileImageUrl);
         setUser({ ...updatedUser, profileImageUrl: newProfileImageUrl });
+        toast.success('ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
       } catch (error) {
         toast.error('ํŒŒ์ผ ์—…๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
+      } finally {
+        setIsLoading(false);
       }
     }
   };
   input.click();
 };

Committable suggestion skipped: line range outside the PR's diff.


return <ProfileCardPresenter data={{ ...user, profileImageUrl }} onEdit={handleEdit} />;
}
50 changes: 50 additions & 0 deletions src/app/(crew)/my-page/_components/profile-card/presenter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client';

import { Menu } from '@mantine/core';
import { Profile } from '@/src/components/common/profile';
import { UserType } from '@/src/types/user';

export interface ProfileCardProps {
data: UserType;
onEdit: () => void;
}

export default function ProfileCardPresenter({ data, onEdit }: ProfileCardProps) {
return (
<div className="flex items-end justify-between">
<div className="flex items-center gap-6.5">
<figure className="h-20 w-20 md:h-30 md:w-30 lg:h-30 lg:w-30">
<Menu
shadow="sm"
width={170}
offset={-5}
withArrow
arrowPosition="side"
position="bottom-start"
>
<Menu.Target>
<div className="h-full w-full">
<Profile editable imageUrl={data?.profileImageUrl ?? ''} />
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๋กœ๋”ฉ ๋ฐ ์—๋Ÿฌ ์ƒํƒœ ์ฒ˜๋ฆฌ ํ•„์š”

ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL์ด ์—†๊ฑฐ๋‚˜ ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ์˜ ์ƒํƒœ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

-<Profile editable imageUrl={data?.profileImageUrl ?? ''} />
+<Profile
+  editable
+  imageUrl={data?.profileImageUrl ?? ''}
+  loading={!data}
+  onError={() => {/* ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง */}}
+/>

Committable suggestion skipped: line range outside the PR's diff.

</div>
</Menu.Target>
<Menu.Dropdown className="translate-x-16 translate-y-2 transform md:translate-x-24 md:translate-y-0 lg:translate-x-24 lg:translate-y-0">
<Menu.Item onClick={onEdit}>ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ˆ˜์ •ํ•˜๊ธฐ</Menu.Item>
<Menu.Item color="red" onClick={() => alert('Delete Profile')}>
๊ธฐ๋ณธ ํ”„๋กœํ•„๋กœ ๋Œ์•„๊ฐ€๊ธฐ
</Menu.Item>
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion

์ ‘๊ทผ์„ฑ ๋ฐ ๊ตญ์ œํ™” ๊ฐœ์„  ํ•„์š”

  1. ๋ฉ”๋‰ด ์•„์ดํ…œ์— aria-label ์ถ”๊ฐ€
  2. ๋ฌธ์ž์—ด์„ ๊ตญ์ œํ™” ์ฒ˜๋ฆฌ
-<Menu.Item onClick={onEdit}>ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ˆ˜์ •ํ•˜๊ธฐ</Menu.Item>
+<Menu.Item onClick={onEdit} aria-label="ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์ˆ˜์ •">
+  {t('profile.edit')}
+</Menu.Item>
-<Menu.Item color="red" onClick={() => alert('Delete Profile')}>
-  ๊ธฐ๋ณธ ํ”„๋กœํ•„๋กœ ๋Œ์•„๊ฐ€๊ธฐ
+<Menu.Item
+  color="red"
+  onClick={() => alert('Delete Profile')}
+  aria-label="๊ธฐ๋ณธ ํ”„๋กœํ•„๋กœ ๋ณต์›"
+>
+  {t('profile.reset')}
+</Menu.Item>

Committable suggestion skipped: line range outside the PR's diff.

Copy link

Choose a reason for hiding this comment

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

๐Ÿ’ก Codebase verification

โš ๏ธ Potential issue

๊ธฐ๋ณธ ํ”„๋กœํ•„ ๋ณต์› API ๊ตฌํ˜„ ๋ฐ ์—ฐ๋™ ํ•„์š”

ํ˜„์žฌ ํ”„๋กœํ•„ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ์ค‘ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ API(updateUserProfile)๋งŒ ๊ตฌํ˜„๋˜์–ด ์žˆ๊ณ , ๊ธฐ๋ณธ ํ”„๋กœํ•„ ๋ณต์› API๊ฐ€ ๊ตฌํ˜„๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ž‘์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

  • /auths/user ์—”๋“œํฌ์ธํŠธ์— ๊ธฐ๋ณธ ํ”„๋กœํ•„ ๋ณต์› API ์ถ”๊ฐ€
  • user-apis.ts์— ๊ธฐ๋ณธ ํ”„๋กœํ•„ ๋ณต์› ํ•จ์ˆ˜ ๊ตฌํ˜„
  • ProfileCard ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ณต์› API ์—ฐ๋™ ๋ฐ ๋กœ๋”ฉ/์—๋Ÿฌ ์ƒํƒœ ์ฒ˜๋ฆฌ
๐Ÿ”— Analysis chain

๊ธฐ๋ณธ ํ”„๋กœํ•„ ๋ณต์› ๊ธฐ๋Šฅ ๊ตฌํ˜„ ํ•„์š”

ํ˜„์žฌ alert๋กœ ์ž„์‹œ ๊ตฌํ˜„๋œ ๊ธฐ๋ณธ ํ”„๋กœํ•„ ๋ณต์› ๊ธฐ๋Šฅ์„ ์‹ค์ œ API์™€ ์—ฐ๋™ํ•˜์—ฌ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# API ๊ตฌํ˜„ ์—ฌ๋ถ€ ํ™•์ธ
rg -g "*.ts" "resetProfile|defaultProfile|removeProfile" 

Length of output: 58


Script:

#!/bin/bash
# API ๊ด€๋ จ ํŒŒ์ผ ๊ฒ€์ƒ‰์„ ์œ„ํ•ด ๋” ๋„“์€ ๋ฒ”์œ„์˜ ํ‚ค์›Œ๋“œ๋กœ ๊ฒ€์ƒ‰
rg -g "*.ts*" -i "profile.*api|api.*profile"

# ํ”„๋กœํ•„ ๊ด€๋ จ ์„œ๋น„์Šค๋‚˜ ํ›… ๊ฒ€์ƒ‰
rg -g "*.ts*" -i "useProfile|ProfileService"

# API ์—”๋“œํฌ์ธํŠธ ์ •์˜ ๊ฒ€์ƒ‰
ast-grep --pattern 'interface $_ {
  $$$
  profile$_: $_
  $$$
}'

Length of output: 305


Script:

#!/bin/bash
# user-apis ํŒŒ์ผ์˜ ๋‚ด์šฉ ํ™•์ธ
cat src/app/\(crew\)/my-page/_components/profile-card/container.tsx

# auth API ๊ด€๋ จ ํŒŒ์ผ ํ™•์ธ
rg -g "*.ts*" "updateUserProfile" -A 5 -B 5

# ํ”„๋กœํ•„ ๊ด€๋ จ ํƒ€์ž… ์ •์˜ ๊ฒ€์ƒ‰
ast-grep --pattern 'type $_ = {
  $$$
  profile$_: $_
  $$$
}'

Length of output: 5133

</Menu.Dropdown>
</Menu>
</figure>
<div className="flex flex-col gap-2">
<p className="text-xl font-semibold text-gray-900 md:text-2xl lg:text-2xl">
{data?.nickname} ๋‹˜, ์•ˆ๋…•ํ•˜์„ธ์š”๐Ÿ™Œ
</p>
<dl className="flex text-base font-medium text-gray-700">
<dt className="flex-shrink-0 flex-grow-0 basis-20">Email</dt>
<dd className="flex-1">{data?.email}</dd>
</dl>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,15 @@
import { useState } from 'react';
import { Divider } from '@mantine/core';
import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll';
import ProfileCardContainer from '@/src/app/(crew)/mypage/_components/profile-card/container';
import { fetchWritableGatheringData } from '@/src/app/(crew)/api/mock-api/writable-gathering';
import { fetchMyReviewData } from '@/src/app/api/mock-api/review';
import ReviewCardList from '@/src/components/common/review-list/review-card-list';
import Tabs from '@/src/components/common/tab';
import WritableGatheringCardList from '@/src/components/common/writable-gathering-card/writable-gathering-card-list';
import { ReviewInformResponse } from '@/src/types/review';
import { WritableGatheringCardInformResponse } from '@/src/types/writable-gathering-card';
import { fetchMyReviewData } from '../../api/mock-api/review';
import { fetchWritableGatheringData } from '../api/mock-api/writable-gathering';

const mockData = {
id: 1,
profileImageUrl: '',
nickname: '์œจ์œจ',
email: 'youlyoul@email.com',
};

export default function MyPage() {
export default function ReviewSection() {
const myPageTabs = [
{ label: '์ž‘์„ฑ ๊ฐ€๋Šฅํ•œ ๋ฆฌ๋ทฐ', id: 'available-review' },
{ label: '์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ', id: 'my-review' },
Expand All @@ -43,15 +35,12 @@ export default function MyPage() {
isFetchingNextPage: isFetchingGatheringNextPage,
} = useInfiniteScroll<WritableGatheringCardInformResponse>({
queryKey: ['crew'],
queryFn: ({ pageParam = 0 }) => {
return fetchWritableGatheringData(pageParam, 3);
},
queryFn: ({ pageParam = 0 }) => fetchWritableGatheringData(pageParam, 3),
getNextPageParam: (lastPage, allPages) =>
lastPage.hasNextPage ? allPages.length + 1 : undefined,
});

const renderTabContent = () => {
// TODO : ๋ฆฌํ„ด ๊ฐ’ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ต์ฒด
switch (currentTab) {
case 'my-review':
return (
Expand All @@ -73,27 +62,20 @@ export default function MyPage() {
);
}
};

return (
<div className="container mx-auto my-0 min-h-screen max-w-pc bg-gray-50 px-3 py-11 md:px-8 lg:px-11">
<div className="lg:gap-4.5 flex flex-col gap-3 md:gap-4">
<ProfileCardContainer data={mockData} />
</div>
<div className="mt-12 flex flex-col">
<h3 className="text-2xl font-semibold text-gray-900">๋‚˜์˜ ๋ฆฌ๋ทฐ ๋ชจ์•„๋ณด๊ธฐ</h3>
<Divider mt={16} mb={24} size={2} />
<div className="flex justify-start">
<Tabs
variant="review"
tabs={myPageTabs}
activeTab={currentTab}
onTabClick={(id) => {
setCurrentTab(id);
}}
/>
</div>
<div className="pt-12">{renderTabContent()}</div>
<div className="mt-12 flex flex-col">
<h3 className="text-2xl font-semibold text-gray-900">๋‚˜์˜ ๋ฆฌ๋ทฐ ๋ชจ์•„๋ณด๊ธฐ</h3>
<Divider mt={16} mb={24} size={2} />
<div className="flex justify-start">
<Tabs
variant="review"
tabs={myPageTabs}
activeTab={currentTab}
onTabClick={(id) => setCurrentTab(id)}
/>
</div>
<div />
<div className="pt-12">{renderTabContent()}</div>
</div>
);
}
14 changes: 14 additions & 0 deletions src/app/(crew)/my-page/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import ProfileCardContainer from '@/src/app/(crew)/my-page/_components/profile-card/container';
import ReviewSection from '@/src/app/(crew)/my-page/_components/review-section';

export default function MyPage() {
return (
<div className="container mx-auto my-0 min-h-screen max-w-pc bg-gray-50 px-3 py-11 md:px-8 lg:px-11">
<div className="lg:gap-4.5 flex flex-col gap-3 md:gap-4">
<ProfileCardContainer />
</div>
<ReviewSection />
<div />
</div>
);
}
14 changes: 0 additions & 14 deletions src/app/(crew)/mypage/_components/profile-card/container.tsx

This file was deleted.

36 changes: 0 additions & 36 deletions src/app/(crew)/mypage/_components/profile-card/presenter.tsx

This file was deleted.

9 changes: 6 additions & 3 deletions src/components/common/header/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import HeaderPresenter from '@/src/components/common/header/presenter';
*
* @param {boolean} isAuth - ๋กœ๊ทธ์ธ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒํƒœ (true: ๋กœ๊ทธ์ธ๋จ, false: ๋น„๋กœ๊ทธ์ธ)
* @param {function} handleLogout - ๋กœ๊ทธ์•„์›ƒ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜
* @param {function} toggleCookie - ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ์ฟ ํ‚ค ์ƒํƒœ๋ฅผ ํ† ๊ธ€ํ•˜๋Š” ํ•จ์ˆ˜ (์ปดํฌ๋„ŒํŠธ ์‹คํ—˜์šฉ)
*/

export default function Header() {
const { isAuth, logout } = useAuthStore();
const { isAuth, user, logout } = useAuthStore(); // user ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

const router = useRouter();

Expand All @@ -24,7 +23,11 @@ export default function Header() {

return (
<div>
<HeaderPresenter isAuth={isAuth} handleLogout={handleLogout} />
<HeaderPresenter
isAuth={isAuth}
handleLogout={handleLogout}
profileImageUrl={user?.profileImageUrl}
/>
</div>
);
}
Loading