Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Org page #800

Merged
merged 69 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
d981b92
Begin implementing org page
rmunn May 14, 2024
92feeef
Create DetailsPage component for orgs & projects
rmunn May 14, 2024
3f996b7
Move BadgeList into DetailsPage component
rmunn May 14, 2024
42f485a
Add DetailItem component for project/org pages
rmunn May 14, 2024
f54cdf7
Use DetailItem in project page as well
rmunn May 14, 2024
d3cd836
Move project members list into own component
rmunn May 14, 2024
239c39c
Implement page load function for org page
rmunn May 14, 2024
56b3d4c
Use page load data in org page
rmunn May 14, 2024
88a45cb
Get ready to add members list to org page
rmunn May 14, 2024
3c1b25a
Almost-functional MembersList in org page
rmunn May 15, 2024
0deefa4
Working MembersList in org page
rmunn May 15, 2024
67cba8f
Move role-change notification inside members list
rmunn May 15, 2024
0d2f28e
Correct notification name for renaming org
rmunn May 15, 2024
4862d7e
Correct notification name for empty org name
rmunn May 15, 2024
080e680
Remove completed TODO
rmunn May 15, 2024
b3af3db
Remove another completed TODO comment
rmunn May 15, 2024
afea22b
Rename projectId prop as per TODO comment
rmunn May 15, 2024
6441155
Fall back to email address if user has no name
rmunn May 15, 2024
01b3f0e
Undo changes to UserModal
rmunn May 15, 2024
2c6f810
Better solution for extra buttons in member list
rmunn May 15, 2024
72b113e
Extract user table from admin dashboard
rmunn May 16, 2024
37cff71
Use user table from admin page in org page
rmunn May 16, 2024
3691eaf
Remove @ts-expect-error comments
myieye May 16, 2024
411b4f2
Add filter field to member list
myieye May 16, 2024
e9d6886
Move table view toggle into members list
rmunn May 17, 2024
5babd40
Implement "Add Org Member" button
rmunn May 17, 2024
eff2853
Add tabs to org page
rmunn May 28, 2024
da2cc1c
Remove table view from MembersList
rmunn May 28, 2024
19e1645
Implement org-specific member table
rmunn May 28, 2024
9a377ae
Remove placeholder details, use real ones
rmunn May 29, 2024
a577ea9
Can now add org members
rmunn May 29, 2024
2ee6c38
Can now remove org members
rmunn May 29, 2024
729ef18
Fix lint error
rmunn May 29, 2024
b7a89db
MembersList is now only for projects
rmunn May 29, 2024
d749f57
Improve display of usernames if missing
rmunn May 30, 2024
27fa187
Remove one roleType line that was missed earlier
rmunn May 30, 2024
e647286
Improve OrgRoleSelect
rmunn May 30, 2024
5d303a6
Fix Typescript types in UserTable component
rmunn May 30, 2024
e991861
Address review comments, styling, refactor etc.
myieye Jun 3, 2024
6c00c2f
Fix lint errors
rmunn Jun 5, 2024
8b5c7e2
Allow changing user role
rmunn Jun 5, 2024
5d820e1
Remove label from OrgRoleSelect
rmunn Jun 6, 2024
b6aa8ff
Implement delete-org button, make it admin-only
rmunn Jun 6, 2024
1ca9e73
Change _deleteOrgUser to take user ID
rmunn Jun 6, 2024
dc81ef5
Implement "Leave Organization" button
rmunn Jun 6, 2024
1b7105d
Move add/invite member button to page actions
myieye Jun 7, 2024
1cb20b0
Remove TODO comments from DeleteOrg mutation
rmunn Jun 7, 2024
d06d6ef
Remove TODO comment on AddOrgMemberModal
rmunn Jun 7, 2024
a542b96
Remove TODO comments about org descriptions
rmunn Jun 7, 2024
5d1f461
Remove TODO comments about org page filter params
rmunn Jun 7, 2024
8fa8e2a
Remove TODO about implementing BulkAddOrgMembers
rmunn Jun 7, 2024
2550bde
Add TODO re: scribe link that needs writing
rmunn Jun 7, 2024
f707c1e
Don't put filterProjectsByUser on org page for now
rmunn Jun 7, 2024
c6c6d7e
Add types to createEventDispatcher
rmunn Jun 7, 2024
a0b3bf1
Select org roles using modal, not select-in-table
rmunn Jun 7, 2024
1092393
Add TODO re openUserModal
rmunn Jun 7, 2024
909070b
Remove double-confirm from org deletion
rmunn Jun 10, 2024
9f2e0d6
Implement user-details modal on org page
rmunn Jun 10, 2024
7fec28b
Remove "or Invite" from org add-member button & modal
rmunn Jun 10, 2024
08e625f
Remove unused props from OrgMemberTable
rmunn Jun 10, 2024
a1719e2
Remove unused import
rmunn Jun 10, 2024
6ef4a22
Remove leftover console.log
rmunn Jun 10, 2024
739524f
Remove unneeded $: property in component
rmunn Jun 10, 2024
f927958
Add missing ChangeOrgMemberRoleModal component
rmunn Jun 10, 2024
5bbdf9d
Merge remote-tracking branch 'origin/develop' into feat/org-page
myieye Jun 11, 2024
461b789
Fix TypeScript errors
myieye Jun 11, 2024
11cf0b9
Update svelte-kit
myieye Jun 11, 2024
4faa6df
Standardize user types, component location, role formatting.
myieye Jun 11, 2024
c9240d6
Make ChangeOrgMemberRole handle missing userId
rmunn Jun 12, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using LexCore.Entities;

