VIBN Frontend for Coolify deployment
This commit is contained in:
34
app/[workspace]/users/layout.tsx
Normal file
34
app/[workspace]/users/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { WorkspaceLeftRail } from "@/components/layout/workspace-left-rail";
|
||||
import { RightPanel } from "@/components/layout/right-panel";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
export default function UsersLayout({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [activeSection, setActiveSection] = useState<string>("users");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-screen w-full overflow-hidden bg-background">
|
||||
{/* Left Rail - Workspace Navigation */}
|
||||
<WorkspaceLeftRail activeSection={activeSection} onSectionChange={setActiveSection} />
|
||||
|
||||
{/* Main Content Area */}
|
||||
<main className="flex-1 flex flex-col overflow-hidden">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
{/* Right Panel - AI Chat */}
|
||||
<RightPanel />
|
||||
</div>
|
||||
|
||||
<Toaster position="top-center" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
190
app/[workspace]/users/page.tsx
Normal file
190
app/[workspace]/users/page.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { auth } from '@/lib/firebase/config';
|
||||
import { toast } from 'sonner';
|
||||
import { Users, UserPlus, Crown, Mail } from 'lucide-react';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
interface WorkspaceUser {
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
role: 'owner' | 'admin' | 'member';
|
||||
joinedAt: any;
|
||||
lastActive: any;
|
||||
}
|
||||
|
||||
export default function UsersPage() {
|
||||
const params = useParams();
|
||||
const workspace = params.workspace as string;
|
||||
const [users, setUsers] = useState<WorkspaceUser[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentUser, setCurrentUser] = useState<WorkspaceUser | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
}, []);
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
const user = auth.currentUser;
|
||||
if (!user) return;
|
||||
|
||||
const token = await user.getIdToken();
|
||||
const response = await fetch(`/api/workspace/${workspace}/users`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setUsers(data.users);
|
||||
setCurrentUser(data.currentUser);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading users:', error);
|
||||
toast.error('Failed to load workspace users');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleBadgeColor = (role: string) => {
|
||||
switch (role) {
|
||||
case 'owner':
|
||||
return 'bg-purple-500/10 text-purple-600 border-purple-500/20';
|
||||
case 'admin':
|
||||
return 'bg-blue-500/10 text-blue-600 border-blue-500/20';
|
||||
default:
|
||||
return 'bg-gray-500/10 text-gray-600 border-gray-500/20';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-auto">
|
||||
<div className="flex-1 p-8 space-y-8 max-w-6xl">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold mb-2">Team Members</h1>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
Manage workspace access and team collaboration
|
||||
</p>
|
||||
</div>
|
||||
<Button disabled>
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
Invite User
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Current User Info */}
|
||||
{currentUser && (
|
||||
<Card className="border-primary/50 bg-primary/5">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Crown className="h-4 w-4" />
|
||||
Your Account
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-medium">{currentUser.displayName || 'Unknown'}</p>
|
||||
<p className="text-sm text-muted-foreground">{currentUser.email}</p>
|
||||
</div>
|
||||
<div className={`px-3 py-1 rounded-full text-xs font-medium border ${getRoleBadgeColor(currentUser.role)}`}>
|
||||
{currentUser.role}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Users List */}
|
||||
{loading ? (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-center text-muted-foreground">Loading team members...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : users.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="pt-6 text-center space-y-4">
|
||||
<div className="flex justify-center">
|
||||
<div className="h-16 w-16 rounded-full bg-muted flex items-center justify-center">
|
||||
<Users className="h-8 w-8 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-2">No team members yet</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Invite team members to collaborate on projects in this workspace
|
||||
</p>
|
||||
<Button disabled>
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
Invite Your First Team Member
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{users.map((user) => (
|
||||
<Card key={user.id}>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center">
|
||||
<Mail className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{user.displayName || 'Unknown'}</p>
|
||||
<p className="text-sm text-muted-foreground">{user.email}</p>
|
||||
{user.lastActive && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Last active: {new Date(user.lastActive._seconds * 1000).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`px-3 py-1 rounded-full text-xs font-medium border ${getRoleBadgeColor(user.role)}`}>
|
||||
{user.role}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Card */}
|
||||
<Card className="border-blue-500/20 bg-blue-500/5">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Team Collaboration (Coming Soon)</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<strong>👥 Team Workspaces:</strong> Invite team members to collaborate on projects together.
|
||||
</p>
|
||||
<p>
|
||||
<strong>🔐 Role-Based Access:</strong> Control what team members can see and do with flexible permissions.
|
||||
</p>
|
||||
<p>
|
||||
<strong>💬 Shared Context:</strong> All team members can access shared AI chat history and project documentation.
|
||||
</p>
|
||||
<p className="text-xs italic pt-2">
|
||||
This feature is currently in development. Check back soon!
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user