src/ApplicationBundle/Modules/HoneybeeWeb/Resources/views/pages/blogs.html.twig line 1

Open in your IDE?
  1. {% include '@Application/inc/central_header.html.twig' %}
  2. <style>
  3. :root {
  4.     --n-cream: #F7F5F0;
  5.     --n-dark:  #1A1D2E;
  6.     --n-amber: #C07D2A;
  7.     --n-sage:  #3D6B52;
  8.     --n-slate: #3D4F72;
  9.     --n-muted: #6B7280;
  10.     --n-border: rgba(26,29,46,.09);
  11. }
  12. /* ── Hero ── */
  13. .n-page-hero {
  14.     background: var(--n-cream);
  15.     padding: 72px 0 48px;
  16.     text-align: center;
  17. }
  18. .n-page-hero .n-eyebrow {
  19.     display: inline-block;
  20.     font-size: .75rem;
  21.     font-weight: 700;
  22.     letter-spacing: .12em;
  23.     text-transform: uppercase;
  24.     color: var(--n-amber);
  25.     margin-bottom: 14px;
  26. }
  27. .n-page-hero h1 {
  28.     font-family: 'Montserrat', sans-serif;
  29.     font-size: clamp(1.9rem, 4vw, 2.6rem);
  30.     font-weight: 700;
  31.     color: var(--n-dark);
  32.     margin: 0 0 20px;
  33. }
  34. /* topic filter */
  35. .n-topic-bar {
  36.     display: flex;
  37.     flex-wrap: wrap;
  38.     gap: 8px;
  39.     justify-content: center;
  40.     max-width: 900px;
  41.     margin: 0 auto 0;
  42. }
  43. .n-topic-btn {
  44.     padding: 7px 18px;
  45.     border: 1.5px solid var(--n-border);
  46.     border-radius: 50px;
  47.     background: #fff;
  48.     font-family: 'DM Sans', sans-serif;
  49.     font-size: .82rem;
  50.     font-weight: 500;
  51.     color: var(--n-muted);
  52.     cursor: pointer;
  53.     transition: all .2s;
  54. }
  55. .n-topic-btn.active,
  56. .n-topic-btn:hover {
  57.     background: var(--n-dark);
  58.     color: #fff;
  59.     border-color: var(--n-dark);
  60. }
  61. .n-search-bar {
  62.     display: flex;
  63.     max-width: 380px;
  64.     margin: 0 auto 0;
  65.     border: 1.5px solid var(--n-border);
  66.     border-radius: 50px;
  67.     overflow: hidden;
  68.     background: #fff;
  69. }
  70. .n-search-bar input {
  71.     flex: 1;
  72.     padding: 10px 18px;
  73.     border: none;
  74.     font-family: 'DM Sans', sans-serif;
  75.     font-size: .88rem;
  76.     color: var(--n-dark);
  77.     background: transparent;
  78.     outline: none;
  79. }
  80. .n-search-bar button {
  81.     padding: 0 18px;
  82.     background: var(--n-amber);
  83.     border: none;
  84.     color: #fff;
  85.     cursor: pointer;
  86.     display: flex;
  87.     align-items: center;
  88.     gap: 6px;
  89.     font-size: .82rem;
  90.     font-weight: 600;
  91.     font-family: 'DM Sans', sans-serif;
  92.     transition: background .2s;
  93. }
  94. .n-search-bar button:hover { background: #a86d24; }
  95. /* filter row */
  96. .n-filter-row {
  97.     max-width: 1100px;
  98.     margin: 0 auto;
  99.     padding: 24px 24px 0;
  100.     display: flex;
  101.     align-items: center;
  102.     justify-content: space-between;
  103.     gap: 16px;
  104.     flex-wrap: wrap;
  105. }
  106. /* ── Blog Body ── */
  107. .n-blog-body {
  108.     background: #fff;
  109.     padding: 56px 0 96px;
  110. }
  111. .n-blog-container {
  112.     max-width: 1100px;
  113.     margin: 0 auto;
  114.     padding: 0 24px;
  115. }
  116. /* Featured + sidebar top row */
  117. .n-blog-top {
  118.     display: grid;
  119.     grid-template-columns: 1fr 1fr;
  120.     gap: 40px;
  121.     margin-bottom: 56px;
  122. }
  123. @media(max-width: 768px) {
  124.     .n-blog-top { grid-template-columns: 1fr; gap: 28px; }
  125.     .n-blog-sidebar { display: none; }
  126. }
  127. /* Featured card */
  128. .n-featured-card {
  129.     border-radius: 16px;
  130.     overflow: hidden;
  131.     border: 1.5px solid var(--n-border);
  132.     background: var(--n-cream);
  133.     display: flex;
  134.     flex-direction: column;
  135.     transition: box-shadow .25s;
  136. }
  137. .n-featured-card:hover { box-shadow: 0 8px 32px rgba(26,29,46,.1); }
  138. .n-featured-card img {
  139.     width: 100%;
  140.     height: 240px;
  141.     object-fit: cover;
  142.     display: block;
  143. }
  144. .n-featured-body { padding: 24px; flex: 1; display: flex; flex-direction: column; }
  145. .n-featured-body h5 {
  146.     font-family: 'Montserrat', sans-serif;
  147.     font-size: 1.1rem;
  148.     font-weight: 700;
  149.     color: var(--n-dark);
  150.     margin: 0 0 10px;
  151.     line-height: 1.3;
  152. }
  153. .n-featured-body p {
  154.     font-size: .9rem;
  155.     color: var(--n-muted);
  156.     line-height: 1.65;
  157.     margin: 0 0 18px;
  158.     flex: 1;
  159. }
  160. .n-featured-footer {
  161.     display: flex;
  162.     justify-content: space-between;
  163.     align-items: center;
  164. }
  165. .n-know-more {
  166.     display: inline-flex;
  167.     align-items: center;
  168.     gap: 6px;
  169.     padding: 8px 18px;
  170.     background: var(--n-dark);
  171.     color: #fff;
  172.     border-radius: 6px;
  173.     font-size: .83rem;
  174.     font-weight: 600;
  175.     text-decoration: none;
  176.     transition: background .2s;
  177. }
  178. .n-know-more:hover { background: var(--n-amber); color: #fff; text-decoration: none; }
  179. .n-card-date { font-size: .78rem; color: var(--n-muted); }
  180. /* Sidebar */
  181. .n-blog-sidebar h4 {
  182.     font-family: 'Montserrat', sans-serif;
  183.     font-size: .95rem;
  184.     font-weight: 700;
  185.     color: var(--n-dark);
  186.     margin: 0 0 18px;
  187. }
  188. .n-recent-list { list-style: none; margin: 0; padding: 0; }
  189. .n-recent-list li { padding: 14px 0; border-bottom: 1px solid var(--n-border); }
  190. .n-recent-list li:last-child { border-bottom: none; }
  191. .n-recent-list a {
  192.     font-family: 'DM Sans', sans-serif;
  193.     font-size: .9rem;
  194.     font-weight: 600;
  195.     color: var(--n-dark);
  196.     text-decoration: none;
  197.     line-height: 1.35;
  198.     display: block;
  199.     margin-bottom: 4px;
  200.     transition: color .2s;
  201. }
  202. .n-recent-list a:hover { color: var(--n-amber); }
  203. .n-recent-meta { font-size: .75rem; color: var(--n-muted); }
  204. .n-recent-meta .n-date { margin-left: 6px; }
  205. /* Blog grid */
  206. .n-blog-grid {
  207.     display: grid;
  208.     grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  209.     gap: 24px;
  210. }
  211. .n-blog-card {
  212.     border-radius: 14px;
  213.     overflow: hidden;
  214.     border: 1.5px solid var(--n-border);
  215.     background: var(--n-cream);
  216.     display: flex;
  217.     flex-direction: column;
  218.     transition: box-shadow .25s, transform .2s;
  219. }
  220. .n-blog-card:hover { box-shadow: 0 6px 24px rgba(26,29,46,.09); transform: translateY(-2px); }
  221. .n-blog-card img {
  222.     width: 100%;
  223.     height: 180px;
  224.     object-fit: cover;
  225.     display: block;
  226. }
  227. .n-blog-card-body { padding: 18px; flex: 1; display: flex; flex-direction: column; }
  228. .n-blog-card-body h5 {
  229.     font-family: 'Montserrat', sans-serif;
  230.     font-size: .95rem;
  231.     font-weight: 700;
  232.     color: var(--n-dark);
  233.     margin: 0 0 8px;
  234.     line-height: 1.3;
  235. }
  236. .n-blog-card-body p {
  237.     font-size: .85rem;
  238.     color: var(--n-muted);
  239.     line-height: 1.6;
  240.     margin: 0 0 12px;
  241.     flex: 1;
  242. }
  243. .n-blog-card-meta { font-size: .75rem; color: var(--n-muted); margin-bottom: 12px; }
  244. .n-blog-card-meta .n-date { margin-left: 6px; }
  245. .n-blog-card-link {
  246.     font-size: .82rem;
  247.     font-weight: 600;
  248.     color: var(--n-amber);
  249.     text-decoration: none;
  250.     transition: color .2s;
  251. }
  252. .n-blog-card-link:hover { color: var(--n-dark); }
  253. /* Pagination */
  254. .n-pagination {
  255.     display: flex;
  256.     align-items: center;
  257.     justify-content: space-between;
  258.     margin-top: 48px;
  259.     padding-top: 28px;
  260.     border-top: 1.5px solid var(--n-border);
  261.     flex-wrap: wrap;
  262.     gap: 12px;
  263. }
  264. .n-pag-btn {
  265.     display: inline-flex;
  266.     align-items: center;
  267.     gap: 6px;
  268.     padding: 8px 18px;
  269.     border: 1.5px solid var(--n-border);
  270.     border-radius: 8px;
  271.     background: #fff;
  272.     font-family: 'DM Sans', sans-serif;
  273.     font-size: .88rem;
  274.     font-weight: 600;
  275.     color: var(--n-dark);
  276.     text-decoration: none;
  277.     transition: all .2s;
  278. }
  279. .n-pag-btn:hover:not(.disabled) { background: var(--n-dark); color: #fff; border-color: var(--n-dark); text-decoration: none; }
  280. .n-pag-btn.disabled { opacity: .4; pointer-events: none; }
  281. .n-page-nums { display: flex; gap: 4px; align-items: center; }
  282. .n-page-num {
  283.     min-width: 34px;
  284.     height: 34px;
  285.     border-radius: 6px;
  286.     display: flex;
  287.     align-items: center;
  288.     justify-content: center;
  289.     font-size: .85rem;
  290.     font-weight: 500;
  291.     text-decoration: none;
  292.     color: var(--n-muted);
  293.     border: 1.5px solid transparent;
  294.     transition: all .2s;
  295. }
  296. .n-page-num:hover { border-color: var(--n-border); color: var(--n-dark); text-decoration: none; }
  297. .n-page-num.active { background: var(--n-dark); color: #fff; font-weight: 700; }
  298. .n-page-num.dots { cursor: default; color: var(--n-muted); }
  299. </style>
  300. <!-- Hero + Filters -->
  301. <section class="n-page-hero">
  302.     <div class="n-wrap">
  303.         <span class="n-eyebrow">Insights &amp; Ideas</span>
  304.         <h1>Latest Blogs</h1>
  305.     </div>
  306.     <div class="n-filter-row" style="max-width:900px; margin:0 auto; padding:20px 24px 0;">
  307.         <div class="n-topic-bar all-tabs">
  308.             <button class="n-topic-btn topic-btn active" data-topic-id="all">All Topics</button>
  309.             {% for topic in topics %}
  310.                 <button class="n-topic-btn topic-btn" data-topic-id="{{ topic.id }}">{{ topic.topicName }}</button>
  311.             {% else %}
  312.             {% endfor %}
  313.         </div>
  314.         <div class="n-search-bar">
  315.             <input type="text" id="blogSearchInput" placeholder="Search blogs…">
  316.             <button id="blogSearchBtn" type="button"><i class="fas fa-search"></i> Search</button>
  317.         </div>
  318.     </div>
  319. </section>
  320. <!-- Blog Content -->
  321. <section class="n-blog-body">
  322.     <div class="n-blog-container">
  323.         <!-- Featured + Sidebar -->
  324.         <div class="n-blog-top">
  325.             <div id="blogSection">
  326.                 {% if featuredBlog %}
  327.                 <div class="n-featured-card">
  328.                     <img src="{{ featuredBlog.mainImage
  329.                         ? absolute_url(path('dashboard')) ~ '/' ~ featuredBlog.mainImage ~ '?version=' ~ constant('ApplicationBundle\\Constants\\GeneralConstant::ENTITY_APP_VERSION')
  330.                         : absolute_url(path('dashboard')) ~ 'honeybee_web_assets/images/Blogs/pic1.png?version=' ~ constant('ApplicationBundle\\Constants\\GeneralConstant::ENTITY_APP_VERSION') }}"
  331.                          alt="{{ featuredBlog.title }}">
  332.                     <div class="n-featured-body">
  333.                         <h5>{{ featuredBlog.title }}</h5>
  334.                         <p class="card-text" data-full-text="{{ featuredBlog.content|striptags }}"></p>
  335.                         <div class="n-featured-footer">
  336.                             <a href="{{ path('honeybee_single_blog') }}?id={{ featuredBlog.id }}" class="n-know-more">
  337.                                 <i class="fa fa-arrow-right"></i> Read More
  338.                             </a>
  339.                             <span class="n-card-date">{{ featuredBlog.createdAt ? featuredBlog.createdAt|date('d/m/Y') : 'N/A' }}</span>
  340.                         </div>
  341.                     </div>
  342.                 </div>
  343.                 {% else %}
  344.                 <div class="n-featured-card">
  345.                     <div class="n-featured-body">
  346.                         <h5>No Featured Blog Available</h5>
  347.                         <p>Check back soon for featured content.</p>
  348.                     </div>
  349.                 </div>
  350.                 {% endif %}
  351.             </div>
  352.             <div class="n-blog-sidebar recent-blog-sidebar">
  353.                 <h4>Recent Posts</h4>
  354.                 <ul class="n-recent-list recent-blogs">
  355.                     {% set recentCount = 0 %}
  356.                     {% for blog in blogs %}
  357.                         {% if recentCount < 4 %}
  358.                             <li>
  359.                                 <a href="{{ path('honeybee_single_blog') }}?id={{ blog.id }}">{{ blog.title }}</a>
  360.                                 <p class="n-recent-meta">
  361.                                     By {{ blog.authorName ?? 'Unknown' }}
  362.                                     <span class="n-date">{{ blog.createdAt ? blog.createdAt|date('F d, Y') : 'N/A' }}</span>
  363.                                 </p>
  364.                             </li>
  365.                             {% set recentCount = recentCount + 1 %}
  366.                         {% endif %}
  367.                     {% else %}
  368.                         <li><p>No recent blogs found.</p></li>
  369.                     {% endfor %}
  370.                 </ul>
  371.             </div>
  372.         </div>
  373.         <!-- Blog Grid -->
  374.         <div class="n-blog-grid" id="additionalBlogs">
  375.             {% for blog in blogs %}
  376.                 <div class="n-blog-card filterable-blog-item" data-topic="{{ blog.topicId }}">
  377.                     <img src="{{ blog.mainImage
  378.                         ? absolute_url(path('dashboard')) ~ '/' ~ blog.mainImage
  379.                         : absolute_url(path('dashboard')) ~ 'honeybee_web_assets/images/Blogs/pic4.png?version=' ~ constant('ApplicationBundle\\Constants\\GeneralConstant::ENTITY_APP_VERSION') }}"
  380.                          alt="{{ blog.title }}">
  381.                     <div class="n-blog-card-body">
  382.                         <h5 class="searchable-title">{{ blog.title }}</h5>
  383.                         <p>{{ blog.content|striptags|slice(0, 150) }}…</p>
  384.                         <p class="n-blog-card-meta">
  385.                             By {{ blog.authorName ?? 'Unknown' }}
  386.                             <span class="n-date">{{ blog.createdAt ? blog.createdAt|date('d/m/Y') : 'N/A' }}</span>
  387.                         </p>
  388.                         <a href="{{ path('honeybee_single_blog') }}?id={{ blog.id }}" class="n-blog-card-link">Read More →</a>
  389.                     </div>
  390.                 </div>
  391.             {% else %}
  392.                 <div style="grid-column:1/-1; text-align:center; color:var(--n-muted); padding:40px 0;">No blogs available at the moment.</div>
  393.             {% endfor %}
  394.         </div>
  395.         <!-- Pagination -->
  396.         {% if totalPages > 1 %}
  397.         <div class="n-pagination">
  398.             <a href="{{ path('honeybee_blogs', {action: 'create', page: currentPage - 1}) }}"
  399.                class="n-pag-btn btn-pag {{ currentPage <= 1 ? 'disabled' : '' }}">
  400.                 <i class="fa fa-chevron-left"></i> Prev
  401.             </a>
  402.             <div class="n-page-nums">
  403.                 {% for p in 1..totalPages %}
  404.                     {% if p == currentPage %}
  405.                         <span class="n-page-num page-num active">{{ p }}</span>
  406.                     {% elseif p == 1 or p == totalPages or (p >= currentPage - 2 and p <= currentPage + 2) %}
  407.                         <a href="{{ path('honeybee_blogs', {action: 'create', page: p}) }}" class="n-page-num page-num">{{ p }}</a>
  408.                     {% elseif p == currentPage - 3 or p == currentPage + 3 %}
  409.                         <span class="n-page-num dots">…</span>
  410.                     {% endif %}
  411.                 {% endfor %}
  412.             </div>
  413.             <a href="{{ path('honeybee_blogs', {action: 'create', page: currentPage + 1}) }}"
  414.                class="n-pag-btn btn-pag {{ currentPage >= totalPages ? 'disabled' : '' }}">
  415.                 Next <i class="fa fa-chevron-right"></i>
  416.             </a>
  417.         </div>
  418.         {% endif %}
  419.     </div>
  420. </section>
  421. {% include '@HoneybeeWeb/footer/central_footer.html.twig' %}
  422. <script>
  423.     document.addEventListener("DOMContentLoaded", function () {
  424.         // Featured blog truncate
  425.         const cardText = document.querySelector("#blogSection .card-text");
  426.         if (cardText) {
  427.             const fullText = cardText.getAttribute("data-full-text") || '';
  428.             const words = fullText.trim().split(/\s+/);
  429.             cardText.textContent = words.length > 30 ? words.slice(0, 30).join(" ") + "..." : fullText;
  430.         }
  431.         const ITEMS_PER_PAGE = 6;
  432.         let currentPage = 1;
  433.         let activeTopicId = 'all';
  434.         let searchTerm = '';
  435.         const searchInput        = document.getElementById('blogSearchInput');
  436.         const searchBtn          = document.getElementById('blogSearchBtn');
  437.         const topicBtns          = document.querySelectorAll('.topic-btn');
  438.         const allBlogItems       = document.querySelectorAll('.filterable-blog-item');
  439.         const featuredSection    = document.getElementById('blogSection');
  440.         const recentSidebar      = document.querySelector('.recent-blog-sidebar');
  441.         const paginationContainer = document.querySelector('.pagination-container');
  442.         const prevBtn            = document.getElementById('blogPrevPageBtn');
  443.         const nextBtn            = document.getElementById('blogNextPageBtn');
  444.         const currentPageDisplay = document.getElementById('currentPageDisplay');
  445.         const totalPagesDisplay  = document.getElementById('totalPagesDisplay');
  446.         function getFilteredItems() {
  447.             return Array.from(allBlogItems).filter(item => {
  448.                 const itemTopic  = item.getAttribute('data-topic');
  449.                 const titleText  = (item.querySelector('.searchable-title')?.textContent || '').toLowerCase();
  450.                 const matchesTopic  = activeTopicId === 'all' || itemTopic == activeTopicId;
  451.                 const matchesSearch = titleText.includes(searchTerm);
  452.                 return matchesTopic && matchesSearch;
  453.             });
  454.         }
  455.         function renderPage() {
  456.             const isFiltering = activeTopicId !== 'all' || searchTerm !== '';
  457.             if (featuredSection) featuredSection.style.display = isFiltering ? 'none' : '';
  458.             if (recentSidebar)   recentSidebar.style.display   = isFiltering ? 'none' : '';
  459.             const filteredItems = getFilteredItems();
  460.             const totalPages    = Math.max(1, Math.ceil(filteredItems.length / ITEMS_PER_PAGE));
  461.             if (currentPage > totalPages) currentPage = totalPages;
  462.             if (currentPage < 1)          currentPage = 1;
  463.             const start = (currentPage - 1) * ITEMS_PER_PAGE;
  464.             const end   = start + ITEMS_PER_PAGE;
  465.             allBlogItems.forEach(item => item.style.display = 'none');
  466.             filteredItems.forEach((item, index) => {
  467.                 item.style.display = (index >= start && index < end) ? '' : 'none';
  468.             });
  469.             if (currentPageDisplay) currentPageDisplay.textContent = currentPage;
  470.             if (totalPagesDisplay)  totalPagesDisplay.textContent  = totalPages;
  471.             if (prevBtn) prevBtn.disabled = currentPage === 1;
  472.             if (nextBtn) nextBtn.disabled = currentPage === totalPages;
  473.             if (paginationContainer) paginationContainer.style.display = totalPages <= 1 ? 'none' : 'flex';
  474.         }
  475.         topicBtns.forEach(btn => {
  476.             btn.addEventListener('click', function() {
  477.                 topicBtns.forEach(b => b.classList.remove('active'));
  478.                 this.classList.add('active');
  479.                 activeTopicId = this.getAttribute('data-topic-id');
  480.                 currentPage = 1;
  481.                 renderPage();
  482.             });
  483.         });
  484.         function onSearch() {
  485.             searchTerm = searchInput.value.toLowerCase().trim();
  486.             currentPage = 1;
  487.             renderPage();
  488.         }
  489.         if (searchInput) searchInput.addEventListener('input', onSearch);
  490.         if (searchBtn)   searchBtn.addEventListener('click', onSearch);
  491.         if (prevBtn) prevBtn.addEventListener('click', function() {
  492.             if (currentPage > 1) { currentPage--; renderPage(); }
  493.         });
  494.         if (nextBtn) nextBtn.addEventListener('click', function() {
  495.             const totalPages = Math.ceil(getFilteredItems().length / ITEMS_PER_PAGE);
  496.             if (currentPage < totalPages) { currentPage++; renderPage(); }
  497.         });
  498.         renderPage();
  499.     });
  500. </script>