feat: better
This commit is contained in:
parent
19afe08bba
commit
38a7e09b68
6 changed files with 1225 additions and 89 deletions
|
@ -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",
|
||||||
|
|
|
@ -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
1141
public/projects.json
Normal file
File diff suppressed because it is too large
Load diff
103
src/app/page.tsx
103
src/app/page.tsx
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue