Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/common/utils/call-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -43,6 +44,7 @@ export interface EventName {
closeAllDropdowns: null
openProfile: null
openMarketModal: null
font_change: FontFamily
}

export function callEvent<K extends keyof EventName>(eventName: K, data?: EventName[K]) {
Expand Down
28 changes: 21 additions & 7 deletions src/context/appearance.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -23,15 +32,15 @@ interface AppearanceContextContextType extends AppearanceData {

const DEFAULT_SETTINGS: AppearanceData = {
contentAlignment: 'top',
fontFamily: 'Vazir',
fontFamily: FontFamily.Vazir,
}

export const AppearanceContext = createContext<AppearanceContextContextType | null>(null)

export function AppearanceProvider({ children }: { children: React.ReactNode }) {
const [settings, setSettings] = useState<AppearanceData>(DEFAULT_SETTINGS)
const [isInitialized, setIsInitialized] = useState(false)
const { mutateAsync: updateSettings } = useUpdateExtensionSettings()
const { mutateAsync: changeFontAsync } = useChangeFont()
const { isAuthenticated, user } = useAuth()

useEffect(() => {
Expand Down Expand Up @@ -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(() => {
Expand Down
5 changes: 5 additions & 0 deletions src/context/theme.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}, [])

Expand Down
21 changes: 21 additions & 0 deletions src/fonts.css
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
63 changes: 9 additions & 54 deletions src/layouts/market/other-items/components/market-item-card.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
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
onPurchase: () => void
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':
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -77,45 +79,7 @@ export function MarketItemCard({
</div>

<section className="mb-4 min-h-[7rem] flex flex-col gap-3 flex-1">
{item.type === 'BROWSER_TITLE' ? (
<div className="relative flex items-center justify-center flex-1 p-2 border bg-base-100 rounded-xl border-base-200">
{item.isOwned ? <IsOwnedBadge /> : null}
{renderBrowserTitlePreview({
template: item.meta?.template || item.name,
className: '!w-96 !max-w-96',
})}
</div>
) : item.previewUrl ? (
<div className="relative flex items-center justify-center flex-1 p-2 border bg-base-100 rounded-xl border-base-200">
<img
src={item.previewUrl}
alt={'تصویر پیش‌نمایش'}
className="object-contain max-w-full rounded-lg max-h-20 min-h-20"
loading="lazy"
/>
<Tooltip
content="مشاهده تصویر کامل"
position="bottom"
offset={-20}
>
<button
onClick={handlePreviewClick}
className="absolute top-1 left-1 p-1.5 bg-black/60 hover:bg-black/80 text-white rounded-full opacity-0 group-hover:opacity-100 transition-all duration-200 backdrop-blur-sm cursor-pointer"
>
<FiEye size={14} />
</button>
</Tooltip>
{item.isOwned ? <IsOwnedBadge /> : null}
</div>
) : (
<div className="relative flex items-center justify-center flex-1 border border-dashed bg-base-100 rounded-xl border-base-300">
{item.isOwned ? <IsOwnedBadge /> : null}
<span className="text-2xl opacity-50">
{getItemTypeEmoji(item.type)}
</span>
</div>
)}

<RenderPreview item={item} handlePreviewClick={handlePreviewClick} />
{item.description && (
<p className="px-1 text-xs leading-relaxed text-muted line-clamp-2">
{item.description}
Expand All @@ -138,12 +102,3 @@ export function MarketItemCard({
</div>
)
}

function IsOwnedBadge() {
return (
<div className="absolute z-10 flex gap-0.5 px-1 rounded-full shadow-sm text-success bg-black/80 items-center top-2 right-2">
<FiShoppingBag size={10} />
<span className="!text-[10px] font-normal">باز شده</span>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -72,29 +71,7 @@ export function MarketItemPurchaseModal({
<p className="mt-1 mb-1 text-xs text-muted">{item.description}</p>

{/* Item preview */}
{item.type === 'BROWSER_TITLE' ? (
<div className="relative flex items-center justify-center flex-1 p-2 border bg-base-100 rounded-xl border-base-200">
{renderBrowserTitlePreview({
template: item.meta?.template || item.name,
className: '!w-96 !max-w-96',
})}
</div>
) : item.previewUrl ? (
<div className="relative flex items-center justify-center flex-1 p-2 border bg-base-100 rounded-xl border-base-200">
<img
src={item.previewUrl}
alt={'تصویر پیش‌نمایش'}
className="object-contain max-w-full rounded-lg max-h-20 min-h-20"
loading="lazy"
/>
</div>
) : (
<div className="flex items-center justify-center flex-1 border border-dashed bg-base-100 rounded-xl border-base-300">
<span className="text-2xl opacity-50">
{getItemTypeEmoji(item.type)}
</span>
</div>
)}
<RenderPreview item={item} handlePreviewClick={() => {}} />
</div>

<div className="p-3 space-y-2 border rounded-xl border-primary/20 bg-primary/5">
Expand Down
78 changes: 78 additions & 0 deletions src/layouts/market/other-items/components/renderPreview.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="absolute z-10 flex gap-0.5 px-1 rounded-full shadow-sm text-success bg-black/80 items-center top-2 right-2">
<FiShoppingBag size={10} />
<span className="!text-[10px] font-normal">باز شده</span>
</div>
)
}

export function RenderPreview({ item, handlePreviewClick }: RenderPreviewProps) {
if (item.previewUrl) {
return (
<div className="relative flex items-center flex-1 p-2 border bg-base-300 rounded-xl border-base-200">
<img
src={item.previewUrl}
alt={'تصویر پیش‌نمایش'}
className="object-center w-full max-w-full rounded-lg max-h-20 min-h-20"
loading="lazy"
/>
<Tooltip content="مشاهده تصویر کامل" position="bottom" offset={-20}>
<button
onClick={handlePreviewClick}
className="absolute top-1 left-1 p-1.5 bg-black/60 hover:bg-black/80 text-white rounded-full opacity-0 group-hover:opacity-100 transition-all duration-200 backdrop-blur-sm cursor-pointer"
>
<FiEye size={14} />
</button>
</Tooltip>
{item.isOwned ? <IsOwnedBadge /> : null}
</div>
)
}

if (item.type === 'BROWSER_TITLE') {
return (
<div className="relative flex items-center justify-center flex-1 p-2 border bg-base-300 rounded-xl border-base-200">
{item.isOwned ? <IsOwnedBadge /> : null}
{renderBrowserTitlePreview({
template: item.meta?.template || item.name,
className: '!w-96 !max-w-96',
})}
</div>
)
}
if (item.type === 'FONT') {
return (
<div className="relative flex items-center justify-center flex-1 p-2 border bg-base-300 rounded-xl border-base-200">
{item.isOwned ? <IsOwnedBadge /> : null}
<div className="text-center">
<div
className={`text-xl leading-relaxed`}
style={{ fontFamily: item.itemValue }}
>
<span className="font-medium">نمونه فونت</span>
<br />
<span></span>
</div>
</div>
</div>
)
}

return (
<div className="relative flex items-center justify-center flex-1 border border-dashed bg-base-100 rounded-xl border-base-300">
{item.isOwned ? <IsOwnedBadge /> : null}
<span className="text-2xl opacity-50">{getItemTypeEmoji(item.type)}</span>
</div>
)
}
12 changes: 12 additions & 0 deletions src/layouts/market/other-items/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
}
Expand All @@ -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 = () => {
Expand Down
5 changes: 2 additions & 3 deletions src/layouts/setting/tabs/appearance/appearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -23,7 +22,7 @@ export function AppearanceSettingTab() {
contentAlignment={contentAlignment}
setContentAlignment={setContentAlignment}
/>
<FontSelector fontFamily={fontFamily} setFontFamily={setFontFamily} />
<FontSelector fetched_fonts={data?.fonts || []} />
</div>
)
}
Loading