remove bootstrap
Some checks are pending
PHP Composer / build (push) Waiting to run

This commit is contained in:
Hesabix 2025-09-05 11:52:08 +03:30
parent 06a2fb398d
commit 1467e83ccf
20 changed files with 4867 additions and 2666 deletions

View file

@ -7,9 +7,327 @@ import './bootstrap.js';
*/ */
// any CSS you import will output into a single css file (app.css in this case) // any CSS you import will output into a single css file (app.css in this case)
import './styles/global.scss'; import './styles/tailwind.css';
import './styles/app.css'; import './styles/app.css';
// Import Bootstrap and make it globally available // Notification System
import * as bootstrap from 'bootstrap'; class NotificationSystem {
window.bootstrap = bootstrap; constructor() {
this.container = null;
this.init();
}
init() {
// Create notification container if it doesn't exist
this.container = document.getElementById('notification-container');
if (!this.container) {
this.container = document.createElement('div');
this.container.id = 'notification-container';
this.container.className = 'notification-container';
document.body.appendChild(this.container);
}
}
show(message, type = 'info', title = '', duration = 5000) {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
const icon = this.getIcon(type);
const closeIcon = this.getCloseIcon();
notification.innerHTML = `
<div class="notification-icon">${icon}</div>
<div class="notification-content">
${title ? `<div class="notification-title">${title}</div>` : ''}
<div class="notification-message">${message}</div>
</div>
<button class="notification-close" onclick="this.parentElement.remove()">${closeIcon}</button>
<div class="notification-progress" style="width: 100%; animation: progress ${duration}ms linear forwards;"></div>
`;
// Add progress bar animation
const style = document.createElement('style');
style.textContent = `
@keyframes progress {
from { width: 100%; }
to { width: 0%; }
}
`;
document.head.appendChild(style);
this.container.appendChild(notification);
// Trigger animation
setTimeout(() => {
notification.classList.add('show');
}, 10);
// Auto remove
if (duration > 0) {
setTimeout(() => {
this.remove(notification);
}, duration);
}
return notification;
}
getIcon(type) {
const icons = {
success: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>`,
error: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>`,
warning: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
</svg>`,
info: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>`
};
return icons[type] || icons.info;
}
getCloseIcon() {
return `<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>`;
}
remove(notification) {
if (notification && notification.parentElement) {
notification.classList.remove('show');
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 300);
}
}
success(message, title = 'موفقیت') {
return this.show(message, 'success', title);
}
error(message, title = 'خطا') {
return this.show(message, 'error', title);
}
warning(message, title = 'هشدار') {
return this.show(message, 'warning', title);
}
info(message, title = 'اطلاعات') {
return this.show(message, 'info', title);
}
}
// Initialize notification system
const notification = new NotificationSystem();
// Make notification available globally
window.notification = notification;
// Test notification system
setTimeout(() => {
if (window.notification) {
console.log('Notification system initialized successfully');
}
}, 1000);
// Blog functionality
document.addEventListener('DOMContentLoaded', function() {
// Blog search enhancement
const searchInput = document.querySelector('input[name="search"]');
if (searchInput) {
searchInput.addEventListener('focus', function() {
this.classList.add('blog-search-input');
});
searchInput.addEventListener('blur', function() {
this.classList.remove('blog-search-input');
});
}
// Blog card hover effects
const blogCards = document.querySelectorAll('.group.bg-white.rounded-2xl');
blogCards.forEach(card => {
card.classList.add('blog-card-hover');
});
// Pagination hover effects
const paginationItems = document.querySelectorAll('nav a, nav span');
paginationItems.forEach(item => {
item.classList.add('pagination-item');
});
// Smooth scroll for anchor links
const anchorLinks = document.querySelectorAll('a[href^="#"]');
anchorLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Lazy loading for images
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('blog-loading');
img.classList.add('opacity-100');
observer.unobserve(img);
}
});
});
images.forEach(img => {
img.classList.add('blog-loading', 'opacity-0');
imageObserver.observe(img);
});
// Reading progress bar for blog posts
const blogPost = document.querySelector('main.min-h-screen.bg-gray-50');
if (blogPost) {
const progressBar = document.createElement('div');
progressBar.className = 'fixed top-0 left-0 w-0 h-1 bg-gradient-to-r from-blue-500 to-purple-500 z-50 transition-all duration-300';
document.body.appendChild(progressBar);
window.addEventListener('scroll', function() {
const scrollTop = window.pageYOffset;
const docHeight = document.body.scrollHeight - window.innerHeight;
const scrollPercent = (scrollTop / docHeight) * 100;
progressBar.style.width = scrollPercent + '%';
});
}
// Copy code blocks functionality
const codeBlocks = document.querySelectorAll('pre code');
codeBlocks.forEach(block => {
const copyButton = document.createElement('button');
copyButton.className = 'absolute top-2 left-2 px-2 py-1 text-xs bg-gray-800 text-white rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200';
copyButton.textContent = 'کپی';
const wrapper = document.createElement('div');
wrapper.className = 'relative group';
wrapper.style.position = 'relative';
block.parentNode.insertBefore(wrapper, block);
wrapper.appendChild(block);
wrapper.appendChild(copyButton);
copyButton.addEventListener('click', function() {
navigator.clipboard.writeText(block.textContent).then(() => {
copyButton.textContent = 'کپی شد!';
setTimeout(() => {
copyButton.textContent = 'کپی';
}, 2000);
});
});
});
// Q&A functionality
// Add hover effects to Q&A cards
const qaCards = document.querySelectorAll('.bg-white.rounded-2xl.shadow-soft');
qaCards.forEach(card => {
card.classList.add('qa-card-hover');
});
// Add hover effects to Q&A tags
const qaTags = document.querySelectorAll('.inline-flex.items-center.px-3.py-1');
qaTags.forEach(tag => {
tag.classList.add('qa-tag-hover');
});
// Add focus effects to Q&A form inputs
const qaInputs = document.querySelectorAll('input, textarea, select');
qaInputs.forEach(input => {
input.classList.add('qa-form-focus');
});
// Add hover effects to Q&A pagination
const qaPaginationItems = document.querySelectorAll('nav a, nav span');
qaPaginationItems.forEach(item => {
item.classList.add('qa-pagination-item');
});
// Add stagger animation to Q&A cards
qaCards.forEach((card, index) => {
card.classList.add('qa-fade-in-up');
if (index < 4) {
card.classList.add(`qa-stagger-${index + 1}`);
}
});
// Q&A search enhancement
const qaSearchInput = document.querySelector('input[name="search"]');
if (qaSearchInput) {
qaSearchInput.addEventListener('focus', function() {
this.parentElement.classList.add('ring-2', 'ring-blue-500');
});
qaSearchInput.addEventListener('blur', function() {
this.parentElement.classList.remove('ring-2', 'ring-blue-500');
});
}
// Q&A filter enhancement
const qaFilterSelect = document.querySelector('select[name="filter"]');
if (qaFilterSelect) {
qaFilterSelect.addEventListener('change', function() {
this.classList.add('bg-blue-50', 'border-blue-300');
setTimeout(() => {
this.classList.remove('bg-blue-50', 'border-blue-300');
}, 300);
});
}
// Q&A vote button enhancement
const qaVoteButtons = document.querySelectorAll('.vote-btn');
qaVoteButtons.forEach(button => {
button.classList.add('qa-vote-btn');
});
// Q&A tag selection enhancement
const qaTagSuggestions = document.querySelectorAll('.tag-suggestion');
qaTagSuggestions.forEach(tag => {
tag.addEventListener('click', function() {
this.classList.toggle('qa-tag-selected');
this.classList.toggle('qa-tag-unselected');
});
});
// Q&A answer acceptance enhancement
const qaAcceptButtons = document.querySelectorAll('.accept-answer-btn');
qaAcceptButtons.forEach(button => {
button.addEventListener('click', function() {
this.classList.add('bg-green-200', 'text-green-800');
setTimeout(() => {
this.classList.remove('bg-green-200', 'text-green-800');
}, 1000);
});
});
// Q&A loading states
const qaForms = document.querySelectorAll('form');
qaForms.forEach(form => {
form.addEventListener('submit', function() {
const submitButton = this.querySelector('button[type="submit"]');
if (submitButton) {
submitButton.classList.add('qa-loading-shimmer');
submitButton.disabled = true;
}
});
});
});

View file

@ -1,5 +1,6 @@
/* Custom styles for Hesabix */
body, .lead { body, .lead {
font-family: "Yekan Bakh FaNum"; font-family: "Yekan Bakh FaNum", "Tahoma", "Arial", sans-serif;
font-feature-settings: "kern" on, "liga" on, "dlig" on; font-feature-settings: "kern" on, "liga" on, "dlig" on;
-moz-font-feature-settings: "kern" on, "liga" on, "dlig" on; -moz-font-feature-settings: "kern" on, "liga" on, "dlig" on;
-webkit-font-feature-settings: "kern" on, "liga" on, "dlig" on; -webkit-font-feature-settings: "kern" on, "liga" on, "dlig" on;
@ -7,118 +8,679 @@ body, .lead {
-o-font-feature-settings: "kern" on, "liga" on, "dlig" on; -o-font-feature-settings: "kern" on, "liga" on, "dlig" on;
} }
/* استایل‌های منوی ناوبری */ /* RTL Support */
.navbar { [dir="rtl"] {
padding: 0.8rem 0; direction: rtl;
} text-align: right;
.navbar-brand {
font-size: 1.4rem;
}
.nav-link {
position: relative;
color: #495057;
font-weight: 500;
transition: all 0.3s ease;
}
.nav-link:hover {
color: #1743bb;
background-color: rgba(23, 67, 187, 0.05);
}
.nav-link.active {
color: #1743bb;
background-color: rgba(23, 67, 187, 0.1);
}
.btn-primary {
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(23, 67, 187, 0.2);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(23, 67, 187, 0.3);
}
.transition-all {
transition: all 0.3s ease;
} }
/* Custom utility classes */
.rul { .rul {
text-decoration: none; text-decoration: none;
} }
.accordion-button:after { /* Code blocks */
margin-right: auto;
margin-left: 0;
}
pre { pre {
border-radius: 0.5rem; border-radius: 0.5rem;
direction: ltr; direction: ltr;
background-color: #d6d6df; background-color: #f3f4f6;
padding: 0.9rem 0.8rem; padding: 0.9rem 0.8rem;
color: blue; color: #1f2937;
font-family: 'Courier New', monospace;
} }
/* استایل‌های RTL برای Modal */ /* Trust seals animation */
#walletModal { .trust-seal-loading {
direction: rtl; animation: pulse 1.5s ease-in-out infinite;
text-align: right;
} }
#walletModal .modal-content { @keyframes pulse {
font-family: 'Yekan Bakh FaNum', 'Tahoma', 'Arial', sans-serif; 0% { opacity: 1; }
direction: rtl; 50% { opacity: 0.5; }
text-align: right; 100% { opacity: 1; }
} }
#walletModal .modal-header { /* Trust seals content */
direction: rtl; .trust-seals-content img {
text-align: right; transition: opacity 0.3s ease-in-out;
border-bottom: 1px solid #dee2e6; max-height: 50px;
width: auto;
} }
#walletModal .modal-title { .trust-seals-content img:hover {
direction: rtl; opacity: 0.8;
text-align: right; transform: scale(1.05);
font-family: 'Yekan Bakh FaNum', 'Tahoma', 'Arial', sans-serif; }
/* Q&A specific styles */
.qa-card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.qa-card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.15);
}
/* Tag System Styles */
.tag-suggestion {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.tag-suggestion::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.tag-suggestion:hover::before {
left: 100%;
}
.tag-suggestion:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.selected-tags {
transition: all 0.3s ease;
}
.selected-tags:empty {
border-color: #e5e7eb;
}
.selected-tags:not(:empty) {
border-color: #3b82f6;
background-color: #f8fafc;
}
.tag-remove {
transition: all 0.2s ease;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.tag-remove:hover {
background-color: rgba(239, 68, 68, 0.1);
transform: scale(1.1);
}
.tag-remove:active {
transform: scale(0.95);
}
/* Tag Input Styles */
#tag-input:focus {
transform: translateY(-1px);
box-shadow: 0 8px 25px -5px rgba(59, 130, 246, 0.3);
}
#add-tag-btn {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
#add-tag-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
#add-tag-btn:hover::before {
left: 100%;
}
#add-tag-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
#add-tag-btn:active {
transform: translateY(0);
}
/* Loading Spinner for Tags */
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid #e5e7eb;
border-top: 2px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Tag Count Animation */
#tag-count {
transition: all 0.3s ease;
font-weight: 500;
}
/* Tag Suggestions Container */
#tag-suggestions {
max-height: 200px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #cbd5e0 #f7fafc;
}
#tag-suggestions::-webkit-scrollbar {
width: 6px;
}
#tag-suggestions::-webkit-scrollbar-track {
background: #f7fafc;
border-radius: 3px;
}
#tag-suggestions::-webkit-scrollbar-thumb {
background: #cbd5e0;
border-radius: 3px;
}
#tag-suggestions::-webkit-scrollbar-thumb:hover {
background: #a0aec0;
}
/* Tag Selection Animation */
.tag-suggestion.selected {
animation: tagSelect 0.3s ease;
}
@keyframes tagSelect {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
/* Tag Validation States */
.tag-container.valid {
border-color: #10b981;
background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%);
}
.tag-container.invalid {
border-color: #ef4444;
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
}
/* Responsive Tag System */
@media (max-width: 640px) {
.selected-tags {
min-height: 60px;
}
.tag-suggestion {
font-size: 0.875rem;
padding: 0.5rem 0.75rem;
}
#tag-input {
font-size: 0.875rem;
padding: 0.75rem;
}
#add-tag-btn {
padding: 0.75rem 1rem;
font-size: 0.875rem;
}
}
.qa-vote-btn {
transition: all 0.2s ease-in-out;
}
.qa-vote-btn:hover {
transform: scale(1.1);
}
.qa-vote-btn:active {
transform: scale(0.95);
}
.qa-tag-hover {
transition: all 0.2s ease-in-out;
}
.qa-tag-hover:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.qa-form-focus {
transition: all 0.2s ease-in-out;
}
.qa-form-focus:focus {
transform: translateY(-1px);
box-shadow: 0 8px 25px -5px rgba(59, 130, 246, 0.3);
}
.qa-accepted-answer {
background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%);
border-left: 4px solid #10b981;
}
.qa-question-stats {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
}
.qa-editor-toolbar {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 1px solid #e2e8f0;
}
.qa-editor-toolbar button {
transition: all 0.2s ease-in-out;
}
.qa-editor-toolbar button:hover {
background-color: #e2e8f0;
transform: translateY(-1px);
}
.qa-editor-toolbar button:active {
transform: translateY(0);
}
.qa-tag-selected {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: white;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.qa-tag-unselected {
background: white;
color: #374151;
border: 1px solid #d1d5db;
}
.qa-tag-unselected:hover {
background: #f3f4f6;
border-color: #9ca3af;
}
.qa-pagination-item {
transition: all 0.2s ease-in-out;
}
.qa-pagination-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.qa-loading-shimmer {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: qa-shimmer 1.5s infinite;
}
@keyframes qa-shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
.qa-fade-in-up {
animation: qa-fadeInUp 0.6s ease-out;
}
@keyframes qa-fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.qa-stagger-1 { animation-delay: 0.1s; }
.qa-stagger-2 { animation-delay: 0.2s; }
.qa-stagger-3 { animation-delay: 0.3s; }
.qa-stagger-4 { animation-delay: 0.4s; }
/* Notification System */
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 400px;
}
.notification {
background: white;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
margin-bottom: 12px;
padding: 16px 20px;
display: flex;
align-items: center;
gap: 12px;
transform: translateX(100%);
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-left: 4px solid;
min-width: 300px;
}
.notification.show {
transform: translateX(0);
opacity: 1;
}
.notification.success {
border-left-color: #10b981;
background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%);
}
.notification.error {
border-left-color: #ef4444;
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
}
.notification.warning {
border-left-color: #f59e0b;
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
}
.notification.info {
border-left-color: #3b82f6;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
}
.notification-icon {
width: 24px;
height: 24px;
flex-shrink: 0;
}
.notification-content {
flex: 1;
}
.notification-title {
font-weight: 600; font-weight: 600;
font-size: 14px;
margin-bottom: 4px;
color: #1f2937;
} }
#walletModal .btn-close { .notification-message {
margin-left: 0; font-size: 13px;
margin-right: auto; color: #6b7280;
order: -1; line-height: 1.4;
} }
#walletModal .modal-body { .notification-close {
direction: rtl; background: none;
border: none;
cursor: pointer;
padding: 4px;
border-radius: 4px;
color: #9ca3af;
transition: all 0.2s ease;
flex-shrink: 0;
}
.notification-close:hover {
background: rgba(0, 0, 0, 0.1);
color: #374151;
}
.notification-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: currentColor;
border-radius: 0 0 12px 12px;
transition: width linear;
}
/* Loading Spinner */
.loading-spinner {
width: 20px;
height: 20px;
border: 2px solid #e5e7eb;
border-top: 2px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Blog specific styles */
.blog-card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.blog-card-hover:hover {
transform: translateY(-8px);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
/* Improved prose styling for blog content */
.prose {
color: #374151;
max-width: none;
}
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
color: #1f2937;
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
}
.prose h1 {
font-size: 2.25rem;
line-height: 1.2;
}
.prose h2 {
font-size: 1.875rem;
line-height: 1.3;
}
.prose h3 {
font-size: 1.5rem;
line-height: 1.4;
}
.prose p {
margin-bottom: 1.5rem;
line-height: 1.8;
}
.prose ul, .prose ol {
margin-bottom: 1.5rem;
padding-right: 1.5rem;
}
.prose li {
margin-bottom: 0.5rem;
}
.prose blockquote {
border-right: 4px solid #3b82f6;
background-color: #f8fafc;
padding: 1.5rem;
margin: 2rem 0;
border-radius: 0.5rem;
font-style: italic;
color: #4b5563;
}
.prose code {
background-color: #f1f5f9;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
font-size: 0.875rem;
color: #e11d48;
font-family: 'Courier New', monospace;
}
.prose pre {
background-color: #1f2937;
color: #f9fafb;
padding: 1.5rem;
border-radius: 0.75rem;
overflow-x: auto;
margin: 2rem 0;
}
.prose pre code {
background-color: transparent;
padding: 0;
color: inherit;
}
.prose img {
border-radius: 0.75rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
margin: 2rem 0;
}
.prose a {
color: #3b82f6;
text-decoration: none;
font-weight: 500;
transition: color 0.2s ease;
}
.prose a:hover {
color: #1d4ed8;
text-decoration: underline;
}
.prose table {
width: 100%;
border-collapse: collapse;
margin: 2rem 0;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.prose th, .prose td {
padding: 0.75rem 1rem;
text-align: right; text-align: right;
font-family: 'Yekan Bakh FaNum', 'Tahoma', 'Arial', sans-serif; border-bottom: 1px solid #e5e7eb;
} }
#walletModal .modal-footer { .prose th {
direction: rtl; background-color: #f9fafb;
text-align: right; font-weight: 600;
border-top: 1px solid #dee2e6; color: #374151;
} }
#walletModal .modal-footer .btn { .prose tr:hover {
font-family: 'Yekan Bakh FaNum', 'Tahoma', 'Arial', sans-serif; background-color: #f9fafb;
direction: rtl;
} }
#walletModal .alert { /* Blog search improvements */
direction: rtl; .blog-search-input {
text-align: right; transition: all 0.3s ease;
font-family: 'Yekan Bakh FaNum', 'Tahoma', 'Arial', sans-serif;
} }
/* استایل‌های backdrop برای RTL */ .blog-search-input:focus {
.modal-backdrop { transform: translateY(-2px);
direction: rtl; box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1);
}
/* Pagination improvements */
.pagination-item {
transition: all 0.2s ease;
}
.pagination-item:hover {
transform: translateY(-1px);
}
/* Loading states */
.blog-loading {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
/* Responsive improvements */
@media (max-width: 640px) {
.prose {
font-size: 0.875rem;
}
.prose h1 {
font-size: 1.875rem;
}
.prose h2 {
font-size: 1.5rem;
}
.prose h3 {
font-size: 1.25rem;
}
}
/* Dark mode support (if needed in future) */
@media (prefers-color-scheme: dark) {
.prose {
color: #d1d5db;
}
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
color: #f9fafb;
}
.prose blockquote {
background-color: #374151;
color: #d1d5db;
}
.prose code {
background-color: #374151;
color: #fbbf24;
}
} }

