Skip to content

Commit

Permalink
chore(sdk): Update examples to handle Content API (#28811)
Browse files Browse the repository at this point in the history
## Proposed changes

- Update `footer` component on Angular and NextJS Library to handle
calls to content API and render content

## Screenshots

### NextJS
<img width="1494" alt="Screenshot 2024-06-10 at 2 33 17 PM"
src="https://github.com/dotCMS/core/assets/63567962/dccd92ed-0572-451c-b7a7-ae75502bcc48">


### Angular
<img width="1489" alt="Screenshot 2024-06-10 at 4 54 22 PM"
src="https://github.com/dotCMS/core/assets/63567962/0ff2c82d-486b-45e0-bd4c-27f91f1c0632">
  • Loading branch information
zJaaal authored Jun 11, 2024
1 parent 02e6e1a commit 7f68e2e
Show file tree
Hide file tree
Showing 16 changed files with 405 additions and 44 deletions.
22 changes: 22 additions & 0 deletions examples/angular/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions examples/angular/src/app/components/blogs/blogs.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Component, OnInit, inject, signal } from '@angular/core';
import { DOTCMS_CLIENT_TOKEN } from '../../client-token/dotcms-client';
import { GenericContentlet } from '../../utils';
import { ContentletsComponent } from '../contentlets/contentlets.component';
import { Contentlet } from '@dotcms/client/src/lib/client/content/shared/types';

