diff --git a/app/portal/projects/[id]/page.tsx b/app/portal/projects/[id]/page.tsx
index 560a5eb..a28efc6 100644
--- a/app/portal/projects/[id]/page.tsx
+++ b/app/portal/projects/[id]/page.tsx
@@ -1,5 +1,8 @@
import { notFound } from "next/navigation";
+import { getSqliteConnection } from "@/server/db/client";
import { DomainError } from "@/server/domain/errors";
+import { ContentTranslationService, getContentFallbackLocale } from "@/server/i18n/content";
+import { resolveRequestLocale } from "@/server/i18n/resolver";
import { requirePortalBackend } from "@/server/web/portal";
import {
PortalProjectClient,
@@ -11,7 +14,11 @@ import {
export default async function PortalProjectPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
+ const locale = await resolveRequestLocale();
const { actor, service } = await requirePortalBackend();
+ const content = new ContentTranslationService(getSqliteConnection().db);
+ const localization = content.getPublicLocalizationContext();
+ const fallbackLocale = getContentFallbackLocale(locale.locale, localization);
let data: {
project: PortalProjectDetail;
sections: PortalPlanningSection[];
@@ -21,32 +28,57 @@ export default async function PortalProjectPage({ params }: { params: Promise<{
try {
const row = service.getProject(actor, id);
+ const projectTranslations = content.listEntityTranslations("project", row.id);
+ const projectRow = content.resolveEntity("project", row, {
+ locale: locale.locale,
+ fallbackLocale,
+ defaultLocale: locale.defaultLocale,
+ translations: projectTranslations,
+ });
const allowance = service.getRevisionAllowance(actor, id);
+ const sectionRows = service.listPlanningSections(actor, id);
+ const sectionTranslations = content.listBatch("planning_section", sectionRows.map((section) => section.id));
+ const taskRows = service.listTasks(actor, id).filter((task) => task.status !== "cancelled");
+ const taskTranslations = content.listBatch("task", taskRows.map((task) => task.id));
data = {
project: {
- id: row.id,
- name: row.name,
- description: row.description,
- status: row.status,
- progress: row.progress,
- due_date: row.dueDate,
+ id: projectRow.id,
+ name: projectRow.name,
+ description: projectRow.description,
+ status: projectRow.status,
+ progress: projectRow.progress,
+ due_date: projectRow.dueDate,
revision_quota: allowance.remaining,
can_request_revision: allowance.canRequest,
},
- sections: service.listPlanningSections(actor, id).map((section) => ({
- id: section.id,
- title: section.title,
- content: section.content,
- type: section.category,
- })),
- tasks: service.listTasks(actor, id)
- .filter((task) => task.status !== "cancelled")
- .map((task) => ({
+ sections: sectionRows.map((section) => {
+ const sectionRow = content.resolveEntity("planning_section", section, {
+ locale: locale.locale,
+ fallbackLocale,
+ defaultLocale: locale.defaultLocale,
+ translations: sectionTranslations.get(section.id) ?? [],
+ });
+ return {
+ id: sectionRow.id,
+ title: sectionRow.title,
+ content: sectionRow.content,
+ type: sectionRow.category,
+ };
+ }),
+ tasks: taskRows.map((task) => {
+ const taskRow = content.resolveEntity("task", task, {
+ locale: locale.locale,
+ fallbackLocale,
+ defaultLocale: locale.defaultLocale,
+ translations: taskTranslations.get(task.id) ?? [],
+ });
+ return {
id: task.id,
- title: task.title,
+ title: taskRow.title,
status: task.status as PortalTask["status"],
date: task.dueAt?.toISOString() ?? task.scheduledDate,
- })),
+ };
+ }),
revisions: service.listRevisions(actor, id).map((revision) => ({
id: revision.id,
description: revision.description,
@@ -65,6 +97,7 @@ export default async function PortalProjectPage({ params }: { params: Promise<{
sections={data.sections}
tasks={data.tasks}
revisions={data.revisions}
+ locale={locale.locale}
/>
);
}
diff --git a/app/portal/projects/[id]/portal-project-client.tsx b/app/portal/projects/[id]/portal-project-client.tsx
index fcb12c4..0b76a72 100644
--- a/app/portal/projects/[id]/portal-project-client.tsx
+++ b/app/portal/projects/[id]/portal-project-client.tsx
@@ -1,15 +1,24 @@
"use client";
import { useState } from "react";
-import { format } from "date-fns";
-import { tr } from "date-fns/locale";
import { Card, CardContent, Badge, Button, Textarea, Label } from "poyraz-ui/atoms";
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "poyraz-ui/molecules";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+ DialogFooter,
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+ toast,
+} from "poyraz-ui/molecules";
import { CheckCircle2, Clock, MessageSquare, Loader2, RefreshCw } from "lucide-react";
-import { toast } from "poyraz-ui/molecules";
import { createRevisionRequest } from "./actions";
-
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "poyraz-ui/molecules";
+import { useTranslations } from "@/components/i18n/i18n-provider";
+import { formatDate, formatDateTime } from "@/lib/i18n/format";
export type PortalProjectDetail = {
id: string;
@@ -48,150 +57,174 @@ type PortalProjectClientProps = {
sections: PortalPlanningSection[];
tasks: PortalTask[];
revisions: PortalRevision[];
+ locale: string;
};
-export function PortalProjectClient({ project, sections, tasks, revisions }: PortalProjectClientProps) {
+export function PortalProjectClient({
+ project,
+ sections,
+ tasks,
+ revisions,
+ locale,
+}: PortalProjectClientProps) {
+ const t = useTranslations();
const [isSubmitting, setIsSubmitting] = useState(false);
const [openRevision, setOpenRevision] = useState(false);
- const handleRevision = async (e: React.FormEvent
) => {
- e.preventDefault();
+ async function handleRevision(event: React.FormEvent) {
+ event.preventDefault();
setIsSubmitting(true);
- const formData = new FormData(e.currentTarget);
+ const formData = new FormData(event.currentTarget);
+
try {
- const res = await createRevisionRequest(project.id, formData);
- if (res.error) throw new Error(res.error);
- toast.success("Revizyon talebiniz başarıyla iletildi.");
+ const response = await createRevisionRequest(project.id, formData);
+ if (response.error) throw new Error(response.error);
+ toast.success(t("portal.revision.success"));
setOpenRevision(false);
} catch (error: unknown) {
- toast.error(error instanceof Error ? error.message : "Revizyon talebi oluşturulamadı.");
+ toast.error(error instanceof Error ? error.message : t("portal.revision.error"));
} finally {
setIsSubmitting(false);
}
- };
+ }
- const pendingRevisions = revisions.filter((revision) => revision.status === 'pending' || revision.status === 'in_progress').length;
+ const pendingRevisions = revisions.filter(
+ (revision) => revision.status === "pending" || revision.status === "in_progress",
+ ).length;
const hasRevisionQuota = project.can_request_revision;
return (
- {/* Header Info */}
-
+
{project.name}
-
{project.status}
+
+ {projectStatusLabel(project.status, t)}
+
{hasRevisionQuota ? (
-
-
- Genel Bakış
+
+
+ {t("portal.tabs.overview")}
-
- Plan & Aşamalar
+
+ {t("portal.tabs.plan")}
-
- Revizyonlar
- {pendingRevisions > 0 && (
- {pendingRevisions}
- )}
+
+ {t("portal.tabs.revisions")}
+ {pendingRevisions > 0 ? (
+
+ {pendingRevisions}
+
+ ) : null}
-
+
-
- İlerleme Durumu
+
+ {t("portal.sections.progress")}
%{project.progress}
-
-
+
-
Projenizin anlık tamamlanma oranı.
+
{t("portal.labels.progress")}
-
-
- Yapılan İşler
+
+
+
+ {t("portal.sections.doneTasks")}
{tasks.length === 0 ? (
- Listelenecek görev bulunmuyor.
+ {t("portal.empty.tasks")}
) : (
-
+
{tasks.map((task) => (
- -
- {task.status === 'done' ? (
-
+
-
+ {task.status === "done" ? (
+
) : (
-
+
)}
-
+
{task.title}
- {task.date && (
-
- {format(new Date(task.date), 'd MMM yyyy', { locale: tr })}
+ {task.date ? (
+
+ {formatDate(task.date, locale)}
- )}
+ ) : null}
))}
@@ -204,21 +237,21 @@ export function PortalProjectClient({ project, sections, tasks, revisions }: Por
{sections.length === 0 ? (
-
- Henüz bir plan yüklenmemiş.
+
+ {t("portal.empty.plan")}
) : (
{sections.map((section) => (
-
+
{section.title}
- {section.type === 'milestone' ? 'Aşama' : section.type === 'deliverable' ? 'Teslimat' : 'Not'}
+ {section.type}
- {section.content}
+ {section.content}
))}
@@ -227,40 +260,43 @@ export function PortalProjectClient({ project, sections, tasks, revisions }: Por
-
-
- Kalan Hak:
{project.revision_quota !== null ? project.revision_quota : 'Sınırsız'}
+
+
+ {t("portal.labels.remainingQuota")}:
+
+ {project.revision_quota !== null ? project.revision_quota : t("portal.labels.unlimited")}
+
-
+
{revisions.length === 0 ? (
-
+
-
Henüz bir revizyon talebi oluşturmadınız.
- {hasRevisionQuota && (
-
setOpenRevision(true)}>Yeni Talep Oluştur
- )}
+
{t("portal.empty.revisions")}
+ {hasRevisionQuota ? (
+
setOpenRevision(true)}>
+ {t("portal.actions.newRequest")}
+
+ ) : null}
) : (
- {revisions.map((rev) => (
-
+ {revisions.map((revision) => (
+
-
+
- {format(new Date(rev.created_at), "d MMM yyyy, HH:mm", { locale: tr })}
+ {formatDateTime(revision.created_at, locale)}
-
- {rev.status === 'pending' ? 'Bekliyor' :
- rev.status === 'in_progress' ? 'İşleniyor' :
- rev.status === 'completed' ? 'Tamamlandı' : 'Reddedildi'}
+
+ {revisionStatusLabel(revision.status, t)}
-
{rev.description}
+
{revision.description}
))}
@@ -271,3 +307,16 @@ export function PortalProjectClient({ project, sections, tasks, revisions }: Por
);
}
+
+function projectStatusLabel(status: PortalProjectDetail["status"], t: ReturnType) {
+ if (status === "completed") return t("portal.status.project.completed");
+ if (status === "active") return t("portal.status.project.active");
+ return t("portal.status.project.waiting");
+}
+
+function revisionStatusLabel(status: PortalRevision["status"], t: ReturnType) {
+ if (status === "pending") return t("portal.status.revision.pending");
+ if (status === "in_progress") return t("portal.status.revision.inProgress");
+ if (status === "completed") return t("portal.status.revision.completed");
+ return t("portal.status.revision.rejected");
+}
diff --git a/app/portal/projects/page.tsx b/app/portal/projects/page.tsx
index cf6e36e..e70c7a1 100644
--- a/app/portal/projects/page.tsx
+++ b/app/portal/projects/page.tsx
@@ -1,25 +1,40 @@
import { Card, CardContent, Badge } from "poyraz-ui/atoms";
import { FolderKanban, Clock } from "lucide-react";
import Link from "next/link";
-import { format } from "date-fns";
-import { tr } from "date-fns/locale";
+import { formatDate } from "@/lib/i18n/format";
+import { getSqliteConnection } from "@/server/db/client";
+import { ContentTranslationService, getContentFallbackLocale } from "@/server/i18n/content";
+import { resolveRequestLocale } from "@/server/i18n/resolver";
+import { createTranslator } from "@/server/i18n/translator";
import { requirePortalBackend } from "@/server/web/portal";
export default async function PortalProjectsPage() {
+ const locale = await resolveRequestLocale();
+ const t = createTranslator(locale.locale, ["portal"]).t;
const { actor, service } = await requirePortalBackend();
- const projects = service.listProjects(actor);
+ const content = new ContentTranslationService(getSqliteConnection().db);
+ const localization = content.getPublicLocalizationContext();
+ const fallbackLocale = getContentFallbackLocale(locale.locale, localization);
+ const projectRows = service.listProjects(actor);
+ const projectTranslations = content.listBatch("project", projectRows.map((project) => project.id));
+ const projects = projectRows.map((project) => content.resolveEntity("project", project, {
+ locale: locale.locale,
+ fallbackLocale,
+ defaultLocale: locale.defaultLocale,
+ translations: projectTranslations.get(project.id) ?? [],
+ }));
return (
-
Projeleriniz
+ {t("portal.projects.title")}
{projects.length === 0 ? (
- Henüz size atanmış bir proje bulunmuyor.
+ {t("portal.projects.empty")}
) : projects.map((project) => (
@@ -29,19 +44,19 @@ export default async function PortalProjectsPage() {
{project.name}
- {project.status}
+ {project.status === "completed" ? t("portal.status.project.completed") : project.status === "active" ? t("portal.status.project.active") : t("portal.status.project.waiting")}
{project.dueDate && (
- Son Teslim: {format(new Date(project.dueDate), "d MMM yyyy", { locale: tr })}
+ {t("portal.labels.deadline")}: {formatDate(project.dueDate, locale.locale)}
)}
- İlerleme
+ {t("portal.labels.progress")}
%{project.progress}
diff --git a/app/portal/revisions/page.tsx b/app/portal/revisions/page.tsx
index bf83b34..3b61209 100644
--- a/app/portal/revisions/page.tsx
+++ b/app/portal/revisions/page.tsx
@@ -1,13 +1,28 @@
import { Card, CardContent, Badge } from "poyraz-ui/atoms";
import { Clock, MessageSquare } from "lucide-react";
import Link from "next/link";
-import { format } from "date-fns";
-import { tr } from "date-fns/locale";
+import { formatDateTime } from "@/lib/i18n/format";
+import { getSqliteConnection } from "@/server/db/client";
+import { ContentTranslationService, getContentFallbackLocale } from "@/server/i18n/content";
+import { resolveRequestLocale } from "@/server/i18n/resolver";
+import { createTranslator } from "@/server/i18n/translator";
import { requirePortalBackend } from "@/server/web/portal";
export default async function PortalRevisionsPage() {
+ const locale = await resolveRequestLocale();
+ const t = createTranslator(locale.locale, ["portal"]).t;
const { actor, service } = await requirePortalBackend();
- const projects = service.listProjects(actor);
+ const content = new ContentTranslationService(getSqliteConnection().db);
+ const localization = content.getPublicLocalizationContext();
+ const fallbackLocale = getContentFallbackLocale(locale.locale, localization);
+ const projectRows = service.listProjects(actor);
+ const projectTranslations = content.listBatch("project", projectRows.map((project) => project.id));
+ const projects = projectRows.map((project) => content.resolveEntity("project", project, {
+ locale: locale.locale,
+ fallbackLocale,
+ defaultLocale: locale.defaultLocale,
+ translations: projectTranslations.get(project.id) ?? [],
+ }));
const projectNames = new Map(projects.map((project) => [project.id, project.name]));
const revisions = service.listPortalRevisions(actor)
.filter((revision) => projectNames.has(revision.projectId));
@@ -15,14 +30,14 @@ export default async function PortalRevisionsPage() {
return (
-
Revizyon Taleplerim
+ {t("portal.revisions.mine")}
{revisions.length === 0 ? (
- Henüz bir revizyon talebinde bulunmadınız.
+ {t("portal.revisions.empty")}
) : revisions.map((revision) => (
@@ -31,17 +46,17 @@ export default async function PortalRevisionsPage() {
- {format(revision.createdAt, "d MMM yyyy, HH:mm", { locale: tr })}
+ {formatDateTime(revision.createdAt, locale.locale)}
- {revision.status === "pending" ? "Bekliyor" : revision.status === "in_progress" ? "İşleniyor" : revision.status === "completed" ? "Tamamlandı" : "Reddedildi"}
+ {revision.status === "pending" ? t("portal.status.revision.pending") : revision.status === "in_progress" ? t("portal.status.revision.inProgress") : revision.status === "completed" ? t("portal.status.revision.completed") : t("portal.status.revision.rejected")}
- Proje:
+ {t("portal.labels.project")}:
{projectNames.get(revision.projectId)}
@@ -50,7 +65,7 @@ export default async function PortalRevisionsPage() {
- Projeye Git →
+ {t("portal.labels.project")} →
diff --git a/app/portal/tasks/page.tsx b/app/portal/tasks/page.tsx
index 850a7a8..4b6d534 100644
--- a/app/portal/tasks/page.tsx
+++ b/app/portal/tasks/page.tsx
@@ -1,28 +1,50 @@
import { Card, CardContent, Badge } from "poyraz-ui/atoms";
import { CheckCircle2, Clock, CalendarDays, KanbanSquare } from "lucide-react";
-import { format } from "date-fns";
-import { tr } from "date-fns/locale";
+import { formatDate } from "@/lib/i18n/format";
+import { getSqliteConnection } from "@/server/db/client";
+import { ContentTranslationService, getContentFallbackLocale } from "@/server/i18n/content";
+import { resolveRequestLocale } from "@/server/i18n/resolver";
+import { createTranslator } from "@/server/i18n/translator";
import { requirePortalBackend } from "@/server/web/portal";
export default async function PortalTasksPage() {
+ const locale = await resolveRequestLocale();
+ const t = createTranslator(locale.locale, ["portal"]).t;
const { actor, service } = await requirePortalBackend();
- const projects = service.listProjects(actor);
+ const content = new ContentTranslationService(getSqliteConnection().db);
+ const localization = content.getPublicLocalizationContext();
+ const fallbackLocale = getContentFallbackLocale(locale.locale, localization);
+ const projectRows = service.listProjects(actor);
+ const projectTranslations = content.listBatch("project", projectRows.map((project) => project.id));
+ const projects = projectRows.map((project) => content.resolveEntity("project", project, {
+ locale: locale.locale,
+ fallbackLocale,
+ defaultLocale: locale.defaultLocale,
+ translations: projectTranslations.get(project.id) ?? [],
+ }));
const projectNames = new Map(projects.map((project) => [project.id, project.name]));
- const tasks = service.listTasks(actor)
+ const taskRows = service.listTasks(actor)
.filter((task) => task.projectId && projectNames.has(task.projectId) && task.status !== "cancelled")
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
+ const taskTranslations = content.listBatch("task", taskRows.map((task) => task.id));
+ const tasks = taskRows.map((task) => content.resolveEntity("task", task, {
+ locale: locale.locale,
+ fallbackLocale,
+ defaultLocale: locale.defaultLocale,
+ translations: taskTranslations.get(task.id) ?? [],
+ }));
return (
-
Yapılan Görevler
+ {t("portal.tasks.title")}
{tasks.length === 0 ? (
- Henüz sizinle paylaşılan bir görev bulunmuyor.
+ {t("portal.tasks.empty")}
) : tasks.map((task) => {
const isDone = task.status === "done";
@@ -36,7 +58,7 @@ export default async function PortalTasksPage() {
{task.title}
- {task.status === "todo" ? "Bekliyor" : task.status === "in_progress" ? "İşleniyor" : "Tamamlandı"}
+ {task.status === "todo" ? t("portal.status.task.todo") : task.status === "in_progress" ? t("portal.status.task.inProgress") : t("portal.status.task.done")}
@@ -46,7 +68,7 @@ export default async function PortalTasksPage() {
- {date ? format(new Date(date), "d MMM yyyy", { locale: tr }) : "Tarih yok"}
+ {date ? formatDate(date, locale.locale) : "-"}
{isDone ?
:
}
diff --git a/app/register/page.tsx b/app/register/page.tsx
index 7847540..c4e8173 100644
--- a/app/register/page.tsx
+++ b/app/register/page.tsx
@@ -1,6 +1,7 @@
import { signup } from "@/app/login/actions";
import { AuthPageShell } from "@/components/auth/auth-page-shell";
import { ErrorToaster } from "@/components/error-toaster";
+import { LocaleSelectForm } from "@/components/i18n/locale-select-form";
import { getFirstFreelancerSetupState } from "@/server/auth/setup";
import { LockKeyhole, Mail, UserPlus } from "lucide-react";
import Link from "next/link";
@@ -8,9 +9,30 @@ import { redirect } from "next/navigation";
import { Input, Label } from "poyraz-ui/atoms";
import { SubmitButton } from "@/components/auth/submit-button";
import { getPublicBranding } from "@/server/branding/runtime";
+import { getSqliteConnection } from "@/server/db/client";
+import { ContentTranslationService } from "@/server/i18n/content";
+import { resolveRequestLocale } from "@/server/i18n/resolver";
+import { createTranslator } from "@/server/i18n/translator";
+import type { TranslationValues } from "@/lib/i18n";
export const dynamic = "force-dynamic";
+function firstParam(value: string | string[] | undefined): string | null {
+ if (Array.isArray(value)) return value[0] ?? null;
+ return value ?? null;
+}
+
+function resolveAuthMessage(
+ code: string | null,
+ fallback: string | null,
+ t: (key: string, values?: TranslationValues) => string,
+): string | null {
+ if (!code) return fallback;
+ const key = code.startsWith("auth.") ? code : `auth.${code}`;
+ const message = t(key);
+ return message === key ? fallback : message;
+}
+
export default async function RegisterPage({
searchParams,
}: {
@@ -19,46 +41,63 @@ export default async function RegisterPage({
const setupState = await getFirstFreelancerSetupState();
if (setupState.errorMessage) {
- redirect(`/login?error=true&message=${encodeURIComponent(setupState.errorMessage)}`);
+ redirect("/login?error=true&code=auth.messages.setupStateError");
}
if (!setupState.available) {
- redirect(
- `/login?error=true&message=${encodeURIComponent(
- "Kayıt kapalı. Bu Neta kurulumunda ilk admin hesabı zaten oluşturulmuş.",
- )}`,
- );
+ redirect("/login?error=true&code=auth.messages.setupUnavailable");
}
const resolvedParams = await searchParams;
- const error = resolvedParams?.error;
- const message = resolvedParams?.message;
+ const error = firstParam(resolvedParams?.error);
+ const code = firstParam(resolvedParams?.code);
+ const rawMessage = firstParam(resolvedParams?.message);
const branding = getPublicBranding();
+ const locale = await resolveRequestLocale();
+ const t = createTranslator(locale.locale, ["auth"]).t;
+ const localization = new ContentTranslationService(getSqliteConnection().db).getPublicLocalizationContext();
+ const message = resolveAuthMessage(code, rawMessage, t);
+ const marketing = {
+ headline: t("auth.marketing.headline"),
+ description: t("auth.marketing.description", { app: branding.organizationName ?? branding.applicationName }),
+ openSource: t("auth.marketing.openSource"),
+ github: t("auth.marketing.github"),
+ via: t("auth.marketing.via"),
+ builtBy: t("auth.marketing.builtBy"),
+ highlights: [
+ t("auth.highlights.clients"),
+ t("auth.highlights.calendar"),
+ t("auth.highlights.finance"),
+ t("auth.highlights.reports"),
+ ] as [string, string, string, string],
+ };
return (
<>
- {error && message &&
}
+ {error && message ?
: null}
+
+ }
+ secondaryAction={null}
+ footer={
+
+ {t("auth.forgot.back")}
+
+ }
+ />
+ );
+}
diff --git a/components/auth/auth-page-shell.tsx b/components/auth/auth-page-shell.tsx
index a3b013c..418a18d 100644
--- a/components/auth/auth-page-shell.tsx
+++ b/components/auth/auth-page-shell.tsx
@@ -27,14 +27,18 @@ type AuthPageShellProps = {
form: ReactNode;
secondaryAction?: ReactNode;
footer: ReactNode;
+ marketing?: {
+ headline: string;
+ description: string;
+ openSource: string;
+ github: string;
+ via: string;
+ builtBy: string;
+ highlights: [string, string, string, string];
+ };
};
-const highlights = [
- { label: "Müşteriler", icon: Kanban },
- { label: "Takvim", icon: CalendarDays },
- { label: "Finans", icon: Wallet },
- { label: "Raporlar", icon: BarChart3 },
-];
+const highlightIcons = [Kanban, CalendarDays, Wallet, BarChart3];
export function AuthPageShell({
branding,
@@ -43,6 +47,15 @@ export function AuthPageShell({
form,
secondaryAction,
footer,
+ marketing = {
+ headline: "Freelancer işlerini, müşterilerini ve finansını tek yerde yönet.",
+ description: `${branding.applicationName}, günlük operasyonunu, projelerini, side projectlerini ve temel finans durumunu sade raporlarla takip etmen için tasarlanır.`,
+ openSource: "Açık kaynak ve self-host edilebilir.",
+ github: "GitHub",
+ via: "üzerinden ulaşabilirsin.",
+ builtBy: "tarafından kodlandı.",
+ highlights: ["Müşteriler", "Takvim", "Finans", "Raporlar"],
+ },
}: AuthPageShellProps) {
const reducedMotion = useReducedMotion();
@@ -87,12 +100,10 @@ export function AuthPageShell({
variant="display"
className="max-w-2xl text-5xl font-semibold leading-[1.02] text-primary-foreground"
>
- Freelancer işlerini, müşterilerini ve finansını tek yerde yönet.
+ {marketing.headline}
- {branding.applicationName}, günlük operasyonunu, projelerini, side projectlerini ve
- temel finans durumunu sade raporlarla takip etmen için
- tasarlanır.
+ {marketing.description}
@@ -102,15 +113,15 @@ export function AuthPageShell({
transition={{ duration: reducedMotion ? 0 : 0.55, delay: 0.16 }}
className="mt-10 grid max-w-xl grid-cols-2 gap-3"
>
- {highlights.map((item) => {
- const Icon = item.icon;
+ {marketing.highlights.map((label, index) => {
+ const Icon = highlightIcons[index] ?? Kanban;
return (
- {item.label}
+ {label}
);
})}
@@ -118,15 +129,15 @@ export function AuthPageShell({
-
Açık kaynak ve self-host edilebilir.{" "}
+
{marketing.openSource}{" "}
- GitHub
+ {marketing.github}
-
üzerinden ulaşabilirsin.
+
{marketing.via}
Poyraz Avsever
-
tarafından kodlandı.
+
{marketing.builtBy}