View file

@ -1,11 +0,0 @@
// assets/styles/global.scss
// customize some Bootstrap variables
$primary: #1743bb;
$link-hover-decoration: none;
$accordion-button-bg: rgb(248, 249, 250);
$accordion-bg: rgb(248, 249, 250);
$accordion-button-active-bg: rgb(248, 249, 250);
$accordion-padding-y: 0.6rem;
// the ~ allows you to reference things in node_modules
@import "~bootstrap/scss/bootstrap";

403
assets/styles/tailwind.css Normal file
View file

@ -0,0 +1,403 @@
@import "tailwindcss";
/* RTL Support */
[dir="rtl"] {
direction: rtl;
text-align: right;
}
/* Custom components */
.btn-primary {
background-color: #1743bb;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.3s ease;
display: inline-block;
text-decoration: none;
border: none;
cursor: pointer;
}
.btn-primary:hover {
background-color: #0d47a1;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(23, 67, 187, 0.3);
}
.btn-outline-primary {
border: 1px solid #1743bb;
color: #1743bb;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.3s ease;
display: inline-block;
text-decoration: none;
background: transparent;
cursor: pointer;
}
.btn-outline-primary:hover {
background-color: #1743bb;
color: white;
}
.card {
background: white;
border-radius: 0.5rem;
box-shadow: 0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04);
padding: 1.5rem;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 0;
border-bottom: 1px solid #e5e7eb;
background-color: #f9fafb;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.nav-link {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
color: #6b7280;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.nav-link:hover {
color: #1743bb;
background-color: rgba(23, 67, 187, 0.05);
}
.dropdown-menu {
position: absolute;
right: 0;
margin-top: 0.5rem;
width: 12rem;
background: white;
border-radius: 0.5rem;
box-shadow: 0 4px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
border: 1px solid #e5e7eb;
padding: 0.5rem 0;
z-index: 50;
}
.dropdown-item {
display: block;
padding: 0.5rem 1rem;
font-size: 0.875rem;
color: #374151;
transition: background-color 0.2s ease;
text-decoration: none;
}
.dropdown-item:hover {
background-color: #f3f4f6;
}
.form-control {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
outline: none;
transition: all 0.2s ease;
}
.form-control:focus {
outline: none;
ring: 2px;
ring-color: #1743bb;
border-color: transparent;
}
.form-label {
display: block;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
margin-bottom: 0.5rem;
}
.alert {
padding: 0.75rem 1rem;
border-radius: 0.5rem;
border: 1px solid;
}
.alert-success {
background-color: #f0fdf4;
border-color: #bbf7d0;
color: #166534;
}
.alert-danger {
background-color: #fef2f2;
border-color: #fecaca;
color: #991b1b;
}
.alert-warning {
background-color: #fffbeb;
border-color: #fed7aa;
color: #92400e;
}
.alert-info {
background-color: #eff6ff;
border-color: #bfdbfe;
color: #1e40af;
}
.spinner-border {
display: inline-block;
width: 1rem;
height: 1rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.spinner-border-sm {
width: 0.75rem;
height: 0.75rem;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.modal {
position: fixed;
inset: 0;
z-index: 50;
overflow-y: auto;
}
.modal-backdrop {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: relative;
background: white;
border-radius: 0.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
max-width: 32rem;
margin: 5rem auto;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.75rem;
padding: 1.5rem;
border-top: 1px solid #e5e7eb;
}
.btn-close {
width: 1.5rem;
height: 1.5rem;
background: transparent;
border: none;
color: #9ca3af;
cursor: pointer;
outline: none;
}
.btn-close:hover {
color: #6b7280;
}
.btn-close::before {
content: "×";
font-size: 1.25rem;
font-weight: bold;
}
/* Custom utilities for RTL */
.text-primary {
color: #1743bb;
}
.bg-primary {
background-color: #1743bb;
}
.border-primary {
border-color: #1743bb;
}
.hover\:bg-primary:hover {
background-color: #1743bb;
}
.hover\:text-primary:hover {
color: #1743bb;
}
.focus\:ring-primary:focus {
--tw-ring-color: #1743bb;
}
/* انیمیشن‌های اضافی */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* کلاس‌های انیمیشن */
.animate-fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
.animate-fade-in-left {
animation: fadeInLeft 0.6s ease-out;
}
.animate-fade-in-right {
animation: fadeInRight 0.6s ease-out;
}
.animate-scale-in {
animation: scaleIn 0.6s ease-out;
}
/* بهبود خط‌بندی متن */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* بهبود hover effects */
.hover-lift {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.hover-lift:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* بهبود gradient text */
.gradient-text {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* بهبود backdrop blur */
.backdrop-blur-custom {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* بهبود responsive spacing */
@media (max-width: 640px) {
.container {
padding-left: 1rem;
padding-right: 1rem;
}
}
/* بهبود focus states */
.focus-ring {
transition: all 0.2s ease;
}
.focus-ring:focus {
outline: none;
ring: 2px;
ring-color: #3b82f6;
ring-offset: 2px;
}
/* بهبود loading states */
.loading-shimmer {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}

834
package-lock.json generated
View file

@ -10,25 +10,41 @@
"@babel/preset-env": "^7.16.0", "@babel/preset-env": "^7.16.0",
"@hotwired/stimulus": "^3.0.0", "@hotwired/stimulus": "^3.0.0",
"@hotwired/turbo": "^7.1.1 || ^8.0", "@hotwired/turbo": "^7.1.1 || ^8.0",
"@popperjs/core": "^2.11.8",
"@symfony/stimulus-bridge": "^3.2.0", "@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets", "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
"@symfony/webpack-encore": "^5.0.0", "@symfony/webpack-encore": "^5.0.0",
"autoprefixer": "^10.4.20", "@tailwindcss/forms": "^0.5.10",
"bootstrap": "^5.3.3", "@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.16",
"autoprefixer": "^10.4.21",
"core-js": "^3.38.0", "core-js": "^3.38.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"postcss": "^8.5.6",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"sass": "^1.83.1", "sass": "^1.83.1",
"sass-loader": "^16.0.4", "sass-loader": "^16.0.4",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"tailwindcss": "^4.1.13",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-cli": "^5.1.0", "webpack-cli": "^5.1.0",
"webpack-dev-server": "^5.2.2", "webpack-dev-server": "^5.2.2",
"webpack-notifier": "^1.15.0" "webpack-notifier": "^1.15.0"
} }
}, },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@ -1584,6 +1600,19 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dev": true,
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@jest/schemas": { "node_modules/@jest/schemas": {
"version": "29.6.3", "version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
@ -1706,6 +1735,17 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@ -1738,9 +1778,9 @@
} }
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -2211,17 +2251,6 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.27.8", "version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@ -2432,6 +2461,345 @@
"webpack": "^5.0.0" "webpack": "^5.0.0"
} }
}, },
"node_modules/@tailwindcss/forms": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
"integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mini-svg-data-uri": "^1.2.3"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz",
"integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"enhanced-resolve": "^5.18.3",
"jiti": "^2.5.1",
"lightningcss": "1.30.1",
"magic-string": "^0.30.18",
"source-map-js": "^1.2.1",
"tailwindcss": "4.1.13"
}
},
"node_modules/@tailwindcss/node/node_modules/jiti": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz",
"integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.4",
"tar": "^7.4.3"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.13",
"@tailwindcss/oxide-darwin-arm64": "4.1.13",
"@tailwindcss/oxide-darwin-x64": "4.1.13",
"@tailwindcss/oxide-freebsd-x64": "4.1.13",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.13",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.13",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.13",
"@tailwindcss/oxide-linux-x64-musl": "4.1.13",
"@tailwindcss/oxide-wasm32-wasi": "4.1.13",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.13",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.13"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz",
"integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz",
"integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz",
"integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz",
"integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz",
"integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz",
"integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz",
"integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz",
"integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz",
"integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz",
"integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
],
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.5",
"@emnapi/runtime": "^1.4.5",
"@emnapi/wasi-threads": "^1.0.4",
"@napi-rs/wasm-runtime": "^0.2.12",
"@tybys/wasm-util": "^0.10.0",
"tslib": "^2.8.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz",
"integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz",
"integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tailwindcss/oxide/node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/@tailwindcss/postcss": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz",
"integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@tailwindcss/node": "4.1.13",
"@tailwindcss/oxide": "4.1.13",
"postcss": "^8.4.41",
"tailwindcss": "4.1.13"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@trysound/sax": { "node_modules/@trysound/sax": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -3122,9 +3490,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.20", "version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -3142,11 +3510,11 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"browserslist": "^4.23.3", "browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001646", "caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7", "fraction.js": "^4.3.7",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"picocolors": "^1.0.1", "picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0" "postcss-value-parser": "^4.2.0"
}, },
"bin": { "bin": {
@ -3366,26 +3734,6 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/bootstrap": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
@ -3520,9 +3868,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001690", "version": "1.0.30001741",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz",
"integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -3571,6 +3919,16 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/chrome-trace-event": { "node_modules/chrome-trace-event": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
@ -4429,9 +4787,9 @@
} }
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.0", "version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5869,6 +6227,255 @@
"shell-quote": "^1.8.3" "shell-quote": "^1.8.3"
} }
}, },
"node_modules/lightningcss": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-darwin-arm64": "1.30.1",
"lightningcss-darwin-x64": "1.30.1",
"lightningcss-freebsd-x64": "1.30.1",
"lightningcss-linux-arm-gnueabihf": "1.30.1",
"lightningcss-linux-arm64-gnu": "1.30.1",
"lightningcss-linux-arm64-musl": "1.30.1",
"lightningcss-linux-x64-gnu": "1.30.1",
"lightningcss-linux-x64-musl": "1.30.1",
"lightningcss-win32-arm64-msvc": "1.30.1",
"lightningcss-win32-x64-msvc": "1.30.1"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss/node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@ -5937,6 +6544,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@ -5944,6 +6558,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.memoize": { "node_modules/lodash.memoize": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -5951,6 +6572,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.uniq": { "node_modules/lodash.uniq": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -5968,6 +6596,16 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/magic-string": {
"version": "0.30.18",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
"integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -6172,6 +6810,16 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"dev": true,
"license": "MIT",
"bin": {
"mini-svg-data-uri": "cli.js"
}
},
"node_modules/minimalistic-assert": { "node_modules/minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@ -6179,6 +6827,45 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"dev": true,
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"dev": true,
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -6201,9 +6888,9 @@
} }
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.8", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -6557,9 +7244,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.49", "version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -6577,7 +7264,7 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
@ -8320,6 +9007,13 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/tailwindcss": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@ -8330,6 +9024,34 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tar/node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/terser": { "node_modules/terser": {
"version": "5.37.0", "version": "5.37.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",

View file

@ -4,19 +4,22 @@
"@babel/preset-env": "^7.16.0", "@babel/preset-env": "^7.16.0",
"@hotwired/stimulus": "^3.0.0", "@hotwired/stimulus": "^3.0.0",
"@hotwired/turbo": "^7.1.1 || ^8.0", "@hotwired/turbo": "^7.1.1 || ^8.0",
"@popperjs/core": "^2.11.8",
"@symfony/stimulus-bridge": "^3.2.0", "@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets", "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
"@symfony/webpack-encore": "^5.0.0", "@symfony/webpack-encore": "^5.0.0",
"autoprefixer": "^10.4.20", "@tailwindcss/forms": "^0.5.10",
"bootstrap": "^5.3.3", "@tailwindcss/postcss": "^4.1.13",
"@tailwindcss/typography": "^0.5.16",
"autoprefixer": "^10.4.21",
"core-js": "^3.38.0", "core-js": "^3.38.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"postcss": "^8.5.6",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"sass": "^1.83.1", "sass": "^1.83.1",
"sass-loader": "^16.0.4", "sass-loader": "^16.0.4",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"tailwindcss": "^4.1.13",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-cli": "^5.1.0", "webpack-cli": "^5.1.0",
"webpack-dev-server": "^5.2.2", "webpack-dev-server": "^5.2.2",

6
postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
}

View file

@ -36,13 +36,6 @@ class GeneralController extends AbstractController
], $response); ], $response);
} }
#[Route('/reviews', name: 'app_reviews')]
public function app_reviews(EntityManagerInterface $em): Response
{
return $this->render('reviews/reviews.html.twig', [
'posts' => $em->getRepository(Post::class)->findBycat('blog', 3)
]);
}
#[Route('/professional-support', name: 'app_professional_support')] #[Route('/professional-support', name: 'app_professional_support')]
public function app_professional_support(): Response public function app_professional_support(): Response

62
tailwind.config.js Normal file
View file

@ -0,0 +1,62 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./templates/**/*.html.twig",
"./assets/**/*.js",
"./src/**/*.php",
"./public/**/*.html"
],
theme: {
extend: {
fontFamily: {
'yekan': ['Yekan Bakh FaNum', 'Tahoma', 'Arial', 'sans-serif'],
},
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#1743bb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
},
secondary: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
}
},
spacing: {
'18': '4.5rem',
'88': '22rem',
},
borderRadius: {
'4xl': '2rem',
},
boxShadow: {
'soft': '0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04)',
'medium': '0 4px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
}
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
// RTL Support
corePlugins: {
// Disable default direction utilities
direction: false,
},
}

View file

