feat: add strapi cms data
- add types - add layout data loading - add mapping functions - add CmsComponent - remove image
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 897 KiB |
@@ -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>
|
||||||
|
|||||||
16
src/lib/components/cms/CmsComponent.svelte
Normal file
16
src/lib/components/cms/CmsComponent.svelte
Normal 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}
|
||||||
131
src/lib/mapping/strapiMapping.svelte.ts
Normal file
131
src/lib/mapping/strapiMapping.svelte.ts
Normal 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);
|
||||||
|
};
|
||||||
@@ -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
135
src/lib/types/strapi.ts
Normal 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;
|
||||||
55
src/routes/+layout.server.ts
Normal file
55
src/routes/+layout.server.ts
Normal 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)
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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} />
|
|
||||||
Reference in New Issue
Block a user