diff --git a/src/components/Elements/MainModal/Main.jsx b/src/components/Elements/MainModal/Main.jsx index 5ce64dfc..5b2214b1 100644 --- a/src/components/Elements/MainModal/Main.jsx +++ b/src/components/Elements/MainModal/Main.jsx @@ -79,6 +79,22 @@ function MainModal({ modalClose, deepLinkData }) { setCurrentTab(linkData.tab); } + // Handle discover category navigation - update section state to match hash + if (linkData.tab === TAB_TYPES.DISCOVER && linkData.category) { + const categoryToLabel = { + all: variables.getMessage('modals.main.marketplace.all'), + photo_packs: variables.getMessage('modals.main.marketplace.photo_packs'), + quote_packs: variables.getMessage('modals.main.marketplace.quote_packs'), + preset_settings: variables.getMessage('modals.main.marketplace.preset_settings'), + collections: variables.getMessage('modals.main.marketplace.collections'), + }; + const sectionLabel = categoryToLabel[linkData.category]; + if (sectionLabel) { + setCurrentSection(sectionLabel); + setCurrentSectionName(linkData.category); + } + } + // Handle settings section navigation if (linkData.tab === TAB_TYPES.SETTINGS && linkData.section) { setNavigationTrigger({ @@ -232,6 +248,7 @@ function MainModal({ modalClose, deepLinkData }) { productView={productView} iframeBreadcrumbs={iframeBreadcrumbs} onTabChange={handleChangeTab} + onSectionChange={handleSectionChange} onSubSectionChange={handleSubSectionChange} onClose={modalClose} onBack={handleBack} diff --git a/src/components/Elements/MainModal/components/ModalTopBar.jsx b/src/components/Elements/MainModal/components/ModalTopBar.jsx index 50e5a558..62b894b4 100644 --- a/src/components/Elements/MainModal/components/ModalTopBar.jsx +++ b/src/components/Elements/MainModal/components/ModalTopBar.jsx @@ -9,15 +9,27 @@ import mueAboutIcon from 'assets/icons/mue_about.png'; // Map marketplace types to translation keys const MARKETPLACE_TYPE_TO_KEY = { photo_packs: 'modals.main.marketplace.photo_packs', + 'photo packs': 'modals.main.marketplace.photo_packs', photos: 'modals.main.marketplace.photo_packs', quote_packs: 'modals.main.marketplace.quote_packs', + 'quote packs': 'modals.main.marketplace.quote_packs', quotes: 'modals.main.marketplace.quote_packs', preset_settings: 'modals.main.marketplace.preset_settings', + 'preset settings': 'modals.main.marketplace.preset_settings', settings: 'modals.main.marketplace.preset_settings', collections: 'modals.main.marketplace.collections', all: 'modals.main.marketplace.all', }; +// Map breadcrumb labels (from website) to category keys for navigation +const BREADCRUMB_LABEL_TO_CATEGORY = { + 'photo packs': 'photo_packs', + 'quote packs': 'quote_packs', + 'preset settings': 'preset_settings', + collections: 'collections', + marketplace: 'all', +}; + function ModalTopBar({ currentTab, currentSection, @@ -26,6 +38,7 @@ function ModalTopBar({ productView, iframeBreadcrumbs, onTabChange, + onSectionChange, onSubSectionChange, onClose, onBack, @@ -94,7 +107,13 @@ function ModalTopBar({ if (currentTabLabel) { breadcrumbPath.push({ label: currentTabLabel, - onClick: productView ? productView.onBackToAll : null, // Clickable if viewing a product + // Make "Discover" clickable when viewing items/categories to go back to "All" + onClick: + (iframeBreadcrumbs && iframeBreadcrumbs.length > 0) || productView + ? () => { + updateHash('#discover/all'); + } + : null, }); // Check if we have iframe breadcrumbs (from Discover iframe) @@ -106,24 +125,42 @@ function ModalTopBar({ relevantCrumbs.forEach((crumb, index) => { const isLast = index === relevantCrumbs.length - 1; + // Translate the breadcrumb label if it's a known category + const lowerLabel = crumb.label.toLowerCase(); + const translationKey = MARKETPLACE_TYPE_TO_KEY[lowerLabel]; + const displayLabel = translationKey ? t(translationKey) : crumb.label; + + // Get the category key for navigation + const categoryKey = BREADCRUMB_LABEL_TO_CATEGORY[lowerLabel]; + breadcrumbPath.push({ - label: crumb.label, + label: displayLabel, // Make it clickable if it has an href and it's not the last item onClick: crumb.clickable && !isLast && crumb.href ? () => { // Convert website href to extension hash format - // e.g., /marketplace/packs/123 -> #discover/photo_packs/123 - // e.g., /marketplace/collections -> #discover/collections const href = crumb.href; - if (href.includes('/collections')) { + // Try to extract type from URL parameters first (most reliable) + if (href.includes('type=')) { + const urlParams = new URLSearchParams(href.split('?')[1]); + const typeParam = urlParams.get('type'); + if (typeParam) { + updateHash(`#discover/${typeParam}`); + } + } + // Otherwise check specific paths + else if (href.includes('/collections')) { updateHash('#discover/collections'); } else if (href.includes('/collection/')) { const collectionId = href.split('/collection/')[1]?.split('?')[0]; if (collectionId) { updateHash(`#discover/collection/${collectionId}`); } + } else if (categoryKey) { + // If we recognized the category from the label, navigate to it + updateHash(`#discover/${categoryKey}`); } else if (href === '/marketplace' || href === '/marketplace/') { updateHash('#discover/all'); } diff --git a/src/features/misc/views/Discover.jsx b/src/features/misc/views/Discover.jsx index 1e31774d..de044c12 100644 --- a/src/features/misc/views/Discover.jsx +++ b/src/features/misc/views/Discover.jsx @@ -22,6 +22,92 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { const previewParam = isPreviewMode ? '&preview=true' : ''; const isOffline = navigator.onLine === false || offlineMode; + // Helper function to update iframe based on hash + const updateIframeFromHash = useRef(() => { + if (!iframeRef.current) return; + + const hash = window.location.hash; + if (!hash || !hash.startsWith('#discover')) return; + + const parts = hash.slice(1).split('/'); + if (parts.length < 2) return; + + const theme = getResolvedTheme(); + const themeParam = `&theme=${theme}`; + let targetUrl = ''; + + if (parts[1] === 'collections') { + targetUrl = `${MARKETPLACE_URL}/collections?embed=true${previewParam}${themeParam}`; + } else if (parts[1] === 'collection' && parts[2]) { + targetUrl = `${MARKETPLACE_URL}/collection/${parts[2]}?embed=true${previewParam}${themeParam}`; + } else if (parts[2]) { + // Item view - map category to path + const pathMap = { + photo_packs: 'packs', + quote_packs: 'packs', + preset_settings: 'presets', + }; + const pathSegment = pathMap[parts[1]] || 'packs'; + targetUrl = `${MARKETPLACE_URL}/${pathSegment}/${parts[2]}?embed=true${previewParam}${themeParam}`; + } else if (parts[1] === 'all') { + // All items + targetUrl = `${MARKETPLACE_URL}?embed=true${previewParam}${themeParam}`; + } else { + // Category filter (photo_packs, quote_packs, preset_settings) + targetUrl = `${MARKETPLACE_URL}?embed=true&type=${parts[1]}${previewParam}${themeParam}`; + } + + // Update iframe src directly + if (targetUrl && iframeRef.current.src !== targetUrl) { + setIsLoading(true); + iframeRef.current.src = targetUrl; + } + }); + + // Update the ref function when previewParam changes + useEffect(() => { + updateIframeFromHash.current = () => { + if (!iframeRef.current) return; + + const hash = window.location.hash; + if (!hash || !hash.startsWith('#discover')) return; + + const parts = hash.slice(1).split('/'); + if (parts.length < 2) return; + + const theme = getResolvedTheme(); + const themeParam = `&theme=${theme}`; + let targetUrl = ''; + + if (parts[1] === 'collections') { + targetUrl = `${MARKETPLACE_URL}/collections?embed=true${previewParam}${themeParam}`; + } else if (parts[1] === 'collection' && parts[2]) { + targetUrl = `${MARKETPLACE_URL}/collection/${parts[2]}?embed=true${previewParam}${themeParam}`; + } else if (parts[2]) { + // Item view - map category to path + const pathMap = { + photo_packs: 'packs', + quote_packs: 'packs', + preset_settings: 'presets', + }; + const pathSegment = pathMap[parts[1]] || 'packs'; + targetUrl = `${MARKETPLACE_URL}/${pathSegment}/${parts[2]}?embed=true${previewParam}${themeParam}`; + } else if (parts[1] === 'all') { + // All items + targetUrl = `${MARKETPLACE_URL}?embed=true${previewParam}${themeParam}`; + } else { + // Category filter (photo_packs, quote_packs, preset_settings) + targetUrl = `${MARKETPLACE_URL}?embed=true&type=${parts[1]}${previewParam}${themeParam}`; + } + + // Update iframe src directly + if (targetUrl && iframeRef.current.src !== targetUrl) { + setIsLoading(true); + iframeRef.current.src = targetUrl; + } + }; + }, [previewParam]); + // Clear breadcrumbs when component unmounts (navigating away from discover) useEffect(() => { return () => { @@ -142,57 +228,29 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { } }, [deepLinkData, previewParam]); - // Send navigation commands to iframe when hash changes externally (e.g., browser back/forward) + // Send navigation commands to iframe when hash changes externally (e.g., browser back/forward, breadcrumb clicks) useEffect(() => { const handleHashChange = () => { - if (!iframeRef.current?.contentWindow) return; + updateIframeFromHash.current(); + }; - const hash = window.location.hash; - if (!hash || !hash.startsWith('#discover')) return; - - // Parse hash to determine target path - // e.g., #discover/photo_packs/123 -> /marketplace/packs/123 - // e.g., #discover/preset_settings/456 -> /marketplace/presets/456 - // e.g., #discover/collections -> /marketplace/collections - // e.g., #discover/collection/featured -> /marketplace/collection/featured - - const parts = hash.slice(1).split('/'); - if (parts.length < 2) return; - - let targetPath = '/marketplace'; - - if (parts[1] === 'collections') { - targetPath = '/marketplace/collections'; - } else if (parts[1] === 'collection' && parts[2]) { - targetPath = `/marketplace/collection/${parts[2]}`; - } else if (parts[2]) { - // Item view - map category to path - const pathMap = { - photo_packs: 'packs', - quote_packs: 'packs', - preset_settings: 'presets', - }; - const pathSegment = pathMap[parts[1]] || 'packs'; - targetPath = `/marketplace/${pathSegment}/${parts[2]}`; - } else if (parts[1] !== 'all') { - // Category filter - targetPath = `/marketplace?type=${parts[1]}`; - } - - // Send navigation command to iframe - const theme = getResolvedTheme(); - iframeRef.current.contentWindow.postMessage( - { - type: 'marketplace:navigate', - payload: { path: targetPath }, - }, - MARKETPLACE_URL, - ); + const handlePopState = () => { + updateIframeFromHash.current(); }; // Listen for hash changes from browser navigation window.addEventListener('hashchange', handleHashChange); - return () => window.removeEventListener('hashchange', handleHashChange); + window.addEventListener('popstate', handlePopState); + + // Also trigger immediately if hash exists + if (window.location.hash.startsWith('#discover')) { + updateIframeFromHash.current(); + } + + return () => { + window.removeEventListener('hashchange', handleHashChange); + window.removeEventListener('popstate', handlePopState); + }; }, []); useEffect(() => { diff --git a/src/utils/deepLinking.js b/src/utils/deepLinking.js index 7c15bf38..5e5f553e 100644 --- a/src/utils/deepLinking.js +++ b/src/utils/deepLinking.js @@ -152,6 +152,8 @@ export const updateHash = (hash, pushToHistory = true) => { } else { window.history.replaceState(null, null, hash); } + // Dispatch a custom event so components can react to programmatic hash changes + window.dispatchEvent(new PopStateEvent('popstate')); } else { window.location.hash = hash; }