From e8c0aa74d48067bf1d68a852c792ee9ebee0411c Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Tue, 27 Jan 2026 21:19:32 +0330 Subject: [PATCH 01/17] add auth status --- .../tabs/account/auth-form/auth-otp.tsx | 23 +++++++++++++++---- src/services/hooks/auth/authService.hook.ts | 17 +++++++++++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx b/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx index 700bcbcb..e0afb103 100644 --- a/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx +++ b/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx @@ -2,7 +2,11 @@ import { useState } from 'react' import { FiKey, FiArrowRight, FiRefreshCw } from 'react-icons/fi' import { TextInput } from '@/components/text-input' import { Button } from '@/components/button/button' -import { useRequestOtp, useVerifyOtp } from '@/services/hooks/auth/authService.hook' +import { + useGetAuthStatus, + useRequestOtp, + useVerifyOtp, +} from '@/services/hooks/auth/authService.hook' import { translateError } from '@/utils/translate-error' import { useAuth } from '@/context/auth.context' import { isEmpty, isEmail, isLessThan } from '@/utils/validators' @@ -11,6 +15,7 @@ import OtpInput from './components/otp-input' import { callEvent } from '@/common/utils/call-event' import { sleep } from '@/common/utils/timeout' import { MdDoorSliding } from 'react-icons/md' +import { TiInfoOutline, TiWarningOutline } from 'react-icons/ti' type AuthOtpProps = { step: 'enter-email' | 'enter-otp' @@ -32,6 +37,7 @@ const AuthOtp: React.FC = ({ step, setStep }) => { setError({ email: null, otp: null, api: null }) } + const { data: authStatus } = useGetAuthStatus() const { mutateAsync: requestOtp, isPending } = useRequestOtp() const { mutateAsync: verifyOtp } = useVerifyOtp() @@ -128,7 +134,7 @@ const AuthOtp: React.FC = ({ step, setStep }) => { if (step === 'enter-email') return (
-
+
-
+ + + {authStatus?.content && ( +
+ + {authStatus.content} +
+ )}
+ + ) : ( +
+
+ {c} + {currentFolderId && ( + + )} +
+ +
+ {displayItems.length > 0 ? ( + displayItems.map((item) => ( +
handleClickItem(item)} + className="flex items-center gap-2.5 p-2 hover:bg-primary/5 hover:text-primary/80 rounded-xl cursor-pointer transition-all group" + > + {item.type === 'FOLDER' ? ( + + ) : ( + + )} + + {item.title} + + {item.type === 'FOLDER' ? ( + + ) : ( + + )} +
+ )) + ) : ( +
+ پوشه خالی است +
+ )} +
+
+ )} + , + document.body + ) +} diff --git a/src/layouts/search/browser-bookmark/browser-bookmark.tsx b/src/layouts/search/browser-bookmark/browser-bookmark.tsx index b153ba67..136af034 100644 --- a/src/layouts/search/browser-bookmark/browser-bookmark.tsx +++ b/src/layouts/search/browser-bookmark/browser-bookmark.tsx @@ -1,218 +1,118 @@ -import { useEffect, useState } from 'react' -import { type RecommendedSite, useGetTrends } from '@/services/hooks/trends/getTrends' -import 'swiper/css' - -import { FiChevronLeft, FiFolder } from 'react-icons/fi' -import { FreeMode, Navigation } from 'swiper/modules' -import { Swiper, SwiperSlide } from 'swiper/react' -import Analytics from '@/analytics' -import { getFromStorage, setToStorage } from '@/common/storage' +import { useState, useRef, useEffect } from 'react' +import { useGetTrends } from '@/services/hooks/trends/getTrends' +import { setToStorage } from '@/common/storage' import { getFaviconFromUrl } from '@/common/utils/icon' import Tooltip from '@/components/toolTip' -import { useGeneralSetting } from '@/context/general-setting.context' -import { - type FetchedBrowserBookmark, - getBrowserBookmarks, -} from '@/layouts/bookmark/utils/browser-bookmarks.util' +import { HiRectangleGroup } from 'react-icons/hi2' +import { MdFolderSpecial } from 'react-icons/md' +import { BookmarkPopover } from './bookmark-popover' +import Analytics from '@/analytics' +import { callEvent } from '@/common/utils/call-event' +import { usePage } from '@/context/page.context' -interface BookmarkItem { - id?: string - name?: string - title?: string - url?: string | null - icon?: string | React.ReactNode - isFolder?: boolean -} +export function BrowserBookmark() { + const { data } = useGetTrends({ enabled: true }) + const { setPage } = usePage() -interface BookmarkSwiperProps { - items: BookmarkItem[] - spaceBetween?: number - grabCursor?: boolean - navigation?: boolean - slidesPerView: number - type: 'browser' | 'recommended' -} + const [isOpen, setIsOpen] = useState(false) + const [popoverCoords, setPopoverCoords] = useState({ top: 0, left: 0 }) + const iconRef = useRef(null) -function BookmarkSwiper({ - items, - spaceBetween = 1, - grabCursor = true, - type, - onItemClick, - slidesPerView, -}: BookmarkSwiperProps & { onItemClick?: (item: BookmarkItem) => void }) { - const swiperProps = { - modules: [FreeMode, Navigation], - spaceBetween, - slidesPerView, - grabCursor, - className: 'w-full bg-content rounded-2xl !pl-3.5 !pr-1', - dir: 'rtl' as const, - } - - function onClick(item: BookmarkItem) { - if (onItemClick) { - onItemClick(item) - Analytics.event('browser_bookmark_folder_clicked') - return + useEffect(() => { + if (data?.recommendedSites?.length) { + setToStorage('recommended_sites', data.recommendedSites) } + }, [data]) - if (item.url) { - window.open(item.url, '_blank') - Analytics.event(`${type}_bookmark_clicked`) + const handleTogglePopover = () => { + if (iconRef.current) { + const rect = iconRef.current.getBoundingClientRect() + setPopoverCoords({ + top: rect.bottom + 10, + left: rect.left, + }) } + + Analytics.event('browser_bookmark_popover_toggled') + + setIsOpen((prev) => !prev) + } + + const onClickToExplorer = () => { + setPage('explorer') + Analytics.event('searchbox_explorer_page_opened') } return ( - - {items.map((item, index) => ( - - +
+
+
+
onClick(item)} + className="flex items-center cursor-pointer group" + onClick={() => onClickToExplorer()} > - {typeof item.icon === 'string' ? ( - + - ) : item.icon ? ( -
- {item.icon} -
- ) : ( - - )} +
- - ))} - - ) -} - -export function BrowserBookmark() { - const { browserBookmarksEnabled } = useGeneralSetting() - - const { data } = useGetTrends({ - enabled: true, - }) - const [fetchedBookmarks, setFetchedBookmarks] = useState([]) - const [browserBookmarks, setBrowserBookmarks] = useState([]) - const [currentFolderId, setCurrentFolderId] = useState(null) - - useEffect(() => { - async function fetchBrowserBookmarks() { - const bookmarks = await getBrowserBookmarks({ includeFolders: true }) - - setFetchedBookmarks(bookmarks) - } - if (browserBookmarksEnabled) { - fetchBrowserBookmarks() - } - }, [browserBookmarksEnabled]) - - useEffect(() => { - const isOtherFolderTitle = (title?: string) => { - if (!title) return false - const t = title.toLowerCase() - return t.includes('other') && t.includes('bookmark') - } - - let candidates: FetchedBrowserBookmark[] = [] - if (currentFolderId) { - candidates = fetchedBookmarks.filter( - (b) => (b.parentId ?? null) === currentFolderId - ) - } else { - const otherFolder = fetchedBookmarks.find( - (b) => b.type === 'FOLDER' && isOtherFolderTitle(b.title) - ) - - const rootEntries = fetchedBookmarks.filter( - (b) => (b.parentId ?? null) === null && b.id !== otherFolder?.id - ) +
- const otherChildren = otherFolder - ? fetchedBookmarks.filter((b) => b.parentId === otherFolder.id) - : [] - - candidates = [...rootEntries, ...otherChildren] - } - - const visible = candidates.map((b) => ({ - id: b.id, - title: b.title, - url: b.url, - icon: - b.type === 'FOLDER' ? ( - - ) : ( - getFaviconFromUrl(b.url || '') - ), - isFolder: b.type === 'FOLDER', - })) - - setBrowserBookmarks(visible) - }, [fetchedBookmarks, currentFolderId]) +
+ +
+
+ +
+
+
+
- useEffect(() => { - if (data?.recommendedSites?.length) { - setToStorage('recommended_sites', data.recommendedSites) - } - }, [data]) +
- return ( -
- + {data?.recommendedSites?.map((item) => ( +
+ +
window.open(item.url || '', '_blank')} + > + {item.name} +
+
+
+ ))} +
+
+ + setIsOpen(false)} + coords={popoverCoords} /> - {browserBookmarksEnabled && ( - , - isFolder: false, - } as BookmarkItem, - ...browserBookmarks, - ] - : browserBookmarks - } - spaceBetween={2} - grabCursor={false} - type="browser" - onItemClick={(item) => { - if (item.id === '__up__') { - const current = fetchedBookmarks.find( - (b) => b.id === currentFolderId - ) - setCurrentFolderId(current?.parentId ?? null) - return - } - - if (item.isFolder) { - setCurrentFolderId(item.id || null) - } else if (item.url) { - window.open(item.url, '_blank') - } - }} - /> - )}
) } diff --git a/src/layouts/setting/tabs/account/components/profile-display.tsx b/src/layouts/setting/tabs/account/components/profile-display.tsx index a919006f..a8fbd670 100644 --- a/src/layouts/setting/tabs/account/components/profile-display.tsx +++ b/src/layouts/setting/tabs/account/components/profile-display.tsx @@ -107,27 +107,33 @@ export const ProfileDisplay = ({ profile, onEditToggle }: ProfileDisplayProps) = value={profile?.name} /> - } - label="ایمیل" - value={profile?.email} - isLtr - /> + {profile?.email && ( + } + label="ایمیل" + value={profile?.email} + isLtr + /> + )} } label="شماره موبایل" value={ - + profile?.phone ? ( + profile.phone + ) : ( + + ) } isLtr showBadge diff --git a/src/pages/home.tsx b/src/pages/home.tsx index ab82ccb5..f3b10cf2 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -16,6 +16,8 @@ import { WidgetSettingsModal } from '@/layouts/widgets-settings/widget-settings- import { getRandomWallpaper } from '@/services/hooks/wallpapers/getWallpaperCategories.hook' import { ContentSection } from './home/content-section' import { ExplorerContent } from '@/layouts/explorer/explorer' +import { usePage } from '@/context/page.context' +import { AnimatePresence, motion } from 'framer-motion' const steps: Step[] = [ { @@ -84,7 +86,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') + const { page } = usePage() useEffect(() => { async function displayModalIfNeeded() { @@ -162,21 +164,11 @@ export function HomePage() { } ) - const openExplorerPageEvent = listenEvent('openExplorerPage', () => { - setCurrentView('explore') - }) - - const closeExplorerPageEvent = listenEvent('closeExplorerPage', () => { - setCurrentView('home') - }) - Analytics.pageView('Home', '/') return () => { wallpaperChangedEvent() openWidgetsSettingsEvent() - openExplorerPageEvent() - closeExplorerPageEvent() } }, []) @@ -266,7 +258,21 @@ export function HomePage() { - {currentView === 'home' ? : } + + + {page === 'home' ? : } + + { diff --git a/src/services/hooks/trends/getTrends.ts b/src/services/hooks/trends/getTrends.ts index 2dda326c..5f56b32b 100644 --- a/src/services/hooks/trends/getTrends.ts +++ b/src/services/hooks/trends/getTrends.ts @@ -16,6 +16,7 @@ export interface RecommendedSubSite { export interface RecommendedSite { name: string + title: string url: string | null icon: string priority: number From 1e475f86fec79a016ecc7ddb585f84b55db59374 Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 04:33:34 +0330 Subject: [PATCH 05/17] refactor: remove trending searches component and related functionality --- src/layouts/search/search.tsx | 14 --- .../search/trending/trending-searches.tsx | 79 -------------- .../search/trending/trending_items.tsx | 102 ------------------ 3 files changed, 195 deletions(-) delete mode 100644 src/layouts/search/trending/trending-searches.tsx delete mode 100644 src/layouts/search/trending/trending_items.tsx diff --git a/src/layouts/search/search.tsx b/src/layouts/search/search.tsx index 9f071d11..47f8bfe9 100644 --- a/src/layouts/search/search.tsx +++ b/src/layouts/search/search.tsx @@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from 'react' import { MdOutlineClear } from 'react-icons/md' import Analytics from '@/analytics' import { BrowserBookmark } from './browser-bookmark/browser-bookmark' -import { TrendingSearches } from './trending/trending-searches' import { VoiceSearchButton } from './voice/VoiceSearchButton' import { FcGoogle } from 'react-icons/fc' @@ -40,13 +39,6 @@ export function SearchLayout() { } } - const handleSelectTrend = (trend: string) => { - setIsInputFocused(false) - // Optional: auto-submit the search - browser.search.query({ text: trend }) - Analytics.event('search_trend_selected') - } - useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -121,12 +113,6 @@ export function SearchLayout() { -
- -
) } diff --git a/src/layouts/search/trending/trending-searches.tsx b/src/layouts/search/trending/trending-searches.tsx deleted file mode 100644 index fe7a1e35..00000000 --- a/src/layouts/search/trending/trending-searches.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { AnimatePresence, domAnimation, LazyMotion, m } from 'framer-motion' -import { useEffect, useState } from 'react' -import { FaChartLine } from 'react-icons/fa6' -import { getFromStorage, setToStorage } from '@/common/storage' -import { SectionPanel } from '@/components/section-panel' -import { type TrendItem, useGetTrends } from '@/services/hooks/trends/getTrends' -import { TrendingItems } from './trending_items' - -interface TrendingSearchesProps { - visible: boolean - onSelectTrend: (trend: string) => void - onSelectSite?: (url: string) => void -} - -export const TrendingSearches = ({ visible, onSelectTrend }: TrendingSearchesProps) => { - const [trends, setTrends] = useState([]) - const [isCached, setIsCached] = useState(false) - - const { data, isError, isLoading } = useGetTrends({ - enabled: visible, - }) - - useEffect(() => { - if (data) { - if (data.trends?.length) { - setTrends(data.trends) - setIsCached(false) - setToStorage('search_trends', data.trends) - } - } - - if (isError) { - const fetchDataFromStorage = async () => { - const storedTrends = await getFromStorage('search_trends') - if (storedTrends?.length) { - setTrends(storedTrends) - setIsCached(true) - } - } - - fetchDataFromStorage() - } - }, [data, isError]) - - if (!visible) return null - - return ( - - - -
- } - size="xs" - > - - -
-
-
-
- ) -} diff --git a/src/layouts/search/trending/trending_items.tsx b/src/layouts/search/trending/trending_items.tsx deleted file mode 100644 index 85d023c6..00000000 --- a/src/layouts/search/trending/trending_items.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { OfflineIndicator } from '@/components/offline-indicator' -import type { TrendItem } from '@/services/hooks/trends/getTrends' -import { LazyMotion, domAnimation, m } from 'framer-motion' - -interface TrendingItemsProps { - trends: TrendItem[] - isLoading: boolean - isCached: boolean - onTrendClick: (trend: string) => void -} - -export const TrendingItems = ({ - trends, - isLoading, - isCached, - onTrendClick, -}: TrendingItemsProps) => { - if (trends.length === 0 && !isLoading) { - return null - } - - return ( -
- {isCached && ( - - )} - - -
- {isLoading - ? [...Array(6)].map((_, index) => ( - - )) - : trends - .slice(0, 6) - .map((trend, index) => ( - onTrendClick(trend.title)} - /> - ))} -
-
-
- ) -} - -interface TrendItemProps { - index: number - trend?: TrendItem - isLoading?: boolean - onClick?: () => void -} - -export const TrendItemComponent = ({ - index, - trend, - isLoading = false, - onClick, -}: TrendItemProps) => { - if (isLoading) { - return ( - -
-
-
- ) - } - - return ( - - {index + 1} -

{trend?.title}

-
- ) -} From 2e9dcb143dfdc1f2118faf5501d4d9405332e656 Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 04:33:42 +0330 Subject: [PATCH 06/17] refactor: update layout styles in ContentSection for improved responsiveness --- src/pages/home.tsx | 2 +- src/pages/home/content-section.tsx | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/pages/home.tsx b/src/pages/home.tsx index f3b10cf2..9f0b16f1 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -268,7 +268,7 @@ export function HomePage() { duration: 0.15, ease: 'linear', }} - className="w-full h-full" + className="flex w-full h-full" > {page === 'home' ? : } diff --git a/src/pages/home/content-section.tsx b/src/pages/home/content-section.tsx index d0826089..b5d7d4f9 100644 --- a/src/pages/home/content-section.tsx +++ b/src/pages/home/content-section.tsx @@ -11,7 +11,6 @@ import { CSS } from '@dnd-kit/utilities' import Analytics from '@/analytics' import { useAppearanceSetting } from '@/context/appearance.context' import { DateProvider } from '@/context/date.context' -import { TodoProvider } from '@/context/todo.context' import { useWidgetVisibility, type WidgetItem } from '@/context/widget-visibility.context' import { BookmarksList } from '@/layouts/bookmark/bookmarks' import { SearchLayout } from '@/layouts/search/search' @@ -122,20 +121,18 @@ export function ContentSection() { className={`flex flex-col items-center overflow-y-auto scrollbar-none ${layoutPositions[contentAlignment]} flex-1 w-full gap-2 px-1 md:px-4 py-2`} >
-
+
-
+
-
+
From 4c99ab217f985abaf5b0a5d56e00a679252c51e9 Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 05:00:29 +0330 Subject: [PATCH 07/17] improve tabs --- src/components/tab-navigation.tsx | 73 +++++++++++++++++++ src/layouts/bookmark/components/shared.tsx | 72 +++++++----------- .../comboWidget/combo-widget.layout.tsx | 33 +++------ .../widgets/notes/components/note-item.tsx | 6 +- src/layouts/widgets/notes/notes.layout.tsx | 22 +++--- src/layouts/widgets/todos/todos.tsx | 22 +++--- .../tools/components/tab-navigation.tsx | 35 --------- src/layouts/widgets/tools/tools.layout.tsx | 12 ++- 8 files changed, 145 insertions(+), 130 deletions(-) create mode 100644 src/components/tab-navigation.tsx delete mode 100644 src/layouts/widgets/tools/components/tab-navigation.tsx diff --git a/src/components/tab-navigation.tsx b/src/components/tab-navigation.tsx new file mode 100644 index 00000000..5c42d33b --- /dev/null +++ b/src/components/tab-navigation.tsx @@ -0,0 +1,73 @@ +import type React from 'react' + +interface TabItem { + id: T + label: string + icon?: React.ReactNode +} + +interface TabNavigationProps { + tabs: TabItem[] + activeTab: T | null + onTabClick: (tab: T) => void + size?: 'small' | 'medium' | 'large' + className?: string +} + +export const TabNavigation = ({ + tabs, + activeTab, + onTabClick, + size = 'medium', + className = '', +}: TabNavigationProps) => { + const sizeClasses = { + small: 'py-1 px-2 text-[10px]', + medium: 'py-2 px-1 text-xs', + large: 'py-3 px-2 text-sm', + } + + return ( +
+ {tabs.map((tab) => { + const isActive = activeTab === tab.id + + return ( + + ) + })} +
+ ) +} diff --git a/src/layouts/bookmark/components/shared.tsx b/src/layouts/bookmark/components/shared.tsx index 3969175e..0282ade2 100644 --- a/src/layouts/bookmark/components/shared.tsx +++ b/src/layouts/bookmark/components/shared.tsx @@ -4,6 +4,8 @@ import { FiChevronUp } from 'react-icons/fi' import { LuX } from 'react-icons/lu' import type { BookmarkType } from '../types/bookmark.types' import { showToast } from '@/common/toast' +import { HiOutlineBookmark, HiOutlineFolder } from 'react-icons/hi2' +import { TabNavigation } from '@/components/tab-navigation' export type IconSourceType = 'auto' | 'upload' | 'url' @@ -15,29 +17,16 @@ export function IconSourceSelector({ setIconSource: (source: IconSourceType) => void theme?: string }) { - const getButtonStyle = (isActive: boolean) => { - if (isActive) { - return 'bg-primary text-white' - } - - return 'text-content' - } - return ( -
-
setIconSource('auto')} - className={`px-3 py-1 cursor-pointer rounded-xl transition-all duration-300 ${getButtonStyle(iconSource === 'auto')}`} - > - آیکون خودکار -
-
setIconSource('upload')} - className={`px-3 py-1 cursor-pointer rounded-xl transition-all duration-300 ${getButtonStyle(iconSource === 'upload')}`} - > - آپلود آیکون -
-
+ + size="small" + tabs={[ + { id: 'auto', label: 'آیکون خودکار' }, + { id: 'upload', label: 'آپلود آیکون' }, + ]} + activeTab={iconSource} + onTabClick={(tab) => setIconSource(tab)} + /> ) } @@ -47,33 +36,22 @@ export function TypeSelector({ }: { type: BookmarkType setType: (type: BookmarkType) => void - theme?: string }) { return ( - <> - - - + + className="w-full!" + size="small" + tabs={[ + { + id: 'BOOKMARK', + label: 'بوکمارک', + icon: , + }, + { id: 'FOLDER', label: 'پوشه', icon: }, + ]} + activeTab={type} + onTabClick={(tab) => setType(tab)} + /> ) } diff --git a/src/layouts/widgets/comboWidget/combo-widget.layout.tsx b/src/layouts/widgets/comboWidget/combo-widget.layout.tsx index 81b366ea..3232b14f 100644 --- a/src/layouts/widgets/comboWidget/combo-widget.layout.tsx +++ b/src/layouts/widgets/comboWidget/combo-widget.layout.tsx @@ -8,6 +8,7 @@ import { WidgetTabKeys } from '@/layouts/widgets-settings/constant/tab-keys' import { NewsLayout } from '../news/news.layout' import { WidgetContainer } from '../widget-container' import { WigiArzLayout } from '../wigiArz/wigi_arz.layout' +import { TabNavigation } from '@/components/tab-navigation' export type ComboTabType = 'news' | 'currency' @@ -48,28 +49,16 @@ export function ComboWidget() { return (
-
-
onTabClick('currency')} - className={`cursor-pointer text-xs w-full text-center rounded-xl py-0.5 px-1 ${ - activeTab === 'currency' - ? 'bg-primary text-gray-200 ' - : 'hover:bg-primary/10' - }`} - > - ارزها -
-
onTabClick('news')} - className={`cursor-pointer text-xs w-full text-center rounded-xl py-0.5 px-1 ${ - activeTab === 'news' - ? 'bg-primary text-gray-200' - : 'hover:bg-primary/10' - }`} - > - اخبار -
-
+
-
-

