Skip to content
2 changes: 1 addition & 1 deletion src/app/(app)/gallery/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export default function GalleryPage() {

return (
<section className="w-full px-4 py-8 sm:px-6 lg:px-8">
<div className="mx-auto w-full max-w-6xl space-y-6">
<div className="mx-auto w-full max-w-7xl space-y-6">
<header className="space-y-1 mx-auto w-full max-w-2xl">
<h1 className="text-3xl font-semibold tracking-tight">Gallery</h1>
<p className="mt-1 text-sm text-muted-foreground">
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod
<PushPermissionRequest />
<div className="flex min-h-dvh bg-background">
<DesktopSidebar primaryLockup={brandContext.primaryLockup} />
<div className="flex min-w-0 flex-1 flex-col md:pl-72">
<div className="flex min-w-0 flex-1 flex-col md:pl-20 lg:pl-72">
<MobileHeader primaryLockup={brandContext.primaryLockup} />
<div className="flex min-h-0 flex-1">
<main className="min-w-0 flex-1 overflow-y-auto pb-[calc(4rem+var(--safe-area-inset-bottom))] md:pb-0">
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/member/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export default function MemberProfilePage() {
)}

{activeTab === "gallery" && member ? (
<div className="space-y-4 mx-auto max-w-6xl">
<div className="space-y-4 mx-auto max-w-7xl">
<MemberGalleryTab familyId={familyId} memberId={member.id} memberName={member.name} />
</div>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/members/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default function MembersPage() {
const isLoading = managementContext.isLoading || membersQuery.isLoading;

return (
<section className="mx-auto w-full max-w-6xl space-y-6 px-4 py-8 sm:px-6 lg:px-8">
<section className="mx-auto w-full max-w-7xl space-y-6 px-4 py-8 sm:px-6 lg:px-8">
<header className="mx-auto w-full max-w-2xl flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<h1 className="text-3xl font-semibold tracking-tight">Family Members</h1>
Expand Down
212 changes: 107 additions & 105 deletions src/app/(app)/notifications/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,119 +154,121 @@ export default function NotificationsPage() {
};

return (
<section className="mx-auto w-full max-w-2xl space-y-6 px-4 py-8 sm:px-6 lg:px-8">
{/* Header */}
<header className="space-y-1">
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-3xl font-semibold tracking-tight">Notifications</h1>
<p className="mt-1 text-sm text-muted-foreground">
Tag events, invites, and family activity.
</p>
<section className="w-full px-4 py-8 sm:px-6 lg:px-8">
<div className="mx-auto w-full space-y-6 max-w-2xl">
{/* Header */}
<header className="space-y-1">
<div className="flex items-start justify-between gap-4">
<div>
<h1 className="text-3xl font-semibold tracking-tight">Notifications</h1>
<p className="mt-1 text-sm text-muted-foreground">
Tag events, invites, and family activity.
</p>
</div>
{showMarkAll && (
<Button
variant="ghost"
size="sm"
className="shrink-0 text-xs"
onClick={() => void handleMarkAllAsRead()}
disabled={markAllAsReadMutation.isPending}
>
Mark all as read
</Button>
)}
</div>
{showMarkAll && (
<Button
variant="ghost"
size="sm"
className="shrink-0 text-xs"
onClick={() => void handleMarkAllAsRead()}
disabled={markAllAsReadMutation.isPending}
</header>

{/* Filter chips */}
<div
className="flex flex-wrap gap-2"
role="tablist"
aria-label="Notification filters"
>
{visibleFilters.map(({ label, value }) => (
<button
key={value}
role="tab"
aria-selected={activeFilter === value}
onClick={() => setActiveFilter(value)}
className={`rounded-full border px-4 py-1.5 text-sm font-medium transition-colors ${
activeFilter === value
? "border-primary bg-primary text-primary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2"
: "border-border bg-card text-muted-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2"
}`}
>
Mark all as read
</Button>
)}
{label}
</button>
))}
</div>
</header>

{/* Filter chips */}
<div
className="flex flex-wrap gap-2"
role="tablist"
aria-label="Notification filters"
>
{visibleFilters.map(({ label, value }) => (
<button
key={value}
role="tab"
aria-selected={activeFilter === value}
onClick={() => setActiveFilter(value)}
className={`rounded-full border px-4 py-1.5 text-sm font-medium transition-colors ${
activeFilter === value
? "border-primary bg-primary text-primary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2"
: "border-border bg-card text-muted-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-offset-2"
}`}
>
{label}
</button>
))}
</div>

{hasNoFamily ? (
<div className="flex flex-col items-center gap-3 rounded-3xl border bg-card py-16 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-muted">
<Bell className="size-5 text-muted-foreground" aria-hidden="true" />
{hasNoFamily ? (
<div className="flex flex-col items-center gap-3 rounded-3xl border bg-card py-16 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-muted">
<Bell className="size-5 text-muted-foreground" aria-hidden="true" />
</div>
<div>
<p className="font-medium">No family membership found</p>
<p className="mt-0.5 text-sm text-muted-foreground">
Join a family to view your notifications.
</p>
</div>
</div>
<div>
<p className="font-medium">No family membership found</p>
<p className="mt-0.5 text-sm text-muted-foreground">
Join a family to view your notifications.
</p>
) : isLoading ? (
<NotificationsListSkeleton />
) : notificationsQuery.error ? (
<div className="rounded-3xl border bg-card px-6 py-14 text-center">
<p className="font-medium">Unable to load notifications</p>
<p className="mt-1 text-sm text-muted-foreground">{notificationsQuery.error.message}</p>
<Button className="mt-4" size="sm" onClick={() => void notificationsQuery.refetch()}>
Retry
</Button>
</div>
</div>
) : isLoading ? (
<NotificationsListSkeleton />
) : notificationsQuery.error ? (
<div className="rounded-3xl border bg-card px-6 py-14 text-center">
<p className="font-medium">Unable to load notifications</p>
<p className="mt-1 text-sm text-muted-foreground">{notificationsQuery.error.message}</p>
<Button className="mt-4" size="sm" onClick={() => void notificationsQuery.refetch()}>
Retry
</Button>
</div>
) : filtered.length === 0 ? (
/* Empty state */
<div className="flex flex-col items-center gap-3 rounded-3xl border bg-card py-16 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-muted">
<Bell className="size-5 text-muted-foreground" aria-hidden="true" />
) : filtered.length === 0 ? (
/* Empty state */
<div className="flex flex-col items-center gap-3 rounded-3xl border bg-card py-16 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-muted">
<Bell className="size-5 text-muted-foreground" aria-hidden="true" />
</div>
<div>
<p className="font-medium">No notifications yet</p>
<p className="mt-0.5 text-sm text-muted-foreground">
Tag events and family activity will appear here.
</p>
</div>
</div>
<div>
<p className="font-medium">No notifications yet</p>
<p className="mt-0.5 text-sm text-muted-foreground">
Tag events and family activity will appear here.
</p>
) : (
<div className="space-y-6">
{/* Unread section */}
{unread.length > 0 && (
<section aria-label="Unread notifications">
<h2 className="mb-3 text-xs font-semibold uppercase tracking-widest text-muted-foreground">
New
</h2>
<div className="space-y-2">
{unread.map((notification) => (
<NotificationCard key={notification.id} notification={notification} initialIsRead={snapshot?.get(notification.id) ?? notification.isRead} />
))}
</div>
</section>
)}

{/* Read section */}
{read.length > 0 && (
<section aria-label="Earlier notifications">
<h2 className="mb-3 text-xs font-semibold uppercase tracking-widest text-muted-foreground">
Earlier
</h2>
<div className="space-y-2">
{read.map((notification) => (
<NotificationCard key={notification.id} notification={notification} initialIsRead={snapshot?.get(notification.id) ?? notification.isRead} />
))}
</div>
</section>
)}
</div>
</div>
) : (
<div className="space-y-6">
{/* Unread section */}
{unread.length > 0 && (
<section aria-label="Unread notifications">
<h2 className="mb-3 text-xs font-semibold uppercase tracking-widest text-muted-foreground">
New
</h2>
<div className="space-y-2">
{unread.map((notification) => (
<NotificationCard key={notification.id} notification={notification} initialIsRead={snapshot?.get(notification.id) ?? notification.isRead} />
))}
</div>
</section>
)}

{/* Read section */}
{read.length > 0 && (
<section aria-label="Earlier notifications">
<h2 className="mb-3 text-xs font-semibold uppercase tracking-widest text-muted-foreground">
Earlier
</h2>
<div className="space-y-2">
{read.map((notification) => (
<NotificationCard key={notification.id} notification={notification} initialIsRead={snapshot?.get(notification.id) ?? notification.isRead} />
))}
</div>
</section>
)}
</div>
)}
)}
</div>
</section>
);
}
Expand Down
10 changes: 6 additions & 4 deletions src/app/(app)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useMemo } from "react";

import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
import { ComposerEntry } from "~/components/feed/composer-entry";
import { useGlobalComposer } from "~/components/feed/global-composer-provider";
import { PostCard } from "~/components/feed/post-card";
import type { PostCardData } from "~/components/feed/post-card";
import { Skeleton } from "~/components/ui/skeleton";
Expand Down Expand Up @@ -101,7 +102,7 @@ function mapFeedItemToPostCardData(item: {
};
}

function FeedEmptyState() {
function FeedEmptyState({ onCreateFirstMemory }: { onCreateFirstMemory: () => void }) {
return (
<section className="rounded-3xl border border-dashed border-border/80 bg-card/70 px-6 py-10 text-center">
<div className="mx-auto flex size-12 items-center justify-center rounded-full border border-border bg-muted">
Expand All @@ -111,7 +112,7 @@ function FeedEmptyState() {
<p className="mx-auto mt-2 max-w-sm text-muted-foreground text-sm sm:text-base">
Start your family timeline by creating the first memory post.
</p>
<Button type="button" className="mt-5" size="lg">
<Button type="button" className="mt-5" size="lg" onClick={onCreateFirstMemory}>
Create first memory
</Button>
</section>
Expand Down Expand Up @@ -170,6 +171,7 @@ function FeedSkeletonList() {
}

export default function FeedPage() {
const { openComposer } = useGlobalComposer();
const managementContext = api.family.getManagementContext.useQuery(undefined, {
retry: false,
refetchOnWindowFocus: false,
Expand Down Expand Up @@ -236,7 +238,7 @@ export default function FeedPage() {

return (
<>
<section className="px-4 pb-6 pt-5 sm:px-6 md:px-8">
<section className="w-full px-4 py-8 sm:px-6 lg:px-8">
<div className="mx-auto w-full max-w-2xl space-y-4">
<header className="space-y-1 mx-auto w-full max-w-2xl">
<h1 className="text-3xl font-semibold tracking-tight">Feed</h1>
Expand Down Expand Up @@ -291,7 +293,7 @@ export default function FeedPage() {
isAdmin={isAdmin}
/>
) : (
<FeedEmptyState />
<FeedEmptyState onCreateFirstMemory={() => openComposer()} />
)}
</div>
</section>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(app)/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export default function ProfilePage() {
)}

{activeTab === "gallery" && member ? (
<div className="space-y-4 mx-auto max-w-6xl">
<div className="space-y-4 mx-auto max-w-7xl">
<MemberGalleryTab familyId={familyId} memberId={member.id} memberName={member.name} />
</div>
) : null}
Expand Down
6 changes: 3 additions & 3 deletions src/app/(app)/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ export default function SettingsLayout({
}

return (
<div className="mx-auto w-full max-w-6xl px-4 py-8 sm:px-6 lg:px-8">
<div className="mx-auto w-full max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<div className="mb-6 flex flex-wrap items-center justify-between gap-3">
<h1 className="font-semibold text-2xl tracking-tight">Settings</h1>
<h1 className="font-semibold text-3xl tracking-tight">Settings</h1>
<LogoutButton variant="destructive" size="sm" className="gap-2">
<Logout className="size-4" />
<span>Log out</span>
Expand Down Expand Up @@ -154,7 +154,7 @@ export default function SettingsLayout({
</DropdownMenu>
</div>

<nav aria-label="Settings navigation" className="hidden shrink-0 md:block md:w-52">
<nav aria-label="Settings navigation" className="hidden shrink-0 md:block md:w-48">
<ul className="flex gap-1 rounded-xl border bg-card/60 p-1 md:flex-col">
{visibleSettingsNav.map((item) => {
const active = isActivePath(pathname, item.href);
Expand Down
Loading
Loading