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
417 changes: 255 additions & 162 deletions bun.lock

Large diffs are not rendered by default.

56 changes: 28 additions & 28 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,53 +21,53 @@
"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@tanstack/react-query": "5.90.2",
"@tanstack/react-query": "5.90.12",
"@wxt-dev/webextension-polyfill": "1.0.0",
"axios": "1.12.2",
"chart.js": "4.5.0",
"dompurify": "3.2.7",
"axios": "1.13.2",
"chart.js": "4.5.1",
"dompurify": "3.3.1",
"jalali-moment": "3.3.11",
"moment": "2.30.1",
"moment-hijri": "3.0.0",
"motion": "12.23.24",
"motion": "12.23.26",
"ms": "2.1.3",
"react": "19.2.0",
"react-chartjs-2": "5.3.0",
"react": "19.2.3",
"react-chartjs-2": "5.3.1",
"react-colorful": "5.6.1",
"react-dom": "19.2.0",
"react-dom": "19.2.3",
"react-ga4": "2.1.0",
"react-hot-toast": "2.6.0",
"react-icons": "5.5.0",
"react-joyride": "2.9.3",
"swiper": "12.0.2",
"swiper": "12.0.3",
"uuid": "13.0.0",
"workbox-background-sync": "7.3.0",
"workbox-cacheable-response": "7.3.0",
"workbox-expiration": "7.3.0",
"workbox-precaching": "7.3.0",
"workbox-routing": "7.3.0",
"workbox-strategies": "7.3.0",
"wxt": "0.20.11"
"workbox-background-sync": "7.4.0",
"workbox-cacheable-response": "7.4.0",
"workbox-expiration": "7.4.0",
"workbox-precaching": "7.4.0",
"workbox-routing": "7.4.0",
"workbox-strategies": "7.4.0",
"wxt": "0.20.13"
},
"devDependencies": {
"@biomejs/biome": "2.2.5",
"@tailwindcss/vite": "4.1.14",
"@biomejs/biome": "2.3.10",
"@tailwindcss/vite": "4.1.18",
"@types/moment-hijri": "2.1.4",
"@types/ms": "2.1.0",
"@types/node": "24.7.2",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.1",
"@types/node": "25.0.3",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
"@types/styled-jsx": "3.4.4",
"@vitejs/plugin-react": "5.0.4",
"@vitejs/plugin-react": "5.1.2",
"@wxt-dev/auto-icons": "1.1.0",
"@wxt-dev/module-react": "1.1.5",
"autoprefixer": "10.4.21",
"daisyui": "5.2.2",
"globals": "16.4.0",
"autoprefixer": "10.4.23",
"daisyui": "5.5.14",
"globals": "16.5.0",
"postcss": "8.5.6",
"tailwindcss": "4.1.14",
"terser": "5.44.0",
"tailwindcss": "4.1.18",
"terser": "5.44.1",
"typescript": "5.9.3",
"vite": "7.1.9"
"vite": "7.3.0"
}
}
23 changes: 23 additions & 0 deletions src/common/playAlarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type infoAlarmType = 'success' | 'done_todo'
let alarms: Record<infoAlarmType, string> = {
success: 'https://cdn.widgetify.ir/effects/alarm-success.mp3',
done_todo:
'https://widgetify-ir.storage.c2.liara.space/effects/alarm_success_todo.mp3',
}
let audioCache: Partial<Record<infoAlarmType, HTMLAudioElement>> = {}

export async function playAlarm(type: infoAlarmType) {
if (!audioCache[type]) {
const alarm = alarms[type]
const audio = new Audio(alarm)
audio.preload = 'auto'
audioCache[type] = audio
await new Promise((resolve) => {
audio.addEventListener('canplaythrough', resolve, { once: true })
audio.addEventListener('error', resolve, { once: true })
})
}
const audio = audioCache[type]
audio.currentTime = 0
await audio.play()
}
8 changes: 2 additions & 6 deletions src/common/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type React from 'react'
import toast from 'react-hot-toast'
import { playAlarm } from './playAlarm'

