diff --git a/src/common/utils/call-event.ts b/src/common/utils/call-event.ts index 506d9654..ce44c23b 100644 --- a/src/common/utils/call-event.ts +++ b/src/common/utils/call-event.ts @@ -31,8 +31,8 @@ export interface EventName { name: string template: string } - openJumpPage: null - closeJumpPage: null + openExplorerPage: null + closeExplorerPage: null // setting keys wigiPadDateSettingsChanged: WigiPadDateSetting diff --git a/src/layouts/explorer/explorer.tsx b/src/layouts/explorer/explorer.tsx new file mode 100644 index 00000000..cf63eb12 --- /dev/null +++ b/src/layouts/explorer/explorer.tsx @@ -0,0 +1,283 @@ +import { useGetContents } from '@/services/hooks/content/get-content.hook' +import { useRef, useState, useEffect } from 'react' +import Analytics from '@/analytics' +import { getFaviconFromUrl } from '@/common/utils/icon' +import { HiOutlineLightBulb } from 'react-icons/hi2' + +interface LinkItem { + name: string + url: string + icon?: string + badge?: string + badgeColor?: string +} + +interface CategoryItem { + id: string + category: string + banner?: string + links: LinkItem[] + icon?: string +} + +export function ExplorerContent() { + const { data: catalogData } = useGetContents() + const [activeCategory, setActiveCategory] = useState(null) + const categoryRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}) + const scrollContainerRef = useRef(null) + + useEffect(() => { + const observerOptions = { + root: scrollContainerRef.current, + rootMargin: '-10% 0px -80% 0px', + threshold: 0, + } + + const observerCallback = (entries: IntersectionObserverEntry[]) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveCategory(entry.target.id) + } + }) + } + + const observer = new IntersectionObserver(observerCallback, observerOptions) + Object.values(categoryRefs.current).forEach((div) => { + if (div) observer.observe(div) + }) + return () => observer.disconnect() + }, [catalogData]) + + const scrollToCategory = (id: string) => { + setActiveCategory(id) + categoryRefs.current[id]?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }) + Analytics.event('explorer_click_category') + } + + return ( +
+ + +
+
+ {catalogData?.contents?.map((cat: CategoryItem) => ( + + ))} +
+ +
+
+
+
+ {catalogData?.contents?.map( + (category: CategoryItem, index: number) => ( +
{ + categoryRefs.current[category.id] = el + }} + className={`relative overflow-hidden border scroll-mt-2 bg-content bg-glass border-base-300 rounded-3xl transition-all duration-300 ${ + index % 3 === 0 + ? 'md:col-span-2' + : 'md:col-span-1' + }`} + > + {category.banner && ( +
+ +
+ )} + +
+
+
+ {category.icon ? ( + + ) : ( +
+ )} +

+ {category.category} +

+
+
+
+ +
+ {category.links?.map((link, idx) => ( + +
+ {link.badge && ( + + {link.badge} + + )} + {link.name} +
+ + {link.name} + +
+ ))} +
+
+
+ ) + )} +
+
+
+ +
+
+ ) +} + +function getUrl(url: string) { + return url.startsWith('http') ? url : `https://${url}` +} diff --git a/src/layouts/navbar/navbar.layout.tsx b/src/layouts/navbar/navbar.layout.tsx index 5efbf80e..15125310 100644 --- a/src/layouts/navbar/navbar.layout.tsx +++ b/src/layouts/navbar/navbar.layout.tsx @@ -13,11 +13,72 @@ import { SyncButton } from './sync/sync' import { useAppearanceSetting } from '@/context/appearance.context' import { MarketButton } from './market/market-button' import Analytics from '@/analytics' +import { HiRectangleGroup } from 'react-icons/hi2' const WIDGETIFY_URLS = { website: 'https://widgetify.ir', } as const +const tabs = [ + { + id: 'explorer', + icon: , + hasBadge: true, + }, + { + id: 'home', + icon: , + }, +] +export function NavbarTabs() { + const [activeTab, setActiveTab] = useState('home') + + const handleTabClick = (tab: string) => { + setActiveTab(tab) + if (tab === 'home') callEvent('closeExplorerPage') + else callEvent('openExplorerPage') + Analytics.event(`navbar_tab_${tab}_click`) + } + + return ( +
+ {tabs.map((tab) => ( + + ))} +
+ ) +} + export function NavbarLayout(): JSX.Element { const { canReOrderWidget, toggleCanReOrderWidget } = useAppearanceSetting() const [showSettings, setShowSettings] = useState(false) @@ -124,12 +185,7 @@ export function NavbarLayout(): JSX.Element {
- + = ({ onTabClick, }) => { return ( -
+
{tabs.map((tab) => (
onTabClick(tab.id)} - className={`cursor-pointer rounded-xl py-0.5 px-2 transition-colors ${ + className={`flex-1 cursor-pointer text-center rounded-xl py-1 px-3 transition-all duration-200 ${ activeTab === tab.id - ? 'bg-primary text-gray-200' - : 'hover:bg-primary/10' + ? 'bg-primary text-white' + : 'text-content hover:bg-base-300' }`} > - {tab.label} + {tab.label}
))}
diff --git a/src/layouts/widgets/widget-container.tsx b/src/layouts/widgets/widget-container.tsx index b84d81bf..473850f8 100644 --- a/src/layouts/widgets/widget-container.tsx +++ b/src/layouts/widgets/widget-container.tsx @@ -12,7 +12,7 @@ export function WidgetContainer({ }: WidgetContainerProps) { return (
{children} diff --git a/src/pages/home.tsx b/src/pages/home.tsx index e905cee4..ab82ccb5 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -15,6 +15,7 @@ import type { WidgetTabKeys } from '@/layouts/widgets-settings/constant/tab-keys import { WidgetSettingsModal } from '@/layouts/widgets-settings/widget-settings-modal' import { getRandomWallpaper } from '@/services/hooks/wallpapers/getWallpaperCategories.hook' import { ContentSection } from './home/content-section' +import { ExplorerContent } from '@/layouts/explorer/explorer' const steps: Step[] = [ { @@ -83,6 +84,7 @@ export function HomePage() { const [showWidgetSettings, setShowWidgetSettings] = useState(false) const [tab, setTab] = useState(null) const [showTour, setShowTour] = useState(false) + const [currentView, setCurrentView] = useState<'home' | 'explore'>('home') useEffect(() => { async function displayModalIfNeeded() { @@ -160,11 +162,21 @@ export function HomePage() { } ) + const openExplorerPageEvent = listenEvent('openExplorerPage', () => { + setCurrentView('explore') + }) + + const closeExplorerPageEvent = listenEvent('closeExplorerPage', () => { + setCurrentView('home') + }) + Analytics.pageView('Home', '/') return () => { wallpaperChangedEvent() openWidgetsSettingsEvent() + openExplorerPageEvent() + closeExplorerPageEvent() } }, []) @@ -254,7 +266,7 @@ export function HomePage() { - + {currentView === 'home' ? : } { diff --git a/src/pages/home/content-section.tsx b/src/pages/home/content-section.tsx index 210a17e1..d0826089 100644 --- a/src/pages/home/content-section.tsx +++ b/src/pages/home/content-section.tsx @@ -119,7 +119,7 @@ export function ContentSection() {
diff --git a/src/services/hooks/content/get-content.hook.ts b/src/services/hooks/content/get-content.hook.ts new file mode 100644 index 00000000..32ed895d --- /dev/null +++ b/src/services/hooks/content/get-content.hook.ts @@ -0,0 +1,23 @@ +import { getMainClient } from '@/services/api' +import { useQuery } from '@tanstack/react-query' + +export interface FetchedContent { + contents: { + id: string + category: string + links: { name: string; url: string; icon?: string }[] + }[] +} + +export const useGetContents = () => { + return useQuery({ + queryKey: ['contents'], + queryFn: async () => { + const api = await getMainClient() + + const { data } = await api.get('/contents/beta') + return data + }, + staleTime: 5 * 60 * 1000, // 5 minutes + }) +} diff --git a/src/services/hooks/todo/get-tags.hook.ts b/src/services/hooks/todo/get-tags.hook.ts index 229e6869..1a10c5f3 100644 --- a/src/services/hooks/todo/get-tags.hook.ts +++ b/src/services/hooks/todo/get-tags.hook.ts @@ -5,9 +5,8 @@ export const useGetTags = (enabled: boolean) => { return useQuery({ queryKey: ['getTags'], queryFn: async () => getTags(), - retry: 0, + staleTime: 5 * 60 * 1000, // 5 minutes enabled, - initialData: [], }) } export async function getTags(): Promise {