diff --git a/hooks/use-webview-message-handler.ts b/hooks/use-webview-message-handler.ts index dbfa87a..3b24a00 100644 --- a/hooks/use-webview-message-handler.ts +++ b/hooks/use-webview-message-handler.ts @@ -1,5 +1,9 @@ -import { WebViewMessage, WebViewMessageEvent, WebViewMessageTypes } from '@/types/webview-message.types'; -import { useCallback } from 'react'; +import { + WebViewMessage, + WebViewMessageEvent, + WebViewMessageTypes, +} from "@/types/webview-message.types"; +import { useCallback } from "react"; interface UseWebViewMessageHandlerOptions { // 뒤로가기 요청 시 호출 @@ -8,8 +12,6 @@ interface UseWebViewMessageHandlerOptions { onSubscribe?: (clubId: string, clubName?: string) => Promise | void; // 알림 구독 해제 요청 시 호출 onUnsubscribe?: (clubId: string) => Promise | void; - // 공유하기 요청 시 호출 - onShare?: (payload: { title: string; text: string; url: string }) => Promise | void; } // WebView 메시지를 처리하는 Hook @@ -17,41 +19,38 @@ export const useWebViewMessageHandler = ({ onNavigateBack, onSubscribe, onUnsubscribe, - onShare, }: UseWebViewMessageHandlerOptions) => { - const handleMessage = useCallback((event: WebViewMessageEvent) => { - try { - const data = event.nativeEvent.data; - if (!data) return; + const handleMessage = useCallback( + (event: WebViewMessageEvent) => { + try { + const data = event.nativeEvent.data; + if (!data) return; - const message: WebViewMessage = JSON.parse(data); + const message: WebViewMessage = JSON.parse(data); - switch (message.type) { - case WebViewMessageTypes.NAVIGATE_BACK: - onNavigateBack?.(); - break; - case WebViewMessageTypes.NOTIFICATION_SUBSCRIBE: - if (message.payload?.clubId) { - onSubscribe?.(message.payload.clubId, message.payload.clubName); - } - break; - case WebViewMessageTypes.NOTIFICATION_UNSUBSCRIBE: - if (message.payload?.clubId) { - onUnsubscribe?.(message.payload.clubId); - } - break; - case WebViewMessageTypes.SHARE: - if (message.payload) { - onShare?.(message.payload); - } - break; - default: - console.warn('[WebViewHandler] 알 수 없는 메시지 타입:', message); + switch (message.type) { + case WebViewMessageTypes.NAVIGATE_BACK: + onNavigateBack?.(); + break; + case WebViewMessageTypes.NOTIFICATION_SUBSCRIBE: + if (message.payload?.clubId) { + onSubscribe?.(message.payload.clubId, message.payload.clubName); + } + break; + case WebViewMessageTypes.NOTIFICATION_UNSUBSCRIBE: + if (message.payload?.clubId) { + onUnsubscribe?.(message.payload.clubId); + } + break; + default: + console.warn("[WebViewHandler] 알 수 없는 메시지 타입:", message); + } + } catch (error) { + console.error("[WebViewHandler] 메시지 파싱 오류:", error); } - } catch (error) { - console.error('[WebViewHandler] 메시지 파싱 오류:', error); - } - }, [onNavigateBack, onSubscribe, onUnsubscribe, onShare]); + }, + [onNavigateBack, onSubscribe, onUnsubscribe], + ); return { handleMessage }; }; diff --git a/types/webview-message.types.ts b/types/webview-message.types.ts index 6ffe7d9..6829d3a 100644 --- a/types/webview-message.types.ts +++ b/types/webview-message.types.ts @@ -1,19 +1,20 @@ - //WebView 메시지 타입 상수 export const WebViewMessageTypes = { - NAVIGATE_BACK: 'NAVIGATE_BACK', - NOTIFICATION_SUBSCRIBE: 'NOTIFICATION_SUBSCRIBE', - NOTIFICATION_UNSUBSCRIBE: 'NOTIFICATION_UNSUBSCRIBE', - SHARE: 'SHARE', + NAVIGATE_BACK: "NAVIGATE_BACK", + NOTIFICATION_SUBSCRIBE: "NOTIFICATION_SUBSCRIBE", + NOTIFICATION_UNSUBSCRIBE: "NOTIFICATION_UNSUBSCRIBE", + SHARE: "SHARE", } as const; // WebView 메시지 Discriminated Union 타입 - + export type WebViewMessage = - | { type: 'NAVIGATE_BACK' } - | { type: 'NOTIFICATION_SUBSCRIBE'; payload: { clubId: string; clubName?: string } } - | { type: 'NOTIFICATION_UNSUBSCRIBE'; payload: { clubId: string } } - | { type: 'SHARE'; payload: { title: string; text: string; url: string } }; + | { type: "NAVIGATE_BACK" } + | { + type: "NOTIFICATION_SUBSCRIBE"; + payload: { clubId: string; clubName?: string }; + } + | { type: "NOTIFICATION_UNSUBSCRIBE"; payload: { clubId: string } }; // WebView 메시지 이벤트 타입 (react-native-webview) export interface WebViewMessageEvent { diff --git a/ui/club-detail/club-detail-screen.tsx b/ui/club-detail/club-detail-screen.tsx index af39a52..db63628 100644 --- a/ui/club-detail/club-detail-screen.tsx +++ b/ui/club-detail/club-detail-screen.tsx @@ -1,19 +1,23 @@ -import { MoaImage } from '@/components/moa-image'; -import { MoaText } from '@/components/moa-text'; -import { PermissionDialog } from '@/components/permission-dialog'; -import { USER_EVENT } from '@/constants/eventname'; -import { useMixpanelContext } from '@/contexts'; -import { useSubscribedClubsContext } from '@/contexts/subscribed-clubs-context'; -import { useMixpanelTrack, useWebViewMessageHandler } from '@/hooks'; -import { Ionicons } from '@expo/vector-icons'; -import Constants from 'expo-constants'; -import { useLocalSearchParams, useRouter } from 'expo-router'; -import { StatusBar } from 'expo-status-bar'; -import { useMemo, useState } from 'react'; -import { ActivityIndicator, Platform, Share, TouchableOpacity } from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { WebView } from 'react-native-webview'; -import styled from 'styled-components/native'; +import { MoaImage } from "@/components/moa-image"; +import { MoaText } from "@/components/moa-text"; +import { PermissionDialog } from "@/components/permission-dialog"; +import { USER_EVENT } from "@/constants/eventname"; +import { useMixpanelContext } from "@/contexts"; +import { useSubscribedClubsContext } from "@/contexts/subscribed-clubs-context"; +import { useMixpanelTrack, useWebViewMessageHandler } from "@/hooks"; +import { Ionicons } from "@expo/vector-icons"; +import Constants from "expo-constants"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { StatusBar } from "expo-status-bar"; +import { useMemo, useState } from "react"; +import { + ActivityIndicator, + Platform, + TouchableOpacity +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { WebView } from "react-native-webview"; +import styled from "styled-components/native"; export default function ClubWebViewScreen() { const router = useRouter(); @@ -34,13 +38,13 @@ export default function ClubWebViewScreen() { const cleanUrl = webviewUrl?.replace(/\/$/, "") || ""; const baseUrl = `${cleanUrl}/club/${id}`; - + const params = new URLSearchParams(); if (sessionId) { - params.append('session_id', sessionId); + params.append("session_id", sessionId); } if (id && isSubscribed(id)) { - params.append('is_subscribed', 'true'); + params.append("is_subscribed", "true"); } const queryString = params.toString(); @@ -109,13 +113,13 @@ export default function ClubWebViewScreen() { trackEvent(USER_EVENT.SUBSCRIBE_BUTTON_CLICKED, { clubName: clubName || name, subscribed: true, - from: 'club_detail', - url: 'app://moadong/club', + from: "club_detail", + url: "app://moadong/club", }); // 이미 구독 중이면 무시 if (isSubscribed(targetId)) return; - + const result = await toggleSubscribe(targetId); if (result.needsPermission) { setShowPermissionDialog(true); @@ -124,27 +128,20 @@ export default function ClubWebViewScreen() { onUnsubscribe: async (targetId: string) => { // 구독 중이 아니면 무시 if (!isSubscribed(targetId)) return; - + trackEvent(USER_EVENT.SUBSCRIBE_BUTTON_CLICKED, { clubName: name, subscribed: false, - from: 'club_detail', - url: 'app://moadong/club', + from: "club_detail", + url: "app://moadong/club", }); - + await toggleSubscribe(targetId); }, - onShare: async ({ title, text, url }: { title: string; text: string; url: string }) => { - await Share.share({ - title, - message: text, - url, - }); - }, }); return ( - + {hasError && (
@@ -156,8 +153,8 @@ export default function ClubWebViewScreen() { {isLoading && (