namespace LexBoxApi.GraphQL.CustomTypes;

[ObjectType]
public class OrgMembersGqlConfiguration : ObjectType<OrgMember>
{
protected override void Configure(IObjectTypeDescriptor<OrgMember> descriptor)
{
descriptor.Field(f => f.User).Type<NonNullType<UserGqlConfiguration>>();
descriptor.Field(f => f.Organization).Type<NonNullType<ProjectGqlConfiguration>>();
}
}
69 changes: 66 additions & 3 deletions backend/LexBoxApi/GraphQL/OrgMutations.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using LexBoxApi.Auth;
using LexBoxApi.Auth.Attributes;
using LexBoxApi.Models.Org;
using LexCore.Entities;
using LexCore.Exceptions;
using LexCore.ServiceInterfaces;
Expand Down Expand Up @@ -36,6 +38,20 @@ public async Task<IQueryable<Organization>> CreateOrganization(string name,
return dbContext.Orgs.Where(o => o.Id == orgId);
}

[Error<DbError>]
[UseMutationConvention]
[AdminRequired]
public async Task<Organization> DeleteOrg(Guid orgId,
LexBoxDbContext dbContext)
{
var org = await dbContext.Orgs.Include(o => o.Members).FirstOrDefaultAsync(o => o.Id == orgId);
NotFoundException.ThrowIfNull(org);

dbContext.Remove(org);
await dbContext.SaveChangesAsync();
return org;
}

/// <summary>
/// set the role of a member in an organization, if the member does not exist it will be created
/// </summary>
Expand All @@ -55,14 +71,39 @@ public async Task<IQueryable<Organization>> SetOrgMemberRole(
Guid orgId,
OrgRole? role,
string emailOrUsername)
{
var user = await dbContext.Users.FindByEmailOrUsername(emailOrUsername);
NotFoundException.ThrowIfNull(user); // TODO: Implement inviting user
return await ChangeOrgMemberRole(dbContext, permissionService, orgId, user.Id, role);
}

