f833d429fc
Backend altyapısı: - 4 yeni Appwrite tablosu: blog_posts, testimonials, seo_pages, seo_settings - Appwrite Storage bucket: kovak-yazilim-media (görsel yüklemeleri) - Appwrite Auth ile session cookie tabanlı koruma Admin paneli (/admin): - Login akışı (email/password) + protected layout - Dashboard: sayım kartları + hızlı aksiyonlar - Blog CRUD: markdown content, kapak görseli, draft/published, SEO alanları - Services CRUD: lucide ikon seçici - Projects CRUD: teknoloji etiketleri, live URL - Testimonials CRUD: puanlama - SEO yöneticisi: global ayarlar + sayfa bazlı override - Mesaj inbox: status filtreleme + güncelleme - Medya kütüphanesi: Appwrite Storage upload/delete Public: - /blog ve /blog/[slug] sayfaları (markdown render) - Anasayfaya Testimonials bölümü - Tüm public sayfalarda generateMetadata + seo_pages override - Header'a Blog linki Route yapısı: - app/(site)/ — public site, Header/Footer ortak - app/admin/login — auth dışı - app/admin/(protected)/ — requireUser() korumalı 23 route üretiliyor, public static, admin dynamic.
203 lines
4.9 KiB
TypeScript
203 lines
4.9 KiB
TypeScript
import Link from "next/link";
|
|
import { ArrowLeft } from "lucide-react";
|
|
|
|
export function PageHeader({
|
|
title,
|
|
description,
|
|
backHref,
|
|
action,
|
|
}: {
|
|
title: string;
|
|
description?: string;
|
|
backHref?: string;
|
|
action?: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div>
|
|
{backHref && (
|
|
<Link
|
|
href={backHref}
|
|
className="mb-2 inline-flex items-center gap-1 text-xs text-[var(--muted)] hover:text-[var(--navy)]"
|
|
>
|
|
<ArrowLeft className="size-3" />
|
|
Geri
|
|
</Link>
|
|
)}
|
|
<h1 className="text-2xl font-bold text-[var(--navy)]">{title}</h1>
|
|
{description && (
|
|
<p className="mt-1 text-sm text-[var(--muted)]">{description}</p>
|
|
)}
|
|
</div>
|
|
{action}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function Field({
|
|
label,
|
|
name,
|
|
type = "text",
|
|
defaultValue,
|
|
placeholder,
|
|
required,
|
|
help,
|
|
}: {
|
|
label: string;
|
|
name: string;
|
|
type?: string;
|
|
defaultValue?: string | number | null;
|
|
placeholder?: string;
|
|
required?: boolean;
|
|
help?: string;
|
|
}) {
|
|
return (
|
|
<label className="block">
|
|
<span className="text-sm font-medium text-[var(--navy)]">
|
|
{label}
|
|
{required && <span className="text-red-500"> *</span>}
|
|
</span>
|
|
<input
|
|
name={name}
|
|
type={type}
|
|
required={required}
|
|
defaultValue={defaultValue ?? undefined}
|
|
placeholder={placeholder}
|
|
className="mt-1.5 w-full rounded-xl border border-[var(--border)] bg-white px-4 py-2.5 text-sm outline-none transition focus:border-[var(--sky)] focus:ring-2 focus:ring-[var(--sky)]/20"
|
|
/>
|
|
{help && <span className="mt-1 block text-xs text-[var(--muted)]">{help}</span>}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
export function Textarea({
|
|
label,
|
|
name,
|
|
defaultValue,
|
|
rows = 4,
|
|
placeholder,
|
|
required,
|
|
help,
|
|
}: {
|
|
label: string;
|
|
name: string;
|
|
defaultValue?: string | null;
|
|
rows?: number;
|
|
placeholder?: string;
|
|
required?: boolean;
|
|
help?: string;
|
|
}) {
|
|
return (
|
|
<label className="block">
|
|
<span className="text-sm font-medium text-[var(--navy)]">
|
|
{label}
|
|
{required && <span className="text-red-500"> *</span>}
|
|
</span>
|
|
<textarea
|
|
name={name}
|
|
required={required}
|
|
rows={rows}
|
|
defaultValue={defaultValue ?? undefined}
|
|
placeholder={placeholder}
|
|
className="mt-1.5 w-full rounded-xl border border-[var(--border)] bg-white px-4 py-2.5 text-sm outline-none transition focus:border-[var(--sky)] focus:ring-2 focus:ring-[var(--sky)]/20"
|
|
/>
|
|
{help && <span className="mt-1 block text-xs text-[var(--muted)]">{help}</span>}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
export function Select({
|
|
label,
|
|
name,
|
|
options,
|
|
defaultValue,
|
|
}: {
|
|
label: string;
|
|
name: string;
|
|
options: { value: string; label: string }[];
|
|
defaultValue?: string | null;
|
|
}) {
|
|
return (
|
|
<label className="block">
|
|
<span className="text-sm font-medium text-[var(--navy)]">{label}</span>
|
|
<select
|
|
name={name}
|
|
defaultValue={defaultValue ?? undefined}
|
|
className="mt-1.5 w-full rounded-xl border border-[var(--border)] bg-white px-4 py-2.5 text-sm outline-none transition focus:border-[var(--sky)] focus:ring-2 focus:ring-[var(--sky)]/20"
|
|
>
|
|
{options.map((o) => (
|
|
<option key={o.value} value={o.value}>
|
|
{o.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
);
|
|
}
|
|
|
|
export function Checkbox({
|
|
label,
|
|
name,
|
|
defaultChecked,
|
|
}: {
|
|
label: string;
|
|
name: string;
|
|
defaultChecked?: boolean;
|
|
}) {
|
|
return (
|
|
<label className="inline-flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
name={name}
|
|
defaultChecked={defaultChecked}
|
|
className="size-4 rounded border-[var(--border)] text-[var(--navy)] focus:ring-[var(--sky)]"
|
|
/>
|
|
<span className="text-sm text-[var(--foreground)]">{label}</span>
|
|
</label>
|
|
);
|
|
}
|
|
|
|
export function FormShell({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="mt-6 rounded-2xl border border-[var(--border)] bg-white p-6 sm:p-8">
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function FormActions({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="mt-6 flex flex-wrap items-center justify-end gap-3 border-t border-[var(--border)] pt-6">
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function PrimaryButton({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<button
|
|
type="submit"
|
|
className="inline-flex items-center gap-2 rounded-full bg-[var(--navy)] px-5 py-2.5 text-sm font-medium text-white transition hover:bg-[var(--navy-700)]"
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function GhostLink({
|
|
href,
|
|
children,
|
|
}: {
|
|
href: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className="text-sm font-medium text-[var(--muted)] hover:text-[var(--navy)]"
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|