docs: add comprehensive frontend landing page plan and download design skills

Add detailed landing page development plan in docs/frontend_landing_plan.md:
- Complete landing page structure (Hero, Problem/Solution, Features, Demo, CTA)
- Design guidelines from downloaded skills (typography, color, motion, composition)
- Security considerations (XSS prevention, input sanitization, CSP)
- Performance targets (LCP <2.5s, bundle <150KB, Lighthouse >90)
- Responsiveness and accessibility requirements (WCAG 2.1 AA)
- Success KPIs and monitoring setup
- 3-week development timeline with daily tasks
- Definition of Done checklist

Download 10+ frontend/UI/UX skills via universal-skills-manager:
- frontend-ui-ux: UI/UX design without mockups
- frontend-design-guidelines: Production-grade interface guidelines
- frontend-developer: React best practices (40+ rules)
- frontend-engineer: Next.js 14 App Router patterns
- ui-ux-master: Comprehensive design systems and accessibility
- ui-ux-systems-designer: Information architecture and interaction
- ui-ux-design-user-experience: Platform-specific guidelines
- Plus additional reference materials and validation scripts

Configure universal-skills MCP with SkillsMP API key for curated skill access.

Safety first: All skills validated before installation, no project code modified.

Refs: Universal Skills Manager (github:jacob-bd/universal-skills-manager)
Next: Begin Sprint 3 landing page development
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-03 13:13:59 +02:00
parent 3d24dfdeaf
commit aa489c7eb8
151 changed files with 17344 additions and 0 deletions

View File

