feat: major UI/UX overhaul, snippets system, and performance fixes
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 9m26s
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 9m26s
This commit is contained in:
142
lib/directus.ts
142
lib/directus.ts
@@ -24,7 +24,11 @@ function toDirectusLocale(locale: string): string {
|
||||
|
||||
interface FetchOptions {
|
||||
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
||||
body?: any;
|
||||
body?: {
|
||||
query?: string;
|
||||
variables?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
async function directusRequest<T>(
|
||||
@@ -75,9 +79,9 @@ async function directusRequest<T>(
|
||||
}
|
||||
|
||||
return data?.data || null;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
// Timeout oder Network Error - stille fallback
|
||||
if (error?.name === 'TimeoutError' || error?.name === 'AbortError') {
|
||||
if (error && typeof error === 'object' && 'name' in error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error('Directus timeout');
|
||||
}
|
||||
@@ -85,7 +89,8 @@ async function directusRequest<T>(
|
||||
}
|
||||
// Andere Errors nur in dev loggen
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error('Directus request failed:', error?.message);
|
||||
const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : 'Unknown error';
|
||||
console.error('Directus request failed:', message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -107,11 +112,20 @@ export async function getMessages(locale: string): Promise<Record<string, string
|
||||
|
||||
try {
|
||||
const result = await directusRequest('', { body: { query } });
|
||||
const messages = (result as any)?.messages || [];
|
||||
interface MessageData {
|
||||
messages: Array<{
|
||||
key: string;
|
||||
translations?: Array<{
|
||||
languages_code?: { code: string };
|
||||
value?: string;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
const messages = (result as MessageData | null)?.messages || [];
|
||||
const dictionary: Record<string, string> = {};
|
||||
|
||||
messages.forEach((m: any) => {
|
||||
const trans = m.translations?.find((t: any) => t.languages_code?.code === directusLocale);
|
||||
messages.forEach((m) => {
|
||||
const trans = m.translations?.find((t) => t.languages_code?.code === directusLocale);
|
||||
if (trans?.value) dictionary[m.key] = trans.value;
|
||||
});
|
||||
|
||||
@@ -140,21 +154,35 @@ export async function getMessage(key: string, locale: string): Promise<string |
|
||||
|
||||
try {
|
||||
const result = await directusRequest('', { body: { query } });
|
||||
const messages = (result as any)?.messages;
|
||||
interface SingleMessageData {
|
||||
messages: Array<{
|
||||
translations?: Array<{
|
||||
languages_code?: { code: string };
|
||||
value?: string;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
const messages = (result as SingleMessageData | null)?.messages;
|
||||
if (!messages || messages.length === 0) return null;
|
||||
|
||||
const translations = messages[0]?.translations || [];
|
||||
const translation = translations.find((t: any) => t.languages_code?.code === directusLocale);
|
||||
const translation = translations.find((t) => t.languages_code?.code === directusLocale);
|
||||
return translation?.value || null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContentPage {
|
||||
slug: string;
|
||||
content?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export async function getContentPage(
|
||||
slug: string,
|
||||
locale: string
|
||||
): Promise<any | null> {
|
||||
): Promise<ContentPage | null> {
|
||||
const directusLocale = toDirectusLocale(locale);
|
||||
const query = `
|
||||
query {
|
||||
@@ -182,7 +210,10 @@ export async function getContentPage(
|
||||
{ body: { query } }
|
||||
);
|
||||
|
||||
const pages = (result as any)?.content_pages || [];
|
||||
interface ContentPagesResult {
|
||||
content_pages: ContentPage[];
|
||||
}
|
||||
const pages = (result as ContentPagesResult | null)?.content_pages || [];
|
||||
if (pages.length === 0) {
|
||||
// Try without locale filter
|
||||
const fallbackQuery = `
|
||||
@@ -200,7 +231,7 @@ export async function getContentPage(
|
||||
}
|
||||
`;
|
||||
const fallbackResult = await directusRequest('', { body: { query: fallbackQuery } });
|
||||
const fallbackPages = (fallbackResult as any)?.content_pages || [];
|
||||
const fallbackPages = (fallbackResult as ContentPagesResult | null)?.content_pages || [];
|
||||
return fallbackPages[0] || null;
|
||||
}
|
||||
|
||||
@@ -301,7 +332,19 @@ export async function getTechStack(locale: string): Promise<TechStackCategory[]
|
||||
{ body: { query: categoriesQuery } }
|
||||
);
|
||||
|
||||
const categories = (categoriesResult as any)?.tech_stack_categories;
|
||||
interface TechStackCategoriesResult {
|
||||
tech_stack_categories: Array<{
|
||||
id: string;
|
||||
key: string;
|
||||
icon: string;
|
||||
sort: number;
|
||||
translations?: Array<{
|
||||
languages_code?: { code: string };
|
||||
name?: string;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
const categories = (categoriesResult as TechStackCategoriesResult | null)?.tech_stack_categories;
|
||||
|
||||
if (!categories || categories.length === 0) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
@@ -325,15 +368,25 @@ export async function getTechStack(locale: string): Promise<TechStackCategory[]
|
||||
);
|
||||
|
||||
const itemsData = await itemsResponse.json();
|
||||
const allItems = itemsData?.data || [];
|
||||
interface ItemsResponseData {
|
||||
data: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
category: string | number;
|
||||
url?: string;
|
||||
icon_url?: string;
|
||||
sort: number;
|
||||
}>;
|
||||
}
|
||||
const allItems = (itemsData as ItemsResponseData | null)?.data || [];
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[getTechStack] Fetched items:', allItems.length);
|
||||
}
|
||||
|
||||
// Group items by category
|
||||
const categoriesWithItems = categories.map((cat: any) => {
|
||||
const categoryItems = allItems.filter((item: any) =>
|
||||
const categoriesWithItems = categories.map((cat) => {
|
||||
const categoryItems = allItems.filter((item) =>
|
||||
item.category === cat.id || item.category === parseInt(cat.id)
|
||||
);
|
||||
|
||||
@@ -346,6 +399,7 @@ export async function getTechStack(locale: string): Promise<TechStackCategory[]
|
||||
itemsToUse = categoryFallback.items.map((name, idx) => ({
|
||||
id: `fallback-${cat.key}-${idx}`,
|
||||
name: name,
|
||||
category: cat.id,
|
||||
url: undefined,
|
||||
icon_url: undefined,
|
||||
sort: idx + 1
|
||||
@@ -509,7 +563,7 @@ export async function getBookReviews(locale: string): Promise<BookReview[] | nul
|
||||
|
||||
// Projects Types
|
||||
export interface Project {
|
||||
id: string;
|
||||
id: string | number; // Allow both string (from Directus) and number (from Prisma)
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -798,3 +852,57 @@ export async function getProjectBySlug(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Snippets Types
|
||||
export interface Snippet {
|
||||
id: string;
|
||||
title: string;
|
||||
category: string;
|
||||
code: string;
|
||||
description: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Snippets from Directus
|
||||
*/
|
||||
export async function getSnippets(limit = 10, featured?: boolean): Promise<Snippet[] | null> {
|
||||
const filters = ['status: { _eq: "published" }'];
|
||||
if (featured !== undefined) {
|
||||
filters.push(`featured: { _eq: ${featured} }`);
|
||||
}
|
||||
const filterString = `filter: { _and: [{ ${filters.join(' }, { ')} }] }`;
|
||||
|
||||
const query = `
|
||||
query {
|
||||
snippets(
|
||||
${filterString}
|
||||
limit: ${limit}
|
||||
) {
|
||||
id
|
||||
title
|
||||
category
|
||||
code
|
||||
description
|
||||
language
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await directusRequest(
|
||||
'',
|
||||
{ body: { query } }
|
||||
);
|
||||
|
||||
const snippets = (result as any)?.snippets;
|
||||
if (!snippets || snippets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return snippets;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch snippets:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user