diff --git a/app/[lang]/privacy/[slug]/page.tsx b/app/[lang]/privacy/[slug]/page.tsx
new file mode 100644
index 0000000..f6b1281
--- /dev/null
+++ b/app/[lang]/privacy/[slug]/page.tsx
@@ -0,0 +1,71 @@
+import { notFound } from "next/navigation";
+import { getDictionary } from "@/get-dictionary";
+import { i18n, type Locale } from "@/i18n-config";
+import PrivacyDetailClient from "@/components/PrivacyDetailClient";
+import { getAllPrivacyApps, getPrivacyAppBySlug } from "@/data/privacy";
+
+export async function generateStaticParams() {
+ const paths: Array<{ lang: Locale; slug: string }> = [];
+ const apps = getAllPrivacyApps();
+
+ for (const lang of i18n.locales) {
+ for (const app of apps) {
+ paths.push({ lang, slug: app.slug });
+ }
+ }
+
+ return paths;
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ lang: Locale; slug: string }>;
+}) {
+ const { lang, slug } = await params;
+ const app = getPrivacyAppBySlug(slug);
+
+ if (!app) {
+ return {
+ title: lang === "tr" ? "Gizlilik Politikası Bulunamadı" : "Privacy Policy Not Found",
+ };
+ }
+
+ const isTr = lang === "tr";
+
+ return {
+ title: isTr
+ ? `${app.name} Gizlilik Politikası & Veri Güvenliği | Ayris Tech`
+ : `${app.name} Privacy Policy & Data Security | Ayris Tech`,
+ description: app.summary[lang],
+ alternates: {
+ canonical: `/${lang}/privacy/${app.slug}`,
+ },
+ };
+}
+
+export default async function PrivacyDetailPage({
+ params,
+}: {
+ params: Promise<{ lang: Locale; slug: string }>;
+}) {
+ const { lang, slug } = await params;
+ const dict = await getDictionary(lang);
+ const app = getPrivacyAppBySlug(slug);
+
+ if (!app) {
+ notFound();
+ }
+
+ const allApps = getAllPrivacyApps();
+ const otherApps = allApps.filter((a) => a.slug !== app.slug);
+
+ return (
+
+ );
+}
diff --git a/app/[lang]/privacy/page.tsx b/app/[lang]/privacy/page.tsx
new file mode 100644
index 0000000..6454a20
--- /dev/null
+++ b/app/[lang]/privacy/page.tsx
@@ -0,0 +1,38 @@
+import { getDictionary } from "@/get-dictionary";
+import type { Locale } from "@/i18n-config";
+import PrivacyClient from "@/components/PrivacyClient";
+import { getAllPrivacyApps } from "@/data/privacy";
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ lang: Locale }>;
+}) {
+ const { lang } = await params;
+ const dict = await getDictionary(lang);
+ const isTr = lang === "tr";
+
+ return {
+ title: isTr
+ ? "Gizlilik Politikaları & Veri Güvenliği | Ayris Tech"
+ : "Privacy Policies & Data Compliance | Ayris Tech",
+ description: isTr
+ ? "Ayris Tech mobil uygulamaları, bulut servisleri ve web platformlarının şeffaf gizlilik politikaları, KVKK ve GDPR uyum ilkeleri."
+ : "Transparent privacy policies, hardware permissions, and data protection compliance for all Ayris Tech applications and cloud services.",
+ alternates: {
+ canonical: `/${lang}/privacy`,
+ },
+ };
+}
+
+export default async function PrivacyPage({
+ params,
+}: {
+ params: Promise<{ lang: Locale }>;
+}) {
+ const { lang } = await params;
+ const dict = await getDictionary(lang);
+ const apps = getAllPrivacyApps();
+
+ return ;
+}
diff --git a/app/sitemap.ts b/app/sitemap.ts
index b929eef..f64ff7a 100644
--- a/app/sitemap.ts
+++ b/app/sitemap.ts
@@ -48,6 +48,18 @@ export default async function sitemap(): Promise {
changeFrequency: 'monthly',
priority: 0.8,
},
+ {
+ url: `${baseUrl}/en/privacy`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly',
+ priority: 0.7,
+ },
+ {
+ url: `${baseUrl}/tr/privacy`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly',
+ priority: 0.7,
+ },
];
try {
@@ -111,7 +123,30 @@ export default async function sitemap(): Promise {
// Ignore if blog fetching fails
}
- return [...staticUrls, ...expertiseUrls, ...workUrls, ...blogUrls];
+ // Privacy App Pages
+ let privacyUrls: MetadataRoute.Sitemap = [];
+ try {
+ const { getAllPrivacyApps } = await import('@/data/privacy');
+ const privacyApps = getAllPrivacyApps();
+ privacyUrls = privacyApps.flatMap((app) => [
+ {
+ url: `${baseUrl}/en/privacy/${app.slug}`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly',
+ priority: 0.6,
+ },
+ {
+ url: `${baseUrl}/tr/privacy/${app.slug}`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly',
+ priority: 0.6,
+ },
+ ]);
+ } catch (err) {
+ // Ignore if privacy fetching fails
+ }
+
+ return [...staticUrls, ...expertiseUrls, ...workUrls, ...blogUrls, ...privacyUrls];
} catch(e) {
return staticUrls;
}
diff --git a/components/Footer.tsx b/components/Footer.tsx
index a2a1fdc..34796cb 100644
--- a/components/Footer.tsx
+++ b/components/Footer.tsx
@@ -103,7 +103,7 @@ export default function Footer({ lang, dict }: { lang: Locale; dict: any }) {
© 2026 Ayris Tech — {dict.footer.rights}
-
+
{dict.footer.privacy}
diff --git a/components/PrivacyClient.tsx b/components/PrivacyClient.tsx
new file mode 100644
index 0000000..56aec8d
--- /dev/null
+++ b/components/PrivacyClient.tsx
@@ -0,0 +1,358 @@
+"use client";
+
+import { useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import Link from "next/link";
+import Header from "@/components/Header";
+import Footer from "@/components/Footer";
+import type { Locale } from "@/i18n-config";
+import type { PrivacyApp } from "@/data/privacy";
+
+const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
+
+export default function PrivacyClient({
+ lang,
+ dict,
+ apps,
+}: {
+ lang: Locale;
+ dict: any;
+ apps: PrivacyApp[];
+}) {
+ const [selectedPlatform, setSelectedPlatform] = useState
("ALL");
+ const [searchQuery, setSearchQuery] = useState("");
+
+ const isTr = lang === "tr";
+
+ const platforms = [
+ { id: "ALL", label: isTr ? "TÜM UYGULAMALAR" : "ALL PRODUCTS" },
+ { id: "iOS", label: "iOS" },
+ { id: "Android", label: "ANDROID" },
+ { id: "Web", label: "WEB & CLOUD" },
+ { id: "API", label: "API & SDK" },
+ ];
+
+ const filteredApps = apps.filter((app) => {
+ const matchesPlatform =
+ selectedPlatform === "ALL" ||
+ app.platforms.some((p) =>
+ selectedPlatform === "Web"
+ ? p === "Web" || p === "Cloud" || p === "SaaS"
+ : selectedPlatform === "API"
+ ? p === "API" || p === "SDK"
+ : p === selectedPlatform
+ );
+
+ const matchesSearch =
+ searchQuery.trim() === "" ||
+ app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ app.summary[lang].toLowerCase().includes(searchQuery.toLowerCase()) ||
+ app.category[lang].toLowerCase().includes(searchQuery.toLowerCase());
+
+ return matchesPlatform && matchesSearch;
+ });
+
+ const corePrinciples = [
+ {
+ code: "SEC-01",
+ title: isTr ? "Sıfır Veri Satışı" : "Zero Data Monetization",
+ desc: isTr
+ ? "Kişisel ve kurumsal verileriniz hiçbir reklam ağı veya üçüncü taraf veri simsarı ile paylaşılmaz, satılmaz veya kiralanmaz."
+ : "Your data is strictly yours. We never sell, rent, or trade your personal records to third-party ad brokers.",
+ badge: isTr ? "KESİN İLKE" : "CORE PRINCIPLE",
+ },
+ {
+ code: "SEC-02",
+ title: isTr ? "Uçtan Uca Şifreleme" : "End-to-End Encryption",
+ desc: isTr
+ ? "Tüm veri akışları TLS 1.3 protokolü ile aktarılır, veri tabanlarımızda AES-256 seviyesinde şifrelenerek depolanır."
+ : "All communication is locked with TLS 1.3 and stored with military-grade AES-256 encryption at rest.",
+ badge: "AES-256",
+ },
+ {
+ code: "SEC-03",
+ title: isTr ? "KVKK & GDPR Uyumu" : "Global Compliance",
+ desc: isTr
+ ? "Tüm ürünlerimiz 6698 sayılı KVKK ve Avrupa Birliği GDPR standartları ile tam uyumlu olarak geliştirilir."
+ : "Engineered from day one in strict compliance with EU GDPR and Turkish KVKK data protection statutes.",
+ badge: "ISO/GDPR",
+ },
+ {
+ code: "SEC-04",
+ title: isTr ? "Anında Veri Silme" : "Instant Data Deletion",
+ desc: isTr
+ ? "Uygulama içi ayarlardan veya tek tıkla destek e-postası ile tüm verilerinizi geri döndürülemez biçimde silebilirsiniz."
+ : "Permanent, irrevocable deletion of your account and files on demand directly in-app or via our security desk.",
+ badge: isTr ? "TAM KONTROL" : "FULL CONTROL",
+ },
+ ];
+
+ return (
+
+
+
+
+ {/* Page Header */}
+
+
+
+
+ {isTr ? "AYRİS TECH — GİZLİLİK VE VERİ MERKEZİ" : "AYRIS TECH — PRIVACY & COMPLIANCE HUB"}
+
+
+
+
+ {isTr ? "GİZLİLİK" : "PRIVACY"}
+ {isTr ? "POLİTİKALARI" : "POLICIES"}
+
+
+
+ {isTr
+ ? "Ayris Tech bünyesindeki tüm mobil uygulamalar, bulut platformları ve geliştirici servislerinin veri işleme ilkelerine, izinlerine ve kullanıcı hakları bildirimlerine buradan ulaşabilirsiniz."
+ : "Access the transparent privacy policies, hardware permission scopes, and data subject rights for all Ayris Tech applications, cloud services, and developer tools."}
+
+
+
+ {/* Core Principles Matrix */}
+
+
+
+ {isTr ? "01 / GÜVENLİK VE GİZLİLİK STANDARTLARIMIZ" : "01 / SECURITY & PRIVACY BENCHMARKS"}
+
+
+ ISO 27001 · KVKK · GDPR
+
+
+
+
+ {corePrinciples.map((item, idx) => (
+
+
+
+ {item.code}
+
+ {item.badge}
+
+
+
+ {item.title}
+
+
+ {item.desc}
+
+
+
+ ))}
+
+
+
+ {/* Apps Privacy Directory */}
+
+
+
+
+ {isTr ? "02 / UYGULAMA GİZLİLİK REHBERİ" : "02 / APPLICATION DIRECTORY"}
+
+
+ {isTr ? "UYGULAMALARIMIZ VE SERVİSLERİMİZ" : "APPLICATIONS & PLATFORMS"}
+
+
+
+ {/* Filter Tabs */}
+
+ {platforms.map((tab) => (
+ setSelectedPlatform(tab.id)}
+ className={`font-mono text-[10px] tracking-[0.15em] uppercase px-3.5 py-2 border transition-all cursor-pointer ${
+ selectedPlatform === tab.id
+ ? "bg-[#0A0A0A] text-[#F4F0E8] border-[#0A0A0A] font-bold"
+ : "bg-[#EDE8E0] text-[#6A6460] border-[#C8C2B8] hover:border-[#0A0A0A] hover:text-[#0A0A0A]"
+ }`}
+ >
+ {tab.label}
+
+ ))}
+
+
+
+ {/* Search Box */}
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full bg-[#EDE8E0] border border-[#C8C2B8] focus:border-[#0A0A0A] px-4 py-3.5 font-mono text-[13px] text-[#0A0A0A] placeholder-[#A0998E] outline-none transition-colors"
+ />
+ {searchQuery && (
+ setSearchQuery("")}
+ className="absolute right-4 top-1/2 -translate-y-1/2 font-mono text-[11px] text-[#A0998E] hover:text-[#0A0A0A]"
+ >
+ {isTr ? "TEMİZLE" : "CLEAR"}
+
+ )}
+
+
+
+ {/* Apps Grid */}
+
+
+ {filteredApps.map((app, idx) => (
+
+
+ {/* Top Row: Icon, Category, Platforms */}
+
+
+
+ {app.icon}
+
+
+
+ {app.category[lang]}
+
+
+ {app.name}
+
+
+
+
+
+ {app.platforms.map((p) => (
+
+ {p}
+
+ ))}
+
+
+
+ {/* Summary */}
+
+ {app.summary[lang]}
+
+
+ {/* Highlights Badges */}
+
+ {app.highlights[lang].slice(0, 4).map((h, i) => (
+
+
+ {h.label}
+
+
+ {h.value}
+
+
+ ))}
+
+
+
+ {/* Bottom Action */}
+
+
+ {isTr ? "Son Güncelleme:" : "Updated:"} {app.lastUpdated[lang]}
+
+
+ {isTr ? "POLİTİKAYI İNCELE" : "VIEW POLICY"}
+
+
+
+
+
+
+
+ ))}
+
+
+ {filteredApps.length === 0 && (
+
+
+ {isTr ? "Kayıt Bulunamadı" : "No Applications Found"}
+
+
+ {isTr
+ ? "Aradığınız kriterlere uygun uygulama gizlilik politikası bulunamadı."
+ : "No application policies matched your filter criteria."}
+
+
+ )}
+
+
+
+ {/* Data Subject Request & Deletion Box */}
+
+
+
+
+ {isTr ? "KVKK MADDE 11 & GDPR HAK TALEPLERİ" : "DATA SUBJECT RIGHTS & ERASURE"}
+
+
+ {isTr ? "VERİLERİNİZİ SİLMEK VEYA BİLGİ ALMAK MI İSTİYORSUNUZ?" : "NEED TO ERASE YOUR DATA OR INQUIRE?"}
+
+
+ {isTr
+ ? "Herhangi bir uygulamamızdaki hesabınızın, fişlerinizin, dava kayıtlarınızın veya kişisel verilerinizin kalıcı olarak silinmesini doğrudan talep edebilirsiniz."
+ : "Request permanent deletion of your account records, telemetry data, or stored files across any of our applications instantly."}
+
+
+
+
+ {isTr ? "HAK / SİLME TALEBİ GÖNDER" : "SUBMIT ERASURE REQUEST"}
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/PrivacyDetailClient.tsx b/components/PrivacyDetailClient.tsx
new file mode 100644
index 0000000..d2862c8
--- /dev/null
+++ b/components/PrivacyDetailClient.tsx
@@ -0,0 +1,344 @@
+"use client";
+
+import { motion, useScroll, useSpring } from "framer-motion";
+import Link from "next/link";
+import Header from "@/components/Header";
+import Footer from "@/components/Footer";
+import type { Locale } from "@/i18n-config";
+import type { PrivacyApp } from "@/data/privacy";
+
+const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
+
+export default function PrivacyDetailClient({
+ lang,
+ dict,
+ app,
+ otherApps,
+}: {
+ lang: Locale;
+ dict: any;
+ app: PrivacyApp;
+ otherApps: PrivacyApp[];
+}) {
+ const { scrollYProgress } = useScroll();
+ const scaleX = useSpring(scrollYProgress, {
+ stiffness: 100,
+ damping: 30,
+ restDelta: 0.001,
+ });
+
+ const isTr = lang === "tr";
+
+ return (
+
+ {/* Scroll indicator */}
+
+
+
+
+
+ {/* Back Link */}
+
+
+
+
+
+
+ {isTr ? "TÜM GİZLİLİK POLİTİKALARI" : "ALL PRIVACY POLICIES"}
+
+
+
+ {/* Hero Header */}
+
+
+
+
+ {app.icon}
+
+
+
+
+ {app.category[lang]}
+
+
+ {app.version}
+
+
+
+ {app.name}
+
+
+
+
+
+
+ {app.platforms.map((p) => (
+
+ {p}
+
+ ))}
+
+
+ {isTr ? "Yürürlük / Güncelleme:" : "Effective / Last Updated:"} {app.lastUpdated[lang]}
+
+
+
+
+
+ {app.summary[lang]}
+
+
+ {/* Key Highlights */}
+
+ {app.highlights[lang].map((h, i) => (
+
+
+ {h.label}
+
+
+ {h.value}
+
+
+ ))}
+
+
+
+ {/* Content Layout: Sticky Sidebar + Main Sections */}
+
+ {/* Sidebar Navigation */}
+
+
+ {/* Main Legal Content */}
+
+ {/* System Permissions (if applicable) */}
+ {app.permissions.length > 0 && (
+
+
+
+
+ {isTr ? "Cihaz İzinleri ve Donanım Erişimi" : "Device Permissions & Hardware Access"}
+
+
+
+
+ {isTr
+ ? `${app.name} uygulaması, yalnızca temel işlevlerini kusursuz yerine getirebilmek için işletim sisteminizden (iOS / Android) aşağıdaki açık izinleri talep eder:`
+ : `${app.name} requests explicit permissions from your operating system (iOS / Android) solely to perform core functionality:`}
+
+
+
+ {app.permissions.map((perm, idx) => (
+
+
+
+ {perm.name[lang]}
+
+
+ {perm.required
+ ? isTr
+ ? "ZORUNLU"
+ : "REQUIRED"
+ : isTr
+ ? "İSTEĞE BAĞLI"
+ : "OPTIONAL"}
+
+
+
+ {perm.reason[lang]}
+
+
+ ))}
+
+
+ )}
+
+ {/* Sections */}
+ {app.sections.map((section, idx) => (
+
+
+
+ #{String(idx + 1).padStart(2, "0")}
+
+
+ {section.title[lang]}
+
+
+
+
+ {section.content[lang]}
+
+
+ {section.bullets && (
+
+ {section.bullets[lang].map((bullet, bIdx) => (
+
+ •
+ {bullet}
+
+ ))}
+
+ )}
+
+ ))}
+
+ {/* Data Deletion and Erasure Request Section */}
+
+
+ {isTr ? "KULLANICI VERİ KONTROLÜ" : "USER DATA CONTROL"}
+
+
+ {isTr
+ ? `${app.name} Verilerinizi Kalıcı Olarak Silin`
+ : `Permanently Erase Your ${app.name} Data`}
+
+
+ {isTr
+ ? "Uygulama içerisinden hesabınızı silebilir veya aşağıdaki buton aracılığıyla doğrudan veri silme ve KVKK hak talebinde bulunabilirsiniz. Talebiniz en geç 72 saat içinde işleme alınır."
+ : "You can delete your account within the app or trigger a formal GDPR/KVKK erasure request by emailing our automated privacy handler. All records are purged within 72 hours."}
+
+
+
+ {isTr ? "VERİ SİLME TALEBİ OLUŞTUR" : "REQUEST DATA DELETION"}
+
+
+
+
+
+
+
+
+
+ {/* Other Applications Footer Switcher */}
+ {otherApps.length > 0 && (
+
+
+ {isTr ? "DİĞER UYGULAMA GİZLİLİK POLİTİKALARI" : "OTHER APPLICATION POLICIES"}
+
+
+ {otherApps.map((other) => (
+
+
+ {other.icon}
+
+
+
+ {other.name}
+
+
+ {other.category[lang]}
+
+
+
+ ))}
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/data/privacy.ts b/data/privacy.ts
new file mode 100644
index 0000000..ea25ce4
--- /dev/null
+++ b/data/privacy.ts
@@ -0,0 +1,528 @@
+export interface PrivacyPermission {
+ name: { tr: string; en: string };
+ reason: { tr: string; en: string };
+ required: boolean;
+}
+
+export interface PrivacySection {
+ id: string;
+ title: { tr: string; en: string };
+ content: { tr: string; en: string };
+ bullets?: { tr: string[]; en: string[] };
+}
+
+export interface PrivacyApp {
+ slug: string;
+ name: string;
+ icon: string;
+ category: { tr: string; en: string };
+ platforms: string[];
+ version: string;
+ lastUpdated: { tr: string; en: string };
+ summary: { tr: string; en: string };
+ contactEmail: string;
+ highlights: {
+ tr: { label: string; value: string }[];
+ en: { label: string; value: string }[];
+ };
+ permissions: PrivacyPermission[];
+ sections: PrivacySection[];
+}
+
+export const privacyApps: PrivacyApp[] = [
+ {
+ slug: "fisio",
+ name: "Fişio",
+ icon: "🧾",
+ category: {
+ tr: "Mobil Uygulama & Muhasebe / Finans",
+ en: "Mobile App & Accounting / Finance"
+ },
+ platforms: ["iOS", "Android"],
+ version: "v1.2.0",
+ lastUpdated: {
+ tr: "17 Ağustos 2026",
+ en: "August 17, 2026"
+ },
+ summary: {
+ tr: "Fişio, fiş ve faturalarınızı yapay zeka ile otomatik dijitalleştiren, harcama, KDV ve muhasebe raporlaması sağlayan akıllı mobil uygulamadır. Verileriniz KVKK ve GDPR standartlarına uygun olarak en üst düzeyde korunur.",
+ en: "Fişio is an AI-powered smart receipt and invoice digitization mobile application for expense, VAT, and accounting reporting. Your personal and financial data is protected under strict KVKK and GDPR compliance standards."
+ },
+ contactEmail: "info@ayris.tech",
+ highlights: {
+ tr: [
+ { label: "Veri İletimi", value: "HTTPS / TLS 1.3" },
+ { label: "Şifre Güvenliği", value: "Bcrypt Kriptografik Hash" },
+ { label: "Üçüncü Tarafa Satış", value: "Kesinlikle Yok" },
+ { label: "Kamera & Galeri Erişimi", value: "Yalnızca Fiş Taramada" }
+ ],
+ en: [
+ { label: "Data Transit", value: "HTTPS / TLS 1.3" },
+ { label: "Password Security", value: "Bcrypt Cryptographic Hash" },
+ { label: "Data Selling", value: "Strictly Never" },
+ { label: "Camera & Photos Access", value: "Receipt Scanning Only" }
+ ]
+ },
+ permissions: [
+ {
+ name: { tr: "Kamera İzni (Camera)", en: "Camera Permission" },
+ reason: {
+ tr: "Fiş ve faturaların fotoğrafını uygulama içerisinden anında çekip OCR analizine gönderebilmeniz için kullanılır. Arka planda habersiz kayıt kesinlikle yapılmaz.",
+ en: "Used solely for capturing photos of physical receipts and invoices directly within the app for OCR processing. No background recording is ever performed."
+ },
+ required: true
+ },
+ {
+ name: { tr: "Fotoğraf Galerisi İzni (Photo Library)", en: "Photo Library Permission" },
+ reason: {
+ tr: "Önceden çekilmiş veya cihazınızda kayıtlı olan fiş/belge fotoğraflarını seçip yükleyebilmeniz için kullanılır.",
+ en: "Used to let you select and upload previously saved receipt or invoice images from your photo library."
+ },
+ required: false
+ }
+ ],
+ sections: [
+ {
+ id: "controller",
+ title: {
+ tr: "1. Veri Sorumlusu ve Hizmet Tanımı",
+ en: "1. Data Controller & Service Scope"
+ },
+ content: {
+ tr: "6698 sayılı Kişisel Verilerin Korunması Kanunu ('KVKK') ve Avrupa Birliği Genel Veri Koruma Tüzüğü ('GDPR') uyarınca, Fişio mobil uygulaması üzerinden işlenen kişisel verileriniz bakımından Veri Sorumlusu Ayris Dev / Ayris Tech'tir (info@ayris.tech).",
+ en: "Pursuant to the Turkish Law on the Protection of Personal Data ('KVKK') and the EU General Data Protection Regulation ('GDPR'), the Data Controller for personal data processed through the Fişio mobile app is Ayris Dev / Ayris Tech (info@ayris.tech)."
+ }
+ },
+ {
+ id: "collected-data",
+ title: {
+ tr: "2. Toplanan Bilgiler ve Veri Türleri",
+ en: "2. Information We Collect"
+ },
+ content: {
+ tr: "Fişio uygulamasını kullandığınızda hizmetin işleyişi için aşağıdaki bilgiler toplanabilir ve işlenebilir:",
+ en: "When you interact with Fişio, the following categories of data may be processed to deliver our service:"
+ },
+ bullets: {
+ tr: [
+ "Hesap ve Kimlik Bilgileri: E-posta adresi, ad ve soyad, kriptografik karma/hash formatında saklanan parola, kullanıcı rolü (Mali Müşavir / SMM veya Müşteri).",
+ "Şirket ve Fatura Bilgileri: Şirket unvanı, Vergi Kimlik Numarası (VKN) / T.C. Kimlik Numarası.",
+ "Fiş ve Harcama Verileri: Fiş ve fatura fotoğrafları, fişten çıkarılan firma adı, fiş numarası, tarih, ürün/hizmet kalemleri, KDV oranları, tutarlar ve muhasebe kategori kodları.",
+ "Cihaz ve Teşhis Verileri: Hata ve çökme logları, temel cihaz bilgisi (Hizmet kalitesini artırmak amacıyla)."
+ ],
+ en: [
+ "Account & Identity Information: Email address, full name, securely hashed password (Bcrypt), user role (Certified Public Accountant / SMM or Client).",
+ "Company & Tax Information: Company business name, Tax ID (VKN) / National ID number.",
+ "Receipt & Expense Data: Receipt and invoice images, extracted merchant names, receipt numbers, dates, line items, VAT rates, totals, and accounting category codes.",
+ "Device & Diagnostic Data: Error and crash logs, basic device diagnostics for quality assurance."
+ ]
+ }
+ },
+ {
+ id: "purposes",
+ title: {
+ tr: "3. Bilgilerin Kullanım Amaçları",
+ en: "3. Purposes of Processing"
+ },
+ content: {
+ tr: "Toplanan veriler yalnızca aşağıdaki meşru amaçlar doğrultusunda işlenir:",
+ en: "Your personal data is processed solely for the following explicit purposes:"
+ },
+ bullets: {
+ tr: [
+ "OCR ve Fiş Analizi: Fiş fotoğraflarındaki metinlerin Optik Karakter Tanıma (OCR) ve yapay zeka teknolojileriyle okunup harcama kalemlerine ve muhasebe kodlarına ayrıştırılması.",
+ "Muhasebe & Raporlama: Fişlerin ait olduğu şirket altında listelenmesi, toplam giderlerin ve KDV tutarlarının hesaplanması, Excel/CSV formatında dışa aktarılması.",
+ "Mali Müşavir (SMM) - Müşteri İletişimi: Mali müşavirlerin kendilerine bağlı müşterilerin fişlerini ve şirket verilerini inceleyebilmesi.",
+ "Hesap Güvenliği: Yetkisiz erişimleri engellemek ve güvenli oturum yönetimini sağlamak."
+ ],
+ en: [
+ "OCR & Receipt Parsing: Extracting text and structured items from receipt images using OCR and AI parsing models into line items and accounting codes.",
+ "Accounting & Reporting: Aggregating receipts under relevant company profiles, calculating total expenditures and VAT sums, and exporting to Excel/CSV format.",
+ "Accountant - Client Collaboration: Enabling certified accountants to view, categorize, and approve receipts for associated client companies.",
+ "Account Security: Preventing unauthorized access, enforcing authentication integrity, and managing sessions securely."
+ ]
+ }
+ },
+ {
+ id: "third-parties",
+ title: {
+ tr: "4. Verilerin Paylaşımı ve Üçüncü Taraf Hizmetler",
+ en: "4. Third-Party Service Providers"
+ },
+ content: {
+ tr: "Fişio, kişisel verilerinizi asla üçüncü şahıslara satmaz veya ticari reklam hedeflemesi amacıyla kullanmaz. Veriler yalnızca uygulamanın temel işlevlerini yerine getirmek amacıyla güvenilir altyapı sağlayıcılarıyla şifrelenmiş kanallar (HTTPS/TLS) üzerinden paylaşılır:",
+ en: "Fişio never sells or monetizes your data with third-party advertisers. Data is exchanged solely through encrypted channels (HTTPS/TLS) with verified infrastructure providers to deliver essential features:"
+ },
+ bullets: {
+ tr: [
+ "Yapay Zeka & OCR Sağlayıcıları (Google Cloud / Gemini API): Fiş görselleri yalnızca metin ayrıştırma işlemi için şifreli olarak işlenir; yapay zeka modellerini eğitmek amacıyla genel kullanıma açılmaz.",
+ "Bulut Depolama & CDN Sağlayıcıları (BunnyCDN): Fiş fotoğraflarının güvenli ve hızlı bir şekilde sunulması için kullanılır.",
+ "Veritabanı Sağlayıcıları: Şifrelenmiş ve izole edilmiş sunucularda güvenli veri tabanı barındırma."
+ ],
+ en: [
+ "AI & OCR Engine (Google Cloud / Gemini API): Receipt images are processed securely for text extraction only; they are not used to train public foundation models.",
+ "Cloud Storage & CDN (BunnyCDN): High-speed, secure encrypted delivery of receipt photos.",
+ "Cloud Database: Isolated and encrypted relational database hosting."
+ ]
+ }
+ },
+ {
+ id: "security",
+ title: {
+ tr: "5. Veri Güvenliği ve Saklama İlkeleri",
+ en: "5. Data Security & Storage Principles"
+ },
+ content: {
+ tr: "Verilerinizin güvenliği için endüstri standardı güvenlik önlemleri uygulanmaktadır:",
+ en: "We deploy industry-standard technical measures to ensure strict confidentiality and data integrity:"
+ },
+ bullets: {
+ tr: [
+ "Tüm veri iletimi HTTPS / TLS 1.3 protokolü ile uçtan uca şifrelenir.",
+ "Şifreler tek yönlü güçlü algoritmalarla (Bcrypt) hash'lenerek saklanır; şifreniz hiçbir yönetici veya çalışan tarafından açık metin olarak görülemez.",
+ "Yetkisiz veri erişimlerine karşı rol tabanlı erişim kontrolü (Role-Based Access Control) uygulanır."
+ ],
+ en: [
+ "All network communication is end-to-end encrypted with HTTPS / TLS 1.3.",
+ "Passwords are salted and cryptographically hashed with Bcrypt; plaintext passwords are never accessible to anyone.",
+ "Strict Role-Based Access Control (RBAC) prevents cross-tenant data leakage."
+ ]
+ }
+ },
+ {
+ id: "retention-deletion",
+ title: {
+ tr: "6. Veri Saklama ve Kalıcı Silme Politikası",
+ en: "6. Data Retention & Erasure Policy"
+ },
+ content: {
+ tr: "Kullanıcı Kontrolü: Kullanıcılar yükledikleri herhangi bir fişi, şirketi veya müşteri hesabını uygulama içinden diledikleri zaman silebilirler. Bir fiş veya şirket silindiğinde, ilişkili tüm veriler ve görseller veritabanımızdan ve depolama sunucularımızdan kalıcı olarak kaldırılır. Hesabınızın ve tüm ilişkili verilerinizin tamamen silinmesini talep etmek için uygulama üzerinden veya info@ayris.tech adresi üzerinden bizimle iletişime geçebilirsiniz.",
+ en: "User Control: Users can delete any uploaded receipt, company profile, or client account from within the app at any time. When a receipt or profile is erased, all affiliated records and images are permanently purged from our database and storage CDN. You can also trigger a complete account wipe by contacting info@ayris.tech."
+ }
+ },
+ {
+ id: "children",
+ title: {
+ tr: "7. Çocukların Gizliliği",
+ en: "7. Children's Privacy"
+ },
+ content: {
+ tr: "Fişio iş ve muhasebe yönetimi amacıyla geliştirilmiş bir uygulamadır ve 13 yaşın altındaki çocuklara yönelik değildir. 13 yaşından küçük kişilerden bilerek kişisel veri toplanmaz.",
+ en: "Fişio is designed for professional business and accounting management and is not directed to individuals under 13 years of age. We do not knowingly collect personal information from children."
+ }
+ },
+ {
+ id: "rights",
+ title: {
+ tr: "8. Haklarınız (KVKK & GDPR Kapsamında)",
+ en: "8. Data Subject Rights (KVKK & GDPR)"
+ },
+ content: {
+ tr: "İlgili veri koruma kanunları (KVKK / GDPR) uyarınca hakkınızda hangi verilerin işlendiğini öğrenme, hatalı verilerin düzeltilmesini talep etme, verilerinizin silinmesini veya anonim hale getirilmesini isteme ve veri işlemeye verdiğiniz açık rızayı dilediğiniz an geri çekme hakkına sahipsiniz. Başvurularınız için: info@ayris.tech",
+ en: "Under KVKK and GDPR regulations, you maintain the right to access, rectify, export, anonymize, or permanently erase your personal data at any time. For requests: info@ayris.tech"
+ }
+ }
+ ]
+ },
+ {
+ slug: "durusma-takvimi",
+ name: "Duruşma Takvimi",
+ icon: "⚖️",
+ category: {
+ tr: "Mobil & Web · Hukuk Teknolojileri",
+ en: "Mobile & Web · Legal Tech"
+ },
+ platforms: ["iOS", "Android", "Web"],
+ version: "v2.1.0",
+ lastUpdated: {
+ tr: "17 Şubat 2026",
+ en: "February 17, 2026"
+ },
+ summary: {
+ tr: "Duruşma Takvimi, avukatlar ve hukuk büroları için geliştirilmiş duruşma, kesin süre, tebligat ve görev takip platformudur. Avukatlık meslek sırrı ve müvekkil gizliliği ilkeleri uyarınca tasarlanmıştır.",
+ en: "Duruşma Takvimi is a specialized court hearing, statutory deadline, and case agenda management platform tailored for attorneys and legal practices, strictly respecting attorney-client privilege."
+ },
+ contactEmail: "durusma@ayristech.com",
+ highlights: {
+ tr: [
+ { label: "Müvekkil Gizliliği", value: "Avukatlık Kanunu Uyumlu" },
+ { label: "Takvim İzni", value: "Yalnızca Duruşma Senkronizasyonu" },
+ { label: "Veri Şifreleme", value: "Çift Katmanlı AES-256" },
+ { label: "Yedekleme Güvenliği", value: "İzole Güvenli Veri Merkezleri" }
+ ],
+ en: [
+ { label: "Client Confidentiality", value: "Legal Privilege Compliant" },
+ { label: "Calendar Permission", value: "Hearing Sync Only" },
+ { label: "Encryption", value: "Dual-Layer AES-256" },
+ { label: "Data Residency", value: "Compliant Sovereign Hosting" }
+ ]
+ },
+ permissions: [
+ {
+ name: { tr: "Takvim Erişimi (Calendar Access)", en: "Calendar Access" },
+ reason: {
+ tr: "Duruşma ve keşif tarihlerinizi cihazınızın yerel takvimine eklemek ve çakışmaları önlemek için kullanılır.",
+ en: "Used to sync hearing and court dates into your device's native calendar and prevent schedule conflicts."
+ },
+ required: false
+ },
+ {
+ name: { tr: "Bildirimler (Notifications)", en: "Notifications" },
+ reason: {
+ tr: "Yaklaşan duruşma günleri, kesin süreler ve mazeret hatırlatmaları için kritik bildirimler gönderir.",
+ en: "Used for critical alerts regarding upcoming hearings, statutory deadlines, and court reminders."
+ },
+ required: true
+ },
+ {
+ name: { tr: "Biyometrik Kimlik Doğrulama (FaceID / TouchID)", en: "Biometric Authentication" },
+ reason: {
+ tr: "Dava ve dosya gizliliğinizi korumak için uygulamayı cihazınızın parmak izi veya yüz tanıma özelliğiyle kilitlemenizi sağlar.",
+ en: "Allows locking the app with your device's FaceID or fingerprint to secure sensitive case files."
+ },
+ required: false
+ }
+ ],
+ sections: [
+ {
+ id: "controller",
+ title: {
+ tr: "1. Veri Sorumlusu ve Mesleki Gizlilik",
+ en: "1. Data Controller & Legal Confidentiality"
+ },
+ content: {
+ tr: "Duruşma Takvimi uygulamasının veri sorumlusu Ayris Tech'tir. Uygulama, 1136 sayılı Avukatlık Kanunu'nda düzenlenen meslek sırrı ve müvekkil mahremiyeti ilkeleri gözetilerek mimarilenmiştir. Kaydedilen dosya numaraları ve duruşma notları hiçbir şekilde üçüncü şahıslara açılmaz.",
+ en: "The data controller is Ayris Tech. Duruşma Takvimi is architected strictly adhering to legal confidentiality standards and professional attorney secrecy codes. Case identifiers and notes are never exposed or disclosed to any unauthorized third party."
+ }
+ },
+ {
+ id: "collected-data",
+ title: {
+ tr: "2. Toplanan ve İşlenen Veriler",
+ en: "2. Data Processed"
+ },
+ content: {
+ tr: "Uygulama kapsamında yalnızca avukatlık ajandası işlevlerinin yürütülmesi için zorunlu olan veriler işlenir:",
+ en: "Only data strictly necessary to manage court schedules and legal reminders is processed:"
+ },
+ bullets: {
+ tr: [
+ "Kullanıcı Profil Verileri: Baro levha no (isteğe bağlı), e-posta, ad-soyad, telefon numarası.",
+ "Duruşma & Dosya Kayıtları: Mahkeme adı, dosya esas no, duruşma saati, duruşma salonu, taraf kısaltmaları ve hatırlatıcı notlar.",
+ "Cihaz Bilgisi & İzin Durumları: Bildirim jetonu (FCM/APNs), işletim sistemi sürümü."
+ ],
+ en: [
+ "User Profile: Bar Association Registration No (optional), email, name, phone number.",
+ "Hearing & Docket Details: Court name, docket number, hearing time, courtroom, party initials, reminder notes.",
+ "Device & Notification Tokens: Push notification tokens (APNs/FCM), operating system details."
+ ]
+ }
+ },
+ {
+ id: "security",
+ title: {
+ tr: "3. Veri Güvenliği ve Şifreleme Standartları",
+ en: "3. Security & Cryptographic Standards"
+ },
+ content: {
+ tr: "Tüm veri akışları TLS 1.3 ile şifrelenir; veri tabanında saklanan duruşma detayları kurumsal seviyede AES-256 şifreleme altındadır. Veri tabanı yedekleri günlük olarak izole ve şifreli ortamlarda oluşturulur.",
+ en: "All communication streams are secured with TLS 1.3. Rest data is stored with AES-256 encryption. Database snapshots and backups are isolated in encrypted sovereign environments."
+ }
+ },
+ {
+ id: "erasure",
+ title: {
+ tr: "4. Hesap ve Veri Silme Talepleri",
+ en: "4. Account & Record Deletion"
+ },
+ content: {
+ tr: "Kullanıcılar diledikleri zaman hesaplarını ve ilişkili tüm duruşma verilerini kalıcı olarak silebilir. Silinen veriler derhal aktif sistemlerden kaldırılır ve yedeklerden azami 15 gün içinde arındırılır.",
+ en: "Users may permanently delete their accounts and all affiliated court agendas at any time. Erased records are purged immediately from production systems and within 15 days from backup stores."
+ }
+ }
+ ]
+ },
+ {
+ slug: "openinary",
+ name: "Openinary",
+ icon: "⚡",
+ category: {
+ tr: "Geliştirici API & Medya Depolama",
+ en: "Developer API & Cloud Media"
+ },
+ platforms: ["API", "SDK", "Cloud"],
+ version: "v3.0.0",
+ lastUpdated: {
+ tr: "17 Şubat 2026",
+ en: "February 17, 2026"
+ },
+ summary: {
+ tr: "Openinary, modern web ve mobil uygulamalar için yüksek hızlı görsel/dosya optimizasyonu, dönüştürme ve CDN dağıtımı sağlayan bağımsız bir bulut medya API servisidir.",
+ en: "Openinary is a high-throughput asset optimization, transformation, and edge CDN distribution platform engineered for modern web and mobile applications."
+ },
+ contactEmail: "api@ayristech.com",
+ highlights: {
+ tr: [
+ { label: "API Yetkilendirme", value: "Bearer Token / İki Katmanlı Key" },
+ { label: "Medya İzolasyonu", value: "Özel Müşteri Dizinleri (Private Buckets)" },
+ { label: "CDN Güvenliği", value: "DDoS Korumalı Global Edge" },
+ { label: "Veri Saklama Süresi", value: "Müşteri Tarafından Yapılandırılabilir" }
+ ],
+ en: [
+ { label: "Authentication", value: "Bearer Token / Scoped Keys" },
+ { label: "Asset Isolation", value: "Private Tenant Buckets" },
+ { label: "CDN Security", value: "DDoS Protected Global Edge" },
+ { label: "Retention Policy", value: "Tenant Configurable" }
+ ]
+ },
+ permissions: [],
+ sections: [
+ {
+ id: "controller",
+ title: {
+ tr: "1. Hizmet Tanımı ve Veri Sorumlusu",
+ en: "1. Service Scope & Data Controller"
+ },
+ content: {
+ tr: "Openinary API hizmetinde Ayris Tech, yüklenen dosya ve içeriklerin barındırılması bakımından 'Veri İşleyen' (Data Processor), API hesap sahiplerinin üyelik ve fatura verileri bakımından ise 'Veri Sorumlusu' (Data Controller) sıfatını haizdir.",
+ en: "In the Openinary API service, Ayris Tech acts as a 'Data Processor' regarding customer uploaded media files and assets, and as a 'Data Controller' regarding account holder profile and billing details."
+ }
+ },
+ {
+ id: "collected-data",
+ title: {
+ tr: "2. İşlenen Veriler ve Loglama",
+ en: "2. Data Processed & Access Logs"
+ },
+ content: {
+ tr: "Openinary altyapısında güvenlik, kota takibi ve performans optimizasyonu amacıyla aşağıdaki teknik veriler toplanır:",
+ en: "The following operational and technical metrics are recorded for security, quota tracking, and edge optimization:"
+ },
+ bullets: {
+ tr: [
+ "Geliştirici Hesap Verileri: E-posta, API Anahtarları (Hashlenmiş), kullanım kotası.",
+ "Yüklenen Medya: Görseller, dokümanlar, dosya boyutları ve MIME türleri.",
+ "Erişim & İstek Logları: İstek zamanı, istemci IP adresi, HTTP metodu, yanıt süresi ve HTTP durum kodları."
+ ],
+ en: [
+ "Developer Credentials: Email, Scoped API Keys (hashed), bandwidth and quota usage.",
+ "Uploaded Assets: Images, files, payloads, MIME signatures, and transformation rules.",
+ "Access Telemetry: Request timestamp, caller IP address, HTTP headers, latency, and status codes."
+ ]
+ }
+ },
+ {
+ id: "security",
+ title: {
+ tr: "3. Medya Güvenliği ve Erişim Denetimi",
+ en: "3. Media Security & Access Control"
+ },
+ content: {
+ tr: "Müşterilere ait medya varlıkları birbirine kesinlikle sızmayacak şekilde dizin ve yetki düzeyinde izole edilir. İmzalı URL (Signed URL) desteği ile özel medyaların yetkisiz erişime açılması engellenir.",
+ en: "Tenant assets are strictly separated in sandboxed object buckets. Signed URL capabilities enable private asset delivery with cryptographically verified expiration timestamps."
+ }
+ }
+ ]
+ },
+ {
+ slug: "ayris-core",
+ name: "Ayris Cloud & Kurumsal Portaller",
+ icon: "🌐",
+ category: {
+ tr: "Kurumsal Web & Bulut Platformları",
+ en: "Enterprise Web & Cloud Platforms"
+ },
+ platforms: ["Web", "Cloud", "SaaS"],
+ version: "v4.0.0",
+ lastUpdated: {
+ tr: "17 Şubat 2026",
+ en: "February 17, 2026"
+ },
+ summary: {
+ tr: "Ayris Tech web sitesi, kurumsal müşteri portalleri ve teklif/iletişim altyapısını kapsayan genel gizlilik politikasıdır.",
+ en: "The general corporate privacy policy covering Ayris Tech web properties, enterprise customer portals, and inquiry systems."
+ },
+ contactEmail: "privacy@ayristech.com",
+ highlights: {
+ tr: [
+ { label: "Çerez Politikası", value: "Sıfır İstenmeyen Takip" },
+ { label: "Form Güvenliği", value: "Spam ve Bot Korumalı TLS" },
+ { label: "Yasal Uyum", value: "KVKK & GDPR Tam Uyum" },
+ { label: "Veri Paylaşımı", value: "Üçüncü Tarafa Veri Satılmaz" }
+ ],
+ en: [
+ { label: "Cookie Policy", value: "Zero Unsolicited Tracking" },
+ { label: "Form Security", value: "Bot-Protected TLS Stream" },
+ { label: "Compliance", value: "Full KVKK & GDPR" },
+ { label: "Data Sharing", value: "No Third-Party Monetization" }
+ ]
+ },
+ permissions: [],
+ sections: [
+ {
+ id: "controller",
+ title: {
+ tr: "1. Veri Sorumlusu Bilgileri",
+ en: "1. Data Controller Details"
+ },
+ content: {
+ tr: "Ayris Tech (Ayris Teknoloji A.Ş.), internet sitesi (ayris.tech) ve bağlı kurumsal alt alan adları üzerinden toplanan kişisel veriler yönünden 6698 sayılı KVKK kapsamında Veri Sorumlusudur.",
+ en: "Ayris Tech is the Data Controller under applicable data protection laws for personal data gathered across ayris.tech and its corporate service domains."
+ }
+ },
+ {
+ id: "collected-data",
+ title: {
+ tr: "2. Web Sitemiz Üzerinden Toplanan Veriler",
+ en: "2. Data Collected via Web Properties"
+ },
+ content: {
+ tr: "Web sitemizi ziyaret ettiğinizde veya teklif/iletişim formlarını doldurduğunuzda aşağıdaki veriler toplanabilmektedir:",
+ en: "When visiting our web applications or submitting project inquiries, the following data may be processed:"
+ },
+ bullets: {
+ tr: [
+ "İletişim Bilgileri: Ad-soyad, kurumsal e-posta adresi, telefon numarası ve şirket unvanı.",
+ "Proje Detayları: Teklif formunda paylaştığınız bütçe aralığı, teknik isterler ve proje açıklaması.",
+ "Teknik Çerezler: Tercih edilen dil (Türkçe/İngilizce) ve oturum doğrulaması için gereken zorunlu çerezler."
+ ],
+ en: [
+ "Contact Details: Full name, business email, phone number, and organization name.",
+ "Project Scope: Budget tier, technology requirements, and project specifications shared in inquiry forms.",
+ "Essential Cookies: Language preference (TR/EN) and cryptographic session cookies."
+ ]
+ }
+ },
+ {
+ id: "cookies",
+ title: {
+ tr: "3. Çerezler (Cookies) ve İzleme İlkeleri",
+ en: "3. Cookie & Telemetry Policy"
+ },
+ content: {
+ tr: "Sitemizde kullanıcıları rahatsız eden reklam çerezleri veya üçüncü taraf veri simsarı takipçileri kullanılmaz. Yalnızca oturumun ve sitenin güvenli çalışması için zorunlu olan teknik çerezler yer alır.",
+ en: "We do not utilize invasive advertising trackers or data brokers. Only essential cookies required for session security, language toggles, and load balancing are placed."
+ }
+ },
+ {
+ id: "contact",
+ title: {
+ tr: "4. KVKK / GDPR Başvuru ve İletişim",
+ en: "4. Inquiries & Data Rights Requests"
+ },
+ content: {
+ tr: "Veri sahibi olarak haklarınızı kullanmak, bilgi almak veya verilerinizin silinmesini istemek için privacy@ayristech.com e-posta adresimiz üzerinden bizimle 7/24 iletişime geçebilirsiniz.",
+ en: "To exercise your statutory data subject rights or request data erasure, you can contact our privacy officers anytime at privacy@ayristech.com."
+ }
+ }
+ ]
+ }
+];
+
+export function getAllPrivacyApps(): PrivacyApp[] {
+ return privacyApps;
+}
+
+export function getPrivacyAppBySlug(slug: string): PrivacyApp | undefined {
+ return privacyApps.find((app) => app.slug.toLowerCase() === slug.toLowerCase());
+}
diff --git a/lib/openinary-loader.ts b/lib/openinary-loader.ts
new file mode 100644
index 0000000..ad51f52
--- /dev/null
+++ b/lib/openinary-loader.ts
@@ -0,0 +1,39 @@
+'use client'
+
+export default function openinaryLoader({ src, width, quality }: { src: string, width: number, quality?: number }) {
+ // Handle already absolute openinary URLs
+ let path = src;
+ if (src.startsWith('https://media.ayris.tech/t/')) {
+ // format: https://media.ayris.tech/t/w_800,h_800/moyqr/img.jpg
+ const parts = src.split('/');
+ path = parts.slice(5).join('/');
+ } else if (src.startsWith('https://media.ayris.tech/upload/')) {
+ // format: https://media.ayris.tech/upload/moyqr/img.jpg
+ const parts = src.split('/');
+ path = parts.slice(4).join('/');
+ } else if (src.includes('res.cloudinary.com')) {
+ // Correctly apply width & quality for unmigrated Cloudinary URLs
+ // e.g. https://res.cloudinary.com/domain/image/upload/v1234/path.jpg
+ // becomes: https://res.cloudinary.com/domain/image/upload/w_800,f_webp,q_75/v1234/path.jpg
+ const parts = src.split('/upload/');
+ if (parts.length === 2) {
+ return `${parts[0]}/upload/w_${width},f_webp,q_${quality || 75}/${parts[1]}`;
+ }
+ return src;
+ } else if (src.startsWith('http')) {
+ // For other external URLs, we append the width as a query parameter to satisfy Next.js.
+ const url = new URL(src);
+ url.searchParams.set('w', width.toString());
+ if (quality) {
+ url.searchParams.set('q', quality.toString());
+ }
+ return url.toString();
+ }
+
+ // Clean up any leading slash
+ if (path.startsWith('/')) {
+ path = path.substring(1);
+ }
+
+ return `https://media.ayris.tech/t/w_${width},f_webp,q_${quality || 75}/${path}`
+}
diff --git a/lib/openinary-url.ts b/lib/openinary-url.ts
new file mode 100644
index 0000000..d8eb533
--- /dev/null
+++ b/lib/openinary-url.ts
@@ -0,0 +1,5 @@
+const BASE = process.env.NEXT_PUBLIC_OPENINARY_URL;
+
+export function optimizedImage(path: string, params: string) {
+ return `${BASE}/t/${params}/${path}`;
+}
diff --git a/lib/openinary.ts b/lib/openinary.ts
new file mode 100644
index 0000000..36fcb13
--- /dev/null
+++ b/lib/openinary.ts
@@ -0,0 +1,19 @@
+export async function uploadToOpeninary(file: File, folder: string) {
+ const formData = new FormData();
+ formData.append("files", file);
+ formData.append("folder", folder);
+
+ const res = await fetch(`${process.env.OPENINARY_API_URL}/api/upload`, {
+ method: "POST",
+ headers: { Authorization: `Bearer ${process.env.OPENINARY_API_KEY}` },
+ body: formData,
+ });
+
+ if (!res.ok) {
+ const errorText = await res.text();
+ console.error("Openinary upload error:", errorText);
+ throw new Error("Upload başarısız: " + errorText);
+ }
+ const data = await res.json();
+ return data.files[0]; // { path, url, size, ... }
+}
diff --git a/scripts/migrate.ts b/scripts/migrate.ts
new file mode 100644
index 0000000..bb1c7e0
--- /dev/null
+++ b/scripts/migrate.ts
@@ -0,0 +1,97 @@
+import { prisma } from '../lib/prisma';
+
+async function uploadToOpeninary(buffer: Buffer, filename: string, folder: string) {
+ const formData = new FormData();
+ const blob = new Blob([new Uint8Array(buffer)], { type: 'image/jpeg' });
+ formData.append("files", blob, filename);
+ formData.append("folder", folder);
+
+ const res = await fetch(`${process.env.OPENINARY_API_URL}/api/upload`, {
+ method: "POST",
+ headers: { Authorization: `Bearer ${process.env.OPENINARY_API_KEY}` },
+ body: formData as any,
+ });
+
+ if (!res.ok) {
+ const errorText = await res.text();
+ throw new Error("Upload failed: " + errorText);
+ }
+ const data = await res.json();
+ return data.files[0];
+}
+
+async function processUrl(url: string, folder: string): Promise {
+ if (!url) return url;
+ if (!url.includes('cloudinary.com')) return url;
+
+ console.log(`Downloading ${url}...`);
+ try {
+ const res = await fetch(url);
+ if (!res.ok) throw new Error(`Failed to fetch ${url}`);
+ const arrayBuffer = await res.arrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+ const filename = url.split('/').pop()?.split('?')[0] || 'image.jpg';
+
+ console.log(`Uploading ${filename} to Openinary...`);
+ const result = await uploadToOpeninary(buffer, filename, folder);
+ console.log(`Uploaded! New path: ${result.path}`);
+ return result.path;
+ } catch (e) {
+ console.error(`Error processing ${url}:`, e);
+ return url;
+ }
+}
+
+async function main() {
+ console.log('Starting migration...');
+
+ // Migrate Projects
+ const projects = await prisma.project.findMany();
+ for (const project of projects) {
+ console.log(`Processing project: ${project.title}`);
+ let updated = false;
+
+ let newImage = project.image;
+ if (newImage && newImage.includes('cloudinary.com')) {
+ newImage = await processUrl(newImage, 'ayristech/projects');
+ updated = true;
+ }
+
+ const newGallery = [];
+ for (const img of project.gallery) {
+ if (img.includes('cloudinary.com')) {
+ const newImg = await processUrl(img, 'ayristech/projects');
+ newGallery.push(newImg);
+ updated = true;
+ } else {
+ newGallery.push(img);
+ }
+ }
+
+ if (updated) {
+ await prisma.project.update({
+ where: { id: project.id },
+ data: { image: newImage, gallery: newGallery }
+ });
+ console.log(`Updated project ${project.id}`);
+ }
+ }
+
+ // Migrate BlogPosts
+ const posts = await prisma.blogPost.findMany();
+ for (const post of posts) {
+ console.log(`Processing blog post: ${post.slug}`);
+ if (post.image && post.image.includes('cloudinary.com')) {
+ const newImage = await processUrl(post.image, 'ayristech/blog');
+ await prisma.blogPost.update({
+ where: { id: post.id },
+ data: { image: newImage }
+ });
+ console.log(`Updated blog post ${post.id}`);
+ }
+ }
+
+ console.log('Migration completed!');
+}
+
+main().catch(console.error).finally(() => prisma.$disconnect());