Skip to content
27 changes: 27 additions & 0 deletions src/app/(app)/gallery/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Skeleton } from "~/components/ui/skeleton";

export default function GalleryLoading() {
return (
<section className="w-full px-4 py-8 sm:px-6 lg:px-8">
<div className="mx-auto w-full max-w-7xl space-y-6">
{/* Header skeleton */}
<header className="space-y-1 mx-auto w-full max-w-2xl">
<Skeleton className="h-9 w-32 rounded-lg" />
<Skeleton className="h-4 w-64 rounded-lg mt-2" />
</header>

{/* Gallery grid skeleton */}
<section className="space-y-4" aria-hidden>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 sm:gap-3 lg:grid-cols-4">
{Array.from({ length: 8 }).map((_, index) => (
<Skeleton
key={`gallery-loading-${index}`}
className="aspect-4/5 rounded-2xl border border-border/70"
/>
))}
</div>
</section>
</div>
</section>
);
}
29 changes: 29 additions & 0 deletions src/app/(app)/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Skeleton } from "~/components/ui/skeleton";

export default function AppRouteLoading() {
return (
<div className="mx-auto w-full max-w-3xl space-y-4 px-4 py-6 sm:px-6 lg:px-8" aria-live="polite" aria-busy="true">
<div className="space-y-2">
<Skeleton className="h-8 w-44 rounded-full" />
<Skeleton className="h-4 w-64 rounded-full" />
</div>

<div className="space-y-3">
{Array.from({ length: 3 }).map((_, index) => (
<section key={index} className="rounded-3xl border border-border/80 bg-card/90 p-4 sm:p-5">
<div className="flex items-center gap-3">
<Skeleton className="size-10 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-3.5 w-28 rounded-full" />
<Skeleton className="h-3 w-16 rounded-full" />
</div>
</div>
<Skeleton className="mt-4 h-3.5 w-11/12 rounded-full" />
<Skeleton className="mt-2 h-3.5 w-9/12 rounded-full" />
<Skeleton className="mt-4 aspect-video rounded-2xl" />
</section>
))}
</div>
</div>
);
}
22 changes: 20 additions & 2 deletions src/app/(app)/member/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ import { cn } from "~/lib/utils";

type ProfileTab = "posts" | "tagged" | "liked" | "gallery";

const tabs: { id: ProfileTab; label: string; icon: typeof Dash }[] = [
const allTabs: { id: ProfileTab; label: string; icon: typeof Dash }[] = [
{ id: "posts", label: "Posts", icon: Dash },
{ id: "gallery", label: "Gallery", icon: Image },
{ id: "tagged", label: "Mentions & Tags", icon: UserSquare },
Expand Down Expand Up @@ -164,6 +164,24 @@ export default function MemberProfilePage() {
} satisfies FamilyMemberProfile)
: undefined;

// Filter tabs based on member status (hide Posts and Liked for unclaimed members)
const isMemberClaimed = member?.status === "claimed";
const visibleTabs = allTabs.filter((tab) => {
if (!isMemberClaimed && (tab.id === "posts" || tab.id === "liked")) {
return false;
}
return true;
});

// Reset active tab if it's hidden for unclaimed members
const isActivTabVisible = visibleTabs.some((tab) => tab.id === activeTab);
if (!isActivTabVisible && visibleTabs.length > 0) {
const firstVisibleTab = visibleTabs[0];
if (firstVisibleTab && activeTab !== firstVisibleTab.id) {
setActiveTab(firstVisibleTab.id);
}
}

const viewerRole = managementContext.data?.role?.toLowerCase();
const callerRole: "owner" | "admin" | "member" =
viewerRole === "owner" || viewerRole === "admin" || viewerRole === "member"
Expand Down Expand Up @@ -221,7 +239,7 @@ export default function MemberProfilePage() {

<section>
<div className="flex w-full max-w-2xl mx-auto border-b">
{tabs.map(({ id, label, icon: Icon }) => (
{visibleTabs.map(({ id, label, icon: Icon }) => (
<button
key={id}
type="button"
Expand Down
46 changes: 46 additions & 0 deletions src/app/(app)/members/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Skeleton } from "~/components/ui/skeleton";

export default function MembersLoading() {
return (
<section className="mx-auto w-full max-w-7xl space-y-6 px-4 py-8 sm:px-6 lg:px-8">
{/* Header skeleton */}
<header className="mx-auto w-full max-w-2xl flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<Skeleton className="h-9 w-48 rounded-lg" />
<Skeleton className="h-4 w-64 rounded-lg mt-2" />
</div>
<Skeleton className="h-10 w-40 rounded-lg" />
</header>

{/* Filters and search skeleton */}
<section className="space-y-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex flex-wrap gap-2">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className="h-9 w-24 rounded-lg" />
))}
</div>
<Skeleton className="h-10 w-80 rounded-lg" />
</div>

{/* Grid skeleton */}
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3" aria-hidden>
{Array.from({ length: 6 }).map((_, index) => (
<article key={`member-skeleton-${index}`} className="rounded-3xl border bg-card p-4 shadow-sm">
<div className="flex items-start gap-3">
<Skeleton className="size-12 shrink-0 rounded-full border" />
<div className="min-w-0 flex-1 space-y-2">
<div className="flex items-center justify-between gap-2">
<Skeleton className="h-4 w-28 rounded-full" />
<Skeleton className="h-5 w-16 rounded-full" />
</div>
<Skeleton className="h-3 w-full rounded-full" />
</div>
</div>
</article>
))}
</div>
</section>
</section>
);
}
Loading
Loading