Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
9 changes: 6 additions & 3 deletions apps/lfx-pcc/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
}
}

.p-select-overlay {
.p-select-list-container {
.p-select-option {
.p-select-overlay,
.p-multiselect-overlay {
.p-select-list-container,
.p-multiselect-list-container {
.p-select-option,
.p-multiselect-option {
@apply text-sm;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->
<lfx-card header="Permission Guide">
<div class="space-y-4">
<!-- Key Points -->
<div class="bg-blue-50 border border-blue-200 rounded p-3">
<p class="text-xs text-blue-800">
<strong>Key:</strong> Project permissions apply to all resources. Committee permissions are limited to assigned committees and their associated meetings
and/or mailing lists only.
</p>
</div>

<!-- Compact Permission Matrix -->
<div class="flex flex-col gap-3">
@for (item of permissionMatrix; track item.scope + item.level) {
<div class="border border-gray-200 rounded-lg p-3">
<div class="flex items-center justify-between mb-2">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium" [class]="item.badge.bgColor + ' ' + item.badge.color">
{{ item.scope }} {{ item.level }}
</span>
</div>
<p class="text-xs text-gray-600 mb-2">{{ item.description }}</p>
<ul class="text-xs text-gray-500 space-y-1">
@for (capability of item.capabilities; track capability) {
<li class="flex items-start">
<i class="fa-light fa-check text-green-500 mr-1 mt-0.5" style="font-size: 10px"></i>
{{ capability }}
</li>
}
</ul>
</div>
}
</div>
</div>
</lfx-card>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import { Component } from '@angular/core';
import { CardComponent } from '@components/card/card.component';
import { PermissionMatrixItem } from '@lfx-pcc/shared/interfaces';

@Component({
selector: 'lfx-permissions-matrix',
standalone: true,
imports: [CardComponent],
templateUrl: './permissions-matrix.component.html',
})
export class PermissionsMatrixComponent {
protected readonly permissionMatrix: PermissionMatrixItem[] = [
{
scope: 'Project',
level: 'View',
description: 'View all project resources',
capabilities: ['View project, committees, meetings, mailing lists'],
badge: {
color: 'text-blue-800',
bgColor: 'bg-blue-100',
},
},
{
scope: 'Project',
level: 'Manage',
description: 'Manage all project resources',
capabilities: ['Manage project, committees, meetings, mailing lists'],
badge: {
color: 'text-blue-800',
bgColor: 'bg-blue-100',
},
},
{
scope: 'Committee',
level: 'View',
description: 'View specific committee only, including meetings and mailing lists that are associated with the committees.',
capabilities: [
'View project (limited to committees)',
'View assigned committees',
'View meetings associated with the committees',
'View mailing lists associated with the committees',
],
badge: {
color: 'text-green-800',
bgColor: 'bg-green-100',
},
},
{
scope: 'Committee',
level: 'Manage',
description: 'Manage specific committee only, including meetings and mailing lists that are associated with the committees.',
capabilities: [
'View project (limited to committees)',
'Manage assigned committees',
'Manage meetings associated with the committees',
'Manage mailing lists associated with the committees',
],
badge: {
color: 'text-green-800',
bgColor: 'bg-green-100',
},
},
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->

<form [formGroup]="form()" (ngSubmit)="onSubmit()" class="space-y-6">
<!-- Basic Information Section -->
<div class="flex flex-col gap-3">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- First Name -->
<div>
<label for="first-name" class="block text-sm font-medium text-gray-700 mb-1"> First Name <span class="text-red-500">*</span> </label>
<lfx-input-text
size="small"
[form]="form()"
control="first_name"
id="first-name"
placeholder="Enter first name"
styleClass="w-full"
data-testid="settings-user-form-first-name"></lfx-input-text>
@if (form().get('first_name')?.errors?.['required'] && form().get('first_name')?.touched) {
<p class="mt-1 text-xs text-red-600">First name is required</p>
}
</div>

<!-- Last Name -->
<div>
<label for="last-name" class="block text-sm font-medium text-gray-700 mb-1"> Last Name <span class="text-red-500">*</span> </label>
<lfx-input-text
size="small"
[form]="form()"
control="last_name"
id="last-name"
placeholder="Enter last name"
styleClass="w-full"
data-testid="settings-user-form-last-name"></lfx-input-text>
@if (form().get('last_name')?.errors?.['required'] && form().get('last_name')?.touched) {
<p class="mt-1 text-xs text-red-600">Last name is required</p>
}
</div>
</div>

<!-- Email Address -->
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-1"> Email Address <span class="text-red-500">*</span> </label>
<lfx-input-text
size="small"
[form]="form()"
control="email"
id="email"
placeholder="Enter email address"
styleClass="w-full"
data-testid="settings-user-form-email"></lfx-input-text>
@if (form().get('email')?.errors?.['required'] && form().get('email')?.touched) {
<p class="mt-1 text-xs text-red-600">Email address is required</p>
}
@if (form().get('email')?.errors?.['email'] && form().get('email')?.touched) {
<p class="mt-1 text-xs text-red-600">Please enter a valid email address</p>
}
</div>
</div>

<!-- Permission Settings Section -->
<div class="flex flex-col gap-3">
<!-- Permission Scope -->
<div>
<div class="flex items-center gap-2 mb-3">
<label class="block text-sm font-medium text-gray-700">Permission Scope</label>
<i
class="fa-light fa-info-circle text-gray-400 cursor-help"
[pTooltip]="scopeTooltip"
tooltipPosition="right"
[escape]="false"
tooltipStyleClass="text-xs max-w-sm"></i>
</div>
<div class="flex flex-col gap-2">
@for (option of permissionScopeOptions; track option.value) {
<lfx-radio-button
[form]="form()"
control="permission_scope"
name="permission_scope"
[inputId]="'scope-' + option.value"
[value]="option.value"
[label]="option.label"></lfx-radio-button>
}
</div>
</div>

<!-- Permission Level -->
<div>
<div class="flex items-center gap-2 mb-3">
<label class="block text-sm font-medium text-gray-700">Permission Level</label>
<i
class="fa-light fa-info-circle text-gray-400 cursor-help"
[pTooltip]="levelTooltip"
tooltipPosition="right"
[escape]="false"
tooltipStyleClass="text-xs max-w-sm"></i>
</div>
<div class="flex flex-col gap-2">
@for (option of permissionLevelOptions; track option.value) {
<lfx-radio-button
[form]="form()"
control="permission_level"
name="permission_level"
[inputId]="'level-' + option.value"
[value]="option.value"
[label]="option.label"></lfx-radio-button>
}
</div>
</div>

<!-- Committee Selection (only when scope is committee) -->
@if (form().get('permission_scope')?.value === 'committee') {
<div>
<label for="committee-ids" class="block text-sm font-medium text-gray-700 mb-1">Select Committees <span class="text-red-500">*</span></label>
<p class="text-xs text-gray-500 mb-2">Choose which committees this user can access</p>
<lfx-multi-select
[form]="form()"
control="committee_ids"
[options]="committees()"
size="small"
placeholder="Select committees"
appendTo="body"
id="committee-ids"
data-testid="settings-user-form-committee-ids"
styleClass="w-full"></lfx-multi-select>
@if (form().get('committee_ids')?.errors?.['required'] && form().get('committee_ids')?.touched) {
<p class="mt-1 text-xs text-red-600">At least one committee must be selected</p>
}
</div>
}

<!-- Permission Summary -->
<div class="bg-gray-50 p-3 rounded-md text-sm">
<div class="font-medium text-gray-700 mb-1">Permission Summary:</div>
@if (form().get('permission_scope')?.value === 'project') {
<div class="text-gray-600">
<strong>{{ form().get('permission_level')?.value === 'read' ? 'View-only' : 'Full' }}</strong> access to <strong>entire project</strong> including all
committees, meetings, and mailing lists.
</div>
} @else {
<div class="text-gray-600">
<strong>{{ form().get('permission_level')?.value === 'read' ? 'View-only' : 'Full' }}</strong> access to <strong>selected committees</strong> and
their associated meetings and mailing lists only.
</div>
}
</div>
</div>

<!-- Form Actions -->
<div class="flex justify-end gap-3 pt-6 border-t">
<lfx-button
label="Cancel"
severity="secondary"
[outlined]="true"
(click)="onCancel()"
size="small"
type="button"
data-testid="settings-user-form-cancel"></lfx-button>
<lfx-button
[label]="isEditing() ? 'Update User' : 'Add User'"
[loading]="submitting()"
[disabled]="submitting()"
type="submit"
size="small"
data-testid="settings-user-form-submit"></lfx-button>
</div>
</form>

<ng-template #scopeTooltip>
<div class="flex flex-col">
<div><strong>Project:</strong> Access to entire project including all committees, meetings, and mailing lists</div>
<div><strong>Committee:</strong> Access limited to specific committees and their associated content only</div>
</div>
</ng-template>

<ng-template #levelTooltip>
<div class="flex flex-col">
<div><strong>View:</strong> Read-only access to view and browse content</div>
<div><strong>Manage:</strong> Full access to create, edit, delete, and manage content</div>
</div>
</ng-template>
Loading
Loading