mirror of
https://github.com/privacy-scaling-explorations/pse.dev.git
synced 2026-04-23 03:01:03 -04:00
349 lines
8.8 KiB
TypeScript
349 lines
8.8 KiB
TypeScript
import { QueryClient, usePrefetchQuery, useQuery } from "@tanstack/react-query"
|
|
import { ProjectInterface, ProjectCategory } from "@/lib/types"
|
|
|
|
export interface Article {
|
|
id: string
|
|
title: string
|
|
image: string
|
|
tldr?: string
|
|
content: string
|
|
date: string
|
|
authors: string[]
|
|
canonical?: string
|
|
tags?: string[]
|
|
projects?: string[]
|
|
[key: string]: any
|
|
}
|
|
|
|
export interface MarkdownContent {
|
|
id: string
|
|
title: string
|
|
image?: string
|
|
tldr?: string
|
|
content: string
|
|
date: string
|
|
authors?: string[]
|
|
signature?: string
|
|
category?: string
|
|
projectStatus?: string
|
|
publicKey?: string
|
|
hash?: string
|
|
canonical?: string
|
|
tags?: string[]
|
|
projects?: string[]
|
|
[key: string]: any // Allow for additional properties
|
|
}
|
|
|
|
export type ProjectSortBy = "random" | "asc" | "desc" | "relevance"
|
|
export type ProjectFilter =
|
|
| "keywords"
|
|
| "builtWith"
|
|
| "themes"
|
|
| "fundingSource"
|
|
export type FiltersProps = Record<ProjectFilter, string[]>
|
|
|
|
interface UseGetBlogArticlesParams {
|
|
tag?: string
|
|
limit?: number
|
|
project?: string
|
|
revalidate?: number
|
|
}
|
|
|
|
interface UseGetProjectsParams {
|
|
tag?: string
|
|
limit?: number
|
|
project?: string
|
|
revalidate?: number
|
|
searchPattern?: string
|
|
sortBy?: ProjectSortBy
|
|
category?: ProjectCategory
|
|
findAnyMatch?: boolean
|
|
themes?: string[]
|
|
keywords?: string[]
|
|
builtWith?: string[]
|
|
fundingSource?: string[]
|
|
activeFilters?: Partial<FiltersProps>
|
|
ids?: string[] | null | undefined
|
|
}
|
|
|
|
// Client-side hook for blog articles using server-side rendering approach
|
|
export const useGetBlogArticles = ({
|
|
tag = "",
|
|
limit = 1000,
|
|
project = "",
|
|
revalidate = 3600,
|
|
}: UseGetBlogArticlesParams = {}) => {
|
|
return useQuery({
|
|
queryKey: [
|
|
"articles",
|
|
{
|
|
tag,
|
|
limit,
|
|
project,
|
|
},
|
|
],
|
|
queryFn: async (): Promise<Article[]> => {
|
|
// This hook should only be used with prefetched data
|
|
// Return empty array as fallback for client-side usage
|
|
console.warn(
|
|
"useGetBlogArticles: No prefetched data available, returning empty array"
|
|
)
|
|
return []
|
|
},
|
|
staleTime: (revalidate || 3600) * 1000,
|
|
retry: 1, // Reduce retries since this is expected to fail on client-side
|
|
retryDelay: 1000,
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
})
|
|
}
|
|
|
|
// Hook specifically for project-related articles (replacing useGetProjectRelatedArticles)
|
|
export const useGetProjectRelatedArticles = ({
|
|
projectId,
|
|
}: {
|
|
projectId: string
|
|
}) => {
|
|
const {
|
|
data: articles = [],
|
|
isLoading: loading,
|
|
error,
|
|
} = useGetBlogArticles({
|
|
project: projectId,
|
|
limit: undefined,
|
|
})
|
|
|
|
return {
|
|
articles,
|
|
loading,
|
|
error,
|
|
}
|
|
}
|
|
|
|
// Client-side hook for projects with enhanced filtering
|
|
export const useGetProjects = ({
|
|
tag,
|
|
limit,
|
|
project,
|
|
revalidate = 3600,
|
|
searchPattern,
|
|
sortBy,
|
|
category,
|
|
findAnyMatch,
|
|
themes,
|
|
keywords,
|
|
builtWith,
|
|
fundingSource,
|
|
activeFilters,
|
|
ids,
|
|
}: UseGetProjectsParams = {}) => {
|
|
return useQuery({
|
|
queryKey: [
|
|
"projects",
|
|
{
|
|
tag,
|
|
limit,
|
|
project,
|
|
searchPattern,
|
|
sortBy,
|
|
category,
|
|
findAnyMatch,
|
|
themes,
|
|
keywords,
|
|
builtWith,
|
|
fundingSource,
|
|
activeFilters,
|
|
ids,
|
|
},
|
|
],
|
|
queryFn: async (): Promise<ProjectInterface[]> => {
|
|
try {
|
|
const params = new URLSearchParams()
|
|
|
|
// Original parameters
|
|
if (tag) params.append("tag", tag)
|
|
if (limit) params.append("limit", limit.toString())
|
|
if (project) params.append("project", project)
|
|
|
|
// New filtering parameters
|
|
if (searchPattern) params.append("searchPattern", searchPattern)
|
|
if (sortBy) params.append("sortBy", sortBy)
|
|
if (category) params.append("category", category)
|
|
if (findAnyMatch) params.append("findAnyMatch", "true")
|
|
|
|
// Filter arrays
|
|
if (themes?.length) params.append("themes", themes.join(","))
|
|
if (keywords?.length) params.append("keywords", keywords.join(","))
|
|
if (builtWith?.length) params.append("builtWith", builtWith.join(","))
|
|
if (fundingSource?.length)
|
|
params.append("fundingSource", fundingSource.join(","))
|
|
|
|
// Handle specific IDs - IMPORTANT: Add parameter even if array is empty
|
|
if (ids !== undefined && ids !== null) {
|
|
params.append("ids", ids.join(",")) // This will be empty string for empty array
|
|
}
|
|
|
|
// Handle activeFilters object
|
|
if (activeFilters) {
|
|
Object.entries(activeFilters).forEach(([key, values]) => {
|
|
if (values && values.length > 0) {
|
|
params.append(key, values.join(","))
|
|
}
|
|
})
|
|
}
|
|
|
|
const response = await fetch(`/api/projects?${params.toString()}`, {
|
|
next: { revalidate },
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch projects: ${response.status}`)
|
|
}
|
|
|
|
const data = await response.json()
|
|
return data.projects || []
|
|
} catch (error) {
|
|
console.error("Error fetching projects:", error)
|
|
throw error
|
|
}
|
|
},
|
|
staleTime: (revalidate || 3600) * 1000,
|
|
retry: 3,
|
|
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: true,
|
|
})
|
|
}
|
|
|
|
// Client-side hook for single project
|
|
export const useGetProject = (id: string) => {
|
|
return useQuery({
|
|
queryKey: [
|
|
"project",
|
|
{
|
|
id: id?.toLowerCase(),
|
|
},
|
|
],
|
|
queryFn: async (): Promise<ProjectInterface | null> => {
|
|
try {
|
|
const response = await fetch(`/api/projects/${id?.toLowerCase()}`)
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) return null
|
|
throw new Error(`Failed to fetch project: ${response.status}`)
|
|
}
|
|
|
|
const data = await response.json()
|
|
return data.project || null
|
|
} catch (error) {
|
|
console.error("Error fetching project:", error)
|
|
throw error
|
|
}
|
|
},
|
|
staleTime: 3600 * 1000, // 1 hour
|
|
})
|
|
}
|
|
|
|
export const useGetProjectsFilters = () => {
|
|
return useQuery({
|
|
queryKey: ["getProjectsFilters"],
|
|
queryFn: async () => {
|
|
const response = await fetch("/api/projects?filters=true")
|
|
const data = await response.json()
|
|
return data.filters || []
|
|
},
|
|
})
|
|
}
|
|
|
|
// Server-side prefetch function for projects with enhanced filtering
|
|
export const prefetchProjects = async (
|
|
queryClient: any,
|
|
{
|
|
tag,
|
|
limit,
|
|
project,
|
|
revalidate = 3600,
|
|
searchPattern,
|
|
sortBy,
|
|
category,
|
|
findAnyMatch,
|
|
themes,
|
|
keywords,
|
|
builtWith,
|
|
fundingSource,
|
|
activeFilters,
|
|
ids,
|
|
}: UseGetProjectsParams = {}
|
|
) => {
|
|
return await queryClient.prefetchQuery({
|
|
queryKey: [
|
|
"projects",
|
|
tag,
|
|
limit,
|
|
project,
|
|
searchPattern,
|
|
sortBy,
|
|
category,
|
|
findAnyMatch,
|
|
themes,
|
|
keywords,
|
|
builtWith,
|
|
fundingSource,
|
|
activeFilters,
|
|
ids,
|
|
],
|
|
queryFn: async (): Promise<ProjectInterface[]> => {
|
|
try {
|
|
const params = new URLSearchParams()
|
|
|
|
// Original parameters
|
|
if (tag) params.append("tag", tag)
|
|
if (limit) params.append("limit", limit.toString())
|
|
if (project) params.append("project", project)
|
|
|
|
// New filtering parameters
|
|
if (searchPattern) params.append("searchPattern", searchPattern)
|
|
if (sortBy) params.append("sortBy", sortBy)
|
|
if (category) params.append("category", category)
|
|
if (findAnyMatch) params.append("findAnyMatch", "true")
|
|
|
|
// Filter arrays
|
|
if (themes?.length) params.append("themes", themes.join(","))
|
|
if (keywords?.length) params.append("keywords", keywords.join(","))
|
|
if (builtWith?.length) params.append("builtWith", builtWith.join(","))
|
|
if (fundingSource?.length)
|
|
params.append("fundingSource", fundingSource.join(","))
|
|
|
|
// Handle specific IDs - IMPORTANT: Add parameter even if array is empty
|
|
if (ids !== undefined && ids !== null) {
|
|
params.append("ids", ids.join(",")) // This will be empty string for empty array
|
|
}
|
|
|
|
// Handle activeFilters object
|
|
if (activeFilters) {
|
|
Object.entries(activeFilters).forEach(([key, values]) => {
|
|
if (values && values.length > 0) {
|
|
params.append(key, values.join(","))
|
|
}
|
|
})
|
|
}
|
|
|
|
const response = await fetch(`/api/projects?${params.toString()}`, {
|
|
next: { revalidate },
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch projects: ${response.status}`)
|
|
}
|
|
|
|
const data = await response.json()
|
|
return data.projects || []
|
|
} catch (error) {
|
|
console.error("Error fetching projects:", error)
|
|
throw error
|
|
}
|
|
},
|
|
staleTime: (revalidate || 3600) * 1000,
|
|
})
|
|
}
|