feat: add privacy hub and application privacy subpages

This commit is contained in:
mstfyldz
2026-08-17 10:29:12 +03:00
parent 59b2afbc5a
commit 39b19bdee9
11 changed files with 1536 additions and 2 deletions
+71
View File
@@ -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 (
<PrivacyDetailClient
lang={lang}
dict={dict}
app={app}
otherApps={otherApps}
/>
);
}
+38
View File
@@ -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 <PrivacyClient lang={lang} dict={dict} apps={apps} />;
}
+36 -1
View File
@@ -48,6 +48,18 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
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<MetadataRoute.Sitemap> {
// 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;
}
+1 -1
View File
@@ -103,7 +103,7 @@ export default function Footer({ lang, dict }: { lang: Locale; dict: any }) {
© 2026 Ayris Tech {dict.footer.rights}
</p>
<div className="flex items-center gap-6">
<Link href="#" className="font-mono text-[10px] tracking-[0.15em] uppercase text-[#C8C2B8] hover:text-[#0A0A0A] transition-colors">
<Link href={`/${lang}/privacy`} className="font-mono text-[10px] tracking-[0.15em] uppercase text-[#C8C2B8] hover:text-[#0A0A0A] transition-colors">
{dict.footer.privacy}
</Link>
<Link href="#" className="font-mono text-[10px] tracking-[0.15em] uppercase text-[#C8C2B8] hover:text-[#0A0A0A] transition-colors">
+358
View File
@@ -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<string>("ALL");
const [searchQuery, setSearchQuery] = useState<string>("");
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 (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-7xl mx-auto">
{/* Page Header */}
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{isTr ? "AYRİS TECH — GİZLİLİK VE VERİ MERKEZİ" : "AYRIS TECH — PRIVACY & COMPLIANCE HUB"}
</span>
</div>
<motion.h1
className="font-display font-black text-4xl sm:text-6xl lg:text-7xl uppercase leading-[0.9] tracking-tight mb-6"
initial={{ opacity: 0, y: 25 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: easeOutExpo }}
>
{isTr ? "GİZLİLİK" : "PRIVACY"}<br />
<span className="text-stroke">{isTr ? "POLİTİKALARI" : "POLICIES"}</span>
</motion.h1>
<p className="text-[#6A6460] text-base sm:text-lg max-w-3xl leading-relaxed">
{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."}
</p>
</div>
{/* Core Principles Matrix */}
<div className="mb-20">
<div className="flex items-center justify-between mb-8 border-b border-[#C8C2B8] pb-4">
<div className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{isTr ? "01 / GÜVENLİK VE GİZLİLİK STANDARTLARIMIZ" : "01 / SECURITY & PRIVACY BENCHMARKS"}
</div>
<span className="font-mono text-[10px] text-[#A0998E]">
ISO 27001 · KVKK · GDPR
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{corePrinciples.map((item, idx) => (
<motion.div
key={item.code}
className="border border-[#C8C2B8] bg-[#EDE8E0] p-6 flex flex-col justify-between hover:border-[#0A0A0A] transition-colors"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.08, duration: 0.6 }}
>
<div>
<div className="flex items-center justify-between mb-4">
<span className="font-mono text-[10px] text-[#A0998E]">{item.code}</span>
<span className="font-mono text-[9px] tracking-wider px-2 py-0.5 bg-[#FFE600] text-[#0A0A0A] font-bold">
{item.badge}
</span>
</div>
<h3 className="font-display font-black text-lg uppercase mb-3 text-[#0A0A0A]">
{item.title}
</h3>
<p className="text-[#6A6460] text-[13px] leading-relaxed">
{item.desc}
</p>
</div>
</motion.div>
))}
</div>
</div>
{/* Apps Privacy Directory */}
<div className="mb-24">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-8 border-b border-[#C8C2B8] pb-6">
<div>
<div className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E] mb-2">
{isTr ? "02 / UYGULAMA GİZLİLİK REHBERİ" : "02 / APPLICATION DIRECTORY"}
</div>
<h2 className="font-display font-black text-2xl sm:text-3xl uppercase text-[#0A0A0A]">
{isTr ? "UYGULAMALARIMIZ VE SERVİSLERİMİZ" : "APPLICATIONS & PLATFORMS"}
</h2>
</div>
{/* Filter Tabs */}
<div className="flex flex-wrap items-center gap-2">
{platforms.map((tab) => (
<button
key={tab.id}
onClick={() => 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}
</button>
))}
</div>
</div>
{/* Search Box */}
<div className="mb-8">
<div className="relative">
<input
type="text"
placeholder={
isTr
? "Uygulama adı, platform veya anahtar kelime ile filtreleyin..."
: "Filter by application name, category, or keyword..."
}
value={searchQuery}
onChange={(e) => 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 && (
<button
onClick={() => 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"}
</button>
)}
</div>
</div>
{/* Apps Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<AnimatePresence mode="popLayout">
{filteredApps.map((app, idx) => (
<motion.div
key={app.slug}
layout
initial={{ opacity: 0, scale: 0.96 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.96 }}
transition={{ duration: 0.4, delay: idx * 0.05 }}
className="border border-[#C8C2B8] bg-[#EDE8E0] p-8 flex flex-col justify-between hover:border-[#0A0A0A] transition-all group"
>
<div>
{/* Top Row: Icon, Category, Platforms */}
<div className="flex items-start justify-between gap-4 mb-6">
<div className="flex items-center gap-4">
<div className="w-14 h-14 bg-[#FFE600] border-2 border-[#0A0A0A] flex items-center justify-center text-2xl flex-shrink-0 group-hover:scale-105 transition-transform">
{app.icon}
</div>
<div>
<div className="font-mono text-[9px] tracking-[0.2em] uppercase text-[#A0998E]">
{app.category[lang]}
</div>
<h3 className="font-display font-black text-2xl uppercase text-[#0A0A0A] tracking-tight">
{app.name}
</h3>
</div>
</div>
<div className="flex flex-wrap gap-1.5 justify-end">
{app.platforms.map((p) => (
<span
key={p}
className="font-mono text-[9px] uppercase px-2 py-0.5 border border-[#C8C2B8] bg-[#F4F0E8] text-[#6A6460]"
>
{p}
</span>
))}
</div>
</div>
{/* Summary */}
<p className="text-[#6A6460] text-[14px] leading-relaxed mb-6">
{app.summary[lang]}
</p>
{/* Highlights Badges */}
<div className="grid grid-cols-2 gap-2 mb-8 pt-4 border-t border-[#C8C2B8]">
{app.highlights[lang].slice(0, 4).map((h, i) => (
<div key={i} className="bg-[#F4F0E8] p-2.5 border border-[#E0DACF]">
<div className="font-mono text-[8px] uppercase tracking-wider text-[#A0998E]">
{h.label}
</div>
<div className="font-display font-bold text-[11px] text-[#0A0A0A] truncate">
{h.value}
</div>
</div>
))}
</div>
</div>
{/* Bottom Action */}
<div className="pt-4 border-t border-[#C8C2B8] flex items-center justify-between">
<span className="font-mono text-[10px] text-[#A0998E]">
{isTr ? "Son Güncelleme:" : "Updated:"} {app.lastUpdated[lang]}
</span>
<Link
href={`/${lang}/privacy/${app.slug}`}
className="btn-brutal btn-brutal-yellow inline-flex items-center gap-2 px-5 py-2.5 font-display font-black text-[11px] tracking-wider uppercase"
>
{isTr ? "POLİTİKAYI İNCELE" : "VIEW POLICY"}
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
</Link>
</div>
</motion.div>
))}
</AnimatePresence>
{filteredApps.length === 0 && (
<div className="col-span-2 border border-dashed border-[#C8C2B8] p-12 text-center">
<div className="font-mono text-[11px] text-[#A0998E] uppercase tracking-wider mb-2">
{isTr ? "Kayıt Bulunamadı" : "No Applications Found"}
</div>
<p className="text-[#6A6460] text-sm">
{isTr
? "Aradığınız kriterlere uygun uygulama gizlilik politikası bulunamadı."
: "No application policies matched your filter criteria."}
</p>
</div>
)}
</div>
</div>
{/* Data Subject Request & Deletion Box */}
<motion.div
className="border-2 border-[#0A0A0A] bg-[#FFE600] p-8 sm:p-12"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<div className="max-w-4xl flex flex-col md:flex-row md:items-center justify-between gap-8">
<div>
<span className="font-mono text-[10px] tracking-[0.25em] uppercase text-[#0A0A0A] font-bold block mb-2">
{isTr ? "KVKK MADDE 11 & GDPR HAK TALEPLERİ" : "DATA SUBJECT RIGHTS & ERASURE"}
</span>
<h2 className="font-display font-black text-2xl sm:text-3xl uppercase text-[#0A0A0A] leading-tight mb-3">
{isTr ? "VERİLERİNİZİ SİLMEK VEYA BİLGİ ALMAK MI İSTİYORSUNUZ?" : "NEED TO ERASE YOUR DATA OR INQUIRE?"}
</h2>
<p className="text-[#0A0A0A]/80 text-sm leading-relaxed max-w-xl">
{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."}
</p>
</div>
<a
href="mailto:privacy@ayristech.com?subject=Veri%20Silme%20ve%20Bilgi%20Talebi%20(Ayris%20Tech)&body=Merhaba,%20ilgili%20uygulamadaki%20verilerimin%20silinmesini/bilgilendirilmesini%20talep%20ediyorum.%0A%0AUygulama%20Ad%C4%B1:%0AKullan%C4%B1c%C4%B1%20E-postas%C4%B1:"
className="px-8 py-4 bg-[#0A0A0A] text-[#F4F0E8] font-display font-black text-xs tracking-widest uppercase hover:bg-[#FF4500] transition-colors flex-shrink-0 text-center"
>
{isTr ? "HAK / SİLME TALEBİ GÖNDER" : "SUBMIT ERASURE REQUEST"}
</a>
</div>
</motion.div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+344
View File
@@ -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 (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
{/* Scroll indicator */}
<motion.div
className="fixed top-0 left-0 right-0 h-1.5 bg-[#FFE600] z-50 origin-[0%]"
style={{ scaleX }}
/>
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-6xl mx-auto">
{/* Back Link */}
<motion.div
className="mb-8"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<Link
href={`/${lang}/privacy`}
className="inline-flex items-center gap-2 font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E] hover:text-[#0A0A0A] transition-colors"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</svg>
{isTr ? "TÜM GİZLİLİK POLİTİKALARI" : "ALL PRIVACY POLICIES"}
</Link>
</motion.div>
{/* Hero Header */}
<div className="border border-[#C8C2B8] bg-[#EDE8E0] p-8 sm:p-12 mb-12">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-6">
<div className="flex items-center gap-4">
<div className="w-16 h-16 bg-[#FFE600] border-2 border-[#0A0A0A] flex items-center justify-center text-3xl flex-shrink-0">
{app.icon}
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="font-mono text-[9px] tracking-[0.2em] uppercase px-2 py-0.5 border border-[#C8C2B8] bg-[#F4F0E8] text-[#6A6460]">
{app.category[lang]}
</span>
<span className="font-mono text-[9px] text-[#A0998E]">
{app.version}
</span>
</div>
<h1 className="font-display font-black text-3xl sm:text-5xl uppercase text-[#0A0A0A] tracking-tight">
{app.name}
</h1>
</div>
</div>
<div className="flex flex-col md:items-end gap-2">
<div className="flex gap-1.5">
{app.platforms.map((p) => (
<span
key={p}
className="font-mono text-[9px] uppercase px-2.5 py-1 bg-[#0A0A0A] text-[#F4F0E8] font-bold"
>
{p}
</span>
))}
</div>
<div className="font-mono text-[10px] text-[#A0998E]">
{isTr ? "Yürürlük / Güncelleme:" : "Effective / Last Updated:"} {app.lastUpdated[lang]}
</div>
</div>
</div>
<p className="text-[#6A6460] text-base leading-relaxed max-w-4xl border-t border-[#C8C2B8] pt-6">
{app.summary[lang]}
</p>
{/* Key Highlights */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mt-6 pt-6 border-t border-[#C8C2B8]">
{app.highlights[lang].map((h, i) => (
<div key={i} className="bg-[#F4F0E8] p-3 border border-[#E0DACF]">
<div className="font-mono text-[8px] uppercase tracking-wider text-[#A0998E] mb-1">
{h.label}
</div>
<div className="font-display font-bold text-xs text-[#0A0A0A]">
{h.value}
</div>
</div>
))}
</div>
</div>
{/* Content Layout: Sticky Sidebar + Main Sections */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
{/* Sidebar Navigation */}
<aside className="lg:col-span-4">
<div className="sticky top-24 border border-[#C8C2B8] bg-[#EDE8E0] p-6 space-y-6">
<div>
<div className="font-mono text-[9px] tracking-[0.25em] uppercase text-[#A0998E] mb-4">
{isTr ? "İÇİNDEKİLER" : "TABLE OF CONTENTS"}
</div>
<nav className="space-y-2">
{app.permissions.length > 0 && (
<a
href="#permissions"
className="block font-mono text-[11px] text-[#6A6460] hover:text-[#0A0A0A] hover:translate-x-1 transition-all py-1 border-b border-[#E0DACF]"
>
{isTr ? "→ Donanım & Sistem İzinleri" : "→ Hardware & Permissions"}
</a>
)}
{app.sections.map((sec) => (
<a
key={sec.id}
href={`#${sec.id}`}
className="block font-mono text-[11px] text-[#6A6460] hover:text-[#0A0A0A] hover:translate-x-1 transition-all py-1 border-b border-[#E0DACF]"
>
{sec.title[lang]}
</a>
))}
<a
href="#data-deletion"
className="block font-mono text-[11px] text-[#FF4500] font-bold hover:translate-x-1 transition-all py-1"
>
{isTr ? "Veri Silme & Destek" : "Data Erasure & Contact"}
</a>
</nav>
</div>
<div className="pt-4 border-t border-[#C8C2B8]">
<div className="font-mono text-[9px] uppercase tracking-wider text-[#A0998E] mb-2">
{isTr ? "Gizlilik İletişim Hattı:" : "Privacy Contact Desk:"}
</div>
<a
href={`mailto:${app.contactEmail}`}
className="font-mono text-xs text-[#0A0A0A] font-bold hover:underline"
>
{app.contactEmail}
</a>
</div>
</div>
</aside>
{/* Main Legal Content */}
<div className="lg:col-span-8 space-y-12">
{/* System Permissions (if applicable) */}
{app.permissions.length > 0 && (
<section id="permissions" className="border border-[#C8C2B8] bg-[#EDE8E0] p-8">
<div className="flex items-center gap-3 mb-6">
<span className="w-2.5 h-2.5 bg-[#FFE600]" />
<h2 className="font-display font-black text-xl uppercase text-[#0A0A0A]">
{isTr ? "Cihaz İzinleri ve Donanım Erişimi" : "Device Permissions & Hardware Access"}
</h2>
</div>
<p className="text-[#6A6460] text-sm leading-relaxed mb-6">
{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:`}
</p>
<div className="space-y-4">
{app.permissions.map((perm, idx) => (
<div
key={idx}
className="bg-[#F4F0E8] p-4 border border-[#C8C2B8] flex flex-col gap-2"
>
<div className="flex items-center justify-between">
<span className="font-display font-black text-sm uppercase text-[#0A0A0A]">
{perm.name[lang]}
</span>
<span
className={`font-mono text-[9px] uppercase px-2 py-0.5 font-bold ${
perm.required
? "bg-[#FFE600] text-[#0A0A0A]"
: "bg-[#E0DACF] text-[#6A6460]"
}`}
>
{perm.required
? isTr
? "ZORUNLU"
: "REQUIRED"
: isTr
? "İSTEĞE BAĞLI"
: "OPTIONAL"}
</span>
</div>
<p className="text-[#6A6460] text-xs leading-relaxed">
{perm.reason[lang]}
</p>
</div>
))}
</div>
</section>
)}
{/* Sections */}
{app.sections.map((section, idx) => (
<section
key={section.id}
id={section.id}
className="border-b border-[#C8C2B8] pb-10 scroll-mt-28"
>
<div className="flex items-center gap-3 mb-4">
<span className="font-mono text-[10px] text-[#A0998E]">
#{String(idx + 1).padStart(2, "0")}
</span>
<h2 className="font-display font-black text-xl sm:text-2xl uppercase text-[#0A0A0A]">
{section.title[lang]}
</h2>
</div>
<p className="text-[#4A4540] text-sm sm:text-base leading-relaxed mb-4">
{section.content[lang]}
</p>
{section.bullets && (
<ul className="space-y-2.5 mt-4 bg-[#EDE8E0] p-6 border-l-2 border-[#FFE600] border-y border-r border-[#C8C2B8]">
{section.bullets[lang].map((bullet, bIdx) => (
<li key={bIdx} className="flex items-start gap-2.5 text-xs sm:text-sm text-[#4A4540]">
<span className="font-mono text-[#A0998E] flex-shrink-0 mt-0.5"></span>
<span>{bullet}</span>
</li>
))}
</ul>
)}
</section>
))}
{/* Data Deletion and Erasure Request Section */}
<section
id="data-deletion"
className="border-2 border-[#0A0A0A] bg-[#FFE600] p-8"
>
<div className="font-mono text-[9px] uppercase tracking-[0.2em] text-[#0A0A0A] font-bold mb-2">
{isTr ? "KULLANICI VERİ KONTROLÜ" : "USER DATA CONTROL"}
</div>
<h2 className="font-display font-black text-2xl uppercase text-[#0A0A0A] mb-3">
{isTr
? `${app.name} Verilerinizi Kalıcı Olarak Silin`
: `Permanently Erase Your ${app.name} Data`}
</h2>
<p className="text-[#0A0A0A]/85 text-xs sm:text-sm leading-relaxed mb-6">
{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."}
</p>
<a
href={`mailto:${app.contactEmail}?subject=${encodeURIComponent(
`[${app.name}] Veri Silme ve Hesap Kapatma Talebi`
)}&body=${encodeURIComponent(
`Merhaba,\n\n${app.name} uygulamasında kayıtlı olan kullanıcı hesabımın ve tüm verilerimin kalıcı olarak silinmesini talep ediyorum.\n\nKayıtlı E-posta Adresim:\nKullanıcı / Hesap ID (varsa):\n\nTeşekkürler.`
)}`}
className="inline-flex items-center gap-3 px-6 py-3.5 bg-[#0A0A0A] text-[#F4F0E8] font-display font-black text-xs tracking-wider uppercase hover:bg-[#FF4500] transition-colors"
>
{isTr ? "VERİ SİLME TALEBİ OLUŞTUR" : "REQUEST DATA DELETION"}
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="22" y1="2" x2="11" y2="13" />
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
</a>
</section>
</div>
</div>
{/* Other Applications Footer Switcher */}
{otherApps.length > 0 && (
<div className="mt-24 pt-12 border-t border-[#C8C2B8]">
<div className="font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E] mb-6">
{isTr ? "DİĞER UYGULAMA GİZLİLİK POLİTİKALARI" : "OTHER APPLICATION POLICIES"}
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
{otherApps.map((other) => (
<Link
key={other.slug}
href={`/${lang}/privacy/${other.slug}`}
className="border border-[#C8C2B8] bg-[#EDE8E0] p-5 hover:border-[#0A0A0A] transition-colors group flex items-center gap-4"
>
<div className="w-10 h-10 bg-[#FFE600] border border-[#0A0A0A] flex items-center justify-center text-xl flex-shrink-0">
{other.icon}
</div>
<div className="min-w-0">
<div className="font-display font-black text-sm uppercase text-[#0A0A0A] group-hover:text-[#FF4500] transition-colors truncate">
{other.name}
</div>
<div className="font-mono text-[9px] text-[#A0998E] truncate">
{other.category[lang]}
</div>
</div>
</Link>
))}
</div>
</div>
)}
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+528
View File
@@ -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());
}
+39
View File
@@ -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}`
}
+5
View File
@@ -0,0 +1,5 @@
const BASE = process.env.NEXT_PUBLIC_OPENINARY_URL;
export function optimizedImage(path: string, params: string) {
return `${BASE}/t/${params}/${path}`;
}
+19
View File
@@ -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, ... }
}
+97
View File
@@ -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<string> {
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());