@ -10,13 +10,12 @@
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-K1R1SYQY8E"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-K1R1SYQY8E"></script>
<script> <script>
window.dataLayer = window.dataLayer || [] ; window.dataLayer = window.dataLayer || [];
function gtag() { function gtag() {
dataLayer.push(arguments) ; dataLayer.push(arguments);
} }
gtag('js', new Date()) ; gtag('js', new Date());
gtag('config', 'G-K1R1SYQY8E');
gtag('config', 'G-K1R1SYQY8E');
</script> </script>
{% if block('des') is not defined %} {% if block('des') is not defined %}
<meta content="{{twigFunctions.systemSettings.des}}" name="description"/> <meta content="{{twigFunctions.systemSettings.des}}" name="description"/>
@ -99,6 +98,111 @@ gtag('config', 'G-K1R1SYQY8E');
50% { opacity: 0.5; } 50% { opacity: 0.5; }
100% { opacity: 1; } 100% { opacity: 1; }
} }
/* استایل‌های هدر جدید */
header {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* انیمیشن hover برای لینک‌ها */
.nav-link {
position: relative;
overflow: hidden;
}
.nav-link::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1), transparent);
transition: left 0.5s;
}
.nav-link:hover::before {
left: 100%;
}
/* بهبود dropdown ها */
.group:hover .group-hover\:opacity-100 {
opacity: 1;
}
.group:hover .group-hover\:visible {
visibility: visible;
}
.group:hover .group-hover\:translate-y-0 {
transform: translateY(0);
}
/* انیمیشن برای دکمه‌ها */
.btn-primary {
position: relative;
overflow: hidden;
}
.btn-primary::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn-primary:hover::before {
left: 100%;
}
/* بهبود responsive */
@media (max-width: 1024px) {
header .container {
padding-left: 1rem;
padding-right: 1rem;
}
}
/* انیمیشن برای لوگو */
.logo-container {
transition: transform 0.3s ease;
}
.logo-container:hover {
transform: scale(1.05);
}
/* مدیریت responsive منو */
#desktopMenu {
display: none !important;
}
@media (min-width: 1024px) {
#desktopMenu {
display: flex !important;
}
/* مخفی کردن دکمه منوی موبایل در دسکتاپ */
#mobileMenuButton {
display: none !important;
}
}
@media (max-width: 1023px) {
#desktopMenu {
display: none !important;
}
/* نمایش دکمه منوی موبایل در موبایل */
#mobileMenuButton {
display: block !important;
}
}
</style> </style>
{% endblock %} {% endblock %}
@ -175,149 +279,266 @@ gtag('config', 'G-K1R1SYQY8E');
} }
}, 3000); }, 3000);
}); });
// تابع toggle برای mobile menu
function toggleMobileMenu() {
const menu = document.getElementById('navbarSupportedContent');
if (menu.classList.contains('hidden')) {
menu.classList.remove('hidden');
} else {
menu.classList.add('hidden');
}
}
// مدیریت نمایش منو بر اساس اندازه صفحه
function handleResponsiveMenu() {
const mobileMenu = document.getElementById('navbarSupportedContent');
if (window.innerWidth >= 1024) {
// دسکتاپ - مخفی کردن منوی موبایل
if (mobileMenu) {
mobileMenu.classList.add('hidden');
}
}
}
// اجرا در بارگذاری صفحه
document.addEventListener('DOMContentLoaded', function() {
handleResponsiveMenu();
});
// اجرا در تغییر اندازه صفحه
window.addEventListener('resize', function() {
handleResponsiveMenu();
});
</script> </script>
{% endblock %} {% endblock %}
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg border-bottom bg-body-tertiary shadow-sm"> <!-- هدر جدید با طراحی مدرن -->
<div class="container"> <header class="bg-white/95 backdrop-blur-md border-b border-gray-100 shadow-lg sticky top-0 z-50">
<a class="navbar-brand text-primary d-flex align-items-center gap-2" href="{{path('app_home')}}"> <div class="container mx-auto px-4 lg:px-6">
<img src="{{asset('/favicon/favicon.svg')}}" alt="نرم افزار حسابداری آنلاین حسابیکس" width="30" height="30"> <div class="flex items-center justify-between h-16">
<span class="">حسابیکس</span> <!-- لوگو و نام برند -->
<div class="flex items-center space-x-3 space-x-reverse">
<a href="{{path('app_home')}}" class="flex items-center space-x-3 space-x-reverse group logo-container">
<div class="relative">
<img src="{{asset('/favicon/favicon.svg')}}"
alt="نرم افزار حسابداری آنلاین حسابیکس"
width="36" height="36"
class="transition-transform duration-300 group-hover:scale-110">
<div class="absolute -top-1 -right-1 w-2.5 h-2.5 bg-green-500 rounded-full animate-pulse"></div>
</div>
<div class="flex flex-col">
<span class="text-xl font-bold bg-gradient-to-l from-blue-600 to-purple-600 bg-clip-text text-transparent">
حسابیکس
</span>
<span class="text-xs text-gray-500 -mt-0.5">نرم‌افزار حسابداری آنلاین</span>
</div>
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> </div>
<span class="navbar-toggler-icon"></span>
</button> <!-- منوی اصلی - دسکتاپ -->
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <nav id="desktopMenu" class="items-center space-x-1 space-x-reverse">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0"> <a href="{{path('app_guide')}}"
<li class="nav-item"> class="nav-link px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
<a class="nav-link px-3 py-2 rounded-3 transition-all" href="{{path('app_guide')}}">راهنمای جامع</a> راهنمای جامع
</li>
<li class="nav-item">
<a class="nav-link px-3 py-2 rounded-3 transition-all" href="{{path('app_professional_support')}}">پشتیبانی سازمانی</a>
</li>
<li class="nav-item">
<a class="nav-link px-3 py-2 rounded-3 transition-all" href="{{path('app_blog_home')}}">وبلاگ</a>
</li>
<li class="nav-item">
<a class="nav-link px-3 py-2 rounded-3 transition-all" href="{{path('qa_index')}}">سوالات</a>
</li>
<li class="nav-item">
<a class="nav-link px-3 py-2 rounded-3 transition-all" href="{{path('app_page',{'url':'sponsors'})}}">حامیان مالی</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link px-3 py-2 rounded-3 transition-all dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
درباره ما
</a> </a>
<ul class="dropdown-menu"> <a href="{{path('app_professional_support')}}"
<li> class="nav-link px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
<a class="dropdown-item" href="{{path('app_page',{'url':'about'})}}">داستان حسابیکس</a> پشتیبانی سازمانی
</li> </a>
<li> <a href="{{path('app_blog_home')}}"
<a class="dropdown-item" href="{{path('app_page',{'url':'contact'})}}">تماس با ما</a> class="nav-link px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
</li> وبلاگ
</ul> </a>
</li> <a href="{{path('qa_index')}}"
</ul> class="nav-link px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
<div class="d-flex gap-2"> سوالات
</a>
<a href="{{path('app_page',{'url':'sponsors'})}}"
class="nav-link px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
حامیان مالی
</a>
<a href="{{path('app_page',{'url':'about'})}}"
class="nav-link px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
داستان حسابیکس
</a>
<a href="{{path('app_page',{'url':'contact'})}}"
class="nav-link px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
تماس با ما
</a>
</nav>
<!-- دکمه‌های کاربر -->
<div class="flex items-center space-x-3 space-x-reverse">
{% if app.user and app.user.roles is defined and 'ROLE_CUSTOMER' in app.user.roles %} {% if app.user and app.user.roles is defined and 'ROLE_CUSTOMER' in app.user.roles %}
{# کاربر وارد شده - نمایش منوی داشبورد #} <!-- کاربر وارد شده -->
<div class="dropdown"> <div class="relative group">
<button class="btn btn-outline-primary rounded-4 px-3 py-2 fw-bold transition-all dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button class="flex items-center space-x-2 space-x-reverse px-3 py-2 bg-gradient-to-l from-blue-50 to-purple-50 text-gray-700 rounded-lg hover:from-blue-100 hover:to-purple-100 transition-all duration-200 border border-blue-200">
<img src="{{ asset('/img/icons/user.svg') }}" alt="کاربر" class="icon-svg icon-user me-2">{{ app.user.name }} <div class="w-7 h-7 bg-gradient-to-l from-blue-500 to-purple-500 rounded-full flex items-center justify-center">
<span class="text-white text-xs font-bold">{{ app.user.name|slice(0,1) }}</span>
</div>
<span class="font-medium text-sm">{{ app.user.name }}</span>
<svg class="w-3 h-3 transition-transform duration-200 group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button> </button>
<ul class="dropdown-menu dropdown-menu-end"> <div class="absolute left-0 mt-2 w-56 bg-white rounded-xl shadow-xl border border-gray-100 py-3 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-300 transform translate-y-2 group-hover:translate-y-0">
<li> <div class="px-4 py-2 border-b border-gray-100">
<a class="dropdown-item" href="{{ path('customer_dashboard') }}"> <div class="text-sm font-medium text-gray-900">{{ app.user.name }}</div>
<img src="{{ asset('/img/icons/cogs.svg') }}" alt="داشبورد" class="icon-svg icon-cogs me-2">داشبورد <div class="text-xs text-gray-500">عضو باشگاه مشتریان</div>
</div>
<a href="{{ path('customer_dashboard') }}"
class="flex items-center space-x-3 space-x-reverse px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 transition-colors duration-200">
<div class="w-7 h-7 bg-blue-100 rounded-lg flex items-center justify-center">
<svg class="w-3.5 h-3.5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"></path>
</svg>
</div>
<span class="text-sm">داشبورد</span>
</a> </a>
</li> <div class="px-4 py-2">
<li><hr class="dropdown-divider"></li> <a href="{{ path('customer_logout') }}"
<li> class="flex items-center space-x-3 space-x-reverse px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors duration-200">
<a class="dropdown-item text-danger" href="{{ path('customer_logout') }}"> <div class="w-7 h-7 bg-red-100 rounded-lg flex items-center justify-center">
<img src="{{ asset('/img/icons/sign-out.svg') }}" alt="خروج" class="icon-svg icon-sign-out me-2">خروج <svg class="w-3.5 h-3.5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
</svg>
</div>
<span class="text-sm">خروج</span>
</a> </a>
</li> </div>
</ul> </div>
</div> </div>
{% else %} {% else %}
{# کاربر وارد نشده - نمایش دکمه ورود #} <!-- کاربر وارد نشده -->
<a class="btn btn-outline-primary rounded-4 px-3 py-2 fw-bold transition-all" href="{{ path('customer_login') }}"> <a href="{{ path('customer_login') }}"
class="px-3 py-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200 font-medium text-sm">
باشگاه مشتریان باشگاه مشتریان
</a> </a>
{% endif %} {% endif %}
<a target="_blank" class="btn btn-primary rounded-4 px-4 py-2 fw-bold transition-all" href="https://app.hesabix.ir">
<!-- دکمه ورود/عضویت -->
<a target="_blank"
href="https://app.hesabix.ir"
class="btn-primary px-5 py-2 bg-gradient-to-l from-blue-600 to-purple-600 text-white rounded-lg hover:from-blue-700 hover:to-purple-700 transition-all duration-200 font-medium text-sm shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
ورود / عضویت ورود / عضویت
</a> </a>
<!-- دکمه منوی موبایل -->
<button id="mobileMenuButton" class="lg:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors duration-200"
type="button"
onclick="toggleMobileMenu()"
aria-label="Toggle navigation">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div>
<!-- منوی موبایل -->
<div class="lg:hidden hidden" id="navbarSupportedContent">
<div class="py-4 border-t border-gray-100">
<div class="space-y-1">
<a href="{{path('app_guide')}}"
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
راهنمای جامع
</a>
<a href="{{path('app_professional_support')}}"
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
پشتیبانی سازمانی
</a>
<a href="{{path('app_blog_home')}}"
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
وبلاگ
</a>
<a href="{{path('qa_index')}}"
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
سوالات
</a>
<a href="{{path('app_page',{'url':'sponsors'})}}"
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
حامیان مالی
</a>
<a href="{{path('app_page',{'url':'about'})}}"
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
داستان حسابیکس
</a>
<a href="{{path('app_page',{'url':'contact'})}}"
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
تماس با ما
</a>
</div> </div>
</div> </div>
</div> </div>
</nav> </div>
</header>
{% block body %}{% endblock %} {% block body %}{% endblock %}
<footer class="py-3 my-4"> <footer class="py-6 my-8">
<div class="d-flex justify-content-center align-items-center gap-3 mb-3" id="trust-seals-container"> <div class="flex justify-center items-center gap-3 mb-6" id="trust-seals-container">
<!-- نشانگرهای بارگذاری --> <!-- نشانگرهای بارگذاری -->
<div class="trust-seal-loading d-flex gap-3"> <div class="trust-seal-loading flex gap-3">
<div class="spinner-border spinner-border-sm text-primary" role="status" aria-hidden="true"></div> <div class="spinner-border" role="status" aria-hidden="true"></div>
<span class="text-muted small">در حال بارگذاری گواهی‌های اعتماد...</span> <span class="text-gray-500 text-sm">در حال بارگذاری گواهی‌های اعتماد...</span>
</div> </div>
<!-- محتوای اصلی که بعد از لود نمایش داده می‌شود --> <!-- محتوای اصلی که بعد از لود نمایش داده می‌شود -->
<div class="trust-seals-content" style="display: none;"> <div class="trust-seals-content hidden">
<a referrerpolicy='origin' target='_blank' href='https://trustseal.enamad.ir/?id=614357&Code=4ATiNTREoPRD5Lz3zwc9zyz0zWGJiZL3'> <a referrerpolicy='origin' target='_blank' href='https://trustseal.enamad.ir/?id=614357&Code=4ATiNTREoPRD5Lz3zwc9zyz0zWGJiZL3'>
<img referrerpolicy='origin' src='https://trustseal.enamad.ir/logo.aspx?id=614357&Code=4ATiNTREoPRD5Lz3zwc9zyz0zWGJiZL3' alt='گواهی اعتماد اناماد' style='cursor:pointer' code='4ATiNTREoPRD5Lz3zwc9zyz0zWGJiZL3' loading="lazy"> <img referrerpolicy='origin' src='https://trustseal.enamad.ir/logo.aspx?id=614357&Code=4ATiNTREoPRD5Lz3zwc9zyz0zWGJiZL3' alt='گواهی اعتماد اناماد' class='cursor-pointer hover:opacity-80 transition-opacity duration-300 max-h-12 w-auto' code='4ATiNTREoPRD5Lz3zwc9zyz0zWGJiZL3' loading="lazy">
</a> </a>
<a href="https://bitpay.ir/certificate-230498-hesabix.ir" target="_blank"> <a href="https://bitpay.ir/certificate-230498-hesabix.ir" target="_blank">
<img src="https://bitpay.ir/theme/public/images/trusted-logo.svg" alt="گواهی اعتماد بیت‌پی" loading="lazy"/> <img src="https://bitpay.ir/theme/public/images/trusted-logo.svg" alt="گواهی اعتماد بیت‌پی" class="hover:opacity-80 transition-opacity duration-300 max-h-12 w-auto" loading="lazy"/>
</a> </a>
<img referrerpolicy='origin' id='rgvjoeukesgtapfufukzrgvj' style='cursor:pointer' onclick='window.open("https://logo.samandehi.ir/Verify.aspx?id=380563&p=xlaomcsiobpddshwgvkaxlao", "Popup","toolbar=no, scrollbars=no, location=no, statusbar=no, menubar=no, resizable=0, width=450, height=630, top=30")' alt='گواهی اعتماد ساماندهی' src='https://logo.samandehi.ir/logo.aspx?id=380563&p=qftiaqgwlymaujynwlbqqfti' loading="lazy"/> <img referrerpolicy='origin' id='rgvjoeukesgtapfufukzrgvj' class='cursor-pointer hover:opacity-80 transition-opacity duration-300 max-h-12 w-auto' onclick='window.open("https://logo.samandehi.ir/Verify.aspx?id=380563&p=xlaomcsiobpddshwgvkaxlao", "Popup","toolbar=no, scrollbars=no, location=no, statusbar=no, menubar=no, resizable=0, width=450, height=630, top=30")' alt='گواهی اعتماد ساماندهی' src='https://logo.samandehi.ir/logo.aspx?id=380563&p=qftiaqgwlymaujynwlbqqfti' loading="lazy"/>
</div> </div>
</div> </div>
<ul class="nav justify-content-center border-bottom pb-3 mb-3"> <ul class="flex justify-center flex-wrap gap-4 border-b border-gray-200 pb-6 mb-6">
<li class="nav-item"> <li>
<a target="_blank" href="https://azadbeh.ir/projects/%D8%AD%D8%B3%D8%A7%D8%A8%DB%8C%DA%A9%D8%B3" class="nav-link px-2">فرصت‌های شغلی</a> <a target="_blank" href="https://azadbeh.ir/projects/%D8%AD%D8%B3%D8%A7%D8%A8%DB%8C%DA%A9%D8%B3" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">فرصت‌های شغلی</a>
</li> </li>
<li class="nav-item"> <li>
<a href="{{path('app_page',{'url':'hsx'})}}" class="nav-link px-2">توکن HSX</a> <a href="{{path('app_page',{'url':'hsx'})}}" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">توکن HSX</a>
</li> </li>
<li class="nav-item"> <li>
<a href="{{path('app_api_docs',{'url':'home'})}}" class="nav-link px-2">مستندات API</a> <a href="{{path('app_api_docs',{'url':'home'})}}" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">مستندات API</a>
</li> </li>
<li class="nav-item"> <li>
<a href="{{path('app_changes')}}" class="nav-link px-2">تغییرات</a> <a href="{{path('app_changes')}}" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">تغییرات</a>
</li> </li>
<li class="nav-item"> <li>
<a href="https://source.hesabix.ir/morrning" target="_blank" class="nav-link px-2">مخازن کد</a> <a href="https://source.hesabix.ir/morrning" target="_blank" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">مخازن کد</a>
</li> </li>
<li class="nav-item"> <li>
<a href="{{path('app_page',{'url':'open-source'})}}" class="nav-link px-2">متن‌باز</a> <a href="{{path('app_page',{'url':'open-source'})}}" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">متن‌باز</a>
</li> </li>
<li class="nav-item"> <li>
<a href="{{path('app_page',{'url':'inMedia'})}}" class="nav-link px-2">در رسانه‌ها</a> <a href="{{path('app_page',{'url':'inMedia'})}}" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">در رسانه‌ها</a>
</li> </li>
<li class="nav-item"> <li>
<a href="{{path('app_page',{'url':'terms'})}}" class="nav-link px-2">قوانین ارائه خدمات</a> <a href="{{path('app_page',{'url':'terms'})}}" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">قوانین ارائه خدمات</a>
</li> </li>
<li class="nav-item"> <li>
<a href="{{path('app_page',{'url':'privacy'})}}" class="nav-link px-2">حریم خصوصی</a> <a href="{{path('app_page',{'url':'privacy'})}}" class="text-gray-600 hover:text-primary-600 px-2 py-1 transition-colors duration-200">حریم خصوصی</a>
</li> </li>
</ul> </ul>
<div class="text-center p-1 m-1"> <div class="text-center p-4">
<div class="row mx-0"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="col-sm-12 col-md-6 text-center text-body-tertiary"> <div class="text-center text-gray-500">
<img src={{asset('/img/sp/parspack.svg')}} alt="Parspack Co" width="25" class="img-fluid"> <img src={{asset('/img/sp/parspack.svg')}} alt="Parspack Co" width="25" class="inline-block">
با خاطری آسوده،استوار بر روی راهکار‌های ابری با خاطری آسوده،استوار بر روی راهکار‌های ابری
<a href="https://parspack.com" target="_blank" class="rul">پارس‌پک</a> <a href="https://parspack.com" target="_blank" class="text-primary-600 hover:underline">پارس‌پک</a>
</div> </div>
<div class="col-sm-12 col-md-6 text-center text-body-tertiary"> <div class="text-center text-gray-500">
حسابیکس با حسابیکس با
<img src={{asset('/img/heart.png')}} alt="Love" width="25" class="img-fluid"> <img src={{asset('/img/heart.png')}} alt="Love" width="25" class="inline-block">
متن باز است متن باز است
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>
</body> </body>

View file

@ -4,353 +4,78 @@
{% block stylesheets %} {% block stylesheets %}
{{ parent() }} {{ parent() }}
<style>
/* تنظیمات کلی برای فارسی */
.customer-dashboard * {
font-family: 'Yekan Bakh FaNum', 'Tahoma', 'Arial', sans-serif;
}
.customer-dashboard {
min-height: 80vh;
padding: 40px 0;
direction: rtl;
text-align: right;
}
.dashboard-header {
background: linear-gradient(135deg, #0d6efd 0%, #6610f2 100%);
color: white;
padding: 40px 0;
margin-bottom: 40px;
direction: rtl;
}
.dashboard-header h1 {
direction: rtl;
text-align: right;
}
.dashboard-header p {
direction: rtl;
text-align: right;
}
.dashboard-card {
background: white;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
padding: 30px;
margin-bottom: 30px;
transition: transform 0.3s ease;
direction: rtl;
}
.dashboard-card:hover {
transform: translateY(-5px);
}
.dashboard-card h3 {
color: #0d6efd;
margin-bottom: 20px;
font-weight: bold;
direction: rtl;
text-align: right;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #e9ecef;
direction: rtl;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-weight: 600;
color: #495057;
direction: rtl;
}
.info-value {
color: #6c757d;
direction: rtl;
}
.status-badge {
padding: 5px 15px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
direction: rtl;
}
.status-active {
background: #d1edff;
color: #0c5460;
}
.status-inactive {
background: #f8d7da;
color: #721c24;
}
.action-buttons {
margin-top: 30px;
direction: rtl;
text-align: center;
}
.btn-customer {
background: linear-gradient(135deg, #0d6efd 0%, #6610f2 100%);
border: none;
border-radius: 10px;
padding: 12px 25px;
color: white;
text-decoration: none;
font-weight: 500;
margin: 5px;
display: inline-block;
transition: all 0.3s ease;
direction: rtl;
}
.btn-customer:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(13, 110, 253, 0.4);
color: white;
}
.welcome-message {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
direction: rtl;
text-align: right;
}
.welcome-message h4 {
direction: rtl;
text-align: right;
}
.welcome-message p {
direction: rtl;
text-align: right;
}
/* بهبود نمایش آیکون‌های SVG */
.icon-svg {
width: 16px;
height: 16px;
margin-left: 5px;
margin-right: 0;
display: inline-block;
vertical-align: middle;
}
/* تغییر رنگ آیکون‌های SVG */
.icon-svg svg {
fill: currentColor;
width: 100%;
height: 100%;
}
/* رنگ‌های مختلف برای آیکون‌ها */
.text-primary .icon-svg svg {
fill: #0d6efd;
}
.text-success .icon-svg svg {
fill: #198754;
}
.text-danger .icon-svg svg {
fill: #dc3545;
}
.text-warning .icon-svg svg {
fill: #ffc107;
}
.text-info .icon-svg svg {
fill: #0dcaf0;
}
.text-muted .icon-svg svg {
fill: #6c757d;
}
.text-white .icon-svg svg {
fill: #ffffff;
}
/* بهبود نمایش متن‌های فارسی */
.text-center {
direction: rtl;
}
.text-end {
direction: rtl;
text-align: right;
}
/* استایل‌های کیف پول */
.wallet-connect-form {
background: #ffffff;
padding: 25px;
border-radius: 12px;
margin-bottom: 25px;
border: 1px solid #e9ecef;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.wallet-connect-form h5 {
color: #0d6efd;
margin-bottom: 20px;
direction: rtl;
text-align: right;
font-weight: 600;
}
#walletAddress {
font-family: 'Courier New', monospace;
font-size: 14px;
direction: ltr;
text-align: left;
}
#walletAddress:read-only {
background-color: #f8f9fa;
cursor: not-allowed;
border: 1px solid #e9ecef;
}
.wallet-connect-form .input-group-text {
background-color: #e9ecef;
border: 1px solid #e9ecef;
color: #6c757d;
}
.wallet-connect-form .btn-lg {
padding: 12px 24px;
font-size: 1.1rem;
font-weight: 600;
}
.wallet-connect-form .btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.wallets-list table {
direction: rtl;
margin-top: 20px;
}
.wallets-list th,
.wallets-list td {
text-align: right;
vertical-align: middle;
padding: 12px;
}
.wallets-list th {
background-color: #f8f9fa;
font-weight: 600;
border-bottom: 2px solid #dee2e6;
}
.wallets-list code {
font-family: 'Courier New', monospace;
font-size: 12px;
background: #f8f9fa;
padding: 4px 8px;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.btn-group-sm .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.btn-group-sm .btn i {
margin-left: 4px;
}
</style>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<div class="customer-dashboard"> <div class="min-h-screen py-10 font-yekan" dir="rtl">
<div class="container"> <div class="container mx-auto px-4">
<div class="welcome-message"> <div class="bg-gradient-to-r from-green-500 to-teal-500 text-white p-6 rounded-xl mb-10">
<h4><img src="{{ asset('/img/icons/heart.svg') }}" alt="قلب" class="icon-svg icon-heart"> از عضویت شما در باشگاه مشتریان حسابیکس سپاسگزاریم!</h4> <h4 class="text-xl font-bold mb-2 flex items-center gap-2">
<img src="{{ asset('/img/icons/heart.svg') }}" alt="قلب" class="w-5 h-5"> از عضویت شما در باشگاه مشتریان حسابیکس سپاسگزاریم!
</h4>
<p class="mb-0">در این بخش می‌توانید اطلاعات حساب کاربری خود را مشاهده و مدیریت کنید.</p> <p class="mb-0">در این بخش می‌توانید اطلاعات حساب کاربری خود را مشاهده و مدیریت کنید.</p>
</div> </div>
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-md-6"> <div class="w-full">
<div class="dashboard-card"> <div class="card hover:shadow-lg transition-shadow duration-300">
<h3><img src="{{ asset('/img/icons/user.svg') }}" alt="کاربر" class="icon-svg icon-user"> اطلاعات شخصی</h3> <h3 class="text-primary-600 mb-6 text-xl font-bold flex items-center gap-2">
<img src="{{ asset('/img/icons/user.svg') }}" alt="کاربر" class="w-5 h-5"> اطلاعات شخصی
</h3>
<div class="info-item"> <div class="flex justify-between items-center py-4 border-b border-gray-200">
<span class="info-label">نام و نام خانوادگی:</span> <span class="font-semibold text-gray-700">نام و نام خانوادگی:</span>
<span class="info-value">{{ user.name }}</span> <span class="text-gray-600">{{ user.name }}</span>
</div> </div>
<div class="info-item"> <div class="flex justify-between items-center py-4 border-b border-gray-200">
<span class="info-label">پست الکترونیکی:</span> <span class="font-semibold text-gray-700">پست الکترونیکی:</span>
<span class="info-value">{{ user.email }}</span> <span class="text-gray-600">{{ user.email }}</span>
</div> </div>
<div class="info-item"> <div class="flex justify-between items-center py-4 border-b border-gray-200">
<span class="info-label">شماره موبایل:</span> <span class="font-semibold text-gray-700">شماره موبایل:</span>
<span class="info-value">{{ user.phone }}</span> <span class="text-gray-600">{{ user.phone }}</span>
</div> </div>
<div class="info-item"> <div class="flex justify-between items-center py-4">
<span class="info-label">وضعیت حساب:</span> <span class="font-semibold text-gray-700">وضعیت حساب:</span>
<span class="status-badge {{ user.isActive ? 'status-active' : 'status-inactive' }}"> <span class="px-4 py-2 rounded-full text-sm font-bold {{ user.isActive ? 'bg-blue-100 text-blue-800' : 'bg-red-100 text-red-800' }}">
{{ user.isActive ? 'فعال' : 'غیرفعال' }} {{ user.isActive ? 'فعال' : 'غیرفعال' }}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="w-full">
<div class="dashboard-card"> <div class="card hover:shadow-lg transition-shadow duration-300">
<h3><img src="{{ asset('/img/icons/calendar.svg') }}" alt="تقویم" class="icon-svg icon-calendar"> اطلاعات عضویت</h3> <h3 class="text-primary-600 mb-6 text-xl font-bold flex items-center gap-2">
<img src="{{ asset('/img/icons/calendar.svg') }}" alt="تقویم" class="w-5 h-5"> اطلاعات عضویت
</h3>
<div class="info-item"> <div class="flex justify-between items-center py-4 border-b border-gray-200">
<span class="info-label">تاریخ عضویت:</span> <span class="font-semibold text-gray-700">تاریخ عضویت:</span>
<span class="info-value">{{ user.createdAt|date('Y/m/d') }}</span> <span class="text-gray-600">{{ user.createdAt|date('Y/m/d') }}</span>
</div> </div>
<div class="info-item"> <div class="flex justify-between items-center py-4">
<span class="info-label">آخرین ورود:</span> <span class="font-semibold text-gray-700">آخرین ورود:</span>
<span class="info-value"> <span class="text-gray-600">
{{ user.lastLoginAt ? user.lastLoginAt|date('Y/m/d H:i') : 'هنوز وارد نشده' }} {{ user.lastLoginAt ? user.lastLoginAt|date('Y/m/d H:i') : 'هنوز وارد نشده' }}
</span> </span>
</div> </div>
<div class="info-item"> <div class="flex justify-between items-center py-4 border-b border-gray-200">
<span class="info-label">تایید ایمیل:</span> <span class="font-semibold text-gray-700">تایید ایمیل:</span>
<span class="status-badge {{ user.emailVerifiedAt ? 'status-active' : 'status-inactive' }}"> <span class="px-4 py-2 rounded-full text-sm font-bold {{ user.emailVerifiedAt ? 'bg-blue-100 text-blue-800' : 'bg-red-100 text-red-800' }}">
{{ user.emailVerifiedAt ? 'تایید شده' : 'تایید نشده' }} {{ user.emailVerifiedAt ? 'تایید شده' : 'تایید نشده' }}
</span> </span>
</div> </div>
{% if user.subscriptionType %} {% if user.subscriptionType %}
<div class="info-item"> <div class="flex justify-between items-center py-4">
<span class="info-label">نوع اشتراک:</span> <span class="font-semibold text-gray-700">نوع اشتراک:</span>
<span class="info-value">{{ user.subscriptionType }}</span> <span class="text-gray-600">{{ user.subscriptionType }}</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -358,21 +83,22 @@
</div> </div>
<!-- بخش کیف پول‌ها --> <!-- بخش کیف پول‌ها -->
<div class="row"> <div class="w-full mt-8">
<div class="col-12"> <div class="card">
<div class="dashboard-card wallet-section"> <h3 class="text-primary-600 mb-6 text-xl font-bold flex items-center gap-2">
<h3><img src="{{ asset('/img/icons/wallet.svg') }}" alt="کیف پول" class="icon-svg icon-wallet"> مدیریت کیف پول‌ها</h3> <img src="{{ asset('/img/icons/wallet.svg') }}" alt="کیف پول" class="w-5 h-5"> مدیریت کیف پول‌ها
</h3>
<!-- فرم اتصال کیف پول جدید --> <!-- فرم اتصال کیف پول جدید -->
<div class="wallet-connect-form mb-4" <div class="bg-white p-6 rounded-xl border border-gray-200 mb-6"
data-controller="wallet-connect" data-controller="wallet-connect"
data-wallet-connect-sign-message-value="{{ signMessage }}" data-wallet-connect-sign-message-value="{{ signMessage }}"
data-wallet-connect-csrf-token-value="{{ csrf_token('wallet_connect_form') }}"> data-wallet-connect-csrf-token-value="{{ csrf_token('wallet_connect_form') }}">
<h5>اتصال کیف پول جدید</h5> <h5 class="text-lg font-semibold mb-4">اتصال کیف پول جدید</h5>
<div class="row g-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="col-md-6"> <div>
<label for="walletType" class="form-label">نوع کیف پول</label> <label for="walletType" class="form-label">نوع کیف پول</label>
<select class="form-select" id="walletType" data-wallet-connect-target="walletType" data-action="change->wallet-connect#onWalletTypeChange"> <select class="form-control" id="walletType" data-wallet-connect-target="walletType" data-action="change->wallet-connect#onWalletTypeChange">
<option value="">انتخاب کنید...</option> <option value="">انتخاب کنید...</option>
<option value="metamask">MetaMask</option> <option value="metamask">MetaMask</option>
<option value="trust">Trust Wallet</option> <option value="trust">Trust Wallet</option>
@ -381,24 +107,24 @@
<option value="other">سایر</option> <option value="other">سایر</option>
</select> </select>
</div> </div>
<div class="col-md-6"> <div>
<label class="form-label">آدرس کیف پول</label> <label class="form-label">آدرس کیف پول</label>
<div class="input-group"> <div class="relative">
<span class="input-group-text"> <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<i class="fas fa-wallet"></i> <i class="fas fa-wallet text-gray-400"></i>
</span> </div>
<input type="text" <input type="text"
class="form-control" class="form-control pr-10"
id="walletAddress" id="walletAddress"
data-wallet-connect-target="walletAddress" data-wallet-connect-target="walletAddress"
placeholder="آدرس کیف پول پس از اتصال نمایش داده می‌شود" placeholder="آدرس کیف پول پس از اتصال نمایش داده می‌شود"
readonly> readonly>
</div> </div>
<div class="form-text">آدرس کیف پول شما پس از اتصال به صورت خودکار پر می‌شود</div> <p class="text-sm text-gray-500 mt-2">آدرس کیف پول شما پس از اتصال به صورت خودکار پر می‌شود</p>
</div> </div>
<div class="col-12"> <div class="col-span-2">
<button type="button" <button type="button"
class="btn btn-primary btn-lg w-100" class="btn-primary w-full py-3 text-lg"
id="connectBtn" id="connectBtn"
data-wallet-connect-target="connectBtn" data-wallet-connect-target="connectBtn"
data-action="click->wallet-connect#connectWallet" data-action="click->wallet-connect#connectWallet"
@ -411,57 +137,57 @@
</div> </div>
<!-- لیست کیف پول‌های متصل --> <!-- لیست کیف پول‌های متصل -->
<div class="wallets-list"> <div class="mt-6">
<h5>کیف پول‌های متصل ({{ user.wallets|length }}/5)</h5> <h5 class="text-lg font-semibold mb-4">کیف پول‌های متصل ({{ user.wallets|length }}/5)</h5>
{% if user.wallets|length > 0 %} {% if user.wallets|length > 0 %}
<div class="table-responsive"> <div class="overflow-x-auto">
<table class="table table-striped"> <table class="min-w-full divide-y divide-gray-200">
<thead> <thead class="bg-gray-50">
<tr> <tr>
<th>آدرس کیف پول</th> <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">آدرس کیف پول</th>
<th>نوع</th> <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">نوع</th>
<th>وضعیت</th> <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">وضعیت</th>
<th>تاریخ اتصال</th> <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">تاریخ اتصال</th>
<th>عملیات</th> <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">عملیات</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-200">
{% for wallet in user.wallets %} {% for wallet in user.wallets %}
<tr> <tr>
<td> <td class="px-6 py-4 whitespace-nowrap">
<code>{{ wallet.shortAddress }}</code> <code class="text-sm bg-gray-100 px-2 py-1 rounded">{{ wallet.shortAddress }}</code>
{% if wallet.isPrimary %} {% if wallet.isPrimary %}
<span class="badge bg-primary ms-2">اصلی</span> <span class="bg-primary-100 text-primary-800 text-xs px-2 py-1 rounded-full mr-2">اصلی</span>
{% endif %} {% endif %}
</td> </td>
<td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<span class="badge bg-info">{{ wallet.walletType }}</span> <span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">{{ wallet.walletType }}</span>
</td> </td>
<td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<span class="badge {{ wallet.isActive ? 'bg-success' : 'bg-danger' }}"> <span class="px-2 py-1 rounded-full text-xs font-medium {{ wallet.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
{{ wallet.isActive ? 'فعال' : 'غیرفعال' }} {{ wallet.isActive ? 'فعال' : 'غیرفعال' }}
</span> </span>
</td> </td>
<td>{{ wallet.connectedAt|date('Y/m/d H:i') }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ wallet.connectedAt|date('Y/m/d H:i') }}</td>
<td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<div class="btn-group btn-group-sm"> <div class="flex gap-2">
{% if not wallet.isPrimary and wallet.isActive %} {% if not wallet.isPrimary and wallet.isActive %}
<button class="btn btn-outline-primary btn-sm" <button class="btn-outline-primary text-xs px-3 py-1"
data-wallet-id="{{ wallet.id }}" data-wallet-id="{{ wallet.id }}"
data-action="click->wallet-connect#setPrimaryWallet"> data-action="click->wallet-connect#setPrimaryWallet">
<i class="fas fa-star"></i> اصلی <i class="fas fa-star"></i> اصلی
</button> </button>
{% endif %} {% endif %}
<button class="btn btn-outline-warning btn-sm" <button class="btn-outline-warning text-xs px-3 py-1"
data-wallet-id="{{ wallet.id }}" data-wallet-id="{{ wallet.id }}"
data-action="click->wallet-connect#toggleWalletStatus"> data-action="click->wallet-connect#toggleWalletStatus">
<i class="fas fa-{{ wallet.isActive ? 'pause' : 'play' }}"></i> <i class="fas fa-{{ wallet.isActive ? 'pause' : 'play' }}"></i>
{{ wallet.isActive ? 'غیرفعال' : 'فعال' }} {{ wallet.isActive ? 'غیرفعال' : 'فعال' }}
</button> </button>
<button class="btn btn-outline-danger btn-sm" <button class="btn-outline-danger text-xs px-3 py-1"
data-wallet-id="{{ wallet.id }}" data-wallet-id="{{ wallet.id }}"
data-action="click->wallet-connect#deleteWallet"> data-action="click->wallet-connect#deleteWallet">
<i class="fas fa-trash"></i> حذف <i class="fas fa-trash"></i> حذف
@ -474,10 +200,10 @@
</table> </table>
</div> </div>
{% else %} {% else %}
<div class="text-center text-muted py-5"> <div class="text-center text-gray-500 py-12">
<i class="fas fa-wallet fa-4x mb-4 text-muted"></i> <i class="fas fa-wallet text-6xl mb-4 text-gray-300"></i>
<h5 class="text-muted">هنوز هیچ کیف پولی متصل نشده است</h5> <h5 class="text-gray-500 text-lg mb-2">هنوز هیچ کیف پولی متصل نشده است</h5>
<p class="text-muted">برای شروع، نوع کیف پول خود را انتخاب کنید و روی دکمه اتصال کلیک کنید</p> <p class="text-gray-400">برای شروع، نوع کیف پول خود را انتخاب کنید و روی دکمه اتصال کلیک کنید</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -485,29 +211,29 @@
</div> </div>
</div> </div>
<div class="row"> <div class="w-full mt-8">
<div class="col-12"> <div class="card">
<div class="dashboard-card"> <h3 class="text-primary-600 mb-6 text-xl font-bold flex items-center gap-2">
<h3><img src="{{ asset('/img/icons/cogs.svg') }}" alt="تنظیمات" class="icon-svg icon-cogs"> عملیات</h3> <img src="{{ asset('/img/icons/cogs.svg') }}" alt="تنظیمات" class="w-5 h-5"> عملیات
</h3>
<div class="action-buttons"> <div class="flex flex-wrap gap-4 justify-center">
<a href="{{ path('customer_forgot_password') }}" class="btn-customer"> <a href="{{ path('customer_forgot_password') }}" class="btn-primary flex items-center gap-2">
<img src="{{ asset('/img/icons/key.svg') }}" alt="کلید" class="icon-svg icon-key"> بازیابی کلمه عبور <img src="{{ asset('/img/icons/key.svg') }}" alt="کلید" class="w-4 h-4"> بازیابی کلمه عبور
</a> </a>
<a href="{{ path('app_home') }}" class="btn-customer"> <a href="{{ path('app_home') }}" class="btn-primary flex items-center gap-2">
<img src="{{ asset('/img/icons/home.svg') }}" alt="خانه" class="icon-svg icon-home"> بازگشت به صفحه اصلی <img src="{{ asset('/img/icons/home.svg') }}" alt="خانه" class="w-4 h-4"> بازگشت به صفحه اصلی
</a> </a>
<a href="{{ path('customer_logout') }}" class="btn-customer" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);"> <a href="{{ path('customer_logout') }}" class="bg-gradient-to-r from-red-500 to-red-600 text-white px-6 py-3 rounded-lg font-medium transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 flex items-center gap-2">
<img src="{{ asset('/img/icons/sign-out.svg') }}" alt="خروج" class="icon-svg icon-sign-out"> خروج <img src="{{ asset('/img/icons/sign-out.svg') }}" alt="خروج" class="w-4 h-4"> خروج
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block javascripts %} {% block javascripts %}

