Private
Public Access
1
0

feat: add strapi cms data

- add types
- add layout data loading
- add mapping functions
- add CmsComponent
- remove image
This commit is contained in:
2026-02-09 19:49:55 +01:00
parent 049f3a5f2f
commit 78b47c4823
9 changed files with 349 additions and 18 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 KiB

View File

@@ -1,14 +1,13 @@
<script lang="ts"> <script lang="ts">
import type { Profile } from '$lib/types/data'; import type { Profile } from '$lib/types/data';
import profileImage from '$lib/assets/images/profile_picture.png';
let { name, title, anchorId, text }: Profile = $props(); let { name, title, anchorId, text, imageUrl }: Profile = $props();
</script> </script>
<section id={anchorId}> <section id={anchorId}>
<div> <div>
<div class="image-wrapper"> <div class="image-wrapper">
<img src={profileImage} alt=""> <img src={imageUrl} alt="">
</div> </div>
<h1>{name}</h1> <h1>{name}</h1>
<p class="title">{title}</p> <p class="title">{title}</p>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import type { StrapiComponentUnion } from '$lib/types/strapi';
import { cmsComponentMapping, mapComponentData } from '$lib/mapping/strapiMapping.svelte';
/** @type {{ data: import('$lib/types/strapi/collection-types/page.js').Component}} */
let data: StrapiComponentUnion = $props();
let component = cmsComponentMapping[data.__component];
let componentData = mapComponentData(data)
</script>
{#if component}
{#await component then { default: Component }}
<Component {...componentData} />
{/await}
{/if}

View File

@@ -0,0 +1,131 @@
import type {
StrapiComponentUnion,
StrapiContactComponent,
StrapiFooterResponse,
StrapiHomepageResponse,
StrapiProfileComponent,
StrapiProjectComponent,
StrapiSkillComponent
} from '$lib/types/strapi';
import type {
Contact,
Footer,
Header,
HeaderLink,
Profile,
Projects,
Skills
} from '$lib/types/data';
import type { ComponentKeys } from '$lib/types/strapi';
import type { Component } from 'svelte';
import { PUBLIC_STRAPI_URL } from '$env/static/public';
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export const cmsComponentMapping: Record<ComponentKeys, Promise<{ default: Component<any> }>> = {
'shared.profile': import('$lib/components/Profile.svelte'),
'shared.project': import('$lib/components/Projects.svelte'),
'shared.skill': import('$lib/components/Skills.svelte'),
'shared.contact': import('$lib/components/Contact.svelte')
};
export const mapHeaderData = (data: StrapiHomepageResponse): Header => {
const links: HeaderLink[] = data.data.components.map((component) => {
if (component.__component === 'shared.profile') {
return {
label: "Profile",
href: `#${component.profile.anchor_id}`
};
} else if (component.__component === 'shared.project') {
return {
label: component.project.title,
href: `#${component.project.anchor_id}`
};
} else if (component.__component === 'shared.skill') {
return {
label: component.skill.title,
href: `#${component.skill.anchor_id}`
};
}
return {
label: component.contact.title,
href: `#${component.contact.anchor_id}`
};
});
return { links };
};
export const mapFooterData = (data: StrapiFooterResponse): Footer => {
return {
copyright: data.data.copyright,
links: {
git: data.data.git,
linkedin: data.data.linkedin
}
};
};
const mapProfileData = (data: StrapiProfileComponent): Profile => {
return {
name: data.profile.name,
title: data.profile.title,
anchorId: data.profile.anchor_id,
text: data.profile.description,
imageUrl: PUBLIC_STRAPI_URL + data.profile.image.formats.small.url
};
};
const mapProjectData = (data: StrapiProjectComponent): Projects => {
return {
title: data.project.title,
anchorId: data.project.anchor_id,
entries: data.project.entries.map((entry) => ({
title: entry.front_title,
description: entry.front_description,
taskHeadline: entry.back_title,
tasks: entry.back_description,
toolsHeadline: entry.back_title_second,
tools: entry.back_content_list
}))
};
};
const mapSkillData = (data: StrapiSkillComponent): Skills => {
return {
anchorId: data.skill.anchor_id,
subtitle: data.skill.subtitle,
title: data.skill.title,
entries: data.skill.entries.map((entry) => ({
title: entry.title,
icon: entry.icon,
items: entry.content
}))
};
};
const mapContactData = (data: StrapiContactComponent): Contact => {
return {
anchorId: data.contact.anchor_id,
subtitle: data.contact.subtitle,
title: data.contact.title,
form: {
title: data.contact.form_title,
subjectPlaceholder: data.contact.subject_placeholder,
messagePlaceholder: data.contact.message_placeholder,
submit: data.contact.cta_text
}
};
};
export const mapComponentData = (data: StrapiComponentUnion) => {
if (data.__component === 'shared.profile') {
return mapProfileData(data);
} else if (data.__component === 'shared.project') {
return mapProjectData(data);
} else if (data.__component === 'shared.skill') {
return mapSkillData(data);
}
return mapContactData(data);
};

View File

@@ -34,6 +34,7 @@ export type Profile = {
title: string; title: string;
anchorId: string; anchorId: string;
text: string; text: string;
imageUrl: string;
}; };
export type Projects = { export type Projects = {

135
src/lib/types/strapi.ts Normal file
View File

@@ -0,0 +1,135 @@
export type StrapiSharedMetaData = {
id: number;
documentId: string;
createdAt: string;
updatedAt: string;
publishedAt: string;
};
export type StrapiImageFormat = {
ext: string;
url: string;
hash: string;
mime: string;
name: string;
path: string;
size: number;
width: number;
height: number;
sizeInBytes: number;
};
export type StrapiImage = {
alternativeText: string;
caption: string;
focalPoint: object;
formats: {
small: StrapiImageFormat;
medium: StrapiImageFormat;
thumbnail: StrapiImageFormat;
};
previewUrl: string;
provider: string;
provider_metadata: object;
} & StrapiSharedMetaData &
Omit<StrapiImageFormat, 'path' | 'sizeInBytes'>;
export type StrapiHomepageResponse = {
data: StrapiHomepage;
meta: object;
};
export type StrapiHomepage = {
title: string;
components: StrapiComponentUnion[];
} & StrapiSharedMetaData;
export interface StrapiProfileComponent {
__component: 'shared.profile';
id: number;
profile: StrapiProfile;
}
export interface StrapiProjectComponent {
__component: 'shared.project';
id: number;
project: StrapiProject;
}
export interface StrapiSkillComponent {
__component: 'shared.skill';
id: number;
skill: StrapiSkill;
}
export interface StrapiContactComponent {
__component: 'shared.contact';
id: number;
contact: StrapiContact;
}
export type StrapiComponentUnion =
| StrapiProfileComponent
| StrapiProjectComponent
| StrapiSkillComponent
| StrapiContactComponent;
export type ComponentKeys = StrapiComponentUnion['__component'];
export type StrapiProfile = {
name: string;
title: string;
description: string;
anchor_id: string;
image: StrapiImage;
} & StrapiSharedMetaData;
export type StrapiProject = {
title: string;
subtitle: string;
anchor_id: string;
entries: StrapiProjectEntry[];
} & StrapiSharedMetaData;
export type StrapiProjectEntry = {
front_title: string;
front_description: string;
back_title: string;
back_description: string;
back_title_second: string;
back_content_list: string[];
} & StrapiSharedMetaData;
export type StrapiSkill = {
title: string;
subtitle: string;
anchor_id: string;
entries: StrapiSkillEntry[];
} & StrapiSharedMetaData;
export type StrapiSkillEntry = {
title: string;
icon: string;
content: string[];
} & StrapiSharedMetaData;
export type StrapiContact = {
title: string;
subtitle: string;
form_title: string;
subject_placeholder: string;
message_placeholder: string;
cta_text: string;
anchor_id: string;
} & StrapiSharedMetaData;
export type StrapiFooterResponse = {
data: StrapiFooter;
meta: object;
};
export type StrapiFooter = {
copyright: string;
linkedin: string;
git: string;
} & StrapiSharedMetaData;

View File

@@ -0,0 +1,55 @@
import { STRAPI_API_KEY } from '$env/static/private';
import { PUBLIC_STRAPI_URL } from '$env/static/public';
import type { LayoutServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { mapFooterData, mapHeaderData } from '$lib/mapping/strapiMapping.svelte.js';
import type { StrapiComponentUnion } from '$lib/types/strapi';
export const load: LayoutServerLoad = async () => {
const query = new URLSearchParams({
// Profile: category 'shared', component 'profile', relation 'profile', sub-field 'image'
'populate[components][on][shared.profile][populate][profile][populate]': 'image',
// Project: category 'shared', component 'project', relation 'project', sub-relation 'entries'
'populate[components][on][shared.project][populate][project][populate]': 'entries',
// Skill: category 'shared', component 'skill', relation 'skill', sub-relation 'entries'
'populate[components][on][shared.skill][populate][skill][populate]': 'entries',
// Contact: category 'shared', component 'contact', relation 'contact'
'populate[components][on][shared.contact][populate]': 'contact'
}).toString();
const requestInit: RequestInit = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${STRAPI_API_KEY}`
}
};
const [homepageResponse, footerResponse] = await Promise.allSettled([
fetch(`${PUBLIC_STRAPI_URL}/api/homepage?${query}`, requestInit),
fetch(`${PUBLIC_STRAPI_URL}/api/footer`, requestInit)
]);
if (
homepageResponse.status === 'rejected' ||
footerResponse.status === 'rejected' ||
!homepageResponse.value.ok ||
!footerResponse.value.ok
) {
throw error(500, 'Failed to connect to the CMS');
}
const [homepageData, footerData] = await Promise.all([
homepageResponse.value.json(),
footerResponse.value.json()
]);
return {
header: mapHeaderData(homepageData),
components: homepageData.data.components as StrapiComponentUnion[],
footer: mapFooterData(footerData)
};
};

View File

@@ -3,12 +3,10 @@
import '$lib/styles/font.css'; import '$lib/styles/font.css';
import favicon from '$lib/assets/favicon.svg'; import favicon from '$lib/assets/favicon.svg';
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
import content from '$lib/data/data.json';
import type { Data } from '$lib/types/data';
import Footer from '$lib/components/Footer.svelte'; import Footer from '$lib/components/Footer.svelte';
import type { LayoutProps } from '../../.svelte-kit/types/src/routes/$types';
let { children } = $props(); let { data, children }: LayoutProps = $props();
let data: Data = content;
</script> </script>
<svelte:head> <svelte:head>

View File

@@ -1,15 +1,11 @@
<script lang="ts"> <script lang="ts">
import content from '$lib/data/data.json'; import type { PageProps } from '../../.svelte-kit/types/src/routes/$types';
import type { Data } from '$lib/types/data'; import CmsComponent from '$lib/components/cms/CmsComponent.svelte';
import Profile from '$lib/components/Profile.svelte';
import Projects from '$lib/components/Projects.svelte';
import Skills from '$lib/components/Skills.svelte';
import Contact from '$lib/components/Contact.svelte';
let data: Data = content; let { data }: PageProps = $props();
console.debug(data)
</script> </script>
<Profile {...data.profile} /> {#each data.components as component, index (index)}
<Projects {...data.projects} /> <CmsComponent {...component} />
<Skills {...data.skills} /> {/each}
<Contact {...data.contact} />