@@ -0,0 +1,424 @@
# Modal Implementation Example: Transaction Modal
Реальный пример из Family Budget: модальное окно для создания транзакции.
## Files
- **Template**: `frontend/web/templates/components/modal_transaction.html`
- **JavaScript**: `frontend/web/static/js/budget/transactionForm.js`
- **Usage**: `frontend/web/templates/index.html`
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ index.html (parent template) │
│ │
│ {% from "components/modal_transaction.html" import │
│ transaction_modal %} │
│ │
│ {{ transaction_modal(modal_id='modal_add_transaction') }}│
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ modal_transaction.html (Jinja2 macro) │
│ │
│ <dialog id="{{ modal_id }}" class="modal"> │
│ <div class="modal-box"> │
│ <form id="form_{{ modal_id }}"> │
│ <!-- Form fields --> │
│ </form> │
│ </div> │
│ </dialog> │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ transactionForm.js (CRUD logic) │
│ │
│ - saveTransaction() - POST /api/v1/facts │
│ - updateTransaction() - PUT /api/v1/facts/{id} │
│ - deleteTransaction() - DELETE /api/v1/facts/{id} │
│ - Offline sync via IndexedDB │
│ - WebSocket broadcast refresh │
└─────────────────────────────────────────────────────────┘
```
## Implementation
### 1. Jinja2 Macro (modal_transaction.html)
```jinja2
{% macro transaction_modal(modal_id='modal_add_transaction') %}
<dialog id="{{ modal_id }}" class="modal">
<div class="modal-box w-full max-w-2xl p-4 max-h-[90vh] overflow-y-auto my-auto">
<h3 class="font-bold text-lg mb-3"> Добавить факт</h3>
<form id="form_{{ modal_id }}" class="space-y-2">
<!-- Дата -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Дата *</span>
</label>
<!-- Quick date buttons -->
<div class="flex gap-1 mb-1">
<button type="button" class="btn btn-xs btn-outline flex-1"
onclick="setTransactionDate(0)">Сегодня</button>
<button type="button" class="btn btn-xs btn-outline flex-1"
onclick="setTransactionDate(-1)">Вчера</button>
<button type="button" class="btn btn-xs btn-outline flex-1"
onclick="setTransactionDate(-2)">Позавчера</button>
</div>
<input type="text" name="fact_date" required
class="input input-bordered w-full transaction-date-input"
placeholder="ДД.ММ.ГГГГ" maxlength="10" />
</div>
<!-- Счет (Financial Center) -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Счет *</span>
</label>
<select name="financial_center_id" required class="select select-bordered">
<option value="">-- Выберите счет --</option>
</select>
</div>
<!-- Тип операции (Radio buttons as DaisyUI buttons) -->
<div class="form-control">
<label class="label py-1">
<span class="label-text font-semibold">Тип операции *</span>
</label>
<div class="grid grid-cols-2 gap-2">
<label class="btn btn-sm btn-outline btn-error transaction-type-btn btn-active"
data-type="expense">
<input type="radio" name="record_type" value="expense" class="hidden" checked />
Расход
</label>
<label class="btn btn-sm btn-outline btn-success transaction-type-btn"
data-type="income">
<input type="radio" name="record_type" value="income" class="hidden" />
Доход
</label>
</div>
</div>
<!-- Категория (Article) - filtered by Financial Center -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Категория *</span>
</label>
<select name="article_id" required class="select select-bordered">
<option value="" disabled hidden>-- Выберите категорию --</option>
</select>
</div>
<!-- Сумма -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Сумма *</span>
</label>
<input type="number" name="amount" step="1" min="1" required
class="input input-bordered" placeholder="0" />
</div>
<!-- Описание -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Описание</span>
</label>
<textarea name="description" class="textarea textarea-bordered"
placeholder="Комментарий" rows="1"></textarea>
</div>
<!-- Modal Actions -->
<div class="modal-action mt-3">
<button type="button" onclick="{{ modal_id }}.close()" class="btn btn-sm btn-ghost">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
Отмена
</button>
<!-- Unified save button (green online, orange offline) -->
<button type="button" class="btn btn-sm save-btn"
data-form-id="form_{{ modal_id }}"
data-modal-id="{{ modal_id }}"
onclick="saveTransaction(this)"
title="Сохранить">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Сохранить
</button>
</div>
</form>
</div>
<!-- Click backdrop to close -->
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{% endmacro %}
```
### 2. JavaScript Logic (transactionForm.js)
```javascript
/**
* Save transaction (CREATE or UPDATE)
* Supports online/offline modes
*/
async function saveTransaction(button) {
const formId = button.dataset.formId;
const modalId = button.dataset.modalId;
const form = document.getElementById(formId);
const modal = document.getElementById(modalId);
// Validate form
if (!form.checkValidity()) {
form.reportValidity();
return;
}
// Get form data
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// Convert types
data.amount = parseFloat(data.amount);
data.financial_center_id = parseInt(data.financial_center_id);
data.article_id = parseInt(data.article_id);
if (data.cost_center_id) {
data.cost_center_id = parseInt(data.cost_center_id);
}
try {
// Check if online or offline
if (navigator.onLine) {
// Online: POST to API
const response = await fetch('/api/v1/facts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to save transaction');
}
const result = await response.json();
console.log('Transaction saved:', result.id);
// Close modal
modal.close();
// Refresh data via HTMX
htmx.trigger('#recent-transactions', 'refresh');
htmx.trigger('#quick-stats', 'refresh');
// Show success toast
showToast('Транзакция сохранена', 'success');
} else {
// Offline: Save to IndexedDB
await window.offlineSync.queueTransaction(data);
// Close modal
modal.close();
// Show offline toast
showToast('Сохранено offline (синхронизируется при подключении)', 'warning');
}
} catch (error) {
console.error('Save transaction error:', error);
showToast(error.message || 'Ошибка сохранения', 'error');
}
}
/**
* Open modal for creating new transaction
*/
function openAddTransactionModal() {
const modal = document.getElementById('modal_add_transaction');
const form = document.getElementById('form_modal_add_transaction');
// Reset form
form.reset();
// Set default date (today)
setTransactionDate(0);
// Set default type (expense)
document.querySelector('input[name="record_type"][value="expense"]').checked = true;
document.querySelectorAll('.transaction-type-btn').forEach(btn => {
btn.classList.remove('btn-active');
});
document.querySelector('.transaction-type-btn[data-type="expense"]').classList.add('btn-active');
// Load Financial Centers and Articles
loadFormOptions();
// Open modal
modal.showModal();
}
/**
* Set transaction date
* @param {number} daysOffset - Days offset from today (0=today, -1=yesterday, etc.)
*/
function setTransactionDate(daysOffset) {
const date = new Date();
date.setDate(date.getDate() + daysOffset);
const dateStr = date.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
const input = document.querySelector('.transaction-date-input');
if (input) {
input.value = dateStr;
}
}
/**
* Load Financial Centers and Articles for form
*/
async function loadFormOptions() {
try {
// Load Financial Centers
const fcResponse = await fetch('/api/v1/financial-centers');
const financialCenters = await fcResponse.json();
const fcSelect = document.querySelector('select[name="financial_center_id"]');
fcSelect.innerHTML = '<option value="">-- Выберите счет --</option>';
financialCenters.forEach(fc => {
fcSelect.innerHTML += `<option value="${fc.id}">${fc.name}</option>`;
});
// Load Articles (filtered by Financial Center after selection)
const articlesResponse = await fetch('/api/v1/articles');
const articles = await articlesResponse.json();
const articleSelect = document.querySelector('select[name="article_id"]');
articleSelect.innerHTML = '<option value="">-- Выберите категорию --</option>';
articles.forEach(article => {
articleSelect.innerHTML += `<option value="${article.id}">${article.name}</option>`;
});
} catch (error) {
console.error('Load form options error:', error);
showToast('Ошибка загрузки справочников', 'error');
}
}
```
### 3. Usage in Template
```jinja2
{% extends "base.html" %}
{% from "components/modal_transaction.html" import transaction_modal %}
{% block content %}
<!-- Page content -->
<div class="space-y-6">
<!-- Quick actions -->
<button onclick="openAddTransactionModal()" class="btn btn-success">
Добавить факт
</button>
<!-- Recent transactions -->
<div id="recent-transactions" hx-get="/api/v1/facts/recent" hx-trigger="load">
<!-- Loaded via HTMX -->
</div>
</div>
<!-- Modal (outside content div) -->
{{ transaction_modal(modal_id='modal_add_transaction') }}
{% endblock %}
```
## Key Features
### 1. DaisyUI Modal
```html
<dialog id="modal_id" class="modal">
<div class="modal-box">...</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
```
- Native `<dialog>` element (HTML5)
- DaisyUI classes: `modal`, `modal-box`, `modal-backdrop`
- JavaScript API: `modal.showModal()`, `modal.close()`
### 2. Form Validation
- HTML5 validation: `required`, `min`, `maxlength`
- JavaScript: `form.checkValidity()`, `form.reportValidity()`
- Custom validation in `saveTransaction()`
### 3. Offline Support
- Check `navigator.onLine`
- Save to IndexedDB when offline
- Sync queue processed when back online
- Visual feedback (orange button when offline)
### 4. HTMX Integration
```javascript
// Refresh HTMX content after save
htmx.trigger('#recent-transactions', 'refresh');
htmx.trigger('#quick-stats', 'refresh');
```
### 5. Radio Buttons as Buttons
```html
<label class="btn btn-outline btn-error transaction-type-btn">
<input type="radio" name="record_type" value="expense" class="hidden" />
Расход
</label>
```
- Styled as DaisyUI buttons
- Hidden radio input
- Toggle `btn-active` class on click
## Performance
- **Modal open**: <100ms (minimal JavaScript)
- **Form load**: ~200ms (2 API calls in parallel)
- **Save**: ~150ms (POST + HTMX refresh)
- **Offline save**: ~50ms (IndexedDB write)
## Accessibility
- ✅ Keyboard navigation (Tab, Enter, Esc)
- ✅ ARIA labels (implicit from `<label>` elements)
- ✅ Focus management (auto-focus first input when opened)
- ✅ Screen reader compatible (DaisyUI semantic HTML)
## Browser Support
- ✅ Chrome/Edge (full support)
- ✅ Firefox (full support)
- ✅ Safari (full support, including iOS)
- ✅ Mobile browsers (responsive design)
## References
- DaisyUI Modal: https://daisyui.com/components/modal/
- HTMX: https://htmx.org/
- HTML Dialog Element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog