Files
play-life/play-life-web/src/components/AddWords.jsx
poignatov 4a06ceb7f6
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 16s
v2.0.0: Multi-user authentication with JWT
Features:
- User registration and login with JWT tokens
- All data is now user-specific (multi-tenancy)
- Profile page with integrations and logout
- Automatic migration of existing data to first user

Backend changes:
- Added users and refresh_tokens tables
- Added user_id to all data tables (projects, entries, nodes, dictionaries, words, progress, configs, telegram_integrations, weekly_goals)
- JWT authentication middleware
- claimOrphanedData() for data migration

Frontend changes:
- AuthContext for state management
- Login/Register forms
- Profile page (replaced Integrations)
- All API calls use authFetch with Bearer token

Migration notes:
- On first deploy, backend automatically adds user_id columns
- First user to login claims all existing data
2026-01-01 18:21:18 +03:00

166 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react'
import { useAuth } from './auth/AuthContext'
import './AddWords.css'
const API_URL = '/api'
function AddWords({ onNavigate, dictionaryId, dictionaryName }) {
const { authFetch } = useAuth()
const [markdownText, setMarkdownText] = useState('')
const [message, setMessage] = useState('')
const [loading, setLoading] = useState(false)
// Hide add button if dictionary name is not set
const canAddWords = dictionaryName && dictionaryName.trim() !== ''
const parseMarkdownTable = (text) => {
const lines = text.split('\n')
const words = []
let foundTable = false
let headerFound = false
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim()
// Skip empty lines
if (!line) continue
// Look for table start (markdown table with |)
if (line.includes('|') && line.includes('Слово')) {
foundTable = true
headerFound = true
continue
}
// Skip separator line (|---|---|)
if (foundTable && line.match(/^\|[\s\-|:]+\|$/)) {
continue
}
// Parse table rows
if (foundTable && headerFound && line.includes('|')) {
const cells = line
.split('|')
.map(cell => (cell || '').trim())
.filter(cell => cell && cell.length > 0)
if (cells.length >= 2) {
// Remove markdown formatting (**bold**, etc.)
const word = cells[0].replace(/\*\*/g, '').trim()
const translation = cells[1].replace(/\*\*/g, '').trim()
if (word && translation) {
words.push({
name: word,
translation: translation,
description: ''
})
}
}
}
}
return words
}
const handleSubmit = async (e) => {
e.preventDefault()
setMessage('')
setLoading(true)
try {
const words = parseMarkdownTable(markdownText)
if (words.length === 0) {
setMessage('Не удалось найти слова в таблице. Убедитесь, что таблица содержит колонки "Слово" и "Перевод".')
setLoading(false)
return
}
// Add dictionary_id to each word if dictionaryId is provided
const wordsWithDictionary = words.map(word => ({
...word,
dictionary_id: dictionaryId !== undefined && dictionaryId !== null ? dictionaryId : undefined
}))
const response = await authFetch(`${API_URL}/words`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ words: wordsWithDictionary }),
})
if (!response.ok) {
throw new Error('Ошибка при добавлении слов')
}
const data = await response.json()
const addedCount = data?.added || 0
setMessage(`Успешно добавлено ${addedCount} слов(а)!`)
setMarkdownText('')
} catch (error) {
setMessage(`Ошибка: ${error.message}`)
} finally {
setLoading(false)
}
}
const handleClose = () => {
onNavigate?.('words', dictionaryId !== undefined && dictionaryId !== null ? { dictionaryId } : {})
}
return (
<div className="add-words">
<button className="close-x-button" onClick={handleClose}>
</button>
<h2>Добавить слова</h2>
<p className="description">
Вставьте текст в формате Markdown с таблицей, содержащей колонки "Слово" и "Перевод"
</p>
<form onSubmit={handleSubmit}>
<textarea
className="markdown-input"
value={markdownText}
onChange={(e) => setMarkdownText(e.target.value)}
placeholder={`Вот таблица, содержащая только слова и их перевод:
| Слово (Word) | Перевод |
| --- | --- |
| **Adventure** | Приключение |
| **Challenge** | Вызов, сложная задача |
| **Decision** | Решение |`}
rows={15}
/>
{canAddWords && (
<button
type="submit"
className="submit-button"
disabled={loading || !markdownText.trim()}
>
{loading ? 'Добавление...' : 'Добавить слова'}
</button>
)}
{!canAddWords && (
<div className="message error">
Сначала установите название словаря на экране списка слов
</div>
)}
</form>
{message && (
<div className={`message ${message.includes('Ошибка') ? 'error' : 'success'}`}>
{message}
</div>
)}
</div>
)
}
export default AddWords