Update files
This commit is contained in:
parent
cfce134654
commit
01f1e1ae94
|
|
@ -0,0 +1,156 @@
|
||||||
|
# Исправление: Пустой экран после верификации
|
||||||
|
|
||||||
|
## 🔴 Проблема
|
||||||
|
После успешной верификации UI открывается, но моментально пропадает (полностью пустой экран).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Что исправлено
|
||||||
|
|
||||||
|
### 1. **Улучшена обработка ошибок**
|
||||||
|
- Добавлено подробное логирование в консоль браузера
|
||||||
|
- Теперь видно, на каком этапе происходит ошибка
|
||||||
|
|
||||||
|
### 2. **Исправлен пустой экран**
|
||||||
|
- Вместо `return null` теперь показывается сообщение с кнопкой перезагрузки
|
||||||
|
- Это помогает понять, что происходит
|
||||||
|
|
||||||
|
### 3. **Отложен запуск initDataChecker**
|
||||||
|
- `initDataChecker` теперь запускается только после успешной загрузки пользователя
|
||||||
|
- Это предотвращает преждевременную перезагрузку страницы
|
||||||
|
|
||||||
|
### 4. **Улучшена валидация данных**
|
||||||
|
- Проверяется, что `userData` действительно получен
|
||||||
|
- Если `null` или `undefined` - показывается ошибка
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Как диагностировать
|
||||||
|
|
||||||
|
### Шаг 1: Откройте консоль браузера
|
||||||
|
|
||||||
|
1. В Telegram откройте приложение
|
||||||
|
2. Нажмите **F12** или **Cmd+Option+I** (Mac)
|
||||||
|
3. Перейдите на вкладку **Console**
|
||||||
|
|
||||||
|
### Шаг 2: Посмотрите логи
|
||||||
|
|
||||||
|
**Должны увидеть:**
|
||||||
|
```
|
||||||
|
[App] Начало инициализации...
|
||||||
|
[App] Telegram WebApp найден, initData: есть
|
||||||
|
[API] verifyAuth: отправка запроса...
|
||||||
|
[API] verifyAuth: получен ответ: { hasUser: true, userId: "...", username: "..." }
|
||||||
|
[App] verifyAuth вернул: данные пользователя
|
||||||
|
[App] Пользователь установлен, ID: ...
|
||||||
|
[App] Инициализация завершена, loading: false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Если видите ошибку:**
|
||||||
|
```
|
||||||
|
[API] verifyAuth: ошибка: { message: "...", status: 401/500 }
|
||||||
|
[App] Ошибка инициализации: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Возможные причины
|
||||||
|
|
||||||
|
### 1. **Ошибка 401 (Unauthorized)**
|
||||||
|
**Причина:** `initData` невалиден или истек
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
- Перезагрузите приложение в Telegram
|
||||||
|
- Убедитесь, что используете официальный клиент Telegram
|
||||||
|
|
||||||
|
### 2. **Ошибка 500 (Server Error)**
|
||||||
|
**Причина:** Проблема на backend
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
```bash
|
||||||
|
# Проверьте логи backend
|
||||||
|
docker logs nakama-backend --tail 100
|
||||||
|
|
||||||
|
# Ищите ошибки:
|
||||||
|
# - "Ошибка verify"
|
||||||
|
# - "MongoDB connection"
|
||||||
|
# - "MinIO connection"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **userData = null/undefined**
|
||||||
|
**Причина:** Backend не вернул данные пользователя
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
- Проверьте маршрут `/api/auth/verify` на backend
|
||||||
|
- Убедитесь, что пользователь существует в БД
|
||||||
|
- Проверьте, что `respondWithUser` работает правильно
|
||||||
|
|
||||||
|
### 4. **Ошибка в компонентах Feed/Layout**
|
||||||
|
**Причина:** Компонент падает при рендеринге
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
- В консоли браузера будет **красная ошибка** с указанием файла и строки
|
||||||
|
- Проверьте, что все данные пользователя присутствуют (`user.settings`, `user.photoUrl`, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Быстрое решение
|
||||||
|
|
||||||
|
### 1. Перезагрузите приложение
|
||||||
|
```javascript
|
||||||
|
// В консоли браузера
|
||||||
|
window.location.reload()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Проверьте backend
|
||||||
|
```bash
|
||||||
|
# Проверьте, что backend работает
|
||||||
|
curl http://your-backend-url/api/health
|
||||||
|
|
||||||
|
# Проверьте логи
|
||||||
|
docker logs nakama-backend -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Проверьте MongoDB
|
||||||
|
```bash
|
||||||
|
# Подключитесь к MongoDB
|
||||||
|
docker exec -it nakama-mongodb mongosh
|
||||||
|
|
||||||
|
# Проверьте пользователей
|
||||||
|
use nakama
|
||||||
|
db.users.find().limit(5)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Контрольный список
|
||||||
|
|
||||||
|
- [ ] Консоль браузера открыта (F12)
|
||||||
|
- [ ] Видны логи `[App]` и `[API]`
|
||||||
|
- [ ] Нет красных ошибок в консоли
|
||||||
|
- [ ] Backend доступен (`/api/health`)
|
||||||
|
- [ ] MongoDB подключена
|
||||||
|
- [ ] Пользователь существует в БД
|
||||||
|
- [ ] `initData` валиден (не истек)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Если все еще не работает
|
||||||
|
|
||||||
|
### Отправьте мне:
|
||||||
|
1. **Логи из консоли браузера** (скопируйте все сообщения)
|
||||||
|
2. **Логи backend** (`docker logs nakama-backend --tail 200`)
|
||||||
|
3. **Скриншот пустого экрана**
|
||||||
|
4. **Ошибки из консоли** (красные сообщения)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ После исправления
|
||||||
|
|
||||||
|
После применения исправлений вы должны видеть:
|
||||||
|
- ✅ Логи в консоли на каждом этапе
|
||||||
|
- ✅ Сообщение об ошибке вместо пустого экрана
|
||||||
|
- ✅ Кнопку "Перезагрузить" если что-то пошло не так
|
||||||
|
|
||||||
|
**Теперь откройте приложение и посмотрите консоль браузера!** 🔍
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ router.get('/', authenticate, async (req, res) => {
|
||||||
query.isNSFW = false;
|
query.isNSFW = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const posts = await Post.find(query)
|
let posts = await Post.find(query)
|
||||||
.populate('author', 'username firstName lastName photoUrl')
|
.populate('author', 'username firstName lastName photoUrl')
|
||||||
.populate('mentionedUsers', 'username firstName lastName')
|
.populate('mentionedUsers', 'username firstName lastName')
|
||||||
.populate('comments.author', 'username firstName lastName photoUrl')
|
.populate('comments.author', 'username firstName lastName photoUrl')
|
||||||
|
|
@ -48,6 +48,9 @@ router.get('/', authenticate, async (req, res) => {
|
||||||
.skip((page - 1) * limit)
|
.skip((page - 1) * limit)
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
|
// Фильтруем посты без автора (защита от ошибок)
|
||||||
|
posts = posts.filter(post => post.author !== null && post.author !== undefined);
|
||||||
|
|
||||||
const count = await Post.countDocuments(query);
|
const count = await Post.countDocuments(query);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ function AppContent() {
|
||||||
initApp()
|
initApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запустить проверку initData
|
// НЕ запускать initDataChecker здесь - он запустится после успешной загрузки пользователя
|
||||||
startInitDataChecker()
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stopInitDataChecker()
|
stopInitDataChecker()
|
||||||
|
|
@ -40,6 +39,7 @@ function AppContent() {
|
||||||
|
|
||||||
const initApp = async () => {
|
const initApp = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log('[App] Начало инициализации...')
|
||||||
initTelegramApp()
|
initTelegramApp()
|
||||||
|
|
||||||
const tg = window.Telegram?.WebApp
|
const tg = window.Telegram?.WebApp
|
||||||
|
|
@ -52,12 +52,25 @@ function AppContent() {
|
||||||
throw new Error('Telegram не передал initData. Откройте приложение из официального клиента.')
|
throw new Error('Telegram не передал initData. Откройте приложение из официального клиента.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[App] Telegram WebApp найден, initData:', tg.initData ? 'есть' : 'нет')
|
||||||
|
|
||||||
tg.disableVerticalSwipes?.()
|
tg.disableVerticalSwipes?.()
|
||||||
tg.expand?.()
|
tg.expand?.()
|
||||||
|
|
||||||
|
console.log('[App] Вызов verifyAuth...')
|
||||||
const userData = await verifyAuth()
|
const userData = await verifyAuth()
|
||||||
|
console.log('[App] verifyAuth вернул:', userData ? 'данные пользователя' : 'null/undefined', userData)
|
||||||
|
|
||||||
|
if (!userData) {
|
||||||
|
throw new Error('Не удалось получить данные пользователя. Попробуйте перезагрузить страницу.')
|
||||||
|
}
|
||||||
|
|
||||||
setUser(userData)
|
setUser(userData)
|
||||||
setError(null)
|
setError(null)
|
||||||
|
console.log('[App] Пользователь установлен, ID:', userData.id || userData._id)
|
||||||
|
|
||||||
|
// Запустить проверку initData только после успешной загрузки
|
||||||
|
startInitDataChecker()
|
||||||
|
|
||||||
if (!startParamProcessed.current && tg?.startParam?.startsWith('post_')) {
|
if (!startParamProcessed.current && tg?.startParam?.startsWith('post_')) {
|
||||||
startParamProcessed.current = true
|
startParamProcessed.current = true
|
||||||
|
|
@ -67,10 +80,17 @@ function AppContent() {
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Ошибка инициализации:', err)
|
console.error('[App] Ошибка инициализации:', err)
|
||||||
|
console.error('[App] Детали ошибки:', {
|
||||||
|
message: err.message,
|
||||||
|
response: err.response?.data,
|
||||||
|
status: err.response?.status
|
||||||
|
})
|
||||||
setError(err?.response?.data?.error || err.message || 'Ошибка авторизации')
|
setError(err?.response?.data?.error || err.message || 'Ошибка авторизации')
|
||||||
|
setUser(null) // Явно сбросить user при ошибке
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
console.log('[App] Инициализация завершена, loading:', false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +146,36 @@ function AppContent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null
|
// Показываем сообщение вместо пустого экрана
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '16px',
|
||||||
|
padding: '20px'
|
||||||
|
}}>
|
||||||
|
<p style={{ color: 'var(--text-primary)', textAlign: 'center' }}>
|
||||||
|
Загрузка данных пользователя...
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
style={{
|
||||||
|
padding: '12px 24px',
|
||||||
|
borderRadius: '12px',
|
||||||
|
background: 'var(--button-accent)',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '16px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Перезагрузить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -64,16 +64,17 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
<div className="post-preview">
|
<div className="post-preview">
|
||||||
<div className="preview-author">
|
<div className="preview-author">
|
||||||
<img
|
<img
|
||||||
src={post.author.photoUrl || '/default-avatar.png'}
|
src={post.author?.photoUrl || '/default-avatar.png'}
|
||||||
alt={post.author.username || post.author.firstName || 'User'}
|
alt={post.author?.username || post.author?.firstName || 'User'}
|
||||||
className="preview-avatar"
|
className="preview-avatar"
|
||||||
|
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="preview-name">
|
<div className="preview-name">
|
||||||
{post.author.firstName || ''} {post.author.lastName || ''}
|
{post.author?.firstName || ''} {post.author?.lastName || ''}
|
||||||
{!post.author.firstName && !post.author.lastName && 'Пользователь'}
|
{!post.author?.firstName && !post.author?.lastName && 'Пользователь'}
|
||||||
</div>
|
</div>
|
||||||
<div className="preview-username">@{post.author.username || post.author.firstName || 'user'}</div>
|
<div className="preview-username">@{post.author?.username || post.author?.firstName || 'user'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -96,18 +97,21 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
<span>Будьте первым!</span>
|
<span>Будьте первым!</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
comments.map((c, index) => (
|
comments
|
||||||
|
.filter(c => c.author) // Фильтруем комментарии без автора
|
||||||
|
.map((c, index) => (
|
||||||
<div key={index} className="comment-item fade-in">
|
<div key={index} className="comment-item fade-in">
|
||||||
<img
|
<img
|
||||||
src={c.author.photoUrl || '/default-avatar.png'}
|
src={c.author?.photoUrl || '/default-avatar.png'}
|
||||||
alt={c.author.username || c.author.firstName || 'User'}
|
alt={c.author?.username || c.author?.firstName || 'User'}
|
||||||
className="comment-avatar"
|
className="comment-avatar"
|
||||||
|
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
||||||
/>
|
/>
|
||||||
<div className="comment-content">
|
<div className="comment-content">
|
||||||
<div className="comment-header">
|
<div className="comment-header">
|
||||||
<span className="comment-author">
|
<span className="comment-author">
|
||||||
{c.author.firstName || ''} {c.author.lastName || ''}
|
{c.author?.firstName || ''} {c.author?.lastName || ''}
|
||||||
{!c.author.firstName && !c.author.lastName && 'Пользователь'}
|
{!c.author?.firstName && !c.author?.lastName && 'Пользователь'}
|
||||||
</span>
|
</span>
|
||||||
<span className="comment-time">{formatDate(c.createdAt)}</span>
|
<span className="comment-time">{formatDate(c.createdAt)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,28 @@ const TAG_NAMES = {
|
||||||
|
|
||||||
export default function PostCard({ post, currentUser, onUpdate }) {
|
export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [liked, setLiked] = useState(post.likes.includes(currentUser.id))
|
const [liked, setLiked] = useState(post.likes?.includes(currentUser.id) || false)
|
||||||
const [likesCount, setLikesCount] = useState(post.likes.length)
|
const [likesCount, setLikesCount] = useState(post.likes?.length || 0)
|
||||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||||
const [showFullView, setShowFullView] = useState(false)
|
const [showFullView, setShowFullView] = useState(false)
|
||||||
|
|
||||||
|
// Проверка на существование автора
|
||||||
|
if (!post.author) {
|
||||||
|
console.warn('[PostCard] Post without author:', post._id)
|
||||||
|
return null // Не показываем посты без автора
|
||||||
|
}
|
||||||
|
|
||||||
// Поддержка и старого поля imageUrl и нового images
|
// Поддержка и старого поля imageUrl и нового images
|
||||||
const images = post.images && post.images.length > 0 ? post.images : (post.imageUrl ? [post.imageUrl] : [])
|
// Фильтруем старые URL из Telegram API
|
||||||
|
const allImages = post.images && post.images.length > 0 ? post.images : (post.imageUrl ? [post.imageUrl] : [])
|
||||||
|
const images = allImages.filter(img => {
|
||||||
|
// Игнорируем старые URL из Telegram API
|
||||||
|
if (img && typeof img === 'string' && img.includes('api.telegram.org/file/bot')) {
|
||||||
|
console.warn('[PostCard] Skipping old Telegram URL:', img)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
const handleLike = async () => {
|
const handleLike = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -61,8 +76,10 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToProfile = () => {
|
const goToProfile = () => {
|
||||||
|
if (post.author?._id) {
|
||||||
navigate(`/user/${post.author._id}`)
|
navigate(`/user/${post.author._id}`)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const openFullView = () => {
|
const openFullView = () => {
|
||||||
if (images.length > 0) {
|
if (images.length > 0) {
|
||||||
|
|
@ -103,17 +120,20 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
<div className="post-header">
|
<div className="post-header">
|
||||||
<div className="post-author" onClick={goToProfile}>
|
<div className="post-author" onClick={goToProfile}>
|
||||||
<img
|
<img
|
||||||
src={post.author.photoUrl || '/default-avatar.png'}
|
src={post.author?.photoUrl || '/default-avatar.png'}
|
||||||
alt={post.author.username || post.author.firstName || 'User'}
|
alt={post.author?.username || post.author?.firstName || 'User'}
|
||||||
className="author-avatar"
|
className="author-avatar"
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = '/default-avatar.png'
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="author-info">
|
<div className="author-info">
|
||||||
<div className="author-name">
|
<div className="author-name">
|
||||||
{post.author.firstName || ''} {post.author.lastName || ''}
|
{post.author?.firstName || ''} {post.author?.lastName || ''}
|
||||||
{!post.author.firstName && !post.author.lastName && 'Пользователь'}
|
{!post.author?.firstName && !post.author?.lastName && 'Пользователь'}
|
||||||
</div>
|
</div>
|
||||||
<div className="post-date">
|
<div className="post-date">
|
||||||
@{post.author.username || post.author.firstName || 'user'} · {formatDate(post.createdAt)}
|
@{post.author?.username || post.author?.firstName || 'user'} · {formatDate(post.createdAt)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -122,16 +122,17 @@ export default function CommentsPage({ user }) {
|
||||||
<div className="post-preview">
|
<div className="post-preview">
|
||||||
<div className="preview-author">
|
<div className="preview-author">
|
||||||
<img
|
<img
|
||||||
src={post.author.photoUrl || '/default-avatar.png'}
|
src={post.author?.photoUrl || '/default-avatar.png'}
|
||||||
alt={post.author.username || post.author.firstName || 'User'}
|
alt={post.author?.username || post.author?.firstName || 'User'}
|
||||||
className="preview-avatar"
|
className="preview-avatar"
|
||||||
|
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="preview-name">
|
<div className="preview-name">
|
||||||
{post.author.firstName || ''} {post.author.lastName || ''}
|
{post.author?.firstName || ''} {post.author?.lastName || ''}
|
||||||
{!post.author.firstName && !post.author.lastName && 'Пользователь'}
|
{!post.author?.firstName && !post.author?.lastName && 'Пользователь'}
|
||||||
</div>
|
</div>
|
||||||
<div className="preview-username">@{post.author.username || post.author.firstName || 'user'}</div>
|
<div className="preview-username">@{post.author?.username || post.author?.firstName || 'user'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -156,23 +157,26 @@ export default function CommentsPage({ user }) {
|
||||||
<span>Будьте первым!</span>
|
<span>Будьте первым!</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
comments.map((c, index) => {
|
comments
|
||||||
|
.filter(c => c.author) // Фильтруем комментарии без автора
|
||||||
|
.map((c, index) => {
|
||||||
const isEditing = editingCommentId === c._id
|
const isEditing = editingCommentId === c._id
|
||||||
const isOwnComment = c.author._id === user.id
|
const isOwnComment = c.author?._id === user.id
|
||||||
const canEdit = isOwnComment || user.role === 'moderator' || user.role === 'admin'
|
const canEdit = isOwnComment || user.role === 'moderator' || user.role === 'admin'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} className="comment-item fade-in">
|
<div key={index} className="comment-item fade-in">
|
||||||
<img
|
<img
|
||||||
src={c.author.photoUrl || '/default-avatar.png'}
|
src={c.author?.photoUrl || '/default-avatar.png'}
|
||||||
alt={c.author.username || c.author.firstName || 'User'}
|
alt={c.author?.username || c.author?.firstName || 'User'}
|
||||||
className="comment-avatar"
|
className="comment-avatar"
|
||||||
|
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
||||||
/>
|
/>
|
||||||
<div className="comment-content">
|
<div className="comment-content">
|
||||||
<div className="comment-header">
|
<div className="comment-header">
|
||||||
<span className="comment-author">
|
<span className="comment-author">
|
||||||
{c.author.firstName || ''} {c.author.lastName || ''}
|
{c.author?.firstName || ''} {c.author?.lastName || ''}
|
||||||
{!c.author.firstName && !c.author.lastName && 'Пользователь'}
|
{!c.author?.firstName && !c.author?.lastName && 'Пользователь'}
|
||||||
</span>
|
</span>
|
||||||
<span className="comment-time">
|
<span className="comment-time">
|
||||||
{formatDate(c.createdAt)}
|
{formatDate(c.createdAt)}
|
||||||
|
|
|
||||||
|
|
@ -71,10 +71,13 @@ export default function Feed({ user }) {
|
||||||
|
|
||||||
const data = await getPosts(params)
|
const data = await getPosts(params)
|
||||||
|
|
||||||
|
// Фильтруем посты без автора (защита от ошибок)
|
||||||
|
const validPosts = data.posts.filter(post => post.author)
|
||||||
|
|
||||||
if (pageNum === 1) {
|
if (pageNum === 1) {
|
||||||
setPosts(data.posts)
|
setPosts(validPosts)
|
||||||
} else {
|
} else {
|
||||||
setPosts(prev => [...prev, ...data.posts])
|
setPosts(prev => [...prev, ...validPosts])
|
||||||
}
|
}
|
||||||
|
|
||||||
setHasMore(pageNum < data.totalPages)
|
setHasMore(pageNum < data.totalPages)
|
||||||
|
|
|
||||||
|
|
@ -188,16 +188,17 @@ export default function PostMenuPage({ user }) {
|
||||||
<div className="post-preview">
|
<div className="post-preview">
|
||||||
<div className="preview-author">
|
<div className="preview-author">
|
||||||
<img
|
<img
|
||||||
src={post.author.photoUrl || '/default-avatar.png'}
|
src={post.author?.photoUrl || '/default-avatar.png'}
|
||||||
alt={post.author.username || post.author.firstName || 'User'}
|
alt={post.author?.username || post.author?.firstName || 'User'}
|
||||||
className="preview-avatar"
|
className="preview-avatar"
|
||||||
|
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="preview-name">
|
<div className="preview-name">
|
||||||
{post.author.firstName || ''} {post.author.lastName || ''}
|
{post.author?.firstName || ''} {post.author?.lastName || ''}
|
||||||
{!post.author.firstName && !post.author.lastName && 'Пользователь'}
|
{!post.author?.firstName && !post.author?.lastName && 'Пользователь'}
|
||||||
</div>
|
</div>
|
||||||
<div className="preview-username">@{post.author.username || post.author.firstName || 'user'}</div>
|
<div className="preview-username">@{post.author?.username || post.author?.firstName || 'user'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,29 @@ export const signInWithTelegram = async (initData) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const verifyAuth = async () => {
|
export const verifyAuth = async () => {
|
||||||
|
try {
|
||||||
|
console.log('[API] verifyAuth: отправка запроса...')
|
||||||
const response = await api.post('/auth/verify')
|
const response = await api.post('/auth/verify')
|
||||||
|
console.log('[API] verifyAuth: получен ответ:', {
|
||||||
|
hasUser: !!response.data?.user,
|
||||||
|
userId: response.data?.user?.id || response.data?.user?._id,
|
||||||
|
username: response.data?.user?.username
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.data?.user) {
|
||||||
|
console.error('[API] verifyAuth: ответ не содержит user:', response.data)
|
||||||
|
throw new Error('Сервер не вернул данные пользователя')
|
||||||
|
}
|
||||||
|
|
||||||
return response.data.user
|
return response.data.user
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[API] verifyAuth: ошибка:', {
|
||||||
|
message: error.message,
|
||||||
|
response: error.response?.data,
|
||||||
|
status: error.response?.status
|
||||||
|
})
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Авторизация через Telegram OAuth (Login Widget)
|
// Авторизация через Telegram OAuth (Login Widget)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue