feat: manage preistraeger fallback media via payload
This commit is contained in:
@@ -97,7 +97,10 @@ export const preistraegerIndexFields: Field[] = [
|
|||||||
label: '06 · Winner cards',
|
label: '06 · Winner cards',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Hover label shown on winner cards.'),
|
admin: sectionAdmin('Hover label shown on winner cards.'),
|
||||||
fields: [text('hoverLabel', 'Hover label', preistraegerIndexContent.card.hoverLabel)],
|
fields: [
|
||||||
|
uploadField('fallbackImage', 'Fallback card image', `Current frontend fallback image: /images/${preistraegerIndexContent.card.fallbackImageFilename}`),
|
||||||
|
text('hoverLabel', 'Hover label', preistraegerIndexContent.card.hoverLabel),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cta',
|
name: 'cta',
|
||||||
|
|||||||
@@ -120,6 +120,15 @@ export const Preistraeger: CollectionConfig = {
|
|||||||
'Shared labels, explanatory copy, CTAs, and supporting media for the Preisträger detail route.',
|
'Shared labels, explanatory copy, CTAs, and supporting media for the Preisträger detail route.',
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'fallbackImage',
|
||||||
|
type: 'upload',
|
||||||
|
relationTo: 'media',
|
||||||
|
label: 'Fallback hero image',
|
||||||
|
admin: {
|
||||||
|
description: `Current frontend fallback image: /images/${preistraegerDetailContent.fallbackImageFilename}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'breadcrumb',
|
name: 'breadcrumb',
|
||||||
label: 'Breadcrumb',
|
label: 'Breadcrumb',
|
||||||
|
|||||||
@@ -1458,6 +1458,10 @@ export interface Page {
|
|||||||
* Hover label shown on winner cards.
|
* Hover label shown on winner cards.
|
||||||
*/
|
*/
|
||||||
card?: {
|
card?: {
|
||||||
|
/**
|
||||||
|
* Current frontend fallback image: /images/gala-saal-overview.jpg
|
||||||
|
*/
|
||||||
|
fallbackImage?: (number | null) | Media;
|
||||||
hoverLabel?: string | null;
|
hoverLabel?: string | null;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
@@ -2760,6 +2764,10 @@ export interface Preistraeger {
|
|||||||
* Shared labels, explanatory copy, CTAs, and supporting media for the Preisträger detail route.
|
* Shared labels, explanatory copy, CTAs, and supporting media for the Preisträger detail route.
|
||||||
*/
|
*/
|
||||||
detailPage?: {
|
detailPage?: {
|
||||||
|
/**
|
||||||
|
* Current frontend fallback image: /images/gala-saal-overview.jpg
|
||||||
|
*/
|
||||||
|
fallbackImage?: (number | null) | Media;
|
||||||
breadcrumb?: {
|
breadcrumb?: {
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
label?: string | null;
|
label?: string | null;
|
||||||
@@ -4306,6 +4314,7 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
card?:
|
card?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
fallbackImage?: T;
|
||||||
hoverLabel?: T;
|
hoverLabel?: T;
|
||||||
};
|
};
|
||||||
cta?:
|
cta?:
|
||||||
@@ -5183,6 +5192,7 @@ export interface PreistraegerSelect<T extends boolean = true> {
|
|||||||
detailPage?:
|
detailPage?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
fallbackImage?: T;
|
||||||
breadcrumb?:
|
breadcrumb?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ function pageData(page: SpaPageRegistryEntry) {
|
|||||||
|
|
||||||
function postData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
function postData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
||||||
const article = articles.find((item) => item.slug === targetSlug(page))
|
const article = articles.find((item) => item.slug === targetSlug(page))
|
||||||
|
const { fallbackImageFilename: _fallbackImageFilename, ...detailContent } = blogDetailContent
|
||||||
return {
|
return {
|
||||||
...baseData(page),
|
...baseData(page),
|
||||||
heroImage: mediaID(mediaMap, article?.img),
|
heroImage: mediaID(mediaMap, article?.img),
|
||||||
@@ -132,7 +133,8 @@ function postData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
|||||||
tags: article?.tags?.map((tag) => ({ tag })) || [],
|
tags: article?.tags?.map((tag) => ({ tag })) || [],
|
||||||
relatedSlugs: article?.relatedSlugs?.map((slug) => ({ slug })) || [],
|
relatedSlugs: article?.relatedSlugs?.map((slug) => ({ slug })) || [],
|
||||||
detailPage: {
|
detailPage: {
|
||||||
...blogDetailContent,
|
...detailContent,
|
||||||
|
fallbackImage: mediaID(mediaMap, `/${blogDetailContent.fallbackImageFilename}`),
|
||||||
metaCopy: blogDetailContent.meta,
|
metaCopy: blogDetailContent.meta,
|
||||||
},
|
},
|
||||||
content: emptyRichText(
|
content: emptyRichText(
|
||||||
@@ -147,6 +149,7 @@ function postData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
|||||||
|
|
||||||
function eventData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
function eventData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
||||||
const event = events.find((item) => item.slug === targetSlug(page))
|
const event = events.find((item) => item.slug === targetSlug(page))
|
||||||
|
const { fallbackImageFilename: _fallbackImageFilename, ...detailContent } = eventDetailContent
|
||||||
return {
|
return {
|
||||||
...baseData(page),
|
...baseData(page),
|
||||||
subtitle: event?.subtitle,
|
subtitle: event?.subtitle,
|
||||||
@@ -162,7 +165,8 @@ function eventData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
|||||||
eckdaten: event?.eckdaten || [],
|
eckdaten: event?.eckdaten || [],
|
||||||
updates: event?.updates?.map(({ id: _id, ...update }) => update) || [],
|
updates: event?.updates?.map(({ id: _id, ...update }) => update) || [],
|
||||||
detailPage: {
|
detailPage: {
|
||||||
...eventDetailContent,
|
...detailContent,
|
||||||
|
fallbackImage: mediaID(mediaMap, `/${eventDetailContent.fallbackImageFilename}`),
|
||||||
updatesCopy: eventDetailContent.updates,
|
updatesCopy: eventDetailContent.updates,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -170,6 +174,7 @@ function eventData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
|||||||
|
|
||||||
function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
||||||
const winner = WINNERS.find((item) => item.slug === targetSlug(page))
|
const winner = WINNERS.find((item) => item.slug === targetSlug(page))
|
||||||
|
const { fallbackImageFilename: _fallbackImageFilename, ...detailContent } = preistraegerDetailContent
|
||||||
const { imageFilename: _sideImageFilename, ...sideImage } = preistraegerDetailContent.sideImage
|
const { imageFilename: _sideImageFilename, ...sideImage } = preistraegerDetailContent.sideImage
|
||||||
return {
|
return {
|
||||||
...baseData(page),
|
...baseData(page),
|
||||||
@@ -188,7 +193,8 @@ function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
|||||||
website: winner?.website,
|
website: winner?.website,
|
||||||
hasMedia: winner?.hasMedia,
|
hasMedia: winner?.hasMedia,
|
||||||
detailPage: {
|
detailPage: {
|
||||||
...preistraegerDetailContent,
|
...detailContent,
|
||||||
|
fallbackImage: mediaID(mediaMap, `/${preistraegerDetailContent.fallbackImageFilename}`),
|
||||||
sideImage: {
|
sideImage: {
|
||||||
...sideImage,
|
...sideImage,
|
||||||
image: mediaID(mediaMap, `/${preistraegerDetailContent.sideImage.imageFilename}`),
|
image: mediaID(mediaMap, `/${preistraegerDetailContent.sideImage.imageFilename}`),
|
||||||
@@ -284,6 +290,9 @@ async function upsertPage(payload: Payload, page: SpaPageRegistryEntry, mediaMap
|
|||||||
async function loadMediaMap(payload: Payload): Promise<MediaMap> {
|
async function loadMediaMap(payload: Payload): Promise<MediaMap> {
|
||||||
const sources = new Set([
|
const sources = new Set([
|
||||||
...WINNERS.map((winner) => winner.img),
|
...WINNERS.map((winner) => winner.img),
|
||||||
|
`/${blogDetailContent.fallbackImageFilename}`,
|
||||||
|
`/${eventDetailContent.fallbackImageFilename}`,
|
||||||
|
`/${preistraegerDetailContent.fallbackImageFilename}`,
|
||||||
`/${preistraegerDetailContent.sideImage.imageFilename}`,
|
`/${preistraegerDetailContent.sideImage.imageFilename}`,
|
||||||
...events.map((event) => event.img),
|
...events.map((event) => event.img),
|
||||||
...articles.map((article) => article.img),
|
...articles.map((article) => article.img),
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ const mediaByFilename = async (payload: Payload, filename: string) => {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
const sideImage = await mediaByFilename(payload, preistraegerDetailContent.sideImage.imageFilename)
|
const [fallbackImage, sideImage] = await Promise.all([
|
||||||
|
mediaByFilename(payload, preistraegerDetailContent.fallbackImageFilename),
|
||||||
|
mediaByFilename(payload, preistraegerDetailContent.sideImage.imageFilename),
|
||||||
|
])
|
||||||
|
const { fallbackImageFilename: _fallbackImageFilename, ...detailContent } = preistraegerDetailContent
|
||||||
const { imageFilename: _sideImageFilename, ...sideImageContent } = preistraegerDetailContent.sideImage
|
const { imageFilename: _sideImageFilename, ...sideImageContent } = preistraegerDetailContent.sideImage
|
||||||
|
|
||||||
const winners = await payload.find({
|
const winners = await payload.find({
|
||||||
@@ -37,7 +41,8 @@ async function main() {
|
|||||||
context: { disableRevalidate: true },
|
context: { disableRevalidate: true },
|
||||||
data: {
|
data: {
|
||||||
detailPage: {
|
detailPage: {
|
||||||
...preistraegerDetailContent,
|
...detailContent,
|
||||||
|
fallbackImage,
|
||||||
sideImage: {
|
sideImage: {
|
||||||
...sideImageContent,
|
...sideImageContent,
|
||||||
image: sideImage,
|
image: sideImage,
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
import 'dotenv/config'
|
import 'dotenv/config'
|
||||||
|
|
||||||
import config from '@payload-config'
|
import config from '@payload-config'
|
||||||
import { getPayload } from 'payload'
|
import { getPayload, type Payload } from 'payload'
|
||||||
|
|
||||||
import { preistraegerIndexContent } from '@/spa/preistraegerIndexContent'
|
import { preistraegerIndexContent } from '@/spa/preistraegerIndexContent'
|
||||||
|
|
||||||
|
const mediaByFilename = async (payload: Payload, filename: string) => {
|
||||||
|
const result = await payload.find({
|
||||||
|
collection: 'media',
|
||||||
|
limit: 1,
|
||||||
|
pagination: false,
|
||||||
|
where: { filename: { equals: filename } },
|
||||||
|
})
|
||||||
|
return result.docs[0]?.id
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
|
const fallbackImage = await mediaByFilename(payload, preistraegerIndexContent.card.fallbackImageFilename)
|
||||||
|
const { fallbackImageFilename: _cardFallbackImageFilename, ...cardContent } = preistraegerIndexContent.card
|
||||||
|
|
||||||
const pageResult = await payload.find({
|
const pageResult = await payload.find({
|
||||||
collection: 'pages',
|
collection: 'pages',
|
||||||
@@ -28,7 +40,13 @@ async function main() {
|
|||||||
overrideAccess: true,
|
overrideAccess: true,
|
||||||
context: { disableRevalidate: true },
|
context: { disableRevalidate: true },
|
||||||
data: {
|
data: {
|
||||||
preistraegerIndex: preistraegerIndexContent,
|
preistraegerIndex: {
|
||||||
|
...preistraegerIndexContent,
|
||||||
|
card: {
|
||||||
|
...cardContent,
|
||||||
|
fallbackImage,
|
||||||
|
},
|
||||||
|
},
|
||||||
} as never,
|
} as never,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -17,18 +17,19 @@ const BG_ALT = '#E4E2E3';
|
|||||||
|
|
||||||
type PreistraegerIndexCms = Partial<typeof preistraegerIndexContent> & {
|
type PreistraegerIndexCms = Partial<typeof preistraegerIndexContent> & {
|
||||||
hero?: Partial<typeof preistraegerIndexContent.hero> & { image?: unknown }
|
hero?: Partial<typeof preistraegerIndexContent.hero> & { image?: unknown }
|
||||||
|
card?: Partial<typeof preistraegerIndexContent.card> & { fallbackImage?: unknown }
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||||
|
|
||||||
const normalizeWinner = (doc: CmsRouteDoc): Winner => ({
|
const normalizeWinner = (doc: CmsRouteDoc, fallbackImage: string): Winner => ({
|
||||||
id: String(doc.id),
|
id: String(doc.id),
|
||||||
slug: String(doc.slug || ''),
|
slug: String(doc.slug || ''),
|
||||||
name: String(doc.title || 'Preisträger'),
|
name: String(doc.title || 'Preisträger'),
|
||||||
category: String(doc.category || 'Preisträger'),
|
category: String(doc.category || 'Preisträger'),
|
||||||
year: Number(doc.year || new Date().getFullYear()),
|
year: Number(doc.year || new Date().getFullYear()),
|
||||||
type: String(doc.awardType || 'Auszeichnung'),
|
type: String(doc.awardType || 'Auszeichnung'),
|
||||||
img: docImageUrl(doc, '/images/gala-saal-overview.jpg'),
|
img: docImageUrl(doc, fallbackImage),
|
||||||
shortDesc: String(doc.shortDesc || doc.description || doc.meta?.description || ''),
|
shortDesc: String(doc.shortDesc || doc.description || doc.meta?.description || ''),
|
||||||
longDesc: String(doc.longDesc || doc.description || doc.meta?.description || ''),
|
longDesc: String(doc.longDesc || doc.description || doc.meta?.description || ''),
|
||||||
quote: String(doc.quote || ''),
|
quote: String(doc.quote || ''),
|
||||||
@@ -52,12 +53,16 @@ export default function Preistraeger() {
|
|||||||
const filters = { ...preistraegerIndexContent.filters, ...(cms.filters || {}) };
|
const filters = { ...preistraegerIndexContent.filters, ...(cms.filters || {}) };
|
||||||
const count = { ...preistraegerIndexContent.count, ...(cms.count || {}) };
|
const count = { ...preistraegerIndexContent.count, ...(cms.count || {}) };
|
||||||
const empty = { ...preistraegerIndexContent.empty, ...(cms.empty || {}) };
|
const empty = { ...preistraegerIndexContent.empty, ...(cms.empty || {}) };
|
||||||
const card = { ...preistraegerIndexContent.card, ...(cms.card || {}) };
|
const card = useMemo(() => ({ ...preistraegerIndexContent.card, ...(cms.card || {}) }), [cms.card]);
|
||||||
const cta = { ...preistraegerIndexContent.cta, ...(cms.cta || {}) };
|
const cta = { ...preistraegerIndexContent.cta, ...(cms.cta || {}) };
|
||||||
|
const cardFallbackImage = useMemo(
|
||||||
|
() => mediaUrl(card.fallbackImage, `/images/${preistraegerIndexContent.card.fallbackImageFilename}`),
|
||||||
|
[card],
|
||||||
|
);
|
||||||
const winners = useMemo(() => {
|
const winners = useMemo(() => {
|
||||||
if (cmsPreistraeger.length) return cmsPreistraeger.map(normalizeWinner).filter((winner) => winner.slug);
|
if (cmsPreistraeger.length) return cmsPreistraeger.map((doc) => normalizeWinner(doc, cardFallbackImage)).filter((winner) => winner.slug);
|
||||||
return WINNERS;
|
return WINNERS;
|
||||||
}, [cmsPreistraeger]);
|
}, [cardFallbackImage, cmsPreistraeger]);
|
||||||
const years = useMemo(() => [...new Set(winners.map((winner) => winner.year))].sort((a, b) => b - a), [winners]);
|
const years = useMemo(() => [...new Set(winners.map((winner) => winner.year))].sort((a, b) => b - a), [winners]);
|
||||||
const categories = useMemo(() => [...new Set(winners.map((winner) => winner.category))].sort((a, b) => a.localeCompare(b, 'de')), [winners]);
|
const categories = useMemo(() => [...new Set(winners.map((winner) => winner.category))].sort((a, b) => a.localeCompare(b, 'de')), [winners]);
|
||||||
const [activeYear, setActiveYear] = useState<number | undefined>(undefined);
|
const [activeYear, setActiveYear] = useState<number | undefined>(undefined);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const GRAY = '#666666';
|
|||||||
const BG_ALT = '#E4E2E3';
|
const BG_ALT = '#E4E2E3';
|
||||||
|
|
||||||
type PreistraegerDetailCms = Partial<typeof preistraegerDetailContent> & {
|
type PreistraegerDetailCms = Partial<typeof preistraegerDetailContent> & {
|
||||||
|
fallbackImage?: unknown
|
||||||
sideImage?: Partial<typeof preistraegerDetailContent.sideImage> & { image?: unknown }
|
sideImage?: Partial<typeof preistraegerDetailContent.sideImage> & { image?: unknown }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +27,11 @@ export default function PreistraegerDetail() {
|
|||||||
const { slug } = useParams<{ slug: string }>();
|
const { slug } = useParams<{ slug: string }>();
|
||||||
const route = useCmsRoute();
|
const route = useCmsRoute();
|
||||||
const cmsWinner = route?.collection === 'preistraeger' ? route.doc : undefined;
|
const cmsWinner = route?.collection === 'preistraeger' ? route.doc : undefined;
|
||||||
|
const cmsDetail = {
|
||||||
|
...preistraegerDetailContent,
|
||||||
|
...((cmsWinner?.detailPage || {}) as PreistraegerDetailCms),
|
||||||
|
} as PreistraegerDetailCms;
|
||||||
|
const winnerFallbackImage = mediaUrl(cmsDetail.fallbackImage, `/images/${preistraegerDetailContent.fallbackImageFilename}`);
|
||||||
const winner = cmsWinner ? {
|
const winner = cmsWinner ? {
|
||||||
id: String(cmsWinner.id),
|
id: String(cmsWinner.id),
|
||||||
slug: String(cmsWinner.slug || slug),
|
slug: String(cmsWinner.slug || slug),
|
||||||
@@ -33,7 +39,7 @@ export default function PreistraegerDetail() {
|
|||||||
category: String(cmsWinner.category || 'Preisträger'),
|
category: String(cmsWinner.category || 'Preisträger'),
|
||||||
year: Number(cmsWinner.year || new Date().getFullYear()),
|
year: Number(cmsWinner.year || new Date().getFullYear()),
|
||||||
type: String(cmsWinner.awardType || 'Auszeichnung'),
|
type: String(cmsWinner.awardType || 'Auszeichnung'),
|
||||||
img: docImageUrl(cmsWinner, '/images/gala-saal-overview.jpg'),
|
img: docImageUrl(cmsWinner, winnerFallbackImage),
|
||||||
shortDesc: String(cmsWinner.shortDesc || cmsWinner.description || cmsWinner.meta?.description || ''),
|
shortDesc: String(cmsWinner.shortDesc || cmsWinner.description || cmsWinner.meta?.description || ''),
|
||||||
longDesc: String(cmsWinner.longDesc || cmsWinner.description || cmsWinner.meta?.description || 'Dieser Preisträger wird aus Payload CMS geladen.'),
|
longDesc: String(cmsWinner.longDesc || cmsWinner.description || cmsWinner.meta?.description || 'Dieser Preisträger wird aus Payload CMS geladen.'),
|
||||||
quote: String(cmsWinner.quote || 'Diese Erfolgsgeschichte wird aktuell in Payload CMS gepflegt.'),
|
quote: String(cmsWinner.quote || 'Diese Erfolgsgeschichte wird aktuell in Payload CMS gepflegt.'),
|
||||||
@@ -47,10 +53,7 @@ export default function PreistraegerDetail() {
|
|||||||
} : WINNERS.find(w => w.slug === slug);
|
} : WINNERS.find(w => w.slug === slug);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const detail = {
|
const detail = (winner && 'detailPage' in winner ? { ...preistraegerDetailContent, ...(winner.detailPage || {}) } : cmsDetail) as PreistraegerDetailCms;
|
||||||
...preistraegerDetailContent,
|
|
||||||
...((winner && 'detailPage' in winner ? winner.detailPage : {}) || {}),
|
|
||||||
} as PreistraegerDetailCms;
|
|
||||||
const breadcrumb = { ...preistraegerDetailContent.breadcrumb, ...(detail.breadcrumb || {}) };
|
const breadcrumb = { ...preistraegerDetailContent.breadcrumb, ...(detail.breadcrumb || {}) };
|
||||||
const sections = { ...preistraegerDetailContent.sections, ...(detail.sections || {}) };
|
const sections = { ...preistraegerDetailContent.sections, ...(detail.sections || {}) };
|
||||||
const sideImage = { ...preistraegerDetailContent.sideImage, ...(detail.sideImage || {}) };
|
const sideImage = { ...preistraegerDetailContent.sideImage, ...(detail.sideImage || {}) };
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export const preistraegerDetailContent = {
|
export const preistraegerDetailContent = {
|
||||||
|
fallbackImageFilename: 'gala-saal-overview.jpg',
|
||||||
notFoundRedirect: '/preistraeger',
|
notFoundRedirect: '/preistraeger',
|
||||||
breadcrumb: {
|
breadcrumb: {
|
||||||
url: '/preistraeger',
|
url: '/preistraeger',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const preistraegerIndexContent = {
|
|||||||
message: 'Keine Preisträger für diese Auswahl.',
|
message: 'Keine Preisträger für diese Auswahl.',
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
|
fallbackImageFilename: 'gala-saal-overview.jpg',
|
||||||
hoverLabel: 'Mehr erfahren',
|
hoverLabel: 'Mehr erfahren',
|
||||||
},
|
},
|
||||||
cta: {
|
cta: {
|
||||||
|
|||||||
Reference in New Issue
Block a user