Initial commit

This commit is contained in:
2026-02-06 18:04:41 +02:00
commit 946563c321
650 changed files with 76186 additions and 0 deletions

39
src/hooks/useBlogPosts.ts Normal file
View File

@@ -0,0 +1,39 @@
"use client";
import { useEffect, useState } from "react";
import { BlogPost, defaultPosts, fetchBlogPosts } from "@/lib/api/blog";
export function useBlogPosts() {
const [posts, setPosts] = useState<BlogPost[]>(defaultPosts);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let isMounted = true;
async function loadPosts() {
try {
const data = await fetchBlogPosts();
if (isMounted) {
setPosts(data);
}
} catch (err) {
if (isMounted) {
setError(err instanceof Error ? err : new Error("Failed to fetch posts"));
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
}
loadPosts();
return () => {
isMounted = false;
};
}, []);
return { posts, isLoading, error };
}

81
src/hooks/useCheckout.ts Normal file
View File

@@ -0,0 +1,81 @@
"use client";
import { useState } from "react";
export type CheckoutItem = {
productId: string;
quantity: number;
};
export type CheckoutResult = {
success: boolean;
url?: string;
error?: string;
};
export function useCheckout() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const checkout = async (items: CheckoutItem[]): Promise<CheckoutResult> => {
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
if (!apiUrl || !projectId) {
const errorMsg = "NEXT_PUBLIC_API_URL or NEXT_PUBLIC_PROJECT_ID not configured";
setError(errorMsg);
return { success: false, error: errorMsg };
}
setIsLoading(true);
setError(null);
try {
const response = await fetch(`${apiUrl}/stripe/project/checkout-session`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
projectId,
items,
successUrl: window.location.href,
cancelUrl: window.location.href,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMsg = errorData.message || `Request failed with status ${response.status}`;
setError(errorMsg);
return { success: false, error: errorMsg };
}
const data = await response.json();
if (data.data.url) {
window.location.href = data.data.url;
}
return { success: true, url: data.data.url };
} catch (err) {
const errorMsg = err instanceof Error ? err.message : "Failed to create checkout session";
setError(errorMsg);
return { success: false, error: errorMsg };
} finally {
setIsLoading(false);
}
};
const buyNow = async (productId: string, quantity: number = 1): Promise<CheckoutResult> => {
return checkout([{ productId, quantity }]);
};
return {
checkout,
buyNow,
isLoading,
error,
clearError: () => setError(null),
};
}

View File

@@ -0,0 +1,38 @@
import { useEffect, RefObject } from "react";
export const useClickOutside = (
ref: RefObject<HTMLElement | null>,
handler: () => void,
isActive: boolean = true,
excludeRefs?: RefObject<HTMLElement | null>[]
) => {
useEffect(() => {
if (!isActive) return;
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
const isExcluded = excludeRefs?.some((excludeRef) =>
excludeRef.current?.contains(event.target as Node)
);
if (!isExcluded) {
handler();
}
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
handler();
}
};
document.addEventListener("click", handleClickOutside);
document.addEventListener("keydown", handleEscape);
return () => {
document.removeEventListener("click", handleClickOutside);
document.removeEventListener("keydown", handleEscape);
};
}, [ref, handler, isActive, excludeRefs]);
};

39
src/hooks/useProducts.ts Normal file
View File

@@ -0,0 +1,39 @@
"use client";
import { useEffect, useState } from "react";
import { Product, fetchProducts } from "@/lib/api/product";
export function useProducts() {
const [products, setProducts] = useState<Product[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let isMounted = true;
async function loadProducts() {
try {
const data = await fetchProducts();
if (isMounted) {
setProducts(data);
}
} catch (err) {
if (isMounted) {
setError(err instanceof Error ? err : new Error("Failed to fetch products"));
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
}
loadProducts();
return () => {
isMounted = false;
};
}, []);
return { products, isLoading, error };
}