fix: replace revalidate with cache force-cache for SSG compatibility
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import type { PageContentRecord } from '$shared/api';
|
||||
import { getFirstRecord } from '$shared/api';
|
||||
|
||||
/**
|
||||
* Bio section component.
|
||||
* Displays personal biography content from PocketBase.
|
||||
*/
|
||||
export default async function BioSection() {
|
||||
const data = await getFirstRecord<PageContentRecord>('bio', {
|
||||
filter: 'slug = "bio"',
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return <p>Loading bio content...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="prose prose-lg dark:prose-invert max-w-none">
|
||||
<p>{data.content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { PageContentRecord } from '$shared/api';
|
||||
import { getFirstRecord } from '$shared/api';
|
||||
|
||||
/**
|
||||
* Intro section component.
|
||||
* Displays primary introduction content from PocketBase.
|
||||
*/
|
||||
export default async function IntroSection() {
|
||||
const data = await getFirstRecord<PageContentRecord>('intro', {
|
||||
filter: 'slug = "intro"',
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return <p>Loading intro content...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="prose prose-lg dark:prose-invert max-w-none">
|
||||
<p>{data.content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { SidebarNav } from './SidebarNav';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { NavItem } from '../model/types';
|
||||
import { SidebarNav } from './SidebarNav';
|
||||
|
||||
const ITEMS: NavItem[] = [
|
||||
{ id: 'bio', label: 'Bio', number: '01' },
|
||||
@@ -9,13 +9,11 @@ const ITEMS: NavItem[] = [
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
global.IntersectionObserver = vi.fn(function () {
|
||||
return {
|
||||
observe: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
};
|
||||
}) as unknown as typeof IntersectionObserver;
|
||||
global.IntersectionObserver = class {
|
||||
observe = vi.fn();
|
||||
disconnect = vi.fn();
|
||||
unobserve = vi.fn();
|
||||
} as unknown as typeof IntersectionObserver;
|
||||
});
|
||||
|
||||
describe('SidebarNav', () => {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
/**
|
||||
* Registry of dynamic section widgets.
|
||||
*/
|
||||
const SECTIONS: Record<string, () => Promise<{ default: React.ComponentType<Record<string, unknown>> }>> = {
|
||||
intro: () => import('../../../IntroSection/ui/IntroSection/IntroSection'),
|
||||
bio: () => import('../../../BioSection/ui/BioSection/BioSection'),
|
||||
skills: () => import('../../../SkillsSection/ui/SkillsSection/SkillsSection'),
|
||||
experience: () => import('../../../ExperienceSection/ui/ExperienceSection/ExperienceSection'),
|
||||
projects: () => import('../../../ProjectsSection/ui/ProjectsSection/ProjectsSection'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the SectionFactory widget.
|
||||
*/
|
||||
export type SectionFactoryProps = {
|
||||
/**
|
||||
* Section slug to render
|
||||
*/
|
||||
slug: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Factory widget that dynamically imports and renders the correct section widget.
|
||||
* Based on the provided slug.
|
||||
*/
|
||||
export async function SectionFactory({ slug }: SectionFactoryProps) {
|
||||
const loadSection = SECTIONS[slug];
|
||||
|
||||
if (!loadSection) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { default: SectionComponent } = await loadSection();
|
||||
|
||||
return <SectionComponent />;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { SkillRecord } from '$shared/api';
|
||||
import { getCollection } from '$shared/api';
|
||||
import { Badge } from '$shared/ui';
|
||||
|
||||
/**
|
||||
* Skills section component.
|
||||
* Displays technology skills grouped by category.
|
||||
*/
|
||||
export default async function SkillsSection() {
|
||||
const data = await getCollection<SkillRecord>('skills', {
|
||||
sort: 'category,order',
|
||||
});
|
||||
|
||||
const categories = data.items.reduce(
|
||||
(acc, skill) => {
|
||||
if (!acc[skill.category]) {
|
||||
acc[skill.category] = [];
|
||||
}
|
||||
acc[skill.category].push(skill);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, SkillRecord[]>,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
{Object.entries(categories).map(([category, items]) => (
|
||||
<div key={category} className="space-y-4">
|
||||
<h3 className="text-xl font-bold uppercase tracking-widest opacity-50">{category}</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{items.map((skill) => (
|
||||
<Badge key={skill.id}>{skill.name}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user