diff --git a/src/layouts/widgets/todos/todo.item.tsx b/src/layouts/widgets/todos/todo.item.tsx
index 9e9f1245..d42837b6 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 d972a045..8b336bdb 100644
--- a/src/layouts/widgets/todos/todos.tsx
+++ b/src/layouts/widgets/todos/todos.tsx
@@ -30,6 +30,8 @@ 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'
+import { HiOutlineCheckCircle, HiOutlineDocumentText } from 'react-icons/hi2'
const viewModeOptions = [
{ value: TodoViewType.Day, label: 'لیست امروز' },
@@ -136,17 +138,24 @@ export function TodosLayout({ onChangeTab }: Prop) {
-
-
- وظایف
-
-
onChangeTab()}
- >
- یادداشت
-
-
+
,
+ },
+ {
+ id: 'notes',
+ label: 'یادداشت',
+ icon:
,
+ },
+ ]}
+ size="small"
+ className="w-fit"
+ />
{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/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 e559add5..6d6f3b55 100644
--- a/src/layouts/widgets/tools/tools.layout.tsx
+++ b/src/layouts/widgets/tools/tools.layout.tsx
@@ -4,7 +4,29 @@ 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 {
+ MdOutlineCurrencyExchange,
+ MdOutlineMosque,
+ MdOutlineTimer,
+} from 'react-icons/md'
+const tabs = [
+ {
+ id: 'pomodoro' as ToolsTabType,
+ label: 'پومودورو',
+ icon:
,
+ },
+ {
+ id: 'religious-time' as ToolsTabType,
+ label: 'اوقات شرعی',
+ icon:
,
+ },
+ {
+ id: 'currency-converter' as ToolsTabType,
+ label: 'تبدیل',
+ icon:
,
+ },
+]
const ReligiousTime = React.lazy(() =>
import('./religious/religious-time').then((module) => ({
@@ -60,7 +82,10 @@ 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..347f3ea5
--- /dev/null
+++ b/src/layouts/widgets/wigiPad/info-panel/infoWeather.tsx
@@ -0,0 +1,125 @@
+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 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {weather.city?.fa}
+
+
+
+ {Math.round(weather.weather?.temperature?.temp || 0)}
+
+
+ {unitsFlag['metric']}
+
+
+
+
+ {weather.weather?.icon?.url && (
+

+ )}
+
+
+
+
+
+
+ {Math.round(weather.weather?.temperature?.wind_speed || 0)}
+ m/s
+
+
+
+
+
+
+ {weather.weather?.temperature?.humidity}%
+
+
+
+
+ {weather.weather?.description?.text}
+
+
+
+ )
+}
diff --git a/src/pages/home.tsx b/src/pages/home.tsx
index ab82ccb5..9f0b16f1 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/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`}
>
-
+
-
+
-
diff --git a/src/services/hooks/auth/authService.hook.ts b/src/services/hooks/auth/authService.hook.ts
index 075035da..053789d7 100644
--- a/src/services/hooks/auth/authService.hook.ts
+++ b/src/services/hooks/auth/authService.hook.ts
@@ -1,4 +1,4 @@
-import { useMutation } from '@tanstack/react-query'
+import { useMutation, useQuery } from '@tanstack/react-query'
import { setToStorage } from '@/common/storage'
import { getMainClient } from '@/services/api'
@@ -119,6 +119,15 @@ async function verifyOtp(payload: OtpVerifyPayload): Promise
{
return response.data
}
+async function getAuthState(): Promise<{
+ content?: string
+ type?: 'warning' | 'info'
+}> {
+ const client = await getMainClient()
+ const response = await client.get('/auth/status')
+ return response.data.data
+}
+
async function setupWizard(data: WizardPayload): Promise {
const client = await getMainClient()
const response = await client.post('/users/@me/complete-wizard', data)
@@ -155,6 +164,12 @@ export function useGoogleSignIn() {
})
}
+export function useGetAuthStatus() {
+ return useQuery({
+ queryFn: () => getAuthState(),
+ queryKey: ['get-auth-status'],
+ })
+}
export function useRequestOtp() {
return useMutation({
mutationFn: (payload: OtpPayload) => requestOtp(payload),
diff --git a/src/services/hooks/extension/getNotifications.hook.ts b/src/services/hooks/extension/getNotifications.hook.ts
new file mode 100644
index 00000000..0535c0be
--- /dev/null
+++ b/src/services/hooks/extension/getNotifications.hook.ts
@@ -0,0 +1,59 @@
+import { useQuery } from '@tanstack/react-query'
+import { getMainClient } from '@/services/api'
+import type { ReactNode } from 'react'
+
+export interface NotificationItem {
+ id?: string
+ title?: string
+ subTitle?: string
+ description?: string
+ node?: ReactNode
+ link?: string
+ icon?: string
+ closeable?: boolean
+ ttl?: number
+ type?: 'text' | 'url' | 'action' | 'page'
+ goTo?: 'explorer'
+ target?: string
+}
+
+export interface NotificationItemResponse {
+ data: {
+ notifications: Array<{
+ id?: string
+ title: string
+ subTitle: string
+ description?: string
+ link?: string
+ icon?: string
+ closeable: boolean | null
+ type?: 'text' | 'url' | 'action' | 'page'
+ goTo?: 'explorer'
+ target?: string
+ }>
+ upcomingCalendarEvents: any[]
+ }
+}
+
+async function fetchNotifications(): Promise {
+ const client = await getMainClient()
+ const { data } = await client.get('/notifications/beta')
+
+ return data
+}
+
+export function useGetNotifications(options: {
+ enabled?: boolean
+ refetchInterval?: number | null
+}) {
+ const { enabled = true, refetchInterval = null } = options
+
+ return useQuery({
+ queryKey: ['notifications'],
+ queryFn: () => fetchNotifications(),
+ enabled,
+ refetchInterval: refetchInterval || false,
+ retry: 1,
+ staleTime: 5 * 60 * 1000, // 5 minutes
+ })
+}
diff --git a/src/services/hooks/extension/getWigiPadData.hook.ts b/src/services/hooks/extension/getWigiPadData.hook.ts
index 5f7f0488..67e5da9f 100644
--- a/src/services/hooks/extension/getWigiPadData.hook.ts
+++ b/src/services/hooks/extension/getWigiPadData.hook.ts
@@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query'
import { getMainClient } from '@/services/api'
+import type { ReactNode } from 'react'
export interface WigiPadBirthday {
name: string
@@ -32,11 +33,16 @@ export interface UpcomingCalendarEvent {
}
export interface NotificationItem {
+ id?: string
title: string
subTitle: string
description?: string
link?: string
icon?: string
+ closeable: boolean | null
+ type?: 'text' | 'url' | 'action' | 'page'
+ goTo?: 'explorer'
+ target?: string
}
export interface WigiPadDataResponse {
birthdays: WigiPadBirthday[]
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
diff --git a/src/services/hooks/user/userService.hook.ts b/src/services/hooks/user/userService.hook.ts
index 8cc9d380..530e4b57 100644
--- a/src/services/hooks/user/userService.hook.ts
+++ b/src/services/hooks/user/userService.hook.ts
@@ -44,6 +44,8 @@ interface FetchedProfile {
label: string
}>
joinedAt: string
+
+ hasTodayMood: boolean
}
export interface UserProfile extends FetchedProfile {
diff --git a/src/utils/translate-error.ts b/src/utils/translate-error.ts
index f7bb72fc..6ae9fb50 100644
--- a/src/utils/translate-error.ts
+++ b/src/utils/translate-error.ts
@@ -80,7 +80,8 @@ const errorTranslations: Record = {
ITEM_NOT_FOUND: 'آیتم مورد نظر یافت نشد',
TODO_NOT_FOUND: 'وظیفه مورد نظر یافت نشد',
INVALID_OTP_CODE: 'کد تایید نامعتبر است',
- USE_EMAIL_FOR_OTP: 'لطفا از ایمیل برای دریافت کد تایید استفاده کنید',
+ USE_EMAIL_FOR_OTP: 'لطفا موقتاََ از ایمیل برای دریافت کد تایید استفاده کنید',
+ USE_PHONE_FOR_OTP: 'لطفا موقتاََ از شماره موبایل برای دریافت کد تایید استفاده کنید',
INVALID_OCCUPATION_ID: 'شغل نامعتبری انتخاب کردی!',
ONE_OR_MORE_INVALID_INTEREST_IDS:
diff --git a/wxt.config.ts b/wxt.config.ts
index 19b6fb39..330093e6 100644
--- a/wxt.config.ts
+++ b/wxt.config.ts
@@ -48,7 +48,7 @@ export default defineConfig({
'@wxt-dev/module-react',
],
manifest: {
- version: '1.0.68',
+ version: '1.0.73',
name: 'Widgetify',
description:
'Transform your new tab into a smart dashboard with Widgetify! Get currency rates, crypto prices, weather & more.',