{note.body}

+
+

+ {note.body} +

) diff --git a/src/layouts/widgets/notes/notes.layout.tsx b/src/layouts/widgets/notes/notes.layout.tsx index 954dbbd7..2c91cdbd 100644 --- a/src/layouts/widgets/notes/notes.layout.tsx +++ b/src/layouts/widgets/notes/notes.layout.tsx @@ -6,6 +6,7 @@ import { WidgetContainer } from '../widget-container' import { NoteEditor } from './components/note-editor' import { NoteNavigation } from './components/note-navigation' import { NoteItem } from './components/note-item' +import { TabNavigation } from '@/components/tab-navigation' function NotesContent() { const { notes, activeNoteId } = useNotes() @@ -70,17 +71,16 @@ export function NotesLayout({ onChangeTab }: Prop) {
-
-
onChangeTab()} - className="cursor-pointer hover:bg-primary/10 rounded-xl py-0.5 px-2" - > - وظایف -
-
- یادداشت -
-
+
diff --git a/src/layouts/widgets/todos/todos.tsx b/src/layouts/widgets/todos/todos.tsx index d972a045..1cf57c3f 100644 --- a/src/layouts/widgets/todos/todos.tsx +++ b/src/layouts/widgets/todos/todos.tsx @@ -30,6 +30,7 @@ import Analytics from '@/analytics' import { AuthRequiredModal } from '@/components/auth/AuthRequiredModal' import { IconLoading } from '@/components/loading/icon-loading' import { parseTodoDate } from './tools/parse-date' +import { TabNavigation } from '@/components/tab-navigation' const viewModeOptions = [ { value: TodoViewType.Day, label: 'لیست امروز' }, @@ -136,17 +137,16 @@ export function TodosLayout({ onChangeTab }: Prop) {
-
-
- وظایف -
-
onChangeTab()} - > - یادداشت -
-
+
{isPending ? : null} diff --git a/src/layouts/widgets/tools/components/tab-navigation.tsx b/src/layouts/widgets/tools/components/tab-navigation.tsx deleted file mode 100644 index 1cc5f33b..00000000 --- a/src/layouts/widgets/tools/components/tab-navigation.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type React from 'react' -import type { ToolsTabType } from '../tools.layout' - -interface TabNavigationProps { - activeTab: ToolsTabType | null - onTabClick: (tab: ToolsTabType) => void -} - -const tabs = [ - { id: 'pomodoro' as ToolsTabType, label: 'پومودورو' }, - { id: 'religious-time' as ToolsTabType, label: 'اوقات شرعی' }, - { id: 'currency-converter' as ToolsTabType, label: 'مبدل قیمت' }, -] -export const TabNavigation: React.FC = ({ - activeTab, - onTabClick, -}) => { - return ( -
- {tabs.map((tab) => ( -
onTabClick(tab.id)} - className={`flex-1 cursor-pointer text-center rounded-xl py-1 px-3 transition-all duration-200 ${ - activeTab === tab.id - ? 'bg-primary text-white' - : 'text-content hover:bg-base-300' - }`} - > - {tab.label} -
- ))} -
- ) -} diff --git a/src/layouts/widgets/tools/tools.layout.tsx b/src/layouts/widgets/tools/tools.layout.tsx index e559add5..891e6cfb 100644 --- a/src/layouts/widgets/tools/tools.layout.tsx +++ b/src/layouts/widgets/tools/tools.layout.tsx @@ -4,7 +4,13 @@ import Analytics from '@/analytics' import { getFromStorage, setToStorage } from '@/common/storage' import { useDate } from '@/context/date.context' import { WidgetContainer } from '../widget-container' -import { TabNavigation } from './components/tab-navigation' +import { TabNavigation } from '@/components/tab-navigation' +// import { TabNavigation } from './components/tab-navigation' +const tabs = [ + { id: 'pomodoro' as ToolsTabType, label: 'پومودورو' }, + { id: 'religious-time' as ToolsTabType, label: 'اوقات شرعی' }, + { id: 'currency-converter' as ToolsTabType, label: 'مبدل قیمت' }, +] const ReligiousTime = React.lazy(() => import('./religious/religious-time').then((module) => ({ @@ -60,7 +66,9 @@ export const ToolsLayout: React.FC = () => {
{})} + onTabClick={onTabClick} + tabs={tabs} + size="small" />
From e6142c09a33635df59f0349ebc612af9680f668a Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 05:00:38 +0330 Subject: [PATCH 08/17] remove description --- .../manage-widgets/manage-widgets.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/layouts/widgets-settings/manage-widgets/manage-widgets.tsx b/src/layouts/widgets-settings/manage-widgets/manage-widgets.tsx index e96cc593..cb563b5e 100644 --- a/src/layouts/widgets-settings/manage-widgets/manage-widgets.tsx +++ b/src/layouts/widgets-settings/manage-widgets/manage-widgets.tsx @@ -37,20 +37,6 @@ export function ManageWidgets() { ))}
- -
- 💡 -
-

