Skip to content

Commit

Permalink
Project page now shows "Ask to join project" button for org members (#…
Browse files Browse the repository at this point in the history
…1390)

* Project page now shows "Ask to join project" button

Org members who aren't yet project members can ask to join a project by
clicking the button, which will send an email to project managers.

* Warn if trying to join project with no managers

---------

Co-authored-by: Kevin Hahn <[email protected]>
Co-authored-by: Tim Haasdyk <[email protected]>
  • Loading branch information
3 people authored Jan 24, 2025
1 parent a8038ef commit 9355ee2
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 1 deletion.
4 changes: 4 additions & 0 deletions backend/LexBoxApi/GraphQL/ProjectMutations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ await dbContext.ProjectUsers
[Error<DbError>]
[Error<ProjectMembersMustBeVerified>]
[Error<ProjectMembersMustBeVerifiedForRole>]
[Error<ProjectHasNoManagers>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
Expand All @@ -254,11 +255,14 @@ public async Task<IQueryable<Project>> AskToJoinProject(
NotFoundException.ThrowIfNull(project);

var managers = project.Users.Where(u => u.Role == ProjectRole.Manager);
var emailsSent = 0;
foreach (var manager in managers)
{
if (manager.User is null) continue;
await emailService.SendJoinProjectRequestEmail(manager.User, user, project);
emailsSent++;
}
if (emailsSent == 0) throw new ProjectHasNoManagers(project.Code);
return dbContext.Projects.Where(p => p.Id == projectId);
}

Expand Down
3 changes: 3 additions & 0 deletions backend/LexCore/Exceptions/ProjectHasNoManagers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace LexCore.Exceptions;

public class ProjectHasNoManagers(string projectCode) : Exception($"project {projectCode} has no managers");
6 changes: 5 additions & 1 deletion frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ type ProjectCreatorsMustHaveEmail implements Error {
message: String!
}

type ProjectHasNoManagers implements Error {
message: String!
}

type ProjectMemberInvitedByEmail implements Error {
message: String!
}
Expand Down Expand Up @@ -579,7 +583,7 @@ union AddProjectToOrgError = DbError | NotFoundError

union AddProjectsToOrgError = DbError | NotFoundError

union AskToJoinProjectError = NotFoundError | DbError | ProjectMembersMustBeVerified | ProjectMembersMustBeVerifiedForRole
union AskToJoinProjectError = NotFoundError | DbError | ProjectMembersMustBeVerified | ProjectMembersMustBeVerifiedForRole | ProjectHasNoManagers

union BulkAddOrgMembersError = NotFoundError | DbError | UnauthorizedAccessError

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ Lexbox is free and [open source](https://github.com/sillsdev/languageforge-lexbo
"no_thanks": "No thanks, create a new project",
"click_to_view_related_projects": "Found {count, plural, one {# related project} other {# related projects}}, click here to see {count, plural, one {it} other {them}}",
"join_request_sent": "Your request to join the {projectName} project has been sent to the project manager(s)",
"join_request_error_no_managers": "The {projectName} project has no project managers, so your request to join could not be sent",
"description": "Description",
"no_description": "This project does not have a description",
"name_missing": "Project name required",
Expand Down Expand Up @@ -531,6 +532,9 @@ Lexbox is free and [open source](https://github.com/sillsdev/languageforge-lexbo
"promote_project": {
"label": "Promote to real project",
},
"join_project": {
"label": "Ask to join project",
},
"open_with_viewer": "Browse",
"open_with_flex": {
"button": "Open with FLEx",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import { getSearchParamValues } from '$lib/util/query-params';
import FlexModelVersionText from '$lib/components/Projects/FlexModelVersionText.svelte';
import CrdtSyncButton from './CrdtSyncButton.svelte';
import {_askToJoinProject} from '../create/+page'; // TODO: Should we duplicate this function in the project_code/+page.ts file, rather than importing it from elsewhere?
import {Duration} from '$lib/util/time';
export let data: PageData;
$: user = data.user;
Expand Down Expand Up @@ -143,6 +145,11 @@
|| projectRole && !project.isConfidential // public by default for members (non-members shouldn't even be here)
|| orgRoles.some(role => role === OrgRole.Admin);
// Almost mirrors PermissionService.CanAskToJoinProject() in C#, but admins won't be shown the "ask to join" button
$: canAskToJoinProject = !user.isAdmin
&& !projectRole
&& orgRoles.some((_) => true);
let resetProjectModal: ResetProjectModal;
async function resetProject(): Promise<void> {
await resetProjectModal.open(project.code, project.resetStatus);
Expand Down Expand Up @@ -271,6 +278,19 @@
}
}
let askLoading = false;
async function askToJoinProject(projectId: string, projectName: string): Promise<void> {
askLoading = true;
const joinResult = await _askToJoinProject(projectId);
askLoading = false;
if (!joinResult.error) {
notifySuccess($t('project.create.join_request_sent', { projectName }), Duration.Persistent);
}
if (joinResult.error?.byType('ProjectHasNoManagers')) {
notifyWarning($t('project.create.join_request_error_no_managers', { projectName }), Duration.Persistent);
}
}
let projectConfidentialityModal: ProjectConfidentialityModal;
let openInFlexModal: OpenInFlexModal;
let leaveModal: ConfirmModal;
Expand Down Expand Up @@ -316,6 +336,17 @@
<CrdtSyncButton projectId={project.id} />
<OpenInFlexModal bind:this={openInFlexModal} {project}/>
<OpenInFlexButton projectId={project.id} on:click={openInFlexModal.open}/>
{:else if canAskToJoinProject}
<Button
variant="btn-primary"
loading={askLoading}
on:click={() => askToJoinProject(project.id, project.name)}
>
{#if !askLoading}
<span class="i-mdi-email text-2xl"></span>
{/if}
{$t('project_page.join_project.label')}
</Button>
{:else}
<Dropdown>
<button class="btn btn-primary">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export async function _askToJoinProject(projectId: string): $OpResult<AskToJoinP
id
}
errors {
__typename
... on DbError {
code
}
Expand Down

0 comments on commit 9355ee2

Please sign in to comment.