mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
feat: add teams (#848)
## Description Add support for teams which will allow users to collaborate on documents. Teams features allows users to: - Create, manage and transfer teams - Manage team members - Manage team emails - Manage a shared team inbox and documents These changes do NOT include the following, which are planned for a future release: - Team templates - Team API - Search menu integration ## Testing Performed - Added E2E tests for general team management - Added E2E tests to validate document counts ## Checklist - [X] I have tested these changes locally and they work as expected. - [X] I have added/updated tests that prove the effectiveness of these changes. - [ ] I have updated the documentation to reflect these changes, if applicable. - [X] I have followed the project's coding style guidelines.
This commit is contained in:
16
packages/lib/utils/billing.ts
Normal file
16
packages/lib/utils/billing.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { Subscription } from '.prisma/client';
|
||||
import { SubscriptionStatus } from '.prisma/client';
|
||||
|
||||
/**
|
||||
* Returns true if there is a subscription that is active and is a community plan.
|
||||
*/
|
||||
export const subscriptionsContainsActiveCommunityPlan = (
|
||||
subscriptions: Subscription[],
|
||||
communityPlanPriceIds: string[],
|
||||
) => {
|
||||
return subscriptions.some(
|
||||
(subscription) =>
|
||||
subscription.status === SubscriptionStatus.ACTIVE &&
|
||||
communityPlanPriceIds.includes(subscription.priceId),
|
||||
);
|
||||
};
|
||||
30
packages/lib/utils/params.ts
Normal file
30
packages/lib/utils/params.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* From an unknown string, parse it into an integer array.
|
||||
*
|
||||
* Filter out unknown values.
|
||||
*/
|
||||
export const parseToIntegerArray = (value: unknown): number[] => {
|
||||
if (typeof value !== 'string') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return value
|
||||
.split(',')
|
||||
.map((value) => parseInt(value, 10))
|
||||
.filter((value) => !isNaN(value));
|
||||
};
|
||||
|
||||
type GetRootHrefOptions = {
|
||||
returnEmptyRootString?: boolean;
|
||||
};
|
||||
|
||||
export const getRootHref = (
|
||||
params: Record<string, string | string[]> | null,
|
||||
options: GetRootHrefOptions = {},
|
||||
) => {
|
||||
if (typeof params?.teamUrl === 'string') {
|
||||
return `/t/${params.teamUrl}`;
|
||||
}
|
||||
|
||||
return options.returnEmptyRootString ? '' : '/';
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Recipient } from '@documenso/prisma/client';
|
||||
|
||||
export const recipientInitials = (text: string) =>
|
||||
export const extractInitials = (text: string) =>
|
||||
text
|
||||
.split(' ')
|
||||
.map((name: string) => name.slice(0, 1).toUpperCase())
|
||||
@ -8,5 +8,5 @@ export const recipientInitials = (text: string) =>
|
||||
.join('');
|
||||
|
||||
export const recipientAbbreviation = (recipient: Recipient) => {
|
||||
return recipientInitials(recipient.name) || recipient.email.slice(0, 1).toUpperCase();
|
||||
return extractInitials(recipient.name) || recipient.email.slice(0, 1).toUpperCase();
|
||||
};
|
||||
|
||||
42
packages/lib/utils/teams.ts
Normal file
42
packages/lib/utils/teams.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { WEBAPP_BASE_URL } from '../constants/app';
|
||||
import type { TEAM_MEMBER_ROLE_MAP } from '../constants/teams';
|
||||
import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../constants/teams';
|
||||
|
||||
export const formatTeamUrl = (teamUrl: string, baseUrl?: string) => {
|
||||
const formattedBaseUrl = (baseUrl ?? WEBAPP_BASE_URL).replace(/https?:\/\//, '');
|
||||
|
||||
return `${formattedBaseUrl}/t/${teamUrl}`;
|
||||
};
|
||||
|
||||
export const formatDocumentsPath = (teamUrl?: string) => {
|
||||
return teamUrl ? `/t/${teamUrl}/documents` : '/documents';
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether a team member can execute a given action.
|
||||
*
|
||||
* @param action The action the user is trying to execute.
|
||||
* @param role The current role of the user.
|
||||
* @returns Whether the user can execute the action.
|
||||
*/
|
||||
export const canExecuteTeamAction = (
|
||||
action: keyof typeof TEAM_MEMBER_ROLE_PERMISSIONS_MAP,
|
||||
role: keyof typeof TEAM_MEMBER_ROLE_MAP,
|
||||
) => {
|
||||
return TEAM_MEMBER_ROLE_PERMISSIONS_MAP[action].some((i) => i === role);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compares the provided `currentUserRole` with the provided `roleToCheck` to determine
|
||||
* whether the `currentUserRole` has permission to modify the `roleToCheck`.
|
||||
*
|
||||
* @param currentUserRole Role of the current user
|
||||
* @param roleToCheck Role of another user to see if the current user can modify
|
||||
* @returns True if the current user can modify the other user, false otherwise
|
||||
*/
|
||||
export const isTeamRoleWithinUserHierarchy = (
|
||||
currentUserRole: keyof typeof TEAM_MEMBER_ROLE_MAP,
|
||||
roleToCheck: keyof typeof TEAM_MEMBER_ROLE_MAP,
|
||||
) => {
|
||||
return TEAM_MEMBER_ROLE_HIERARCHY[currentUserRole].some((i) => i === roleToCheck);
|
||||
};
|
||||
21
packages/lib/utils/token-verification.ts
Normal file
21
packages/lib/utils/token-verification.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { DurationLike } from 'luxon';
|
||||
import { DateTime } from 'luxon';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
/**
|
||||
* Create a token verification object.
|
||||
*
|
||||
* @param expiry The date the token expires, or the duration until the token expires.
|
||||
*/
|
||||
export const createTokenVerification = (expiry: Date | DurationLike) => {
|
||||
const expiresAt = expiry instanceof Date ? expiry : DateTime.now().plus(expiry).toJSDate();
|
||||
|
||||
return {
|
||||
expiresAt,
|
||||
token: nanoid(32),
|
||||
};
|
||||
};
|
||||
|
||||
export const isTokenExpired = (expiresAt: Date) => {
|
||||
return expiresAt < new Date();
|
||||
};
|
||||
Reference in New Issue
Block a user