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
227 lines
8.6 KiB
HTML
227 lines
8.6 KiB
HTML
{% macro resource_modal(modal_id='modal_resource', title='Resource') %}
|
||
<!--
|
||
DaisyUI Modal Component Template
|
||
|
||
Usage:
|
||
1. Import in your template:
|
||
{% from "components/modal_resource.html" import resource_modal %}
|
||
|
||
2. Call the macro:
|
||
{{ resource_modal(modal_id='modal_create_resource', title='Create Resource') }}
|
||
|
||
3. Open modal from JavaScript:
|
||
document.getElementById('modal_create_resource').showModal();
|
||
|
||
Features:
|
||
- Responsive width (max-w-2xl on desktop, full width on mobile)
|
||
- Max height with overflow (90vh)
|
||
- Auto-centering (my-auto)
|
||
- DaisyUI styled form controls
|
||
- HTMX-ready form handling
|
||
- Mobile-optimized button sizes
|
||
- Tailwind CSS utility classes
|
||
-->
|
||
<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">
|
||
<!-- Modal Header -->
|
||
<h3 class="font-bold text-lg mb-3">➕ {{ title }}</h3>
|
||
|
||
<!-- Modal Form -->
|
||
<form id="form_{{ modal_id }}" class="space-y-2">
|
||
<!-- Text Input Example -->
|
||
<div class="form-control">
|
||
<label class="label py-1">
|
||
<span class="label-text">Name *</span>
|
||
</label>
|
||
<input type="text" name="name" required
|
||
class="input input-bordered w-full"
|
||
placeholder="Enter name"
|
||
autocomplete="off" />
|
||
</div>
|
||
|
||
<!-- Select Dropdown Example -->
|
||
<div class="form-control">
|
||
<label class="label py-1">
|
||
<span class="label-text">Category *</span>
|
||
</label>
|
||
<select name="category_id" required class="select select-bordered">
|
||
<option value="">-- Select category --</option>
|
||
<!-- Options populated dynamically -->
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Radio Button Group Example (DaisyUI buttons) -->
|
||
<div class="form-control">
|
||
<label class="label py-1">
|
||
<span class="label-text font-semibold">Type *</span>
|
||
</label>
|
||
<div class="grid grid-cols-2 gap-2">
|
||
<label class="btn btn-sm btn-outline btn-error resource-type-btn btn-active" data-type="type1">
|
||
<input type="radio" name="type" value="type1" class="hidden" checked />
|
||
Type 1
|
||
</label>
|
||
<label class="btn btn-sm btn-outline btn-success resource-type-btn" data-type="type2">
|
||
<input type="radio" name="type" value="type2" class="hidden" />
|
||
Type 2
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Number Input Example -->
|
||
<div class="form-control">
|
||
<label class="label py-1">
|
||
<span class="label-text">Amount *</span>
|
||
</label>
|
||
<input type="number" name="amount" step="1" min="0" required
|
||
class="input input-bordered" placeholder="0" />
|
||
</div>
|
||
|
||
<!-- Date Input Example with Quick Buttons -->
|
||
<div class="form-control">
|
||
<label class="label py-1">
|
||
<span class="label-text">Date *</span>
|
||
</label>
|
||
<!-- Quick date buttons (mobile-optimized) -->
|
||
<div class="flex gap-1 mb-1">
|
||
<button type="button" class="btn btn-xs btn-outline flex-1" onclick="setDate(0)">Today</button>
|
||
<button type="button" class="btn btn-xs btn-outline flex-1" onclick="setDate(-1)">Yesterday</button>
|
||
<button type="button" class="btn btn-xs btn-outline flex-1" onclick="setDate(-2)">2 days ago</button>
|
||
</div>
|
||
<input type="text" name="date" required
|
||
class="input input-bordered w-full date-input"
|
||
placeholder="DD.MM.YYYY"
|
||
maxlength="10"
|
||
autocomplete="off" />
|
||
</div>
|
||
|
||
<!-- Textarea Example -->
|
||
<div class="form-control">
|
||
<label class="label py-1">
|
||
<span class="label-text">Description</span>
|
||
</label>
|
||
<textarea name="description" class="textarea textarea-bordered"
|
||
placeholder="Optional comment" rows="2"></textarea>
|
||
</div>
|
||
|
||
<!-- Checkbox Example -->
|
||
<div class="form-control">
|
||
<label class="label cursor-pointer justify-start gap-3">
|
||
<input type="checkbox" name="is_active" class="checkbox checkbox-primary" checked />
|
||
<span class="label-text">Active</span>
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Modal Actions -->
|
||
<div class="modal-action mt-3">
|
||
<!-- Cancel Button -->
|
||
<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>
|
||
Cancel
|
||
</button>
|
||
|
||
<!-- Save Button (with offline mode support) -->
|
||
<button type="button" class="btn btn-sm save-btn"
|
||
data-form-id="form_{{ modal_id }}"
|
||
data-modal-id="{{ modal_id }}"
|
||
onclick="saveResource(this)"
|
||
title="Save">
|
||
<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>
|
||
Save
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Modal Backdrop (click to close) -->
|
||
<form method="dialog" class="modal-backdrop">
|
||
<button>close</button>
|
||
</form>
|
||
</dialog>
|
||
|
||
<!-- CSS for type buttons toggle (add to global CSS) -->
|
||
<style>
|
||
.resource-type-btn {
|
||
transition: all 0.2s;
|
||
}
|
||
.resource-type-btn.btn-active {
|
||
opacity: 1;
|
||
}
|
||
.resource-type-btn:not(.btn-active) {
|
||
opacity: 0.6;
|
||
}
|
||
</style>
|
||
|
||
<!-- JavaScript Example (add to global JS or page-specific script) -->
|
||
<script>
|
||
// Toggle radio button styles
|
||
document.querySelectorAll('.resource-type-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
// Remove btn-active from all buttons
|
||
document.querySelectorAll('.resource-type-btn').forEach(b => {
|
||
b.classList.remove('btn-active');
|
||
});
|
||
// Add btn-active to clicked button
|
||
this.classList.add('btn-active');
|
||
// Check the radio input
|
||
this.querySelector('input[type="radio"]').checked = true;
|
||
});
|
||
});
|
||
|
||
// Quick date setter
|
||
function setDate(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('#{{ modal_id }} .date-input');
|
||
if (input) input.value = dateStr;
|
||
}
|
||
|
||
// Save resource (HTMX or Fetch API)
|
||
async function saveResource(button) {
|
||
const formId = button.dataset.formId;
|
||
const modalId = button.dataset.modalId;
|
||
const form = document.getElementById(formId);
|
||
const modal = document.getElementById(modalId);
|
||
|
||
if (!form.checkValidity()) {
|
||
form.reportValidity();
|
||
return;
|
||
}
|
||
|
||
const formData = new FormData(form);
|
||
const data = Object.fromEntries(formData.entries());
|
||
|
||
try {
|
||
// Example: POST to API endpoint
|
||
const response = await fetch('/api/v1/resources', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (response.ok) {
|
||
modal.close();
|
||
// Refresh data via HTMX
|
||
htmx.trigger('#dynamic-content', 'refresh');
|
||
// Show success toast
|
||
showToast('Resource created successfully', 'success');
|
||
} else {
|
||
const error = await response.json();
|
||
showToast(error.detail || 'Failed to create resource', 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('Save error:', error);
|
||
showToast('Network error', 'error');
|
||
}
|
||
}
|
||
</script>
|
||
{% endmacro %}
|