type ToastType = 'success' | 'error' | 'info'
interface ToastOptions {
Expand All @@ -13,8 +14,6 @@ interface ToastOptions {
| 'bottom-right'
alarmSound?: boolean
}
const audio = new Audio('https://cdn.widgetify.ir/effects/alarm-success.mp3')
audio.preload = 'auto'

export function showToast(
message: React.ReactNode | string,
Expand Down Expand Up @@ -180,9 +179,6 @@ export function showToast(
}

if (options?.alarmSound) {
const sound = audio.cloneNode() as HTMLAudioElement
sound.play().catch(() => {
console.log('Audio blocked by browser autoplay policy')
})
playAlarm('success')
}
}
7 changes: 4 additions & 3 deletions src/context/todo.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useReorderTodos } from '@/services/hooks/todo/reorder-todo.hook'
import { useUpdateTodo } from '@/services/hooks/todo/update-todo.hook'
import { useGetTodos } from '@/services/hooks/todo/get-todos.hook'
import type { FetchedTodo, Todo } from '@/services/hooks/todo/todo.interface'
import { playAlarm } from '@/common/playAlarm'

export enum TodoViewType {
Day = 'day',
Expand Down Expand Up @@ -199,12 +200,12 @@ export function TodoProvider({ children }: { children: React.ReactNode }) {
'error'
)
}

const isCompleted = !current.completed
const [err, _] = await safeAwait(
updateTodoAsync({
id: onlineId,
input: {
completed: !current.completed,
completed: isCompleted,
},
})
)
Expand All @@ -213,9 +214,9 @@ export function TodoProvider({ children }: { children: React.ReactNode }) {
showToast(translateError(err) as string, 'error')
return
}

refetch()
Analytics.event('todo_toggled')
if (isCompleted) playAlarm('done_todo')
}

