diff --git a/app/layout.tsx b/app/layout.tsx
index 2966036..8699f75 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,12 +1,7 @@
import type { Metadata } from "next";
import "./globals.css";
-import { SiteNavbar } from "@/components/site-navbar";
-import { NekoFollower } from "@/components/neko-follower";
-import { ANNOUNCEMENT_ITEMS } from "@/data/site-settings";
-import { Icon } from "@iconify/react";
-import Link from "next/link";
-import { AnnouncementBar } from "poyraz-ui/organisms";
+import { AppShell } from "@/components/app-shell";
export const metadata: Metadata = {
metadataBase: new URL("https://poyrazavsever.com"),
@@ -87,37 +82,10 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
- const announcement = ANNOUNCEMENT_ITEMS[0];
-
return (
-
-
-
- {announcement ? (
-
}
- action={
- announcement.actionLabel && announcement.actionHref ? (
-
- {announcement.actionLabel}
-
- ) : null
- }
- >
- {announcement.text}
-
- ) : null}
-
{children}
-
+ {children}
);
diff --git a/app/links/page.tsx b/app/links/page.tsx
index 746ca09..2d2cfb3 100644
--- a/app/links/page.tsx
+++ b/app/links/page.tsx
@@ -1,4 +1,35 @@
-import { LinksContent } from "@/components/links-content";
+import type { Metadata } from "next";
+import { LinksContent } from "@/components/links-content";
+
+export const metadata: Metadata = {
+ title: "Links",
+ description:
+ "Poyraz Avsever'in sosyal medya, portfolyo ve içerik bağlantılarına tek sayfadan ulaş.",
+ alternates: {
+ canonical: "/links",
+ },
+ openGraph: {
+ title: "Poyraz Avsever | Links",
+ description:
+ "Poyraz Avsever'in sosyal medya, portfolyo ve içerik bağlantılarına tek sayfadan ulaş.",
+ url: "https://poyrazavsever.com/links",
+ images: [
+ {
+ url: "/logo/cover.png",
+ width: 1536,
+ height: 512,
+ alt: "Poyraz Avsever Links sayfası kapak görseli",
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "Poyraz Avsever | Links",
+ description:
+ "Poyraz Avsever'in sosyal medya, portfolyo ve içerik bağlantılarına tek sayfadan ulaş.",
+ images: ["/logo/cover.png"],
+ },
+};
export default function LinksPage() {
return ;
diff --git a/components/app-shell.tsx b/components/app-shell.tsx
new file mode 100644
index 0000000..60c1ba5
--- /dev/null
+++ b/components/app-shell.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import { Icon } from "@iconify/react";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { AnnouncementBar } from "poyraz-ui/organisms";
+import { SiteNavbar } from "@/components/site-navbar";
+import { NekoFollower } from "@/components/neko-follower";
+import { ANNOUNCEMENT_ITEMS } from "@/data/site-settings";
+
+type AppShellProps = {
+ children: React.ReactNode;
+};
+
+export function AppShell({ children }: AppShellProps) {
+ const pathname = usePathname();
+ const announcement = ANNOUNCEMENT_ITEMS[0];
+ const isStandaloneLinksPage = pathname === "/links" || pathname.startsWith("/links/");
+
+ if (isStandaloneLinksPage) {
+ return children;
+ }
+
+ return (
+ <>
+
+
+
+ {announcement ? (
+
}
+ action={
+ announcement.actionLabel && announcement.actionHref ? (
+
+ {announcement.actionLabel}
+
+ ) : null
+ }
+ >
+ {announcement.text}
+
+ ) : null}
+
{children}
+
+ >
+ );
+}
diff --git a/components/links-content.tsx b/components/links-content.tsx
index edd17e8..6c1f38e 100644
--- a/components/links-content.tsx
+++ b/components/links-content.tsx
@@ -1,33 +1,52 @@
"use client";
+import Image from "next/image";
import { Icon } from "@iconify/react";
import Link from "next/link";
import { useMemo, useState } from "react";
import {
- Badge,
- Button,
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
Input,
Typography,
} from "poyraz-ui/atoms";
-import { Tabs, TabsList, TabsTrigger } from "poyraz-ui/molecules";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "poyraz-ui/molecules";
import {
LINK_DIRECTORY,
LINK_DIRECTORY_CATEGORIES,
+ SOCIAL_LINKS,
+ TOP_ICON_LINKS,
type LinkDirectoryCategory,
} from "@/lib/links";
type CategoryFilter = "all" | LinkDirectoryCategory;
const FILTER_ITEMS: ReadonlyArray<{ id: CategoryFilter; label: string }> = [
- { id: "all", label: "Tümü" },
+ { id: "all", label: "Tüm kategoriler" },
...LINK_DIRECTORY_CATEGORIES,
];
+const CATEGORY_LABELS = Object.fromEntries(
+ LINK_DIRECTORY_CATEGORIES.map((item) => [item.id, item.label]),
+) as Record<(typeof LINK_DIRECTORY_CATEGORIES)[number]["id"], string>;
+
+const CATEGORY_ORDER = {
+ resources: 0,
+ navigation: 1,
+ social: 2,
+} as const;
+
+const DIRECTORY_ITEMS = [...LINK_DIRECTORY].sort((left, right) => {
+ const categoryCompare = CATEGORY_ORDER[left.category] - CATEGORY_ORDER[right.category];
+ if (categoryCompare !== 0) {
+ return categoryCompare;
+ }
+
+ return left.label.localeCompare(right.label, "tr");
+});
+
function normalize(value: string) {
return value
.toLocaleLowerCase("tr-TR")
@@ -35,135 +54,226 @@ function normalize(value: string) {
.replace(/[\u0300-\u036f]/g, "");
}
-function getCategoryLabel(category: LinkDirectoryCategory) {
- return LINK_DIRECTORY_CATEGORIES.find((item) => item.id === category)?.label ?? "Kategori";
+function formatHref(href: string) {
+ if (href.startsWith("mailto:")) {
+ return href.replace("mailto:", "");
+ }
+
+ if (href.startsWith("/")) {
+ return `poyrazavsever.com${href}`;
+ }
+
+ return href.replace(/^https?:\/\//, "").replace(/\/$/, "");
}
export function LinksContent() {
const [activeCategory, setActiveCategory] = useState("all");
const [query, setQuery] = useState("");
- const [copiedId, setCopiedId] = useState(null);
- const filteredLinks = useMemo(() => {
+ const filteredItems = useMemo(() => {
const normalizedQuery = normalize(query.trim());
const queryTokens = normalizedQuery.split(/\s+/).filter(Boolean);
- return LINK_DIRECTORY.filter((item) => {
- const categoryMatch = activeCategory === "all" || item.category === activeCategory;
- if (!categoryMatch) return false;
- if (queryTokens.length === 0) return true;
+ return DIRECTORY_ITEMS.filter((item) => {
+ if (activeCategory !== "all" && item.category !== activeCategory) {
+ return false;
+ }
- const haystack = normalize([
- item.label,
- item.href,
- ...item.keywords,
- getCategoryLabel(item.category),
- ].join(" "));
+ if (queryTokens.length === 0) {
+ return true;
+ }
+
+ const haystack = normalize(
+ [item.label, item.href, CATEGORY_LABELS[item.category], ...item.keywords].join(" "),
+ );
return queryTokens.every((token) => haystack.includes(token));
});
}, [activeCategory, query]);
- const handleCopy = async (id: string, href: string) => {
- try {
- await navigator.clipboard.writeText(href);
- setCopiedId(id);
- setTimeout(() => {
- setCopiedId((current) => (current === id ? null : current));
- }, 1400);
- } catch {
- setCopiedId(null);
- }
- };
-
return (
-
-
-
- Link Merkezi
-
- Tüm sosyal medya, sayfa ve kaynak linklerini tek yerden ara, filtrele ve yönet.
-
-
+
-
-
-
setQuery(event.target.value)}
- placeholder="Linklerde ara... (örn: github, medium, /blog)"
- aria-label="Linklerde arama"
+
+
+
+

-
- {filteredLinks.length} / {LINK_DIRECTORY.length} sonuç
-
+
- setActiveCategory(value as CategoryFilter)}
- >
-
- {FILTER_ITEMS.map((item) => (
-
- {item.label}
-
- ))}
-
-
-
-
+
+
+
+
+
+ PA
+
+
- {filteredLinks.length === 0 ? (
-
-
- Arama kriterine uygun link bulunamadı.
-
-
- ) : (
-
- {filteredLinks.map((item) => (
-
-
-
-
-
-
-
- {item.label}
- {item.href}
-
+
+
+
+ Poyraz
+
+
+ Avsever
+
-
{getCategoryLabel(item.category)}
-
-
-
+
+
+ {SOCIAL_LINKS.map((item) => (
+
+
+ ))}
+
+
+
+ {TOP_ICON_LINKS.map((item) => (
+
- Aç
+
+
+
+
+
+
+
+ {item.label}
+
+
+ {formatHref(item.href)}
+
+
+
+
-
+ ))}
+
-
-
-
- ))}
-
- )}
+
+
+
+ Tüm Linkler
+
+
+ Sayfalar, kaynaklar ve sosyal profiller
+
+
+
+
+
+
+ setQuery(event.target.value)}
+ placeholder="Link ara... github, medium, /blog"
+ aria-label="Linklerde arama"
+ className="h-11 rounded-2xl"
+ />
+
+
+
+ {filteredItems.map((item) => (
+
+
+
+
+
+
+
+
+
+
+ {item.label}
+
+
+ {CATEGORY_LABELS[item.category]}
+
+
+
+
+ {formatHref(item.href)}
+
+
+
+
+
+
+
+
+
+ ))}
+
+ {filteredItems.length === 0 ? (
+
+
+ Seçili filtreye uygun link bulunamadı.
+
+
+ ) : null}
+
+
+
+
+
+
);
}
diff --git a/public/logo/cover.png b/public/logo/cover.png
index 9e28ddc..ad0e128 100644
Binary files a/public/logo/cover.png and b/public/logo/cover.png differ