diff --git a/app/admin/users/page.tsx b/app/admin/users/page.tsx index ad75d01..4e6d205 100644 --- a/app/admin/users/page.tsx +++ b/app/admin/users/page.tsx @@ -2,12 +2,13 @@ import { redirect } from "next/navigation" import { createClient } from "@/lib/supabase/server" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" -import { Users, UserPlus, Clock } from "lucide-react" +import { Users, UserPlus, Clock, UsersRound } from "lucide-react" import Link from "next/link" import { Button } from "@/components/ui/button" import { PendingRegistrationsTable } from "./pending-registrations-table" +import { PendingTeamMembersTable } from "./pending-team-members-table" import { ExistingUsersTable } from "./existing-users-table" -import { getPendingRegistrations, getAllUsers } from "./user-management-actions" +import { getPendingRegistrations, getPendingTeamMembers, getAllUsers } from "./user-management-actions" async function updateUserRole(formData: FormData) { "use server" @@ -47,6 +48,7 @@ export default async function AdminUsersPage() { } const pendingRegistrations = await getPendingRegistrations() + const pendingTeamMembers = await getPendingTeamMembers() const allUsers = await getAllUsers() return ( @@ -76,6 +78,26 @@ export default async function AdminUsersPage() { + + +
+
+ + + Pending Team Members + + Approve team members from the Teams module and create user accounts +
+ + {pendingTeamMembers.length} pending + +
+
+ + + +
+

All Users

+ + + + + + ) +} diff --git a/app/admin/users/user-management-actions.ts b/app/admin/users/user-management-actions.ts index 5799ede..0062fbe 100644 --- a/app/admin/users/user-management-actions.ts +++ b/app/admin/users/user-management-actions.ts @@ -343,3 +343,125 @@ export async function getAllUsers() { return data || [] } + +export async function getPendingTeamMembers() { + const supabase = await createClient() + + // Get pending team members that don't have a user account yet + const { data: teamMembers, error: teamError } = await supabase + .from("team_members") + .select("*") + .eq("status", "pending") + .order("invited_at", { ascending: false }) + + if (teamError) { + console.error(" Error fetching team members:", teamError) + return [] + } + + if (!teamMembers || teamMembers.length === 0) { + return [] + } + + // Check which team members already have user accounts + const emails = teamMembers.map((tm) => tm.email) + const { data: existingUsers } = await supabase.from("profiles").select("email").in("email", emails) + + const existingEmails = new Set(existingUsers?.map((u) => u.email) || []) + + // Return only team members without user accounts + return teamMembers.filter((tm) => !existingEmails.has(tm.email)) +} + +export async function approveTeamMember(teamMemberId: string, assignedRole?: string) { + const supabase = await createClient() + const serviceClient = await getServiceClient() + + // Get current user + const { + data: { user: currentUser }, + } = await supabase.auth.getUser() + if (!currentUser) { + return { error: "Unauthorized" } + } + + // Get team member details + const { data: teamMember, error: teamError } = await supabase + .from("team_members") + .select("*") + .eq("id", teamMemberId) + .single() + + if (teamError || !teamMember) { + return { error: "Team member not found" } + } + + // Check if user already exists + const { data: existingUser } = await supabase.from("profiles").select("id").eq("email", teamMember.email).single() + + if (existingUser) { + return { error: "User account already exists for this email" } + } + + // Use assigned role or team member's role + const role = assignedRole || teamMember.role + + // Generate temporary password + const tempPassword = `Temp${Math.random().toString(36).slice(-8)}!` + + // Create auth user using service client + const { data: authUser, error: authError } = await serviceClient.auth.admin.createUser({ + email: teamMember.email, + password: tempPassword, + email_confirm: true, + user_metadata: { + first_name: teamMember.first_name, + last_name: teamMember.last_name, + }, + }) + + if (authError) { + console.error(" Auth creation error:", authError) + return { error: "Failed to create user account" } + } + + // Create profile + const { error: profileError } = await supabase.from("profiles").insert({ + id: authUser.user.id, + email: teamMember.email, + first_name: teamMember.first_name, + last_name: teamMember.last_name, + role: role, + is_admin: role === "admin", + requires_password_change: true, + is_active: true, + created_by: currentUser.id, + status: "active", + }) + + if (profileError) { + console.error(" Profile creation error:", profileError) + return { error: "Failed to create user profile" } + } + + // Update team member status to active + await supabase + .from("team_members") + .update({ + status: "active", + invitation_token: null, + }) + .eq("id", teamMemberId) + + // Log activity + await logActivity(currentUser.id, "approve_team_member", "users", authUser.user.id, { + email: teamMember.email, + assigned_role: role, + team_member_id: teamMemberId, + }) + + revalidatePath("/admin/users") + revalidatePath("/team") + + return { success: true, tempPassword, email: teamMember.email } +} diff --git a/app/layout.tsx b/app/layout.tsx index 9cf6f19..74e7025 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,32 +1,48 @@ -import type { Metadata } from "next" -import { Inter } from "next/font/google" -import { ThemeProvider } from "@/components/theme-provider" -import ClientLayout from "./client-layout" +import type { Metadata, Viewport } from "next" +import { Geist, Geist_Mono } from "next/font/google" import "./globals.css" +import ClientLayout from "./client-layout" -const inter = Inter({ subsets: ["latin"] }) +const geistSans = Geist({ + subsets: ["latin"], + variable: "--font-geist-sans", +}) + +const geistMono = Geist_Mono({ + subsets: ["latin"], + variable: "--font-geist-mono", +}) export const metadata: Metadata = { - title: "Exela PMS - Property Management System", - description: "Complete property management solution for landlords", + title: "Exela Property Management Software", + description: + "Complete property management solution for landlords. Track tenants, manage maintenance requests, collect payments, and streamline operations.", + icons: { + icon: [ + { url: "/Exela-Logo.png", sizes: "32x32", type: "image/png" }, + { url: "/Exela-Logo.png", sizes: "192x192", type: "image/png" }, + ], + apple: "/Exela-Logo.png", // white background is okay for Apple +} + +} + +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#0a0a0a" }, + ], } export default function RootLayout({ children, -}: Readonly<{ +}: { children: React.ReactNode -}>) { +}) { return ( - - - - {children} - + + + {children} )