diff --git a/src/App.tsx b/src/App.tsx index e94f3eb..4059263 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,8 @@ import { Footer } from './components/Footer'; import { AuthGuard } from './components/AuthGuard'; import { ImportArticlesPage } from "./pages/ImportArticlesPage"; +const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000'; + function App() { const { setUser, setLoading } = useAuthStore(); @@ -20,7 +22,7 @@ function App() { const token = localStorage.getItem('token'); if (token) { setLoading(true); - axios.get('http://localhost:5000/api/auth/me', { + axios.get(`${API_URL}/api/auth/me`, { headers: { Authorization: `Bearer ${token}` } }) .then(response => { diff --git a/src/components/ArticleCard.tsx b/src/components/ArticleCard.tsx index 618ee37..b905d30 100644 --- a/src/components/ArticleCard.tsx +++ b/src/components/ArticleCard.tsx @@ -15,29 +15,29 @@ export function ArticleCard({ article, featured = false }: ArticleCardProps) {
-
+
{article.title} -
- +
+ {categoryName} - + {CityTitles[article.cityId]}
- +
{article.author.displayName}

{article.author.displayName}

diff --git a/src/components/FeaturedSection.tsx b/src/components/FeaturedSection.tsx index 569a514..b7270d9 100644 --- a/src/components/FeaturedSection.tsx +++ b/src/components/FeaturedSection.tsx @@ -16,7 +16,7 @@ export function FeaturedSection() { const [totalPages, setTotalPages] = useState(1); const [error, setError] = useState(null); - // Загрузка статей с backend + // Загрузка статей useEffect(() => { const fetchArticles = async () => { try { diff --git a/src/components/TipTapEditor.tsx b/src/components/TipTapEditor.tsx index 081eb3d..5b21ce6 100644 --- a/src/components/TipTapEditor.tsx +++ b/src/components/TipTapEditor.tsx @@ -4,7 +4,20 @@ import TextAlign from '@tiptap/extension-text-align'; import Blockquote from '@tiptap/extension-blockquote'; import Image from '@tiptap/extension-image'; import StarterKit from '@tiptap/starter-kit'; -import { Bold, Italic, List, ListOrdered, Quote, Redo, Undo, AlignLeft, AlignCenter, Plus, Minus, Text } from 'lucide-react'; +import { + Bold, + Italic, + List, + ListOrdered, + Quote, + Redo, + Undo, + AlignLeft, + AlignCenter, + Plus, + Minus, + SquareUser +} from 'lucide-react'; import { useEffect, useState } from "react"; @@ -334,7 +347,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr onClick={handleClick} className="p-1 rounded hover:bg-gray-200" > - + + {editingId && ( + + )}
0) { + setCategoryId(availableCategoryIds[0]); + } + if (availableCityIds.length > 0) { + setCityId(availableCityIds[0]); + } + //setCategoryId(1); + //setCityId(1); setCoverImage(DEFAULT_COVER_IMAGE); setReadTime(5); + setAuthorId(authors[0].id || ''); setGallery([]); setContent(''); }} @@ -356,11 +484,12 @@ export function AdminPage() { type="submit" className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" > - {editingId ? 'Изменить' : 'Созранить черновик'} + {editingId ? 'Изменить' : 'Сохранить черновик'}
+ )} {/* Модальная загрузка изображений галереи */} {showGalleryUploader && ( @@ -438,7 +567,7 @@ export function AdminPage() { {articles.map((article) => (
  • @@ -448,6 +577,15 @@ export function AdminPage() {

    · {CategoryTitles[article.categoryId]} · {CityTitles[article.cityId]} · {article.readTime} чтения

    +
    + {article.author.displayName} + {article.author.displayName} +
    +
    diff --git a/src/services/galleryService.ts b/src/services/galleryService.ts index 78c0c9e..9e75144 100644 --- a/src/services/galleryService.ts +++ b/src/services/galleryService.ts @@ -11,12 +11,12 @@ export const galleryService = { size: number; format: string }) => { - const { data } = await axios.post(`/gallery/article/${articleId}`, imageData); + const { data } = await axios.post(`/api/gallery/article/${articleId}`, imageData); return data; }, updateImage: async (id: string, updates: Partial) => { - const { data } = await axios.put(`/gallery/${id}`, updates); + const { data } = await axios.put(`/api/gallery/${id}`, updates); return data; }, @@ -25,11 +25,11 @@ export const galleryService = { }, reorderImages: async (articleId: string, imageIds: string[]) => { - await axios.post(`/gallery/article/${articleId}/reorder`, { imageIds }); + await axios.post(`/api/gallery/article/${articleId}/reorder`, { imageIds }); }, getArticleGallery: async (articleId: string) => { - const { data } = await axios.get(`/gallery/article/${articleId}`); + const { data } = await axios.get(`/api/gallery/article/${articleId}`); return data as GalleryImage[]; } }; \ No newline at end of file diff --git a/src/services/userService.ts b/src/services/userService.ts index 5a046d0..e129ac4 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -7,8 +7,8 @@ export const userService = { const response = await axios.get('/users'); return response.data; } catch (error) { - console.error('Error fetching users:', error); - throw new Error('Failed to fetch users'); + console.error('Ошибка получения списка пользователей:', error); + throw new Error('Ошибка получения списка пользователей'); } }, diff --git a/src/types/auth.ts b/src/types/auth.ts index 311af3e..b5622ad 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -6,6 +6,8 @@ export interface UserPermissions { isAdmin: boolean; } +export type PermissionAction = 'edit' | 'create' | 'delete'; + export interface User { id: string; email: string; @@ -15,6 +17,7 @@ export interface User { } export interface UserFormData { + id: string email: string; password: string; displayName: string; diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 58b4857..7a18b63 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -1,31 +1,53 @@ -import { Category, City } from '../types'; -import { User } from '../types/auth'; +import { PermissionAction, User } from '../types/auth'; export const checkPermission = ( user: User, - category: Category, - action: 'create' | 'edit' | 'delete' + categoryId: string, + action: PermissionAction ): boolean => { + // Если пользователь админ — ему разрешено всё if (user.permissions.isAdmin) return true; - return !!user.permissions.categories[category.name]?.[action]; + + // Проверяем, есть ли такая категория в permissions + const categoryPermissions = user.permissions.categories[categoryId]; + + // Если категория отсутствует или в ней нет действия — запрет + return categoryPermissions ? categoryPermissions[action] : false; }; -export const checkCityAccess = (user: User, city: City): boolean => { +export const checkCityAccess = (user: User, cityId: number): boolean => { if (user.permissions.isAdmin) return true; - return user.permissions.cities.includes(city); + return user.permissions.cities.includes(cityId); }; export const getDefaultPermissions = () => ({ categories: { - Film: { create: false, edit: false, delete: false }, - Theater: { create: false, edit: false, delete: false }, - Music: { create: false, edit: false, delete: false }, - Sports: { create: false, edit: false, delete: false }, - Art: { create: false, edit: false, delete: false }, - Legends: { create: false, edit: false, delete: false }, - Anniversaries: { create: false, edit: false, delete: false }, - Memory: { create: false, edit: false, delete: false } + 1: { create: false, edit: false, delete: false }, + 2: { create: false, edit: false, delete: false }, + 3: { create: false, edit: false, delete: false }, + 4: { create: false, edit: false, delete: false }, + 5: { create: false, edit: false, delete: false }, + 6: { create: false, edit: false, delete: false }, + 7: { create: false, edit: false, delete: false }, + 8: { create: false, edit: false, delete: false } }, cities: [], isAdmin: false -}); \ No newline at end of file +}); + +export const getUserAvailableCategories = (user: User): number[] => { + if (!user) return []; + if (user.permissions.isAdmin) return [1, 2, 3, 4, 5, 6, 7, 8]; + + return Object.entries(user.permissions.categories) + .filter(([, permissions]) => permissions.create || permissions.edit) // Убрали `_` + .map(([categoryId]) => Number(categoryId)) + .filter((categoryId) => !isNaN(categoryId)); +}; + +export const getUserAvailableCities = (user: User): number[] => { + if (!user) return []; + if (user.permissions.isAdmin) return [1, 2]; + + return user.permissions.cities; +}; \ No newline at end of file