diff --git a/src/common/utils/call-event.ts b/src/common/utils/call-event.ts index 0575a7f7..016a4909 100644 --- a/src/common/utils/call-event.ts +++ b/src/common/utils/call-event.ts @@ -10,6 +10,7 @@ import type { WigiPadDateSetting } from '@/layouts/widgets/wigiPad/date-display/ import type { WidgetTabKeys } from '@/layouts/widgets-settings/constant/tab-keys' import type { StoredWallpaper } from '../wallpaper.interface' import type { Todo } from '@/services/hooks/todo/todo.interface' +import { FontFamily } from '@/context/appearance.context' export interface EventName { startSync: SyncTarget @@ -43,6 +44,7 @@ export interface EventName { closeAllDropdowns: null openProfile: null openMarketModal: null + font_change: FontFamily } export function callEvent(eventName: K, data?: EventName[K]) { diff --git a/src/context/appearance.context.tsx b/src/context/appearance.context.tsx index 817e9fe5..eccc22be 100644 --- a/src/context/appearance.context.tsx +++ b/src/context/appearance.context.tsx @@ -2,10 +2,19 @@ import type React from 'react' import { createContext, useContext, useEffect, useState } from 'react' import Analytics from '@/analytics' import { getFromStorage, setToStorage } from '@/common/storage' -import { useUpdateExtensionSettings } from '@/services/hooks/extension/updateSetting.hook' +import { useChangeFont } from '@/services/hooks/extension/updateSetting.hook' import { useAuth } from './auth.context' - -export type FontFamily = 'Vazir' | 'Samim' | 'Pofak' | 'rooyin' +import { safeAwait } from '@/services/api' +import { showToast } from '@/common/toast' +import { translateError } from '@/utils/translate-error' + +export enum FontFamily { + Vazir = 'Vazir', + Samim = 'Samim', + Pofak = 'Pofak', + rooyin = 'rooyin', + Arad = 'Arad', +} export interface AppearanceData { contentAlignment: 'center' | 'top' @@ -23,7 +32,7 @@ interface AppearanceContextContextType extends AppearanceData { const DEFAULT_SETTINGS: AppearanceData = { contentAlignment: 'top', - fontFamily: 'Vazir', + fontFamily: FontFamily.Vazir, } export const AppearanceContext = createContext(null) @@ -31,7 +40,7 @@ export const AppearanceContext = createContext(DEFAULT_SETTINGS) const [isInitialized, setIsInitialized] = useState(false) - const { mutateAsync: updateSettings } = useUpdateExtensionSettings() + const { mutateAsync: changeFontAsync } = useChangeFont() const { isAuthenticated, user } = useAuth() useEffect(() => { @@ -85,11 +94,16 @@ export function AppearanceProvider({ children }: { children: React.ReactNode }) } const setFontFamily = async (value: FontFamily) => { + const currentFont = settings.fontFamily updateSetting('fontFamily', value) + Analytics.event(`set_font_${value}`) if (isAuthenticated) { - await updateSettings({ font: value }) + const [err] = await safeAwait(changeFontAsync({ font: value })) + if (err) { + updateSetting('fontFamily', currentFont) + return showToast(translateError(err) as any, 'error') + } } - Analytics.event(`set_font_${value}`) } useEffect(() => { diff --git a/src/context/theme.context.tsx b/src/context/theme.context.tsx index 54f1e5b5..d722b063 100644 --- a/src/context/theme.context.tsx +++ b/src/context/theme.context.tsx @@ -51,9 +51,14 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) { setToStorage('browserTitle', newTitle) }) + const eventForFont = listenEvent('font_change', async (newFont) => { + document.body.style.fontFamily = `"${newFont}", sans-serif` + }) + return () => { event() eventForTitle() + eventForFont() } }, []) diff --git a/src/fonts.css b/src/fonts.css index 94cd5615..e11fd831 100644 --- a/src/fonts.css +++ b/src/fonts.css @@ -115,6 +115,27 @@ font-style: normal; } +@font-face { + font-family: "Arad"; + src: url("https://widgetify-ir.storage.c2.liara.space/fonts/arad/Arad-Light.woff2") format("woff2"); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: "Arad"; + src: url("https://widgetify-ir.storage.c2.liara.space/fonts/arad/Arad-Medium.woff2") format("woff2"); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: "Arad"; + src: url("https://widgetify-ir.storage.c2.liara.space/fonts/arad/Arad-Bold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} + body { -moz-font-feature-settings: "ss01"; -webkit-font-feature-settings: "ss01"; diff --git a/src/layouts/market/other-items/components/market-item-card.tsx b/src/layouts/market/other-items/components/market-item-card.tsx index 2e786ee8..49219d83 100644 --- a/src/layouts/market/other-items/components/market-item-card.tsx +++ b/src/layouts/market/other-items/components/market-item-card.tsx @@ -1,12 +1,12 @@ -import { FiEye, FiShoppingBag, FiShoppingCart } from 'react-icons/fi' +import { FiShoppingCart } from 'react-icons/fi' import { Button } from '@/components/button/button' import { ItemPrice } from '@/components/item-price/item-price' import { getItemTypeEmoji } from '@/components/market/getItemTypeEmoji' -import { renderBrowserTitlePreview } from '@/components/market/title/title-render-preview' -import Tooltip from '@/components/toolTip' import { Theme } from '@/context/theme.context' import type { MarketItem, MarketItemType } from '@/services/hooks/market/market.interface' import { showToast } from '@/common/toast' +import { FontFamily } from '@/context/appearance.context' +import { RenderPreview } from './renderPreview' interface MarketItemCardProps { item: MarketItem @@ -14,7 +14,7 @@ interface MarketItemCardProps { isAuthenticated: boolean isOwned?: boolean } -const SUPPORTED_TYPES: MarketItemType[] = ['BROWSER_TITLE', 'THEME'] +const SUPPORTED_TYPES: MarketItemType[] = ['BROWSER_TITLE', 'THEME', 'FONT'] const getItemTypeLabel = (type: string) => { switch (type) { case 'BROWSER_TITLE': @@ -42,9 +42,11 @@ export function MarketItemCard({ } let needUpgrade = !SUPPORTED_TYPES.includes(item.type) - if (!needUpgrade && item.type === 'THEME') { + if (!needUpgrade) { if (item.itemValue) { - if (!(item.itemValue in Theme)) needUpgrade = true + if (item.type === 'THEME' && !(item.itemValue in Theme)) needUpgrade = true + if (item.type === 'FONT' && !(item.itemValue in FontFamily)) + needUpgrade = true } } @@ -77,45 +79,7 @@ export function MarketItemCard({
- {item.type === 'BROWSER_TITLE' ? ( -
- {item.isOwned ? : null} - {renderBrowserTitlePreview({ - template: item.meta?.template || item.name, - className: '!w-96 !max-w-96', - })} -
- ) : item.previewUrl ? ( -
- {'تصویر - - - - {item.isOwned ? : null} -
- ) : ( -
- {item.isOwned ? : null} - - {getItemTypeEmoji(item.type)} - -
- )} - + {item.description && (

{item.description} @@ -138,12 +102,3 @@ export function MarketItemCard({ ) } - -function IsOwnedBadge() { - return ( -

- - باز شده -
- ) -} diff --git a/src/layouts/market/other-items/components/market-item-purchase-modal.tsx b/src/layouts/market/other-items/components/market-item-purchase-modal.tsx index 30956f30..445639b6 100644 --- a/src/layouts/market/other-items/components/market-item-purchase-modal.tsx +++ b/src/layouts/market/other-items/components/market-item-purchase-modal.tsx @@ -2,14 +2,13 @@ import { FiCheck, FiX } from 'react-icons/fi' import Analytics from '@/analytics' import { Button } from '@/components/button/button' import { ItemPrice } from '@/components/item-price/item-price' -import { getItemTypeEmoji } from '@/components/market/getItemTypeEmoji' -import { renderBrowserTitlePreview } from '@/components/market/title/title-render-preview' import Modal from '@/components/modal' import { UserCoin } from '@/layouts/setting/tabs/account/components/user-coin' import type { MarketItem } from '@/services/hooks/market/market.interface' import { usePurchaseMarketItem } from '@/services/hooks/market/purchaseMarketItem.hook' import { translateError } from '@/utils/translate-error' import { showToast } from '@/common/toast' +import { RenderPreview } from './renderPreview' interface MarketItemPurchaseModalProps { isOpen: boolean @@ -72,29 +71,7 @@ export function MarketItemPurchaseModal({

{item.description}

{/* Item preview */} - {item.type === 'BROWSER_TITLE' ? ( -
- {renderBrowserTitlePreview({ - template: item.meta?.template || item.name, - className: '!w-96 !max-w-96', - })} -
- ) : item.previewUrl ? ( -
- {'تصویر -
- ) : ( -
- - {getItemTypeEmoji(item.type)} - -
- )} + {}} />
diff --git a/src/layouts/market/other-items/components/renderPreview.tsx b/src/layouts/market/other-items/components/renderPreview.tsx new file mode 100644 index 00000000..061ee43c --- /dev/null +++ b/src/layouts/market/other-items/components/renderPreview.tsx @@ -0,0 +1,78 @@ +import { getItemTypeEmoji } from '@/components/market/getItemTypeEmoji' +import { renderBrowserTitlePreview } from '@/components/market/title/title-render-preview' +import Tooltip from '@/components/toolTip' +import type { MarketItem } from '@/services/hooks/market/market.interface' +import { FiEye, FiShoppingBag } from 'react-icons/fi' + +interface RenderPreviewProps { + item: MarketItem + handlePreviewClick: () => void +} +function IsOwnedBadge() { + return ( +
+ + باز شده +
+ ) +} + +export function RenderPreview({ item, handlePreviewClick }: RenderPreviewProps) { + if (item.previewUrl) { + return ( +
+ {'تصویر + + + + {item.isOwned ? : null} +
+ ) + } + + if (item.type === 'BROWSER_TITLE') { + return ( +
+ {item.isOwned ? : null} + {renderBrowserTitlePreview({ + template: item.meta?.template || item.name, + className: '!w-96 !max-w-96', + })} +
+ ) + } + if (item.type === 'FONT') { + return ( +
+ {item.isOwned ? : null} +
+
+ نمونه فونت +
+ +
+
+
+ ) + } + + return ( +
+ {item.isOwned ? : null} + {getItemTypeEmoji(item.type)} +
+ ) +} diff --git a/src/layouts/market/other-items/index.tsx b/src/layouts/market/other-items/index.tsx index 9ee0caa7..4aa295ee 100644 --- a/src/layouts/market/other-items/index.tsx +++ b/src/layouts/market/other-items/index.tsx @@ -9,6 +9,8 @@ import { useGetMarketItems } from '@/services/hooks/market/getMarketItems.hook' import type { MarketItem } from '@/services/hooks/market/market.interface' import { MarketItemCard } from './components/market-item-card' import { MarketItemPurchaseModal } from './components/market-item-purchase-modal' +import { showToast } from '@/common/toast' +import type { FontFamily } from '@/context/appearance.context' export function MarketOtherItems() { const { user, isAuthenticated, refetchUser } = useAuth() @@ -29,6 +31,12 @@ export function MarketOtherItems() { const filteredItems = marketData?.items || [] const handlePurchaseClick = (item: MarketItem) => { + if (!isAuthenticated) { + Analytics.event('market_item_purchase_unauthenticated') + showToast('برای خرید آیتم باید وارد حساب کاربری خود شوید.', 'error') + return + } + setSelectedItem(item) setShowPurchaseModal(true) } @@ -49,6 +57,10 @@ export function MarketOtherItems() { if (item.type === 'THEME') { callEvent('theme_change', item.itemValue as Theme) } + + if (item.type === 'FONT') { + callEvent('font_change', item.itemValue as FontFamily) + } } const onNextPage = () => { diff --git a/src/layouts/setting/tabs/appearance/appearance.tsx b/src/layouts/setting/tabs/appearance/appearance.tsx index d62c85c2..6bfd7506 100644 --- a/src/layouts/setting/tabs/appearance/appearance.tsx +++ b/src/layouts/setting/tabs/appearance/appearance.tsx @@ -6,8 +6,7 @@ import { ContentAlignmentSettings } from './components/content-alignment-setting import { FontSelector } from './components/font-selector' import { ThemeSelector } from './components/theme-selector' export function AppearanceSettingTab() { - const { contentAlignment, setContentAlignment, fontFamily, setFontFamily } = - useAppearanceSetting() + const { contentAlignment, setContentAlignment } = useAppearanceSetting() const { isAuthenticated } = useAuth() const { data } = useGetUserInventory(isAuthenticated) @@ -23,7 +22,7 @@ export function AppearanceSettingTab() { contentAlignment={contentAlignment} setContentAlignment={setContentAlignment} /> - +
) } diff --git a/src/layouts/setting/tabs/appearance/components/font-selector.tsx b/src/layouts/setting/tabs/appearance/components/font-selector.tsx index 907c3edd..3c397f44 100644 --- a/src/layouts/setting/tabs/appearance/components/font-selector.tsx +++ b/src/layouts/setting/tabs/appearance/components/font-selector.tsx @@ -1,47 +1,97 @@ +import { useEffect, useState } from 'react' +import { FiShoppingBag } from 'react-icons/fi' +import Analytics from '@/analytics' +import { callEvent } from '@/common/utils/call-event' import { ItemSelector } from '@/components/item-selector' import { SectionPanel } from '@/components/section-panel' -import type { FontFamily } from '@/context/appearance.context' +import { FontFamily, useAppearanceSetting } from '@/context/appearance.context' +import type { UserInventoryItem } from '@/services/hooks/market/market.interface' -interface FontSelectorProps { - fontFamily: FontFamily - setFontFamily: (fontFamily: FontFamily) => void +interface FontItem { + value: FontFamily + label: string + description?: string } -const availableFonts: { value: FontFamily; label: string }[] = [ + +const defaultFonts: FontItem[] = [ { - value: 'Vazir', + value: FontFamily.Vazir, label: 'وزیر', + description: 'وقتی بچه بودم می‌خواستم بزرگ بشم!', }, { - value: 'Samim', + value: FontFamily.Samim, label: 'صمیم', + description: 'وقتی بچه بودم می‌خواستم بزرگ بشم!', }, { - value: 'Pofak', + value: FontFamily.Pofak, label: 'پفـک', + description: 'وقتی بچه بودم می‌خواستم بزرگ بشم!', }, { - value: 'rooyin', + value: FontFamily.rooyin, label: 'رویین', + description: 'وقتی بچه بودم می‌خواستم بزرگ بشم!', }, ] -export function FontSelector({ fontFamily, setFontFamily }: FontSelectorProps) { + +interface FontSelectorProps { + fetched_fonts: UserInventoryItem[] +} + +export function FontSelector({ fetched_fonts }: FontSelectorProps) { + const { fontFamily, setFontFamily } = useAppearanceSetting() + + const [fonts, setFonts] = useState(defaultFonts) + + useEffect(() => { + if (fetched_fonts.length) { + const mapped: FontItem[] = fetched_fonts.map((item) => ({ + value: item.value as FontFamily, + label: item.name ?? 'بدون نام', + description: item?.description || 'فونت خریداری شده', + })) + setFonts([...defaultFonts, ...mapped]) + } + }, [fetched_fonts]) + + const handleMoreClick = () => { + Analytics.event('font_market_opened') + callEvent('openMarketModal') + } + + const renderFontPreview = ({ value }: FontItem) => ( + + دریایچه‌ای از آرامش + + ) + return (

فونت مورد نظر خود را برای نمایش در تمامی بخش‌های افزونه انتخاب کنید:

-
- {availableFonts.map((font) => ( +
+ {fonts.map((font) => ( setFontFamily(font.value)} key={font.value} + className="w-full !h-20 !max-h-20 !min-h-20" label={font.label} - description={'فرض کن این فونت‌ها ستارن!'} + description={renderFontPreview(font)} style={{ fontFamily: font.value }} /> ))} +
handleMoreClick()} + > + + فروشگاه +
diff --git a/src/layouts/setting/tabs/appearance/components/theme-selector.tsx b/src/layouts/setting/tabs/appearance/components/theme-selector.tsx index 48ed6f3a..4d2bb649 100644 --- a/src/layouts/setting/tabs/appearance/components/theme-selector.tsx +++ b/src/layouts/setting/tabs/appearance/components/theme-selector.tsx @@ -9,10 +9,7 @@ import { ItemSelector } from '@/components/item-selector' import { SectionPanel } from '@/components/section-panel' import Tooltip from '@/components/toolTip' import { useTheme } from '@/context/theme.context' -import type { - UserInventoryItem, - UserInventoryResponse, -} from '@/services/hooks/market/market.interface' +import type { UserInventoryItem } from '@/services/hooks/market/market.interface' interface ThemeItem { id: string diff --git a/src/services/hooks/extension/updateSetting.hook.ts b/src/services/hooks/extension/updateSetting.hook.ts index f331bb2f..d413ba7d 100644 --- a/src/services/hooks/extension/updateSetting.hook.ts +++ b/src/services/hooks/extension/updateSetting.hook.ts @@ -5,7 +5,7 @@ import { getMainClient } from '@/services/api' export interface UpdateExtensionSettingsInput { pet?: string | null petName?: string | null - font?: FontFamily + font?: any theme?: string timeZone?: string wallpaperId?: string @@ -46,3 +46,12 @@ export function useChangeBrowserTitle() { }, }) } + +export function useChangeFont() { + return useMutation({ + mutationFn: async ({ font }) => { + const client = await getMainClient() + await client.put('/extension/@me/font', { font }) + }, + }) +} diff --git a/src/utils/translate-error.ts b/src/utils/translate-error.ts index 04a52cd6..8804bbee 100644 --- a/src/utils/translate-error.ts +++ b/src/utils/translate-error.ts @@ -77,6 +77,7 @@ const errorTranslations: Record = { DATE_OUT_OF_RANGE: 'تاریخ انتخاب شده خارج از محدوده مجاز است', + ITEM_NOT_FOUND: 'آیتم مورد نظر یافت نشد', TODO_NOT_FOUND: 'وظیفه مورد نظر یافت نشد', }