@Component({
selector: 'app-blogs',
standalone: true,
imports: [ContentletsComponent],
template: ` <div class="flex flex-col">
<h2 class="text-2xl font-bold mb-7 text-black">Latest Blog Posts</h2>
@if(!!blogs().length) { <app-contentlets [contentlets]="blogs()" /> }
</div>`,
})
export class BlogsComponent implements OnInit {
private readonly client = inject(DOTCMS_CLIENT_TOKEN);

readonly blogs = signal<Contentlet<GenericContentlet>[]>([]);

ngOnInit(): void {
this.client.content
.getCollection<GenericContentlet>('Blog')
.limit(3)
.sortBy([
{
field: 'modDate',
order: 'desc',
},
])
.then((response) => {
this.blogs.set(response.contentlets);
})
.catch((error) => {
console.error('Error fetching Blogs', error);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Component, Input } from '@angular/core';
import { Contentlet } from '@dotcms/client/src/lib/client/content/shared/types';
import { GenericContentlet } from '../../utils';
import { environment } from '../../../environments/environment';
import { NgOptimizedImage } from '@angular/common';

@Component({
selector: 'app-contentlets',
standalone: true,
imports: [NgOptimizedImage],
template: `<ul class="flex flex-col gap-7">
@for(contentlet of contentlets; track contentlet.identifier) {
<li class="flex gap-7 min-h-16">
<a class="min-w-32 relative" [href]="contentlet.urlMap ?? contentlet.url">
<img
[ngSrc]="getImageUrl(contentlet)"
[fill]="true"
[alt]="contentlet.urlTitle ?? contentlet.title"
class="object-cover"
/>
</a>
<div class="flex flex-col gap-1">
<a
class="text-sm text-zinc-900 font-bold"
[href]="contentlet.urlMap ?? contentlet.url"
>
{{ contentlet.title }}
</a>
<time class="text-zinc-600">
{{ getDateString(contentlet.modDate) }}
</time>
</div>
</li>
}
</ul> `,
})
export class ContentletsComponent {
@Input() contentlets: Contentlet<GenericContentlet>[] = [];

dateFormatOptions: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'long',
day: 'numeric',
};

getDateString(date: string): string {
return new Date(date).toLocaleDateString('en-US', this.dateFormatOptions);
}

getImageUrl(contentlet: Contentlet<GenericContentlet>): string {
const host = environment.dotcmsUrl;
return `${host}${contentlet.image}?language_id=${
contentlet.languageId || 1
}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Component, OnInit, inject, signal } from '@angular/core';
import { DOTCMS_CLIENT_TOKEN } from '../../client-token/dotcms-client';
import { GenericContentlet } from '../../utils';
import { ContentletsComponent } from '../contentlets/contentlets.component';
import { Contentlet } from '@dotcms/client/src/lib/client/content/shared/types';

@Component({
selector: 'app-destinations',
standalone: true,
imports: [ContentletsComponent],
template: ` <div class="flex flex-col">
<h2 class="text-2xl font-bold mb-7 text-black">Popular Destinations</h2>
@if(!!destinations().length) {
<app-contentlets [contentlets]="destinations()" /> }
</div>`,
})
export class DestinationsComponent implements OnInit {
private readonly client = inject(DOTCMS_CLIENT_TOKEN);

readonly destinations = signal<Contentlet<GenericContentlet>[]>([]);

ngOnInit() {
this.client.content
.getCollection<GenericContentlet>('Destination')
.limit(3)
.sortBy([
{
field: 'modDate',
order: 'desc',
},
])
.then((response) => {
this.destinations.set(response.contentlets);
})
.catch((error) => {
console.error('Error fetching Destinations', error);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { BlogsComponent } from '../../../components/blogs/blogs.component';
import { DestinationsComponent } from '../../../components/destinations/destinations.component';
import { NgOptimizedImage } from '@angular/common';

@Component({
selector: 'app-footer',
standalone: true,
imports: [],
template: `<footer class="p-4 text-white bg-red-400">Footer</footer>`,
imports: [BlogsComponent, DestinationsComponent, NgOptimizedImage],
template: `<footer class="p-4 text-white bg-red-100 py-24">
<div
class="grid md:grid-cols-3 sm:grid-cols-1 md:grid-rows-1 sm:grid-rows-3 gap-7 mx-24"
>
<div class="flex gap-7 flex-col">
<h2 class="text-2xl font-bold text-black">About us</h2>
<p class="text-sm text-zinc-800">
We are TravelLux, a community of dedicated travel experts,
journalists, and bloggers. Our aim is to offer you the best insight on
where to go for your travel as well as to give you amazing
opportunities with free benefits and bonuses for registered clients.
</p>
<img
[ngSrc]="
environment.dotcmsUrl +
'/contentAsset/image/82da90eb-044d-44cc-a71b-86f79820b61b/fileAsset'
"
height="53"
width="221"
alt="TravelLux logo"
/>
</div>
<app-blogs />
<app-destinations />
</div>
</footer>`,
styleUrl: './footer.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooterComponent { }
export class FooterComponent {
protected readonly environment = environment;
}
15 changes: 11 additions & 4 deletions examples/angular/src/app/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@ import { DynamicComponentEntity } from '@dotcms/angular';

export const DYNAMIC_COMPONENTS: { [key: string]: DynamicComponentEntity } = {
Activity: import('../pages/content-types/activity/activity.component').then(
(c) => c.ActivityComponent,
(c) => c.ActivityComponent
),
Banner: import('../pages/content-types/banner/banner.component').then(
(c) => c.BannerComponent,
(c) => c.BannerComponent
),
Image: import('../pages/content-types/image/image.component').then(
(c) => c.ImageComponent,
(c) => c.ImageComponent
),
webPageContent: import(
'../pages/content-types/web-page-content/web-page-content.component'
).then((c) => c.WebPageContentComponent),
Product: import('../pages/content-types/product/product.component').then(
(c) => c.ProductComponent,
(c) => c.ProductComponent
),
CustomNoComponent: import(
'../pages/content-types/custom-no-component/custom-no-component.component'
).then((c) => c.CustomNoComponent),
};

export type GenericContentlet = {
urlMap?: string;
url: string;
urlTitle?: string;
image: string;
};
28 changes: 14 additions & 14 deletions examples/nextjs/src/app/[[...slug]]/page.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,48 @@
import { dotcmsClient } from "@dotcms/client";
import { MyPage } from "@/components/my-page";

import { handleVanityUrlRedirect } from "../utils";
import { dotcmsClient } from "@dotcms/client";

const client = dotcmsClient.init({
dotcmsUrl: process.env.NEXT_PUBLIC_DOTCMS_HOST,
authToken: process.env.DOTCMS_AUTH_TOKEN,
siteId: '59bb8831-6706-4589-9ca0-ff74016e02b2',
siteId: "59bb8831-6706-4589-9ca0-ff74016e02b2",
requestOptions: {
// In production you might want to deal with this differently
cache: 'no-cache'
}
cache: "no-cache",
},
});

export async function generateMetadata({ params, searchParams }) {
const requestData = {
path: params?.slug ? params.slug.join('/') : 'index',
path: params?.slug ? params.slug.join("/") : "index",
language_id: searchParams.language_id,
"com.dotmarketing.persona.id":
searchParams['com.dotmarketing.persona.id'] || '',
searchParams["com.dotmarketing.persona.id"] || "",
mode: searchParams.mode,
variantName: searchParams['variantName']
variantName: searchParams["variantName"],
};

const data = await client.page.get(requestData);

return {
title: data.entity.page.friendlyName || data.entity.page.title
title: data.entity.page.friendlyName || data.entity.page.title,
};
};
}

export default async function Home({ searchParams, params }) {
const requestData = {
path: params?.slug ? params.slug.join('/') : 'index',
path: params?.slug ? params.slug.join("/") : "index",
language_id: searchParams.language_id,
'com.dotmarketing.persona.id':
searchParams['com.dotmarketing.persona.id'] || '',
"com.dotmarketing.persona.id":
searchParams["com.dotmarketing.persona.id"] || "",
mode: searchParams.mode,
variantName: searchParams['variantName']
variantName: searchParams["variantName"],
};

const data = await client.page.get(requestData);
const nav = await client.nav.get({
path: '/',
path: "/",
depth: 2,
languageId: searchParams.language_id,
});
Expand Down
8 changes: 8 additions & 0 deletions examples/nextjs/src/app/utils/dotcmsClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { dotcmsClient } from "@dotcms/client";

// Client for content fetching
export const client = dotcmsClient.init({
dotcmsUrl: process.env.NEXT_PUBLIC_DOTCMS_HOST,
authToken: "NO_TOKEN",
siteId: "59bb8831-6706-4589-9ca0-ff74016e02b2",
});
19 changes: 12 additions & 7 deletions examples/nextjs/src/components/content-types/activity.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import Image from 'next/image';
import Link from 'next/link';
import { useDotcmsPageContext } from '@dotcms/react';
import Image from "next/image";
import Link from "next/link";
import { useDotcmsPageContext } from "@dotcms/react";

function Activity({ title, description, image, urlTitle }) {
const {
pageAsset: {viewAs: { language }}
pageAsset: {
viewAs: { language },
},
} = useDotcmsPageContext();

return (
<article className="p-4 overflow-hidden bg-white rounded shadow-lg">
{image && (
<Image
className="w-full"
src={`${process.env.NEXT_PUBLIC_DOTCMS_HOST}${image?.idPath || image}?language_id=${language?.id}`}
src={`${process.env.NEXT_PUBLIC_DOTCMS_HOST}${
image?.idPath || image
}?language_id=${language?.id}`}
width={100}
height={100}
alt="Activity Image"
Expand All @@ -24,8 +28,9 @@ function Activity({ title, description, image, urlTitle }) {
</div>
<div className="px-6 pt-4 pb-2">
<Link
href={`/activities/${urlTitle || '#'}`}
className="inline-block px-4 py-2 font-bold text-white bg-purple-500 rounded-full hover:bg-purple-700">
href={`/activities/${urlTitle || "#"}`}
className="inline-block px-4 py-2 font-bold text-white bg-purple-500 rounded-full hover:bg-purple-700"
>
Link to detail →
</Link>
</div>
Expand Down
6 changes: 0 additions & 6 deletions examples/nextjs/src/components/layout/footer.js

This file was deleted.

25 changes: 25 additions & 0 deletions examples/nextjs/src/components/layout/footer/components/aboutUs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import Image from "next/image";

function AboutUs() {
return (
<div className="flex gap-7 flex-col">
<h2 className="text-2xl font-bold text-black">About us</h2>
<p className="text-sm text-zinc-800">
We are TravelLux, a community of dedicated travel experts,
journalists, and bloggers. Our aim is to offer you the best
insight on where to go for your travel as well as to give you
amazing opportunities with free benefits and bonuses for
registered clients.
</p>
<Image
src={`${process.env.NEXT_PUBLIC_DOTCMS_HOST}/contentAsset/image/82da90eb-044d-44cc-a71b-86f79820b61b/fileAsset`}
height={53}
width={221}
alt="TravelLux logo"
/>
</div>
);
}

export default AboutUs;
Loading

0 comments on commit 7f68e2e

Please sign in to comment.