MOVE YOUR BUSINESS FORWARD START WITH A FREE WEBSITE AUDIT
MOVE YOURBUSINESSFORWARD
START WITH A FREE WEBSITE AUDIT
// full-stack web developer
Ιοαnnis Καperdαs
I design and build high-performance websites and web apps. From strategy to launch.
tools & software
design · development · delivery
selected work: My latest projects
A few recent projects: the problem, what I shipped, and what changed.
Recurring office ordering · 2026
CafeScoop
result
A direct channel that helps neighborhood cafés turn nearby offices into repeat customers, while each office gets a fast link-to-order flow and the café gets one clear ticket instead of message chaos.
1 link → 1 café ticket
project details
- problem
- Neighborhood cafés lose repeat office business to fragmented phone and WhatsApp orders: the same details are collected every day, cutoffs are unclear, and staff must manually combine each person's choices into one order.
- built
- A bilingual recurring-order platform in Next.js 15, TypeScript and Supabase. One organizer creates the group and schedule, teammates join from a link without required signup, the live order locks automatically, and the café receives one aggregated ticket. Kitchen workflow, email notifications, revenue reporting and Sentry complete the operating loop.
- Next.js 15
- TypeScript
- Supabase
- Realtime
- EL / EN
code: Recent Code Initiatives
Patterns I reach for on real work. Each one solves a problem you'll recognise. Pick one to read it.
Modal.tsx
Modal dialogs that trap focus and restore it, without pulling in a UI library.
// Problem: accessible modal dialogs that trap focus and restore it on close,
// without pulling in a whole UI library.
import { useEffect, useRef } from "react";
export function Modal({ open, onClose, title, children }: {
open: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const previouslyFocused = document.activeElement as HTMLElement | null;
const focusables = ref.current!.querySelectorAll<HTMLElement>(
'a[href], button:not([disabled]), input, [tabindex]:not([tabindex="-1"])',
);
focusables[0]?.focus();
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") return onClose();
if (e.key !== "Tab" || focusables.length === 0) return;
const first = focusables[0];
const last = focusables[focusables.length - 1];
// Wrap focus so Tab never escapes the dialog.
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
previouslyFocused?.focus(); // restore focus to the trigger element
};
}, [open, onClose]);
if (!open) return null;
return (
<div className="overlay" onClick={onClose}>
<div
ref={ref}
role="dialog"
aria-modal="true"
aria-label={title}
onClick={(e) => e.stopPropagation()}
>
{children}
</div>
</div>
);
}about: Full-stack execution
I'm Ioannis Kaperdas, a full-stack web developer with hands-on experience delivering websites and web applications across a range of projects. I hold an MSc in Business Information Technology Systems from the University of Strathclyde and an MSc in IT and Applications from the University of West Attica.
I care about the things that make software actually good: fast load times, accessible markup, typed and maintainable code, and clear communication along the way.
Tools are means to an end. I pick the right ones for the job and keep the codebase something the next person can live with.
what I do
- Web apps & dashboards
- /Marketing & landing sites
- /Performance & SEO
- /Design to code
- based in
- Athens, Greece · Remote worldwide
- stack
- TypeScript · React · Next.js
- working with
- Startups, agencies & founders
// toolkit
- TypeScript
- React
- Next.js
- Node.js
- Tailwind CSS
- PostgreSQL
- REST & GraphQL
- Figma → Code
- Web Vitals
- Vercel / CI
contact: Let's work together
Have something in mind, or just want to chat? Tell me about it and I'll reply within a day.

