diff --git a/cypress/e2e/blog.cy.ts b/cypress/e2e/blog.cy.ts
index 53770ae9..8694b26c 100644
--- a/cypress/e2e/blog.cy.ts
+++ b/cypress/e2e/blog.cy.ts
@@ -12,7 +12,7 @@ describe('/blog',{
})
it('should create a blog entry correctly', () => {
- cy.intercept({method: 'GET', url: '**/domeblog'}, {statusCode: 200, body: []}).as('blogGET')
+ cy.intercept({method: 'GET', url: '**/domeblog*'}, {statusCode: 200, body: []}).as('blogGET')
cy.intercept({method: 'POST', url: '**/domeblog'}, {statusCode: 201, body: blogEntry}).as('blogPOST')
cy.visit('/blog')
cy.wait('@blogGET')
diff --git a/cypress/support/constants.ts b/cypress/support/constants.ts
index c7509316..572ad92c 100644
--- a/cypress/support/constants.ts
+++ b/cypress/support/constants.ts
@@ -344,6 +344,7 @@ export const productOffering = {
]
}
export const blogEntry = {
+ contentType: 'blog',
title: 'Entry title',
slug: 'entry-title',
featuredImage: '',
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index dcf09ce0..07a32ff3 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -5,6 +5,7 @@ import { AboutDomeComponent } from "src/app/pages/about-dome/about-dome.componen
import { BlogEntryDetailComponent } from "src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component";
import { DomeBlogComponent } from "src/app/pages/dome-blog/dome-blog.component";
import { EntryFormComponent } from "src/app/pages/dome-blog/entry-form/entry-form.component";
+import { FaqComponent } from "src/app/offerings/faq/faq.component";
import { UsageSpecsComponent } from "src/app/pages/usage-specs/usage-specs.component";
import { AuthGuard } from './guard/auth.guard';
import { quoteGuardGuard } from './guard/quote-guard.guard';
@@ -126,11 +127,27 @@ const routes: Routes = [
},
{
path: 'blog',
- component: DomeBlogComponent
+ component: DomeBlogComponent,
+ data: { contentType: 'blog' }
},
{
path: 'blog/:slugOrId',
- component: BlogEntryDetailComponent
+ component: BlogEntryDetailComponent,
+ data: { contentType: 'blog' }
+ },
+ {
+ path: 'news',
+ component: DomeBlogComponent,
+ data: { contentType: 'news' }
+ },
+ {
+ path: 'news/:slugOrId',
+ component: BlogEntryDetailComponent,
+ data: { contentType: 'news' }
+ },
+ {
+ path: 'faq',
+ component: FaqComponent
},
{
path: 'blog-entry',
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 952108db..2257a51d 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -100,6 +100,7 @@ import { OperatorRevenueSharingComponent } from "src/app/pages/admin/operator-re
import { ProviderRevenueSharingComponent } from "src/app/pages/user-profile/profile-sections/provider-revenue-sharing/provider-revenue-sharing.component";
import { MarkdownTextareaComponent } from "src/app/shared/forms/markdown-textarea/markdown-textarea.component";
import { RequestValidationModalComponent } from './pages/seller-offerings/offerings/seller-product-spec/update-product-spec/request-validation-modal/request-validation-modal.component';
+import { ConfirmDialogComponent } from './shared/confirm-dialog/confirm-dialog.component';
@NgModule({
declarations: [
@@ -204,7 +205,8 @@ import { RequestValidationModalComponent } from './pages/seller-offerings/offeri
AboutDomeComponent,
MarkdownTextareaComponent,
ProviderRevenueSharingComponent,
- OperatorRevenueSharingComponent
+ OperatorRevenueSharingComponent,
+ ConfirmDialogComponent
],
providers: [
AppInitService,
diff --git a/src/app/offerings/faq/faq.component.css b/src/app/offerings/faq/faq.component.css
index e69de29b..2423929f 100644
--- a/src/app/offerings/faq/faq.component.css
+++ b/src/app/offerings/faq/faq.component.css
@@ -0,0 +1,98 @@
+:host {
+ display: block;
+}
+
+.faq-card {
+ background: #fff;
+ border: 1px solid #d8dade;
+ border-radius: 8px;
+ padding: 24px;
+ transition: border-color 0.25s ease, box-shadow 0.25s ease;
+}
+
+.faq-card--open {
+ box-shadow: 0 10px 26px rgba(11, 21, 40, 0.08);
+}
+
+.faq-question {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 24px;
+ background: transparent;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ text-align: left;
+ font: inherit;
+ color: inherit;
+}
+
+.faq-question__title {
+ flex: 1;
+ color: #0b1528;
+ font-weight: 700;
+ font-size: 20px;
+ line-height: 26px;
+ overflow-wrap: anywhere;
+}
+
+.faq-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #dde6f6;
+ color: #2d58a7;
+ flex-shrink: 0;
+ transition: background-color 0.25s ease, color 0.25s ease;
+}
+
+.faq-icon--open {
+ background: #2d58a7;
+ color: #fff;
+}
+
+.faq-answer {
+ display: grid;
+ grid-template-rows: 0fr;
+ opacity: 0;
+ margin-top: 0;
+ transition: grid-template-rows 0.25s ease, opacity 0.2s ease, margin-top 0.25s ease;
+}
+
+.faq-answer--open {
+ grid-template-rows: 1fr;
+ opacity: 1;
+ margin-top: 20px;
+}
+
+.faq-answer__inner {
+ min-height: 0;
+ overflow: hidden;
+}
+
+.faq-divider {
+ height: 1px;
+ width: 100%;
+ background: #d8dade;
+ margin-bottom: 20px;
+}
+
+.faq-answer__content {
+ color: #4c5a6b;
+ font-size: 17px;
+ line-height: 27px;
+ overflow-wrap: anywhere;
+}
+
+:host-context(.dark) .faq-card {
+ background: var(--secondary-100, #1f2937);
+}
+
+:host-context(.dark) .faq-question__title,
+:host-context(.dark) .faq-answer__content {
+ color: #fff;
+}
diff --git a/src/app/offerings/faq/faq.component.html b/src/app/offerings/faq/faq.component.html
index e4925d6e..2c43cb2c 100644
--- a/src/app/offerings/faq/faq.component.html
+++ b/src/app/offerings/faq/faq.component.html
@@ -1,58 +1,76 @@
-
-
-
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
- sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
-
-
- Go to FAQs
-
+
+
+
+
+
+
+
+ Add FAQ
+
+
+
+
+
+
+
+
+
+
+
+ {{ faq.title }}
-
-
-
-
Flowbite is an open-source library of interactive components built on top of Tailwind CSS including buttons, dropdowns, modals, navbars, and more.
-
Check out this guide to learn how to get started and start developing websites even faster with components on top of Tailwind CSS.
-
-
-
-
- Is there a Figma file available?
-
+
+
-
+
+
+
+
+
-
-
-
-
-
Flowbite is first conceptualized and designed using the Figma software so everything you see in the library has a design equivalent in our Figma file.
-
Check out the Figma design system based on the utility classes from Tailwind CSS and components from Flowbite.
+
+
-
-
- What are the differences between Flowbite and Tailwind UI?
-
-
-
-
-
-
-
-
-
The main difference is that the core components from Flowbite are open source under the MIT license, whereas Tailwind UI is a paid product. Another difference is that Flowbite relies on smaller and standalone components, whereas Tailwind UI offers sections of pages.
-
However, we actually recommend using both Flowbite, Flowbite Pro, and even Tailwind UI as there is no technical reason stopping you from using the best of two worlds.
-
Learn more about these technologies:
-
+
+
-
-
+
+
+ No FAQs have been published yet.
+
+
+
+
+
diff --git a/src/app/offerings/faq/faq.component.spec.ts b/src/app/offerings/faq/faq.component.spec.ts
index efc33537..f1308f52 100644
--- a/src/app/offerings/faq/faq.component.spec.ts
+++ b/src/app/offerings/faq/faq.component.spec.ts
@@ -2,19 +2,29 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { DomeBlogServiceService } from 'src/app/services/dome-blog-service.service';
+import { LocalStorageService } from 'src/app/services/local-storage.service';
import { FaqComponent } from './faq.component';
describe('FaqComponent', () => {
let component: FaqComponent;
let fixture: ComponentFixture;
+ let domeBlogService: jasmine.SpyObj;
beforeEach(async () => {
+ domeBlogService = jasmine.createSpyObj('DomeBlogServiceService', ['getBlogEntries', 'deleteBlogEntry']);
+ domeBlogService.getBlogEntries.and.resolveTo([]);
+ domeBlogService.deleteBlogEntry.and.resolveTo({});
+
await TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
- imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
- declarations: [FaqComponent]
+ imports: [RouterTestingModule, TranslateModule.forRoot()],
+ declarations: [FaqComponent],
+ providers: [
+ { provide: DomeBlogServiceService, useValue: domeBlogService },
+ { provide: LocalStorageService, useValue: { getObject: jasmine.createSpy('getObject').and.returnValue({}) } }
+ ]
})
.compileComponents();
@@ -26,4 +36,13 @@ describe('FaqComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ it('should open one FAQ at a time', () => {
+ component.toggleFaq('one');
+ expect(component.isOpen('one')).toBeTrue();
+
+ component.toggleFaq('two');
+ expect(component.isOpen('one')).toBeFalse();
+ expect(component.isOpen('two')).toBeTrue();
+ });
});
diff --git a/src/app/offerings/faq/faq.component.ts b/src/app/offerings/faq/faq.component.ts
index 06c68c49..400aa14d 100644
--- a/src/app/offerings/faq/faq.component.ts
+++ b/src/app/offerings/faq/faq.component.ts
@@ -1,10 +1,136 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+import moment from 'moment';
+import { LoginInfo } from 'src/app/models/interfaces';
+import { DomeBlogServiceService } from 'src/app/services/dome-blog-service.service';
+import { LocalStorageService } from 'src/app/services/local-storage.service';
@Component({
selector: 'app-faq',
templateUrl: './faq.component.html',
styleUrl: './faq.component.css'
})
-export class FaqComponent {
+export class FaqComponent implements OnInit {
+ faqs: any[] = [];
+ openFaqId = '';
+ checkAdmin = false;
+ deletingEntryId: string | null = null;
+ showDeleteConfirm = false;
+ pendingDeleteEntry: any = null;
+ deleteConfirmTitle = 'Delete FAQ';
+ deleteConfirmMessage = '';
+ deleteConfirmButtonText = 'Delete';
+ deleteConfirmButtonClass = 'px-4 py-2 text-sm font-medium text-white bg-red-700 border border-transparent rounded-md hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500';
+
+ constructor(
+ private router: Router,
+ private domeBlogService: DomeBlogServiceService,
+ private localStorage: LocalStorageService
+ ) {}
+
+ async ngOnInit(): Promise {
+ this.initPartyInfo();
+ await this.loadFaqs();
+ }
+
+ async loadFaqs() {
+ try {
+ const response = await this.domeBlogService.getBlogEntries({ contentType: 'faq' });
+ this.faqs = this.extractEntries(response).filter((entry) => this.isFaqEntry(entry));
+ } catch (error) {
+ this.faqs = [];
+ }
+ }
+
+ toggleFaq(id: string) {
+ this.openFaqId = this.openFaqId === id ? '' : id;
+ }
+
+ isOpen(id: string): boolean {
+ return this.openFaqId === id;
+ }
+
+ getEntryId(entry: any): string {
+ return entry?._id || entry?.id || entry?.slug || entry?.title;
+ }
+
+ goToCreate() {
+ this.router.navigate(['/blog-entry'], { queryParams: { type: 'faq' } });
+ }
+
+ goToUpdate(entry: any) {
+ const id = entry?._id || entry?.id;
+ if (!id) {
+ return;
+ }
+
+ this.router.navigate(['/blog-entry/', id], { queryParams: { type: 'faq' } });
+ }
+
+ canManageEntry(): boolean {
+ return this.checkAdmin;
+ }
+
+ isDeletingEntry(entry: any): boolean {
+ return this.deletingEntryId === (entry?._id || entry?.id);
+ }
+
+ openDeleteDialog(entry: any) {
+ const id = entry?._id || entry?.id;
+ if (!id || this.isDeletingEntry(entry)) {
+ return;
+ }
+
+ this.pendingDeleteEntry = entry;
+ this.deleteConfirmMessage = `Are you sure you want to delete "${entry.title}"? This action cannot be undone.`;
+ this.showDeleteConfirm = true;
+ }
+
+ closeDeleteDialog() {
+ this.showDeleteConfirm = false;
+ this.pendingDeleteEntry = null;
+ }
+
+ async confirmDeleteEntry() {
+ const id = this.pendingDeleteEntry?._id || this.pendingDeleteEntry?.id;
+ if (!id) {
+ this.closeDeleteDialog();
+ return;
+ }
+
+ this.deletingEntryId = id;
+ this.closeDeleteDialog();
+ try {
+ await this.domeBlogService.deleteBlogEntry(id);
+ await this.loadFaqs();
+ } catch (error) {
+ console.error('There was an error while deleting the FAQ entry!', error);
+ } finally {
+ this.deletingEntryId = null;
+ }
+ }
+
+ private initPartyInfo(){
+ const aux = this.localStorage.getObject('login_items') as LoginInfo;
+ if(JSON.stringify(aux) != '{}' && (((aux.expire - moment().unix())-4) > 0)) {
+ this.checkAdmin=aux.roles.some(role =>
+ role.name === 'admin'
+ );
+ }
+ }
+
+ private extractEntries(response: any): any[] {
+ if (Array.isArray(response)) {
+ return response;
+ }
+
+ const possibleEntries = response?.items || response?.entries || response?.data || response?.results || response?.content;
+ return Array.isArray(possibleEntries) ? possibleEntries : [];
+ }
+
+ private isFaqEntry(entry: any): boolean {
+ const contentType = (entry?.contentType || entry?.type || '').toString().trim().toLowerCase();
+ return contentType === 'faq';
+ }
}
diff --git a/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.html b/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.html
index 4f1bdfe4..259c9640 100644
--- a/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.html
+++ b/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.html
@@ -1,5 +1,5 @@
-
+
@@ -29,7 +29,7 @@
-
{{entry.title}}
+
{{entry.title}}
@if(canManageEntry()){
{
await component.ngOnInit();
- expect(domeBlogService.getBlogEntries).toHaveBeenCalled();
+ expect(domeBlogService.getBlogEntries).toHaveBeenCalledWith({ contentType: 'blog' });
expect(domeBlogService.getBlogEntryById).toHaveBeenCalledWith('entry-1');
expect(titleService.setTitle).toHaveBeenCalledWith('Entry title');
expect(metaService.updateTag).toHaveBeenCalledWith({ name: 'description', content: 'Meta description' });
diff --git a/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.ts b/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.ts
index 64a2b18c..0adb9afc 100644
--- a/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.ts
+++ b/src/app/pages/dome-blog/blog-entry-detail/blog-entry-detail.component.ts
@@ -8,6 +8,7 @@ import { LoginInfo } from "src/app/models/interfaces";
import moment from 'moment';
import { ConfirmDialogComponent } from "src/app/shared/confirm-dialog/confirm-dialog.component";
import { Meta, Title } from "@angular/platform-browser";
+import { DomeBlogContentType } from "src/app/services/dome-blog-service.service";
@Component({
selector: 'app-blog-entry-detail',
@@ -30,6 +31,8 @@ export class BlogEntryDetailComponent implements OnInit {
blogId:any='';
partyId:any='';
checkAdmin:boolean=false;
+ contentType: DomeBlogContentType = 'blog';
+ routeBase = '/blog';
deleting:boolean=false;
showDeleteConfirm = false;
deleteConfirmTitle = 'Delete entry';
@@ -38,6 +41,7 @@ export class BlogEntryDetailComponent implements OnInit {
deleteConfirmButtonClass = 'px-4 py-2 text-sm font-medium text-white bg-red-700 border border-transparent rounded-md hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500';
async ngOnInit(): Promise {
+ this.applyRouteConfiguration();
this.initPartyInfo();
this.blogId = this.route.snapshot.paramMap.get('slugOrId') || this.route.snapshot.paramMap.get('id')!;
this.entry = await this.getEntryBySlugOrId(this.blogId);
@@ -45,7 +49,7 @@ export class BlogEntryDetailComponent implements OnInit {
}
goBack(){
- this.router.navigate(['/blog']);
+ this.router.navigate([this.routeBase]);
}
goToUpdate() {
@@ -54,7 +58,7 @@ export class BlogEntryDetailComponent implements OnInit {
return;
}
- this.router.navigate(['/blog-entry/', entryId]);
+ this.router.navigate(['/blog-entry/', entryId], { queryParams: { type: this.contentType } });
}
canManageEntry(): boolean {
@@ -107,7 +111,8 @@ export class BlogEntryDetailComponent implements OnInit {
}
try {
- const entries = await this.domeBlogService.getBlogEntries();
+ const entriesResponse = await this.domeBlogService.getBlogEntries({ contentType: this.contentType });
+ const entries = this.extractEntries(entriesResponse);
const matchedEntry = entries.find((entry: any) => entry.slug === slugOrId);
if (matchedEntry?._id) {
try {
@@ -162,6 +167,21 @@ export class BlogEntryDetailComponent implements OnInit {
}
}
+ private applyRouteConfiguration() {
+ const routeContentType = this.route?.snapshot.data?.['contentType'] as DomeBlogContentType | undefined;
+ this.contentType = routeContentType || 'blog';
+ this.routeBase = this.contentType === 'news' ? '/news' : '/blog';
+ }
+
+ private extractEntries(response: any): any[] {
+ if (Array.isArray(response)) {
+ return response;
+ }
+
+ const possibleEntries = response?.items || response?.entries || response?.data || response?.results || response?.content;
+ return Array.isArray(possibleEntries) ? possibleEntries : [];
+ }
+
private getEntryMetaDescription(): string {
const explicitMeta = (this.entry?.metaDescription || '').toString().trim();
if (explicitMeta) {
diff --git a/src/app/pages/dome-blog/dome-blog.component.html b/src/app/pages/dome-blog/dome-blog.component.html
index 82218c42..d3bc3a46 100644
--- a/src/app/pages/dome-blog/dome-blog.component.html
+++ b/src/app/pages/dome-blog/dome-blog.component.html
@@ -1,19 +1,19 @@
-
+
@if(checkAdmin){
- Add a new entry
+ {{ createButtonLabel }}
@@ -28,7 +28,7 @@
@for(entry of entries; track entry){
-
{{ entry.title }}
+
{{ entry.title }}
@if(canManageEntry(entry)){
@@ -65,7 +65,7 @@
@if(getFeaturedImage(entry); as featuredImage){
+ [ngClass]="getImageClass()">
}
}
+ @if(hasMoreEntries){
+
+
+ {{ loadingMore ? 'Loading...' : 'Load more' }}
+
+
+ }
diff --git a/src/app/pages/dome-blog/dome-blog.component.ts b/src/app/pages/dome-blog/dome-blog.component.ts
index 147e5dfc..af92ac9f 100644
--- a/src/app/pages/dome-blog/dome-blog.component.ts
+++ b/src/app/pages/dome-blog/dome-blog.component.ts
@@ -1,9 +1,9 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import {EventMessageService} from "src/app/services/event-message.service";
import {LocalStorageService} from "src/app/services/local-storage.service";
-import { DomeBlogServiceService } from "src/app/services/dome-blog-service.service"
+import { DomeBlogContentType, DomeBlogServiceService } from "src/app/services/dome-blog-service.service"
import { LoginInfo } from 'src/app/models/interfaces';
import moment from 'moment';
import { Subject } from 'rxjs';
@@ -17,11 +17,14 @@ import { ConfirmDialogComponent } from "src/app/shared/confirm-dialog/confirm-di
styleUrl: './dome-blog.component.css'
})
export class DomeBlogComponent implements OnInit, OnDestroy {
+ private readonly pageLimit = 9;
+
constructor(
private router: Router,
private eventMessage: EventMessageService,
private localStorage: LocalStorageService,
private domeBlogService: DomeBlogServiceService,
+ private route?: ActivatedRoute,
) {
this.eventMessage.messages$.subscribe(ev => {
if(ev.type === 'ChangedSession') {
@@ -42,8 +45,17 @@ export class DomeBlogComponent implements OnInit, OnDestroy {
deleteConfirmButtonClass = 'px-4 py-2 text-sm font-medium text-white bg-red-700 border border-transparent rounded-md hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500';
entries:any[]=[ ]
+ contentType: DomeBlogContentType = 'blog';
+ pageTitle = 'Blog';
+ createButtonLabel = 'Add a new entry';
+ routeBase = '/blog';
+ isNewsLayout = false;
+ currentPage = 1;
+ hasMoreEntries = false;
+ loadingMore = false;
async ngOnInit(): Promise
{
+ this.applyRouteConfiguration();
this.initPartyInfo();
await this.loadEntries();
}
@@ -72,11 +84,11 @@ export class DomeBlogComponent implements OnInit, OnDestroy {
goToDetails(entry:any) {
- this.router.navigate(['/blog/', this.getEntryRouteId(entry)]);
+ this.router.navigate([`${this.routeBase}/`, this.getEntryRouteId(entry)]);
}
goToCreate(){
- this.router.navigate(['/blog-entry']);
+ this.router.navigate(['/blog-entry'], { queryParams: { type: this.contentType } });
}
goToUpdate(id:any){
@@ -126,10 +138,42 @@ export class DomeBlogComponent implements OnInit, OnDestroy {
async loadEntries() {
try {
- let entries = await this.domeBlogService.getBlogEntries();
- this.entries = Array.isArray(entries) ? entries : [];
+ this.currentPage = 1;
+ const response = await this.domeBlogService.getBlogEntries({
+ contentType: this.contentType,
+ page: this.currentPage,
+ limit: this.pageLimit
+ });
+ const { entries, hasMore } = this.normalizeEntryResponse(response, this.currentPage);
+ this.entries = entries;
+ this.hasMoreEntries = hasMore;
} catch (error) {
this.entries = [];
+ this.hasMoreEntries = false;
+ }
+ }
+
+ async loadMoreEntries() {
+ if (this.loadingMore || !this.hasMoreEntries) {
+ return;
+ }
+
+ this.loadingMore = true;
+ try {
+ const nextPage = this.currentPage + 1;
+ const response = await this.domeBlogService.getBlogEntries({
+ contentType: this.contentType,
+ page: nextPage,
+ limit: this.pageLimit
+ });
+ const { entries, hasMore } = this.normalizeEntryResponse(response, nextPage);
+ this.entries = [...this.entries, ...entries];
+ this.currentPage = nextPage;
+ this.hasMoreEntries = hasMore;
+ } catch (error) {
+ this.hasMoreEntries = false;
+ } finally {
+ this.loadingMore = false;
}
}
@@ -186,6 +230,55 @@ export class DomeBlogComponent implements OnInit, OnDestroy {
return this.truncateText(plainTextContent, 260);
}
+ getCardTitleClass(): string {
+ return this.isNewsLayout
+ ? 'text-[28px] leading-tight md:text-[30px]'
+ : 'text-2xl';
+ }
+
+ getImageClass(): string {
+ return this.isNewsLayout
+ ? 'h-72 w-full rounded-lg border border-gray-300 object-cover md:h-64 md:w-[432px] md:min-w-[432px] dark:border-gray-700'
+ : 'h-52 w-full rounded-lg border border-gray-300 object-contain p-2 md:h-44 md:w-72 md:min-w-72 dark:border-gray-700';
+ }
+
+ private applyRouteConfiguration() {
+ const routeContentType = this.route?.snapshot.data?.['contentType'] as DomeBlogContentType | undefined;
+ this.contentType = routeContentType || 'blog';
+ this.isNewsLayout = this.contentType === 'news';
+ this.routeBase = this.isNewsLayout ? '/news' : '/blog';
+ this.pageTitle = this.isNewsLayout ? 'News & Events' : 'Blog';
+ this.createButtonLabel = this.isNewsLayout ? 'Add news/event' : 'Add a new entry';
+ }
+
+ private normalizeEntryResponse(response: any, page: number): { entries: any[]; hasMore: boolean } {
+ const rawEntries = this.extractEntries(response);
+ const entries = rawEntries.filter((entry) => this.matchesContentType(entry));
+ const total = Number(response?.total ?? response?.count ?? response?.pagination?.total ?? 0);
+ const hasExplicitNext = Boolean(response?.next || response?.hasMore || response?.pagination?.hasNext);
+ const hasMore = hasExplicitNext || (total > 0 && page * this.pageLimit < total);
+
+ return { entries, hasMore };
+ }
+
+ private extractEntries(response: any): any[] {
+ if (Array.isArray(response)) {
+ return response;
+ }
+
+ const possibleEntries = response?.items || response?.entries || response?.data || response?.results || response?.content;
+ return Array.isArray(possibleEntries) ? possibleEntries : [];
+ }
+
+ private matchesContentType(entry: any): boolean {
+ const entryContentType = (entry?.contentType || entry?.type || '').toString().trim().toLowerCase();
+ if (this.contentType === 'blog') {
+ return !entryContentType || entryContentType === 'blog';
+ }
+
+ return entryContentType === this.contentType;
+ }
+
private stripMarkdown(content: string): string {
return content
.replace(/!\[[^\]]*]\([^)]*\)/g, ' ')
diff --git a/src/app/pages/dome-blog/entry-form/entry-form.component.html b/src/app/pages/dome-blog/entry-form/entry-form.component.html
index 6b5adcc8..afa95a36 100644
--- a/src/app/pages/dome-blog/entry-form/entry-form.component.html
+++ b/src/app/pages/dome-blog/entry-form/entry-form.component.html
@@ -1,6 +1,6 @@
-
+
@@ -29,8 +29,8 @@
-
- {{ blogId ? 'Update blog entry' : 'Create blog entry' }}
+
+ {{ blogId ? 'Update CMS entry' : 'Create CMS entry' }}
@if(loading){