File diff suppressed because it is too large Load diff

View file

@ -8,117 +8,165 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<main class="container-fluid px-0"> <main class="min-h-screen bg-gray-50">
<div class="blog-header position-relative"> <!-- هدر وبلاگ -->
<div class="overlay"></div> <div class="relative bg-gradient-to-br from-blue-600 via-purple-600 to-indigo-700 overflow-hidden">
<div class="container position-relative"> <div class="absolute inset-0 bg-black/40"></div>
<div class="row min-vh-50 align-items-center"> <div class="relative container mx-auto px-4 py-20 lg:py-24">
<div class="col-12 text-center text-white"> <div class="text-center text-white">
<h1 class="display-4 fw-bold mb-4">وبلاگ حسابیکس</h1> <h1 class="text-4xl lg:text-6xl font-bold mb-6 animate-fade-in-up">
<p class="" style="font-family: 'Yekan Bakh FaNum', sans-serif;">جدیدترین اطلاعات و خبرها از دنیای حسابداری</p> وبلاگ حسابیکس
</div> </h1>
<p class="text-lg lg:text-xl text-blue-100 max-w-2xl mx-auto animate-fade-in-up" style="animation-delay: 0.2s;">
جدیدترین اطلاعات و خبرها از دنیای حسابداری
</p>
</div> </div>
</div> </div>
</div> </div>
<div class="container mt-5 mb-5"> <!-- محتوای اصلی -->
<div class="row"> <div class="container mx-auto px-4 py-12">
<div class="col-md-12"> <!-- جستجو -->
<div class="row g-4"> <div class="mb-8">
<form method="GET" action="{{ path('app_blog_home') }}" class="max-w-md mx-auto">
<div class="relative">
<input type="text"
name="search"
value="{{ search }}"
placeholder="جستجو در وبلاگ..."
class="w-full px-4 py-3 pr-12 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-right">
<button type="submit"
class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-blue-600 transition-colors duration-200">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
</div>
</form>
</div>
<!-- لیست پست‌ها -->
{% if posts|length > 0 %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12">
{% for post in posts %} {% for post in posts %}
<div class="col-md-4"> <article class="group bg-white rounded-2xl shadow-soft hover:shadow-medium transition-all duration-300 overflow-hidden hover-lift">
<article class="card h-100 border-0 shadow-sm hover-shadow transition-all"> <!-- تصویر پست -->
<div class="position-relative"> <div class="relative overflow-hidden">
<img src="{{asset('uploaded/'~ post.mainPic)}}" alt="{{post.title}}" class="card-img-top object-fit-cover" style="height: 200px;"> <img src="{{asset('uploaded/'~ post.mainPic)}}"
<div class="position-absolute top-0 end-0 m-3"> alt="{{post.title}}"
class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300">
<div class="absolute top-4 right-4 flex flex-wrap gap-2">
{% for tree in post.tree %} {% for tree in post.tree %}
<span class="badge bg-primary me-1">{{ tree.label }}</span> <span class="px-3 py-1 bg-blue-600 text-white text-xs font-medium rounded-full shadow-lg">
{{ tree.label }}
</span>
{% endfor %} {% endfor %}
</div> </div>
<div class="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</div> </div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2"> <!-- محتوای کارت -->
<small class="text-muted"> <div class="p-6">
<i class="fas fa-calendar-alt me-1"></i> <!-- متا اطلاعات -->
{{ Jdate.jdate('Y/n/d',post.dateSubmit) }} <div class="flex items-center justify-between text-sm text-gray-500 mb-4">
</small> <div class="flex items-center space-x-2 space-x-reverse">
<small class="text-muted"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<i class="fas fa-eye me-1"></i> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
{{ post.views }} </svg>
</small> <span>{{ Jdate.jdate('Y/n/d',post.dateSubmit) }}</span>
</div> </div>
<h5 class="card-title text-primary fw-bold">{{ post.title }}</h5> <div class="flex items-center space-x-2 space-x-reverse">
<p class="card-text text-muted">{{ post.intro }}</p> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<a href="{{path('app_blog_post',{'url':post.url})}}" class="btn btn-primary rounded-pill stretched-link"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
ادامه مطلب <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
<i class="fas fa-arrow-left me-2"></i> </svg>
<span>{{ post.views }}</span>
</div>
</div>
<!-- عنوان و توضیحات -->
<h3 class="text-xl font-bold text-gray-900 mb-3 line-clamp-2 group-hover:text-blue-600 transition-colors duration-200">
{{ post.title }}
</h3>
<p class="text-gray-600 mb-6 line-clamp-3 leading-relaxed">
{{ post.intro }}
</p>
<!-- دکمه ادامه مطلب -->
<a href="{{path('app_blog_post',{'url':post.url})}}"
class="inline-flex items-center space-x-2 space-x-reverse text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200 group">
<span>ادامه مطلب</span>
<svg class="w-4 h-4 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</a> </a>
</div> </div>
</article> </article>
</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="row mt-5"> <!-- صفحه‌بندی -->
<nav aria-label="Page navigation"> {% if maxpages > 1 %}
<ul class="pagination justify-content-center"> <nav class="flex justify-center" aria-label="صفحه‌بندی">
<li class="page-item {% if page == 1 %}disabled{% endif %}"> <div class="flex items-center space-x-2 space-x-reverse">
<a href="{{ path('app_blog_home',{'page':page -1})}}" class="page-link rounded-pill mx-1"> <!-- دکمه صفحه قبل -->
<i class="fas fa-chevron-right"></i> {% if page > 1 %}
صفحه قبل <a href="{{ path('app_blog_home',{'page':page -1})}}"
class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
<span>قبلی</span>
</a> </a>
</li>
<li class="page-item">
<a class="page-link rounded-pill mx-1 active" href="{{ path('app_blog_home',{'page':page })}}">{{page}}</a>
</li>
{% if (page + 1) <= maxpages %}
<li class="page-item">
<a class="page-link rounded-pill mx-1" href="{{ path('app_blog_home',{'page':page +1})}}">{{page +1}}</a>
</li>
{% endif %} {% endif %}
{% if (page + 2) <= maxpages %}
<li class="page-item"> <!-- شماره صفحات -->
<a class="page-link rounded-pill mx-1" href="{{ path('app_blog_home',{'page':page +2})}}">{{page + 2}}</a> <div class="flex items-center space-x-1 space-x-reverse">
</li> {% for i in range(1, maxpages) %}
{% endif %} {% if i == page %}
{% if (page + 3) <= maxpages %} <span class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-lg">
<li class="page-item"> {{ i }}
<a class="page-link rounded-pill mx-1" href="{{ path('app_blog_home',{'page':page +3})}}">{{page + 3}}</a> </span>
</li> {% elseif i <= page + 2 and i >= page - 2 %}
{% endif %} <a href="{{ path('app_blog_home',{'page':i})}}"
<li class="page-item"> class="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
<a href="{{ path('app_blog_home',{'page':page +1})}}" class="page-link rounded-pill mx-1 {% if (page + 1) > maxpages %}disabled{% endif %}"> {{ i }}
صفحه بعدی
<i class="fas fa-chevron-left"></i>
</a> </a>
</li> {% endif %}
</ul> {% endfor %}
</div>
<!-- دکمه صفحه بعد -->
{% if page < maxpages %}
<a href="{{ path('app_blog_home',{'page':page +1})}}"
class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
<span>بعدی</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
</a>
{% endif %}
</div>
</nav> </nav>
{% endif %}
{% else %}
<!-- پیام عدم وجود پست -->
<div class="text-center py-16">
<div class="max-w-md mx-auto">
<svg class="w-16 h-16 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<h3 class="text-xl font-semibold text-gray-900 mb-2">هیچ پستی یافت نشد</h3>
<p class="text-gray-600">
{% if search %}
برای عبارت "{{ search }}" هیچ نتیجه‌ای یافت نشد.
{% else %}
هنوز هیچ پستی منتشر نشده است.
{% endif %}
</p>
</div> </div>
</div> </div>
</div> {% endif %}
</div> </div>
</main> </main>
<style>
.blog-header {
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ asset('img/blog/blog-header.jpg') }}');
background-size: cover;
background-position: center;
min-height: 250px;
}
.hover-shadow {
transition: all 0.3s ease;
}
.hover-shadow:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
}
.min-vh-50 {
min-height: 250px;
}
.object-fit-cover {
object-fit: cover;
}
</style>
{% endblock %} {% endblock %}

