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
20 changes: 20 additions & 0 deletions src/common/constant/priority_options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const PRIORITY_OPTIONS = [
{
value: 'low',
ariaLabel: 'اولویت کم',
bgColor: 'bg-green-500',
hoverBgColor: 'hover:bg-green-600',
},
{
value: 'medium',
ariaLabel: 'اولویت متوسط',
bgColor: 'bg-yellow-400',
hoverBgColor: 'hover:bg-yellow-400',
},
{
value: 'high',
ariaLabel: 'اولویت زیاد',
bgColor: 'bg-red-500',
hoverBgColor: 'hover:bg-red-500',
},
]
104 changes: 48 additions & 56 deletions src/context/notes.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,21 @@ import {
} from 'react'
import Analytics from '@/analytics'
import { getFromStorage, setToStorage } from '@/common/storage'
import { sleep } from '@/common/utils/timeout'
import { safeAwait } from '@/services/api'
import {
deleteNote,
type FetchedNote,
getNotes,
upsertNote,
} from '@/services/note/note-api'
import { translateError } from '@/utils/translate-error'
import { showToast } from '@/common/toast'

export interface Note {
id: string
title: string
body: string
createdAt: number
updatedAt: number
}
import { useGetNotes } from '@/services/hooks/note/get-notes.hook'
import { useAuth } from './auth.context'
import { useRemoveNote } from '@/services/hooks/note/delete-note.hook'
import { useUpsertNote } from '@/services/hooks/note/upsert-note.hook'
import type { FetchedNote } from '@/services/hooks/note/note.interface'

