Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions api/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export default class Project {
throw new Error(`Error removing member: ${response.status}`)
}

delete this.collaborators[userId]
return await response
} catch (error) {
userMessage(error.message)
Expand All @@ -160,6 +161,9 @@ export default class Project {
throw new Error(`Error promoting user to LEADER: ${response.status}`)
}

if (this.collaborators[userId] && !this.collaborators[userId].roles.includes("LEADER")) {
this.collaborators[userId].roles.push("LEADER")
}
return response
} catch (error) {
userMessage(error.message)
Expand All @@ -181,6 +185,9 @@ export default class Project {
throw new Error(`Error removing LEADER role: ${response.status}`)
}

if (this.collaborators[userId]) {
this.collaborators[userId].roles = this.collaborators[userId].roles.filter(role => role !== "LEADER")
}
return response
} catch (error) {
userMessage(error.message)
Expand All @@ -201,8 +208,9 @@ export default class Project {
if (!response.ok) {
throw new Error(`Error revoking write access: ${response.status}`)
}

return response
if (this.collaborators[userId]) {
this.collaborators[userId].roles = ["VIEWER"]
} return response
} catch (error) {
userMessage(error.message)
}
Expand All @@ -223,6 +231,9 @@ export default class Project {
throw new Error(`Error setting user roles: ${response.status}`)
}

if (this.collaborators[userId]) {
this.collaborators[userId].roles = roles
}
return response
} catch (error) {
userMessage(error.message)
Expand Down
121 changes: 71 additions & 50 deletions components/manage-role/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class ManageRole extends HTMLElement {

permissions = []
isExistingRole = false
/** @type {Object} Local cache of roles for this component */
group = {}

constructor() {
super()
Expand Down Expand Up @@ -55,10 +57,11 @@ class ManageRole extends HTMLElement {
'Authorization': `Bearer ${TPEN.getAuthorization()}`
}
}).then(response => response.json())
this.render(group)
this.group = group || {}
this.render(this.group)
}

