Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cc52cca
refactor: cards horizontales pleine largeur en mode client
ddrayko Jul 20, 2026
9b14932
fix: ajout du dégradé smooth entre l'image et le texte sur les cards
ddrayko Jul 20, 2026
5ba4064
fix: remplace le dégradé couleur par un blur dégradé sur l'image
ddrayko Jul 20, 2026
42cea0d
fix: image en fond étendu avec blur dégradé pour transition smooth ve…
ddrayko Jul 20, 2026
03183f6
fix: image s'étend sur toute la card avec blur gradient de 38% à 55%,…
ddrayko Jul 20, 2026
19c8060
fix: ajout d'un dégradé de transparence en plus du blur pour fondre l…
ddrayko Jul 20, 2026
6df11fc
Revert "fix: ajout d'un dégradé de transparence en plus du blur pour …
ddrayko Jul 20, 2026
e5493e5
fix: texte à gauche, image à droite, blur inversé
ddrayko Jul 20, 2026
95b0ee5
fix: limite la largeur du texte avec max-w-prose
ddrayko Jul 20, 2026
8808022
fix: limite la description avec max-w-sm
ddrayko Jul 20, 2026
ab3e366
fix: limite aussi la largeur des tags
ddrayko Jul 20, 2026
021f1d0
fix: élargit de max-w-sm à max-w-md
ddrayko Jul 20, 2026
e6c261f
Merge pull request #3 from ddrayko/main
ddrayko Jul 20, 2026
1a7e0cc
feat: détection automatique de la luminosité du fond pour adapter la …
ddrayko Jul 21, 2026
eaec60a
fix: remplace la détection canvas par un overlay sombre pour la lisib…
ddrayko Jul 21, 2026
aca9385
fix: détection de luminosité canvas avec gestion CORS + texte adaptat…
ddrayko Jul 21, 2026
fbd35e3
fix: utilise l'URL optimisée Next.js (_next/image) pour contourner CO…
ddrayko Jul 21, 2026
3e02dfb
fix: proxy API img-proxy pour contourner CORS + échantillonne le côté…
ddrayko Jul 21, 2026
67e525b
fix: remplace la détection canvas par mix-blend-mode:difference pour …
ddrayko Jul 21, 2026
710bdd1
fix: remplace mix-blend-mode par text-shadow pour une lisibilité gara…
ddrayko Jul 21, 2026
401d0d8
fix: proxy API + canvas échantillonne le côté gauche + texte propre b…
ddrayko Jul 21, 2026
c7ea5c2
fix: utilise le onLoad de Next.js Image + dessine l'élément img direc…
ddrayko Jul 21, 2026
73caac8
fix: remplace la détection canvas par une API serverless (sharp) pour…
ddrayko Jul 21, 2026
871d4ca
feat: remplace détection auto luminosité par toggle manuel text_color…
ddrayko Jul 21, 2026
b6b5608
fix: remplace text-gradient par text-white sur le titre, ajoute coule…
ddrayko Jul 21, 2026
51dd102
feat: ajoute le champ featured, section projet en vedette + grille 3 …
ddrayko Jul 22, 2026
ba5fa84
fix: uniformise la hauteur des petites cards avec flex-col h-full
ddrayko Jul 22, 2026
316c6b0
fix: passe le texte des petites cards en noir avec fond blanc
ddrayko Jul 22, 2026
297a520
chore: supprime la migration manuelle (incompatible avec le snapshot …
ddrayko Jul 22, 2026
b42dcda
fix: ne convertit les booléens en 0/1 que pour SQLite, pas pour Postg…
ddrayko Jul 22, 2026
626587f
supprime les titres Projet en vedette et Autres projets
ddrayko Jul 22, 2026
d0ea7cc
reduce l'espacement entre le projet featured et la grille
ddrayko Jul 22, 2026
08d29d4
remonte les boutons du hero avec items-start pt-48
ddrayko Jul 22, 2026
c2a6ece
rapProche les boutons du sous-titre dans le hero
ddrayko Jul 22, 2026
ef12820
remonte tout le contenu du hero (items-start pt-32)
ddrayko Jul 22, 2026
4f58aed
augmente l'espace entre les deux boutons du hero
ddrayko Jul 22, 2026
0dfdcac
retire la fleche ArrowUpRight sur la carte projet
ddrayko Jul 22, 2026
4adf9b2
augmente encore l'espace entre les boutons (gap-8)
ddrayko Jul 22, 2026
932a33b
augmente l'espace entre les boutons et le texte du hero
ddrayko Jul 22, 2026
de3a47d
reduit l'espacement entre les deux boutons
ddrayko Jul 22, 2026
7fbae3d
feat: add GitHub projects slider with admin management
ddrayko Jul 25, 2026
0ff2367
chore: remove Projects I've Created from GitHub slider
ddrayko Jul 25, 2026
a4e3b71
chore: remove subtitle from GitHub slider section
ddrayko Jul 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions app/admin/dashboard/admin-dashboard-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,33 @@

import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { Plus, LogOut, Home, UserPlus, LayoutDashboard, Database, Activity } from "lucide-react"
import { Plus, LogOut, Home, UserPlus, LayoutDashboard, Database, Activity, Github } from "lucide-react"
import { AdminProjectCard } from "@/components/admin-project-card"
import { ProjectDialog } from "@/components/project-dialog"
import { AdminDialog } from "@/components/admin-dialog"
import { AdminCard } from "@/components/admin-card"
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/components/ui/accordion"
import type { Project, Admin } from "@/lib/types"
import type { Project, Admin, GitHubProject } from "@/lib/types"
import { logoutAdmin } from "@/lib/admin-auth"
import { getAdmins, getMaintenanceMode, getAvailability, getProjects } from "@/lib/actions"
import { getAdmins, getMaintenanceMode, getAvailability, getProjects, getGitHubProjects } from "@/lib/actions"
import { GitHubProjectDialog } from "@/components/github-project-dialog"
import { AdminGitHubProjectCard } from "@/components/admin-github-project-card"
import { MaintenanceToggle } from "@/components/maintenance-toggle"
import { AvailabilityToggle } from "@/components/availability-toggle"
import Link from "next/link"

export default function AdminDashboardClient() {
const [projects, setProjects] = useState<Project[]>([])
const [admins, setAdmins] = useState<Admin[]>([])
const [githubProjects, setGitHubProjects] = useState<GitHubProject[]>([])
const [isLoading, setIsLoading] = useState(true)
const [addProjectOpen, setAddProjectOpen] = useState(false)
const [addAdminOpen, setAddAdminOpen] = useState(false)
const [maintenanceMode, setMaintenanceMode] = useState(false)
const [maintenanceMessage, setMaintenanceMessage] = useState("")
const [maintenanceProgress, setMaintenanceProgress] = useState(0)
const [isAvailable, setIsAvailable] = useState(true)
const [addGitHubProjectOpen, setAddGitHubProjectOpen] = useState(false)

const fetchProjects = async () => {
const result = await getProjects()
Expand Down Expand Up @@ -56,6 +60,13 @@ export default function AdminDashboardClient() {
}
}

const fetchGitHubProjects = async () => {
const result = await getGitHubProjects()
if (result.success) {
setGitHubProjects(result.data)
}
}

const fetchAvailability = async () => {
const result = await getAvailability()
if (result.success && result.isAvailable !== undefined) {
Expand All @@ -70,12 +81,17 @@ export default function AdminDashboardClient() {
fetchAdmins()
fetchMaintenanceMode()
fetchAvailability()
fetchGitHubProjects()
}, [])

const handleProjectDeleted = (projectId: string) => {
setProjects((prev) => prev.filter((p) => p.id !== projectId))
}

const handleGitHubProjectDeleted = (projectId: string) => {
setGitHubProjects((prev) => prev.filter((p) => p.id !== projectId))
}

const handleProjectUpdated = () => {
fetchProjects()
}
Expand Down Expand Up @@ -182,6 +198,41 @@ export default function AdminDashboardClient() {
)}
</div>

{/* GitHub Projects Section */}
<div className="space-y-8 pt-8 border-t border-white/5">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-8">
<div className="space-y-2">
<h2 className="text-2xl font-bold tracking-tight">GITHUB PROJECTS</h2>
<p className="text-muted-foreground font-medium max-w-xl text-sm">
Add repos from GitHub — name & description are fetched automatically.
</p>
</div>
<Button onClick={() => setAddGitHubProjectOpen(true)} className="rounded-xl h-12 px-6 bg-primary hover:bg-primary/90 text-primary-foreground shadow-lg shadow-primary/25 group self-start md:self-auto transition-all" aria-label="Add GitHub project">
<Plus className="mr-2 h-4 w-4 group-hover:rotate-90 transition-transform duration-300" aria-hidden="true" />
Add Repo
</Button>
</div>

{githubProjects.length === 0 ? (
<div className="bg-black/5 dark:bg-white/5 p-12 rounded-3xl border border-black/5 dark:border-white/5 text-center space-y-4 shadow-inner">
<div className="w-16 h-16 rounded-full bg-black/5 dark:bg-white/5 mx-auto flex items-center justify-center text-muted-foreground">
<Github className="h-8 w-8" />
</div>
<p className="text-muted-foreground italic">No GitHub projects added yet.</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{githubProjects.map((gp) => (
<AdminGitHubProjectCard
key={gp.id}
project={gp}
onDeleted={handleGitHubProjectDeleted}
/>
))}
</div>
)}
</div>

</div>
</AccordionContent>
</AccordionItem>
Expand Down Expand Up @@ -275,6 +326,7 @@ export default function AdminDashboardClient() {

<ProjectDialog open={addProjectOpen} onOpenChange={setAddProjectOpen} onSuccess={fetchProjects} />
<AdminDialog open={addAdminOpen} onOpenChange={setAddAdminOpen} onSuccess={fetchAdmins} />
<GitHubProjectDialog open={addGitHubProjectOpen} onOpenChange={setAddGitHubProjectOpen} onSuccess={fetchGitHubProjects} />
</div>
)
}
60 changes: 50 additions & 10 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { db } from "@/db"
import { projets as projetsTable } from "@/db/schema"
import { desc } from "drizzle-orm"
import type { Project } from "@/lib/types"
import { projets as projetsTable, githubProjects as githubProjectsTable } from "@/db/schema"
import { desc, eq, or, isNull } from "drizzle-orm"
import type { Project, GitHubProject } from "@/lib/types"
import { PortfolioContent } from "@/components/portfolio-content"
import { GitHubProjectSlider } from "@/components/github-project-slider"
import { TechStack } from "@/components/tech-stack"
import { Button } from "@/components/ui/button"
import { ScrollButton } from "@/components/scroll-button"
Expand Down Expand Up @@ -46,17 +47,54 @@ export default async function HomePage() {
}
}

