11"use client" ;
2+ import { useState } from "react" ;
23import useSWR from "swr" ;
34import { apiFetch } from "@/lib/api" ;
45import { useAppContext } from "@/providers/app-context" ;
56import UsersLoading from "@/app/users/loading" ;
6- import { UserManagement , type AccessLevel , type User } from "@/components/user-management" ;
7+ import { canUpdateUsers , UserManagement , type AccessLevel , type User } from "@/components/user-management" ;
8+ import { ApplicationAccessModal } from "@/components/application-access-modal" ;
9+ import { Button } from "@/components/ui/button" ;
10+ import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card" ;
11+ import { Users , Building2 } from "lucide-react" ;
12+ import { toastSuccess , toastError } from "@/hooks/use-toast" ;
13+ import { useRouter } from "next/navigation" ;
714
815type OrgUsers = { users : User [ ] } ;
916
1017export default function ApplicationUsersPage ( ) {
1118 const { token, org, app, getAppAccess, getOrgAccess, updateOrgs } = useAppContext ( ) ;
19+ const router = useRouter ( ) ;
20+ const [ isAppAccessModalOpen , setIsAppAccessModalOpen ] = useState ( false ) ;
21+
22+ // Application users data
1223 const { data, isLoading, error, mutate } = useSWR < OrgUsers > (
1324 token && org ? "/organisations/applications/user/list" : null ,
1425 ( url : string ) => apiFetch < any > ( url , { } , { token, org, app } )
1526 ) ;
1627
28+ // Organization users data (for the application access modal)
29+ const { data : orgUsersData , isLoading : orgUsersLoading } = useSWR < OrgUsers > (
30+ token && org ? "/organisations/user/list" : null ,
31+ ( url : string ) => apiFetch < any > ( url , { } , { token, org } )
32+ ) ;
33+
1734 const addUser = async ( user : string , access : AccessLevel ) => {
1835 await apiFetch (
1936 "/organisations/applications/user/create" ,
@@ -40,6 +57,46 @@ export default function ApplicationUsersPage() {
4057 updateOrgs ( ) ;
4158 } ;
4259
60+ // Handle application access invites (grant access to existing org users)
61+ const handleApplicationInvite = async ( invites : { userId : string ; role : string } [ ] ) => {
62+ try {
63+ // Call the API for each user in parallel
64+ const promises = invites . map ( ( invite ) =>
65+ apiFetch (
66+ "/organisations/applications/user/create" ,
67+ {
68+ method : "POST" ,
69+ body : {
70+ user : invite . userId ,
71+ access : invite . role as AccessLevel ,
72+ } ,
73+ } ,
74+ { token, org, app }
75+ )
76+ ) ;
77+
78+ // Wait for all API calls to complete
79+ await Promise . all ( promises ) ;
80+
81+ toastSuccess (
82+ "Access Granted" ,
83+ `Successfully granted ${ app } access to ${ invites . length } user${ invites . length !== 1 ? "s" : "" } `
84+ ) ;
85+
86+ setIsAppAccessModalOpen ( false ) ; // Close the modal
87+ mutate ( ) ; // Refresh the users list
88+ updateOrgs ( ) ; // Update organizations data
89+ } catch ( error : any ) {
90+ console . error ( "Failed to grant application access:" , error ) ;
91+ toastError ( "Failed to Grant Access" , error . message || "Could not grant application access" ) ;
92+ }
93+ } ;
94+
95+ // Handle redirect to organization users page
96+ const handleRedirectToOrgUsers = ( ) => {
97+ router . push ( `/dashboard/${ org } /users` ) ;
98+ } ;
99+
43100 if ( isLoading ) {
44101 return < UsersLoading /> ;
45102 }
@@ -48,8 +105,63 @@ export default function ApplicationUsersPage() {
48105 return < div className = "p-6" > Error loading users</ div > ;
49106 }
50107
108+ // Prepare org users for the application access modal (exclude users already in app)
109+ const currentAppUsernames = new Set ( ( data ?. users || [ ] ) . map ( ( user ) => user . username ) ) ;
110+ const orgUsers =
111+ orgUsersData ?. users
112+ ?. filter ( ( user ) => ! currentAppUsernames . has ( user . username ) ) // Filter out existing app users
113+ ?. map ( ( user ) => ( {
114+ id : user . username ,
115+ name : user . username ,
116+ email : user . username , // Assuming username is email for now
117+ username : user . username ,
118+ roles : user . roles ,
119+ } ) ) || [ ] ;
120+
121+ const canUpdateAppUsers = canUpdateUsers ( "application" , getOrgAccess ( org ) , getAppAccess ( org , app ) ) ;
122+ const canUpdateOrgUsers = canUpdateUsers ( "organisation" , getOrgAccess ( org ) , getAppAccess ( org , app ) ) ;
123+
51124 return (
52- < div className = "container mx-auto p-6" >
125+ < div className = "container mx-auto p-6 space-y-6" >
126+ { /* Add User to Application Card */ }
127+ { ( canUpdateAppUsers || canUpdateOrgUsers ) && (
128+ < Card >
129+ < CardHeader >
130+ < CardTitle className = "flex items-center gap-2" >
131+ < Users className = "h-5 w-5" />
132+ Grant Application Access
133+ </ CardTitle >
134+ < p className = "text-sm text-muted-foreground" >
135+ Add existing organization members to this application (excluding current app users)
136+ </ p >
137+ </ CardHeader >
138+ < CardContent className = "space-y-4" >
139+ < div className = "flex flex-col sm:flex-row gap-3 sm:justify-start" >
140+ { canUpdateAppUsers && (
141+ < Button onClick = { ( ) => setIsAppAccessModalOpen ( true ) } size = "sm" disabled = { orgUsers . length === 0 } >
142+ < Users className = "h-4 w-4 mr-2" />
143+ Add User to Application
144+ </ Button >
145+ ) }
146+ { canUpdateOrgUsers && (
147+ < Button variant = "outline" onClick = { handleRedirectToOrgUsers } size = "sm" >
148+ < Building2 className = "h-4 w-4 mr-2" />
149+ Add someone to organisation
150+ </ Button >
151+ ) }
152+ </ div >
153+ { canUpdateAppUsers && (
154+ < p className = "text-xs text-muted-foreground" >
155+ { orgUsers . length === 0
156+ ? "All organization users already have access to this application. Use 'Add someone to organisation' to invite new users."
157+ : `${ orgUsers . length } organization member${ orgUsers . length !== 1 ? "s" : "" } available to add to this application.` }
158+ </ p >
159+ ) }
160+ </ CardContent >
161+ </ Card >
162+ ) }
163+
164+ { /* Current Application Users */ }
53165 < UserManagement
54166 users = { data ?. users || [ ] }
55167 currentUserAppAccess = { getAppAccess ( org , app ) }
@@ -60,6 +172,18 @@ export default function ApplicationUsersPage() {
60172 title = "Application Users"
61173 description = "Manage users and their access levels for this application"
62174 entityType = "application"
175+ hideAddUserButton = { true }
176+ />
177+
178+ { /* Application Access Modal */ }
179+ < ApplicationAccessModal
180+ isOpen = { isAppAccessModalOpen }
181+ onClose = { ( ) => setIsAppAccessModalOpen ( false ) }
182+ onSubmit = { handleApplicationInvite }
183+ orgUsers = { orgUsers }
184+ applicationName = { app || "" }
185+ availableRoles = { [ "read" , "write" , "admin" ] }
186+ isLoading = { orgUsersLoading }
63187 />
64188 </ div >
65189 ) ;
0 commit comments