diff --git a/src/icons/packs/default.tsx b/src/icons/packs/default.tsx index 87bbdde0..679e224a 100644 --- a/src/icons/packs/default.tsx +++ b/src/icons/packs/default.tsx @@ -3,6 +3,7 @@ import { FiBell, FiClock, FiCoffee, + FiFilter, FiGift, FiInbox, FiKey, @@ -13,7 +14,6 @@ import { FiMaximize, FiMinimize, FiPhone, - FiPlay, FiRefreshCw, FiRotateCcw, FiSave, @@ -58,7 +58,6 @@ import { FaSortAmountDown, FaSpinner, FaTelegramPlane, - FaUpload, FaUserSecret, } from 'react-icons/fa' import { @@ -74,7 +73,6 @@ import { TbNotebook, TbArrowsRightLeft, TbCloudUpload, - TbBookmark, } from 'react-icons/tb' import { MdDragIndicator, @@ -255,4 +253,5 @@ export const defaultIcons = { arrowRightLeft: TbArrowsRightLeft, bookmark: HiBookmark, userSecret: FaUserSecret, + filter: FiFilter, } satisfies IconMap diff --git a/src/icons/types.ts b/src/icons/types.ts index b13ef183..f64e8747 100644 --- a/src/icons/types.ts +++ b/src/icons/types.ts @@ -131,4 +131,5 @@ export type IconName = | 'arrowRightLeft' | 'bookmark' | 'userSecret' + | 'filter' export type IconMap = Record diff --git a/src/layouts/setting/tabs/wallpapers/components/gradient-wallpaper.component.tsx b/src/layouts/setting/tabs/wallpapers/components/gradient-wallpaper.component.tsx deleted file mode 100644 index 47b329ee..00000000 --- a/src/layouts/setting/tabs/wallpapers/components/gradient-wallpaper.component.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useEffect, useState } from 'react' -import type { GradientColors, Wallpaper } from '@/common/wallpaper.interface' -import { SectionPanel } from '@/components/section-panel' - -interface GradientWallpaperProps { - onSelectGradient: (gradient: Wallpaper) => void - selectedGradient?: Wallpaper -} - -const predefinedGradients: { from: string; to: string; name: string }[] = [ - { from: '#ff9a9e', to: '#fad0c4', name: 'صورتی ملایم' }, - { from: '#a1c4fd', to: '#c2e9fb', name: 'آبی آسمانی' }, - { from: '#d4fc79', to: '#96e6a1', name: 'سبز بهاری' }, - { from: '#ffecd2', to: '#fcb69f', name: 'نارنجی پاییزی' }, - { from: '#84fab0', to: '#8fd3f4', name: 'فیروزه‌ای' }, - { from: '#cfd9df', to: '#e2ebf0', name: 'خاکستری روشن' }, - { from: '#a6c0fe', to: '#f68084', name: 'آبی به صورتی' }, - { from: '#fbc2eb', to: '#a6c1ee', name: 'بنفش ملایم' }, -] - -export function GradientWallpaper({ - onSelectGradient, - selectedGradient, -}: GradientWallpaperProps) { - const [direction, setDirection] = useState('to-r') - - useEffect(() => { - if ( - selectedGradient?.type === 'GRADIENT' && - selectedGradient.gradient?.direction - ) { - setDirection(selectedGradient.gradient.direction) - } - }, [selectedGradient]) - - const getTailwindDirectionToCss = (direction: string): string => { - const directionMap: Record = { - 'to-r': 'to right', - 'to-l': 'to left', - 'to-t': 'to top', - 'to-b': 'to bottom', - 'to-tr': 'to top right', - 'to-tl': 'to top left', - 'to-br': 'to bottom right', - 'to-bl': 'to bottom left', - } - return directionMap[direction] || 'to right' - } - - const createGradientId = (from: string, to: string): string => { - return `gradient-${from.replace('#', '')}-${to.replace('#', '')}` - } - - const createGradientWallpaper = ( - from: string, - to: string, - name: string - ): Wallpaper => { - return { - id: createGradientId(from, to), - name: name, - type: 'GRADIENT', - src: '', - previewSrc: '', - gradient: { - from, - to, - direction, - }, - } - } - - const handlePredefinedGradientSelect = (from: string, to: string, name: string) => { - const gradient = createGradientWallpaper(from, to, name) - onSelectGradient(gradient) - } - - const isSelected = (from: string, to: string) => { - if (!selectedGradient) return false - - const gradientId = createGradientId(from, to) - return selectedGradient.id === gradientId - } - - return ( - -
-
- {predefinedGradients.map((gradient, index) => ( -
- handlePredefinedGradientSelect( - gradient.from, - gradient.to, - gradient.name - ) - } - > -
-
- {gradient.name} -
-
- ))} -
-
-
- ) -} diff --git a/src/layouts/setting/tabs/wallpapers/components/retouch-filter.component.tsx b/src/layouts/setting/tabs/wallpapers/components/retouch-filter.component.tsx deleted file mode 100644 index 3ec98ab4..00000000 --- a/src/layouts/setting/tabs/wallpapers/components/retouch-filter.component.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { CheckBoxWithDescription } from '@/components/checkbox-description.component' - -interface RetouchFilterProps { - isEnabled: boolean - onToggle: () => void -} - -export function RetouchFilter({ isEnabled, onToggle }: RetouchFilterProps) { - return ( - - ) -} diff --git a/src/layouts/setting/tabs/wallpapers/components/upload-area.component.tsx b/src/layouts/setting/tabs/wallpapers/components/upload-area.component.tsx index f857605f..d9810d1e 100644 --- a/src/layouts/setting/tabs/wallpapers/components/upload-area.component.tsx +++ b/src/layouts/setting/tabs/wallpapers/components/upload-area.component.tsx @@ -5,6 +5,9 @@ import { useWallpaperUpload } from '../hooks/use-wallpaper-upload' import { MediaPreview } from './media-preview.component' import { Icon } from '@/src/icons' +const MAX_SIZE = 2 +const MAX_FILE_SIZE = MAX_SIZE * 1024 * 1024 + interface UploadAreaProps { customWallpaper: Wallpaper | null onWallpaperChange: (newWallpaper: Wallpaper) => void @@ -12,7 +15,11 @@ interface UploadAreaProps { export function UploadArea({ customWallpaper, onWallpaperChange }: UploadAreaProps) { const fileInputRef = useRef(null) - const { processFile } = useWallpaperUpload({ onWallpaperChange }) + const { processFile } = useWallpaperUpload({ + onWallpaperChange, + max_size: MAX_SIZE, + max_file_size: MAX_FILE_SIZE, + }) const handleFileSelect = () => { if (fileInputRef.current) { @@ -32,23 +39,26 @@ export function UploadArea({ customWallpaper, onWallpaperChange }: UploadAreaPro return (
@@ -93,7 +103,7 @@ export function UploadArea({ customWallpaper, onWallpaperChange }: UploadAreaPro type="file" ref={fileInputRef} className="hidden" - accept="image/*,video/*" + accept="image/*" onChange={handleFileChange} /> diff --git a/src/layouts/setting/tabs/wallpapers/components/wallpaper-header.tsx b/src/layouts/setting/tabs/wallpapers/components/wallpaper-header.tsx new file mode 100644 index 00000000..63c5ef0f --- /dev/null +++ b/src/layouts/setting/tabs/wallpapers/components/wallpaper-header.tsx @@ -0,0 +1,27 @@ +import { UploadArea } from './upload-area.component' +import { useWallpaperContext } from '@/context/wallpaper.context' + +export function WallpaperHeader() { + const { handleCustomWallpaperChange, customWallpaper } = useWallpaperContext() + return ( +
+
+

+ انتخاب از سیستم +

+
+ +
+

+ تصویر زمینه‌ها +

+

+ تصویر زمینه مورد علاقه خود را انتخاب کنید +

+
+
+ ) +} diff --git a/src/layouts/setting/tabs/wallpapers/components/wallpaper-sidebar.tsx b/src/layouts/setting/tabs/wallpapers/components/wallpaper-sidebar.tsx new file mode 100644 index 00000000..c0885b0a --- /dev/null +++ b/src/layouts/setting/tabs/wallpapers/components/wallpaper-sidebar.tsx @@ -0,0 +1,173 @@ +import { useState } from 'react' +import type { Category } from '@/common/wallpaper.interface' +import { Icon } from '@/src/icons' +import { NewBadge } from '@/components/badges/new.badge' +import { TabNavigation } from '@/components/tab-navigation' + +type FilterType = 'all' | 'image' | 'video' +type FilterAccess = 'all' | 'free' | 'coin' + +interface WallpaperSidebarProps { + categories: Category[] + selectedCategoryId: string | null + onSelectCategory: (categoryId: string | null) => void + typeFilter: FilterType + onTypeFilterChange: (type: FilterType) => void + accessFilter: FilterAccess + onAccessFilterChange: (access: FilterAccess) => void + totalCount?: number +} + +export function WallpaperSidebar({ + categories, + selectedCategoryId, + onSelectCategory, + typeFilter, + onTypeFilterChange, + accessFilter, + onAccessFilterChange, + totalCount, +}: WallpaperSidebarProps) { + const [isFilterOpen, setIsFilterOpen] = useState(false) + + return ( + + ) +} diff --git a/src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper-upload.ts b/src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper-upload.ts index f2b45f1e..0d717d4a 100644 --- a/src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper-upload.ts +++ b/src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper-upload.ts @@ -2,26 +2,29 @@ import type { Wallpaper } from '@/common/wallpaper.interface' import Analytics from '../../../../../analytics' import { showToast } from '@/common/toast' -const MAX_SIZE = 3 -const MAX_FILE_SIZE = MAX_SIZE * 1024 * 1024 // 3MB - interface UseWallpaperUploadProps { onWallpaperChange: (wallpaper: Wallpaper) => void + max_size: number + max_file_size: number } -export function useWallpaperUpload({ onWallpaperChange }: UseWallpaperUploadProps) { +export function useWallpaperUpload({ + onWallpaperChange, + max_file_size, + max_size, +}: UseWallpaperUploadProps) { const processFile = (file: File) => { const isImage = file.type.startsWith('image/') - const isVideo = file.type.startsWith('video/') + // const isVideo = file.type.startsWith('video/') - if (!isImage && !isVideo) { - showToast('لطفا یک فایل تصویری یا ویدیویی انتخاب کنید', 'error') + if (!isImage) { + showToast('لطفا یک فایل تصویری انتخاب کنید', 'error') return } - if (file.size > MAX_FILE_SIZE) { + if (file.size > max_file_size) { showToast( - `حجم فایل نباید بیشتر از ${MAX_SIZE} مگابایت باشد. حجم فعلی: ${(file.size / (1024 * 1024)).toFixed(1)} مگابایت`, + `حجم فایل نباید بیشتر از ${max_size} مگابایت باشد. حجم فعلی: ${(file.size / (1024 * 1024)).toFixed(1)} مگابایت`, 'error' ) return diff --git a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/category/category-folder.component.tsx b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/category/category-folder.component.tsx deleted file mode 100644 index bf2e9a42..00000000 --- a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/category/category-folder.component.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { NewBadge } from '@/components/badges/new.badge' -import { Icon } from '@/src/icons' - -interface CategoryFolderProps { - id: string - name: string - previewImages: any[] - hasNewContent: boolean - onSelect: (categoryId: string) => void -} - -export function CategoryFolder({ - id, - name, - previewImages, - onSelect, - hasNewContent, -}: CategoryFolderProps) { - return ( -
onSelect(id)} - className="flex flex-col h-32 px-2 py-1 transition-all border cursor-pointer rounded-2xl bg-content border-content max-h-32 hover:border-primary/20! active:scale-98 relative" - > - {hasNewContent ? : null} - -
- -

{name}

-
- -
- {previewImages?.length > 0 ? ( -
- {previewImages.slice(0, 4).map((image) => ( -
- {image.name} -
- ))} -
- ) : ( -
- -

بدون تصویر

-
- )} -
-
- ) -} diff --git a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/category/category-view.tsx b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/category/category-view.tsx deleted file mode 100644 index fa3d7d81..00000000 --- a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/category/category-view.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { Category } from '@/common/wallpaper.interface' -import { Pagination } from '@/components/pagination' -import { useGetWallpaperCategoriesPaginated } from '@/services/hooks/wallpapers/get-wallpaper-categories.hook' -import { CategoryFolder } from './category-folder.component' - -interface CategoryGridProps { - onCategorySelect: (category: Category) => void -} -const CATEGORIES_PER_PAGE = 9 -export function CategoryView({ onCategorySelect }: CategoryGridProps) { - const [currentPage, setCurrentPage] = useState(1) - - const { - data: categoryResponse, - isLoading, - isFetching, - error, - } = useGetWallpaperCategoriesPaginated({ - page: currentPage, - limit: CATEGORIES_PER_PAGE, - }) - - const goToNextPage = () => { - if (currentPage < (categoryResponse?.totalPages || 1)) { - setCurrentPage(currentPage + 1) - } - } - - const goToPrevPage = () => { - if (currentPage > 1) { - setCurrentPage(currentPage - 1) - } - } - - if (isLoading) { - return ( -
- {Array.from({ length: CATEGORIES_PER_PAGE }).map((_, index) => ( -
-
-
- {[1, 2, 3, 4].map((_, imgIndex) => ( -
- ))} -
-
- ))} -
- ) - } - - if (error) { - return ( -
-
خطا در بارگذاری دسته‌بندی‌ها
-
- ) - } - - return ( -
-
- {categoryResponse?.categories?.map((category) => ( - onCategorySelect(category)} - /> - ))} -
- - -
- ) -} diff --git a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/hover-play-video.tsx b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/hover-play-video.tsx index 616f4d21..cb75e423 100644 --- a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/hover-play-video.tsx +++ b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/hover-play-video.tsx @@ -1,4 +1,5 @@ -import React, { useRef, useState, useEffect } from 'react' +import type React from 'react' +import { useRef, useState, useEffect } from 'react' interface HoverPlayVideoProps { videoSrc: string diff --git a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-item.tsx b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-item.tsx index cec2a8a1..b7e08287 100644 --- a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-item.tsx +++ b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-item.tsx @@ -134,16 +134,16 @@ function WallpaperItemFu({
- {wallpaper.name && ( -
+ {wallpaper.name ? ( +
{wallpaper.name}
- )} + ) : null}
- {wallpaper.coin && ( + {wallpaper.coin ? (
- )} + ) : null}
{wallpaper.extensionUI ? ( @@ -200,9 +200,9 @@ function WallpaperItemFu({
)} -
+
- {!isSelected && !wallpaper.isOwned && wallpaper.coin && ( + {!isSelected && !wallpaper.isOwned && wallpaper.coin ? ( - )} + ) : null} )} diff --git a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-view.tsx b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-view.tsx index ebd78092..d686691b 100644 --- a/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-view.tsx +++ b/src/layouts/setting/tabs/wallpapers/tab/gallery/components/wallpaper-item/wallpaper-view.tsx @@ -1,7 +1,6 @@ -import { useEffect } from 'react' +import { useEffect, useMemo } from 'react' import { preloadImages } from '@/common/utils/preload-images' -import type { Category, Wallpaper } from '@/common/wallpaper.interface' -import { FolderPath } from '@/layouts/bookmark/components/folder-path' +import type { Wallpaper } from '@/common/wallpaper.interface' import { useGetWallpapersInfiniteQuery } from '@/services/hooks/wallpapers/get-wallpaper-categories.hook' import { useWallpaperContext } from '@/context/wallpaper.context' import { useInfiniteScroll } from '@/hooks/use-infinite-scroll' @@ -9,22 +8,24 @@ import { WallpaperItem } from './wallpaper-item' import { usePreviewHandler } from '@/hooks/use-preview-handler' import { MarketItemType } from '@/services/hooks/market/market.interface' import Analytics from '@/analytics' +import { Icon } from '@/src/icons' interface WallpaperViewProps { - selectedCategory: Category | null - onBackToCategories: () => void + selectedCategoryId: string | null + typeFilter: 'all' | 'image' | 'video' + accessFilter: 'all' | 'free' | 'coin' } -const WALLPAPERS_PER_PAGE = 12 +const WALLPAPERS_PER_PAGE = 18 export function WallpaperView({ - selectedCategory, - onBackToCategories, + selectedCategoryId, + typeFilter, + accessFilter, }: WallpaperViewProps) { const { selectedBackground, handleSelectBackground, - syncWithFetchedWallpapers, currentStoredWallpaper, } = useWallpaperContext() @@ -62,12 +63,13 @@ export function WallpaperView({ fetchNextPage, hasNextPage, isFetchingNextPage, + isLoading, } = useGetWallpapersInfiniteQuery( { - categoryId: selectedCategory?.id, + categoryId: selectedCategoryId || undefined, limit: WALLPAPERS_PER_PAGE, }, - !!selectedCategory + true ) const { containerRef, loadMoreRef } = useInfiniteScroll({ @@ -78,8 +80,9 @@ export function WallpaperView({ threshold: 0.1, }) - const allWallpapers = - wallpaperResponse?.pages.flatMap((page) => page.wallpapers) || [] + const allWallpapers = useMemo(() => { + return wallpaperResponse?.pages.flatMap((page) => page.wallpapers) || [] + }, [wallpaperResponse]) useEffect(() => { if (allWallpapers.length) { @@ -92,52 +95,77 @@ export function WallpaperView({ } }, [allWallpapers]) - if (!selectedCategory) return null + const filteredWallpapers = useMemo(() => { + return allWallpapers.filter((wp) => { + if (typeFilter === 'image' && wp.type !== 'IMAGE') return false + if ( + typeFilter === 'video' && + wp.type !== 'VIDEO' && + !wp.src?.endsWith('.gif') && + !wp.previewSrc?.endsWith('.gif') + ) + return false + + if (accessFilter === 'free' && wp.coin && !wp.isOwned) return false + if (accessFilter === 'coin' && (!wp.coin || wp.isOwned)) return false + + return true + }) + }, [allWallpapers, typeFilter, accessFilter]) return (
-
- -
-
-
- {allWallpapers.map((wallpaper) => ( -
- -
- ))} + {isLoading ? ( +
{MakeSkeleton(6)}
+ ) : filteredWallpapers.length === 0 ? ( +
+ +

تصویر زمینه‌ای یافت نشد

+

+ فیلترهای انتخابی را تغییر دهید +

- {hasNextPage && ( -
- {MakeSkeleton(3)} + ) : ( +
+
+ {filteredWallpapers.map((wallpaper) => ( +
+ +
+ ))}
- )} -
+ + {hasNextPage && ( +
+ {MakeSkeleton(3)} +
+ )} + +
+ + نمایش {filteredWallpapers.length.toLocaleString('fa-IR')}{' '} + تصویر زمینه + +
+
+ )}
) } function MakeSkeleton(count: number) { - return [...Array(count)].map((_, catIdx) => ( + return [...Array(count)].map((_, idx) => (
+ key={`loading-${idx}`} + className="w-full aspect-video rounded-xl skeleton bg-base-300 border border-base-content/5" + /> )) } diff --git a/src/layouts/setting/tabs/wallpapers/tab/gallery/gallery.tab.tsx b/src/layouts/setting/tabs/wallpapers/tab/gallery/gallery.tab.tsx index 48a4ef0f..e0a8ea4a 100644 --- a/src/layouts/setting/tabs/wallpapers/tab/gallery/gallery.tab.tsx +++ b/src/layouts/setting/tabs/wallpapers/tab/gallery/gallery.tab.tsx @@ -1,36 +1,39 @@ import { useState } from 'react' -import type { Category } from '@/common/wallpaper.interface' -import { SectionPanel } from '@/components/section-panel' -import { CategoryView } from './components/category/category-view' +import { useGetWallpaperCategories } from '@/services/hooks/wallpapers/get-wallpaper-categories.hook' +import { WallpaperSidebar } from '../../components/wallpaper-sidebar' +import { WallpaperHeader } from '../../components/wallpaper-header' import { WallpaperView } from './components/wallpaper-item/wallpaper-view' -import { UploadArea } from '../../components/upload-area.component' -import { useWallpaperContext } from '@/context/wallpaper.context' export function GalleryTab() { - const [selectedCategory, setSelectedCategory] = useState(null) - const { customWallpaper, handleCustomWallpaperChange } = useWallpaperContext() + const [selectedCategoryId, setSelectedCategoryId] = useState(null) + const [typeFilter, setTypeFilter] = useState<'all' | 'image' | 'video'>('all') + const [accessFilter, setAccessFilter] = useState<'all' | 'free' | 'coin'>('all') + + const { data: categories } = useGetWallpaperCategories() return ( - -
- {!selectedCategory ? ( - - ) : ( - setSelectedCategory(null)} - /> - )} -
+
+ - -
- + + +
+
- - +
+
) } diff --git a/src/layouts/setting/tabs/wallpapers/wallpapers.tsx b/src/layouts/setting/tabs/wallpapers/wallpapers.tsx index 91c13870..9f46e396 100644 --- a/src/layouts/setting/tabs/wallpapers/wallpapers.tsx +++ b/src/layouts/setting/tabs/wallpapers/wallpapers.tsx @@ -2,7 +2,7 @@ import { GalleryTab } from './tab/gallery/gallery.tab' export function WallpaperSetting() { return ( -
+
) diff --git a/src/services/hooks/wallpapers/get-wallpaper-categories.hook.ts b/src/services/hooks/wallpapers/get-wallpaper-categories.hook.ts index aaccf66f..e2c78153 100644 --- a/src/services/hooks/wallpapers/get-wallpaper-categories.hook.ts +++ b/src/services/hooks/wallpapers/get-wallpaper-categories.hook.ts @@ -3,11 +3,14 @@ import type { Category, Wallpaper, WallpaperResponse } from '@/common/wallpaper. import { getMainClient } from '@/services/api' export const useGetWallpaperCategories = () => { - return useQuery({ + return useQuery({ queryKey: ['getWallpaperCategories'], queryFn: async () => getWallpaperCategories(), retry: 0, - initialData: [], + initialData: { + totalPages: 1, + categories: [], + }, }) } @@ -49,9 +52,9 @@ export const useGetWallpaperCategoriesPaginated = ( }) } -async function getWallpaperCategories(): Promise { +async function getWallpaperCategories(): Promise { const client = getMainClient() - const { data } = await client.get('/wallpapers/categories') + const { data } = await client.get('/wallpapers/categories') return data } @@ -95,7 +98,7 @@ export const useGetWallpapers = (q: GetWallpaperQuery, enabled: boolean) => { export const useGetWallpapersInfiniteQuery = (q: GetWallpaperQuery, enabled: boolean) => { return useInfiniteQuery({ - queryKey: ['getWallpapers', q.limit, q.categoryId], + queryKey: ['getWallpapers', q.page, q.categoryId], queryFn: async ({ pageParam }) => getWallpapersByCategoryId({ ...q, page: pageParam as number }), retry: 0,