- چگونه ترتیب ویجت‌ها را تغییر دهم؟ -

-

- در صفحه، روی بالای هر ویجت کلیک کرده و آن را بکشید تا ترتیب - آن‌ها را تغییر دهید. تغییرات به‌صورت خودکار ذخیره می‌شوند. -

-
-
-
) } From 4cf86e22ad13854956967f1254076619258a4410 Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 07:00:14 +0330 Subject: [PATCH 09/17] improve ui --- src/common/constant/priority_options.ts | 2 +- src/components/tab-navigation.tsx | 2 +- .../comboWidget/combo-widget.layout.tsx | 13 +- .../widgets/news/components/news-item.tsx | 2 +- .../widgets/notes/components/note-item.tsx | 31 +-- src/layouts/widgets/notes/notes.layout.tsx | 37 ++-- src/layouts/widgets/todos/todo-stats.tsx | 2 +- src/layouts/widgets/todos/todo.item.tsx | 186 +++++++----------- src/layouts/widgets/todos/todos.tsx | 15 +- .../widgets/tools/pomodoro/pomodoro-timer.tsx | 2 +- src/layouts/widgets/tools/tools.layout.tsx | 21 +- .../widgets/wigiPad/info-panel/info-panel.tsx | 67 ++----- .../wigiPad/info-panel/infoWeather.tsx | 122 ++++++++++++ wxt.config.ts | 2 +- 14 files changed, 305 insertions(+), 199 deletions(-) create mode 100644 src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx diff --git a/src/common/constant/priority_options.ts b/src/common/constant/priority_options.ts index 0b3c50fe..aa46f05e 100644 --- a/src/common/constant/priority_options.ts +++ b/src/common/constant/priority_options.ts @@ -13,7 +13,7 @@ export const PRIORITY_OPTIONS = [ }, { value: 'high', - ariaLabel: 'اولویت زیاد', + ariaLabel: 'اولویت مهم', bgColor: 'bg-red-500', hoverBgColor: 'hover:bg-red-500', }, diff --git a/src/components/tab-navigation.tsx b/src/components/tab-navigation.tsx index 5c42d33b..d323fec5 100644 --- a/src/components/tab-navigation.tsx +++ b/src/components/tab-navigation.tsx @@ -40,7 +40,7 @@ export const TabNavigation = ({ type="button" onClick={() => onTabClick(tab.id)} className={` - flex-1 flex items-center justify-center gap-2 + flex-1 flex items-center justify-center gap-1 cursor-pointer rounded-xl transition-all duration-200 active:scale-95 z-10 diff --git a/src/layouts/widgets/comboWidget/combo-widget.layout.tsx b/src/layouts/widgets/comboWidget/combo-widget.layout.tsx index 3232b14f..81e25ef4 100644 --- a/src/layouts/widgets/comboWidget/combo-widget.layout.tsx +++ b/src/layouts/widgets/comboWidget/combo-widget.layout.tsx @@ -9,6 +9,7 @@ import { NewsLayout } from '../news/news.layout' import { WidgetContainer } from '../widget-container' import { WigiArzLayout } from '../wigiArz/wigi_arz.layout' import { TabNavigation } from '@/components/tab-navigation' +import { HiCurrencyBangladeshi, HiOutlineNewspaper } from 'react-icons/hi2' export type ComboTabType = 'news' | 'currency' @@ -53,8 +54,16 @@ export function ComboWidget() { activeTab={activeTab} onTabClick={onTabClick} tabs={[ - { id: 'currency', label: 'ارزها' }, - { id: 'news', label: 'اخبار' }, + { + id: 'currency', + label: 'ارزها', + icon: , + }, + { + id: 'news', + label: 'اخبار', + icon: , + }, ]} size="small" className="w-32" diff --git a/src/layouts/widgets/news/components/news-item.tsx b/src/layouts/widgets/news/components/news-item.tsx index 671da8d4..b60eb8df 100644 --- a/src/layouts/widgets/news/components/news-item.tsx +++ b/src/layouts/widgets/news/components/news-item.tsx @@ -40,7 +40,7 @@ export const NewsItem = ({ return (
{image_url && ( diff --git a/src/layouts/widgets/notes/components/note-item.tsx b/src/layouts/widgets/notes/components/note-item.tsx index 82963bf9..70f7154d 100644 --- a/src/layouts/widgets/notes/components/note-item.tsx +++ b/src/layouts/widgets/notes/components/note-item.tsx @@ -1,6 +1,7 @@ import { PRIORITY_OPTIONS } from '@/common/constant/priority_options' import type { FetchedNote } from '@/services/hooks/note/note.interface' import moment from 'jalali-moment' +import { FiCalendar } from 'react-icons/fi' interface Prop { note: FetchedNote @@ -8,25 +9,31 @@ interface Prop { } export function NoteItem({ note, handleNoteClick }: Prop) { const p = PRIORITY_OPTIONS.find((p) => p.value === note.priority) + return (
handleNoteClick(note.id)} > -
-
-
- {note.title || 'بدون عنوان'} +
+
+
+

+ {note.title || 'بدون عنوان'} +

-
- {moment(note.createdAt).locale('fa').format('jD jMMM YY')} +
+ + + {moment(note.createdAt).locale('fa').format('jD jMMM')} +
-
-

+ +

+

{note.body}

diff --git a/src/layouts/widgets/notes/notes.layout.tsx b/src/layouts/widgets/notes/notes.layout.tsx index 2c91cdbd..ebf8d73d 100644 --- a/src/layouts/widgets/notes/notes.layout.tsx +++ b/src/layouts/widgets/notes/notes.layout.tsx @@ -7,6 +7,7 @@ import { NoteEditor } from './components/note-editor' import { NoteNavigation } from './components/note-navigation' import { NoteItem } from './components/note-item' import { TabNavigation } from '@/components/tab-navigation' +import { HiDocumentText, HiOutlineCheckCircle } from 'react-icons/hi2' function NotesContent() { const { notes, activeNoteId } = useNotes() @@ -69,23 +70,29 @@ export function NotesLayout({ onChangeTab }: Prop) { return ( -
-
- +
+ , + }, + { + id: 'notes', + label: 'یادداشت', + icon: , + }, + ]} + size="small" + className="w-fit" + /> - -
- +
+ ) diff --git a/src/layouts/widgets/todos/todo-stats.tsx b/src/layouts/widgets/todos/todo-stats.tsx index 3114ac34..fdf49f95 100644 --- a/src/layouts/widgets/todos/todo-stats.tsx +++ b/src/layouts/widgets/todos/todo-stats.tsx @@ -82,7 +82,7 @@ export function TodoStats() { }} >
- زیاد + مهم
diff --git a/src/layouts/widgets/todos/todo.item.tsx b/src/layouts/widgets/todos/todo.item.tsx index 9e9f1245..eba39835 100644 --- a/src/layouts/widgets/todos/todo.item.tsx +++ b/src/layouts/widgets/todos/todo.item.tsx @@ -1,6 +1,6 @@ import type React from 'react' import { useState } from 'react' -import { FiChevronDown, FiTrash2, FiEdit3 } from 'react-icons/fi' +import { FiChevronDown, FiTrash2, FiEdit3, FiClock, FiTag } from 'react-icons/fi' import { MdDragIndicator } from 'react-icons/md' import CustomCheckbox from '@/components/checkbox' import { useTodoStore, type TodoPriority } from '@/context/todo.context' @@ -28,7 +28,7 @@ interface Prop { const translatedPriority = { low: 'کم', medium: 'متوسط', - high: 'زیاد', + high: 'مهم', } export function TodoItem({ @@ -47,47 +47,28 @@ export function TodoItem({ const [isSyncing, setIsSyncing] = useState(todo.id.startsWith('temp-') || false) const isTemp = todo.id.startsWith('temp-') - const handleDelete = (e: React.MouseEvent) => { - if (isTemp) { - return showToast( - 'این وظیفه هنوز همگام‌سازی نشده است و نمی‌توان آن را حذف کرد.', - 'error' - ) - } + const handleDelete = (e: React.MouseEvent) => { + if (isTemp) return showToast('این وظیفه هنوز همگام‌سازی نشده است.', 'error') e.stopPropagation() if (isPending) return - if (!isAuthenticated) return showToast('برای حذف وظیفه باید وارد شوید', 'error') - + if (!isAuthenticated) return showToast('برای حذف باید وارد شوید', 'error') setShowConfirmation(true) } const handleEdit = (e: React.MouseEvent) => { - if (isTemp) { - return showToast( - 'این وظیفه هنوز همگام‌سازی نشده است و نمی‌توان آن را ویرایش کرد.', - 'error' - ) - } - + if (isTemp) return showToast('این وظیفه هنوز همگام‌سازی نشده است.', 'error') e.stopPropagation() - if (!isAuthenticated) - return showToast('برای ویرایش وظیفه باید وارد شوید', 'error') + if (!isAuthenticated) return showToast('برای ویرایش باید وارد شوید', 'error') setShowEditModal(true) } const onConfirmDelete = async () => { if (isPending || isSyncing) return - const onlineId = todo.onlineId || todo.id - if (validate(onlineId)) { - return showToast( - 'این وظیفه هنوز همگام‌سازی نشده است و نمی‌توان آن را حذف کرد.', - 'error' - ) - } + if (validate(onlineId)) return showToast('خطا در شناسه وظیفه', 'error') - const [err, _] = await safeAwait(mutateAsync()) + const [err] = await safeAwait(mutateAsync()) setShowConfirmation(false) if (err) { showToast(translateError(err) as any, 'error') @@ -110,6 +91,7 @@ export function TodoItem({ if (!isAuthenticated) { return showToast('برای تغییر وضعیت وظیفه باید وارد شوید', 'error') } + setIsSyncing(true) try { await toggleTodo(todo.id) @@ -118,121 +100,117 @@ export function TodoItem({ } } - const handleExpand = (e: React.MouseEvent) => { - e.stopPropagation() - setExpanded(!expanded) - } - return (
-
-
+
+
e.stopPropagation()} + className="cursor-grab p-0.5 text-muted hover:text-base-content active:cursor-grabbing" > - +
-
- onToggleClick(e)} - /> -
+
setExpanded(!expanded)} > - {todo.text} +

+ {todo.text} +

- {/* Actions */} -
+
{isSyncing ? ( - + ) : ( - <> +
- +
)} -
{expanded && ( -
-

{todo.text}

+
+

+ {todo.text} +

-
+
{todo.category && ( - + + {todo.category} )} + {translatedPriority[todo.priority as TodoPriority]} - - {parseTodoDate(todo.date) - .locale('fa') - .format('ddd، jD jMMMM')} + + + + {parseTodoDate(todo.date).locale('fa').format('jD jMMMM')}
- {/* Notes */} - {todo.notes && } + {todo.notes && ( +
+ +
+ )}
)} + {showConfirmation && ( (isPending ? null : setShowConfirmation(false))} - onConfirm={() => onConfirmDelete()} + onClose={() => setShowConfirmation(false)} + onConfirm={onConfirmDelete} confirmText={isPending ? : 'حذف'} - message="آیا از حذف این وظیفه مطمئن هستید؟" + message="آیا از حذف مطمئن هستید؟" variant="danger" /> )} @@ -246,10 +224,8 @@ export function TodoItem({
) } -interface NoteIsLinkProps { - note: string -} -function NoteLinkRenderer({ note }: NoteIsLinkProps): React.JSX.Element | null { + +function NoteLinkRenderer({ note }: { note: string }) { const urlRegex = /(https?:\/\/[^\s]+)/gi const urls = note.match(urlRegex) if (urls) { @@ -258,14 +234,13 @@ function NoteLinkRenderer({ note }: NoteIsLinkProps): React.JSX.Element | null { href={urls[0]} target="_blank" rel="noopener noreferrer" - className="text-blue-500 underline break-all" + className="block text-blue-500 underline break-all" > {urls[0]} ) } - - return

{note}

+ return

{note}

} const getBorderStyle = (priority: string) => { @@ -294,18 +269,7 @@ const getCheckedCheckboxStyle = (priority: string) => { } } -const getUnCheckedCheckboxStyle = (priority: string) => { - switch (priority) { - case 'high': - return '!border-error' - case 'medium': - return '!border-warning' - case 'low': - return '!border-success' - default: - return '!border-primary' - } -} +const getUnCheckedCheckboxStyle = (priority: string) => getBorderStyle(priority) const getPriorityColor = (priority: string) => { switch (priority) { @@ -316,6 +280,6 @@ const getPriorityColor = (priority: string) => { case 'low': return 'bg-success/10 text-success' default: - return 'bg-primary text-primary-content' + return 'bg-primary/10 text-primary' } } diff --git a/src/layouts/widgets/todos/todos.tsx b/src/layouts/widgets/todos/todos.tsx index 1cf57c3f..76edcfe6 100644 --- a/src/layouts/widgets/todos/todos.tsx +++ b/src/layouts/widgets/todos/todos.tsx @@ -31,6 +31,7 @@ import { AuthRequiredModal } from '@/components/auth/AuthRequiredModal' import { IconLoading } from '@/components/loading/icon-loading' import { parseTodoDate } from './tools/parse-date' import { TabNavigation } from '@/components/tab-navigation' +import { HiDocumentText, HiOutlineCheckCircle } from 'react-icons/hi2' const viewModeOptions = [ { value: TodoViewType.Day, label: 'لیست امروز' }, @@ -141,11 +142,19 @@ export function TodosLayout({ onChangeTab }: Prop) { activeTab="todos" onTabClick={onChangeTab} tabs={[ - { id: 'todos', label: 'وظایف' }, - { id: 'notes', label: 'یادداشت' }, + { + id: 'todos', + label: 'وظایف', + icon: , + }, + { + id: 'notes', + label: 'یادداشت', + icon: , + }, ]} size="small" - className="w-32" + className="w-fit" />
diff --git a/src/layouts/widgets/tools/pomodoro/pomodoro-timer.tsx b/src/layouts/widgets/tools/pomodoro/pomodoro-timer.tsx index 988234a3..b698dbc4 100644 --- a/src/layouts/widgets/tools/pomodoro/pomodoro-timer.tsx +++ b/src/layouts/widgets/tools/pomodoro/pomodoro-timer.tsx @@ -284,7 +284,7 @@ export const PomodoroTimer: React.FC = ({ onComplete }) => { } return ( -
+
{/* Mode Selection */}
diff --git a/src/layouts/widgets/tools/tools.layout.tsx b/src/layouts/widgets/tools/tools.layout.tsx index 891e6cfb..5fa7dc00 100644 --- a/src/layouts/widgets/tools/tools.layout.tsx +++ b/src/layouts/widgets/tools/tools.layout.tsx @@ -5,11 +5,23 @@ import { getFromStorage, setToStorage } from '@/common/storage' import { useDate } from '@/context/date.context' import { WidgetContainer } from '../widget-container' import { TabNavigation } from '@/components/tab-navigation' -// import { TabNavigation } from './components/tab-navigation' +import { MdCurrencyExchange, MdMosque, MdTimer } from 'react-icons/md' const tabs = [ - { id: 'pomodoro' as ToolsTabType, label: 'پومودورو' }, - { id: 'religious-time' as ToolsTabType, label: 'اوقات شرعی' }, - { id: 'currency-converter' as ToolsTabType, label: 'مبدل قیمت' }, + { + id: 'pomodoro' as ToolsTabType, + label: 'پومودورو', + icon: , + }, + { + id: 'religious-time' as ToolsTabType, + label: 'اوقات شرعی', + icon: , + }, + { + id: 'currency-converter' as ToolsTabType, + label: 'تبدیل', + icon: , + }, ] const ReligiousTime = React.lazy(() => @@ -69,6 +81,7 @@ export const ToolsLayout: React.FC = () => { onTabClick={onTabClick} tabs={tabs} size="small" + className="w-full " />
diff --git a/src/layouts/widgets/wigiPad/info-panel/info-panel.tsx b/src/layouts/widgets/wigiPad/info-panel/info-panel.tsx index ceceff78..58a01c00 100644 --- a/src/layouts/widgets/wigiPad/info-panel/info-panel.tsx +++ b/src/layouts/widgets/wigiPad/info-panel/info-panel.tsx @@ -3,40 +3,23 @@ import { NotificationItem } from './components/ann-item' import { useInfoPanelData } from './hooks/useInfoPanelData' import { BirthdayTab } from './tabs/birthday/birthday-tab' import { BsFillCalendar2WeekFill } from 'react-icons/bs' +import { TabNavigation } from '@/components/tab-navigation' +import { InfoWeather } from './infoWeather' +const sections = [ + { id: 'all', label: 'ویجی تب', icon: '📋' }, + { id: 'weather', label: 'آب و هوا', icon: '⛅' }, + { id: 'birthdays', label: 'تولدها', icon: '🎂' }, +] export function InfoPanel() { const [activeSection, setActiveSection] = useState('all') const data = useInfoPanelData() const tabContainerRef = useRef(null) - const sections = [ - { id: 'all', label: 'ویجی تب', icon: '📋' }, - { id: 'birthdays', label: 'تولدها', icon: '🎂' }, - ] - const handleSectionClick = ( - sectionId: string, - event: React.MouseEvent - ) => { - setActiveSection(sectionId) - - const button = event.currentTarget - const container = tabContainerRef.current - - if (button && container) { - const containerRect = container.getBoundingClientRect() - const buttonRect = button.getBoundingClientRect() - - const scrollLeft = - button.offsetLeft - containerRect.width / 2 + buttonRect.width / 2 - - container.scrollTo({ - left: scrollLeft, - behavior: 'smooth', - }) - } - } const renderContent = () => { switch (activeSection) { + case 'weather': + return case 'birthdays': return case 'google-meetings': @@ -62,29 +45,21 @@ export function InfoPanel() { return (
+
+ {renderContent()} +
+
- {sections.map((section) => ( - - ))} -
- -
- {renderContent()} + setActiveSection(tab)} + tabs={sections} + size="small" + className="m-0! py-0.5! border-none! flex-nowrap w-full" + />
) diff --git a/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx b/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx new file mode 100644 index 00000000..7f24c0b5 --- /dev/null +++ b/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx @@ -0,0 +1,122 @@ +import { useGetWeatherByLatLon } from '@/services/hooks/weather/getWeatherByLatLon' +import { useAuth } from '@/context/auth.context' +import { TbWind } from 'react-icons/tb' +import { WiHumidity } from 'react-icons/wi' +import { unitsFlag } from '../../weather/unitSymbols' + +export function InfoWeather() { + const { user } = useAuth() + + const { data: weather } = useGetWeatherByLatLon({ + units: 'metric', + lat: user?.city?.id ? undefined : 35.696111, + lon: user?.city?.id ? undefined : 51.423056, + enabled: true, + refetchInterval: 0, + }) + + if (!weather) return
+ + const hasBanner = !!weather.weather?.statusBanner + + return ( +
+ {hasBanner ? ( +
+
+
+ ) : ( +
+ )} + + {/* Main Content */} +
+
+ + {weather.city?.fa} + +
+ + {Math.round(weather.weather?.temperature?.temp || 0)} + + + {unitsFlag['metric']} + +
+
+ + {weather.weather?.icon?.url && ( + w + )} +
+ +
+
+ + + {Math.round(weather.weather?.temperature?.wind_speed || 0)} + m/s + +
+ +
+ + + {weather.weather?.temperature?.humidity}% + +
+ + + {weather.weather?.description?.text} + +
+
+ ) +} diff --git a/wxt.config.ts b/wxt.config.ts index 6e0f05cd..29586719 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -48,7 +48,7 @@ export default defineConfig({ '@wxt-dev/module-react', ], manifest: { - version: '1.0.69', + version: '1.0.71', name: 'Widgetify', description: 'Transform your new tab into a smart dashboard with Widgetify! Get currency rates, crypto prices, weather & more.', From 0c94873efca7a2df895ace44cae3e2575e35581f Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 07:08:19 +0330 Subject: [PATCH 10/17] improve --- .../browser-bookmark/browser-bookmark.tsx | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/layouts/search/browser-bookmark/browser-bookmark.tsx b/src/layouts/search/browser-bookmark/browser-bookmark.tsx index 136af034..9a919d60 100644 --- a/src/layouts/search/browser-bookmark/browser-bookmark.tsx +++ b/src/layouts/search/browser-bookmark/browser-bookmark.tsx @@ -1,14 +1,12 @@ -import { useState, useRef, useEffect } from 'react' +import { useState, useRef, useEffect, useCallback } from 'react' import { useGetTrends } from '@/services/hooks/trends/getTrends' -import { setToStorage } from '@/common/storage' import { getFaviconFromUrl } from '@/common/utils/icon' import Tooltip from '@/components/toolTip' import { HiRectangleGroup } from 'react-icons/hi2' import { MdFolderSpecial } from 'react-icons/md' import { BookmarkPopover } from './bookmark-popover' -import Analytics from '@/analytics' -import { callEvent } from '@/common/utils/call-event' import { usePage } from '@/context/page.context' +import Analytics from '@/analytics' export function BrowserBookmark() { const { data } = useGetTrends({ enabled: true }) @@ -18,24 +16,43 @@ export function BrowserBookmark() { const [popoverCoords, setPopoverCoords] = useState({ top: 0, left: 0 }) const iconRef = useRef(null) - useEffect(() => { - if (data?.recommendedSites?.length) { - setToStorage('recommended_sites', data.recommendedSites) - } - }, [data]) - - const handleTogglePopover = () => { + const updateCoords = useCallback(() => { if (iconRef.current) { const rect = iconRef.current.getBoundingClientRect() + const popoverWidth = 280 + const padding = 10 + + let left = rect.left + if (left + popoverWidth > window.innerWidth) { + left = window.innerWidth - popoverWidth - padding + } + + left = Math.max(padding, left) + setPopoverCoords({ - top: rect.bottom + 10, - left: rect.left, + top: rect.bottom + window.scrollY + 8, + left: left, }) } + }, []) - Analytics.event('browser_bookmark_popover_toggled') + useEffect(() => { + if (isOpen) { + updateCoords() + const handleUpdate = () => requestAnimationFrame(updateCoords) + window.addEventListener('resize', handleUpdate) + window.addEventListener('scroll', handleUpdate) + return () => { + window.removeEventListener('resize', handleUpdate) + window.removeEventListener('scroll', handleUpdate) + } + } + }, [isOpen, updateCoords]) + const handleTogglePopover = () => { + updateCoords() setIsOpen((prev) => !prev) + Analytics.event('browser_bookmark_popover_toggled') } const onClickToExplorer = () => { @@ -44,18 +61,18 @@ export function BrowserBookmark() { } return ( -
-
-
+
+
+
onClickToExplorer()} > -
+
@@ -81,7 +98,7 @@ export function BrowserBookmark() {
-
+
{data?.recommendedSites?.map((item) => ( @@ -108,6 +125,7 @@ export function BrowserBookmark() {
+ {/* پاپ‌اور با مختصات هوشمند */} setIsOpen(false)} From 821ad71030bd47b2268d61e03c63106603b252a1 Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 07:15:54 +0330 Subject: [PATCH 11/17] improve --- src/layouts/widgets/todos/todo.item.tsx | 2 +- .../widgets/wigiPad/info-panel/infoWeather.tsx | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/layouts/widgets/todos/todo.item.tsx b/src/layouts/widgets/todos/todo.item.tsx index eba39835..d42837b6 100644 --- a/src/layouts/widgets/todos/todo.item.tsx +++ b/src/layouts/widgets/todos/todo.item.tsx @@ -102,7 +102,7 @@ export function TodoItem({ return (
diff --git a/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx b/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx index 7f24c0b5..347f3ea5 100644 --- a/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx +++ b/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx @@ -20,25 +20,28 @@ export function InfoWeather() { const hasBanner = !!weather.weather?.statusBanner return ( -
+
{hasBanner ? (
-
-
+ style={{ + backgroundImage: `url(${weather.weather?.statusBanner})`, + maskImage: + 'linear-gradient(-1deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.6) 30%, rgba(0, 0, 0, 0.3) 60%, rgba(0, 0, 0, 0) 85%)', + WebkitMaskImage: + 'linear-gradient(-1deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.6) 30%, rgba(0, 0, 0, 0.3) 60%, rgba(0, 0, 0, 0) 85%)', + }} + >
) : (
)} - {/* Main Content */}
From be1752f6cd57260d50bc24d4ea8730eda712950b Mon Sep 17 00:00:00 2001 From: sajjad isvand Date: Sat, 31 Jan 2026 07:27:02 +0330 Subject: [PATCH 12/17] improve --- .../explorer/components/content-site.tsx | 4 +- src/layouts/explorer/explorer.tsx | 299 ++++++++++-------- 2 files changed, 176 insertions(+), 127 deletions(-) diff --git a/src/layouts/explorer/components/content-site.tsx b/src/layouts/explorer/components/content-site.tsx index 45b7e215..f35cb618 100644 --- a/src/layouts/explorer/components/content-site.tsx +++ b/src/layouts/explorer/components/content-site.tsx @@ -26,14 +26,14 @@ export function RenderContentSite({ link }: SiteProp) { href={getUrl(link.url)} target="_blank" rel="noopener noreferrer" - className={` flex flex-col items-center gap-1 transition-all duration-500 group active:scale-95 ${pos} rounded-2xl`} + className={`flex flex-col items-center gap-1 transition-all duration-500 group active:scale-95 ${pos} rounded-2xl hover:bg-base-300 group-hover:shadow-sm p-0.5`} style={{ gridColumn: col ? `span ${col} / span ${col}` : undefined, gridRow: row ? `span ${row} / span ${row}` : undefined, }} > {link.icon && ( -
+
{badge && ( + {[1, 2, 3, 4, 5, 6].map((i) => ( +
+
+
+
+
+
+ {[1, 2, 3, 4, 5, 6, 7, 8].map((j) => ( +
+
+
+
+ ))} +
+
+ ))} +
+ ) +} + export function ExplorerContent() { const { theme } = useTheme() const { fontFamily } = useAppearanceSetting() - const { data: catalogData } = useGetContents() + const { data: catalogData, isLoading } = useGetContents() const [activeCategory, setActiveCategory] = useState(null) const categoryRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}) const scrollContainerRef = useRef(null) @@ -57,66 +83,84 @@ export function ExplorerContent() { return (
-