/// <summary>
/// Change the role of an existing member in an organization
/// </summary>
/// <param name="dbContext"></param>
/// <param name="permissionService"></param>
/// <param name="orgId"></param>
/// <param name="userId">ID (GUID) of the user whose membership should be updated</param>
/// <param name="role">set to null to remove the member</param>
[Error<DbError>]
[Error<NotFoundException>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
public async Task<IQueryable<Organization>> ChangeOrgMemberRole(
LexBoxDbContext dbContext,
IPermissionService permissionService,
Guid orgId,
Guid userId,
OrgRole? role)
{
var org = await dbContext.Orgs.Include(o => o.Members).FirstOrDefaultAsync(o => o.Id == orgId);
NotFoundException.ThrowIfNull(org);
var user = await dbContext.Users.FindByEmailOrUsername(emailOrUsername);
NotFoundException.ThrowIfNull(user);

permissionService.AssertCanEditOrg(org);
await UpdateOrgMemberRole(dbContext, org, role, user.Id);
var user = await dbContext.Users.FindAsync(userId);
NotFoundException.ThrowIfNull(user);
await UpdateOrgMemberRole(dbContext, org, role, userId);
return dbContext.Orgs.Where(o => o.Id == orgId);
}

Expand All @@ -85,4 +126,26 @@ private async Task UpdateOrgMemberRole(LexBoxDbContext dbContext, Organization o

await dbContext.SaveChangesAsync();
}

[Error<NotFoundException>]
[Error<DbError>]
[Error<RequiredException>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
public async Task<IQueryable<Organization>> ChangeOrgName(ChangeOrgNameInput input,
IPermissionService permissionService,
LexBoxDbContext dbContext)
{
if (string.IsNullOrEmpty(input.Name)) throw new RequiredException("Org name cannot be empty");

var org = await dbContext.Orgs.FindAsync(input.OrgId);
NotFoundException.ThrowIfNull(org);
permissionService.AssertCanEditOrg(org);

org.Name = input.Name;
org.UpdateUpdatedDate();
await dbContext.SaveChangesAsync();
return dbContext.Orgs.Where(o => o.Id == input.OrgId);
}
}
3 changes: 3 additions & 0 deletions backend/LexBoxApi/Models/Org/ChangeOrgInputs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace LexBoxApi.Models.Org;

public record ChangeOrgNameInput(Guid OrgId, string Name);
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@iconify-json/mdi": "^1.1.66",
"@playwright/test": "^1.44.0",
"@sveltejs/adapter-node": "^4.0.1",
"@sveltejs/kit": "^2.5.8",
"@sveltejs/kit": "^2.5.10",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@tailwindcss/typography": "^0.5.13",
"@types/mjml": "^4.7.4",
Expand Down
34 changes: 17 additions & 17 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 41 additions & 2 deletions frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ type BulkAddProjectMembersResult {
existingMembers: [UserProjectRole!]!
}

type ChangeOrgMemberRolePayload {
organization: Organization
errors: [ChangeOrgMemberRoleError!]
}

type ChangeOrgNamePayload {
organization: Organization
errors: [ChangeOrgNameError!]
}

type ChangeProjectDescriptionPayload {
project: Project
errors: [ChangeProjectDescriptionError!]
Expand Down Expand Up @@ -107,6 +117,11 @@ type DeleteDraftProjectPayload {
errors: [DeleteDraftProjectError!]
}

type DeleteOrgPayload {
organization: Organization
errors: [DeleteOrgError!]
}

type DeleteUserByAdminOrSelfPayload {
user: User
errors: [DeleteUserByAdminOrSelfError!]
Expand Down Expand Up @@ -176,7 +191,10 @@ type MeDto {

type Mutation {
createOrganization(input: CreateOrganizationInput!): CreateOrganizationPayload!
deleteOrg(input: DeleteOrgInput!): DeleteOrgPayload! @authorize(policy: "AdminRequiredPolicy")
setOrgMemberRole(input: SetOrgMemberRoleInput!): SetOrgMemberRolePayload!
changeOrgMemberRole(input: ChangeOrgMemberRoleInput!): ChangeOrgMemberRolePayload!
changeOrgName(input: ChangeOrgNameInput!): ChangeOrgNamePayload!
createProject(input: CreateProjectInput!): CreateProjectPayload! @authorize(policy: "VerifiedEmailRequiredPolicy")
addProjectMember(input: AddProjectMemberInput!): AddProjectMemberPayload!
bulkAddProjectMembers(input: BulkAddProjectMembersInput!): BulkAddProjectMembersPayload! @authorize(policy: "AdminRequiredPolicy")
Expand All @@ -201,11 +219,11 @@ type NotFoundError implements Error {
}

type OrgMember {
user: User!
organization: Project!
userId: UUID!
orgId: UUID!
role: OrgRole!
user: User
organization: Organization
id: UUID!
createdDate: DateTime!
updatedDate: DateTime!
Expand Down Expand Up @@ -360,6 +378,10 @@ union AddProjectMemberError = NotFoundError | DbError | ProjectMembersMustBeVeri

union BulkAddProjectMembersError = NotFoundError | InvalidEmailError | DbError

union ChangeOrgMemberRoleError = DbError | NotFoundError

union ChangeOrgNameError = NotFoundError | DbError | RequiredError

union ChangeProjectDescriptionError = NotFoundError | DbError

union ChangeProjectMemberRoleError = NotFoundError | DbError | ProjectMembersMustBeVerified | ProjectMembersMustBeVerifiedForRole
Expand All @@ -378,6 +400,8 @@ union CreateProjectError = DbError | AlreadyExistsError | ProjectCreatorsMustHav

union DeleteDraftProjectError = NotFoundError | DbError

union DeleteOrgError = DbError

union DeleteUserByAdminOrSelfError = NotFoundError | DbError

union LeaveProjectError = NotFoundError | LastMemberCantLeaveError
Expand Down Expand Up @@ -408,6 +432,17 @@ input BulkAddProjectMembersInput {
passwordHash: String!
}

input ChangeOrgMemberRoleInput {
orgId: UUID!
userId: UUID!
role: OrgRole
}

input ChangeOrgNameInput {
orgId: UUID!
name: String!
}

input ChangeProjectDescriptionInput {
projectId: UUID!
description: String!
Expand Down Expand Up @@ -481,6 +516,10 @@ input DeleteDraftProjectInput {
draftProjectId: UUID!
}

input DeleteOrgInput {
orgId: UUID!
}

input DeleteUserByAdminOrSelfInput {
userId: UUID!
}
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/lib/app.postcss
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,27 @@ img[src*="onestory-editor-logo"] {
radial-gradient(ellipse 10px 70% at center right, oklch(var(--b3)) 0px, rgba(0, 0, 0, 0) 100%);
background-attachment: local, local, scroll, scroll, local, local, scroll, scroll;
}

.tab {
/* https://daisyui.com/docs/themes/#-5 */
--tab-border: 0.1rem;
/* using a tab radius leads to tiny rendering issues at random screen sizes */
--tab-radius: 0;

/* https://daisyui.com/components/tab/#tabs-with-custom-color */
--tab-border-color: oklch(var(--bc));

&:not(.tab-active):not(.tab-divider) {
border: var(--tab-border) solid var(--tab-border-color);

&:hover {
@apply bg-base-200;
}
}

/* .tab-divider needs .tab so it can access the tab css-variables */
&.tab-divider {
@apply px-2;
border-bottom: var(--tab-border) solid var(--tab-border-color);
}
}
5 changes: 2 additions & 3 deletions frontend/src/lib/components/Badges/MemberBadge.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
<script lang="ts">
import { ProjectRole } from '$lib/gql/types';
import FormatUserProjectRole from '../FormatUserProjectRole.svelte';
import FormatUserProjectRole from '../Projects/FormatUserProjectRole.svelte';
import ActionBadge from './ActionBadge.svelte';
import Badge from './Badge.svelte';
export let member: { name: string; role: ProjectRole };
export let canManage = false;

export let type: 'existing' | 'new' = 'existing';
$: actionIcon = (type === 'existing' ? 'i-mdi-dots-vertical' as const : 'i-mdi-close' as const);

$: variant = member.role === ProjectRole.Manager ? 'btn-primary' as const : 'btn-secondary' as const;
</script>

Expand All @@ -21,6 +20,6 @@
<span class="flex-grow" />

<Badge>
<FormatUserProjectRole projectRole={member.role} />
<FormatUserProjectRole role={member.role} />
</Badge>
</ActionBadge>
Loading
Loading