Kovak Yazılım kurumsal site — Next.js 16 + Appwrite

- Anasayfa, Hizmetler, Projeler, Hakkımızda, İletişim sayfaları
- Header/Footer, Hero, ServicesGrid, ProjectsGrid, ContactForm bileşenleri
- Appwrite TablesDB entegrasyonu (services, projects, contact_messages)
- Server Action ile iletişim formu (submitContact)
- Brand palette: navy #0F2C5C + sky #4DA3C7
- kovakyazilim.com'dan alınan logo public/logo.png
This commit is contained in:
Ege Can Komur
2026-05-20 01:52:27 +03:00
parent 8a3a466087
commit 3b3efafcc8
26 changed files with 1192 additions and 75 deletions
+119
View File
@@ -0,0 +1,119 @@
"use client";
import { useActionState } from "react";
import { submitContact, type ContactFormState } from "@/app/actions";
import { Send, CheckCircle2, AlertCircle } from "lucide-react";
const initial: ContactFormState = { ok: false, message: "" };
export function ContactForm() {
const [state, formAction, pending] = useActionState(submitContact, initial);
return (
<form action={formAction} className="space-y-5">
<div className="grid gap-5 sm:grid-cols-2">
<Field
label="Ad Soyad"
name="name"
placeholder="Adınız Soyadınız"
error={state.errors?.name}
required
/>
<Field
label="E-posta"
name="email"
type="email"
placeholder="ornek@firma.com"
error={state.errors?.email}
required
/>
<Field
label="Telefon"
name="phone"
placeholder="+90 5xx xxx xx xx"
/>
<Field
label="Konu"
name="subject"
placeholder="Proje türü"
/>
</div>
<div>
<label className="text-sm font-medium text-[var(--navy)]">
Mesajınız <span className="text-red-500">*</span>
</label>
<textarea
name="message"
required
minLength={10}
rows={5}
placeholder="Proje hakkında kısa bir özet paylaşın…"
className="mt-1.5 w-full rounded-xl border border-[var(--border)] bg-white px-4 py-3 text-sm text-[var(--foreground)] outline-none transition placeholder:text-[var(--muted)]/60 focus:border-[var(--sky)] focus:ring-2 focus:ring-[var(--sky)]/20"
/>
{state.errors?.message && (
<p className="mt-1 text-xs text-red-500">{state.errors.message}</p>
)}
</div>
<button
type="submit"
disabled={pending}
className="inline-flex w-full items-center justify-center gap-2 rounded-full bg-[var(--navy)] px-6 py-3 text-sm font-medium text-white transition hover:bg-[var(--navy-700)] disabled:opacity-60 sm:w-auto"
>
{pending ? "Gönderiliyor…" : "Mesaj Gönder"}
<Send className="size-4" />
</button>
{state.message && (
<div
className={`flex items-start gap-3 rounded-xl border p-4 text-sm ${
state.ok
? "border-green-200 bg-green-50 text-green-800"
: "border-red-200 bg-red-50 text-red-800"
}`}
>
{state.ok ? (
<CheckCircle2 className="mt-0.5 size-5 shrink-0" />
) : (
<AlertCircle className="mt-0.5 size-5 shrink-0" />
)}
<span>{state.message}</span>
</div>
)}
</form>
);
}
function Field({
label,
name,
type = "text",
placeholder,
error,
required,
}: {
label: string;
name: string;
type?: string;
placeholder?: string;
error?: string;
required?: boolean;
}) {
return (
<div>
<label className="text-sm font-medium text-[var(--navy)]">
{label}
{required && <span className="text-red-500"> *</span>}
</label>
<input
name={name}
type={type}
required={required}
placeholder={placeholder}
className="mt-1.5 w-full rounded-xl border border-[var(--border)] bg-white px-4 py-3 text-sm text-[var(--foreground)] outline-none transition placeholder:text-[var(--muted)]/60 focus:border-[var(--sky)] focus:ring-2 focus:ring-[var(--sky)]/20"
/>
{error && <p className="mt-1 text-xs text-red-500">{error}</p>}
</div>
);
}