feat(households): hane seçici, ayarlar, admin hane yönetimi ve hane-yok sayfası
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<PageHeader title={tr.adminHouseholds.title} description={tr.adminHouseholds.description} />
|
||||
<HouseholdsAdmin households={data.households} users={data.users} currentUserId={admin.id} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<PageHeader title={t.title} description={t.description} />
|
||||
<div className="grid gap-4 lg:grid-cols-2 lg:items-start">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t.household}</CardTitle>
|
||||
<CardDescription>{isOwner ? t.householdDescription : t.ownerOnly}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HouseholdSettingsForm settings={detail.settings} canEdit={isOwner} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t.members}</CardTitle>
|
||||
<CardDescription>{t.membersDescription}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SettingsMembers
|
||||
members={detail.members}
|
||||
householdName={detail.name}
|
||||
currentUserId={user.id}
|
||||
canManage={isOwner}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<PageHeader
|
||||
title={tr.dashboard.welcome(user.name.split(" ")[0] ?? user.name)}
|
||||
description={tr.dashboard.welcomeDescription}
|
||||
description={`${household.name} · ${tr.dashboard.welcomeDescription}`}
|
||||
/>
|
||||
<Suspense fallback={<SummaryCardsSkeleton />}>
|
||||
<SummaryCards />
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<Card className="max-w-md">
|
||||
<CardHeader>
|
||||
<span className="mb-2 flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||
<House className="size-6" />
|
||||
</span>
|
||||
<CardTitle className="text-xl">{t.title}</CardTitle>
|
||||
<CardDescription>{t.description}</CardDescription>
|
||||
</CardHeader>
|
||||
{ctx.user.role === "admin" ? (
|
||||
<CardContent className="flex flex-col items-start gap-3">
|
||||
<p className="text-sm text-muted-foreground">{t.adminHint}</p>
|
||||
<Button asChild>
|
||||
<Link href="/admin/haneler">{t.adminLink}</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
) : null}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<SidebarProvider>
|
||||
<QuickAddProvider>
|
||||
<AppSidebar isAdmin={user.role === "admin"} version={APP_VERSION} />
|
||||
<SidebarInset>
|
||||
{/* FAZ 5: kullanıcının üye olduğu haneler */}
|
||||
<AppHeader user={user} households={MOCK_HOUSEHOLDS} version={APP_VERSION} />
|
||||
<AppHeader
|
||||
user={user}
|
||||
households={memberships}
|
||||
activeHouseholdId={household?.id}
|
||||
canWrite={canWrite}
|
||||
version={APP_VERSION}
|
||||
/>
|
||||
{/* Mobilde alt menü yüksekliği kadar boşluk */}
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 pb-24 md:p-6 md:pb-6">{children}</div>
|
||||
</SidebarInset>
|
||||
<BottomNav />
|
||||
<BottomNav canWrite={canWrite} />
|
||||
</QuickAddProvider>
|
||||
</SidebarProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user