interface NotesContextType {
notes: Note[]
notes: FetchedNote[]
activeNoteId: string | null
setActiveNoteId: (id: string | null) => void
addNote: () => void
updateNote: (id: string, updates: Partial<Note>) => void
updateNote: (id: string, updates: Partial<FetchedNote>) => void
deleteNote: (id: string) => void
isSaving: boolean
isCreatingNote: boolean
Expand All @@ -42,39 +32,41 @@ interface NotesContextType {
const NotesContext = createContext<NotesContextType | undefined>(undefined)

export function NotesProvider({ children }: { children: ReactNode }) {
const [notes, setNotes] = useState<Note[]>([])
const { isAuthenticated } = useAuth()
const [notes, setNotes] = useState<FetchedNote[]>([])
const [activeNoteId, setActiveNoteId] = useState<string | null>(null)
const [isSaving, setIsSaving] = useState(false)
const [isCreatingNote, setIsCreatingNote] = useState(false)
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null)

const { data: fetchedNotes, refetch, dataUpdatedAt } = useGetNotes(isAuthenticated)
const { mutateAsync: removeNoteAsync } = useRemoveNote()
const { mutateAsync: upsertNoteAsync } = useUpsertNote()

useEffect(() => {
async function loadNotes() {
const storedNotes = await getFromStorage('notes_data')
if (storedNotes && Array.isArray(storedNotes) && storedNotes.length > 0) {
if (storedNotes && storedNotes.length > 0) {
setNotes(storedNotes)
}

await sleep(1000)
setIsSaving(true)
const [error, fetchedNotes] = await safeAwait<AxiosError, FetchedNote[]>(
getNotes()
)
setIsSaving(false)
if (!error) {
setNotes(fetchedNotes)
}
}

loadNotes()
}, [])

useEffect(() => {
console.log('fetchedNotes')
if (fetchedNotes.length) {
sync(fetchedNotes, true)
}
}, [dataUpdatedAt])

const addNote = async () => {
if (isCreatingNote) return

setIsCreatingNote(true)

const newNote: Note = {
const newNote: FetchedNote = {
id: '',
title: '',
body: '',
Expand All @@ -83,25 +75,22 @@ export function NotesProvider({ children }: { children: ReactNode }) {
}

const [er, createdNote] = await safeAwait<AxiosError, FetchedNote>(
upsertNote(newNote)
upsertNoteAsync(newNote)
)

setIsCreatingNote(false)

if (er) {
showToast(translateError(er) as any, 'error')
return
}

setNotes((prevNotes) => {
const updatedNotes = [createdNote, ...prevNotes]
setToStorage('notes_data', updatedNotes)
return updatedNotes
})
sync([createdNote, ...notes], true)
setActiveNoteId(createdNote.id)
Analytics.event('add_notes')
}

const updateNote = (id: string, updates: Partial<Note>) => {
const updateNote = (id: string, updates: Partial<FetchedNote>) => {
setIsSaving(true)

if (saveTimeoutRef.current) {
Expand All @@ -111,10 +100,11 @@ export function NotesProvider({ children }: { children: ReactNode }) {
saveTimeoutRef.current = setTimeout(async () => {
Analytics.event('update_notes')
const [error, updatedNote] = await safeAwait<AxiosError, FetchedNote>(
upsertNote({
upsertNoteAsync({
title: updates.title || null,
body: updates.body || null,
id,
priority: updates.priority,
})
)
setIsSaving(false)
Expand All @@ -127,30 +117,32 @@ export function NotesProvider({ children }: { children: ReactNode }) {
return showToast(`${key}: ${translatedError[key]}`, 'error')
}

// update notes
const updatedNotes = notes.map((note) =>
note.id === id ? updatedNote : note
)

await setToStorage('notes_data', updatedNotes)
}, 2500)
let noteIndex = notes.findIndex((n) => n.id === id)
if (noteIndex === -1) return showToast('یادداشت پیدا نشد', 'error')
notes[noteIndex] = updatedNote
sync(notes, true)
}, 400)
}

const onDeleteNote = async (id: string) => {
const existingNote = notes.findIndex((note) => note.id === id)
if (existingNote !== -1) {
setIsSaving(true)
const updatedNotes = [...notes]
updatedNotes.splice(existingNote, 1)
setNotes(updatedNotes)
setActiveNoteId(null)

await safeAwait(deleteNote(id))

await setToStorage('notes_data', updatedNotes)
setIsSaving(true)
const [err, _] = await safeAwait(removeNoteAsync(id))
if (err) {
setIsSaving(false)
return showToast(translateError(err) as any, 'error')
}

refetch()
Analytics.event('delete_notes')
setActiveNoteId(null)
setIsSaving(false)
}

const sync = (data: FetchedNote[], syncLocal: boolean) => {
setNotes(data)
if (syncLocal) {
setToStorage('notes_data', notes)
}
}

return (
Expand Down
87 changes: 59 additions & 28 deletions src/context/widget-visibility.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { YouTubeLayout } from '@/layouts/widgets/youtube/youtube.layout'
import { useAuth } from './auth.context'
import { CurrencyProvider } from './currency.context'
import { showToast } from '@/common/toast'
import { YadkarWidget } from '@/layouts/widgets/yadkar/yadkar'

export enum WidgetKeys {
comboWidget = 'comboWidget',
Expand All @@ -34,6 +35,7 @@ export enum WidgetKeys {
youtube = 'youtube',
wigiPad = 'wigiPad',
network = 'network',
yadKar = 'yadKar',
}
export interface WidgetItem {
id: WidgetKeys
Expand All @@ -58,6 +60,15 @@ export const widgetItems: WidgetItem[] = [
canToggle: true,
popular: true,
},
{
id: WidgetKeys.yadKar,
emoji: '📒',
label: 'یادکار (وظایف و یادداشت)',
order: 0,
node: <YadkarWidget />,
canToggle: true,
isNew: true,
},
{
id: WidgetKeys.tools,
emoji: '🧰',
Expand All @@ -66,14 +77,7 @@ export const widgetItems: WidgetItem[] = [
node: <ToolsLayout />,
canToggle: true,
},
{
id: WidgetKeys.todos,
emoji: '✅',
label: 'وظایف',
order: 2,
node: <TodosLayout />,
canToggle: true,
},

{
id: WidgetKeys.weather,
emoji: '🌤️',
Expand Down Expand Up @@ -117,12 +121,13 @@ export const widgetItems: WidgetItem[] = [
},

{
id: WidgetKeys.notes,
emoji: '📝',
label: 'یادداشت‌ها',
order: 7,
node: <NotesLayout />,
id: WidgetKeys.network,
emoji: '🌐',
label: 'شبکه',
order: 9,
node: <NetworkLayout />,
canToggle: true,
isNew: false,
},
{
id: WidgetKeys.youtube,
Expand All @@ -135,13 +140,22 @@ export const widgetItems: WidgetItem[] = [
soon: true,
},
{
id: WidgetKeys.network,
emoji: '🌐',
label: 'شبکه',
order: 9,
node: <NetworkLayout />,
canToggle: true,
isNew: true,
id: WidgetKeys.todos,
emoji: '✅',
label: 'وظایف',
order: 2,
node: <TodosLayout />,
canToggle: false,
disabled: true,
},
{
id: WidgetKeys.notes,
emoji: '📝',
label: 'یادداشت‌ها',
order: 7,
node: <NotesLayout />,
canToggle: false,
disabled: true,
},
]

Expand All @@ -155,7 +169,7 @@ interface WidgetVisibilityContextType {
const defaultVisibility: WidgetKeys[] = [
WidgetKeys.calendar,
WidgetKeys.tools,
WidgetKeys.todos,
WidgetKeys.yadKar,
WidgetKeys.comboWidget,
]
export const MAX_VISIBLE_WIDGETS = 5
Expand All @@ -178,13 +192,34 @@ export function WidgetVisibilityProvider({ children }: { children: ReactNode })
useState<Record<WidgetKeys, number>>(getDefaultWidgetOrders)
const firstRender = useRef(true)
const { isAuthenticated } = useAuth()

const saveActiveWidgets = () => {
const activeWidgets = widgetItems
.filter((item) => visibility.includes(item.id))
.map((item) => ({
...item,
order: widgetOrders[item.id] ?? item.order,
}))
setToStorage('activeWidgets', activeWidgets)
}

useEffect(() => {
async function loadSettings() {
const storedVisibility = await getFromStorage('activeWidgets')
if (storedVisibility) {
const visibilityIds = storedVisibility
.filter((item) => widgetItems.some((w) => w.id === item.id))
.map((item: any) => item.id as WidgetKeys)

if (
visibilityIds.includes(WidgetKeys.todos) ||
visibilityIds.includes(WidgetKeys.notes)
) {
Analytics.event('yadkar_merged')
visibilityIds.push(WidgetKeys.yadKar)
saveActiveWidgets()
}

setVisibility(visibilityIds)

const orders: Record<WidgetKeys, number> = {} as Record<
Expand All @@ -205,17 +240,13 @@ export function WidgetVisibilityProvider({ children }: { children: ReactNode })

loadSettings()
}, [])

useEffect(() => {
if (!firstRender.current) {
const activeWidgets = widgetItems
.filter((item) => visibility.includes(item.id))
.map((item) => ({
...item,
order: widgetOrders[item.id] ?? item.order,
}))
setToStorage('activeWidgets', activeWidgets)
saveActiveWidgets()
}
}, [visibility, widgetOrders])

const toggleWidget = (widgetId: WidgetKeys) => {
setVisibility((prev) => {
const isCurrentlyVisible = prev.includes(widgetId)
Expand Down
Loading