Bladeren bron

feat: update dialog texts to persian

Mohammad Mahdi Salimi 1 maand geleden
bovenliggende
commit
a20c579d64

+ 6 - 5
src/components/command-menu.tsx

@@ -30,10 +30,10 @@ export function CommandMenu() {
 
   return (
     <CommandDialog modal open={open} onOpenChange={setOpen}>
-      <CommandInput placeholder='Type a command or search...' />
+      <CommandInput placeholder='یک دستور تایپ کنید یا جستجو کنید...' />
       <CommandList>
         <ScrollArea type='hover' className='h-72 pe-1'>
-          <CommandEmpty>No results found.</CommandEmpty>
+          <CommandEmpty>هیچ نتیجه ای یافت نشد.</CommandEmpty>
           {sidebarData.navGroups.map((group) => (
             <CommandGroup key={group.title} heading={group.title}>
               {group.items.map((navItem, i) => {
@@ -73,15 +73,16 @@ export function CommandMenu() {
           <CommandSeparator />
           <CommandGroup heading='Theme'>
             <CommandItem onSelect={() => runCommand(() => setTheme('light'))}>
-              <Sun /> <span>Light</span>
+              <Sun />
+              <span>روشن</span>
             </CommandItem>
             <CommandItem onSelect={() => runCommand(() => setTheme('dark'))}>
               <Moon className='scale-90' />
-              <span>Dark</span>
+              <span>دارک</span>
             </CommandItem>
             <CommandItem onSelect={() => runCommand(() => setTheme('system'))}>
               <Laptop />
-              <span>System</span>
+              <span>سیستم</span>
             </CommandItem>
           </CommandGroup>
         </ScrollArea>

+ 4 - 9
src/components/layout/app-sidebar.tsx

@@ -2,35 +2,30 @@ import { useLayout } from '@/context/layout-provider'
 import {
   Sidebar,
   SidebarContent,
-  SidebarFooter,
   SidebarHeader,
   SidebarRail,
 } from '@/components/ui/sidebar'
-// import { AppTitle } from './app-title'
+import { AppTitle } from './app-title'
 import { sidebarData } from './data/sidebar-data'
 import { NavGroup } from './nav-group'
-import { NavUser } from './nav-user'
-import { TeamSwitcher } from './team-switcher'
 
 export function AppSidebar() {
   const { collapsible, variant } = useLayout()
   return (
     <Sidebar collapsible={collapsible} variant={variant}>
       <SidebarHeader>
-        <TeamSwitcher teams={sidebarData.teams} />
+        {/* <TeamSwitcher teams={sidebarData.teams} /> */}
 
         {/* Replace <TeamSwitch /> with the following <AppTitle />
          /* if you want to use the normal app title instead of TeamSwitch dropdown */}
-        {/* <AppTitle /> */}
+        <AppTitle />
       </SidebarHeader>
       <SidebarContent>
         {sidebarData.navGroups.map((props) => (
           <NavGroup key={props.title} {...props} />
         ))}
       </SidebarContent>
-      <SidebarFooter>
-        <NavUser user={sidebarData.user} />
-      </SidebarFooter>
+      {/* <SidebarFooter><NavUser user={sidebarData.user} /> </SidebarFooter> */}
       <SidebarRail />
     </Sidebar>
   )

+ 2 - 2
src/components/layout/app-title.tsx

@@ -25,8 +25,8 @@ export function AppTitle() {
               onClick={() => setOpenMobile(false)}
               className='grid flex-1 text-start text-sm leading-tight'
             >
-              <span className='truncate font-bold'>Shadcn-Admin</span>
-              <span className='truncate text-xs'>Vite + ShadcnUI</span>
+              <span className='truncate font-bold'>تلنت‌یار</span>
+              <span className='truncate text-xs'>ادمین پنل</span>
             </Link>
             <ToggleSidebar />
           </div>

+ 17 - 41
src/components/layout/data/sidebar-data.ts

@@ -3,12 +3,10 @@ import {
   LayoutDashboard,
   Monitor,
   Bug,
-  ListTodo,
   FileX,
   HelpCircle,
   Lock,
   Bell,
-  Package,
   Palette,
   ServerOff,
   Settings,
@@ -18,66 +16,44 @@ import {
   Users,
   MessagesSquare,
   ShieldCheck,
-  AudioWaveform,
-  Command,
-  GalleryVerticalEnd,
+  Play,
 } from 'lucide-react'
-// import { ClerkLogo } from '@/assets/clerk-logo'
 import { type SidebarData } from '../types'
+import Logo from '@/assets/logo.svg'
 
 export const sidebarData: SidebarData = {
-  user: {
-    name: 'satnaing',
-    email: 'satnaingdev@gmail.com',
-    avatar: '/avatars/shadcn.jpg',
-  },
   teams: [
     {
-      name: 'Shadcn Admin',
-      logo: Command,
-      plan: 'Vite + ShadcnUI',
-    },
-    {
-      name: 'Acme Inc',
-      logo: GalleryVerticalEnd,
-      plan: 'Enterprise',
-    },
-    {
-      name: 'Acme Corp.',
-      logo: AudioWaveform,
-      plan: 'Startup',
+      name: 'تلنت‌یار',
+      logo: Logo,
+      plan: 'ادمین پنل',
     },
   ],
   navGroups: [
     {
-      title: 'General',
+      title: 'عمومی',
       items: [
         {
-          title: 'Dashboard',
+          title: 'داشبورد',
           url: '/',
           icon: LayoutDashboard,
         },
         {
-          title: 'Tasks',
-          url: '/tasks',
-          icon: ListTodo,
+          title: 'کاربران',
+          url: '/users',
+          icon: Users,
         },
         {
-          title: 'Apps',
-          url: '/apps',
-          icon: Package,
+          title: 'پست ها',
+          url: '/tasks',
+          icon: Play,
         },
         {
-          title: 'Chats',
+          title: 'کامنت ها',
           url: '/chats',
           badge: '3',
           icon: MessagesSquare,
         },
-        {
-          title: 'Users',
-          url: '/users',
-          icon: Users,
-        },
         // {
         //   title: 'Secured by Clerk',
         //   icon: ClerkLogo,
@@ -99,7 +75,7 @@ export const sidebarData: SidebarData = {
       ],
     },
     {
-      title: 'Pages',
+      title: 'صفحات',
       items: [
         {
           title: 'Auth',
@@ -128,7 +104,7 @@ export const sidebarData: SidebarData = {
           ],
         },
         {
-          title: 'Errors',
+          title: 'خطاها',
           icon: Bug,
           items: [
             {
@@ -161,7 +137,7 @@ export const sidebarData: SidebarData = {
       ],
     },
     {
-      title: 'Other',
+      title: 'دیگر',
       items: [
         {
           title: 'Settings',

+ 7 - 7
src/components/layout/types.ts

@@ -1,14 +1,14 @@
 import { type LinkProps } from '@tanstack/react-router'
 
-type User = {
-  name: string
-  email: string
-  avatar: string
-}
+// type User = {
+//   name: string
+//   email: string
+//   avatar: string
+// }
 
 type Team = {
   name: string
-  logo: React.ElementType
+  logo: string
   plan: string
 }
 
@@ -36,7 +36,7 @@ type NavGroup = {
 }
 
 type SidebarData = {
-  user: User
+  // user: User
   teams: Team[]
   navGroups: NavGroup[]
 }

+ 1 - 1
src/components/search.tsx

@@ -4,8 +4,8 @@ import { useSearch } from '@/context/search-provider'
 import { Button } from './ui/button'
 
 export function Search({
+  placeholder = 'جستجو',
   className = '',
-  placeholder = 'Search',
   ...props
 }: React.ComponentProps<'button'> & { placeholder?: string }) {
   const { setOpen } = useSearch()

+ 3 - 3
src/components/theme-switch.tsx

@@ -32,21 +32,21 @@ export function ThemeSwitch() {
       </DropdownMenuTrigger>
       <DropdownMenuContent align='end'>
         <DropdownMenuItem onClick={() => setTheme('light')}>
-          Light{' '}
+          روشن{' '}
           <Check
             size={14}
             className={cn('ms-auto', theme !== 'light' && 'hidden')}
           />
         </DropdownMenuItem>
         <DropdownMenuItem onClick={() => setTheme('dark')}>
-          Dark
+          دارک
           <Check
             size={14}
             className={cn('ms-auto', theme !== 'dark' && 'hidden')}
           />
         </DropdownMenuItem>
         <DropdownMenuItem onClick={() => setTheme('system')}>
-          System
+          سیستم
           <Check
             size={14}
             className={cn('ms-auto', theme !== 'system' && 'hidden')}

+ 10 - 4
src/lib/axios.ts → src/core/api/axios.ts

@@ -1,18 +1,23 @@
+import { useAuthStore } from '@/stores/auth-store'
 import axios from 'axios'
 
 const api = axios.create({
   baseURL:
-    import.meta.env.VITE_API_BASE_URL || 'https://api.talentyar.com/api/v1',
+    import.meta.env.VITE_API_BASE_URL || 'https://api.talentyar.net/api/v1',
+  // withCredentials: true,
   headers: {
     'Content-Type': 'application/json',
+    Accept: 'application/json',
   },
 })
 
 api.interceptors.request.use((config) => {
-  const token = localStorage.getItem('auth_token')
+  const token = useAuthStore.getState().token
+
   if (token) {
     config.headers.Authorization = `Bearer ${token}`
   }
+
   return config
 })
 
@@ -20,9 +25,10 @@ api.interceptors.response.use(
   (response) => response,
   (error) => {
     if (error.response?.status === 401) {
-      localStorage.removeItem('auth_token')
-      window.location.href = '/sign-in'
+      const logout = useAuthStore.getState().logout
+      logout()
     }
+
     return Promise.reject(error)
   }
 )

+ 16 - 0
src/core/api/http.ts

@@ -0,0 +1,16 @@
+import type { AxiosRequestConfig } from 'axios'
+import api from './axios'
+
+export const http = {
+  get: <T>(url: string, config?: AxiosRequestConfig) =>
+    api.get<T>(url, config).then((res) => res.data),
+
+  post: <T, B = unknown>(url: string, body?: B, config?: AxiosRequestConfig) =>
+    api.post<T>(url, body, config).then((res) => res.data),
+
+  put: <T, B = unknown>(url: string, body?: B, config?: AxiosRequestConfig) =>
+    api.put<T>(url, body, config).then((res) => res.data),
+
+  delete: <T>(url: string, config?: AxiosRequestConfig) =>
+    api.delete<T>(url, config).then((res) => res.data),
+}

+ 6 - 0
src/core/api/types.ts

@@ -0,0 +1,6 @@
+export type ApiResponse<T> = {
+  success: boolean;
+  message: string;
+  data: T | null;
+  errors?: Record<string, string[]>;
+};

+ 10 - 0
src/core/react-query/keys.ts

@@ -0,0 +1,10 @@
+export const queryKeys = {
+  roles: {
+    all: ['roles'] as const,
+  },
+
+  users: {
+    all: ['users'] as const,
+    // search: (username: string) => ['users', 'search', username] as const,
+  },
+}

+ 1 - 1
src/features/auth/sign-in/components/user-auth-form.tsx

@@ -57,7 +57,7 @@ export function UserAuthForm({
     try {
       await login(data)
 
-      toast.success('ورود با موفقیت انجام شد 🎉')
+      toast.success('ورود با موفقیت انجام شد')
       navigate({ to: redirectTo || '/', replace: true })
     } catch {
       toast.error('نام کاربری یا رمز عبور اشتباه است')

+ 4 - 0
src/features/users/api/getUsers.ts

@@ -0,0 +1,4 @@
+import { http } from '@/core/api/http'
+import { type GetUserResponse } from '../types'
+
+export const getUsers = () => http.get<GetUserResponse>('/admin/users')

+ 3 - 3
src/features/users/components/data-table-row-actions.tsx

@@ -38,7 +38,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
               setOpen('edit')
             }}
           >
-            Edit
+            ویرایش
             <DropdownMenuShortcut>
               <UserPen size={16} />
             </DropdownMenuShortcut>
@@ -51,9 +51,9 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
             }}
             className='text-red-500!'
           >
-            Delete
+            حذف
             <DropdownMenuShortcut>
-              <Trash2 size={16} />
+              <Trash2 size={16} color='red' />
             </DropdownMenuShortcut>
           </DropdownMenuItem>
         </DropdownMenuContent>

+ 4 - 4
src/features/users/components/users-columns.tsx

@@ -58,8 +58,8 @@ export const usersColumns: ColumnDef<User>[] = [
       <DataTableColumnHeader column={column} title='نام' />
     ),
     cell: ({ row }) => {
-      const { firstName, lastName } = row.original
-      const fullName = `${firstName} ${lastName}`
+      const { first_name, last_name } = row.original
+      const fullName = `${first_name} ${last_name}`
       return <LongText className='max-w-36'>{fullName}</LongText>
     },
     meta: { className: 'w-36' },
@@ -74,11 +74,11 @@ export const usersColumns: ColumnDef<User>[] = [
     ),
   },
   {
-    accessorKey: 'phoneNumber',
+    accessorKey: 'phone',
     header: ({ column }) => (
       <DataTableColumnHeader column={column} title='شماره موبایل' />
     ),
-    cell: ({ row }) => <div>{row.getValue('phoneNumber')}</div>,
+    cell: ({ row }) => <div>{row.getValue('phone')}</div>,
     enableSorting: false,
   },
   {

+ 15 - 14
src/features/users/components/users-delete-dialog.tsx

@@ -41,7 +41,7 @@ export function UsersDeleteDialog({
             className='me-1 inline-block stroke-destructive'
             size={18}
           />{' '}
-          Delete User
+          حذف کاربر
         </span>
       }
       desc={
@@ -54,35 +54,36 @@ export function UsersDeleteDialog({
           className='space-y-4'
         >
           <p className='mb-2'>
-            Are you sure you want to delete{' '}
-            <span className='font-bold'>{currentRow.username}</span>?
+            آیا مطمئن هستید که می خواهید{' '}
+            <span className='font-bold'>{currentRow.username}</span> حذف کنید ؟
             <br />
-            This action will permanently remove the user with the role of{' '}
-            <span className='font-bold'>
-              {currentRow.role.toUpperCase()}
-            </span>{' '}
-            from the system. This cannot be undone.
+            این عمل برای همیشه کاربر را با نقش حذف می کند{' '}
+            {/* <span className='font-bold'>
+              {currentRow.role.toUpperCase() ?? ''}
+            </span>{' '} */}
+            از سیستم این قابل واگرد نیست.
           </p>
 
-          <Label className='my-2'>
-            Username:
+          <Label>
+            نام کاربری :
             <Input
               value={value}
               onChange={(e) => setValue(e.target.value)}
-              placeholder='Enter username to confirm deletion.'
+              placeholder='نام کاربری را برای تایید حذف وارد کنید.'
               autoFocus
             />
           </Label>
 
           <Alert variant='destructive'>
-            <AlertTitle>Warning!</AlertTitle>
+            <AlertTitle>هشدار!</AlertTitle>
             <AlertDescription>
-              Please be careful, this operation can not be rolled back.
+              لطفا مراقب باشید، این عملیات قابل برگشت نیست.
             </AlertDescription>
           </Alert>
         </form>
       }
-      confirmText='Delete'
+      confirmText='حذف'
+      cancelBtnText='انصراف'
       destructive
     />
   )

+ 1 - 1
src/features/users/components/users-table.tsx

@@ -177,7 +177,7 @@ export function UsersTable({ data, search, navigate }: DataTableProps) {
             ) : (
               <TableRow>
                 <TableCell
-                  colSpan={columns.length}
+                  colSpan={columns?.length}
                   className='h-24 text-center'
                 >
                   نتیجه ‌ای یافت نشد.

+ 2 - 2
src/features/users/data/schema.ts

@@ -17,8 +17,8 @@ const userRoleSchema = z.union([
 
 const _userSchema = z.object({
   id: z.string(),
-  firstName: z.string(),
-  lastName: z.string(),
+  first_name: z.string(),
+  last_name: z.string(),
   username: z.string(),
   email: z.string(),
   phoneNumber: z.string(),

+ 15 - 0
src/features/users/hooks/useUsers.tsx

@@ -0,0 +1,15 @@
+import { queryKeys } from '@/core/react-query/keys'
+import { useQuery } from '@tanstack/react-query'
+import { getUsers } from '../api/getUsers'
+
+export const useUsers = (enabled?: boolean) => {
+  return useQuery({
+    queryKey: queryKeys.users.all,
+    queryFn: async () => {
+      const res = await getUsers()
+      return res.data?.users
+    },
+    staleTime: 1000 * 60 * 2,
+    enabled,
+  })
+}

+ 6 - 4
src/features/users/index.tsx

@@ -9,7 +9,7 @@ import { UsersDialogs } from './components/users-dialogs'
 import { UsersPrimaryButtons } from './components/users-primary-buttons'
 import { UsersProvider } from './components/users-provider'
 import { UsersTable } from './components/users-table'
-import { users } from './data/users'
+import { useUsers } from './hooks/useUsers'
 
 const route = getRouteApi('/_authenticated/users/')
 
@@ -17,6 +17,8 @@ export function Users() {
   const search = route.useSearch()
   const navigate = route.useNavigate()
 
+  const { data: users } = useUsers()
+
   return (
     <UsersProvider>
       <Header fixed>
@@ -29,14 +31,14 @@ export function Users() {
       <Main className='flex flex-1 flex-col gap-4 sm:gap-6'>
         <div className='flex flex-wrap items-end justify-between gap-2'>
           <div>
-            <h2 className='text-2xl font-bold tracking-tight'>User List</h2>
+            <h2 className='text-2xl font-bold tracking-tight'>لیست کاربران</h2>
             <p className='text-muted-foreground'>
-              Manage your users and their roles here.
+              در اینجا کاربران و نقش آنها را مدیریت کنید.
             </p>
           </div>
           <UsersPrimaryButtons />
         </div>
-        <UsersTable data={users} search={search} navigate={navigate} />
+        <UsersTable data={users ?? []} search={search} navigate={navigate} />
       </Main>
 
       <UsersDialogs />

+ 1 - 0
src/features/users/types.ts

@@ -0,0 +1 @@
+export type GetUserResponse = any

+ 5 - 9
src/stores/auth-store.ts

@@ -1,7 +1,7 @@
 import { create } from 'zustand'
 import { persist } from 'zustand/middleware'
 import type { AdminUser, LoginResponse } from '@/types/auth'
-import api from '@/lib/axios'
+import { http } from '@/core/api/http'
 
 interface LoginPayload {
   username: string
@@ -28,17 +28,16 @@ export const useAuthStore = create<AuthState>()(
       loading: false,
       error: null,
 
-      /* -------------------------- LOGIN -------------------------- */
       login: async (data: LoginPayload) => {
         set({ loading: true, error: null })
 
         try {
-          const response = await api.post<LoginResponse>(
+          const response = await http.post<LoginResponse>(
             '/admin/auth/login',
             data
           )
 
-          const { token, admin } = response.data.data
+          const { token, admin } = response.data
 
           set({
             token,
@@ -61,7 +60,6 @@ export const useAuthStore = create<AuthState>()(
         }
       },
 
-      /* -------------------------- LOGOUT -------------------------- */
       logout: () => {
         set({
           user: null,
@@ -71,13 +69,12 @@ export const useAuthStore = create<AuthState>()(
         })
       },
 
-      /* ----------------------- FETCH PROFILE ---------------------- */
       fetchProfile: async () => {
         try {
-          const res = await api.get<{ data: AdminUser }>('/admin/auth/profile')
+          const res = await http.get<{ data: AdminUser }>('/admin/auth/profile')
 
           set({
-            user: res.data.data,
+            user: res.data,
             isAuthenticated: true,
           })
         } catch (error) {
@@ -89,7 +86,6 @@ export const useAuthStore = create<AuthState>()(
     {
       name: 'auth-storage',
 
-      // فقط اطلاعات لازم persist شود
       partialize: (state) => ({
         user: state.user,
         token: state.token,