feat: better

This commit is contained in:
Matz Hilven 2024-05-30 08:51:53 +02:00
parent 19afe08bba
commit 38a7e09b68
Signed by: MatzHilven
GPG key ID: ACEB669C2CB79EB7
6 changed files with 1225 additions and 89 deletions

View file

@ -21,7 +21,8 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"sharp": "^0.33.4", "sharp": "^0.33.4",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.12.12", "@types/node": "^20.12.12",

View file

@ -44,6 +44,9 @@ dependencies:
tailwindcss-animate: tailwindcss-animate:
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.3) version: 1.0.7(tailwindcss@3.4.3)
zod:
specifier: ^3.23.8
version: 3.23.8
devDependencies: devDependencies:
'@types/node': '@types/node':
@ -3639,3 +3642,7 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
dev: false

1141
public/projects.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,85 +1,29 @@
'use client' 'use client'
import React, { useState } from 'react' import React, { useState, useEffect } from 'react'
import { Project } from '@/typings/project' import { Project, Tag, projectsSchema } from '@/typings/project'
import { ProjectCard } from '@/components/ProjectCard' import { ProjectCard } from '@/components/ProjectCard'
import projectsData from '../../public/projects.json'
const Page = () => { const Page = () => {
const projects: Project[] = [ const parsedProjects = projectsSchema.safeParse(projectsData)
{
name: 'Project Alpha', if (!parsedProjects.success) {
description: 'Description for Project Alpha', console.error('Invalid project data', parsedProjects.error)
category: 'Web', return <div>Error loading projects</div>
imageUrl: 'https://via.placeholder.com/150', }
socials: { Github: 'https://github.com', YouTube: 'https://youtube.com' },
languages: ['Java', 'TypeScript'], const projects: Project[] = parsedProjects.data
tags: ['Frontend', 'Backend'],
},
{
name: 'Project Beta',
description: 'Description for Project Beta',
category: 'Minecraft',
imageUrl: 'https://via.placeholder.com/150',
socials: { Github: 'https://github.com' },
languages: ['Rust', 'Go'],
tags: ['1.8', 'Fullstack'],
},
{
name: 'Project Gamma',
description: 'Description for Project Gamma',
category: 'Discord',
imageUrl: 'https://via.placeholder.com/150',
socials: { YouTube: 'https://youtube.com' },
languages: ['TypeScript'],
tags: ['Backend', 'Archived'],
},
{
name: 'Project Delta',
description: 'Description for Project Delta',
category: 'Unity',
imageUrl: 'https://via.placeholder.com/150',
socials: {},
languages: ['Java'],
tags: ['1.17', 'Frontend'],
},
{
name: 'Project Epsilon',
description: 'Description for Project Epsilon',
category: 'Misc',
imageUrl: 'https://via.placeholder.com/150',
socials: { Github: 'https://github.com', YouTube: 'https://youtube.com' },
languages: ['Go', 'Rust'],
tags: ['Fullstack', 'Paper'],
},
// Add more projects to test pagination
{
name: 'Project Zeta',
description: 'Description for Project Zeta',
category: 'Misc',
imageUrl: 'https://via.placeholder.com/150',
socials: { Github: 'https://github.com', YouTube: 'https://youtube.com' },
languages: ['Go', 'Rust'],
tags: ['Fullstack', 'Paper'],
},
{
name: 'Project Eta',
description: 'Description for Project Eta',
category: 'Misc',
imageUrl: 'https://via.placeholder.com/150',
socials: { Github: 'https://github.com', YouTube: 'https://youtube.com' },
languages: ['Go', 'Rust'],
tags: ['Fullstack', 'Paper'],
},
// Add as many projects as needed to test
]
const [currentPage, setCurrentPage] = useState(1) const [currentPage, setCurrentPage] = useState(1)
const [itemsPerPage] = useState(4) const [itemsPerPage, setItemsPerPage] = useState<number>(4)
const [filterTag, setFilterTag] = useState<string | null>(null) const [filterTag, setFilterTag] = useState<Tag | null>(null)
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc') const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
const handleFilterChange = (event: React.ChangeEvent<HTMLSelectElement>) => { const handleFilterChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setFilterTag(event.target.value === 'All' ? null : event.target.value) setFilterTag(
event.target.value === 'All' ? null : (event.target.value as Tag)
)
setCurrentPage(1) // Reset to the first page when filter changes setCurrentPage(1) // Reset to the first page when filter changes
} }
@ -88,6 +32,21 @@ const Page = () => {
setCurrentPage(1) // Reset to the first page when sort order changes setCurrentPage(1) // Reset to the first page when sort order changes
} }
useEffect(() => {
const handleResize = () => {
const cardWidth = 240 // Width of a single card (adjust as needed)
const windowWidth = window.innerWidth
const newItemsPerPage = Math.floor(windowWidth / cardWidth)
setItemsPerPage(newItemsPerPage > 0 ? newItemsPerPage : 1)
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const filteredProjects = filterTag const filteredProjects = filterTag
? projects.filter(project => project.tags.includes(filterTag)) ? projects.filter(project => project.tags.includes(filterTag))
: projects : projects

View file

@ -37,7 +37,7 @@ const tagStyles: Record<Tag, string> = {
export const ProjectCard = ({ project }: Props) => { export const ProjectCard = ({ project }: Props) => {
return ( return (
<div className="h-80 w-80 transform overflow-hidden rounded-lg bg-white shadow-md transition-transform hover:-translate-y-2"> <div className="h-80 w-80 transform overflow-hidden rounded-lg bg-white shadow-md transition-transform hover:scale-105">
<img <img
src={project.imageUrl} src={project.imageUrl}
alt={project.name} alt={project.name}

View file

@ -1,18 +1,46 @@
export type Social = 'Github' | 'YouTube' import { z } from 'zod'
export type Language = 'Java' | 'Rust' | 'Go' | 'TypeScript'
type MinecraftTags = '1.8' | '1.17' | 'Paper' | 'Bungee' export const SocialSchema = z.union([z.literal('Github'), z.literal('YouTube')])
type WebTags = 'Frontend' | 'Backend' | 'Fullstack' export type Social = z.infer<typeof SocialSchema>
export type Tag = MinecraftTags | WebTags | 'Archived'
export type Category = 'Minecraft' | 'Web' | 'Discord' | 'Misc' | 'Unity' export const LanguageSchema = z.union([
z.literal('Java'),
z.literal('Rust'),
z.literal('Go'),
z.literal('TypeScript'),
])
export type Language = z.infer<typeof LanguageSchema>
export type Project = { export const TagSchema = z.union([
name: string z.literal('1.8'),
description: string z.literal('1.17'),
category: Category z.literal('Paper'),
imageUrl: string z.literal('Bungee'),
socials: Partial<Record<Social, string>> z.literal('Frontend'),
languages: Language[] z.literal('Backend'),
tags: Tag[] z.literal('Fullstack'),
} z.literal('Archived'),
])
export type Tag = z.infer<typeof TagSchema>
export const CategorySchema = z.union([
z.literal('Minecraft'),
z.literal('Web'),
z.literal('Discord'),
z.literal('Misc'),
z.literal('Unity'),
])
export type Category = z.infer<typeof CategorySchema>
export const ProjectSchema = z.object({
name: z.string(),
description: z.string(),
category: CategorySchema,
imageUrl: z.string().url(),
socials: z.record(SocialSchema, z.string().url()).optional(),
languages: z.array(LanguageSchema),
tags: z.array(TagSchema),
})
export type Project = z.infer<typeof ProjectSchema>
export const projectsSchema = z.array(ProjectSchema)