View file

@ -11,119 +11,174 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<style>
.blog-post {
transition: all 0.3s ease;
}
.blog-post:hover {
transform: translateY(-5px);
}
.post-image {
border-radius: 15px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.post-image:hover {
transform: scale(1.02);
}
.post-card {
border: none;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
border-radius: 15px;
}
.post-card:hover {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.recent-posts .card {
border: none;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
border-radius: 15px;
overflow: hidden;
}
.recent-posts .card:hover {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.recent-posts .card img {
transition: transform 0.3s ease;
}
.recent-posts .card:hover img {
transform: scale(1.1);
}
.post-title {
font-size: 1.5rem;
font-weight: 600;
line-height: 1.4;
margin-bottom: 1.5rem;
font-family: inherit;
}
.post-intro {
font-size: 1.1rem;
line-height: 1.8;
color: #555;
}
.post-meta {
font-size: 0.9rem;
color: #666;
}
.post-content {
font-size: 1.1rem;
line-height: 1.8;
}
</style>
{% if item.plain is not null %} {% if item.plain is not null %}
{{ item.plain | raw}} {{ item.plain | raw}}
{% endif %} {% endif %}
{% if item.body is not null %} {% if item.body is not null %}
<div class="container mt-4"> <main class="min-h-screen bg-gray-50">
<div class="row"> <!-- هدر پست -->
<div class="col-sm-12 col-md-8"> <div class="bg-white border-b border-gray-200">
<article class="blog-post mb-4"> <div class="container mx-auto px-4 py-8">
<img class="img-fluid post-image mb-4" src="{{asset('uploaded/'~ item.mainPic)}}" alt="{{item.title}}"/> <div class="max-w-4xl mx-auto">
<h1 class="text-primary fs-5">{{item.title}}</h1> <!-- برچسب‌ها -->
<div class="card post-card bg-body-tertiary mb-4"> {% if item.tree|length > 0 %}
<div class="card-body"> <div class="flex flex-wrap gap-2 mb-4">
<div class="post-intro card-text mb-3"> {% for tree in item.tree %}
<span class="px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full">
{{ tree.label }}
</span>
{% endfor %}
</div>
{% endif %}
<!-- عنوان -->
<h1 class="text-3xl lg:text-4xl font-bold text-gray-900 mb-6 leading-tight">
{{item.title}}
</h1>
<!-- متا اطلاعات -->
<div class="flex flex-wrap items-center gap-6 text-sm text-gray-600 mb-8">
<div class="flex items-center space-x-2 space-x-reverse">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
</svg>
<span>توسط <strong class="text-gray-900">{{item.submitter.name}}</strong></span>
</div>
<div class="flex items-center space-x-2 space-x-reverse">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<span>{{Jdate.jdate('Y/n/d',item.dateSubmit)}}</span>
</div>
<div class="flex items-center space-x-2 space-x-reverse">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
</svg>
<span>{{ item.views }} بازدید</span>
</div>
</div>
</div>
</div>
</div>
<!-- محتوای اصلی -->
<div class="container mx-auto px-4 py-12">
<div class="max-w-6xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12">
<!-- محتوای پست -->
<div class="lg:col-span-2">
<article class="prose prose-lg max-w-none">
<!-- تصویر اصلی -->
<div class="mb-8">
<img src="{{asset('uploaded/'~ item.mainPic)}}"
alt="{{item.title}}"
class="w-full h-64 lg:h-80 object-cover rounded-2xl shadow-lg hover:shadow-xl transition-shadow duration-300">
</div>
<!-- خلاصه پست -->
{% if item.intro %}
<div class="bg-blue-50 border-r-4 border-blue-500 p-6 rounded-lg mb-8">
<p class="text-lg text-gray-700 leading-relaxed font-medium">
{{item.intro}} {{item.intro}}
</p>
</div> </div>
<div class="post-meta card-text"> {% endif %}
<figure>
<blockquote class="blockquote"></blockquote> <!-- محتوای پست -->
<figcaption class="blockquote-footer"> <div class="prose prose-lg max-w-none text-gray-800 leading-relaxed">
توسط
<strong>{{item.submitter.name}}</strong>
<cite title="Source Title">
در تاریخ
{{Jdate.jdate('Y/n/d',item.dateSubmit)}}
</cite>
</figcaption>
</figure>
</div>
</div>
</div>
<div class="post-content mt-4">
{{ item.body | raw }} {{ item.body | raw }}
</div> </div>
</article>
</div> <!-- اشتراک‌گذاری -->
<div class="col-sm-12 col-md-4"> <div class="mt-12 pt-8 border-t border-gray-200">
<div class="recent-posts"> <div class="flex items-center justify-between">
<h3 class="text-primary mb-3">جدیدترین‌ها</h3> <div>
{% for post in posts %} <h3 class="text-lg font-semibold text-gray-900 mb-4">این مطلب را به اشتراک بگذارید</h3>
<div class="card mb-3"> <div class="flex space-x-3 space-x-reverse">
<img src="{{asset('uploaded/'~ post.mainPic)}}" class="card-img-top" alt="{{post.title}}"> <a href="https://twitter.com/intent/tweet?text={{item.title|url_encode}}&url={{app.request.uri|url_encode}}"
<div class="card-body"> target="_blank"
<a href="{{path('app_blog_post',{'url':post.url})}}" class="stretched-link text-decoration-none"> class="flex items-center space-x-2 space-x-reverse px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors duration-200">
<h5 class="card-title text-primary">{{post.title}}</h5> <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
</svg>
<span>توییتر</span>
</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{app.request.uri|url_encode}}"
target="_blank"
class="flex items-center space-x-2 space-x-reverse px-4 py-2 bg-blue-700 text-white rounded-lg hover:bg-blue-800 transition-colors duration-200">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
<span>لینکدین</span>
</a>
<a href="https://t.me/share/url?url={{app.request.uri|url_encode}}&text={{item.title|url_encode}}"
target="_blank"
class="flex items-center space-x-2 space-x-reverse px-4 py-2 bg-blue-400 text-white rounded-lg hover:bg-blue-500 transition-colors duration-200">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
</svg>
<span>تلگرام</span>
</a> </a>
</div> </div>
</div> </div>
</div>
</div>
</article>
</div>
<!-- سایدبار -->
<div class="lg:col-span-1">
<div class="sticky top-8">
<!-- پست‌های مرتبط -->
{% if posts|length > 0 %}
<div class="bg-white rounded-2xl shadow-soft p-6 mb-8">
<h3 class="text-xl font-bold text-gray-900 mb-6 flex items-center">
<svg class="w-5 h-5 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"></path>
</svg>
جدیدترین‌ها
</h3>
<div class="space-y-4">
{% for post in posts %}
<a href="{{path('app_blog_post',{'url':post.url})}}"
class="group block bg-gray-50 rounded-xl p-4 hover:bg-gray-100 transition-colors duration-200">
<div class="flex space-x-3 space-x-reverse">
<img src="{{asset('uploaded/'~ post.mainPic)}}"
alt="{{post.title}}"
class="w-16 h-16 object-cover rounded-lg flex-shrink-0">
<div class="flex-1 min-w-0">
<h4 class="text-sm font-semibold text-gray-900 group-hover:text-blue-600 transition-colors duration-200 line-clamp-2">
{{post.title}}
</h4>
<p class="text-xs text-gray-500 mt-1">
{{ Jdate.jdate('Y/n/d',post.dateSubmit) }}
</p>
</div>
</div>
</a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %}
<!-- دکمه بازگشت به وبلاگ -->
<div class="bg-gradient-to-br from-blue-50 to-purple-50 rounded-2xl p-6 text-center">
<h3 class="text-lg font-semibold text-gray-900 mb-4">بازگشت به وبلاگ</h3>
<p class="text-sm text-gray-600 mb-4">مطالب بیشتری را در وبلاگ ما بخوانید</p>
<a href="{{path('app_blog_home')}}"
class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
<span>مشاهده همه مطالب</span>
</a>
</div> </div>
</div> </div>
</div>
</div>
</div>
</div>
</main>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -15,15 +15,22 @@
{{ item.plain | raw}} {{ item.plain | raw}}
{% endif %} {% endif %}
{% if item.body is not null %} {% if item.body is not null %}
<div class="container mt-3"> <main class="min-h-screen bg-gray-50">
<div class="row"> <div class="container mx-auto px-4 py-12">
<div class="col"> <div class="max-w-4xl mx-auto">
<h1 class="text-primary py-4">{{item.title}}</h1> <div class="bg-white rounded-2xl shadow-soft p-8 lg:p-12">
<!-- عنوان صفحه -->
<h1 class="text-3xl lg:text-4xl font-bold text-gray-900 mb-8 leading-tight">
{{item.title}}
</h1>
<!-- محتوای صفحه -->
<div class="prose prose-lg max-w-none text-gray-800 leading-relaxed">
{{ item.body | raw}} {{ item.body | raw}}
</div> </div>
</div> </div>
</div> </div>
</div>
</main>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -3,55 +3,77 @@
{% block title %}پرسیدن سوال جدید - پرسش و پاسخ{% endblock %} {% block title %}پرسیدن سوال جدید - پرسش و پاسخ{% endblock %}
{% block body %} {% block body %}
<div class="container my-4"> <main class="min-h-screen bg-gray-50">
<div class="row justify-content-center"> <div class="container mx-auto px-4 py-8">
<div class="col-lg-8"> <div class="max-w-4xl mx-auto">
<div class="card"> <!-- هدر فرم -->
<div class="card-header"> <div class="text-center mb-8">
<h3 class="mb-0"> <h1 class="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> پرسیدن سوال جدید
<circle cx="12" cy="12" r="10"></circle> </h1>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path> <p class="text-lg text-gray-600">
<line x1="12" y1="17" x2="12.01" y2="17"></line> سوال خود را مطرح کنید تا از تجربه دیگران استفاده کنید
</svg>پرسیدن سوال جدید </p>
</h3>
</div> </div>
<div class="card-body">
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
<div class="mb-4"> <!-- فرم ایجاد سوال -->
{{ form_label(form.title) }} <div class="bg-white rounded-2xl shadow-soft overflow-hidden">
{{ form_widget(form.title) }} <div class="p-8">
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-8'}}) }}
<!-- عنوان سوال -->
<div>
<label for="{{ form.title.vars.id }}" class="block text-sm font-medium text-gray-700 mb-2">
عنوان سوال
</label>
{{ form_widget(form.title, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200'}}) }}
{{ form_errors(form.title) }} {{ form_errors(form.title) }}
<div class="form-text"> <p class="mt-2 text-sm text-gray-600">
عنوان سوال باید واضح و مختصر باشد. سعی کنید مشکل خود را در یک جمله خلاصه کنید. عنوان سوال باید واضح و مختصر باشد. سعی کنید مشکل خود را در یک جمله خلاصه کنید.
</div> </p>
</div> </div>
<div class="mb-4"> <!-- محتوای سوال -->
{{ form_label(form.content) }} <div>
<div class="editor-toolbar mb-2"> <label for="{{ form.content.vars.id }}" class="block text-sm font-medium text-gray-700 mb-2">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="formatText('bold')" title="پررنگ"> محتوای سوال
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> </label>
<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
<path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path> <!-- نوار ابزار ادیتور -->
<div class="flex flex-wrap gap-2 p-3 bg-gray-50 border border-gray-300 rounded-t-xl">
<button type="button"
onclick="formatText('bold')"
title="پررنگ"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
</svg> </svg>
</button> </button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="formatText('italic')" title="کج"> <button type="button"
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> onclick="formatText('italic')"
title="کج"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="19" y1="4" x2="10" y2="4"></line> <line x1="19" y1="4" x2="10" y2="4"></line>
<line x1="14" y1="20" x2="5" y2="20"></line> <line x1="14" y1="20" x2="5" y2="20"></line>
<line x1="15" y1="4" x2="9" y2="20"></line> <line x1="15" y1="4" x2="9" y2="20"></line>
</svg> </svg>
</button> </button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="formatText('code')" title="کد"> <button type="button"
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> onclick="formatText('code')"
title="کد"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="16,18 22,12 16,6"></polyline> <polyline points="16,18 22,12 16,6"></polyline>
<polyline points="8,6 2,12 8,18"></polyline> <polyline points="8,6 2,12 8,18"></polyline>
</svg> </svg>
</button> </button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="insertList()" title="لیست"> <button type="button"
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> onclick="insertList()"
title="لیست"
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-200 rounded-lg transition-colors duration-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="8" y1="6" x2="21" y2="6"></line> <line x1="8" y1="6" x2="21" y2="6"></line>
<line x1="8" y1="12" x2="21" y2="12"></line> <line x1="8" y1="12" x2="21" y2="12"></line>
<line x1="8" y1="18" x2="21" y2="18"></line> <line x1="8" y1="18" x2="21" y2="18"></line>
@ -61,56 +83,115 @@
</svg> </svg>
</button> </button>
</div> </div>
{{ form_widget(form.content, {'attr': {'class': 'form-control editor-textarea', 'rows': 10}}) }}
{{ form_widget(form.content, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 border-t-0 rounded-b-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200', 'rows': 10}}) }}
{{ form_errors(form.content) }} {{ form_errors(form.content) }}
<div class="form-text"> <p class="mt-2 text-sm text-gray-600">
سوال خود را به تفصیل شرح دهید. هرچه جزئیات بیشتری ارائه دهید، پاسخ‌های بهتری دریافت خواهید کرد. سوال خود را به تفصیل شرح دهید. هرچه جزئیات بیشتری ارائه دهید، پاسخ‌های بهتری دریافت خواهید کرد.
<br><small class="text-muted">می‌توانید از دکمه‌های بالا برای فرمت کردن متن استفاده کنید.</small> <br><span class="text-gray-500">می‌توانید از دکمه‌های بالا برای فرمت کردن متن استفاده کنید.</span>
</p>
</div>
<!-- تگ‌ها -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
تگ‌ها
<span class="text-red-500">*</span>
</label>
<div class="border border-gray-300 rounded-xl p-6 bg-gradient-to-br from-gray-50 to-blue-50">
<!-- تگ‌های انتخاب شده -->
<div class="mb-6">
<div class="flex items-center justify-between mb-3">
<h4 class="text-sm font-semibold text-gray-700">تگ‌های انتخاب شده</h4>
<span class="text-xs text-gray-500" id="tag-count">0 تگ انتخاب شده</span>
</div>
<div class="selected-tags min-h-12 flex flex-wrap gap-2 p-3 bg-white rounded-lg border-2 border-dashed border-gray-200" id="selected-tags">
<div class="flex items-center text-gray-400 text-sm">
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
هیچ تگی انتخاب نشده است
</div>
</div> </div>
</div> </div>
<div class="mb-4"> <!-- جستجو و افزودن تگ -->
<label class="form-label">تگ‌ها</label> <div class="mb-6">
<div class="tags-container"> <div class="relative">
<div class="selected-tags mb-2" id="selected-tags"></div> <input type="text"
<div class="input-group"> class="w-full px-4 py-3 pr-12 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-sm"
<input type="text" class="form-control" id="tag-input" placeholder="تگ جدید را وارد کنید یا از لیست انتخاب کنید..."> id="tag-input"
<button class="btn btn-outline-primary" type="button" id="add-tag-btn"> placeholder="نام تگ را تایپ کنید...">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <button class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors duration-200"
<line x1="12" y1="5" x2="12" y2="19"></line> type="button"
<line x1="5" y1="12" x2="19" y2="12"></line> id="search-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg> </svg>
</button> </button>
</div> </div>
<div class="available-tags mt-2" id="available-tags"> <div class="flex items-center justify-between mt-3">
<small class="text-muted">تگ‌های موجود:</small> <button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200 text-sm font-medium flex items-center space-x-2 space-x-reverse"
<div class="tag-suggestions mt-1"> type="button"
id="add-tag-btn">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
<span>افزودن تگ</span>
</button>
<span class="text-xs text-gray-500">حداقل 1 و حداکثر 5 تگ</span>
</div>
</div>
<!-- تگ‌های پیشنهادی -->
<div>
<div class="flex items-center justify-between mb-3">
<h4 class="text-sm font-semibold text-gray-700">تگ‌های محبوب</h4>
<button class="text-xs text-blue-600 hover:text-blue-800 transition-colors duration-200"
type="button"
id="show-all-tags">
نمایش همه
</button>
</div>
<div class="flex flex-wrap gap-2" id="tag-suggestions">
{% for tag in availableTags %} {% for tag in availableTags %}
<span class="badge bg-light text-dark me-1 mb-1 tag-suggestion" data-tag-id="{{ tag.id }}" data-tag-name="{{ tag.name }}"> <button type="button"
class="inline-flex items-center px-3 py-2 bg-white text-gray-700 text-sm font-medium rounded-full border border-gray-300 hover:bg-blue-50 hover:border-blue-300 hover:text-blue-700 cursor-pointer transition-all duration-200 tag-suggestion"
data-tag-id="{{ tag.id }}"
data-tag-name="{{ tag.name }}">
<svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
{{ tag.name }} {{ tag.name }}
</span> </button>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
<div class="form-text"> <p class="mt-2 text-sm text-gray-600">
تگ‌های مرتبط را انتخاب کنید تا دیگران راحت‌تر بتوانند سوال شما را پیدا کنند. تگ‌های مرتبط را انتخاب کنید تا دیگران راحت‌تر بتوانند سوال شما را پیدا کنند.
<br><small class="text-muted">می‌توانید تگ جدید ایجاد کنید یا از تگ‌های موجود انتخاب کنید.</small> <br><span class="text-gray-500">می‌توانید تگ جدید ایجاد کنید یا از تگ‌های موجود انتخاب کنید.</span>
</div> </p>
</div> </div>
<div class="d-flex justify-content-between"> <!-- دکمه‌های فرم -->
<a href="{{ path('qa_index') }}" class="btn btn-outline-secondary"> <div class="flex justify-between items-center pt-6 border-t border-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> <a href="{{ path('qa_index') }}"
<path d="M19 12H5"></path> class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-all duration-200 font-medium">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 12H5"></path>
<polyline points="12,19 5,12 12,5"></polyline> <polyline points="12,19 5,12 12,5"></polyline>
</svg>انصراف </svg>
<span>انصراف</span>
</a> </a>
<button type="submit" class="btn btn-primary"> <button type="submit"
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> class="inline-flex items-center space-x-2 space-x-reverse px-8 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="22" y1="2" x2="11" y2="13"></line> <line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22,2 15,22 11,13 2,9 22,2"></polygon> <polygon points="22,2 15,22 11,13 2,9 22,2"></polygon>
</svg>ارسال سوال </svg>
<span>ارسال سوال</span>
</button> </button>
</div> </div>
@ -119,36 +200,87 @@
</div> </div>
<!-- راهنمای پرسیدن سوال --> <!-- راهنمای پرسیدن سوال -->
<div class="card mt-4"> <div class="bg-white rounded-2xl shadow-soft mt-8 overflow-hidden">
<div class="card-header"> <div class="bg-gradient-to-r from-blue-50 to-purple-50 px-8 py-6">
<h5 class="mb-0"> <h3 class="text-xl font-semibold text-gray-900 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> <svg class="w-6 h-6 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4"></path>
<path d="M21 12c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path> <path d="M21 12c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
<path d="M3 12c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path> <path d="M3 12c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
<path d="M12 3c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path> <path d="M12 3c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
<path d="M12 21c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path> <path d="M12 21c.552 0 1-.448 1-1s-.448-1-1-1-1 .448-1 1 .448 1 1 1z"></path>
</svg>راهنمای پرسیدن سوال خوب </svg>
</h5> راهنمای پرسیدن سوال خوب
</h3>
</div> </div>
<div class="card-body"> <div class="p-8">
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="col-md-6"> <div>
<h6 class="text-success">✅ کارهای درست:</h6> <h4 class="text-lg font-semibold text-green-700 mb-4 flex items-center">
<ul class="list-unstyled"> <svg class="w-5 h-5 text-green-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<li><i class="fas fa-check text-success me-2"></i>عنوان واضح و مختصر</li> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<li><i class="fas fa-check text-success me-2"></i>شرح کامل مشکل</li> </svg>
<li><i class="fas fa-check text-success me-2"></i>انتخاب تگ‌های مناسب</li> کارهای درست
<li><i class="fas fa-check text-success me-2"></i>استفاده از کلمات کلیدی</li> </h4>
<ul class="space-y-3">
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">عنوان واضح و مختصر</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">شرح کامل مشکل</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">انتخاب تگ‌های مناسب</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-gray-700">استفاده از کلمات کلیدی</span>
</li>
</ul> </ul>
</div> </div>
<div class="col-md-6"> <div>
<h6 class="text-danger">❌ کارهای نادرست:</h6> <h4 class="text-lg font-semibold text-red-700 mb-4 flex items-center">
<ul class="list-unstyled"> <svg class="w-5 h-5 text-red-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<li><i class="fas fa-times text-danger me-2"></i>عنوان مبهم</li> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
<li><i class="fas fa-times text-danger me-2"></i>شرح ناکافی</li> </svg>
<li><i class="fas fa-times text-danger me-2"></i>عدم انتخاب تگ</li> کارهای نادرست
<li><i class="fas fa-times text-danger me-2"></i>سوال تکراری</li> </h4>
<ul class="space-y-3">
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">عنوان مبهم</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">شرح ناکافی</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">عدم انتخاب تگ</span>
</li>
<li class="flex items-center space-x-3 space-x-reverse">
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span class="text-gray-700">سوال تکراری</span>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -156,232 +288,145 @@
</div> </div>
</div> </div>
</div> </div>
</div> </main>
<style>
.form-control:focus, .form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border: none;
}
.card-header {
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.form-text {
font-size: 0.9rem;
color: #6c757d;
}
.list-unstyled li {
margin-bottom: 0.5rem;
}
.editor-toolbar {
border: 1px solid #dee2e6;
border-bottom: none;
border-radius: 0.375rem 0.375rem 0 0;
padding: 0.5rem;
background-color: #f8f9fa;
}
.editor-textarea {
border-radius: 0 0 0.375rem 0.375rem;
border-top: none;
}
.tags-container {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
background-color: #f8f9fa;
}
.selected-tags {
min-height: 40px;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
.tag-item {
display: inline-flex;
align-items: center;
background-color: #0d6efd;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
font-size: 0.875rem;
}
.tag-remove {
background: none;
border: none;
color: white;
margin-left: 0.5rem;
cursor: pointer;
padding: 0;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.tag-suggestion {
cursor: pointer;
transition: all 0.3s ease;
}
.tag-suggestion:hover {
background-color: #0d6efd !important;
color: white !important;
}
.tag-suggestion.selected {
background-color: #0d6efd !important;
color: white !important;
}
</style>
<script> <script>
let selectedTags = []; // سیستم مدیریت تگ‌ها
class TagManager {
constructor() {
this.selectedTags = [];
this.maxTags = 5;
this.minTags = 1;
this.init();
}
function initializeQuestionForm() { init() {
// ادیتور متن this.tagInput = document.getElementById('tag-input');
const textarea = document.querySelector('.editor-textarea'); this.addTagBtn = document.getElementById('add-tag-btn');
this.selectedTagsContainer = document.getElementById('selected-tags');
this.tagSuggestions = document.querySelectorAll('.tag-suggestion');
this.tagCount = document.getElementById('tag-count');
this.showAllTagsBtn = document.getElementById('show-all-tags');
// سیستم تگ‌ها if (!this.tagInput || !this.addTagBtn || !this.selectedTagsContainer) {
const tagInput = document.getElementById('tag-input'); console.error('Required elements not found for tag system');
const addTagBtn = document.getElementById('add-tag-btn');
const selectedTagsContainer = document.getElementById('selected-tags');
const tagSuggestions = document.querySelectorAll('.tag-suggestion');
// اضافه کردن تگ
function addTag(tagId, tagName) {
if (selectedTags.find(tag => tag.id === tagId)) {
return; return;
} }
selectedTags.push({ id: tagId, name: tagName }); this.bindEvents();
renderSelectedTags(); this.updateTagCount();
updateHiddenInput(); this.renderSelectedTags();
console.log('Tag added:', { id: tagId, name: tagName });
} }
// حذف تگ bindEvents() {
window.removeTag = function(tagId) { // افزودن تگ با دکمه
selectedTags = selectedTags.filter(tag => tag.id !== tagId); this.addTagBtn.addEventListener('click', () => this.addTagFromInput());
renderSelectedTags();
updateHiddenInput();
}
// نمایش تگ‌های انتخاب شده // افزودن تگ با Enter
function renderSelectedTags() { this.tagInput.addEventListener('keypress', (e) => {
selectedTagsContainer.innerHTML = ''; if (e.key === 'Enter') {
selectedTags.forEach(tag => { e.preventDefault();
const tagElement = document.createElement('span'); this.addTagFromInput();
tagElement.className = 'tag-item';
tagElement.innerHTML = `
${tag.name}
<button type="button" class="tag-remove" data-tag-id="${tag.id}">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`;
// اضافه کردن event listener برای دکمه حذف
const removeBtn = tagElement.querySelector('.tag-remove');
removeBtn.addEventListener('click', function() {
const tagId = this.dataset.tagId;
window.removeTag(tagId);
// حذف کلاس selected از تگ پیشنهادی
const suggestion = document.querySelector(`[data-tag-id="${tagId}"]`);
if (suggestion) {
suggestion.classList.remove('selected');
} }
}); });
selectedTagsContainer.appendChild(tagElement);
});
}
// به‌روزرسانی input مخفی // جستجوی تگ‌ها
function updateHiddenInput() { this.tagInput.addEventListener('input', () => this.filterSuggestions());
// حذف تمام input های قبلی
const existingInputs = document.querySelectorAll('input[name="question[tags][]"]');
existingInputs.forEach(input => input.remove());
// اضافه کردن input های جدید
selectedTags.forEach(tag => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'question[tags][]';
input.value = tag.id;
document.querySelector('form').appendChild(input);
});
console.log('Selected tags:', selectedTags);
}
// کلیک روی تگ‌های پیشنهادی // کلیک روی تگ‌های پیشنهادی
tagSuggestions.forEach(suggestion => { this.tagSuggestions.forEach(suggestion => {
suggestion.addEventListener('click', function() { suggestion.addEventListener('click', () => this.toggleTagSuggestion(suggestion));
const tagId = this.dataset.tagId; });
const tagName = this.dataset.tagName;
if (selectedTags.find(tag => tag.id === tagId)) { // نمایش همه تگ‌ها
window.removeTag(tagId); if (this.showAllTagsBtn) {
this.classList.remove('selected'); this.showAllTagsBtn.addEventListener('click', () => this.showAllTags());
} else { }
addTag(tagId, tagName); }
this.classList.add('selected');
addTag(tagId, tagName) {
// بررسی محدودیت تعداد تگ‌ها
if (this.selectedTags.length >= this.maxTags) {
if (window.notification) {
window.notification.warning(`حداکثر ${this.maxTags} تگ می‌توانید انتخاب کنید`);
}
return false;
} }
});
});
// اضافه کردن تگ جدید
addTagBtn.addEventListener('click', function() {
const tagName = tagInput.value.trim();
if (tagName) {
// بررسی وجود تگ // بررسی وجود تگ
const existingTag = Array.from(tagSuggestions).find(suggestion => if (this.selectedTags.find(tag => tag.id === tagId)) {
if (window.notification) {
window.notification.info('این تگ قبلاً انتخاب شده است');
}
return false;
}
this.selectedTags.push({ id: tagId, name: tagName });
this.updateTagCount();
this.renderSelectedTags();
this.updateHiddenInput();
this.updateSuggestionState(tagId, true);
if (window.notification) {
window.notification.success(`تگ "${tagName}" اضافه شد`);
}
return true;
}
removeTag(tagId) {
const tagIndex = this.selectedTags.findIndex(tag => tag.id === tagId);
if (tagIndex === -1) return false;
const removedTag = this.selectedTags[tagIndex];
this.selectedTags.splice(tagIndex, 1);
this.updateTagCount();
this.renderSelectedTags();
this.updateHiddenInput();
this.updateSuggestionState(tagId, false);
if (window.notification) {
window.notification.info(`تگ "${removedTag.name}" حذف شد`);
}
return true;
}
addTagFromInput() {
const tagName = this.tagInput.value.trim();
if (!tagName) {
if (window.notification) {
window.notification.warning('لطفاً نام تگ را وارد کنید');
}
return;
}
// بررسی تگ موجود
const existingTag = Array.from(this.tagSuggestions).find(suggestion =>
suggestion.dataset.tagName.toLowerCase() === tagName.toLowerCase() suggestion.dataset.tagName.toLowerCase() === tagName.toLowerCase()
); );
if (existingTag) { if (existingTag) {
const tagId = existingTag.dataset.tagId; const tagId = existingTag.dataset.tagId;
const tagName = existingTag.dataset.tagName; const tagName = existingTag.dataset.tagName;
addTag(tagId, tagName); this.addTag(tagId, tagName);
existingTag.classList.add('selected'); this.tagInput.value = '';
} else { return;
// ایجاد تگ جدید
createNewTag(tagName);
} }
tagInput.value = '';
}
});
// Enter برای اضافه کردن تگ
tagInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
addTagBtn.click();
}
});
// ایجاد تگ جدید // ایجاد تگ جدید
function createNewTag(tagName) { this.createNewTag(tagName);
}
createNewTag(tagName) {
// نمایش حالت بارگذاری
const originalContent = this.addTagBtn.innerHTML;
this.addTagBtn.innerHTML = '<div class="loading-spinner"></div>';
this.addTagBtn.disabled = true;
fetch('/qa/tag/create', { fetch('/qa/tag/create', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -393,44 +438,184 @@ function initializeQuestionForm() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.error) { if (data.error) {
alert(data.error); if (window.notification) {
window.notification.error(data.error);
}
return; return;
} }
// اضافه کردن تگ به لیست انتخاب شده // افزودن تگ جدید
addTag(data.id, data.name); if (this.addTag(data.id, data.name)) {
this.tagInput.value = '';
// اضافه کردن تگ به لیست پیشنهادی // افزودن به لیست پیشنهادی
const suggestionElement = document.createElement('span'); this.addToSuggestions(data.id, data.name);
suggestionElement.className = 'badge bg-light text-dark me-1 mb-1 tag-suggestion selected';
suggestionElement.dataset.tagId = data.id;
suggestionElement.dataset.tagName = data.name;
suggestionElement.textContent = data.name;
suggestionElement.addEventListener('click', function() {
if (selectedTags.find(tag => tag.id === data.id)) {
removeTag(data.id);
this.classList.remove('selected');
} else {
addTag(data.id, data.name);
this.classList.add('selected');
} }
});
document.querySelector('.tag-suggestions').appendChild(suggestionElement);
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error creating tag:', error);
alert('خطا در ایجاد تگ جدید'); if (window.notification) {
window.notification.error('خطا در ایجاد تگ جدید. لطفاً دوباره تلاش کنید.');
}
})
.finally(() => {
// بازگردانی دکمه
this.addTagBtn.innerHTML = originalContent;
this.addTagBtn.disabled = false;
}); });
} }
});
// توابع ادیتور متن addToSuggestions(tagId, tagName) {
function formatText(command) { const suggestionElement = document.createElement('button');
const textarea = document.querySelector('.editor-textarea'); suggestionElement.type = 'button';
const start = textarea.selectionStart; suggestionElement.className = 'inline-flex items-center px-3 py-2 bg-white text-gray-700 text-sm font-medium rounded-full border border-gray-300 hover:bg-blue-50 hover:border-blue-300 hover:text-blue-700 cursor-pointer transition-all duration-200 tag-suggestion';
const end = textarea.selectionEnd; suggestionElement.dataset.tagId = tagId;
const selectedText = textarea.value.substring(start, end); suggestionElement.dataset.tagName = tagName;
suggestionElement.innerHTML = `
<svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
${tagName}
`;
suggestionElement.addEventListener('click', () => this.toggleTagSuggestion(suggestionElement));
const container = document.getElementById('tag-suggestions');
if (container) {
container.appendChild(suggestionElement);
this.tagSuggestions = document.querySelectorAll('.tag-suggestion');
}
}
toggleTagSuggestion(suggestion) {
const tagId = suggestion.dataset.tagId;
const tagName = suggestion.dataset.tagName;
if (this.selectedTags.find(tag => tag.id === tagId)) {
this.removeTag(tagId);
} else {
this.addTag(tagId, tagName);
}
}
updateSuggestionState(tagId, isSelected) {
const suggestion = document.querySelector(`[data-tag-id="${tagId}"]`);
if (!suggestion) return;
if (isSelected) {
suggestion.classList.remove('bg-white', 'text-gray-700', 'border-gray-300');
suggestion.classList.add('bg-blue-600', 'text-white', 'border-blue-600');
} else {
suggestion.classList.remove('bg-blue-600', 'text-white', 'border-blue-600');
suggestion.classList.add('bg-white', 'text-gray-700', 'border-gray-300');
}
}
renderSelectedTags() {
this.selectedTagsContainer.innerHTML = '';
if (this.selectedTags.length === 0) {
this.selectedTagsContainer.innerHTML = `
<div class="flex items-center text-gray-400 text-sm">
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
هیچ تگی انتخاب نشده است
</div>
`;
return;
}
this.selectedTags.forEach(tag => {
const tagElement = document.createElement('div');
tagElement.className = 'inline-flex items-center px-3 py-2 bg-blue-600 text-white text-sm font-medium rounded-full shadow-sm';
tagElement.innerHTML = `
<svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
${tag.name}
<button type="button"
class="tag-remove mr-2 text-white hover:text-red-200 transition-colors duration-200"
data-tag-id="${tag.id}">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
`;
// رویداد حذف تگ
const removeBtn = tagElement.querySelector('.tag-remove');
removeBtn.addEventListener('click', () => this.removeTag(tag.id));
this.selectedTagsContainer.appendChild(tagElement);
});
}
updateTagCount() {
if (this.tagCount) {
this.tagCount.textContent = `${this.selectedTags.length} تگ انتخاب شده`;
}
}
updateHiddenInput() {
// حذف input های قبلی
const existingInputs = document.querySelectorAll('input[name="question[tags][]"]');
existingInputs.forEach(input => input.remove());
// افزودن input های جدید
this.selectedTags.forEach(tag => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'question[tags][]';
input.value = tag.id;
document.querySelector('form').appendChild(input);
});
}
filterSuggestions() {
const searchTerm = this.tagInput.value.toLowerCase();
this.tagSuggestions.forEach(suggestion => {
const tagName = suggestion.dataset.tagName.toLowerCase();
if (tagName.includes(searchTerm)) {
suggestion.style.display = 'inline-flex';
} else {
suggestion.style.display = 'none';
}
});
}
showAllTags() {
this.tagSuggestions.forEach(suggestion => {
suggestion.style.display = 'inline-flex';
});
this.tagInput.value = '';
}
validateTags() {
if (this.selectedTags.length < this.minTags) {
if (window.notification) {
window.notification.error(`حداقل ${this.minTags} تگ باید انتخاب کنید`);
}
return false;
}
return true;
}
}
// ادیتور متن
class TextEditor {
constructor() {
this.textarea = document.querySelector('textarea[name="question[content]"]');
}
formatText(command) {
if (!this.textarea) return;
const start = this.textarea.selectionStart;
const end = this.textarea.selectionEnd;
const selectedText = this.textarea.value.substring(start, end);
let formattedText = ''; let formattedText = '';
@ -446,23 +631,64 @@ function formatText(command) {
break; break;
} }
textarea.value = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end); this.textarea.value = this.textarea.value.substring(0, start) + formattedText + this.textarea.value.substring(end);
textarea.focus(); this.textarea.focus();
textarea.setSelectionRange(start + formattedText.length, start + formattedText.length); this.textarea.setSelectionRange(start + formattedText.length, start + formattedText.length);
} }
function insertList() { insertList() {
const textarea = document.querySelector('.editor-textarea'); if (!this.textarea) return;
const start = textarea.selectionStart;
const end = textarea.selectionEnd; const start = this.textarea.selectionStart;
const selectedText = textarea.value.substring(start, end); const end = this.textarea.selectionEnd;
const selectedText = this.textarea.value.substring(start, end);
const listText = selectedText.split('\n').map(line => `- ${line}`).join('\n'); const listText = selectedText.split('\n').map(line => `- ${line}`).join('\n');
textarea.value = textarea.value.substring(0, start) + listText + textarea.value.substring(end); this.textarea.value = this.textarea.value.substring(0, start) + listText + this.textarea.value.substring(end);
textarea.focus(); this.textarea.focus();
}
} }
// اجرا در هر دو حالت // متغیرهای سراسری
let tagManager;
let textEditor;
// توابع سراسری برای HTML
window.formatText = function(command) {
if (textEditor) {
textEditor.formatText(command);
}
};
window.insertList = function() {
if (textEditor) {
textEditor.insertList();
}
};
// مقداردهی اولیه
function initializeQuestionForm() {
if (typeof window.notification === 'undefined') {
setTimeout(initializeQuestionForm, 100);
return;
}
tagManager = new TagManager();
textEditor = new TextEditor();
// اعتبارسنجی فرم
const form = document.querySelector('form');
if (form) {
form.addEventListener('submit', function(e) {
if (!tagManager.validateTags()) {
e.preventDefault();
return false;
}
});
}
}
// اجرا در بارگذاری صفحه
document.addEventListener('DOMContentLoaded', initializeQuestionForm); document.addEventListener('DOMContentLoaded', initializeQuestionForm);
document.addEventListener('turbo:load', initializeQuestionForm); document.addEventListener('turbo:load', initializeQuestionForm);
</script> </script>

