-
Notifications
You must be signed in to change notification settings - Fork 1
feat(client): 사이드바 구글 프로필 추가 #216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
073efca
c70255f
83bd130
2896846
31ed69f
045b9cd
76e1870
a3125c7
7100373
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,81 @@ | ||||||||||||||||||||||||||||||
| import { Icon } from '@pinback/design-system/icons'; | ||||||||||||||||||||||||||||||
| import { Button } from '@pinback/design-system/ui'; | ||||||||||||||||||||||||||||||
| import formatRemindTime from '@shared/utils/formatRemindTime'; | ||||||||||||||||||||||||||||||
| import { useEffect, useRef } from 'react'; | ||||||||||||||||||||||||||||||
| import { useNavigate } from 'react-router-dom'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| interface ProfilePopupProps { | ||||||||||||||||||||||||||||||
| open: boolean; | ||||||||||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||||||||||
| profileImage: string | null; | ||||||||||||||||||||||||||||||
| email: string; | ||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+11
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프로필 이미지가 null일 경우에는 이미지가 비어있게 되나요?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기본 동그라미로 대체됩니다-! |
||||||||||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||||||||||
| remindTime?: string; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default function ProfilePopup({ | ||||||||||||||||||||||||||||||
| open, | ||||||||||||||||||||||||||||||
| onClose, | ||||||||||||||||||||||||||||||
| profileImage, | ||||||||||||||||||||||||||||||
| email, | ||||||||||||||||||||||||||||||
| name, | ||||||||||||||||||||||||||||||
| remindTime, | ||||||||||||||||||||||||||||||
| }: ProfilePopupProps) { | ||||||||||||||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||||||||||||||
| const popupRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||
| function handleClickOutside(e: MouseEvent) { | ||||||||||||||||||||||||||||||
| if (popupRef.current && !popupRef.current.contains(e.target as Node)) { | ||||||||||||||||||||||||||||||
| onClose(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (open) document.addEventListener('mousedown', handleClickOutside); | ||||||||||||||||||||||||||||||
| return () => document.removeEventListener('mousedown', handleClickOutside); | ||||||||||||||||||||||||||||||
| }, [open, onClose]); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (!open) return null; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const handleLogout = () => { | ||||||||||||||||||||||||||||||
| localStorage.removeItem('token'); | ||||||||||||||||||||||||||||||
| localStorage.removeItem('email'); | ||||||||||||||||||||||||||||||
| navigate('/onboarding'); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <div className="fixed inset-0 z-[2000] flex items-start justify-start pl-[19rem] pt-[7rem]"> | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 하드코딩된 위치 값으로 인한 유지보수성 저하.
🔎 개선 방안다음 중 하나를 선택하여 개선하세요: 방안 1: CSS 변수 사용 -<div className="fixed inset-0 z-[2000] flex items-start justify-start pl-[19rem] pt-[7rem]">
+<div className="fixed inset-0 z-[2000] flex items-start justify-start pl-[var(--sidebar-width)] pt-[var(--header-height)]">방안 2: 동적 위치 계산
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||
| ref={popupRef} | ||||||||||||||||||||||||||||||
| className="common-shadow flex w-[26rem] flex-col items-center rounded-[1.2rem] bg-white pb-[2.4rem] pt-[3.2rem]" | ||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||
| <div className="mb-[0.8rem] flex h-[13.2rem] w-[13.2rem] items-center justify-center overflow-hidden rounded-full bg-gray-100"> | ||||||||||||||||||||||||||||||
| {profileImage ? ( | ||||||||||||||||||||||||||||||
| <img | ||||||||||||||||||||||||||||||
| src={profileImage} | ||||||||||||||||||||||||||||||
| alt="프로필" | ||||||||||||||||||||||||||||||
| className="h-full w-full object-cover" | ||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||
| <div className="h-full w-full bg-gray-200" /> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <p className="sub1-sb">{name}</p> | ||||||||||||||||||||||||||||||
| <p className="body4-r text-font-gray-3 mt-[0.8rem]">{email}</p> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <div className="text-font-gray-3 mb-[1.6rem] flex items-center gap-[0.2rem]"> | ||||||||||||||||||||||||||||||
| <Icon name="ic_clock_active" width={18} height={18} /> | ||||||||||||||||||||||||||||||
| <span className="">리마인드 알람 </span> | ||||||||||||||||||||||||||||||
| <span className="caption2-m">{formatRemindTime(remindTime)}</span> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <div className="w-full px-[7.6rem]"> | ||||||||||||||||||||||||||||||
| <Button variant="secondary" size="small" onClick={handleLogout}> | ||||||||||||||||||||||||||||||
| 로그아웃 | ||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
|
Comment on lines
73
to
77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로그아웃 버튼 기능이 구현되지 않았습니다. 버튼이 렌더링되지만 로그아웃 기능 구현을 도와드릴까요? Firebase 인증 로그아웃 로직과 라우팅 처리가 필요합니다. 🔎 임시 해결책기능 구현 전까지 버튼을 비활성화하거나 제거하는 것을 권장합니다: - <div className="w-full px-[7.6rem]">
- <Button variant="secondary" size="small">
- 로그아웃
- </Button>
- </div>
+ {/* TODO: 로그아웃 기능 구현 필요 */}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -16,14 +16,18 @@ import { | |||||
| useGetArcons, | ||||||
| usePutCategory, | ||||||
| useDeleteCategory, | ||||||
| useGetGoogleProfile, | ||||||
| useGetMyProfile, | ||||||
| } from '@shared/apis/queries'; | ||||||
| import { useEffect, useState } from 'react'; | ||||||
| import { useQueryClient } from '@tanstack/react-query'; | ||||||
| import { useNavigate } from 'react-router-dom'; | ||||||
| import ProfilePopup from '../profilePopup/ProfilePopup'; | ||||||
|
|
||||||
| export function Sidebar() { | ||||||
| const [newCategoryName, setNewCategoryName] = useState(''); | ||||||
| const [toastIsOpen, setToastIsOpen] = useState(false); | ||||||
| const [profileOpen, setProfileOpen] = useState(false); | ||||||
|
|
||||||
| const queryClient = useQueryClient(); | ||||||
| const navigate = useNavigate(); | ||||||
|
|
@@ -33,6 +37,15 @@ export function Sidebar() { | |||||
| const { mutate: createCategory } = usePostCategory(); | ||||||
| const { data, isPending } = useGetArcons(); | ||||||
| const { mutate: deleteCategory } = useDeleteCategory(); | ||||||
| const { data: googleProfileData } = useGetGoogleProfile(); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프로필 로딩 및 에러 상태 처리 부재.
다음과 같이 수정하세요: - const { data: googleProfileData } = useGetGoogleProfile();
+ const { data: googleProfileData, isLoading: isProfileLoading } = useGetGoogleProfile();그리고 UI에서 로딩 상태를 처리하세요: <button
className="h-[3.6rem] w-[3.6rem] flex-shrink-0 overflow-hidden rounded-full border border-gray-200"
onClick={() => console.log('프로필 클릭', profileImageUrl)}
>
- {profileImageUrl ? (
+ {isProfileLoading ? (
+ <div className="h-full w-full animate-pulse bg-gray-200" />
+ ) : profileImageUrl ? (
<img
src={profileImageUrl}
alt="프로필 이미지"
className="h-full w-full object-cover"
/>
) : (
<div className="h-full w-full bg-gray-200" />
)}
</button>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 API pending관련 처리는 이후에 추가될까요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @constantly-dev, 좋은 질문입니다! 현재 PR에서 로딩 상태 처리를 추가하실 계획인가요, 아니면 추후 별도 PR에서 진행하실 예정인가요? 만약 이후에 추가하실 계획이시라면, 추적을 위해 별도 이슈를 생성해드릴까요? 이렇게 하면 향후 UX 개선 작업 시 놓치지 않고 처리하실 수 있을 것 같습니다. 😊 🧠 Learnings used |
||||||
| const { data: myProfile } = useGetMyProfile(); | ||||||
|
|
||||||
| const profileImageUrl = googleProfileData?.googleProfile || null; | ||||||
|
Comment on lines
+41
to
+43
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사이드바 프로필 클릭할때, 그때 유저정보 불러오는게 아니라, |
||||||
|
|
||||||
| const chippiImageUrl = myProfile?.profileImage ?? null; | ||||||
| const profileEmail = myProfile?.email ?? ''; | ||||||
| const profileName = myProfile?.name ?? ''; | ||||||
| const remindAt = myProfile?.remindAt ?? 'AM 09:00'; | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제꺼 알람시간 설정 변동이 어려워서, 다른 시간대 (PM 09:00? 등의 경우)는 UI 확인 못하였는데ㅠㅠ 서버에서 remindAt을
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 시간 포맷팅은 확인하지 못했네요.. 수정하겠습니다 |
||||||
|
|
||||||
| const { | ||||||
| activeTab, | ||||||
|
|
@@ -76,9 +89,7 @@ export function Sidebar() { | |||||
| queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] }); | ||||||
| close(); | ||||||
| }, | ||||||
| onError: () => { | ||||||
| setToastIsOpen(true); | ||||||
| }, | ||||||
| onError: () => setToastIsOpen(true), | ||||||
| }); | ||||||
| }; | ||||||
|
|
||||||
|
|
@@ -92,9 +103,7 @@ export function Sidebar() { | |||||
| close(); | ||||||
| moveNewCategory(id); | ||||||
| }, | ||||||
| onError: () => { | ||||||
| setToastIsOpen(true); | ||||||
| }, | ||||||
| onError: () => setToastIsOpen(true), | ||||||
| } | ||||||
| ); | ||||||
| }; | ||||||
|
|
@@ -105,9 +114,7 @@ export function Sidebar() { | |||||
| queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] }); | ||||||
| close(); | ||||||
| }, | ||||||
| onError: () => { | ||||||
| setToastIsOpen(true); | ||||||
| }, | ||||||
| onError: () => setToastIsOpen(true), | ||||||
| }); | ||||||
| }; | ||||||
|
|
||||||
|
|
@@ -128,16 +135,34 @@ export function Sidebar() { | |||||
| return ( | ||||||
| <aside className="bg-white-bg sticky top-0 h-screen w-[24rem] border-r border-gray-300"> | ||||||
| <div className="flex h-full flex-col px-[0.8rem]"> | ||||||
| <header className="px-[0.8rem] py-[2.8rem]"> | ||||||
| {/* 헤더 */} | ||||||
| <header className="flex items-center justify-between px-[0.8rem] py-[2.8rem]"> | ||||||
| <Icon | ||||||
| name="logo" | ||||||
| aria-label="Pinback 로고" | ||||||
| className="h-[2.4rem] w-[8.7rem] cursor-pointer" | ||||||
| /> | ||||||
|
|
||||||
| <button | ||||||
| type="button" | ||||||
| className="h-[3.6rem] w-[3.6rem] flex-shrink-0 overflow-hidden rounded-full border border-gray-200" | ||||||
| onClick={() => setProfileOpen(true)} | ||||||
| > | ||||||
|
Comment on lines
146
to
150
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| {profileImageUrl ? ( | ||||||
| <img | ||||||
| src={profileImageUrl} | ||||||
| alt="프로필" | ||||||
| className="h-full w-full object-cover" | ||||||
| /> | ||||||
| ) : ( | ||||||
| <div className="h-full w-full bg-gray-200" /> | ||||||
| )} | ||||||
| </button> | ||||||
| </header> | ||||||
|
|
||||||
| <hr className="my-[0.8rem] border-gray-100" /> | ||||||
|
|
||||||
| {/* 메뉴 영역 */} | ||||||
| <div className="flex-1 overflow-y-auto"> | ||||||
| <SideItem | ||||||
| icon="clock" | ||||||
|
|
@@ -211,19 +236,30 @@ export function Sidebar() { | |||||
| acorns={acornCount} | ||||||
| isActive={activeTab === 'level'} | ||||||
| onClick={() => { | ||||||
| setSelectedCategoryId(null); | ||||||
| closeMenu(); | ||||||
| setSelectedCategoryId(null); | ||||||
| goLevel(); | ||||||
| }} | ||||||
| /> | ||||||
| )} | ||||||
| </footer> | ||||||
| </div> | ||||||
|
|
||||||
| {/* 팝업 영역 */} | ||||||
|
|
||||||
| <ProfilePopup | ||||||
| open={profileOpen} | ||||||
| onClose={() => setProfileOpen(false)} | ||||||
| profileImage={chippiImageUrl} | ||||||
| name={profileName} | ||||||
| email={profileEmail} | ||||||
| remindTime={remindAt} | ||||||
| /> | ||||||
|
|
||||||
| <PopupPortal | ||||||
| popup={popup} | ||||||
| onClose={handlePopupClose} | ||||||
| onChange={handleCategoryChange} | ||||||
| onChange={setNewCategoryName} | ||||||
| onCreateConfirm={handleCreateCategory} | ||||||
| onEditConfirm={(id) => handlePatchCategory(id)} | ||||||
| onDeleteConfirm={(id) => handleDeleteCategory(id)} | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,39 @@ | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import { useCallback, useState } from 'react'; | ||
| import { useNavigate, useLocation, useSearchParams } from 'react-router-dom'; | ||
| import { useCallback, useEffect, useState } from 'react'; | ||
|
|
||
| export type SidebarTab = 'mybookmark' | 'remind' | 'level'; | ||
|
|
||
| export function useSidebarNav() { | ||
| const navigate = useNavigate(); | ||
| const location = useLocation(); | ||
| const [searchParams] = useSearchParams(); | ||
|
|
||
| const [activeTab, setActiveTab] = useState<SidebarTab>('remind'); | ||
| const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>( | ||
| null | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| const path = location.pathname; | ||
|
|
||
| if (path.startsWith('/my-bookmarks')) { | ||
| setActiveTab('mybookmark'); | ||
|
|
||
| const id = searchParams.get('id'); | ||
| if (id) { | ||
| setSelectedCategoryId(Number(id)); | ||
| } else { | ||
| setSelectedCategoryId(null); | ||
| } | ||
| } else if (path === '/' || path.startsWith('/remind')) { | ||
| setActiveTab('remind'); | ||
| setSelectedCategoryId(null); | ||
| } else if (path.startsWith('/level')) { | ||
| setActiveTab('level'); | ||
| setSelectedCategoryId(null); | ||
| } | ||
| }, [location.pathname, searchParams]); | ||
|
Comment on lines
+16
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. URL 쿼리 파라미터 파싱 시 유효성 검증이 필요합니다. Line 24에서 다음 diff를 적용하여 안전하게 파싱하세요: const id = searchParams.get('id');
if (id) {
- setSelectedCategoryId(Number(id));
+ const numId = parseInt(id, 10);
+ setSelectedCategoryId(isNaN(numId) ? null : numId);
} else {
setSelectedCategoryId(null);
}🤖 Prompt for AI Agents |
||
|
|
||
| const goRemind = useCallback(() => { | ||
| setActiveTab('remind'); | ||
| setSelectedCategoryId(null); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,22 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function formatRemindTime(time: string | undefined): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!time) return ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [period, timePart] = time.split(' '); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [hourStr, minute] = timePart.split(':'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let hour = Number(hourStr); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isNaN(hour)) return ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 입력 형식 검증 및 에러 처리가 누락되었습니다. 다음과 같은 문제가 있습니다:
🔎 제안하는 수정 사항 function formatRemindTime(time: string | undefined): string {
if (!time) return '';
const [period, timePart] = time.split(' ');
+
+ // 입력 형식 검증
+ if (!period || !timePart || (period !== 'AM' && period !== 'PM')) {
+ return '';
+ }
+
const [hourStr, minute] = timePart.split(':');
+
+ if (!hourStr || !minute) {
+ return '';
+ }
let hour = Number(hourStr);
if (isNaN(hour)) return '';📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (period === 'PM') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hour = hour === 12 ? 12 : hour - 12; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PM 시간 변환 로직에 치명적인 버그가 있습니다. 현재 로직
이 로직은 입력이 "PM 13:00", "PM 14:00" 같은 혼합 형식(AM/PM 접두사 + 24시간 형식 시)일 때만 올바르게 작동합니다. 그러나 이는 매우 비표준적인 형식입니다. 올바른 변환 로직:
실제 입력 데이터의 형식을 확인하기 위해 다음 스크립트를 실행해주세요: #!/bin/bash
# Description: ProfilePopup에서 remindTime이 어떤 형식으로 전달되는지 확인
# remindTime 사용처 검색
rg -n -C5 'remindTime' --type=tsx --type=ts🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (period === 'AM' && hour === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hour = 12; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major AM 시간 처리 로직이 혼란스러운 입력 형식을 가정하고 있습니다. Line 15-17의 로직은 문제점:
권장사항: 입력 형식을 명확히 정의하고, 다음 중 하나를 선택하세요:
🔎 24시간 → 12시간 변환을 위한 올바른 구현 예시function formatRemindTime(time: string | undefined): string {
if (!time) return '';
// 24시간 형식 "HH:MM" 파싱
const [hourStr, minute] = time.split(':');
if (!hourStr || !minute) return '';
let hour = Number(hourStr);
if (isNaN(hour) || hour < 0 || hour > 23) return '';
// 24시간 → 12시간 변환
const period = hour >= 12 ? 'PM' : 'AM';
const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
return `${period} ${hour12}:${minute}`;
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `${period} ${hour}:${minute}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default formatRemindTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 따로 바뀌는 경우가 없어서
staleTime을Infinity로 지정하신거죠??