render(group) {
render(group = this.group) {
this.shadowRoot.innerHTML = `
<style>
h3 {
Expand Down Expand Up @@ -454,17 +457,26 @@ class ManageRole extends HTMLElement {
this.shadowRoot.getElementById('role-name').value = ''
this.permissions = []
const roleId = roleLi.querySelector("#roleID").textContent.toUpperCase()
await fetch(`${TPEN.servicesURL}/project/${TPEN.activeProject._id}/removeCustomRoles`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TPEN.getAuthorization()}`
},
body: JSON.stringify({ roles: [roleId] })
}).then(response => {
TPEN.eventDispatcher.dispatch("tpen-toast", response.ok ? { status: "info", message: 'Successfully Removed Role' } : { status: "error", message: 'Error Removing Role' })
})
this.render()
try {
const response = await fetch(`${TPEN.servicesURL}/project/${TPEN.activeProject._id}/removeCustomRoles`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TPEN.getAuthorization()}`
},
body: JSON.stringify({ roles: [roleId] })
})

if (response.ok) {
delete this.group[roleId]
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "info", message: 'Successfully Removed Role' })
} else {
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: 'Error Removing Role' })
}
} catch (err) {
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: `Error Removing Role: ${err.message}` })
}
this.render(this.group)
})
})

Expand All @@ -474,7 +486,7 @@ class ManageRole extends HTMLElement {
})
}

updateRolePermissions(group, selectedRole) {
updateRolePermissions(group = this.group, selectedRole) {
this.shadowRoot.getElementById('role-name').value = selectedRole.querySelector('#roleID').textContent
this.permissions = []
this.isExistingRole = true
Expand Down Expand Up @@ -509,33 +521,34 @@ class ManageRole extends HTMLElement {
const role = this.shadowRoot.getElementById('role-name')
if (!role.value) return TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: 'No role selected for update' })

Object.keys(group || {}).forEach(key => {
Object.keys(this.group || {}).forEach(key => {
if (key.toUpperCase() === role.value.toUpperCase()) {
group[key] = this.permissions
this.group[key] = [...this.permissions]
}
})

await fetch(`${TPEN.servicesURL}/project/${TPEN.activeProject._id}/updateCustomRoles`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TPEN.getAuthorization()}`
},
body: JSON.stringify({ roles: group })
}).then(response => {
try {
const response = await fetch(`${TPEN.servicesURL}/project/${TPEN.activeProject._id}/updateCustomRoles`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TPEN.getAuthorization()}`
},
body: JSON.stringify({ roles: this.group })
})

if (response.ok) {
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "info", message: 'Successfully Updated Role' })
this.render()
// Reset internal state before rendering
this.permissions = []
this.isExistingRole = false
this.render(this.group)
} else {
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: 'Error Updating Role' })
}
}).catch(error => {
} catch (error) {
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: `Error updating role: ${error.message}` })
})

this.resetPermissions()
role.value = ''
this.permissions = []
}
}


Expand Down Expand Up @@ -644,6 +657,7 @@ class ManageRole extends HTMLElement {
}

addPermissions(group) {
group = group || this.group
let permissionString = this.shadowRoot.getElementById('permission')
const permissionsDiv = this.shadowRoot.getElementById('permissions')
const role = this.shadowRoot.getElementById('role-name')
Expand Down Expand Up @@ -751,6 +765,7 @@ class ManageRole extends HTMLElement {

async addRole(group) {
const role = this.shadowRoot.getElementById('role-name')
group = group || this.group

if (!role.value) {
return TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: 'Role name is required' })
Expand All @@ -770,29 +785,35 @@ class ManageRole extends HTMLElement {
return TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: 'At least one permission is required' })
}

await fetch(`${TPEN.servicesURL}/project/${TPEN.activeProject._id}/addCustomRoles`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TPEN.getAuthorization()}`
},
body: JSON.stringify({
roles: {
[role.value.toUpperCase()]: this.permissions
}
try {
const response = await fetch(`${TPEN.servicesURL}/project/${TPEN.activeProject._id}/addCustomRoles`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TPEN.getAuthorization()}`
},
body: JSON.stringify({
roles: {
[role.value.toUpperCase()]: this.permissions
}
})
})
})
.then(response => {

if (response.ok) {
return TPEN.eventDispatcher.dispatch("tpen-toast", { status: "info", message: 'Custom role added successfully' })
// update local cache and re-render without refetching
this.group = this.group || {}
this.group[role.value.toUpperCase()] = [...this.permissions]
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "info", message: 'Custom role added successfully' })
// Reset internal state before rendering
this.permissions = []
this.isExistingRole = false
this.render(this.group)
} else {
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: 'Error adding role' })
}
})
.catch(error => {
} catch (error) {
TPEN.eventDispatcher.dispatch("tpen-toast", { status: "error", message: `Error adding role: ${error.message}` })
})

this.resetPermissions()
this.render()
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions components/member-invitation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ class InviteMemberElement extends HTMLElement {
const response = await TPEN.activeProject.addMember(this.shadowRoot.querySelector('#invitee-email').value)
if (!response) throw new Error("Invitation failed")

// Dispatch event to notify other components of the new member
TPEN.eventDispatcher.dispatch('tpen-member-invited', { email: email })

this.shadowRoot.querySelector('#submit').textContent = "Submit"
this.shadowRoot.querySelector('#submit').disabled = false
this.shadowRoot.querySelector('#invitee-email').value = ""
Expand Down
8 changes: 8 additions & 0 deletions components/project-collaborators/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ class ProjectCollaborators extends HTMLElement {
})
.join(" ")
}

/**
* Refreshes the collaborators display by re-rendering the collaborators list.
* This is called when role changes occur to update the UI without a full page refresh.
*/
refreshCollaborators() {
this.renderProjectCollaborators()
}
}

customElements.define('project-collaborators', ProjectCollaborators)
Loading