View file

@ -3,111 +3,153 @@
{% block title %}پرسش و پاسخ - حسابیکس{% endblock %} {% block title %}پرسش و پاسخ - حسابیکس{% endblock %}
{% block body %} {% block body %}
<div class="container my-4"> <main class="min-h-screen bg-gray-50">
<div class="row"> <!-- هدر Q&A -->
<div class="col-12"> <div class="bg-gradient-to-br from-blue-600 via-purple-600 to-indigo-700">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="container mx-auto px-4 py-12">
<h1 class="text-primary fw-bold">پرسش و پاسخ</h1> <div class="max-w-6xl mx-auto">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
<div class="text-white mb-6 lg:mb-0">
<h1 class="text-4xl lg:text-5xl font-bold mb-4 animate-fade-in-up">
پرسش و پاسخ
</h1>
<p class="text-lg text-blue-100 animate-fade-in-up" style="animation-delay: 0.2s;">
سوالات خود را بپرسید و از تجربه دیگران استفاده کنید
</p>
</div>
<div class="animate-fade-in-up" style="animation-delay: 0.4s;">
{% if app.user and 'ROLE_CUSTOMER' in app.user.roles %} {% if app.user and 'ROLE_CUSTOMER' in app.user.roles %}
<a href="{{ path('qa_ask') }}" class="btn btn-primary"> <a href="{{ path('qa_ask') }}"
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-white text-blue-600 rounded-xl hover:bg-blue-50 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-1">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19"></line> <line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line> <line x1="5" y1="12" x2="19" y2="12"></line>
</svg>سوال جدید </svg>
<span>سوال جدید</span>
</a> </a>
{% else %} {% else %}
<a href="{{ path('customer_login') }}" class="btn btn-outline-primary"> <a href="{{ path('customer_login') }}"
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-blue-500 text-white rounded-xl hover:bg-blue-600 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-1">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path> <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
<polyline points="10,17 15,12 10,7"></polyline> <polyline points="10,17 15,12 10,7"></polyline>
<line x1="15" y1="12" x2="3" y2="12"></line> <line x1="15" y1="12" x2="3" y2="12"></line>
</svg>ورود برای پرسیدن سوال </svg>
<span>ورود برای پرسیدن سوال</span>
</a> </a>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
</div>
</div>
<!-- محتوای اصلی -->
<div class="container mx-auto px-4 py-8">
<div class="max-w-6xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<!-- محتوای اصلی -->
<div class="lg:col-span-3">
<!-- فیلترها و جستجو --> <!-- فیلترها و جستجو -->
<div class="card mb-4"> <div class="bg-white rounded-2xl shadow-soft p-6 mb-8">
<div class="card-body"> <form method="GET" class="space-y-4">
<form method="GET" class="row g-3"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="col-md-4"> <div>
<label for="filter" class="form-label">فیلتر:</label> <label for="filter" class="block text-sm font-medium text-gray-700 mb-2">فیلتر:</label>
<select name="filter" id="filter" class="form-select"> <select name="filter" id="filter"
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200">
<option value="all" {{ currentFilter == 'all' ? 'selected' : '' }}>همه سوالات</option> <option value="all" {{ currentFilter == 'all' ? 'selected' : '' }}>همه سوالات</option>
<option value="unsolved" {{ currentFilter == 'unsolved' ? 'selected' : '' }}>سوالات حل نشده</option> <option value="unsolved" {{ currentFilter == 'unsolved' ? 'selected' : '' }}>سوالات حل نشده</option>
<option value="solved" {{ currentFilter == 'solved' ? 'selected' : '' }}>سوالات حل شده</option> <option value="solved" {{ currentFilter == 'solved' ? 'selected' : '' }}>سوالات حل شده</option>
<option value="popular" {{ currentFilter == 'popular' ? 'selected' : '' }}>محبوب‌ترین</option> <option value="popular" {{ currentFilter == 'popular' ? 'selected' : '' }}>محبوب‌ترین</option>
</select> </select>
</div> </div>
<div class="col-md-6"> <div>
<label for="search" class="form-label">جستجو:</label> <label for="search" class="block text-sm font-medium text-gray-700 mb-2">جستجو:</label>
<input type="text" name="search" id="search" class="form-control" <input type="text" name="search" id="search"
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
value="{{ currentSearch }}" placeholder="جستجو در سوالات..."> value="{{ currentSearch }}" placeholder="جستجو در سوالات...">
</div> </div>
<div class="col-md-2"> <div class="flex items-end">
<label class="form-label">&nbsp;</label> <button type="submit"
<button type="submit" class="btn btn-primary w-100"> class="w-full px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-1"> <div class="flex items-center justify-center space-x-2 space-x-reverse">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="8"></circle> <circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path> <path d="m21 21-4.35-4.35"></path>
</svg>جستجو </svg>
<span>جستجو</span>
</div>
</button> </button>
</div> </div>
</div>
</form> </form>
</div> </div>
</div>
<div class="row">
<!-- لیست سوالات --> <!-- لیست سوالات -->
<div class="col-lg-9">
{% if questions is empty %} {% if questions is empty %}
<div class="card"> <div class="bg-white rounded-2xl shadow-soft p-12 text-center">
<div class="card-body text-center py-5"> <div class="max-w-md mx-auto">
<i class="fas fa-question-circle fa-3x text-muted mb-3"></i> <svg class="w-16 h-16 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<h4 class="text-muted">سوالی یافت نشد</h4> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
<p class="text-muted">هنوز سوالی در این دسته‌بندی وجود ندارد.</p> </svg>
<h3 class="text-xl font-semibold text-gray-900 mb-2">سوالی یافت نشد</h3>
<p class="text-gray-600 mb-6">هنوز سوالی در این دسته‌بندی وجود ندارد.</p>
{% if app.user and 'ROLE_CUSTOMER' in app.user.roles %} {% if app.user and 'ROLE_CUSTOMER' in app.user.roles %}
<a href="{{ path('qa_ask') }}" class="btn btn-primary">اولین سوال را بپرسید</a> <a href="{{ path('qa_ask') }}"
class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
<span>اولین سوال را بپرسید</span>
</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="space-y-6">
{% for question in questions %} {% for question in questions %}
<div class="card mb-3 question-card"> <div class="bg-white rounded-2xl shadow-soft hover:shadow-medium transition-all duration-300 overflow-hidden hover-lift group">
<div class="card-body"> <div class="p-6">
<div class="row"> <div class="flex space-x-4 space-x-reverse">
<div class="col-2 col-md-1 text-center"> <!-- آمار سوال -->
<div class="d-flex flex-column align-items-center"> <div class="flex flex-col items-center space-y-2 min-w-0">
<div class="vote-count {{ question.votes > 0 ? 'text-success' : (question.votes < 0 ? 'text-danger' : 'text-muted') }}"> <div class="text-center">
<div class="text-2xl font-bold {{ question.votes > 0 ? 'text-green-600' : (question.votes < 0 ? 'text-red-600' : 'text-gray-500') }}">
{{ question.votes }} {{ question.votes }}
</div> </div>
<small class="text-muted">رای</small> <div class="text-xs text-gray-500">رای</div>
</div> </div>
</div> <div class="text-center">
<div class="col-2 col-md-1 text-center"> <div class="text-lg font-semibold {{ question.answers|length > 0 ? 'text-green-600' : 'text-gray-500' }}">
<div class="d-flex flex-column align-items-center">
<div class="answer-count {{ question.answers|length > 0 ? 'text-success' : 'text-muted' }}">
{{ question.answers|length }} {{ question.answers|length }}
</div> </div>
<small class="text-muted">پاسخ</small> <div class="text-xs text-gray-500">پاسخ</div>
</div> </div>
</div> </div>
<div class="col-8 col-md-10">
<div class="d-flex justify-content-between align-items-start mb-2"> <!-- محتوای سوال -->
<h5 class="card-title mb-0"> <div class="flex-1 min-w-0">
<div class="flex items-start justify-between mb-3">
<h3 class="text-lg font-semibold text-gray-900 group-hover:text-blue-600 transition-colors duration-200">
<a href="{{ path('qa_question_show', {'id': question.id}) }}" <a href="{{ path('qa_question_show', {'id': question.id}) }}"
class="text-decoration-none text-dark"> class="hover:underline">
{{ question.title }} {{ question.title }}
</a> </a>
</h5> </h3>
{% if question.isSolved %} {% if question.isSolved %}
<span class="badge bg-success"> <span class="inline-flex items-center space-x-1 space-x-reverse px-3 py-1 bg-green-100 text-green-800 text-sm font-medium rounded-full">
<i class="fas fa-check me-1"></i>حل شده <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span>حل شده</span>
</span> </span>
{% endif %} {% endif %}
</div> </div>
<div class="card-text text-muted mb-2 question-preview"> <div class="text-gray-600 mb-4 line-clamp-3 leading-relaxed">
{% set content = question.content|markdown|raw %} {% set content = question.content|markdown|raw %}
{% set plainText = content|striptags %} {% set plainText = content|striptags %}
{% if plainText|length > 200 %} {% if plainText|length > 200 %}
@ -117,19 +159,38 @@
{% endif %} {% endif %}
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="flex flex-wrap items-center justify-between gap-4">
<div class="tags"> <!-- تگ‌ها -->
<div class="flex flex-wrap gap-2">
{% for tagRelation in question.tagRelations %} {% for tagRelation in question.tagRelations %}
<a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}" <a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}"
class="badge bg-light text-dark text-decoration-none me-1"> class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full hover:bg-blue-200 transition-colors duration-200">
{{ tagRelation.tag.name }} {{ tagRelation.tag.name }}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
<div class="text-muted small">
<i class="fas fa-user me-1"></i>{{ question.author.name }} <!-- اطلاعات سوال -->
<i class="fas fa-clock ms-3 me-1"></i>{{ question.createdAt|date('Y/m/d H:i') }} <div class="flex items-center space-x-4 space-x-reverse text-sm text-gray-500">
<i class="fas fa-eye ms-3 me-1"></i>{{ question.views }} <div class="flex items-center space-x-1 space-x-reverse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
</svg>
<span>{{ question.author.name }}</span>
</div>
<div class="flex items-center space-x-1 space-x-reverse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>{{ question.createdAt|date('Y/m/d H:i') }}</span>
</div>
<div class="flex items-center space-x-1 space-x-reverse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
</svg>
<span>{{ question.views }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -137,88 +198,104 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div>
<!-- صفحه‌بندی --> <!-- صفحه‌بندی -->
{% if totalPages > 1 %} {% if totalPages > 1 %}
<nav aria-label="صفحه‌بندی سوالات"> <div class="mt-8">
<ul class="pagination justify-content-center"> <nav class="flex justify-center" aria-label="صفحه‌بندی سوالات">
<div class="flex items-center space-x-2 space-x-reverse">
<!-- دکمه صفحه قبل -->
{% if currentPage > 1 %} {% if currentPage > 1 %}
<li class="page-item"> <a href="?page={{ currentPage - 1 }}{{ currentFilter != 'all' ? '&filter=' ~ currentFilter : '' }}{{ currentSearch ? '&search=' ~ currentSearch : '' }}{{ currentTag ? '&tag=' ~ currentTag : '' }}"
<a class="page-link" href="?page={{ currentPage - 1 }}{{ currentFilter != 'all' ? '&filter=' ~ currentFilter : '' }}{{ currentSearch ? '&search=' ~ currentSearch : '' }}{{ currentTag ? '&tag=' ~ currentTag : '' }}">قبلی</a> class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
</li> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
<span>قبلی</span>
</a>
{% endif %} {% endif %}
<!-- شماره صفحات -->
<div class="flex items-center space-x-1 space-x-reverse">
{% for page in 1..totalPages %} {% for page in 1..totalPages %}
{% if page == currentPage %} {% if page == currentPage %}
<li class="page-item active"> <span class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-lg">
<span class="page-link">{{ page }}</span> {{ page }}
</li> </span>
{% else %} {% elseif page <= currentPage + 2 and page >= currentPage - 2 %}
<li class="page-item"> <a href="?page={{ page }}{{ currentFilter != 'all' ? '&filter=' ~ currentFilter : '' }}{{ currentSearch ? '&search=' ~ currentSearch : '' }}{{ currentTag ? '&tag=' ~ currentTag : '' }}"
<a class="page-link" href="?page={{ page }}{{ currentFilter != 'all' ? '&filter=' ~ currentFilter : '' }}{{ currentSearch ? '&search=' ~ currentSearch : '' }}{{ currentTag ? '&tag=' ~ currentTag : '' }}">{{ page }}</a> class="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
</li> {{ page }}
</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div>
<!-- دکمه صفحه بعد -->
{% if currentPage < totalPages %} {% if currentPage < totalPages %}
<li class="page-item"> <a href="?page={{ currentPage + 1 }}{{ currentFilter != 'all' ? '&filter=' ~ currentFilter : '' }}{{ currentSearch ? '&search=' ~ currentSearch : '' }}{{ currentTag ? '&tag=' ~ currentTag : '' }}"
<a class="page-link" href="?page={{ currentPage + 1 }}{{ currentFilter != 'all' ? '&filter=' ~ currentFilter : '' }}{{ currentSearch ? '&search=' ~ currentSearch : '' }}{{ currentTag ? '&tag=' ~ currentTag : '' }}">بعدی</a> class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
</li> <span>بعدی</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
</a>
{% endif %} {% endif %}
</ul> </div>
</nav> </nav>
</div>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
<!-- سایدبار --> <!-- سایدبار -->
<div class="col-lg-3"> <div class="lg:col-span-1">
<div class="space-y-6">
<!-- تگ‌های محبوب --> <!-- تگ‌های محبوب -->
<div class="card mb-4"> <div class="bg-white rounded-2xl shadow-soft p-6">
<div class="card-header"> <h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<h6 class="mb-0"> <svg class="w-5 h-5 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path> </svg>
<line x1="7" y1="7" x2="7.01" y2="7"></line> تگ‌های محبوب
</svg>تگ‌های محبوب </h3>
</h6> <div class="flex flex-wrap gap-2">
</div>
<div class="card-body">
{% for tag in popularTags %} {% for tag in popularTags %}
<a href="{{ path('qa_tag_questions', {'name': tag.name}) }}" <a href="{{ path('qa_tag_questions', {'name': tag.name}) }}"
class="badge bg-light text-dark text-decoration-none me-1 mb-2 d-inline-block"> class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full hover:bg-blue-200 transition-colors duration-200">
{{ tag.name }} {{ tag.name }}
<span class="text-muted">({{ tag.usageCount }})</span> <span class="text-blue-600 mr-1">({{ tag.usageCount }})</span>
</a> </a>
{% endfor %} {% endfor %}
<div class="mt-3">
<a href="{{ path('qa_tags') }}" class="btn btn-outline-primary btn-sm">
مشاهده همه تگ‌ها
</a>
</div> </div>
<div class="mt-4">
<a href="{{ path('qa_tags') }}"
class="inline-flex items-center space-x-2 space-x-reverse text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
<span>مشاهده همه تگ‌ها</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</a>
</div> </div>
</div> </div>
<!-- آمار --> <!-- آمار -->
<div class="card"> <div class="bg-gradient-to-br from-blue-50 to-purple-50 rounded-2xl p-6">
<div class="card-header"> <h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<h6 class="mb-0"> <svg class="w-5 h-5 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-2"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
<path d="M18 20V10"></path> </svg>
<path d="M12 20V4"></path> آمار
<path d="M6 20v-6"></path> </h3>
</svg>آمار <div class="grid grid-cols-2 gap-4 text-center">
</h6> <div>
<div class="text-2xl font-bold text-blue-600">{{ questions|length }}</div>
<div class="text-sm text-gray-600">سوال</div>
</div> </div>
<div class="card-body"> <div>
<div class="row text-center"> <div class="text-2xl font-bold text-green-600">{{ popularTags|length }}</div>
<div class="col-6"> <div class="text-sm text-gray-600">تگ</div>
<div class="h4 text-primary">{{ questions|length }}</div>
<small class="text-muted">سوال</small>
</div>
<div class="col-6">
<div class="h4 text-success">{{ popularTags|length }}</div>
<small class="text-muted">تگ</small>
</div> </div>
</div> </div>
</div> </div>
@ -227,80 +304,6 @@
</div> </div>
</div> </div>
</div> </div>
</div> </main>
<style>
.question-card {
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
.question-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-left-color: #0d6efd;
}
.vote-count, .answer-count {
font-size: 1.2rem;
font-weight: bold;
}
.tags .badge {
font-size: 0.8rem;
transition: all 0.3s ease;
}
.tags .badge:hover {
background-color: #0d6efd !important;
color: white !important;
}
.card-title a:hover {
color: #0d6efd !important;
}
.question-preview {
line-height: 1.6;
max-height: 4.8em; /* حدود 3 خط */
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.question-card {
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
.question-card:hover {
border-left-color: #0d6efd;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.question-stats {
font-size: 0.9rem;
color: #6c757d;
}
.question-stats .stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem;
}
.question-stats .stat-number {
font-weight: bold;
font-size: 1.1rem;
color: #495057;
}
.question-stats .stat-label {
font-size: 0.8rem;
margin-top: 0.25rem;
}
</style>
{% endblock %} {% endblock %}

View file

@ -3,65 +3,88 @@
{% block title %}{{ question.title }} - پرسش و پاسخ{% endblock %} {% block title %}{{ question.title }} - پرسش و پاسخ{% endblock %}
{% block body %} {% block body %}
<div class="container my-4"> <main class="min-h-screen bg-gray-50">
<div class="row"> <div class="container mx-auto px-4 py-8">
<div class="col-12"> <div class="max-w-6xl mx-auto">
<!-- سوال --> <!-- سوال -->
<div class="card mb-4"> <div class="bg-white rounded-2xl shadow-soft mb-8 overflow-hidden">
<div class="card-body"> <div class="p-8">
<div class="row"> <div class="flex space-x-6 space-x-reverse">
<div class="col-1 text-center"> <!-- سیستم رای‌دهی -->
<div class="vote-section"> <div class="flex flex-col items-center space-y-2 min-w-0">
<button class="btn btn-outline-success btn-sm vote-btn" <button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-green-200 text-green-600 hover:bg-green-50 hover:border-green-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
data-type="question" data-type="question"
data-id="{{ question.id }}" data-id="{{ question.id }}"
data-upvote="true" data-upvote="true"
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}> {% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="18,15 12,9 6,15"></polyline> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
</svg> </svg>
</button> </button>
<div class="vote-count mt-2 mb-2" id="question-votes-{{ question.id }}"> <div class="text-2xl font-bold text-gray-900" id="question-votes-{{ question.id }}">
{{ question.votes }} {{ question.votes }}
</div> </div>
<button class="btn btn-outline-danger btn-sm vote-btn" <button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
data-type="question" data-type="question"
data-id="{{ question.id }}" data-id="{{ question.id }}"
data-upvote="false" data-upvote="false"
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}> {% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="6,9 12,15 18,9"></polyline> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg> </svg>
</button> </button>
</div> </div>
</div>
<div class="col-11"> <!-- محتوای سوال -->
<div class="d-flex justify-content-between align-items-start mb-3"> <div class="flex-1 min-w-0">
<h1 class="card-title mb-0">{{ question.title }}</h1> <div class="flex items-start justify-between mb-6">
<h1 class="text-3xl font-bold text-gray-900 leading-tight">{{ question.title }}</h1>
{% if question.isSolved %} {% if question.isSolved %}
<span class="badge bg-success fs-6"> <span class="inline-flex items-center space-x-1 space-x-reverse px-4 py-2 bg-green-100 text-green-800 text-sm font-medium rounded-full">
<i class="fas fa-check me-1"></i>حل شده <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span>حل شده</span>
</span> </span>
{% endif %} {% endif %}
</div> </div>
<div class="question-content mb-3"> <div class="prose prose-lg max-w-none text-gray-800 leading-relaxed mb-6">
{{ question.content|markdown|raw }} {{ question.content|markdown|raw }}
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="flex flex-wrap items-center justify-between gap-4 pt-6 border-t border-gray-200">
<div class="tags"> <!-- تگ‌ها -->
<div class="flex flex-wrap gap-2">
{% for tagRelation in question.tagRelations %} {% for tagRelation in question.tagRelations %}
<a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}" <a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}"
class="badge bg-light text-dark text-decoration-none me-1"> class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full hover:bg-blue-200 transition-colors duration-200">
{{ tagRelation.tag.name }} {{ tagRelation.tag.name }}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
<div class="text-muted small">
<i class="fas fa-user me-1"></i>{{ question.author.name }} <!-- اطلاعات سوال -->
<i class="fas fa-clock ms-3 me-1"></i>{{ question.createdAt|date('Y/m/d H:i') }} <div class="flex items-center space-x-6 space-x-reverse text-sm text-gray-500">
<i class="fas fa-eye ms-3 me-1"></i>{{ question.views }} بازدید <div class="flex items-center space-x-1 space-x-reverse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
</svg>
<span>{{ question.author.name }}</span>
</div>
<div class="flex items-center space-x-1 space-x-reverse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>{{ question.createdAt|date('Y/m/d H:i') }}</span>
</div>
<div class="flex items-center space-x-1 space-x-reverse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
</svg>
<span>{{ question.views }} بازدید</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -70,85 +93,119 @@
</div> </div>
<!-- پاسخ‌ها --> <!-- پاسخ‌ها -->
<div class="mb-4"> <div class="mb-8">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="flex items-center justify-between mb-6">
<h3> <h2 class="text-2xl font-bold text-gray-900 flex items-center">
<i class="fas fa-comments me-2"></i>پاسخ‌ها <svg class="w-6 h-6 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<span class="badge bg-primary">{{ totalAnswers }}</span> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</h3> </svg>
پاسخ‌ها
<span class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full mr-3">
{{ totalAnswers }}
</span>
</h2>
{% if app.user and 'ROLE_CUSTOMER' in app.user.roles %} {% if app.user and 'ROLE_CUSTOMER' in app.user.roles %}
<a href="{{ path('qa_answer', {'id': question.id}) }}" class="btn btn-primary"> <a href="{{ path('qa_answer', {'id': question.id}) }}"
<i class="fas fa-plus me-2"></i>پاسخ دهید class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
<span>پاسخ دهید</span>
</a> </a>
{% else %} {% else %}
<a href="{{ path('customer_login') }}" class="btn btn-outline-primary"> <a href="{{ path('customer_login') }}"
<i class="fas fa-sign-in-alt me-2"></i>ورود برای پاسخ دادن class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-all duration-200 font-medium">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
<polyline points="10,17 15,12 10,7"></polyline>
<line x1="15" y1="12" x2="3" y2="12"></line>
</svg>
<span>ورود برای پاسخ دادن</span>
</a> </a>
{% endif %} {% endif %}
</div> </div>
{% if answers is empty %} {% if answers is empty %}
<div class="card"> <div class="bg-white rounded-2xl shadow-soft p-12 text-center">
<div class="card-body text-center py-5"> <div class="max-w-md mx-auto">
<i class="fas fa-comment-slash fa-3x text-muted mb-3"></i> <svg class="w-16 h-16 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<h4 class="text-muted">هنوز پاسخی داده نشده</h4> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
<p class="text-muted">اولین کسی باشید که به این سوال پاسخ می‌دهد.</p> </svg>
<h3 class="text-xl font-semibold text-gray-900 mb-2">هنوز پاسخی داده نشده</h3>
<p class="text-gray-600">اولین کسی باشید که به این سوال پاسخ می‌دهد.</p>
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="space-y-6">
{% for answer in answers %} {% for answer in answers %}
<div class="card mb-3 answer-card {{ answer.isAccepted ? 'border-success' : '' }}"> <div class="bg-white rounded-2xl shadow-soft overflow-hidden {{ answer.isAccepted ? 'ring-2 ring-green-200 border-green-200' : '' }}">
<div class="card-body"> <div class="p-8">
<div class="row"> <div class="flex space-x-6 space-x-reverse">
<div class="col-1 text-center"> <!-- سیستم رای‌دهی پاسخ -->
<div class="vote-section"> <div class="flex flex-col items-center space-y-2 min-w-0">
<button class="btn btn-outline-success btn-sm vote-btn" <button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-green-200 text-green-600 hover:bg-green-50 hover:border-green-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
data-type="answer" data-type="answer"
data-id="{{ answer.id }}" data-id="{{ answer.id }}"
data-upvote="true" data-upvote="true"
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}> {% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="18,15 12,9 6,15"></polyline> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
</svg> </svg>
</button> </button>
<div class="vote-count mt-2 mb-2" id="answer-votes-{{ answer.id }}"> <div class="text-2xl font-bold text-gray-900" id="answer-votes-{{ answer.id }}">
{{ answer.votes }} {{ answer.votes }}
</div> </div>
<button class="btn btn-outline-danger btn-sm vote-btn" <button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
data-type="answer" data-type="answer"
data-id="{{ answer.id }}" data-id="{{ answer.id }}"
data-upvote="false" data-upvote="false"
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}> {% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="6,9 12,15 18,9"></polyline> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg> </svg>
</button> </button>
</div> </div>
</div>
<div class="col-11"> <!-- محتوای پاسخ -->
<div class="d-flex justify-content-between align-items-start mb-2"> <div class="flex-1 min-w-0">
<div class="answer-content"> <div class="flex items-start justify-between mb-4">
<div class="prose prose-lg max-w-none text-gray-800 leading-relaxed">
{{ answer.content|markdown|raw }} {{ answer.content|markdown|raw }}
</div> </div>
{% if answer.isAccepted %} {% if answer.isAccepted %}
<span class="badge bg-success ms-2"> <span class="inline-flex items-center space-x-1 space-x-reverse px-3 py-1 bg-green-100 text-green-800 text-sm font-medium rounded-full mr-4">
<i class="fas fa-check me-1"></i>پاسخ پذیرفته شده <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span>پاسخ پذیرفته شده</span>
</span> </span>
{% endif %} {% endif %}
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="flex items-center justify-between pt-4 border-t border-gray-200">
<div class="text-muted small"> <div class="flex items-center space-x-4 space-x-reverse text-sm text-gray-500">
<i class="fas fa-user me-1"></i>{{ answer.author.name }} <div class="flex items-center space-x-1 space-x-reverse">
<i class="fas fa-clock ms-3 me-1"></i>{{ answer.createdAt|date('Y/m/d H:i') }} <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
</svg>
<span>{{ answer.author.name }}</span>
</div>
<div class="flex items-center space-x-1 space-x-reverse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>{{ answer.createdAt|date('Y/m/d H:i') }}</span>
</div>
</div> </div>
{% if app.user and app.user == question.author and not answer.isAccepted %} {% if app.user and app.user == question.author and not answer.isAccepted %}
<button class="btn btn-outline-success btn-sm accept-answer-btn" <button class="accept-answer-btn inline-flex items-center space-x-2 space-x-reverse px-4 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition-colors duration-200 font-medium"
data-answer-id="{{ answer.id }}"> data-answer-id="{{ answer.id }}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="me-1"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="20,6 9,17 4,12"></polyline> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>پذیرفتن پاسخ </svg>
<span>پذیرفتن پاسخ</span>
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -157,95 +214,80 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div>
<!-- صفحه‌بندی پاسخ‌ها --> <!-- صفحه‌بندی پاسخ‌ها -->
{% if totalPages > 1 %} {% if totalPages > 1 %}
<nav aria-label="صفحه‌بندی پاسخ‌ها" class="mt-4"> <div class="mt-8">
<ul class="pagination justify-content-center"> <nav class="flex justify-center" aria-label="صفحه‌بندی پاسخ‌ها">
<div class="flex items-center space-x-2 space-x-reverse">
<!-- دکمه صفحه قبل -->
{% if currentPage > 1 %} {% if currentPage > 1 %}
<li class="page-item"> <a href="?page={{ currentPage - 1 }}"
<a class="page-link" href="?page={{ currentPage - 1 }}">قبلی</a> class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
</li> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
<span>قبلی</span>
</a>
{% endif %} {% endif %}
<!-- شماره صفحات -->
<div class="flex items-center space-x-1 space-x-reverse">
{% for page in 1..totalPages %} {% for page in 1..totalPages %}
{% if page == currentPage %} {% if page == currentPage %}
<li class="page-item active"> <span class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-lg">
<span class="page-link">{{ page }}</span> {{ page }}
</li> </span>
{% else %} {% elseif page <= currentPage + 2 and page >= currentPage - 2 %}
<li class="page-item"> <a href="?page={{ page }}"
<a class="page-link" href="?page={{ page }}">{{ page }}</a> class="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
</li> {{ page }}
</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div>
<!-- دکمه صفحه بعد -->
{% if currentPage < totalPages %} {% if currentPage < totalPages %}
<li class="page-item"> <a href="?page={{ currentPage + 1 }}"
<a class="page-link" href="?page={{ currentPage + 1 }}">بعدی</a> class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
</li> <span>بعدی</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
</a>
{% endif %} {% endif %}
</ul> </div>
</nav> </nav>
</div>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
<!-- دکمه بازگشت --> <!-- دکمه بازگشت -->
<div class="text-center"> <div class="text-center mt-8">
<a href="{{ path('qa_index') }}" class="btn btn-outline-secondary"> <a href="{{ path('qa_index') }}"
<i class="fas fa-arrow-right me-2"></i>بازگشت به لیست سوالات class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-all duration-200 font-medium">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
<span>بازگشت به لیست سوالات</span>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> </main>
<style>
.vote-section {
display: flex;
flex-direction: column;
align-items: center;
}
.vote-btn {
width: 30px;
height: 30px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.vote-count {
font-size: 1.1rem;
font-weight: bold;
min-width: 30px;
text-align: center;
}
.answer-card.border-success {
border-left: 4px solid #198754 !important;
}
.question-content, .answer-content {
line-height: 1.6;
font-size: 1.1rem;
}
.tags .badge {
font-size: 0.9rem;
transition: all 0.3s ease;
}
.tags .badge:hover {
background-color: #0d6efd !important;
color: white !important;
}
</style>
<script> <script>
function initializeVoteButtons() { function initializeVoteButtons() {
// Wait for notification system to be available
if (typeof window.notification === 'undefined') {
console.log('Waiting for notification system...');
setTimeout(initializeVoteButtons, 100);
return;
}
// رای‌دهی // رای‌دهی
document.querySelectorAll('.vote-btn').forEach(button => { document.querySelectorAll('.vote-btn').forEach(button => {
button.addEventListener('click', function() { button.addEventListener('click', function() {
@ -266,7 +308,9 @@ function initializeVoteButtons() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.error) { if (data.error) {
alert(data.error); if (window.notification) {
window.notification.error(data.error);
}
return; return;
} }
@ -275,27 +319,35 @@ function initializeVoteButtons() {
// تغییر رنگ دکمه‌ها // تغییر رنگ دکمه‌ها
const buttons = document.querySelectorAll(`[data-type="${type}"][data-id="${id}"]`); const buttons = document.querySelectorAll(`[data-type="${type}"][data-id="${id}"]`);
buttons.forEach(btn => { buttons.forEach(btn => {
btn.classList.remove('btn-success', 'btn-danger', 'btn-outline-success', 'btn-outline-danger'); btn.classList.remove('bg-green-500', 'bg-red-500', 'border-green-500', 'border-red-500', 'text-white');
btn.classList.add(btn.dataset.upvote === 'true' ? 'btn-outline-success' : 'btn-outline-danger'); btn.classList.add(btn.dataset.upvote === 'true' ? 'border-green-200 text-green-600' : 'border-red-200 text-red-600');
}); });
if (data.userVote) { if (data.userVote) {
const activeButton = document.querySelector(`[data-type="${type}"][data-id="${id}"][data-upvote="${data.userVote === 'up' ? 'true' : 'false'}"]`); const activeButton = document.querySelector(`[data-type="${type}"][data-id="${id}"][data-upvote="${data.userVote === 'up' ? 'true' : 'false'}"]`);
if (activeButton) { if (activeButton) {
activeButton.classList.remove('btn-outline-success', 'btn-outline-danger'); activeButton.classList.remove('border-green-200', 'border-red-200', 'text-green-600', 'text-red-600');
activeButton.classList.add(data.userVote === 'up' ? 'btn-success' : 'btn-danger'); activeButton.classList.add(data.userVote === 'up' ? 'bg-green-500 text-white border-green-500' : 'bg-red-500 text-white border-red-500');
}
if (window.notification) {
window.notification.success('رای شما ثبت شد');
} }
} else { } else {
// اگر رای حذف شده، همه دکمه‌ها را به حالت عادی برگردان // اگر رای حذف شده، همه دکمه‌ها را به حالت عادی برگردان
buttons.forEach(btn => { buttons.forEach(btn => {
btn.classList.remove('btn-success', 'btn-danger'); btn.classList.remove('bg-green-500', 'bg-red-500', 'text-white', 'border-green-500', 'border-red-500');
btn.classList.add(btn.dataset.upvote === 'true' ? 'btn-outline-success' : 'btn-outline-danger'); btn.classList.add(btn.dataset.upvote === 'true' ? 'border-green-200 text-green-600' : 'border-red-200 text-red-600');
}); });
if (window.notification) {
window.notification.info('رای شما حذف شد');
}
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
alert('خطا در ارسال رای'); if (window.notification) {
window.notification.error('خطا در ارسال رای. لطفاً دوباره تلاش کنید.');
}
}); });
}); });
}); });
@ -305,10 +357,17 @@ function initializeVoteButtons() {
button.addEventListener('click', function() { button.addEventListener('click', function() {
const answerId = this.dataset.answerId; const answerId = this.dataset.answerId;
if (!confirm('آیا مطمئن هستید که می‌خواهید این پاسخ را بپذیرید؟')) { // Show confirmation dialog
const confirmed = confirm('آیا مطمئن هستید که می‌خواهید این پاسخ را بپذیرید؟');
if (!confirmed) {
return; return;
} }
// Show loading state
const originalText = this.innerHTML;
this.innerHTML = '<div class="loading-spinner"></div>';
this.disabled = true;
fetch(`/qa/answer/${answerId}/accept`, { fetch(`/qa/answer/${answerId}/accept`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -320,22 +379,56 @@ function initializeVoteButtons() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.error) { if (data.error) {
alert(data.error); if (window.notification) {
window.notification.error(data.error);
}
return; return;
} }
if (window.notification) {
window.notification.success('پاسخ با موفقیت پذیرفته شد');
}
setTimeout(() => {
location.reload(); location.reload();
}, 1500);
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
alert('خطا در پذیرفتن پاسخ'); if (window.notification) {
window.notification.error('خطا در پذیرفتن پاسخ. لطفاً دوباره تلاش کنید.');
}
})
.finally(() => {
// Reset button state
this.innerHTML = originalText;
this.disabled = false;
}); });
}); });
}); });
} }
// اجرا در هر دو حالت // اجرا در هر دو حالت
document.addEventListener('DOMContentLoaded', initializeVoteButtons); document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('turbo:load', initializeVoteButtons); // Wait for notification system to be available
const checkNotification = () => {
if (window.notification) {
initializeVoteButtons();
} else {
setTimeout(checkNotification, 100);
}
};
checkNotification();
});
document.addEventListener('turbo:load', function() {
// Wait for notification system to be available
const checkNotification = () => {
if (window.notification) {
initializeVoteButtons();
} else {
setTimeout(checkNotification, 100);
}
};
checkNotification();
});
</script> </script>
{% endblock %} {% endblock %}