let projects: Project[] = []
let featuredProject: Project | null = null
let otherProjects: Project[] = []
let fetchError = false
let githubCreated: GitHubProject[] = []
let githubUsed: GitHubProject[] = []

try {
const rawProjects = await db.select().from(projetsTable).orderBy(desc(projetsTable.created_at))
projects = rawProjects.map((p: any) => ({
const [rawFeatured] = await db.select().from(projetsTable)
.where(eq(projetsTable.featured, true))
.limit(1)

const rawOther = await db.select().from(projetsTable)
.where(
or(
eq(projetsTable.featured, false),
isNull(projetsTable.featured)
)
)
.orderBy(desc(projetsTable.created_at))

featuredProject = rawFeatured ? ({
...rawFeatured,
id: rawFeatured.id.toString(),
created_at: rawFeatured.created_at?.toISOString() || new Date().toISOString(),
updated_at: new Date().toISOString(),
}) as unknown as Project : null

otherProjects = rawOther.map((p: any) => ({
...p,
id: p.id.toString(),
created_at: p.created_at?.toISOString() || new Date().toISOString(),
updated_at: new Date().toISOString(),
})) as unknown as Project[]
const rawGithub = await db.select().from(githubProjectsTable).orderBy(desc(githubProjectsTable.created_at))
githubCreated = rawGithub
.filter((p: any) => p.type === "created")
.map((p: any) => ({
...p,
id: p.id.toString(),
created_at: p.created_at?.toISOString() || new Date().toISOString(),
})) as unknown as GitHubProject[]
githubUsed = rawGithub
.filter((p: any) => p.type === "used")
.map((p: any) => ({
...p,
id: p.id.toString(),
created_at: p.created_at?.toISOString() || new Date().toISOString(),
})) as unknown as GitHubProject[]
} catch (e) {
console.error("Database connection error:", e)
fetchError = true
Expand Down Expand Up @@ -95,8 +133,8 @@ export default async function HomePage() {
</header>

<main className="relative z-10 pt-20">
<section className="relative min-h-[calc(100dvh-5rem)] flex items-center justify-center overflow-hidden">
<div className="container px-6 py-24 mx-auto text-center space-y-12">
<section className="relative min-h-[calc(100dvh-5rem)] flex items-start justify-center pt-32 overflow-hidden">
<div className="container px-6 mx-auto text-center space-y-10">

<div className="space-y-6">
<h2 className="text-7xl md:text-8xl lg:text-[10rem] font-black tracking-[calc(-0.05em)] leading-[0.85] animate-fade-in-up font-display">
Expand All @@ -113,7 +151,7 @@ export default async function HomePage() {
</p>
</div>

<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-8 animate-fade-in-up" style={{ animationDelay: "0.2s" }}>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 animate-fade-in-up" style={{ animationDelay: "0.2s" }}>
<ScrollButton targetId="projects" size="lg" className="rounded-full px-8 bg-primary hover:bg-primary text-white hover:text-white shadow-xl shadow-primary/25 hover:shadow-2xl hover:shadow-primary/50 group w-full sm:w-auto transition-all text-sm font-bold uppercase tracking-widest">
Discover Projects
<ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" />
Expand Down Expand Up @@ -152,10 +190,12 @@ export default async function HomePage() {
)}
</div>

<PortfolioContent projects={projects} />
<PortfolioContent featuredProject={featuredProject} otherProjects={otherProjects} />
</div>
</section>

<GitHubProjectSlider created={githubCreated} used={githubUsed} />

<TechStack />
</main>
</div >
Expand Down
97 changes: 97 additions & 0 deletions components/admin-github-project-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"use client"

import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Trash2, RefreshCw, Github, Star, Loader2, ExternalLink } from "lucide-react"
import { deleteGitHubProject, refreshGitHubProject } from "@/lib/actions"
import { DeleteGitHubProjectDialog } from "./delete-github-project-dialog"
import type { GitHubProject } from "@/lib/types"

interface AdminGitHubProjectCardProps {
project: GitHubProject
onDeleted: (id: string) => void
}

export function AdminGitHubProjectCard({ project, onDeleted }: AdminGitHubProjectCardProps) {
const [refreshing, setRefreshing] = useState(false)
const [deleteOpen, setDeleteOpen] = useState(false)

const handleRefresh = async () => {
setRefreshing(true)
await refreshGitHubProject(project.id)
setRefreshing(false)
}

return (
<>
<div className="bg-black/5 dark:bg-white/5 rounded-2xl border border-black/5 dark:border-white/5 p-5 space-y-3 hover:bg-black/10 dark:hover:bg-white/10 transition-colors group">
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-2 min-w-0">
<Github className="h-4 w-4 text-muted-foreground flex-shrink-0" />
<span className="font-semibold text-sm truncate">{project.name}</span>
</div>
<div className="flex items-center gap-1 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={handleRefresh}
disabled={refreshing}
title="Refresh from GitHub"
>
{refreshing ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <RefreshCw className="h-3.5 w-3.5" />}
</Button>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-destructive hover:text-destructive"
onClick={() => setDeleteOpen(true)}
title="Delete"
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
</div>
{project.description && (
<p className="text-xs text-muted-foreground line-clamp-2">{project.description}</p>
)}
<div className="flex items-center gap-3 text-xs text-muted-foreground">
{project.language && (
<div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-full bg-primary/60" />
<span>{project.language}</span>
</div>
)}
<div className="flex items-center gap-1">
<Star className="h-3 w-3" />
<span>{project.stars}</span>
</div>
<a
href={project.github_url}
target="_blank"
rel="noopener noreferrer"
className="ml-auto flex items-center gap-1 hover:text-foreground transition-colors"
>
<ExternalLink className="h-3 w-3" />
</a>
</div>
<div className="pt-1">
<span className={`text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full ${
project.type === "created"
? "bg-blue-500/10 text-blue-500"
: "bg-green-500/10 text-green-500"
}`}>
{project.type === "created" ? "Created" : "Used"}
</span>
</div>
</div>
<DeleteGitHubProjectDialog
open={deleteOpen}
onOpenChange={setDeleteOpen}
projectId={project.id}
projectName={project.name}
onDeleted={() => onDeleted(project.id)}
/>
</>
)
}
5 changes: 5 additions & 0 deletions components/admin-project-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export function AdminProjectCard({ project, onDeleted, onUpdated }: AdminProject
Archived
</Badge>
)}
{project.featured && (
<Badge variant="secondary" className="bg-amber-500/10 text-amber-500 border-amber-500/20 text-[10px]">
Featured
</Badge>
)}
</div>
<p className="text-sm text-muted-foreground line-clamp-2 font-medium">
{project.description || "No description."}
Expand Down
61 changes: 61 additions & 0 deletions components/delete-github-project-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client"

import { useState } from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { deleteGitHubProject } from "@/lib/actions"

interface DeleteGitHubProjectDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
projectId: string
projectName: string
onDeleted: () => void
}

export function DeleteGitHubProjectDialog({
open,
onOpenChange,
projectId,
projectName,
onDeleted,
}: DeleteGitHubProjectDialogProps) {
const [loading, setLoading] = useState(false)

const handleDelete = async () => {
setLoading(true)
const result = await deleteGitHubProject(projectId)
if (result.success) {
onDeleted()
onOpenChange(false)
}
setLoading(false)
}

return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete GitHub Project</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to remove <strong>{projectName}</strong> from the list?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={loading}>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} disabled={loading} className="bg-destructive hover:bg-destructive/90">
{loading ? "Deleting..." : "Delete"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
Loading
Loading