diff --git a/app/projects/page.tsx b/app/projects/page.tsx new file mode 100644 index 0000000..7dc8864 --- /dev/null +++ b/app/projects/page.tsx @@ -0,0 +1,5 @@ +import { ProjectsContent } from "@/components/projects-content"; + +export default function ProjectsPage() { + return ; +} diff --git a/components/projects-content.tsx b/components/projects-content.tsx new file mode 100644 index 0000000..de1c1cc --- /dev/null +++ b/components/projects-content.tsx @@ -0,0 +1,180 @@ +import Image from "next/image"; +import Link from "next/link"; +import { Icon } from "@iconify/react"; +import { Badge, Card, Typography } from "poyraz-ui/atoms"; +import { ImageCard } from "poyraz-ui/molecules"; +import { FIGMA_TEMPLATES, MOBILE_APPS, WEB_APPS, type ProjectItem } from "@/data/projects"; +import { getGithubRepos, getNpmPackages } from "@/lib/project-feeds"; + +function getLanguageMeta(language: string) { + const key = language.toLowerCase(); + + if (key.includes("typescript")) { + return { icon: "vscode-icons:file-type-typescript-official", color: "text-blue-600" }; + } + if (key.includes("javascript")) { + return { icon: "vscode-icons:file-type-js-official", color: "text-amber-500" }; + } + if (key.includes("python")) { + return { icon: "vscode-icons:file-type-python", color: "text-yellow-600" }; + } + if (key.includes("go")) { + return { icon: "vscode-icons:file-type-go", color: "text-cyan-600" }; + } + if (key.includes("rust")) { + return { icon: "vscode-icons:file-type-rust", color: "text-orange-600" }; + } + if (key.includes("java")) { + return { icon: "vscode-icons:file-type-java", color: "text-red-600" }; + } + if (key.includes("html")) { + return { icon: "vscode-icons:file-type-html", color: "text-orange-500" }; + } + if (key.includes("css")) { + return { icon: "vscode-icons:file-type-css", color: "text-blue-500" }; + } + + return { icon: "mdi:code-tags", color: "text-zinc-500" }; +} + +function ProjectSection({ title, items }: { title: string; items: ProjectItem[] }) { + return ( +
+ + {title} + +
+ {items.map((item) => ( + + ))} +
+
+ ); +} + +export async function ProjectsContent() { + const [repos, npmPackages] = await Promise.all([getGithubRepos(), getNpmPackages()]); + + return ( +
+ +
+ GitHub contribution activity for poyrazavsever +
+
+ + + + + +
+ + npm paketleri (@poyrazavsever) + +
+ {npmPackages.length === 0 ? ( + + + npm API response is empty right now. + + + ) : ( + npmPackages.map((pkg) => ( + + + + {pkg.name} + + + {pkg.description} + +
+ v{pkg.version} +
+
+ + )) + )} +
+
+ +
+ + GitHub Repos (@poyrazavsever) + +
+ {repos.length === 0 ? ( + + + GitHub API response is empty right now. + + + ) : ( + repos.map((repo) => { + const languageMeta = getLanguageMeta(repo.language); + + return ( + + + + {repo.name} + + + {repo.description} + +
+ + + + {repo.language} + + + + + + {repo.stars} + + +
+
+ + ); + }) + )} +
+
+
+ ); +} diff --git a/data/projects.ts b/data/projects.ts new file mode 100644 index 0000000..a729c56 --- /dev/null +++ b/data/projects.ts @@ -0,0 +1,69 @@ +export type ProjectItem = { + id: string; + title: string; + description: string; + image: string; + badge?: string; + href?: string; +}; + +export const MOBILE_APPS: ProjectItem[] = [ + { + id: "mobile-habit-flow", + title: "Habit Flow", + description: + "A mobile-first productivity app focused on clean habit tracking, reminders, and weekly review loops.", + image: "/images/hero1.png", + badge: "React Native", + }, + { + id: "mobile-campus-connect", + title: "Campus Connect", + description: + "Event and club discovery app prototype for university communities with role-based content publishing.", + image: "/images/hero2.png", + badge: "Expo", + }, +]; + +export const WEB_APPS: ProjectItem[] = [ + { + id: "web-portfolio", + title: "Personal Portfolio", + description: + "A minimal portfolio system with blog details, command palette navigation, and reusable design system primitives.", + image: "/news/design.svg", + badge: "Next.js", + href: "https://poyrazavsever.com", + }, + { + id: "web-poyraz-ui-docs", + title: "Poyraz UI Docs", + description: + "Documentation and showcase site for the UI kit, including component examples and implementation patterns.", + image: "/news/performance.svg", + badge: "poyraz-ui", + href: "https://ui.poyrazavsever.com", + }, +]; + +export const FIGMA_TEMPLATES: ProjectItem[] = [ + { + id: "figma-minimal-saas", + title: "Minimal SaaS Landing Kit", + description: + "Landing page template set with token-based spacing and typography scales for fast startup launches.", + image: "/news/design.svg", + badge: "Figma", + href: "https://www.behance.net/poyrazavsever", + }, + { + id: "figma-dashboard-clean", + title: "Clean Dashboard Blocks", + description: + "Reusable dashboard cards and layout blocks designed for rapid MVP iteration and design consistency.", + image: "/news/performance.svg", + badge: "Design System", + href: "https://www.behance.net/poyrazavsever", + }, +]; diff --git a/lib/project-feeds.ts b/lib/project-feeds.ts new file mode 100644 index 0000000..693bbee --- /dev/null +++ b/lib/project-feeds.ts @@ -0,0 +1,138 @@ +const USERNAME = "poyrazavsever"; + +export type GithubRepo = { + id: number; + name: string; + htmlUrl: string; + description: string; + language: string; + stars: number; + updatedAt: string; +}; + +export type NpmPackage = { + name: string; + version: string; + description: string; + npmUrl: string; +}; + +export type GithubActivity = { + id: string; + type: string; + repo: string; + createdAt: string; +}; + +type GithubRepoApi = { + id: number; + name: string; + html_url: string; + description: string | null; + language: string | null; + stargazers_count: number; + updated_at: string; +}; + +type NpmSearchResponse = { + objects?: Array<{ + package?: { + name?: string; + version?: string; + description?: string; + links?: { + npm?: string; + }; + }; + }>; +}; + +type GithubEventApi = { + id: string; + type: string; + repo?: { + name?: string; + }; + created_at: string; +}; + +export async function getGithubRepos(): Promise { + try { + const response = await fetch( + `https://api.github.com/users/${USERNAME}/repos?sort=updated&per_page=12&type=owner`, + { + next: { revalidate: 1800 }, + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "portfolio-new", + }, + }, + ); + + if (!response.ok) return []; + const data = (await response.json()) as GithubRepoApi[]; + + return data.map((repo) => ({ + id: repo.id, + name: repo.name, + htmlUrl: repo.html_url, + description: repo.description ?? "No description", + language: repo.language ?? "Unknown", + stars: repo.stargazers_count, + updatedAt: repo.updated_at, + })); + } catch { + return []; + } +} + +export async function getNpmPackages(): Promise { + try { + const response = await fetch( + `https://registry.npmjs.org/-/v1/search?text=maintainer:${USERNAME}&size=12`, + { + next: { revalidate: 1800 }, + }, + ); + + if (!response.ok) return []; + const data = (await response.json()) as NpmSearchResponse; + const items = data.objects ?? []; + + return items + .map((item) => item.package) + .filter((pkg): pkg is NonNullable => Boolean(pkg?.name)) + .map((pkg) => ({ + name: pkg.name ?? "unknown-package", + version: pkg.version ?? "-", + description: pkg.description ?? "No description", + npmUrl: pkg.links?.npm ?? `https://www.npmjs.com/package/${pkg.name}`, + })); + } catch { + return []; + } +} + +export async function getGithubActivity(): Promise { + try { + const response = await fetch(`https://api.github.com/users/${USERNAME}/events/public?per_page=6`, { + next: { revalidate: 600 }, + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "portfolio-new", + }, + }); + + if (!response.ok) return []; + const data = (await response.json()) as GithubEventApi[]; + + return data.map((event) => ({ + id: event.id, + type: event.type, + repo: event.repo?.name ?? "unknown-repo", + createdAt: event.created_at, + })); + } catch { + return []; + } +} diff --git a/next.config.ts b/next.config.ts index e9ffa30..7e9c382 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,14 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "ghchart.rshah.org", + }, + ], + }, }; export default nextConfig;