-

+
{name}
{title}
diff --git a/src/lib/components/cms/CmsComponent.svelte b/src/lib/components/cms/CmsComponent.svelte
new file mode 100644
index 0000000..b546925
--- /dev/null
+++ b/src/lib/components/cms/CmsComponent.svelte
@@ -0,0 +1,16 @@
+
+
+{#if component}
+ {#await component then { default: Component }}
+
+ {/await}
+{/if}
diff --git a/src/lib/mapping/strapiMapping.svelte.ts b/src/lib/mapping/strapiMapping.svelte.ts
new file mode 100644
index 0000000..dda2a35
--- /dev/null
+++ b/src/lib/mapping/strapiMapping.svelte.ts
@@ -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
}>> = {
+ '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);
+};
diff --git a/src/lib/types/data.ts b/src/lib/types/data.ts
index 23be4a2..f5aa20e 100644
--- a/src/lib/types/data.ts
+++ b/src/lib/types/data.ts
@@ -34,6 +34,7 @@ export type Profile = {
title: string;
anchorId: string;
text: string;
+ imageUrl: string;
};
export type Projects = {
diff --git a/src/lib/types/strapi.ts b/src/lib/types/strapi.ts
new file mode 100644
index 0000000..fe31fcf
--- /dev/null
+++ b/src/lib/types/strapi.ts
@@ -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;
+
+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;
\ No newline at end of file
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts
new file mode 100644
index 0000000..203bd9b
--- /dev/null
+++ b/src/routes/+layout.server.ts
@@ -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)
+ };
+};
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index c4baccd..0161d55 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -3,12 +3,10 @@
import '$lib/styles/font.css';
import favicon from '$lib/assets/favicon.svg';
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 type { LayoutProps } from '../../.svelte-kit/types/src/routes/$types';
- let { children } = $props();
- let data: Data = content;
+ let { data, children }: LayoutProps = $props();
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 7bffda2..c5ef0c0 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,15 +1,11 @@
-
-
-
-
\ No newline at end of file
+{#each data.components as component, index (index)}
+
+{/each}
\ No newline at end of file