my-fullstack-ai-platform/app/dashboard/settings/team/page.tsx

156 lines
5.7 KiB
TypeScript

import { createClient } from "@/lib/supabase/server";
import { inviteUser, revokeInvitation } from "./actions";
import { Suspense } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { Trash2 } from "lucide-react";
async function TeamSettingsContent() {
const supabase = await createClient();
const { data: userData } = await supabase.auth.getUser();
const userId = userData?.user?.id;
if (!userId) return null;
// Get profile
const { data: profile } = await supabase
.from("profiles")
.select("organization_id, role")
.eq("id", userId)
.single();
if (!profile?.organization_id) return null;
// Get organization members
const { data: members } = await supabase
.from("profiles")
.select("id, email, role")
.eq("organization_id", profile.organization_id);
// Get pending invitations
const { data: invitations } = await supabase
.from("invitations")
.select("id, email, token, status, expires_at")
.eq("organization_id", profile.organization_id)
.eq("status", "pending")
.gt("expires_at", new Date().toISOString());
return (
<div className="p-8 max-w-4xl mx-auto space-y-8">
<div>
<h1 className="text-2xl font-semibold mb-1 text-foreground">Team Management</h1>
<p className="text-sm text-muted-foreground">Manage your organization&apos;s members and invitations.</p>
</div>
<div className="space-y-4">
<h2 className="text-lg font-medium text-foreground border-b border-border pb-2">Members</h2>
<div className="bg-card border border-border rounded-lg overflow-hidden shadow-sm">
<ul className="divide-y divide-border">
{members?.map((m) => (
<li key={m.id} className="p-4 flex items-center justify-between hover:bg-muted/50 transition-colors">
<div>
<div className="text-sm font-medium">{m.email}</div>
<div className="text-xs text-muted-foreground capitalize">{m.role}</div>
</div>
</li>
))}
</ul>
</div>
</div>
{profile.role === "owner" && (
<div className="space-y-4">
<h2 className="text-lg font-medium text-foreground border-b border-border pb-2">Invite New Member</h2>
<div className="bg-card border border-border rounded-lg p-4 shadow-sm">
<form action={inviteUser} className="flex gap-4">
<input
type="email"
name="email"
placeholder="Email address"
required
className="flex-1 px-3 py-2 bg-background border border-border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
<button
type="submit"
className="bg-primary text-primary-foreground px-4 py-2 rounded-md text-sm font-medium hover:bg-primary/90 transition-colors flex-shrink-0"
>
Send Invite
</button>
</form>
</div>
</div>
)}
{profile.role === "owner" && invitations && invitations.length > 0 && (
<div className="space-y-4">
<h2 className="text-lg font-medium text-foreground border-b border-border pb-2">Pending Invitations</h2>
<div className="bg-card border border-border rounded-lg overflow-hidden shadow-sm">
<ul className="divide-y divide-border">
{invitations.map((inv) => (
<li key={inv.id} className="p-4 flex items-center justify-between gap-4 hover:bg-muted/50 transition-colors">
<div className="truncate flex-1">
<div className="text-sm font-medium">{inv.email}</div>
</div>
<div className="flex items-center gap-3">
<div className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded">Pending</div>
<form action={revokeInvitation}>
<input type="hidden" name="id" value={inv.id} />
<button type="submit" className="text-muted-foreground hover:text-destructive transition-colors p-1" title="Revoke Invitation">
<Trash2 className="h-4 w-4" />
</button>
</form>
</div>
</li>
))}
</ul>
</div>
</div>
)}
</div>
);
}
function TeamSettingsSkeleton() {
return (
<div className="p-8 max-w-4xl mx-auto space-y-8">
<div>
<Skeleton className="h-8 w-48 mb-2" />
<Skeleton className="h-4 w-96" />
</div>
<div className="space-y-4">
<div className="border-b border-border pb-2">
<Skeleton className="h-6 w-24" />
</div>
<div className="bg-card border border-border rounded-lg overflow-hidden shadow-sm p-4 space-y-4">
<div className="space-y-2">
<Skeleton className="h-4 w-48" />
<Skeleton className="h-3 w-20" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-48" />
<Skeleton className="h-3 w-20" />
</div>
</div>
</div>
<div className="space-y-4">
<div className="border-b border-border pb-2">
<Skeleton className="h-6 w-36" />
</div>
<div className="bg-card border border-border rounded-lg p-4 shadow-sm flex gap-4">
<Skeleton className="h-10 flex-1" />
<Skeleton className="h-10 w-28" />
</div>
</div>
</div>
);
}
export default function TeamSettingsPage() {
return (
<Suspense fallback={<TeamSettingsSkeleton />}>
<TeamSettingsContent />
</Suspense>
);
}