const updateTodo = async (id: string, updates: Partial<Omit<Todo, 'id'>>) => {
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/widgets/notes/components/note-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function NoteEditor({ note }: NoteEditorProps) {
}

return (
<div className="flex flex-col h-full gap-y-2">
<div className="flex flex-col h-full overflow-hidden gap-y-2">
<input
ref={titleRef}
type="text"
Expand Down
4 changes: 1 addition & 3 deletions src/layouts/widgets/notes/components/note-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ export function NoteItem({ note, handleNoteClick }: Prop) {
</div>
</div>
<div className="w-full p-2 rounded-md bg-base-200/50 min-h-10">
<p className="text-xs font-light text-muted line-clamp-2">
{note.body ? note.body.slice(0, 45) + '...' : ''}
</p>
<p className="text-xs font-light text-muted">{note.body}</p>
</div>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/widgets/todos/todo.item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function TodoItem({
}: Prop) {
const { toggleTodo, refetchTodos, todos, setTodos } = useTodoStore()
const { isAuthenticated } = useAuth()
const [expanded, setExpanded] = useState(false)
const [expanded, setExpanded] = useState(true)
const [showConfirmation, setShowConfirmation] = useState(false)
const [showEditModal, setShowEditModal] = useState(false)
const isUpdating = useIsMutating({ mutationKey: ['updateTodo'] }) > 0
Expand Down
69 changes: 26 additions & 43 deletions src/layouts/widgets/todos/todos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ import { ExpandableTodoInput } from './expandable-todo-input'
import { SortableTodoItem } from './sortable-todo-item'
import { TodoStats } from './todo-stats'
import { useAuth } from '@/context/auth.context'
import { SelectBox } from '@/components/selectbox/selectbox'
import Analytics from '@/analytics'
import { AuthRequiredModal } from '@/components/auth/AuthRequiredModal'
import { IconLoading } from '@/components/loading/icon-loading'
import Analytics from '@/analytics'

const viewModeOptions = [
{ value: TodoViewType.Day, label: 'لیست امروز' },
{ value: TodoViewType.Monthly, label: 'لیست ماهانه' },
{ value: TodoViewType.All, label: 'همه وظایف' },
]
interface Prop {
onChangeTab?: any
}
Expand All @@ -38,7 +43,7 @@ export function TodosLayout({ onChangeTab }: Prop) {
const { addTodo, todos, updateOptions, todoOptions, reorderTodos, isPending } =
useTodoStore()
const { blurMode } = useGeneralSetting()
const [filter, setFilter] = useState<'active' | 'completed'>('active')
const [filter, setFilter] = useState<'active' | 'completed' | 'all'>('all')
const [showStats, setShowStats] = useState<boolean>(false)
const [todoText, setTodoText] = useState('')
const selectedDateStr = formatDateStr(selectedDate.clone())
Expand All @@ -56,7 +61,7 @@ export function TodosLayout({ onChangeTab }: Prop) {
updateOptions({ viewMode })
}

const updateTodoFilter = (newFilter: 'active' | 'completed') => {
const updateTodoFilter = (newFilter: 'active' | 'completed' | 'all') => {
setFilter(newFilter)
Analytics.event(`todo_filter_${newFilter}_click`)
}
Expand Down Expand Up @@ -151,57 +156,35 @@ export function TodosLayout({ onChangeTab }: Prop) {
) : (
<div className="flex justify-between mb-2">
<div className="flex gap-0.5">
<button
onClick={() => updateTodoFilter('all')}
className={`px-1 rounded-xl border-none text-[10px] leading-none cursor-pointer active:scale-95 ${filter === 'all' ? 'bg-primary text-white' : 'text-muted bg-base-300'}`}
>
همه
</button>
<button
onClick={() => updateTodoFilter('active')}
className={`px-1.5 py-0.5 rounded-full border-none text-[10px] leading-none cursor-pointer active:scale-95 ${filter === 'active' ? 'bg-primary text-white' : 'text-muted bg-base-300'}`}
className={`px-1 rounded-xl border-none text-[10px] leading-none cursor-pointer active:scale-95 ${filter === 'active' ? 'bg-primary text-white' : 'text-muted bg-base-300'}`}
>
فعال
</button>
<button
onClick={() => updateTodoFilter('completed')}
className={`px-1.5 py-0.5 rounded-full border-none text-[10px] leading-none cursor-pointer active:scale-95 ${filter === 'completed' ? 'bg-primary text-white' : 'text-muted bg-base-300'}`}
className={`px-1 rounded-xl border-none text-[10px] leading-none cursor-pointer active:scale-95 ${filter === 'completed' ? 'bg-primary text-white' : 'text-muted bg-base-300'}`}
>
تکمیل شده
تکمیل‌ها
</button>
</div>
<div className="relative">
<select
<SelectBox
options={viewModeOptions}
value={todoOptions.viewMode}
onChange={(e) =>
handleChangeViewMode(
e.target.value as TodoViewType
)
onChange={(value) =>
handleChangeViewMode(value as TodoViewType)
}
className={
'select select-xs text-[10px] w-[5.5rem] !px-2.5 rounded-xl !outline-none !border-none !shadow-none text-muted bg-base-300 cursor-pointer'
}
style={{
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e")`,
backgroundPosition: 'left 0.5rem center',
backgroundRepeat: 'no-repeat',
backgroundSize: '1.3em 1.3em',
paddingLeft: '3rem',
}}
>
<option
value={TodoViewType.Day}
className="text-content"
>
لیست امروز
</option>
<option
value={TodoViewType.Monthly}
className="text-content"
>
لیست ماهانه
</option>
<option
value={TodoViewType.All}
className="text-content"
>
همه وظایف
</option>
</select>
className="!text-[10px] !w-[4.5rem] !px-2.5 rounded-xl !outline-none !border-none !shadow-none text-muted bg-base-300 cursor-pointer"
optionClassName="text-content"
/>
</div>
</div>
)}
Expand All @@ -214,7 +197,7 @@ export function TodosLayout({ onChangeTab }: Prop) {
onDragEnd={handleDragEnd}
>
<div
className={`space-y-1.5 overflow-y-auto h-full ${blurMode ? 'blur-mode' : 'disabled-blur-mode'}`}
className={`space-y-1.5 overflow-y-auto scrollbar-none h-full ${blurMode ? 'blur-mode' : 'disabled-blur-mode'}`}
>
{selectedDateTodos.length > 0 ? (
<SortableContext
Expand Down