Compare commits
+8
-11
@@ -2,28 +2,25 @@
|
|||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import sizeOf from 'image-size';
|
import sharp from 'sharp';
|
||||||
import PhotoGallery from '@/app/components/PhotoGallery';
|
import PhotoGallery from '@/app/components/PhotoGallery';
|
||||||
|
|
||||||
async function getImages() {
|
async function getImages() {
|
||||||
const galleryDir = path.join(process.cwd(), 'public/gallery');
|
const galleryDir = path.join(process.cwd(), 'public/gallery');
|
||||||
const files = fs.readdirSync(galleryDir);
|
const files = fs.readdirSync(galleryDir);
|
||||||
const images = files
|
const imagePromises = files
|
||||||
.filter((file) => file.endsWith('.jpg') || file.endsWith('.png') || file.endsWith('.jpeg'))
|
.filter((file) => file.match(/\.(jpg|jpeg|png)$/i))
|
||||||
.map((file) => {
|
.map(async (file) => {
|
||||||
const filePath = path.join(galleryDir, file);
|
const filePath = path.join(galleryDir, file);
|
||||||
const buffer = fs.readFileSync(filePath);
|
const metadata = await sharp(filePath).metadata();
|
||||||
const dimensions = sizeOf(buffer);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
src: `/gallery/${file}`,
|
src: `/gallery/${file}`,
|
||||||
width: dimensions.width || 1000,
|
width: metadata.width || 1000,
|
||||||
height: dimensions.height || 1000,
|
height: metadata.height || 1000,
|
||||||
name: file,
|
name: file,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
return Promise.all(imagePromises);
|
||||||
return images;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function GalleryPage() {
|
export default async function GalleryPage() {
|
||||||
|
|||||||
+24
-42
@@ -1,9 +1,11 @@
|
|||||||
// app/projects/code/page.tsx
|
// app/projects/code/page.tsx
|
||||||
|
|
||||||
import MatrixRain from '@/app/components/MatrixRain';
|
import MatrixRain from '@/app/components/MatrixRain';
|
||||||
import { FaCodeBranch, FaGithub, FaGitlab, FaGlobe, FaStar } from 'react-icons/fa';
|
import { FaCodeBranch, FaGithub, FaGlobe, FaStar } from 'react-icons/fa';
|
||||||
|
import { SiGitea } from 'react-icons/si';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
|
||||||
interface GithubProfile {
|
interface GithubProfile {
|
||||||
html_url: string;
|
html_url: string;
|
||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
@@ -24,12 +26,12 @@ interface GithubRepo {
|
|||||||
forks: number;
|
forks: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitlabRepo {
|
interface GiteaRepo {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
web_url: string;
|
html_url: string;
|
||||||
star_count: number;
|
stars_count: number;
|
||||||
forks_count: number;
|
forks_count: number;
|
||||||
language?: string;
|
language?: string;
|
||||||
}
|
}
|
||||||
@@ -54,36 +56,16 @@ async function getGithubRepos(): Promise<GithubRepo[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getGitlabRepos(): Promise<GitlabRepo[]> {
|
async function getGiteaRepos(): Promise<GiteaRepo[]> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://gitlab.soderberg.tech/api/v4/groups/soderberg-tech/projects', {
|
const res = await fetch('https://git.stws.cc/api/v1/users/WilliamSoderberg/repos', {
|
||||||
next: { revalidate: 60 }
|
next: { revalidate: 60 }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) return [];
|
if (!res.ok) return [];
|
||||||
const projects: GitlabRepo[] = await res.json();
|
return res.json();
|
||||||
const projectsWithLanguages = await Promise.all(
|
|
||||||
projects.map(async (repo) => {
|
|
||||||
try {
|
|
||||||
const langRes = await fetch(`https://gitlab.soderberg.tech/api/v4/projects/${repo.id}/languages`, {
|
|
||||||
next: { revalidate: 60 }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (langRes.ok) {
|
|
||||||
const languages = await langRes.json();
|
|
||||||
const topLanguage = Object.keys(languages).sort((a, b) => languages[b] - languages[a])[0];
|
|
||||||
return { ...repo, language: topLanguage };
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
console.error(`Failed to fetch language for GitLab repo: ${repo.id}`);
|
console.error("Gitea fetch failed, returning empty array");
|
||||||
}
|
|
||||||
return repo;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return projectsWithLanguages;
|
|
||||||
} catch {
|
|
||||||
console.error("GitLab fetch failed, returning empty array");
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,10 +82,10 @@ const langColors: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function CodePage() {
|
export default async function CodePage() {
|
||||||
const [profile, githubRepos, gitlabRepos] = await Promise.all([
|
const [profile, githubRepos, giteaRepos] = await Promise.all([
|
||||||
getProfile(),
|
getProfile(),
|
||||||
getGithubRepos(),
|
getGithubRepos(),
|
||||||
getGitlabRepos()
|
getGiteaRepos()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -153,24 +135,24 @@ export default async function CodePage() {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 3. GitLab Repositories Section */}
|
{/* 3. Gitea Repositories Section */}
|
||||||
<section className="bg-[#fc6d26]/15 dark:bg-[#fc6d26]/10 backdrop-blur-lg rounded-3xl border border-[#fc6d26]/20 dark:border-[#fc6d26]/30 p-8 shadow-xl dark:shadow-2xl transition-colors duration-300">
|
<section className="bg-[#609926]/15 dark:bg-[#609926]/10 backdrop-blur-lg rounded-3xl border border-[#609926]/20 dark:border-[#609926]/30 p-8 shadow-xl dark:shadow-2xl transition-colors duration-300">
|
||||||
<div className="flex justify-center items-center flex-col mb-10">
|
<div className="flex justify-center items-center flex-col mb-10">
|
||||||
<a href="https://gitlab.soderberg.tech/WilliamSoderberg" target="_blank" rel="noopener noreferrer">
|
<a href="https://git.stws.cc/WilliamSoderberg" target="_blank" rel="noopener noreferrer">
|
||||||
<FaGitlab className="text-[#fc6d26] text-[55px] drop-shadow-[0_0_15px_rgba(252,109,38,0.4)] hover:scale-110 transition-transform cursor-pointer" />
|
<SiGitea className="text-[#609926] text-[55px] drop-shadow-[0_0_15px_rgba(96,153,38,0.4)] hover:scale-110 transition-transform cursor-pointer" />
|
||||||
</a>
|
</a>
|
||||||
<h2 className="text-slate-900 dark:text-white mt-3.75 text-[32px] md:text-[38px] uppercase font-bold text-center tracking-wider transition-colors">
|
<h2 className="text-slate-900 dark:text-white mt-3.75 text-[32px] md:text-[38px] uppercase font-bold text-center tracking-wider transition-colors">
|
||||||
GitLab Projects
|
Gitea Projects
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-600 dark:text-gray-300 text-center max-w-2xl mt-3 transition-colors">Self-hosted infrastructure and private development.</p>
|
<p className="text-slate-600 dark:text-gray-300 text-center max-w-2xl mt-3 transition-colors">Self-hosted infrastructure and private development.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{gitlabRepos.length > 0 ? (
|
{giteaRepos.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 justify-center">
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 justify-center">
|
||||||
{gitlabRepos.map((repo) => (
|
{giteaRepos.map((repo) => (
|
||||||
<a key={repo.id} href={repo.web_url} target="_blank" rel="noopener noreferrer" className="group flex flex-col justify-between bg-white/90 dark:bg-linear-to-br dark:from-[#2a2a2a]/90 dark:to-[#222222]/90 border border-slate-200 dark:border-white/10 p-6 rounded-2xl hover:-translate-y-1 hover:border-[#fc6d26]/60 dark:hover:border-[#fc6d26]/60 hover:shadow-[0_8px_24px_rgba(252,109,38,0.15)] dark:hover:shadow-[0_8px_24px_rgba(252,109,38,0.2)] transition-all duration-300 no-underline">
|
<a key={repo.id} href={repo.html_url} target="_blank" rel="noopener noreferrer" className="group flex flex-col justify-between bg-white/90 dark:bg-linear-to-br dark:from-[#2a2a2a]/90 dark:to-[#222222]/90 border border-slate-200 dark:border-white/10 p-6 rounded-2xl hover:-translate-y-1 hover:border-[#609926]/60 dark:hover:border-[#609926]/60 hover:shadow-[0_8px_24px_rgba(96,153,38,0.15)] dark:hover:shadow-[0_8px_24px_rgba(96,153,38,0.2)] transition-all duration-300 no-underline">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-[#fc6d26] text-[19px] font-bold group-hover:text-slate-900 dark:group-hover:text-white transition-colors truncate">
|
<h3 className="text-[#609926] text-[19px] font-bold group-hover:text-slate-900 dark:group-hover:text-white transition-colors truncate">
|
||||||
{repo.name}
|
{repo.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[14px] mb-4 mt-2.5 text-slate-500 dark:text-gray-400 text-left line-clamp-3 leading-relaxed transition-colors">
|
<p className="text-[14px] mb-4 mt-2.5 text-slate-500 dark:text-gray-400 text-left line-clamp-3 leading-relaxed transition-colors">
|
||||||
@@ -184,9 +166,9 @@ export default async function CodePage() {
|
|||||||
<span className="text-slate-600 dark:text-gray-300">{repo.language}</span>
|
<span className="text-slate-600 dark:text-gray-300">{repo.language}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{repo.star_count > 0 && (
|
{repo.stars_count > 0 && (
|
||||||
<span className="flex items-center mr-4">
|
<span className="flex items-center mr-4">
|
||||||
<FaStar className="mr-1.5" /> {repo.star_count}
|
<FaStar className="mr-1.5" /> {repo.stars_count}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{repo.forks_count > 0 && (
|
{repo.forks_count > 0 && (
|
||||||
@@ -199,7 +181,7 @@ export default async function CodePage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center text-slate-500 dark:text-gray-400 italic py-8 bg-black/5 dark:bg-black/20 rounded-xl transition-colors">No public GitLab repositories found or API is restricted.</div>
|
<div className="text-center text-slate-500 dark:text-gray-400 italic py-8 bg-black/5 dark:bg-black/20 rounded-xl transition-colors">No public Gitea repositories found or API is restricted.</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Generated
+1148
-897
File diff suppressed because it is too large
Load Diff
+8
-4
@@ -11,15 +11,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-three/drei": "^10.7.7",
|
"@react-three/drei": "^10.7.7",
|
||||||
"@react-three/fiber": "^9.5.0",
|
"@react-three/fiber": "^9.5.0",
|
||||||
"image-size": "^2.0.2",
|
"next": "^16.3.1",
|
||||||
"next": "16.1.6",
|
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"photoswipe": "^5.4.4",
|
"photoswipe": "^5.4.4",
|
||||||
"react": "19.2.3",
|
"react": "^19.2.8",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "^19.2.8",
|
||||||
"react-icons": "^5.6.0",
|
"react-icons": "^5.6.0",
|
||||||
"react-photoswipe-gallery": "^4.0.0",
|
"react-photoswipe-gallery": "^4.0.0",
|
||||||
"react-syntax-highlighter": "^16.1.1",
|
"react-syntax-highlighter": "^16.1.1",
|
||||||
|
"sharp": "^0.35.3",
|
||||||
"three": "^0.183.2"
|
"three": "^0.183.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -33,5 +33,9 @@
|
|||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.1.6",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"allowScripts": {
|
||||||
|
"sharp@0.34.5": true,
|
||||||
|
"unrs-resolver@1.12.2": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user