diff --git a/src/components/auth/AuthRequiredModal.tsx b/src/components/auth/AuthRequiredModal.tsx
index 495be0f8..fbeced95 100644
--- a/src/components/auth/AuthRequiredModal.tsx
+++ b/src/components/auth/AuthRequiredModal.tsx
@@ -18,7 +18,7 @@ export function AuthRequiredModal({
title = 'ورود به حساب کاربری',
message = 'برای دسترسی به این بخش یا انجام این عملیات، ابتدا وارد حساب کاربری خود شوید.',
loginButtonText = 'ورود به حساب',
- cancelButtonText = 'بعداً',
+ cancelButtonText = 'بعدا',
}: AuthRequiredModalProps) {
function triggerAccountTabDisplay() {
onClose()
diff --git a/src/components/auth/require-auth.tsx b/src/components/auth/require-auth.tsx
index 6165b69e..20b0b95f 100644
--- a/src/components/auth/require-auth.tsx
+++ b/src/components/auth/require-auth.tsx
@@ -1,4 +1,4 @@
-import { Motion as motion } from "@/common/motion";
+import { Motion as motion } from '@/common/motion'
import type { ReactNode } from 'react'
import { callEvent } from '@/common/utils/call-event'
import { useAuth } from '@/context/auth.context'
@@ -44,7 +44,7 @@ export const RequireAuth = ({ children, fallback, mode = 'block' }: RequireAuthP
نیاز ورود به حساب کاربری
- برای دسترسی به این بخش، لطفاً وارد حساب کاربری خود شوید.
+ برای دسترسی به این بخش، لطفا وارد حساب کاربری خود شوید.
نیاز ورود به حساب کاربری
- برای دسترسی به این بخش، لطفاً وارد حساب کاربری خود شوید.
+ برای دسترسی به این بخش، لطفا وارد حساب کاربری خود شوید.
نیاز به تأیید حساب
- برای دسترسی به این بخش، لطفاً حساب کاربری خود را تأیید کنید.
+ برای دسترسی به این بخش، لطفا حساب کاربری خود را تأیید کنید.
@@ -65,7 +65,7 @@ export const RequireVerification = ({
>
نیاز به تأیید حساب
- برای دسترسی به این بخش، لطفاً حساب کاربری خود را تأیید کنید.
+ برای دسترسی به این بخش، لطفا حساب کاربری خود را تأیید کنید.
تأیید حساب
diff --git a/src/components/extension-installed-modal.tsx b/src/components/extension-installed-modal.tsx
index 9af84311..3a38f1b6 100644
--- a/src/components/extension-installed-modal.tsx
+++ b/src/components/extension-installed-modal.tsx
@@ -111,7 +111,7 @@ const StepFirefoxConsent = ({ onGetStarted }: StepFirefoxConsentProps) => {
{' '}
Privacy Notice (حریم خصوصی)
- لطفاً انتخاب کنید چه دادههایی ارسال شوند:
+ لطفا انتخاب کنید چه دادههایی ارسال شوند:
diff --git a/src/components/welcome-wizard.tsx b/src/components/welcome-wizard.tsx
index fb03c628..95333790 100644
--- a/src/components/welcome-wizard.tsx
+++ b/src/components/welcome-wizard.tsx
@@ -91,7 +91,7 @@ export const WelcomeWizard = ({ isOpen, onClose }: WelcomeWizardProps) => {
selectedInterests.length === 0 ||
!selectedReferralSource
) {
- showToast('لطفاً تمام مراحل را تکمیل کنید.', 'error')
+ showToast('لطفا تمام مراحل را تکمیل کنید.', 'error')
return
}
@@ -104,7 +104,7 @@ export const WelcomeWizard = ({ isOpen, onClose }: WelcomeWizardProps) => {
})
)
if (err) {
- showToast('خطا در ثبت اطلاعات. لطفاً دوباره تلاش کنید.', 'error')
+ showToast('خطا در ثبت اطلاعات. لطفا دوباره تلاش کنید.', 'error')
Analytics.event('welcome_wizard_completion_failed')
return
}
@@ -276,7 +276,7 @@ export const WelcomeWizard = ({ isOpen, onClose }: WelcomeWizardProps) => {
مرحله ۴: از کجا شنیدی؟
- لطفاً بگو از کجا با ویجتیفای آشنا شدی.
+ لطفا بگو از کجا با ویجتیفای آشنا شدی.
diff --git a/src/layouts/bookmark/bookmark-grid.tsx b/src/layouts/bookmark/bookmark-grid.tsx
index 16412331..250e9cb2 100644
--- a/src/layouts/bookmark/bookmark-grid.tsx
+++ b/src/layouts/bookmark/bookmark-grid.tsx
@@ -169,7 +169,6 @@ export function BookmarkGrid({
document.removeEventListener('click', handleClickOutside)
}
}, [])
- // todo: padding: 0px 8px;
return (
([])
@@ -143,12 +145,12 @@ export function BookmarksList() {
}
const bookmarkCount = currentFolderItems.length
- const minBookmarks = 10
- const needsFillers = bookmarkCount < minBookmarks
- const fillersCount = needsFillers ? minBookmarks - bookmarkCount : 0
+ const maxBookmarks = 10
+ const needsFillers = bookmarkCount < maxBookmarks
+ const fillersCount = needsFillers ? maxBookmarks - bookmarkCount : 0
const folderItems = [...currentFolderItems, ...new Array(fillersCount).fill(null)]
- if (folderItems.length > minBookmarks) {
+ if (bookmarkCount >= maxBookmarks) {
folderItems.push(null)
}
return folderItems
@@ -206,9 +208,20 @@ export function BookmarksList() {
addBookmark(bookmark, () => setShowAddBookmarkModal(false))
}
parentId={currentFolderId}
+ onOpenImport={() => {
+ setShowAddBookmarkModal(false)
+ setShowImportBookmarksModal(true)
+ }}
/>
)
)}
+ {showImportBookmarksModal && (
+
setShowImportBookmarksModal(false)}
+ parentId={currentFolderId}
+ />
+ )}
>
)
}
diff --git a/src/layouts/bookmark/components/bookmark-suggestions.tsx b/src/layouts/bookmark/components/bookmark-suggestions.tsx
index d56caa27..3958664d 100644
--- a/src/layouts/bookmark/components/bookmark-suggestions.tsx
+++ b/src/layouts/bookmark/components/bookmark-suggestions.tsx
@@ -23,13 +23,13 @@ export function BookmarkSuggestions({ onSelect }: BookmarkSuggestionsProps) {
onSelect(suggestion)}
- className="p-2 flex flex-col items-center gap-y-0.5 text-center transition-colors duration-200 bg-content hover:!bg-base-300/75 border border-base-300/40 rounded-xl cursor-pointer"
+ className="p-1 flex flex-col items-center justify-center text-center transition-colors duration-200 bg-content hover:!bg-base-300/75 h-12 border border-base-300/40 rounded-xl cursor-pointer"
>
-
+
{
const target = e.target as HTMLImageElement
target.style.display = 'none'
diff --git a/src/layouts/bookmark/components/modal/add-bookmark.modal.tsx b/src/layouts/bookmark/components/modal/add-bookmark.modal.tsx
index 5adff506..846303be 100644
--- a/src/layouts/bookmark/components/modal/add-bookmark.modal.tsx
+++ b/src/layouts/bookmark/components/modal/add-bookmark.modal.tsx
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { Button } from '@/components/button/button'
import Modal from '@/components/modal'
import { TextInput } from '@/components/text-input'
+import { Icon } from '@/src/icons'
import type { BookmarkType } from '../../types/bookmark.types'
import { BookmarkSuggestions } from '../bookmark-suggestions'
import { ShowAdvancedButton, TypeSelector } from '../shared'
@@ -14,6 +15,7 @@ interface AddBookmarkModalProps {
onClose: () => void
onAdd: (bookmark: BookmarkCreateFormFields) => void
parentId: string | null
+ onOpenImport?: () => void
}
export interface BookmarkCreateFormFields {
@@ -50,6 +52,7 @@ export function AddBookmarkModal({
onClose,
onAdd,
parentId = null,
+ onOpenImport,
}: AddBookmarkModalProps) {
const [type, setType] = useState
('BOOKMARK')
const [showAdvanced, setShowAdvanced] = useState(false)
@@ -180,7 +183,16 @@ export function AddBookmarkModal({
>
-
+ {onOpenImport && (
+
+
+ درونریزی از بوکمارکهای مرورگر
+
+ )}
لغو
@@ -252,7 +262,7 @@ export function AddBookmarkModal({
isPrimary={true}
loading={isAdding}
className={
- 'btn btn-circle !w-fit px-8 border-none shadow-none text-secondary rounded-xl transition-colors duration-300 ease-in-out'
+ 'btn w-28 border-none shadow-none text-secondary rounded-2xl transition-colors duration-300 ease-in-out'
}
>
ذخیره
diff --git a/src/layouts/bookmark/components/modal/edit-bookmark.modal.tsx b/src/layouts/bookmark/components/modal/edit-bookmark.modal.tsx
index 8a622fa1..603620ff 100644
--- a/src/layouts/bookmark/components/modal/edit-bookmark.modal.tsx
+++ b/src/layouts/bookmark/components/modal/edit-bookmark.modal.tsx
@@ -154,10 +154,10 @@ export function EditBookmarkModal({
size="md"
title={`ویرایش ${type === 'FOLDER' ? 'پوشه' : 'بوکمارک'}`}
direction="rtl"
- className="!overflow-y-hidden"
+ className="overflow-y-hidden!"
closeOnBackdropClick={false}
>
-
+
@@ -189,7 +189,7 @@ export function EditBookmarkModal({
}
/>
-
+
{type === 'BOOKMARK' && (
لغو
@@ -232,9 +230,7 @@ export function EditBookmarkModal({
size="md"
isPrimary={true}
loading={isUpdating}
- className={
- 'btn btn-circle !w-fit px-8 border-none shadow-none text-secondary !rounded-2xl transition-colors duration-300 ease-in-out'
- }
+ className="transition-colors duration-300 ease-in-out border-none shadow-none btn w-28 text-secondary rounded-2xl"
>
ذخیره
diff --git a/src/layouts/bookmark/components/modal/import-browser-bookmarks.modal.tsx b/src/layouts/bookmark/components/modal/import-browser-bookmarks.modal.tsx
new file mode 100644
index 00000000..61ff111f
--- /dev/null
+++ b/src/layouts/bookmark/components/modal/import-browser-bookmarks.modal.tsx
@@ -0,0 +1,373 @@
+import { useEffect, useMemo, useState } from 'react'
+import Analytics from '@/analytics'
+import { showToast } from '@/common/toast'
+import { getFaviconFromUrl } from '@/common/utils/icon'
+import { useGeneralSetting } from '@/context/general-setting.context'
+import { Icon } from '@/src/icons'
+import { Button } from '@/components/button/button'
+import Modal from '@/components/modal'
+import { useBookmarkStore } from '../../context/bookmark.context'
+import { MAX_BROWSER_IMPORT_ITEMS } from '../../constants/browser-import.constant'
+import type { BrowserImportNode } from '../../types/bookmark.types'
+import {
+ type FetchedBrowserBookmark,
+ getBrowserBookmarks,
+} from '../../utils/browser-bookmarks.util'
+
+interface ImportBrowserBookmarksModalProps {
+ isOpen: boolean
+ onClose: () => void
+ parentId: string | null
+ onImported?: () => void
+}
+
+function collectDescendantIds(node: FetchedBrowserBookmark, acc: Set) {
+ acc.add(node.id)
+ node.children?.forEach((child) => collectDescendantIds(child, acc))
+}
+
+function buildImportNodes(
+ nodes: FetchedBrowserBookmark[],
+ selectedIds: Set
+): BrowserImportNode[] {
+ const result: BrowserImportNode[] = []
+
+ for (const node of nodes) {
+ if (selectedIds.has(node.id)) {
+ if (node.type === 'FOLDER') {
+ result.push({
+ title: node.title || 'پوشه بدون عنوان',
+ type: 'FOLDER',
+ url: null,
+ children: node.children
+ ? buildImportNodes(node.children, selectedIds)
+ : [],
+ })
+ } else if (node.url) {
+ result.push({
+ title: node.title || node.url,
+ type: 'BOOKMARK',
+ url: node.url,
+ })
+ }
+ } else if (node.children?.length) {
+ result.push(...buildImportNodes(node.children, selectedIds))
+ }
+ }
+
+ return result
+}
+
+interface TreeNodeProps {
+ node: FetchedBrowserBookmark
+ depth: number
+ selectedIds: Set
+ expandedIds: Set
+ onToggleSelect: (node: FetchedBrowserBookmark) => void
+ onToggleExpand: (id: string) => void
+}
+
+function TreeNode({
+ node,
+ depth,
+ selectedIds,
+ expandedIds,
+ onToggleSelect,
+ onToggleExpand,
+}: TreeNodeProps) {
+ const isFolder = node.type === 'FOLDER'
+ const isExpanded = expandedIds.has(node.id)
+ const isChecked = selectedIds.has(node.id)
+
+ const descendantIds = useMemo(() => {
+ const acc = new Set()
+ if (isFolder) collectDescendantIds(node, acc)
+ acc.delete(node.id)
+ return acc
+ }, [node, isFolder])
+
+ const selectedDescendantsCount = isFolder
+ ? [...descendantIds].filter((id) => selectedIds.has(id)).length
+ : 0
+ const isIndeterminate =
+ isFolder &&
+ !isChecked &&
+ selectedDescendantsCount > 0 &&
+ selectedDescendantsCount < descendantIds.size
+
+ return (
+
+
onToggleSelect(node)}
+ >
+ {isFolder ? (
+
{
+ e.stopPropagation()
+ onToggleExpand(node.id)
+ }}
+ className="flex items-center justify-center w-4 h-4 shrink-0 text-muted"
+ >
+
+
+ ) : (
+
+ )}
+
+
+ {(isChecked || isIndeterminate) && (
+
+ )}
+
+
+ {isFolder ? (
+
+ ) : (
+
+ )}
+
+
+ {node.title || 'بدون عنوان'}
+
+
+
+ {isFolder && isExpanded && node.children && node.children.length > 0 && (
+
+ {node.children.map((child) => (
+
+ ))}
+
+ )}
+
+ )
+}
+
+export function ImportBrowserBookmarksModal({
+ isOpen,
+ onClose,
+ parentId,
+ onImported,
+}: ImportBrowserBookmarksModalProps) {
+ const { browserBookmarksEnabled, setBrowserBookmarksEnabled } = useGeneralSetting()
+ const { importBrowserBookmarks } = useBookmarkStore()
+
+ const [rootNodes, setRootNodes] = useState([])
+ const [selectedIds, setSelectedIds] = useState>(new Set())
+ const [expandedIds, setExpandedIds] = useState>(new Set())
+ const [isLoadingTree, setIsLoadingTree] = useState(false)
+ const [isImporting, setIsImporting] = useState(false)
+
+ useEffect(() => {
+ if (!isOpen) return
+
+ setSelectedIds(new Set())
+ }, [isOpen])
+
+ useEffect(() => {
+ if (!isOpen || !browserBookmarksEnabled) return
+
+ let cancelled = false
+ async function fetchTree() {
+ setIsLoadingTree(true)
+ const tree = await getBrowserBookmarks({ asTree: true })
+ if (cancelled) return
+
+ const initialNodes = tree[0]?.children?.length ? tree[0].children : tree
+ setRootNodes(initialNodes)
+ setExpandedIds(new Set(initialNodes.map((n) => n.id)))
+ setIsLoadingTree(false)
+ }
+ fetchTree()
+
+ return () => {
+ cancelled = true
+ }
+ }, [isOpen, browserBookmarksEnabled])
+
+ const handlePermission = () => {
+ Analytics.event('browser_bookmark_import_permission_clicked')
+ setBrowserBookmarksEnabled(true)
+ }
+
+ const handleToggleExpand = (id: string) => {
+ setExpandedIds((prev) => {
+ const next = new Set(prev)
+ if (next.has(id)) next.delete(id)
+ else next.add(id)
+ return next
+ })
+ }
+
+ const handleToggleSelect = (node: FetchedBrowserBookmark) => {
+ setSelectedIds((prev) => {
+ const ids = new Set()
+ collectDescendantIds(node, ids)
+
+ const shouldSelect = !prev.has(node.id)
+
+ if (shouldSelect) {
+ const newIds = [...ids].filter((id) => !prev.has(id))
+ if (prev.size + newIds.length > MAX_BROWSER_IMPORT_ITEMS) {
+ showToast(
+ `حداکثر ${MAX_BROWSER_IMPORT_ITEMS} مورد در هر بار قابل انتخاب است.`,
+ 'error'
+ )
+ return prev
+ }
+
+ const next = new Set(prev)
+ newIds.forEach((id) => next.add(id))
+ return next
+ }
+
+ const next = new Set(prev)
+ ids.forEach((id) => next.delete(id))
+ return next
+ })
+ }
+
+ const handleImport = async () => {
+ const importNodes = buildImportNodes(rootNodes, selectedIds)
+ if (importNodes.length === 0) {
+ showToast('حداقل یک بوکمارک را انتخاب کنید.', 'error')
+ return
+ }
+
+ setIsImporting(true)
+
+ const result = await importBrowserBookmarks(importNodes, parentId)
+
+ setIsImporting(false)
+
+ if (result && result.importedCount > 0) {
+ showToast(`${result.importedCount} مورد با موفقیت درونریزی شد.`, 'success')
+ onImported?.()
+ onClose()
+ } else if (result) {
+ showToast('موردی برای درونریزی یافت نشد.', 'error')
+ }
+ }
+
+ return (
+
+ {!browserBookmarksEnabled ? (
+
+
+
+
+
دسترسی به بوکمارکها
+
+ برای درونریزی بوکمارکهای مرورگر، نیاز به دسترسی شما داریم.
+
+
+ فعالسازی دسترسی
+
+
+ ) : (
+
+
+
+ {selectedIds.size > 0
+ ? `${selectedIds.size} از ${MAX_BROWSER_IMPORT_ITEMS} مورد انتخاب شده`
+ : `حداکثر ${MAX_BROWSER_IMPORT_ITEMS} مورد قابل انتخاب است`}
+
+
+
+
+ {isLoadingTree ? (
+
+
+
+ ) : rootNodes.length > 0 ? (
+ rootNodes.map((node) => (
+
+ ))
+ ) : (
+
+ بوکمارکی در مرورگر شما یافت نشد.
+
+ )}
+
+
+
+
+ انصراف
+
+ در حال درونریزی...
+ }
+ className="w-32 transition-colors duration-300 ease-in-out border-none shadow-none btn btn-circle text-secondary rounded-2xl"
+ >
+ درونریزی
+
+
+
+ )}
+
+ )
+}
diff --git a/src/layouts/bookmark/constants/browser-import.constant.ts b/src/layouts/bookmark/constants/browser-import.constant.ts
new file mode 100644
index 00000000..891f9113
--- /dev/null
+++ b/src/layouts/bookmark/constants/browser-import.constant.ts
@@ -0,0 +1 @@
+export const MAX_BROWSER_IMPORT_ITEMS = 50
diff --git a/src/layouts/bookmark/context/bookmark.context.tsx b/src/layouts/bookmark/context/bookmark.context.tsx
index 1232faf2..76ef2a76 100644
--- a/src/layouts/bookmark/context/bookmark.context.tsx
+++ b/src/layouts/bookmark/context/bookmark.context.tsx
@@ -3,12 +3,14 @@ import React, { createContext, useEffect, useState } from 'react'
import Analytics from '@/analytics'
import { getFromStorage, setToStorage } from '@/common/storage'
import { callEvent, listenEvent } from '@/common/utils/call-event'
-import type { Bookmark } from '@/layouts/bookmark/types/bookmark.types'
+import type { Bookmark, BrowserImportNode } from '@/layouts/bookmark/types/bookmark.types'
import { safeAwait } from '@/services/api'
import { useRemoveBookmark } from '@/services/hooks/bookmark/remove-bookmark.hook'
import { translateError } from '@/utils/translate-error'
import { useAuth } from '@/context/auth.context'
import { useAddBookmark } from '@/services/hooks/bookmark/add-bookmark.hook'
+import { useImportBrowserBookmarks } from '@/services/hooks/bookmark/import-browser-bookmarks.hook'
+import type { BulkImportBookmarkNode } from '@/services/hooks/bookmark/import-browser-bookmarks.hook'
import type { AxiosError } from 'axios'
import type { BookmarkCreateFormFields } from '../components/modal/add-bookmark.modal'
import type { BookmarkUpdateFormFields } from '../components/modal/edit-bookmark.modal'
@@ -26,6 +28,10 @@ export interface BookmarkStoreContext {
setBookmarks: (bookmarks: Bookmark[]) => void
getCurrentFolderItems: (parentId: string | null) => Bookmark[]
addBookmark: (bookmark: BookmarkCreateFormFields, cb: () => void) => Promise
+ importBrowserBookmarks: (
+ nodes: BrowserImportNode[],
+ parentId: string | null
+ ) => Promise<{ importedCount: number; createdFolders: number } | null>
editBookmark: (bookmark: BookmarkUpdateFormFields, cb: () => void) => void
deleteBookmark: (id: string, cb: () => void) => void
currentFolderId: string | null
@@ -37,6 +43,7 @@ const bookmarkContext = createContext({
setBookmarks: () => {},
getCurrentFolderItems: () => [],
addBookmark: async () => {},
+ importBrowserBookmarks: async () => null,
editBookmark: () => {},
deleteBookmark: () => {},
currentFolderId: null,
@@ -53,6 +60,7 @@ export const BookmarkProvider: React.FC<{ children: React.ReactNode }> = ({
const { mutateAsync: removeBookmarkAsync } = useRemoveBookmark()
const { mutateAsync: addBookmarkAsync } = useAddBookmark()
+ const { mutateAsync: importBrowserBookmarksAsync } = useImportBrowserBookmarks()
const { mutateAsync: updateBookmarkAsync } = useUpdateBookmark()
useEffect(() => {
@@ -215,6 +223,62 @@ export const BookmarkProvider: React.FC<{ children: React.ReactNode }> = ({
}
}
+ const importBrowserBookmarks = async (
+ nodes: BrowserImportNode[],
+ parentId: string | null
+ ): Promise<{ importedCount: number; createdFolders: number } | null> => {
+ if (!isAuthenticated) {
+ showToast('برای درونریزی بوکمارکها باید وارد شوید.', 'error')
+ return null
+ }
+
+ let resolvedParentId = parentId
+ if (parentId && validate(parentId)) {
+ const parentBookmark = bookmarks?.find(
+ (b) => b.id === parentId || b.onlineId === parentId
+ )
+ if (parentBookmark?.onlineId) {
+ resolvedParentId = parentBookmark.onlineId
+ }
+ }
+
+ if (resolvedParentId && validate(resolvedParentId)) {
+ showToast(
+ 'برای درونریزی در این پوشه، لطفا ابتدا بوکمارکهای خود را همگامسازی کنید.',
+ 'error',
+ { duration: 8000 }
+ )
+ return null
+ }
+
+ const toApiNode = (node: BrowserImportNode): BulkImportBookmarkNode => ({
+ title: node.title,
+ type: node.type,
+ url: node.type === 'BOOKMARK' ? node.url : null,
+ children: node.children?.map(toApiNode),
+ })
+
+ const [err, result] = await safeAwait<
+ AxiosError,
+ { importedCount: number; createdFolders: number }
+ >(
+ importBrowserBookmarksAsync({
+ parentId: resolvedParentId,
+ items: nodes.map(toApiNode),
+ })
+ )
+
+ if (err || !result) {
+ autoFormatErrorToast(err)
+ return null
+ }
+
+ await refetch()
+ Analytics.event('import_browser_bookmarks')
+
+ return result
+ }
+
const editBookmark = async (input: BookmarkUpdateFormFields, cb: () => void) => {
if (!isAuthenticated) return
@@ -311,6 +375,7 @@ export const BookmarkProvider: React.FC<{ children: React.ReactNode }> = ({
setBookmarks,
getCurrentFolderItems,
addBookmark: addBookmark as any,
+ importBrowserBookmarks,
editBookmark,
deleteBookmark,
currentFolderId,
diff --git a/src/layouts/bookmark/types/bookmark.types.ts b/src/layouts/bookmark/types/bookmark.types.ts
index 10773388..082116b4 100644
--- a/src/layouts/bookmark/types/bookmark.types.ts
+++ b/src/layouts/bookmark/types/bookmark.types.ts
@@ -23,3 +23,10 @@ export interface FolderPathItem {
id: string
title: string
}
+
+export interface BrowserImportNode {
+ title: string
+ type: BookmarkType
+ url: string | null
+ children?: BrowserImportNode[]
+}
diff --git a/src/layouts/friends/components/activity-card/manage-activity.bottomSheet.tsx b/src/layouts/friends/components/activity-card/manage-activity.bottomSheet.tsx
index e63fdac1..3d1ebfce 100644
--- a/src/layouts/friends/components/activity-card/manage-activity.bottomSheet.tsx
+++ b/src/layouts/friends/components/activity-card/manage-activity.bottomSheet.tsx
@@ -50,7 +50,7 @@ export function ManageActivityBottomSheet({
const handleSave = async () => {
if (!activity.trim()) {
- showToast('لطفاً متنی وارد کنید', 'error')
+ showToast('لطفا متنی وارد کنید', 'error')
return
}
@@ -277,7 +277,7 @@ export function ManageActivityBottomSheet({
در صورت گزارش محتوای نامناسب، حساب کاربری شما ممکن است محدود شود.
- لطفاً با رعایت قوانین به ما در حفظ فضای امن و دوستانه کمک کنید.
+ لطفا با رعایت قوانین به ما در حفظ فضای امن و دوستانه کمک کنید.
در هر روز فقط یک بار امکان انتشار نوشته جدید دارید.
diff --git a/src/layouts/navbar/friends-list/friends.navbar.tsx b/src/layouts/navbar/friends-list/friends.navbar.tsx
index bf35879d..28512feb 100644
--- a/src/layouts/navbar/friends-list/friends.navbar.tsx
+++ b/src/layouts/navbar/friends-list/friends.navbar.tsx
@@ -94,7 +94,7 @@ export function FriendsListNavbar() {
title="ورود به حساب کاربری"
message="برای دسترسی به بخش مدیریت دوستان، ابتدا وارد حساب کاربری خود شوید."
loginButtonText="ورود به حساب"
- cancelButtonText="بعداً"
+ cancelButtonText="بعدا"
/>
)}
>
diff --git a/src/layouts/search/image/image-search.portal.tsx b/src/layouts/search/image/image-search.portal.tsx
index 3db346d2..febf59f3 100644
--- a/src/layouts/search/image/image-search.portal.tsx
+++ b/src/layouts/search/image/image-search.portal.tsx
@@ -27,7 +27,7 @@ export function ImageSearchPortal({
const handleUpload = async (file: File) => {
if (!file.type.startsWith('image/')) {
- showToast('لطفاً فقط فایل تصویری انتخاب کنید', 'error')
+ showToast('لطفا فقط فایل تصویری انتخاب کنید', 'error')
return
}
diff --git a/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx b/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx
index 636e8793..92031516 100644
--- a/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx
+++ b/src/layouts/setting/tabs/account/auth-form/auth-otp.tsx
@@ -47,7 +47,7 @@ const AuthOtp: React.FC
= ({ step, setStep }) => {
if (isEmpty(email))
return setError((prev) => ({
...prev,
- email: 'لطفاً ایمیل/شماره موبایل خود را وارد کنید.',
+ email: 'لطفا ایمیل/شماره موبایل خود را وارد کنید.',
}))
onSendOtp()
@@ -102,7 +102,7 @@ const AuthOtp: React.FC = ({ step, setStep }) => {
if (err.response?.data?.message === 'INVALID_OTP_CODE') {
setError({
email: null,
- otp: 'کد تایید نامعتبر است. لطفاً دوباره تلاش کنید.',
+ otp: 'کد تایید نامعتبر است. لطفا دوباره تلاش کنید.',
api: null,
})
} else setError({ email: null, otp: null, api: content })
@@ -111,7 +111,7 @@ const AuthOtp: React.FC = ({ step, setStep }) => {
setError({
email: null,
otp: null,
- api: 'کد تایید نامعتبر است. لطفاً دوباره تلاش کنید.',
+ api: 'کد تایید نامعتبر است. لطفا دوباره تلاش کنید.',
})
return
}
diff --git a/src/layouts/setting/tabs/account/auth-form/auth-password.tsx b/src/layouts/setting/tabs/account/auth-form/auth-password.tsx
index 3f95ee73..aa563d2c 100644
--- a/src/layouts/setting/tabs/account/auth-form/auth-password.tsx
+++ b/src/layouts/setting/tabs/account/auth-form/auth-password.tsx
@@ -30,13 +30,13 @@ export default function AuthPassword() {
if (isEmpty(email))
return setError((prev) => ({
...prev,
- email: 'لطفاً ایمیل خود را وارد کنید.',
+ email: 'لطفا ایمیل خود را وارد کنید.',
}))
if (!isEmail(email))
return setError((prev) => ({
...prev,
- email: 'لطفاً یک ایمیل معتبر وارد کنید.',
+ email: 'لطفا یک ایمیل معتبر وارد کنید.',
}))
// Password validation
diff --git a/src/layouts/setting/tabs/account/components/account-verification-status.tsx b/src/layouts/setting/tabs/account/components/account-verification-status.tsx
index 5cf3e8c3..3298f303 100644
--- a/src/layouts/setting/tabs/account/components/account-verification-status.tsx
+++ b/src/layouts/setting/tabs/account/components/account-verification-status.tsx
@@ -23,7 +23,7 @@ export const AccountVerificationStatus = ({
⚠️ حساب شما تایید نشده است
- لطفاً ایمیل خود را بررسی کنید یا ایمیل جدید درخواست کنید.
+ لطفا ایمیل خود را بررسی کنید یا ایمیل جدید درخواست کنید.
diff --git a/src/layouts/setting/tabs/account/components/modals/add-email.modal.tsx b/src/layouts/setting/tabs/account/components/modals/add-email.modal.tsx
index 50961f4b..2e9b6163 100644
--- a/src/layouts/setting/tabs/account/components/modals/add-email.modal.tsx
+++ b/src/layouts/setting/tabs/account/components/modals/add-email.modal.tsx
@@ -45,7 +45,7 @@ export function AddEmailModal(prop: AddPhoneProp) {
if (isEmpty(email))
return setError((prev) => ({
...prev,
- email: 'لطفاً ایمیل خود را وارد کنید.',
+ email: 'لطفا ایمیل خود را وارد کنید.',
}))
const [err, _] = await safeAwait(requestChange(email))
if (err) {
diff --git a/src/layouts/setting/tabs/account/components/modals/add-phone.modal.tsx b/src/layouts/setting/tabs/account/components/modals/add-phone.modal.tsx
index 58415e77..a8491df1 100644
--- a/src/layouts/setting/tabs/account/components/modals/add-phone.modal.tsx
+++ b/src/layouts/setting/tabs/account/components/modals/add-phone.modal.tsx
@@ -44,7 +44,7 @@ export function AddPhoneModal(prop: AddPhoneProp) {
if (isEmpty(phone))
return setError((prev) => ({
...prev,
- email: 'لطفاً ایمیل/شماره موبایل خود را وارد کنید.',
+ email: 'لطفا ایمیل/شماره موبایل خود را وارد کنید.',
}))
const [err, _] = await safeAwait(requestChange(phone))
if (err) {
diff --git a/src/layouts/setting/tabs/account/tabs/user-profile/user-profile.tsx b/src/layouts/setting/tabs/account/tabs/user-profile/user-profile.tsx
index ff9607bf..72aea54b 100644
--- a/src/layouts/setting/tabs/account/tabs/user-profile/user-profile.tsx
+++ b/src/layouts/setting/tabs/account/tabs/user-profile/user-profile.tsx
@@ -54,7 +54,7 @@ export const UserProfile = () => {
return 'نیاز به ورود مجدد به حساب کاربری دارید.'
}
- return 'خطا در بارگذاری پروفایل کاربری. لطفاً دوباره تلاش کنید.'
+ return 'خطا در بارگذاری پروفایل کاربری. لطفا دوباره تلاش کنید.'
}
if (isLoading) {
diff --git a/src/layouts/setting/tabs/general/components/select-city.tsx b/src/layouts/setting/tabs/general/components/select-city.tsx
index cddea537..f6a6426f 100644
--- a/src/layouts/setting/tabs/general/components/select-city.tsx
+++ b/src/layouts/setting/tabs/general/components/select-city.tsx
@@ -108,7 +108,7 @@ export function SelectCity({ size }: Prop) {
خطا در دریافت اطلاعات
- لطفا اتصال اینترنت خود را بررسی کرده و مجدداً تلاش کنید.
+ لطفا اتصال اینترنت خود را بررسی کرده و مجددا تلاش کنید.
)}
@@ -117,7 +117,7 @@ export function SelectCity({ size }: Prop) {
setShowAuthModal(!showAuthModal)}
- message="برای انتخاب شهر، لطفاً وارد حساب کاربری خود شوید."
+ message="برای انتخاب شهر، لطفا وارد حساب کاربری خود شوید."
/>
)}
با فعالسازی این گزینه، افزونه فقط برای نمایش بوکمارکهای
مرورگر شما دسترسی خواهد داشت. هیچ اطلاعاتی ذخیره یا ارسال
- نمیشود و بوکمارکها صرفاً در محیط افزونه نمایش داده میشوند.
+ نمیشود و بوکمارکها صرفا در محیط افزونه نمایش داده میشوند.
diff --git a/src/layouts/widgets/calendar/components/google-calendar/google-calendar-view.tsx b/src/layouts/widgets/calendar/components/google-calendar/google-calendar-view.tsx
index 350b4440..7b286494 100644
--- a/src/layouts/widgets/calendar/components/google-calendar/google-calendar-view.tsx
+++ b/src/layouts/widgets/calendar/components/google-calendar/google-calendar-view.tsx
@@ -91,7 +91,7 @@ export const GoogleCalendarView: React.FC = () => {
{isAuthenticated
? 'برای مشاهده برنامهها، تقویم گوگل را متصل کنید.'
- : 'لطفاً وارد حساب کاربری خود شوید.'}
+ : 'لطفا وارد حساب کاربری خود شوید.'}
واحد اندازهگیری
-
+
نحوه رسیدن به هدف
-
+
{
)
if (feedExists) {
- setError(`فید "${suggestedFeed.name}" قبلاً اضافه شده است`)
+ setError(`فید "${suggestedFeed.name}" قبلا اضافه شده است`)
return
}
diff --git a/src/layouts/widgets/tools/pomodoro/components/requestNotification-modal.tsx b/src/layouts/widgets/tools/pomodoro/components/requestNotification-modal.tsx
index 89765ad1..e6e52ad9 100644
--- a/src/layouts/widgets/tools/pomodoro/components/requestNotification-modal.tsx
+++ b/src/layouts/widgets/tools/pomodoro/components/requestNotification-modal.tsx
@@ -86,7 +86,7 @@ export function RequestNotificationModal({
className="flex-1 px-4 py-2 text-sm font-medium transition-colors border rounded-2xl border-content text-content"
size="md"
>
- فعلاً نه
+ فعلا نه
{
+ return useMutation({
+ mutationKey: ['importBrowserBookmarks'],
+ mutationFn: async (
+ input: BulkImportBookmarksPayload
+ ): Promise => {
+ return await ImportBrowserBookmarksApi(input)
+ },
+ })
+}
+
+export async function ImportBrowserBookmarksApi(
+ input: BulkImportBookmarksPayload
+): Promise {
+ const client = getMainClient()
+
+ const { data } = await client.post<{ data: BulkImportBookmarksResult }>(
+ '/bookmarks/import',
+ {
+ parentId: input.parentId || undefined,
+ items: input.items,
+ }
+ )
+
+ return data.data
+}
diff --git a/src/utils/translate-error.ts b/src/utils/translate-error.ts
index d74d3a63..bf3e176c 100644
--- a/src/utils/translate-error.ts
+++ b/src/utils/translate-error.ts
@@ -14,7 +14,7 @@ const errorTranslations: Record = {
// Password reset errors
FORGOT_PASSWORD_REQUEST_LIMIT:
- 'تعداد درخواستهای بازیابی رمز عبور به حد مجاز رسیده است. لطفاً بعداً تلاش کنید',
+ 'تعداد درخواستهای بازیابی رمز عبور به حد مجاز رسیده است. لطفا بعدا تلاش کنید',
RESET_TOKEN_EXPIRED:
'لینک بازیابی رمز عبور منقضی شده است. لطفا درخواست جدید ارسال کنید',
INVALID_RESET_TOKEN: 'لینک بازیابی رمز عبور نامعتبر است',
@@ -29,22 +29,22 @@ const errorTranslations: Record = {
// HTTP status errors
INTERNAL_SERVER_ERROR: 'خطای داخلی سرور رخ داده است',
SERVICE_UNAVAILABLE: 'سرویس در حال حاضر در دسترس نیست',
- TOO_MANY_REQUESTS: 'تعداد درخواستها بیش از حد مجاز است. لطفاً کمی صبر کنید',
+ TOO_MANY_REQUESTS: 'تعداد درخواستها بیش از حد مجاز است. لطفا کمی صبر کنید',
BAD_REQUEST: 'درخواست نامعتبر است',
NOT_FOUND: 'منبع درخواستی یافت نشد',
ACTIVITY_UPDATE_RATE_LIMIT_EXCEEDED:
- 'تعداد درخواستها برای بروزرسانی "وضعیت" بیش از حد مجاز است. لطفاً کمی صبر کنید',
+ 'تعداد درخواستها برای بروزرسانی "وضعیت" بیش از حد مجاز است. لطفا کمی صبر کنید',
// Friend-related errors
CANT_REQUEST_YOURSELF: 'نمیتوانید به خودتان درخواست دوستی ارسال کنید',
- FRIEND_REQUEST_ALREADY_SENT: 'درخواست دوستی قبلاً ارسال شده است',
- FRIEND_REQUEST_ALREADY_EXISTS: 'درخواست دوستی قبلاً وجود دارد',
+ FRIEND_REQUEST_ALREADY_SENT: 'درخواست دوستی قبلا ارسال شده است',
+ FRIEND_REQUEST_ALREADY_EXISTS: 'درخواست دوستی قبلا وجود دارد',
FAILED_TO_FETCH_FRIENDS: 'خطا در دریافت لیست دوستان',
FAILED_TO_SEND_REQUEST: 'خطا در ارسال درخواست دوستی',
FAILED_TO_ACCEPT_REQUEST: 'خطا در پذیرش درخواست دوستی',
FAILED_TO_REMOVE_FRIEND: 'خطا در حذف دوست',
FRIEND_REQUEST_SENT: 'درخواست دوستی ارسال شد',
FRIEND_REQUEST_NOT_FOUND: 'درخواست دوستی یافت نشد',
- SET_USERNAME_FIRST: 'لطفاً ابتدا نام کاربری خود را تنظیم کنید',
+ SET_USERNAME_FIRST: 'لطفا ابتدا نام کاربری خود را تنظیم کنید',
// Translate-related errors
SOURCE_AND_TARGET_LANG_MUST_BE_DIFFERENT: 'زبان مبدأ و مقصد نمیتوانند یکسان باشند',
@@ -60,21 +60,21 @@ const errorTranslations: Record = {
// Widget-related errors
WIDGET_NOT_FOUND: 'ویجت مورد نظر یافت نشد',
- WIDGET_ALREADY_EXISTS: 'این ویجت قبلاً اضافه شده است',
+ WIDGET_ALREADY_EXISTS: 'این ویجت قبلا اضافه شده است',
INVALID_WIDGET_POSITION: 'موقعیت ویجت نامعتبر است',
MAX_WIDGETS_REACHED: 'به حداکثر تعداد مجاز ویجت رسیدهاید',
// Network errors
NETWORK_ERROR: 'خطای شبکه. لطفا اتصال اینترنت خود را بررسی کنید',
- CONNECTION_TIMEOUT: 'زمان اتصال به پایان رسید. لطفا مجدداً تلاش کنید',
- CONNECTION_REFUSED: 'اتصال رد شد. لطفا بعداً تلاش کنید',
+ CONNECTION_TIMEOUT: 'زمان اتصال به پایان رسید. لطفا مجددا تلاش کنید',
+ CONNECTION_REFUSED: 'اتصال رد شد. لطفا بعدا تلاش کنید',
FIRST_VERIFY_YOUR_ACCOUNT: 'اول باید حساب کاربری رو تایید کنی!',
USERNAME_ALREADY_EXISTS: 'این نام کاربری قبلا وجود داشته است',
INVALID_FILE_TYPE: 'نوع فایل نامعتبر است',
NOT_ENOUGH_COINS: 'ویجکوینهات کافی نیست 😕',
INVALID_REFERRAL_CODE: 'کد دعوت نامعتبر است',
- ITEM_ALREADY_EXISTS: 'این رو قبلاً با ویجکوین گرفتی — نیازی به خرید دوباره نیست.',
+ ITEM_ALREADY_EXISTS: 'این رو قبلا با ویجکوین گرفتی — نیازی به خرید دوباره نیست.',
INVALID_ID: 'آیدی نامعتبر است',
@@ -110,6 +110,10 @@ const errorTranslations: Record = {
TRY_NEXT_TIME: 'چیزی اشتباه پیش رفت، لطفا بعدا دوباره تلاش کنید',
TOO_MANY_ATTEMPTS_HABIT: 'بیش از حد نمیتونید بسازید.',
+
+ FOLDER_STRUCTURE_TOO_DEEP: 'FOLDER_STRUCTURE_TOO_DEEP',
+ BULK_IMPORT_LIMIT_EXCEEDED: `تعداد آیتم درونریزی بیشاز حد هست`,
+ NO_VALID_ITEMS_TO_IMPORT: 'هیچ آیتم معتبری برای درونریزی وجود نداره',
}
const validationTranslations: Record = {