Files

331 lines
11 KiB
TypeScript

import { Link } from "@/i18n/routing";
import { Icon } from "@iconify/react";
import {
Badge,
Button,
ButtonIcon,
ButtonLabel,
Card,
Typography,
} from "poyraz-ui/atoms";
import { StaggerContainer, StaggerItem } from "@/components/motion-wrapper";
import { GithubContributionGraph } from "@/components/github-contribution-graph";
import { ProjectCardWithPopover } from "@/components/project-card-with-popover";
import {
EXTENSIONS,
FIGMA_TEMPLATES,
MOBILE_APPS,
WEB_APPS,
type ProjectItem,
} from "@/data/projects";
import {
getGithubContributions,
getGithubRepos,
getNpmPackages,
} from "@/lib/project-feeds";
import { getTranslations, getLocale } from "next-intl/server";
import { getLocalizedValue } from "@/lib/locale";
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" };
}
type LocalizedProjectItem = {
id: string;
title: string;
description: string;
image: string;
technologies: string[];
architecture: string;
badge?: string;
href?: string;
caseStudySlug?: string;
};
function ProjectSection({
title,
items,
technologiesLabel,
architectureLabel,
caseStudyLabel,
}: {
title: string;
items: LocalizedProjectItem[];
technologiesLabel: string;
architectureLabel: string;
caseStudyLabel: string;
}) {
return (
<section className="space-y-3">
<Typography variant="large" className="text-base">
{title}
</Typography>
<StaggerContainer className="grid grid-cols-2 gap-2 lg:grid-cols-4">
{items.map((item) => (
<StaggerItem key={item.id}>
<ProjectCardWithPopover
image={item.image}
title={item.title}
description={item.description}
badge={item.badge}
href={item.href}
caseStudyHref={
item.caseStudySlug
? `/projects/${item.caseStudySlug}`
: undefined
}
caseStudyLabel={caseStudyLabel}
technologies={item.technologies}
architecture={item.architecture}
technologiesLabel={technologiesLabel}
architectureLabel={architectureLabel}
triggerClassName="w-full"
className="aspect-square rounded-sm border-border"
/>
</StaggerItem>
))}
</StaggerContainer>
</section>
);
}
export async function ProjectsContent() {
const t = await getTranslations("Projects");
const locale = await getLocale();
const [repos, npmPackages, contributions] = await Promise.all([
getGithubRepos(),
getNpmPackages(),
getGithubContributions(),
]);
const localizeItems = (items: ProjectItem[]): LocalizedProjectItem[] => {
return items.map((item) => ({
...item,
title: getLocalizedValue(item.title, locale),
description: getLocalizedValue(item.description, locale),
architecture: getLocalizedValue(item.architecture, locale),
badge: item.badge ? getLocalizedValue(item.badge, locale) : undefined,
}));
};
return (
<section className="flex h-full flex-col gap-8 overflow-y-auto md:gap-10">
<Card className="rounded-sm border-border bg-background p-2">
<GithubContributionGraph
days={contributions}
locale={locale}
labels={{
calendar: t("contributionCalendar"),
unavailable: t("contributionUnavailable"),
none: t("noContributions"),
singular: t("contributionSingular"),
plural: t("contributionPlural"),
}}
/>
</Card>
<ProjectSection
title={t("sections.webApps")}
items={localizeItems(WEB_APPS)}
technologiesLabel={t("technologies")}
architectureLabel={t("architecture")}
caseStudyLabel={t("viewCaseStudy")}
/>
<ProjectSection
title={t("sections.mobileApps")}
items={localizeItems(MOBILE_APPS)}
technologiesLabel={t("technologies")}
architectureLabel={t("architecture")}
caseStudyLabel={t("viewCaseStudy")}
/>
<ProjectSection
title={t("sections.extensions")}
items={localizeItems(EXTENSIONS)}
technologiesLabel={t("technologies")}
architectureLabel={t("architecture")}
caseStudyLabel={t("viewCaseStudy")}
/>
<ProjectSection
title={t("sections.figmaTemplates")}
items={localizeItems(FIGMA_TEMPLATES)}
technologiesLabel={t("technologies")}
architectureLabel={t("architecture")}
caseStudyLabel={t("viewCaseStudy")}
/>
<section className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3">
<Typography variant="large" className="text-base">
{t("sections.npmPackages")}
</Typography>
<Button asChild variant="outline" size="xs" radius="sm">
<a
href="https://www.npmjs.com/~poyrazavsever"
target="_blank"
rel="noopener noreferrer"
>
<ButtonIcon>
<Icon icon="mdi:npm" width={16} height={16} />
</ButtonIcon>
<ButtonLabel>{t("viewNpmPackages")}</ButtonLabel>
</a>
</Button>
</div>
<div className="grid gap-2 md:grid-cols-2">
{npmPackages.length === 0 ? (
<Card className="rounded-sm border-border p-3 md:col-span-2">
<Typography variant="small" className="text-muted-foreground">
{t("emptyNpm")}
</Typography>
</Card>
) : (
npmPackages.map((pkg) => (
<Link
key={pkg.name}
href={pkg.npmUrl}
target="_blank"
rel="noreferrer"
className="block h-full no-underline text-inherit"
>
<Card className="flex h-full flex-col rounded-sm border-border p-3 transition-colors hover:border-zinc-700">
<Typography
variant="large"
className="text-base leading-tight"
>
{pkg.name}
</Typography>
<Typography
variant="small"
className="mt-1 flex-1 line-clamp-3 text-muted-foreground"
>
{pkg.description}
</Typography>
<div className="mt-2">
<Badge className="rounded-sm">v{pkg.version}</Badge>
</div>
</Card>
</Link>
))
)}
</div>
</section>
<section className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3">
<Typography variant="large" className="text-base">
{t("sections.githubRepos")}
</Typography>
<Button asChild variant="outline" size="xs" radius="sm">
<a
href="https://github.com/poyrazavsever"
target="_blank"
rel="noopener noreferrer"
>
<ButtonIcon>
<Icon icon="mdi:github" width={16} height={16} />
</ButtonIcon>
<ButtonLabel>{t("visitGithub")}</ButtonLabel>
</a>
</Button>
</div>
<div className="grid gap-2 md:grid-cols-2">
{repos.length === 0 ? (
<Card className="rounded-sm border-border p-3 md:col-span-2">
<Typography variant="small" className="text-muted-foreground">
{t("emptyGithub")}
</Typography>
</Card>
) : (
repos.map((repo) => {
const languageMeta = getLanguageMeta(repo.language);
return (
<Link
key={repo.id}
href={repo.htmlUrl}
target="_blank"
rel="noreferrer"
className="block h-full no-underline text-inherit"
>
<Card className="flex h-full flex-col rounded-sm border-border p-3 transition-colors hover:border-zinc-700">
<Typography
variant="large"
className="text-base leading-tight"
>
{repo.name}
</Typography>
<Typography
variant="small"
className="mt-1 flex-1 line-clamp-4 text-muted-foreground"
>
{repo.description}
</Typography>
<div className="mt-2 flex flex-wrap gap-1.5">
<Badge variant="outline" className="rounded-sm">
<span className="inline-flex items-center gap-1">
<Icon
icon={languageMeta.icon}
width={14}
height={14}
className={languageMeta.color}
/>
<span>{repo.language}</span>
</span>
</Badge>
<Badge variant="outline" className="rounded-sm">
<span className="inline-flex items-center gap-1">
<Icon
icon="mdi:star"
width={14}
height={14}
className="text-amber-500"
/>
<span>{repo.stars}</span>
</span>
</Badge>
</div>
</Card>
</Link>
);
})
)}
</div>
</section>
</section>
);
}