Skip to content
Snippets Groups Projects
Commit db42cf5c authored by Linus Schmueser's avatar Linus Schmueser
Browse files

First version of Detector Pages and detector Page

parent ee7b12fe
No related branches found
No related tags found
1 merge request!22detectorpages are added
'use client'
import { Button } from '@/components/ui/button'
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error(error)
}, [error])
return (
<div className="container max-w-4xl mx-auto py-16 px-4 flex flex-col items-center justify-center">
<div className="bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-300 p-6 rounded-lg text-center max-w-md">
<h2 className="text-xl font-bold mb-4">Something went wrong!</h2>
<p className="mb-6">We couldn't load the detector page you requested. Please try again.</p>
<Button onClick={reset} variant="outline">
Try again
</Button>
</div>
</div>
)
}
import { Skeleton } from '@/components/ui/skeleton'
export default function Loading() {
return (
<div className="container max-w-4xl mx-auto py-8 px-4 space-y-6">
<Skeleton className="h-10 w-32" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-8 w-3/4" />
<div className="flex items-center gap-4">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-24" />
</div>
</div>
<Skeleton className="h-64 w-full rounded-lg" />
<div className="space-y-4">
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-5/6" />
<Skeleton className="h-6 w-3/4" />
</div>
</div>
)
}
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import config from '@/payload.config'
import { format } from 'date-fns'
import Image from 'next/image'
import { notFound } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import { RichTextRenderer } from '../../components/RichTextRenderer'
interface Detector {
id: string
detectorName: string
description: string
slug: string
content: SerializedEditorState
featuredImage?: {
url: string
width: number
height: number
alt: string
}
}
export default async function DetectorPage({
params,
}: {
params: { slug: string }
}) {
// await prevents warning in browser console
const { slug } = await params
const payload = await getPayload({ config })
const result = await payload.find({
collection: 'detectors',
where: {
slug: {
equals: slug,
},
},
depth: 5,
})
if (!result.docs || result.docs.length === 0) {
notFound()
}
const article = result.docs[0] as unknown as Detector
return (
<main className="container mx-auto py-8 px-4">
<article className="max-w-4xl mx-auto">
<div className="mb-8 mt-8 md:mt-0">
<h1 className="text-4xl font-bold mb-2">{article.detectorName}</h1>
{article.description && (
<h2 className="text-xl text-gray-600 mb-4">{article.description}</h2>
)}
</div>
<div className="prose prose-lg max-w-none">
<RichTextRenderer content={article.content} />
</div>
</article>
</main>
)
}
import { Card, CardContent } from '@/components/ui/card'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Input } from '@/components/ui/input'
import config from '@/payload.config'
import { format } from 'date-fns'
import { BookOpen, Calendar, ChevronDown, ChevronRight, Filter, Newspaper, Search, User } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { getPayload } from 'payload'
import Tag from '../components/Tag'
interface Detector {
id: string
detectorName: string
description: string
slug: string
featuredImage?: {
url: string
width: number
height: number
alt: string
}
}
export default async function DetectorPage() {
const payload = await getPayload({ config })
const result = await payload.find({
collection: 'detectors',
})
const articles = result.docs as unknown as Detector[]
const remainingArticles = articles.length > 0 ? articles : []
return (
<main className="bg-gray-50 dark:bg-neutral-950 min-h-screen pb-16 md:pt-5">
<div className="container mx-auto px-4">
{remainingArticles.length > 0
? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{remainingArticles.map(article => (
<Link
key={article.id}
href={`/detectors/${article.slug}`}
className="group"
>
<Card className="overflow-hidden h-full flex flex-col bg-white dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 group-hover:border-red-300 dark:group-hover:border-red-800 transition-colors shadow-sm group-hover:shadow-md">
<div className="relative w-full h-48 overflow-hidden bg-gray-200 dark:bg-neutral-700">
{article.featuredImage && article.featuredImage.url
? (
<Image
src={article.featuredImage.url}
alt={article.featuredImage.alt || article.detectorName}
fill
className="object-cover transition-transform group-hover:scale-105"
/>
)
: (
<div className="w-full h-full flex items-center justify-center">
<Newspaper className="w-12 h-12 text-gray-400 dark:text-neutral-500" />
</div>
)}
<div className="absolute top-3 left-3">
{// <Tag
// name={article.tag.name}
// color={article.tag.color}
/// >
}
</div>
</div>
<CardContent className="flex-grow flex flex-col p-5">
<div className="flex items-center text-xs text-gray-500 dark:text-gray-400 mb-2">
{// <Calendar className="w-3 h-3 mr-1" />
// <time dateTime={article.publishedDate}>
// {format(new Date(article.publishedDate), 'MMM d, yyyy')}
// </time>
// <span className="mx-2">•</span>
// <User className="w-3 h-3 mr-1" />
// <span>{article.author.name}</span>
}
</div>
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3 line-clamp-2 group-hover:text-red-600 dark:group-hover:text-red-400 transition-colors">
{article.detectorName}
</h3>
{article.description && (
<p className="text-gray-600 dark:text-gray-300 mb-4 line-clamp-3 flex-grow">
{article.description}
</p>
)}
<div className="flex items-center text-red-600 dark:text-red-400 mt-auto pt-2 text-sm font-medium">
<BookOpen className="w-4 h-4 mr-1" />
<span>Read more</span>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
)
: (
<Card className="p-12 text-center bg-white dark:bg-neutral-800">
<p className="text-gray-500 dark:text-gray-400">No news articles found</p>
</Card>
)}
</div>
</main>
)
}
import type { CollectionConfig } from 'payload'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
export const Detectors: CollectionConfig = {
slug: 'detectors',
labels: {
singular: 'Detector',
plural: 'Detectors',
},
access: {
read: () => true,
},
fields: [
{
name: 'detectorName',
label: 'detector name',
type: 'text',
required: true,
},
{
name: 'description',
label: 'description',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
admin: {
description: 'URL-friendly version of the detector name',
position: 'sidebar',
},
hooks: {
beforeValidate: [
({ value, siblingData }) => {
if (!value && siblingData?.headline) {
return siblingData.headline
.toLowerCase()
.trim()
.replace(/[^\w\s]/g, '')
.replace(/\s+/g, '-')
}
return value
},
],
},
},
{
name: 'content',
type: 'richText',
label: 'Article Content',
required: true,
editor: lexicalEditor({
features: ({ defaultFeatures, rootFeatures }) => [
...defaultFeatures,
...rootFeatures,
],
}),
},
{
name: 'featuredImage',
type: 'upload',
relationTo: 'news-article-images',
label: 'Featured Image',
admin: {
description: 'The main image for the article',
},
},
],
}
......@@ -71,6 +71,7 @@ export interface Config {
'slideshow-images': SlideshowImage;
publications: Publication;
'publication-documents': PublicationDocument;
detectors: Detector;
'news-articles': NewsArticle;
'news-article-images': NewsArticleImage;
tags: Tag;
......@@ -86,6 +87,7 @@ export interface Config {
'slideshow-images': SlideshowImagesSelect<false> | SlideshowImagesSelect<true>;
publications: PublicationsSelect<false> | PublicationsSelect<true>;
'publication-documents': PublicationDocumentsSelect<false> | PublicationDocumentsSelect<true>;
detectors: DetectorsSelect<false> | DetectorsSelect<true>;
'news-articles': NewsArticlesSelect<false> | NewsArticlesSelect<true>;
'news-article-images': NewsArticleImagesSelect<false> | NewsArticleImagesSelect<true>;
tags: TagsSelect<false> | TagsSelect<true>;
......@@ -228,6 +230,59 @@ export interface PublicationDocument {
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "detectors".
*/
export interface Detector {
id: string;
detectorName: string;
description: string;
/**
* URL-friendly version of the detector name
*/
slug: string;
content: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
};
/**
* The main image for the article
*/
featuredImage?: (string | null) | NewsArticleImage;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "news-article-images".
*/
export interface NewsArticleImage {
id: string;
alt: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "news-articles".
......@@ -282,25 +337,6 @@ export interface NewsArticle {
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "news-article-images".
*/
export interface NewsArticleImage {
id: string;
alt: string;
updatedAt: string;
createdAt: string;
url?: string | null;
thumbnailURL?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* Create and manage tags for categorizing news articles
*
......@@ -373,6 +409,10 @@ export interface PayloadLockedDocument {
relationTo: 'publication-documents';
value: string | PublicationDocument;
} | null)
| ({
relationTo: 'detectors';
value: string | Detector;
} | null)
| ({
relationTo: 'news-articles';
value: string | NewsArticle;
......@@ -527,6 +567,19 @@ export interface PublicationDocumentsSelect<T extends boolean = true> {
focalX?: T;
focalY?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "detectors_select".
*/
export interface DetectorsSelect<T extends boolean = true> {
detectorName?: T;
description?: T;
slug?: T;
content?: T;
featuredImage?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "news-articles_select".
......
......@@ -7,6 +7,7 @@ import { EXPERIMENTAL_TableFeature, FixedToolbarFeature, lexicalEditor } from '@
import { buildConfig } from 'payload'
import sharp from 'sharp'
import { Detectors } from './collections/Detectors'
import { Event } from './collections/Event'
import { Media } from './collections/Media'
import { NewsArticleImages } from './collections/NewsArticleImages'
......@@ -28,7 +29,7 @@ export default buildConfig({
},
},
collections: [Users, Media, SlideshowImages, Publications, PublicationDocuments, NewsArticles, NewsArticleImages, Tags, Event],
collections: [Users, Media, SlideshowImages, Publications, PublicationDocuments, Detectors, NewsArticles, NewsArticleImages, Tags, Event],
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment