feat: integrate Strapi CMS for dynamic portfolio data

This commit is contained in:
2026-04-19 12:43:32 +02:00
parent 67e01e9a5e
commit ef9f16c2cc
3 changed files with 154 additions and 156 deletions

View File

@@ -1,10 +1,5 @@
import { createServerFn } from '@tanstack/react-start'
import type { TExperience, TEducation, TSkillGroup } from '#/types/strapi'
import {
experiences as fallbackExperiences,
educations as fallbackEducations,
skillGroups as fallbackSkillGroups,
} from '#/data/portfolio'
import type { TExperience, TEducation, TProfile } from '#/types/strapi'
const STRAPI_BASE = process.env['STRAPI_URL'] ?? 'https://strapi.moshariri.com'
@@ -13,107 +8,41 @@ const buildHeaders = (): Record<string, string> => {
return token ? { Authorization: `Bearer ${token}` } : {}
}
const fetchFromStrapi = async <T>(path: string): Promise<{ data: T[] }> => {
const url = `${STRAPI_BASE}/api${path}`
console.log(`[Strapi] → GET ${url}`)
const response = await fetch(url, { headers: buildHeaders() })
if (!response.ok) {
throw new Error(`Strapi request failed: ${response.status} ${url}`)
}
const json = (await response.json()) as { data: T[] }
console.log(`[Strapi] ← GET ${url} (${json.data.length} items)`)
return json
}
export const fetchExperiences = createServerFn({ method: 'GET' }).handler(
async (): Promise<TExperience[]> => {
try {
const { data } = await fetchFromStrapi<TExperience>(
'/experiences?sort=order:asc&populate=*',
)
return data.map((item) => ({
id: item.id,
company: item.company,
role: item.role,
employmentType: item.employmentType,
startDate: item.startDate,
endDate: item.endDate,
location: item.location,
bullets: item.bullets ?? [],
tech: item.tech ?? [],
order: item.order,
}))
} catch (err) {
console.error('[Strapi] fetchExperiences failed, using fallback:', err)
return fallbackExperiences.map((e, i) => ({
id: i,
company: e.company,
role: e.role,
employmentType:
e.type === 'Full-time' ? 'full-time' : ('part-time' as const),
startDate: e.startDate,
endDate: e.endDate ?? undefined,
location: e.location,
bullets: [...e.bullets],
tech: [...e.tech],
order: i,
}))
}
const url = `${STRAPI_BASE}/api/experiences?populate[objectives]=*&populate[technologies]=*`
console.log(`[Strapi] → GET ${url}`)
const res = await fetch(url, { headers: buildHeaders() })
if (!res.ok)
throw new Error(`Strapi request failed: ${res.status} ${res.url}`)
const json = (await res.json()) as { data: TExperience[] }
console.log(`[Strapi] ← GET ${url} (${json.data.length} items)`)
return json.data
},
)
export const fetchEducations = createServerFn({ method: 'GET' }).handler(
async (): Promise<TEducation[]> => {
try {
const { data } = await fetchFromStrapi<TEducation>(
'/educations?sort=order:asc&populate=*',
)
return data.map((item) => ({
id: item.id,
institution: item.institution,
degree: item.degree,
fieldOfStudy: item.fieldOfStudy,
startYear: item.startYear,
endYear: item.endYear,
grade: item.grade,
order: item.order,
}))
} catch (err) {
console.error('[Strapi] fetchEducations failed, using fallback:', err)
return fallbackEducations.map((e, i) => ({
id: i,
institution: e.institution,
degree: e.degree,
fieldOfStudy: undefined,
startYear: e.startYear,
endYear: e.endYear,
grade: e.grade ?? undefined,
order: i,
}))
}
const url = `${STRAPI_BASE}/api/educations?populate=*`
console.log(`[Strapi] → GET ${url}`)
const res = await fetch(url, { headers: buildHeaders() })
if (!res.ok)
throw new Error(`Strapi request failed: ${res.status} ${res.url}`)
const json = (await res.json()) as { data: TEducation[] }
console.log(`[Strapi] ← GET ${url} (${json.data.length} items)`)
return json.data
},
)
export const fetchSkillGroups = createServerFn({ method: 'GET' }).handler(
async (): Promise<TSkillGroup[]> => {
try {
const { data } = await fetchFromStrapi<TSkillGroup>(
'/skill-groups?sort=order:asc&populate=*',
)
return data.map((item) => ({
id: item.id,
category: item.category,
skills: item.skills ?? [],
order: item.order,
}))
} catch (err) {
console.error('[Strapi] fetchSkillGroups failed, using fallback:', err)
return fallbackSkillGroups.map((g, i) => ({
id: i,
category: g.category,
skills: [...g.skills],
order: i,
}))
}
export const fetchProfile = createServerFn({ method: 'GET' }).handler(
async (): Promise<TProfile> => {
const url = `${STRAPI_BASE}/api/profile?populate[skills][populate]=*`
console.log(`[Strapi] → GET ${url}`)
const res = await fetch(url, { headers: buildHeaders() })
if (!res.ok)
throw new Error(`Strapi request failed: ${res.status} ${res.url}`)
const json = (await res.json()) as { data: TProfile }
console.log(`[Strapi] ← GET ${url} OK`)
return json.data
},
)