22
33import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
44import { createLogger } from '@sim/logger'
5+ import { useQueryClient } from '@tanstack/react-query'
56import { Check , Clipboard , Key , Search } from 'lucide-react'
67import { useParams , useRouter } from 'next/navigation'
78import {
@@ -42,6 +43,7 @@ import {
4243 useWorkspaceCredentials ,
4344 type WorkspaceCredential ,
4445 type WorkspaceCredentialRole ,
46+ workspaceCredentialKeys ,
4547} from '@/hooks/queries/credentials'
4648import {
4749 usePersonalEnvironment ,
@@ -125,6 +127,7 @@ interface WorkspaceVariableRowProps {
125127 renamingKey : string | null
126128 pendingKeyValue : string
127129 hasCredential : boolean
130+ isAdmin : boolean
128131 onRenameStart : ( key : string ) => void
129132 onPendingKeyChange : ( value : string ) => void
130133 onRenameEnd : ( key : string , value : string ) => void
@@ -138,12 +141,18 @@ function WorkspaceVariableRow({
138141 renamingKey,
139142 pendingKeyValue,
140143 hasCredential,
144+ isAdmin,
141145 onRenameStart,
142146 onPendingKeyChange,
143147 onRenameEnd,
144148 onDelete,
145149 onViewDetails,
146150} : WorkspaceVariableRowProps ) {
151+ const [ valueFocused , setValueFocused ] = useState ( false )
152+
153+ const maskedValueStyle =
154+ isAdmin && ! valueFocused ? ( { WebkitTextSecurity : 'disc' } as React . CSSProperties ) : undefined
155+
147156 return (
148157 < div className = 'contents' >
149158 < EmcnInput
@@ -163,12 +172,19 @@ function WorkspaceVariableRow({
163172 />
164173 < div />
165174 < EmcnInput
166- value = { value ? '\u2022' . repeat ( value . length ) : '' }
175+ value = { isAdmin ? value : value ? '\u2022' . repeat ( value . length ) : '' }
167176 readOnly
177+ onFocus = { ( ) => {
178+ if ( isAdmin ) setValueFocused ( true )
179+ } }
180+ onBlur = { ( ) => {
181+ if ( isAdmin ) setValueFocused ( false )
182+ } }
168183 autoComplete = 'off'
169184 autoCorrect = 'off'
170185 autoCapitalize = 'off'
171186 spellCheck = 'false'
187+ style = { maskedValueStyle }
172188 className = 'h-9'
173189 />
174190 < Button
@@ -298,6 +314,14 @@ export function CredentialsManager() {
298314 )
299315
300316 const { data : workspacePermissions } = useWorkspacePermissionsQuery ( workspaceId || null )
317+ const queryClient = useQueryClient ( )
318+
319+ const isAdmin = useMemo ( ( ) => {
320+ const userId = session ?. user ?. id
321+ if ( ! userId || ! workspacePermissions ?. users ) return false
322+ const currentUser = workspacePermissions . users . find ( ( user ) => user . userId === userId )
323+ return currentUser ?. permissionType === 'admin'
324+ } , [ session ?. user ?. id , workspacePermissions ?. users ] )
301325
302326 const isLoading = isPersonalLoading || isWorkspaceLoading
303327 const variables = useMemo ( ( ) => personalEnvData || { } , [ personalEnvData ] )
@@ -923,6 +947,7 @@ export function CredentialsManager() {
923947
924948 const prevInitialVars = [ ...initialVarsRef . current ]
925949 const prevInitialWorkspaceVars = { ...initialWorkspaceVarsRef . current }
950+ const mutations : Promise < unknown > [ ] = [ ]
926951
927952 try {
928953 setShowUnsavedChanges ( false )
@@ -944,8 +969,6 @@ export function CredentialsManager() {
944969 . filter ( ( v ) => v . key && v . value )
945970 . reduce < Record < string , string > > ( ( acc , { key, value } ) => ( { ...acc , [ key ] : value } ) , { } )
946971
947- await savePersonalMutation . mutateAsync ( { variables : validVariables } )
948-
949972 const before = prevInitialWorkspaceVars
950973 const after = mergedWorkspaceVars
951974 const toUpsert : Record < string , string > = { }
@@ -961,33 +984,44 @@ export function CredentialsManager() {
961984 if ( ! ( k in after ) ) toDelete . push ( k )
962985 }
963986
964- if ( workspaceId ) {
965- if ( Object . keys ( toUpsert ) . length ) {
966- await upsertWorkspaceMutation . mutateAsync ( { workspaceId, variables : toUpsert } )
967- }
968- if ( toDelete . length ) {
969- await removeWorkspaceMutation . mutateAsync ( { workspaceId, keys : toDelete } )
987+ const personalChanged = ( ( ) => {
988+ const initialMap = new Map (
989+ prevInitialVars . filter ( ( v ) => v . key && v . value ) . map ( ( v ) => [ v . key , v . value ] )
990+ )
991+ const currentKeys = Object . keys ( validVariables )
992+ if ( initialMap . size !== currentKeys . length ) return true
993+ for ( const [ key , value ] of Object . entries ( validVariables ) ) {
994+ if ( initialMap . get ( key ) !== value ) return true
970995 }
996+ return false
997+ } ) ( )
998+
999+ if ( personalChanged ) {
1000+ mutations . push ( savePersonalMutation . mutateAsync ( { variables : validVariables } ) )
1001+ }
1002+ if ( workspaceId && Object . keys ( toUpsert ) . length ) {
1003+ mutations . push ( upsertWorkspaceMutation . mutateAsync ( { workspaceId, variables : toUpsert } ) )
1004+ }
1005+ if ( workspaceId && toDelete . length ) {
1006+ mutations . push ( removeWorkspaceMutation . mutateAsync ( { workspaceId, keys : toDelete } ) )
9711007 }
9721008
1009+ await Promise . all ( mutations )
1010+
9731011 setWorkspaceVars ( mergedWorkspaceVars )
9741012 setNewWorkspaceRows ( [ createEmptyEnvVar ( ) ] )
9751013 } catch ( error ) {
9761014 hasSavedRef . current = false
9771015 initialVarsRef . current = prevInitialVars
9781016 initialWorkspaceVarsRef . current = prevInitialWorkspaceVars
9791017 logger . error ( 'Failed to save environment variables:' , error )
1018+ } finally {
1019+ if ( mutations . length > 0 ) {
1020+ queryClient . invalidateQueries ( { queryKey : workspaceCredentialKeys . lists ( ) } )
1021+ }
9801022 }
981- } , [
982- isListSaving ,
983- envVars ,
984- workspaceVars ,
985- newWorkspaceRows ,
986- workspaceId ,
987- savePersonalMutation ,
988- upsertWorkspaceMutation ,
989- removeWorkspaceMutation ,
990- ] )
1023+ // eslint-disable-next-line react-hooks/exhaustive-deps
1024+ } , [ isListSaving , envVars , workspaceVars , newWorkspaceRows , workspaceId ] )
9911025
9921026 const handleDiscardAndNavigate = useCallback ( ( ) => {
9931027 shouldBlockNavRef . current = false
@@ -1494,6 +1528,7 @@ export function CredentialsManager() {
14941528 renamingKey = { renamingKey }
14951529 pendingKeyValue = { pendingKeyValue }
14961530 hasCredential = { envKeyToCredential . has ( key ) }
1531+ isAdmin = { isAdmin }
14971532 onRenameStart = { setRenamingKey }
14981533 onPendingKeyChange = { setPendingKeyValue }
14991534 onRenameEnd = { handleWorkspaceKeyRename }
0 commit comments