From ae2957def3bf1eb3c39f50bb6f98da8369a6d11c Mon Sep 17 00:00:00 2001 From: Jino Bae Date: Sat, 17 Jan 2026 00:18:57 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20Fix=20react=20effect=20deps?= =?UTF-8?q?=20optimiztion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remotes/src/components/ErrorBoundary.tsx | 3 +- .../components/remotes/Comments/Comments.tsx | 5 +-- .../remotes/Login/components/LoginForm.tsx | 3 +- .../remotes/PostEditor/EditPostEditor.tsx | 5 +-- .../remotes/PostEditor/NewPostEditor.tsx | 2 +- .../PostEditor/components/TempPostsPanel.tsx | 3 +- .../remotes/PostEditor/hooks/useAutoSave.ts | 6 ++-- .../remotes/RelatedPosts/RelatedPosts.tsx | 3 +- .../remotes/SearchModal/SearchModal.tsx | 3 +- .../pages/AccountSetting/AccountSetting.tsx | 2 +- .../pages/BannerSetting/BannerSetting.tsx | 2 +- .../pages/FormsSetting/FormsSetting.tsx | 2 +- .../IntegrationSetting/IntegrationSetting.tsx | 2 +- .../PostsSetting/hooks/usePostsActions.ts | 2 +- .../pages/ProfileSetting/ProfileSetting.tsx | 2 +- .../pages/SeriesSetting/SeriesSetting.tsx | 2 +- .../TempPostsSetting/TempPostsSetting.tsx | 2 +- .../src/components/remotes/Signup/Signup.tsx | 6 ++-- .../SocialLogin/hooks/useSocialProviders.ts | 5 +-- .../remotes/src/contexts/ConfirmContext.tsx | 25 +------------ .../src/contexts/LoginPromptContext.tsx | 27 +++----------- .../contexts/internal/ConfirmContextDef.ts | 15 ++++++++ .../internal/LoginPromptContextDef.ts | 7 ++++ .../apps/remotes/src/hooks/useConfirm.ts | 10 ++++++ .../apps/remotes/src/hooks/useLoginPrompt.ts | 10 ++++++ backend/islands/apps/remotes/src/island.tsx | 30 ++++++++-------- .../apps/remotes/src/lib/query-client.ts | 7 ++-- .../src/scripts/syntax-highlighting.ts | 5 +-- .../islands/apps/remotes/src/utils/logger.ts | 36 +++++++++++++++++++ backend/src/board/templates/board/base.html | 3 +- 30 files changed, 139 insertions(+), 96 deletions(-) create mode 100644 backend/islands/apps/remotes/src/contexts/internal/ConfirmContextDef.ts create mode 100644 backend/islands/apps/remotes/src/contexts/internal/LoginPromptContextDef.ts create mode 100644 backend/islands/apps/remotes/src/hooks/useConfirm.ts create mode 100644 backend/islands/apps/remotes/src/hooks/useLoginPrompt.ts create mode 100644 backend/islands/apps/remotes/src/utils/logger.ts diff --git a/backend/islands/apps/remotes/src/components/ErrorBoundary.tsx b/backend/islands/apps/remotes/src/components/ErrorBoundary.tsx index 231c0a92a..5a8fc0995 100644 --- a/backend/islands/apps/remotes/src/components/ErrorBoundary.tsx +++ b/backend/islands/apps/remotes/src/components/ErrorBoundary.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { logger } from '~/utils/logger'; class ErrorBoundary extends React.Component<{ fallback: React.ReactNode; @@ -13,7 +14,7 @@ class ErrorBoundary extends React.Component<{ } static getDerivedStateFromError(error: Error) { - console.error('Error in component:', error); + logger.error('Error in component:', error); return { hasError: true }; } diff --git a/backend/islands/apps/remotes/src/components/remotes/Comments/Comments.tsx b/backend/islands/apps/remotes/src/components/remotes/Comments/Comments.tsx index 8a3a8674a..4aead8caa 100644 --- a/backend/islands/apps/remotes/src/components/remotes/Comments/Comments.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/Comments/Comments.tsx @@ -1,8 +1,9 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { isLoggedIn as checkIsLoggedIn, showLoginPrompt } from '~/utils/loginPrompt'; import { toast } from '~/utils/toast'; +import { logger } from '~/utils/logger'; import { getComments, createComment, @@ -125,7 +126,7 @@ const Comments = (props: CommentsProps) => { toast.error('댓글 정보를 불러오는데 실패했습니다.'); } } catch (err) { - console.error('댓글 수정 오류:', err); + logger.error('댓글 수정 오류:', err); toast.error('댓글 정보를 불러오는 중 오류가 발생했습니다.'); } }; diff --git a/backend/islands/apps/remotes/src/components/remotes/Login/components/LoginForm.tsx b/backend/islands/apps/remotes/src/components/remotes/Login/components/LoginForm.tsx index 8c1dad864..fc5feada0 100644 --- a/backend/islands/apps/remotes/src/components/remotes/Login/components/LoginForm.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/Login/components/LoginForm.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useRef } from 'react'; import SocialLogin from '~/components/remotes/SocialLogin'; +import { logger } from '~/utils/logger'; interface LoginFormProps { username: string; @@ -71,7 +72,7 @@ const LoginForm = ({ } }); } catch (e) { - console.error('Failed to render hCaptcha:', e); + logger.error('Failed to render hCaptcha:', e); } } diff --git a/backend/islands/apps/remotes/src/components/remotes/PostEditor/EditPostEditor.tsx b/backend/islands/apps/remotes/src/components/remotes/PostEditor/EditPostEditor.tsx index 9730bf790..8a60d1768 100644 --- a/backend/islands/apps/remotes/src/components/remotes/PostEditor/EditPostEditor.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/PostEditor/EditPostEditor.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { toast } from '~/utils/toast'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import PostEditorWrapper from './PostEditorWrapper'; import PostActions from './components/PostActions'; import PostForm from './components/PostForm'; @@ -8,6 +8,7 @@ import SettingsDrawer from './components/SettingsDrawer'; import { getSeries } from '~/lib/api/settings'; import { getPostForEdit } from '~/lib/api/posts'; import { api } from '~/components/shared'; +import { logger } from '~/utils/logger'; interface Series { id: string; @@ -127,7 +128,7 @@ const EditPostEditor = ({ username, postUrl }: EditPostEditorProps) => { return data.body.url; } } catch (error) { - console.error('Image upload failed', error); + logger.error('Image upload failed', error); } return undefined; }; diff --git a/backend/islands/apps/remotes/src/components/remotes/PostEditor/NewPostEditor.tsx b/backend/islands/apps/remotes/src/components/remotes/PostEditor/NewPostEditor.tsx index 829998f8d..afd3fc844 100644 --- a/backend/islands/apps/remotes/src/components/remotes/PostEditor/NewPostEditor.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/PostEditor/NewPostEditor.tsx @@ -5,7 +5,7 @@ import { useRef } from 'react'; import { toast } from '~/utils/toast'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import PostEditorWrapper from './PostEditorWrapper'; import PostActions from './components/PostActions'; import PostForm from './components/PostForm'; diff --git a/backend/islands/apps/remotes/src/components/remotes/PostEditor/components/TempPostsPanel.tsx b/backend/islands/apps/remotes/src/components/remotes/PostEditor/components/TempPostsPanel.tsx index 11837ab59..c019dd515 100644 --- a/backend/islands/apps/remotes/src/components/remotes/PostEditor/components/TempPostsPanel.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/PostEditor/components/TempPostsPanel.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { logger } from '~/utils/logger'; import { Dialog } from '@blex/ui'; import { getTempPosts, type TempPost } from '~/lib/api/settings'; import { cx } from '~/lib/classnames'; @@ -35,7 +36,7 @@ const TempPostsPanel = ({ setTempPosts([]); } } catch (error) { - console.error('Failed to fetch temp posts:', error); + logger.error('Failed to fetch temp posts:', error); setTempPosts([]); } finally { setIsLoading(false); diff --git a/backend/islands/apps/remotes/src/components/remotes/PostEditor/hooks/useAutoSave.ts b/backend/islands/apps/remotes/src/components/remotes/PostEditor/hooks/useAutoSave.ts index c0edddb6d..b9d8400c3 100644 --- a/backend/islands/apps/remotes/src/components/remotes/PostEditor/hooks/useAutoSave.ts +++ b/backend/islands/apps/remotes/src/components/remotes/PostEditor/hooks/useAutoSave.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { createTempPost, updateTempPost } from '~/lib/api/posts'; interface AutoSaveData { @@ -48,7 +48,7 @@ export const useAutoSave = (data: AutoSaveData, options: UseAutoSaveOptions) => }); // Manual save - const manualSave = async () => { + const manualSave = useCallback(async () => { const currentData = dataRef.current; const currentOptions = optionsRef.current; @@ -98,7 +98,7 @@ export const useAutoSave = (data: AutoSaveData, options: UseAutoSaveOptions) => } finally { setIsSaving(false); } - }; + }, [isSaving]); // Auto-save effect useEffect(() => { diff --git a/backend/islands/apps/remotes/src/components/remotes/RelatedPosts/RelatedPosts.tsx b/backend/islands/apps/remotes/src/components/remotes/RelatedPosts/RelatedPosts.tsx index dd4b878c5..366852341 100644 --- a/backend/islands/apps/remotes/src/components/remotes/RelatedPosts/RelatedPosts.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/RelatedPosts/RelatedPosts.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { logger } from '~/utils/logger'; import { getRelatedPosts, type RelatedPost } from '~/lib/api/posts'; interface RelatedPostsProps { @@ -18,7 +19,7 @@ const RelatedPosts = ({ postUrl, username }: RelatedPostsProps) => { setRelatedPosts(data.body.posts || []); } } catch (error) { - console.error('Failed to fetch related posts:', error); + logger.error('Failed to fetch related posts:', error); } finally { setIsLoading(false); } diff --git a/backend/islands/apps/remotes/src/components/remotes/SearchModal/SearchModal.tsx b/backend/islands/apps/remotes/src/components/remotes/SearchModal/SearchModal.tsx index 1cf6a6627..a1ccce9c0 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SearchModal/SearchModal.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SearchModal/SearchModal.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { getMediaPath } from '~/modules/static.module'; import { Modal } from '~/components/shared'; import { searchPosts, type SearchResult } from '~/lib/api'; +import { logger } from '~/utils/logger'; interface SearchModalProps { isOpen?: boolean; @@ -65,7 +66,7 @@ const SearchModal = ({ isOpen: initialIsOpen = false }: SearchModalProps) => { setPage(pageNum); } } catch (error) { - console.error('Search error:', error); + logger.error('Search error:', error); } finally { setIsLoading(false); } diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/AccountSetting/AccountSetting.tsx b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/AccountSetting/AccountSetting.tsx index a6aaedcf8..b4dcd9edf 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/AccountSetting/AccountSetting.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/AccountSetting/AccountSetting.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { toast } from '~/utils/toast'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { SettingsHeader } from '../../components'; import { getAccountSettings, updateAccountSettings, deleteAccount } from '~/lib/api/settings'; import { enable2FA, disable2FA, verify2FASetup } from '~/lib/api/auth'; diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/BannerSetting/BannerSetting.tsx b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/BannerSetting/BannerSetting.tsx index bb8f38b4b..a27ec83bc 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/BannerSetting/BannerSetting.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/BannerSetting/BannerSetting.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { toast } from '~/utils/toast'; import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { SettingsHeader } from '../../components'; import { Button, Modal } from '~/components/shared'; import { diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/FormsSetting/FormsSetting.tsx b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/FormsSetting/FormsSetting.tsx index 4a9a475e7..154848c0a 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/FormsSetting/FormsSetting.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/FormsSetting/FormsSetting.tsx @@ -14,7 +14,7 @@ import { TITLE, ACTIONS_CONTAINER } from '~/components/shared'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { getForms, getForm, diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/IntegrationSetting/IntegrationSetting.tsx b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/IntegrationSetting/IntegrationSetting.tsx index 0e419dff1..89f44f757 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/IntegrationSetting/IntegrationSetting.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/IntegrationSetting/IntegrationSetting.tsx @@ -3,7 +3,7 @@ import { toast } from '~/utils/toast'; import { useSuspenseQuery } from '@tanstack/react-query'; import { SettingsHeader } from '../../components'; import { Button } from '~/components/shared'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { getTelegramStatus, generateTelegramToken, disconnectTelegram as disconnectTelegramAPI } from '~/lib/api/telegram'; const IntegrationSettings = () => { diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/PostsSetting/hooks/usePostsActions.ts b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/PostsSetting/hooks/usePostsActions.ts index 62ac016c2..451a10136 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/PostsSetting/hooks/usePostsActions.ts +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/PostsSetting/hooks/usePostsActions.ts @@ -1,5 +1,5 @@ import { toast } from '~/utils/toast'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { togglePostVisibility, togglePostNotice, diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/ProfileSetting/ProfileSetting.tsx b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/ProfileSetting/ProfileSetting.tsx index b9fca219f..76f85a405 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/ProfileSetting/ProfileSetting.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/ProfileSetting/ProfileSetting.tsx @@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useSuspenseQuery } from '@tanstack/react-query'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { SettingsHeader } from '../../components'; import { Button, Input, Card } from '~/components/shared'; import { getProfileSettings, updateProfileSettings, uploadAvatar, uploadCover } from '~/lib/api/settings'; diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/SeriesSetting/SeriesSetting.tsx b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/SeriesSetting/SeriesSetting.tsx index 9e5c19ea1..a3f297e4b 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/SeriesSetting/SeriesSetting.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/SeriesSetting/SeriesSetting.tsx @@ -32,7 +32,7 @@ import { ACTIONS_CONTAINER, DRAG_HANDLE } from '~/components/shared'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { getSeriesWithUsername, updateSeriesOrder, diff --git a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/TempPostsSetting/TempPostsSetting.tsx b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/TempPostsSetting/TempPostsSetting.tsx index 06b765a0e..6e5cf64fa 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/TempPostsSetting/TempPostsSetting.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/SettingsApp/pages/TempPostsSetting/TempPostsSetting.tsx @@ -12,7 +12,7 @@ import { SUBTITLE, ACTIONS_CONTAINER } from '~/components/shared'; -import { useConfirm } from '~/contexts/ConfirmContext'; +import { useConfirm } from '~/hooks/useConfirm'; import { getTempPosts, deleteTempPost } from '~/lib/api/settings'; const TempPostsSetting = () => { diff --git a/backend/islands/apps/remotes/src/components/remotes/Signup/Signup.tsx b/backend/islands/apps/remotes/src/components/remotes/Signup/Signup.tsx index 9a31868e1..3ad7ff5e6 100644 --- a/backend/islands/apps/remotes/src/components/remotes/Signup/Signup.tsx +++ b/backend/islands/apps/remotes/src/components/remotes/Signup/Signup.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import SocialLogin from '~/components/remotes/SocialLogin'; declare global { @@ -38,7 +38,7 @@ const Signup = () => { const captchaRef = useRef(null); const widgetIdRef = useRef(null); - const renderCaptcha = () => { + const renderCaptcha = useCallback(() => { if (!captchaRendered && captchaRef.current && window.hcaptcha && window.HCAPTCHA_SITE_KEY) { try { widgetIdRef.current = window.hcaptcha.render(captchaRef.current, { @@ -54,7 +54,7 @@ const Signup = () => { // Ignore error } } - }; + }, [captchaRendered]); useEffect(() => { setIsVisible(true); diff --git a/backend/islands/apps/remotes/src/components/remotes/SocialLogin/hooks/useSocialProviders.ts b/backend/islands/apps/remotes/src/components/remotes/SocialLogin/hooks/useSocialProviders.ts index 5d909b13c..2953a0f73 100644 --- a/backend/islands/apps/remotes/src/components/remotes/SocialLogin/hooks/useSocialProviders.ts +++ b/backend/islands/apps/remotes/src/components/remotes/SocialLogin/hooks/useSocialProviders.ts @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { logger } from '~/utils/logger'; import { getSocialProviders, type SocialProvider } from '~/lib/api'; export const useSocialProviders = () => { @@ -14,7 +15,7 @@ export const useSocialProviders = () => { setProviders(data.body); } } catch (error) { - console.error('Failed to load social providers:', error); + logger.error('Failed to load social providers:', error); } setLoading(false); }; @@ -26,4 +27,4 @@ export const useSocialProviders = () => { providers, loading }; -}; \ No newline at end of file +}; diff --git a/backend/islands/apps/remotes/src/contexts/ConfirmContext.tsx b/backend/islands/apps/remotes/src/contexts/ConfirmContext.tsx index 6952bc16a..56c2d27b2 100644 --- a/backend/islands/apps/remotes/src/contexts/ConfirmContext.tsx +++ b/backend/islands/apps/remotes/src/contexts/ConfirmContext.tsx @@ -1,32 +1,9 @@ import { - createContext, - useContext, useState, type ReactNode } from 'react'; import { Modal } from '@blex/ui'; - -interface ConfirmOptions { - title: string; - message: string; - confirmText?: string; - cancelText?: string; - variant?: 'default' | 'danger'; -} - -interface ConfirmContextType { - confirm: (options: ConfirmOptions) => Promise; -} - -const ConfirmContext = createContext(null); - -export const useConfirm = () => { - const context = useContext(ConfirmContext); - if (!context) { - throw new Error('useConfirm must be used within ConfirmProvider'); - } - return context; -}; +import { ConfirmContext, type ConfirmOptions } from './internal/ConfirmContextDef'; interface ConfirmDialogState extends ConfirmOptions { isOpen: boolean; diff --git a/backend/islands/apps/remotes/src/contexts/LoginPromptContext.tsx b/backend/islands/apps/remotes/src/contexts/LoginPromptContext.tsx index 6be9c4ddb..d14626807 100644 --- a/backend/islands/apps/remotes/src/contexts/LoginPromptContext.tsx +++ b/backend/islands/apps/remotes/src/contexts/LoginPromptContext.tsx @@ -1,25 +1,11 @@ import { - createContext, - useContext, useState, useEffect, + useCallback, type ReactNode } from 'react'; import { Modal } from '~/components/shared'; - -interface LoginPromptContextType { - showLoginPrompt: (action: string) => void; -} - -const LoginPromptContext = createContext(null); - -export const useLoginPrompt = () => { - const context = useContext(LoginPromptContext); - if (!context) { - throw new Error('useLoginPrompt must be used within LoginPromptProvider'); - } - return context; -}; +import { LoginPromptContext } from './internal/LoginPromptContextDef'; interface LoginPromptDialogState { isOpen: boolean; @@ -34,7 +20,7 @@ export const LoginPromptProvider = ({ children }: { children: ReactNode }) => { const isLoggedIn = !!window.configuration?.user?.username; - const showLoginPrompt = (action: string) => { + const showLoginPrompt = useCallback((action: string) => { if (isLoggedIn) { return; } @@ -43,7 +29,7 @@ export const LoginPromptProvider = ({ children }: { children: ReactNode }) => { isOpen: true, action }); - }; + }, [isLoggedIn]); // 전역 커스텀 이벤트로 모달 열기 (Alpine.js에서도 사용 가능) // 중복 방지: 같은 페이지에 여러 island가 있어도 한 번만 모달 표시 @@ -132,8 +118,3 @@ export const LoginPromptProvider = ({ children }: { children: ReactNode }) => { ); }; - -// Export a helper to check login status -export const isUserLoggedIn = (): boolean => { - return !!window.configuration?.user?.username; -}; diff --git a/backend/islands/apps/remotes/src/contexts/internal/ConfirmContextDef.ts b/backend/islands/apps/remotes/src/contexts/internal/ConfirmContextDef.ts new file mode 100644 index 000000000..fdf22d265 --- /dev/null +++ b/backend/islands/apps/remotes/src/contexts/internal/ConfirmContextDef.ts @@ -0,0 +1,15 @@ +import { createContext } from 'react'; + +export interface ConfirmOptions { + title: string; + message: string; + confirmText?: string; + cancelText?: string; + variant?: 'default' | 'danger'; +} + +export interface ConfirmContextType { + confirm: (options: ConfirmOptions) => Promise; +} + +export const ConfirmContext = createContext(null); diff --git a/backend/islands/apps/remotes/src/contexts/internal/LoginPromptContextDef.ts b/backend/islands/apps/remotes/src/contexts/internal/LoginPromptContextDef.ts new file mode 100644 index 000000000..8f135b46c --- /dev/null +++ b/backend/islands/apps/remotes/src/contexts/internal/LoginPromptContextDef.ts @@ -0,0 +1,7 @@ +import { createContext } from 'react'; + +export interface LoginPromptContextType { + showLoginPrompt: (action: string) => void; +} + +export const LoginPromptContext = createContext(null); diff --git a/backend/islands/apps/remotes/src/hooks/useConfirm.ts b/backend/islands/apps/remotes/src/hooks/useConfirm.ts new file mode 100644 index 000000000..5a1a2254d --- /dev/null +++ b/backend/islands/apps/remotes/src/hooks/useConfirm.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { ConfirmContext } from '../contexts/internal/ConfirmContextDef'; + +export const useConfirm = () => { + const context = useContext(ConfirmContext); + if (!context) { + throw new Error('useConfirm must be used within ConfirmProvider'); + } + return context; +}; diff --git a/backend/islands/apps/remotes/src/hooks/useLoginPrompt.ts b/backend/islands/apps/remotes/src/hooks/useLoginPrompt.ts new file mode 100644 index 000000000..fdc1c5ad8 --- /dev/null +++ b/backend/islands/apps/remotes/src/hooks/useLoginPrompt.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { LoginPromptContext } from '../contexts/internal/LoginPromptContextDef'; + +export const useLoginPrompt = () => { + const context = useContext(LoginPromptContext); + if (!context) { + throw new Error('useLoginPrompt must be used within LoginPromptProvider'); + } + return context; +}; diff --git a/backend/islands/apps/remotes/src/island.tsx b/backend/islands/apps/remotes/src/island.tsx index 6e5953353..18f6499e8 100644 --- a/backend/islands/apps/remotes/src/island.tsx +++ b/backend/islands/apps/remotes/src/island.tsx @@ -1,3 +1,4 @@ +import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'; import App from './components/App'; @@ -23,17 +24,12 @@ customElements.define('island-component', class extends HTMLElement { connectedCallback(): void { const name = this.getAttribute('name'); - const lazy = this.getAttribute('lazy') === 'true'; if (!name) { return; } - if (lazy) { - this.setupLazyLoading(name); - } else { - this.renderComponent(name); - } + this.renderComponent(name); } disconnectedCallback(): void { @@ -75,16 +71,18 @@ customElements.define('island-component', class extends HTMLElement { const queryClient = createQueryClient(); const root = createRoot(this); root.render( - Component Error: {name}}> - - - - + + Component Error: {name}}> + + + + + ); } catch { this.innerHTML = `
Component Error: ${name}
`; diff --git a/backend/islands/apps/remotes/src/lib/query-client.ts b/backend/islands/apps/remotes/src/lib/query-client.ts index 9c94a2e16..dd3bc4a62 100644 --- a/backend/islands/apps/remotes/src/lib/query-client.ts +++ b/backend/islands/apps/remotes/src/lib/query-client.ts @@ -1,5 +1,6 @@ import { QueryClient } from '@tanstack/react-query'; import type { PersistedClient, Persister } from '@tanstack/react-query-persist-client'; +import { logger } from '~/utils/logger'; const getCurrentUsername = () => { return window.configuration?.user?.username || 'anonymous'; @@ -17,7 +18,7 @@ export const sessionStoragePersister: Persister = { const key = getUserCacheKey(); sessionStorage.setItem(key, JSON.stringify(client)); } catch (error) { - console.error('Failed to persist client:', error); + logger.error('Failed to persist client:', error); } }, restoreClient: async () => { @@ -28,7 +29,7 @@ export const sessionStoragePersister: Persister = { const cached = sessionStorage.getItem(key); return cached ? JSON.parse(cached) : undefined; } catch (error) { - console.error('Failed to restore client:', error); + logger.error('Failed to restore client:', error); return undefined; } }, @@ -39,7 +40,7 @@ export const sessionStoragePersister: Persister = { const key = getUserCacheKey(); sessionStorage.removeItem(key); } catch (error) { - console.error('Failed to remove client:', error); + logger.error('Failed to remove client:', error); } } }; diff --git a/backend/islands/apps/remotes/src/scripts/syntax-highlighting.ts b/backend/islands/apps/remotes/src/scripts/syntax-highlighting.ts index 5d092d00c..ebc0d9f96 100644 --- a/backend/islands/apps/remotes/src/scripts/syntax-highlighting.ts +++ b/backend/islands/apps/remotes/src/scripts/syntax-highlighting.ts @@ -4,6 +4,7 @@ import { registerLanguage, highlightElement } from '@blex/editor'; +import { logger } from '~/utils/logger'; const loadedLanguages = new Set(); @@ -34,7 +35,7 @@ function createCopyButton(codeElement: HTMLElement): HTMLButtonElement { button.title = 'Copy code'; }, 2000); } catch (err) { - console.error('Failed to copy code:', err); + logger.error('Failed to copy code:', err); } }); @@ -53,7 +54,7 @@ async function loadLanguage(language: string): Promise { registerLanguage(language, module.default); loadedLanguages.add(language); } catch (error) { - console.warn(`Failed to load language: ${language}`, error); + logger.warn(`Failed to load language: ${language}`, error); } } } diff --git a/backend/islands/apps/remotes/src/utils/logger.ts b/backend/islands/apps/remotes/src/utils/logger.ts new file mode 100644 index 000000000..f6361bc73 --- /dev/null +++ b/backend/islands/apps/remotes/src/utils/logger.ts @@ -0,0 +1,36 @@ +/* eslint-disable no-console */ + +class Logger { + private static instance: Logger; + + private constructor() { } + + public static getInstance(): Logger { + if (!Logger.instance) { + Logger.instance = new Logger(); + } + return Logger.instance; + } + + public info(message: string, ...args: unknown[]) { + console.info(message, ...args); + } + + public warn(message: string, ...args: unknown[]) { + console.warn(message, ...args); + } + + public error(message: string, ...args: unknown[]) { + console.error(message, ...args); + } + + public debug(message: string, ...args: unknown[]) { + // Only log debug messages in development + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((import.meta as any).env.DEV) { + console.debug(message, ...args); + } + } +} + +export const logger = Logger.getInstance(); diff --git a/backend/src/board/templates/board/base.html b/backend/src/board/templates/board/base.html index 09f5db1d8..95e7a4138 100644 --- a/backend/src/board/templates/board/base.html +++ b/backend/src/board/templates/board/base.html @@ -60,9 +60,8 @@ githubClientId: "{{ GITHUB_OAUTH_CLIENT_ID|default:'' }}" }; - {% block extra_scripts %}{% endblock %} - {% vite_hmr_client %} + {% block extra_scripts %}{% endblock %} From caa48168196a5083bad9693ccb2d42c2f933d841 Mon Sep 17 00:00:00 2001 From: Jino Bae Date: Sat, 17 Jan 2026 00:21:09 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Restore=20lazy=20loadi?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/islands/apps/remotes/src/island.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/islands/apps/remotes/src/island.tsx b/backend/islands/apps/remotes/src/island.tsx index 18f6499e8..90af4c7da 100644 --- a/backend/islands/apps/remotes/src/island.tsx +++ b/backend/islands/apps/remotes/src/island.tsx @@ -24,12 +24,17 @@ customElements.define('island-component', class extends HTMLElement { connectedCallback(): void { const name = this.getAttribute('name'); + const lazy = this.getAttribute('lazy') === 'true'; if (!name) { return; } - this.renderComponent(name); + if (lazy) { + this.setupLazyLoading(name); + } else { + this.renderComponent(name); + } } disconnectedCallback(): void {