View file

@ -1,335 +0,0 @@
{% extends 'base.html.twig' %}
{% block title %}
دیدگاه‌های کاربران
{% endblock %}
{% block des %}
دیدگاه سایر کاربران استفاده کننده از حسابیکس دید بهترین برای انتخاب محصولی عالی را برای شما مهیا می کند.
{% endblock %}
{% block body %}
<div class="container mt-3">
<div class="mb-3">
<h3 class="mb-4">نظرات کاربران درباره حسابیکس</h3>
<div class="row align-items-center">
<div class="col-auto text-center">
<h3 class="display-2 fw-bold">4.5</h3>
<span class="fs-6">
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-half text-warning"></i>
</span>
<p class="mb-0 fs-6">(بر اساس 27 نظر)</p>
</div>
<!-- Progress Bar -->
<div class="col order-3 order-md-2">
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: 90%;" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: 80%;" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: 70%;" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="progress mb-3" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: 60%;" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="progress mb-0" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: 50%;" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
<div
class="col-md-auto col-6 order-2 order-md-3">
<!-- Rating -->
<div>
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
<span class="ms-1">53%</span>
</div>
<div>
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
<span class="ms-1">36%</span>
</div>
<div>
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
<span class="ms-1">9%</span>
</div>
<div>
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
<span class="ms-1">3%</span>
</div>
<div>
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-light" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
<span class="ms-1">2%</span>
</div>
</div>
</div>
</div>
<hr class="my-5">
<div class="mb-3">
<div
class="d-lg-flex align-items-center justify-content-between mb-5">
<!-- Reviews -->
<div class="mb-3 mb-lg-0">
<h3 class="mb-0">نظرات کاربران</h3>
</div>
<div>
<form class="form-inline">
<div class="d-flex align-items-center me-2">
<span class="position-absolute ps-3">
<i class="fe fe-search"></i>
</span>
<input type="search" class="form-control ps-6" placeholder="جستجوی نظرات">
</div>
</form>
</div>
</div>
<!-- Rating -->
<div class="d-flex align-items-start border-bottom pb-4 mb-4">
<img src="{{asset('assets/images/avatar/avatar-2.jpg')}}" alt="" class="rounded-circle avatar-lg">
<div class="ms-3">
<h4 class="mb-1">
حمیدرضا تینای تهرانی
<span class="ms-1 fs-6">2 روز پیش</span>
</h4>
<div class="mb-2">
<span class="fs-6">
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-fill text-warning"></i>
<i class="bi bi-star-fill text-warning"></i>
</span>
</div>
<p>حسابیکس بهترین نرم افزار حسابداری آنلاینی هست که تا حالا استفاده کردم. امکاناتش کامل و پشتیبانی عالی و به‌روزرسانی‌های مداوم باعث شده که همیشه از جدیدترین امکانات بهره‌مند بشم.</p>
<div class="d-lg-flex">
<p class="mb-0">آیا این نظر برای شما مفید بود؟</p>
<a href="#" class="btn btn-xs btn-primary ms-lg-3">بله</a>
<a href="#" class="btn btn-xs btn-outline-secondary ms-1">خیر</a>
</div>
</div>
</div>
<!-- Rating -->
<div class="d-flex align-items-start border-bottom pb-4 mb-4">
<img src="../assets/images/avatar/avatar-3.jpg" alt="" class="rounded-circle avatar-lg">
<div class="ms-3">
<h4 class="mb-1">Arthur Williamson
<span class="ms-1 fs-6 ">3 Days
ago</span>
</h4>
<div class="mb-2">
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
</div>
<p>Its pretty good.Just a reminder that there are also
students with Windows, meaning Figma its a bit different
of yours. Thank you!</p>
<div class="d-lg-flex">
<p class="mb-0">Was this review helpful?</p>
<a href="#" class="btn btn-xs btn-primary ms-lg-3">Yes</a>
<a href="#" class="btn btn-xs btn-outline-secondary ms-1">No</a>
</div>
</div>
</div>
<!-- Rating -->
<div class="d-flex align-items-start border-bottom pb-4 mb-4">
<img src="../assets/images/avatar/avatar-4.jpg" alt="" class="rounded-circle avatar-lg">
<div class="ms-3">
<h4 class="mb-1">Claire Jones
<span class="ms-1 fs-6 ">4 Days
ago</span>
</h4>
<div class="mb-2">
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
</div>
<p>
Great course for learning Figma, the only bad detail
would be that some icons are not included in the assets.
But 90% of the icons needed are included, and the voice
of the instructor was very clear and easy to understood.
</p>
<div class="d-lg-flex">
<p class="mb-0">Was this review helpful?</p>
<a href="#" class="btn btn-xs btn-primary ms-lg-3">Yes</a>
<a href="#" class="btn btn-xs btn-outline-secondary ms-1">No</a>
</div>
</div>
</div>
<!-- Rating -->
<div class="d-flex align-items-start">
<img src="../assets/images/avatar/avatar-5.jpg" alt="" class="rounded-circle avatar-lg">
<div class="ms-3">
<h4 class="mb-1">
Bessie Pena
<span class="ms-1 fs-6 ">5 Days
ago</span>
</h4>
<div class="mb-2">
<span class="fs-6">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-star-fill text-warning" viewbox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
</span>
</div>
<p>
I have really enjoyed this class and learned a lot,
found it very inspiring and helpful, thank you!
</p>
<div class="d-lg-flex">
<p class="mb-0">Was this review helpful?</p>
<a href="#" class="btn btn-xs btn-primary ms-lg-3">Yes</a>
<a href="#" class="btn btn-xs btn-outline-secondary ms-1">No</a>
</div>
</div>
</div>
</div>
</div>
<style>
.avatar-lg {
width: 60px;
height: 60px;
object-fit: cover;
}
.progress {
background-color: #e9ecef;
}
.progress-bar {
background-color: #ffc107;
}
.btn-xs {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.border-bottom {
border-bottom: 1px solid #dee2e6 !important;
}
.text-warning {
color: #ffc107 !important;
}
</style>
{% endblock %}

View file

@ -59,6 +59,9 @@ Encore
// enables Sass/SCSS support // enables Sass/SCSS support
.enableSassLoader() .enableSassLoader()
// enables PostCSS support for Tailwind
.enablePostCssLoader()
// uncomment if you use TypeScript // uncomment if you use TypeScript
//.enableTypeScriptLoader() //.enableTypeScriptLoader()