diff --git a/src/app/(app)/admin/haneler/page.tsx b/src/app/(app)/admin/haneler/page.tsx
new file mode 100644
index 0000000..046001c
--- /dev/null
+++ b/src/app/(app)/admin/haneler/page.tsx
@@ -0,0 +1,18 @@
+import type { Metadata } from "next";
+import { PageHeader } from "@/components/page-header";
+import { HouseholdsAdmin } from "@/features/admin-households/components/households-admin";
+import { getAdminHouseholdsData } from "@/features/admin-households/queries";
+import { tr } from "@/i18n/tr";
+import { requireRole } from "@/lib/dal";
+
+export const metadata: Metadata = { title: tr.adminHouseholds.title };
+
+export default async function AdminHouseholdsPage() {
+ const [admin, data] = await Promise.all([requireRole("admin"), getAdminHouseholdsData()]);
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/app/(app)/ayarlar/page.tsx b/src/app/(app)/ayarlar/page.tsx
new file mode 100644
index 0000000..47f0125
--- /dev/null
+++ b/src/app/(app)/ayarlar/page.tsx
@@ -0,0 +1,50 @@
+import type { Metadata } from "next";
+import { notFound } from "next/navigation";
+import { PageHeader } from "@/components/page-header";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { HouseholdSettingsForm } from "@/features/households/components/household-settings-form";
+import { SettingsMembers } from "@/features/households/components/settings-members";
+import { getHouseholdDetail } from "@/features/households/service";
+import { tr } from "@/i18n/tr";
+import { requireActiveHousehold } from "@/lib/dal";
+
+export const metadata: Metadata = { title: tr.settings.title };
+
+export default async function SettingsPage() {
+ const { user, household } = await requireActiveHousehold("viewer");
+ const detail = await getHouseholdDetail(user.id, household.id);
+ if (!detail) notFound();
+ const isOwner = household.role === "owner";
+ const t = tr.settings;
+
+ return (
+ <>
+
+
+
+
+ {t.household}
+ {isOwner ? t.householdDescription : t.ownerOnly}
+
+
+
+
+
+
+
+ {t.members}
+ {t.membersDescription}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx
index d3879db..c69c6a3 100644
--- a/src/app/(app)/dashboard/page.tsx
+++ b/src/app/(app)/dashboard/page.tsx
@@ -4,7 +4,7 @@ import { PageHeader } from "@/components/page-header";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { SummaryCardsSkeleton } from "@/features/dashboard/components/summary-cards-skeleton";
import { tr } from "@/i18n/tr";
-import { requireSession } from "@/lib/dal";
+import { requireActiveHousehold } from "@/lib/dal";
import { cn } from "@/lib/utils";
import { formatMoney } from "@/shared/format";
@@ -52,12 +52,12 @@ async function SummaryCards() {
}
export default async function DashboardPage() {
- const user = await requireSession();
+ const { user, household } = await requireActiveHousehold("viewer");
return (
<>
}>
diff --git a/src/app/(app)/hane-yok/page.tsx b/src/app/(app)/hane-yok/page.tsx
new file mode 100644
index 0000000..13570a3
--- /dev/null
+++ b/src/app/(app)/hane-yok/page.tsx
@@ -0,0 +1,39 @@
+import { House } from "lucide-react";
+import type { Metadata } from "next";
+import Link from "next/link";
+import { redirect } from "next/navigation";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { tr } from "@/i18n/tr";
+import { getHouseholdContext } from "@/lib/dal";
+
+export const metadata: Metadata = { title: tr.households.noHousehold.title };
+
+/** Hiçbir haneye üye olmayan kullanıcılar (ör. hanesi olmayan admin) buraya yönlenir. */
+export default async function NoHouseholdPage() {
+ const ctx = await getHouseholdContext();
+ if (!ctx) redirect("/login");
+ if (ctx.household) redirect("/dashboard");
+ const t = tr.households.noHousehold;
+ return (
+
+
+
+
+
+
+ {t.title}
+ {t.description}
+
+ {ctx.user.role === "admin" ? (
+
+ {t.adminHint}
+
+
+ ) : null}
+
+
+ );
+}
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx
index 439af9a..8d26e58 100644
--- a/src/app/(app)/layout.tsx
+++ b/src/app/(app)/layout.tsx
@@ -1,3 +1,4 @@
+import { redirect } from "next/navigation";
import { Suspense } from "react";
import { AppHeader } from "@/components/layout/app-header";
import { AppShellFallback } from "@/components/layout/app-shell-fallback";
@@ -5,8 +6,8 @@ import { AppSidebar } from "@/components/layout/app-sidebar";
import { BottomNav } from "@/components/layout/bottom-nav";
import { QuickAddProvider } from "@/components/layout/quick-add";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
-import { MOCK_HOUSEHOLDS } from "@/features/households/mock";
-import { requireSession } from "@/lib/dal";
+import { hasHouseholdRole } from "@/features/households/roles";
+import { getHouseholdContext } from "@/lib/dal";
import { APP_VERSION } from "@/lib/version";
/**
@@ -23,18 +24,26 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
}
async function AuthenticatedShell({ children }: { children: React.ReactNode }) {
- const user = await requireSession();
+ const ctx = await getHouseholdContext();
+ if (!ctx) redirect("/login");
+ const { user, household, memberships } = ctx;
+ const canWrite = household ? hasHouseholdRole(household.role, "member") : false;
return (
- {/* FAZ 5: kullanıcının üye olduğu haneler */}
-
+
{/* Mobilde alt menü yüksekliği kadar boşluk */}
{children}
-
+
);
diff --git a/src/components/layout/app-header.tsx b/src/components/layout/app-header.tsx
index d0e0d20..291acf7 100644
--- a/src/components/layout/app-header.tsx
+++ b/src/components/layout/app-header.tsx
@@ -11,18 +11,23 @@ import { UserMenu } from "./user-menu";
export function AppHeader({
user,
households,
+ activeHouseholdId,
+ canWrite,
version,
}: {
user: SessionUser;
households: HouseholdSummary[];
+ activeHouseholdId: string | undefined;
+ /** Aktif hanede en az "member" mi? (İzleyici veri giremez.) */
+ canWrite: boolean;
version: string;
}) {
return (
-
+
-
+ {canWrite ? : null}
diff --git a/src/components/layout/bottom-nav.tsx b/src/components/layout/bottom-nav.tsx
index ce8bc15..812d6d2 100644
--- a/src/components/layout/bottom-nav.tsx
+++ b/src/components/layout/bottom-nav.tsx
@@ -40,7 +40,7 @@ function BottomNavLink({ item, pathname }: { item: NavItem; pathname: string })
}
/** Mobil alt gezinme çubuğu (md altı). */
-export function BottomNav() {
+export function BottomNav({ canWrite }: { canWrite: boolean }) {
const pathname = usePathname();
const { openQuickAdd } = useQuickAdd();
const { setOpenMobile } = useSidebar();
@@ -57,8 +57,10 @@ export function BottomNav() {
diff --git a/src/components/layout/household-switcher.tsx b/src/components/layout/household-switcher.tsx
index 733738f..73df1b0 100644
--- a/src/components/layout/household-switcher.tsx
+++ b/src/components/layout/household-switcher.tsx
@@ -1,7 +1,8 @@
"use client";
import { House } from "lucide-react";
-import { useState } from "react";
+import { useTransition } from "react";
+import { toast } from "sonner";
import {
Select,
SelectContent,
@@ -9,17 +10,38 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
+import { switchHouseholdAction } from "@/features/households/actions";
import type { HouseholdSummary } from "@/features/households/types";
import { tr } from "@/i18n/tr";
-/** Üst bar aktif hane seçici. FAZ 5: seçim sunucuda (cookie) saklanacak. */
-export function HouseholdSwitcher({ households }: { households: HouseholdSummary[] }) {
- const [value, setValue] = useState(households[0]?.id);
- const current = households.find((h) => h.id === value);
+/** Üst bar aktif hane seçici. Seçim sunucuda doğrulanır ve cookie'ye yazılır. */
+export function HouseholdSwitcher({
+ households,
+ activeId,
+}: {
+ households: HouseholdSummary[];
+ activeId: string | undefined;
+}) {
+ const [pending, startTransition] = useTransition();
+ const current = households.find((h) => h.id === activeId);
if (households.length === 0) return null;
+
+ function onChange(id: string) {
+ if (id === activeId) return;
+ startTransition(async () => {
+ const result = await switchHouseholdAction(id);
+ if (result.ok) toast.success(tr.household.switched(result.data.name));
+ else toast.error(result.error);
+ });
+ }
+
return (
-