Upgraded the projects view. Looks and acts MUCH nicer
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m21s

This commit is contained in:
2026-02-03 11:16:07 -07:00
parent ba1193896f
commit 6b77ce091d
6 changed files with 178 additions and 158 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "atridotdad",
"type": "module",
"version": "4.0.0",
"version": "4.1.0",
"scripts": {
"dev": "astro dev",
"build": "astro build",

View File

@@ -1,144 +0,0 @@
---
import { Icon } from "astro-icon/components";
import { config } from "../config";
import { fetchGiteaInfoFromUrl, formatRelativeTime } from "../utils/gitea";
import type { Project } from "../types";
Astro.response.headers.set(
"Cache-Control",
"public, max-age=300, s-maxage=300, stale-while-revalidate=60",
);
function isGiteaDomain(url: string): boolean {
if (!config.siteConfig.giteaDomains) return true;
try {
const urlObj = new URL(url);
return config.siteConfig.giteaDomains.some(
(domain) => urlObj.origin === new URL(domain).origin,
);
} catch {
return false;
}
}
const projectsWithGiteaInfo = await Promise.all(
config.projects.map(async (project) => {
if (
project.gitLink &&
!project.giteaInfo &&
isGiteaDomain(project.gitLink)
) {
const giteaInfo = await fetchGiteaInfoFromUrl(project.gitLink);
if (giteaInfo) {
return { ...project, giteaInfo } as Project;
}
}
return project;
}),
);
const sortedProjects = projectsWithGiteaInfo.sort((a, b) => {
const aTime = a.giteaInfo?.updatedAt
? new Date(a.giteaInfo.updatedAt).getTime()
: 0;
const bTime = b.giteaInfo?.updatedAt
? new Date(b.giteaInfo.updatedAt).getTime()
: 0;
return bTime - aTime;
});
---
<ul class="list bg-base-200 rounded-box max-w-4xl mx-auto">
{
sortedProjects.map((project) => (
<li class="list-row hover:bg-base-300 transition-colors">
{/* Project icon/avatar */}
<div class="list-col-grow-0">
<div class="w-12 h-12 rounded-lg bg-accent flex items-center justify-center">
<Icon name="mdi:code-braces" class="w-6 h-6 text-accent-content" />
</div>
</div>
{/* Main content */}
<div class="list-col-grow">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1">
<h3 class="font-bold text-lg">{project.name}</h3>
{project.giteaInfo?.updatedAt && (
<span class="text-xs opacity-60">
{formatRelativeTime(project.giteaInfo.updatedAt)}
</span>
)}
</div>
<p class="text-sm opacity-80 mt-1">{project.description}</p>
{/* Languages & Topics */}
<div class="flex flex-wrap gap-1 mt-2">
{project.giteaInfo?.languages?.slice(0, 3).map((lang: string) => (
<span class="badge badge-sm badge-primary">{lang}</span>
))}
{project.giteaInfo?.topics?.slice(0, 4).map((topic: string) => (
<span class="badge badge-sm badge-outline">{topic}</span>
))}
</div>
</div>
{/* Action buttons */}
<div class="list-col-grow-0 flex flex-wrap gap-1 justify-end">
{project.webLink && (
<a
href={project.webLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-primary hover:bg-primary hover:text-primary-content transition-all"
aria-label={`Visit ${project.name} website`}
>
<Icon name="mdi:web" class="w-5 h-5" />
</a>
)}
{project.gitLink && (
<a
href={project.gitLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-secondary hover:bg-secondary hover:text-secondary-content transition-all"
aria-label={`View ${project.name} source`}
>
<Icon name="simple-icons:gitea" class="w-5 h-5" />
</a>
)}
{project.iosLink && (
<a
href={project.iosLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-accent hover:bg-accent hover:text-accent-content transition-all"
aria-label={`${project.name} on iOS`}
>
<Icon name="mdi:apple" class="w-5 h-5" />
</a>
)}
{project.androidLink && (
<a
href={project.androidLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-success hover:bg-success hover:text-success-content transition-all"
aria-label={`${project.name} on Android`}
>
<Icon name="mdi:google-play" class="w-5 h-5" />
</a>
)}
</div>
</li>
))
}
</ul>
{
sortedProjects.length === 0 && (
<p class="text-center text-gray-500 mt-12">
No projects available yet. Check back soon!
</p>
)
}

View File

@@ -1,7 +0,0 @@
---
---
<div class="flex justify-center items-center py-12">
<span class="loading loading-spinner loading-lg text-primary"></span>
</div>

View File

@@ -206,6 +206,7 @@ export const config: Config = {
id: "atrodotdad",
name: "Personal Site",
description: "My personal website built with Astro.",
webLink: "https://atri.dad",
gitLink: "https://git.atri.dad/atridad/atridotdad",
},
],

View File

@@ -1,21 +1,189 @@
---
import Layout from "../layouts/Layout.astro";
import ProjectsIsland from "../components/ProjectsIsland.astro";
import ProjectsLoader from "../components/ProjectsLoader.astro";
import { Icon } from "astro-icon/components";
import { config } from "../config";
import { fetchGiteaInfoFromUrl, formatRelativeTime } from "../utils/gitea";
import type { Project } from "../types";
export const prerender = false;
Astro.response.headers.set(
"Cache-Control",
"public, max-age=300, s-maxage=300, stale-while-revalidate=60",
);
function isGiteaDomain(url: string): boolean {
if (!config.siteConfig.giteaDomains) return true;
try {
const urlObj = new URL(url);
return config.siteConfig.giteaDomains.some(
(domain) => urlObj.origin === new URL(domain).origin,
);
} catch {
return false;
}
}
const projectsWithGiteaInfo = await Promise.all(
config.projects.map(async (project) => {
if (
project.gitLink &&
!project.giteaInfo &&
isGiteaDomain(project.gitLink)
) {
const giteaInfo = await fetchGiteaInfoFromUrl(project.gitLink);
if (giteaInfo) {
return { ...project, giteaInfo } as Project;
}
}
return project;
}),
);
const sortedProjects = projectsWithGiteaInfo.sort((a, b) => {
const aTime = a.giteaInfo?.updatedAt
? new Date(a.giteaInfo.updatedAt).getTime()
: 0;
const bTime = b.giteaInfo?.updatedAt
? new Date(b.giteaInfo.updatedAt).getTime()
: 0;
return bTime - aTime;
});
---
<Layout>
<div class="w-full p-4 sm:p-8">
<div class="w-full max-w-3xl mx-auto p-4 sm:p-8">
<h1
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
>
Projects
</h1>
<ProjectsIsland server:defer>
<ProjectsLoader slot="fallback" />
</ProjectsIsland>
<ul
class="flex flex-col bg-base-100 rounded-box shadow-md border border-base-content/20 divide-y divide-base-content/20"
>
{
sortedProjects.map((project) => (
<li class="flex items-center hover:bg-base-200/50 transition-colors p-4 group relative rounded-none first:rounded-t-box last:rounded-b-box">
{/* Project icon/avatar */}
<div class="flex-none z-10 mr-4">
{project.giteaInfo?.avatarUrl ? (
<img
src={project.giteaInfo.avatarUrl}
alt={`${project.name} avatar`}
class="w-12 h-12 rounded-lg object-cover"
/>
) : (
<div class="w-12 h-12 rounded-lg bg-accent flex items-center justify-center">
<Icon
name="mdi:code-braces"
class="w-6 h-6 text-accent-content"
/>
</div>
)}
</div>
{/* Main content */}
<div class="flex-grow flex flex-col justify-center gap-1 z-10 pointer-events-none">
<div class="flex flex-col sm:flex-row sm:items-baseline gap-1 sm:gap-2">
<h3 class="font-bold text-lg sm:text-xl text-primary group-hover:text-accent transition-colors">
{project.name}
</h3>
{project.giteaInfo?.updatedAt && (
<span class="text-xs opacity-60">
{formatRelativeTime(
project.giteaInfo.updatedAt,
)}
</span>
)}
</div>
<p class="text-sm opacity-80 leading-relaxed">
{project.description}
</p>
{/* Languages & Topics */}
<div class="flex flex-wrap gap-1 mt-1">
{project.giteaInfo?.languages
?.slice(0, 3)
.map((lang: string) => (
<span class="badge badge-xs badge-primary">
{lang}
</span>
))}
{project.giteaInfo?.topics
?.slice(0, 4)
.map((topic: string) => (
<span class="badge badge-xs badge-outline opacity-70">
{topic}
</span>
))}
</div>
</div>
{/* Action buttons */}
<div class="flex-none flex flex-wrap gap-1 justify-end ml-4 z-20">
{project.webLink && (
<a
href={project.webLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-primary hover:bg-primary hover:text-primary-content transition-all"
aria-label={`Visit ${project.name} website`}
>
<Icon name="mdi:web" class="w-5 h-5" />
</a>
)}
{project.gitLink && (
<a
href={project.gitLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-success hover:bg-success hover:text-success-content transition-all"
aria-label={`View ${project.name} source`}
>
<Icon
name="simple-icons:gitea"
class="w-5 h-5"
/>
</a>
)}
{project.iosLink && (
<a
href={project.iosLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-accent hover:bg-accent hover:text-accent-content transition-all"
aria-label={`${project.name} on iOS`}
>
<Icon name="mdi:apple" class="w-5 h-5" />
</a>
)}
{project.androidLink && (
<a
href={project.androidLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-sm btn-square btn-ghost text-success hover:bg-success hover:text-success-content transition-all"
aria-label={`${project.name} on Android`}
>
<Icon
name="mdi:google-play"
class="w-5 h-5"
/>
</a>
)}
</div>
</li>
))
}
</ul>
{
sortedProjects.length === 0 && (
<p class="text-center text-gray-500 mt-12">
No projects available yet. Check back soon!
</p>
)
}
</div>
</Layout>

View File

@@ -4,6 +4,7 @@ export interface GiteaRepoInfo {
size: number;
defaultBranch: string;
topics: string[];
avatarUrl?: string;
}
export interface GiteaConfig {
@@ -74,6 +75,7 @@ export async function fetchGiteaRepoInfo(
size: data.size || 0,
defaultBranch: data.default_branch || "main",
topics: Array.isArray(data.topics) ? data.topics : [],
avatarUrl: data.avatar_url,
};
} catch (error) {
return null;