Compare commits

...

3 Commits

10 changed files with 140 additions and 31 deletions

View File

@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Organization, "displayAllMembersInvite"))) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.boolean("displayAllMembersInvite").defaultTo(true);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Organization, "displayAllMembersInvite")) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.dropColumn("displayAllMembersInvite");
});
}
}

View File

@ -22,7 +22,8 @@ export const OrganizationsSchema = z.object({
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
defaultMembershipRole: z.string().default("member"),
enforceMfa: z.boolean().default(false),
selectedMfaMethod: z.string().nullable().optional()
selectedMfaMethod: z.string().nullable().optional(),
displayAllMembersInvite: z.boolean().default(true)
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

View File

@ -257,7 +257,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
scimEnabled: z.boolean().optional(),
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
enforceMfa: z.boolean().optional(),
selectedMfaMethod: z.nativeEnum(MfaMethod).optional()
selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
displayAllMembersInvite: z.boolean().optional()
}),
response: {
200: z.object({

View File

@ -286,7 +286,16 @@ export const orgServiceFactory = ({
actorOrgId,
actorAuthMethod,
orgId,
data: { name, slug, authEnforced, scimEnabled, defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod }
data: {
name,
slug,
authEnforced,
scimEnabled,
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod,
displayAllMembersInvite
}
}: TUpdateOrgDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
@ -358,7 +367,8 @@ export const orgServiceFactory = ({
scimEnabled,
defaultMembershipRole,
enforceMfa,
selectedMfaMethod
selectedMfaMethod,
displayAllMembersInvite
});
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
return org;

View File

@ -72,6 +72,7 @@ export type TUpdateOrgDTO = {
defaultMembershipRoleSlug: string;
enforceMfa: boolean;
selectedMfaMethod: MfaMethod;
displayAllMembersInvite: boolean;
}>;
} & TOrgPermission;

View File

@ -51,7 +51,7 @@ const formSchema = z.object({
.trim()
.max(256, "Description too long, max length is 256 characters")
.optional(),
addMembers: z.boolean(),
addMembers: z.boolean().default(false),
kmsKeyId: z.string(),
template: z.string()
});
@ -246,30 +246,32 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
)}
/>
</div>
<div className="mt-4 pl-1">
<Controller
control={control}
name="addMembers"
defaultValue={false}
render={({ field: { onBlur, value, onChange } }) => (
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.Member}>
{(isAllowed) => (
<div>
<Checkbox
id="add-project-layout"
isChecked={value}
onCheckedChange={onChange}
isDisabled={!isAllowed}
onBlur={onBlur}
>
Add all members of my organization to this project
</Checkbox>
</div>
)}
</OrgPermissionCan>
)}
/>
</div>
{currentOrg?.displayAllMembersInvite && (
<div className="mt-4 pl-1">
<Controller
control={control}
name="addMembers"
defaultValue={false}
render={({ field: { onBlur, value, onChange } }) => (
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.Member}>
{(isAllowed) => (
<div>
<Checkbox
id="add-project-layout"
isChecked={value}
onCheckedChange={onChange}
isDisabled={!isAllowed}
onBlur={onBlur}
>
Add all members of my organization to this project
</Checkbox>
</div>
)}
</OrgPermissionCan>
)}
/>
</div>
)}
<div className="mt-14 flex">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="advance-settings" className="data-[state=open]:border-none">

View File

@ -109,7 +109,8 @@ export const useUpdateOrg = () => {
orgId,
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod
selectedMfaMethod,
displayAllMembersInvite
}) => {
return apiRequest.patch(`/api/v1/organization/${orgId}`, {
name,
@ -118,7 +119,8 @@ export const useUpdateOrg = () => {
slug,
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod
selectedMfaMethod,
displayAllMembersInvite
});
},
onSuccess: () => {

View File

@ -15,6 +15,7 @@ export type Organization = {
defaultMembershipRole: string;
enforceMfa: boolean;
selectedMfaMethod?: MfaMethod;
displayAllMembersInvite?: boolean;
};
export type UpdateOrgDTO = {
@ -26,6 +27,7 @@ export type UpdateOrgDTO = {
defaultMembershipRoleSlug?: string;
enforceMfa?: boolean;
selectedMfaMethod?: MfaMethod;
displayAllMembersInvite?: boolean;
};
export type BillingDetails = {

View File

@ -11,6 +11,7 @@ import { OrgAuthTab } from "../OrgAuthTab";
import { OrgEncryptionTab } from "../OrgEncryptionTab";
import { OrgGeneralTab } from "../OrgGeneralTab";
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
import { ProductSettings } from "../ProductSettings/ProductSettings";
import { ProjectTemplatesTab } from "../ProjectTemplatesTab";
export const OrgTabGroup = () => {
@ -20,6 +21,7 @@ export const OrgTabGroup = () => {
const tabs = [
{ name: "General", key: "tab-org-general", component: OrgGeneralTab },
{ name: "Security", key: "tab-org-security", component: OrgAuthTab },
{ name: "Products", key: "tab-org-products", component: ProductSettings },
{ name: "Encryption", key: "tab-org-encryption", component: OrgEncryptionTab },
{
name: "Workflow Integrations",

View File

@ -0,0 +1,69 @@
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { ContentLoader, Switch } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useUpdateOrg } from "@app/hooks/api";
const ProductConfigSection = () => {
const { currentOrg } = useOrganization();
const { mutateAsync } = useUpdateOrg();
const handleDisplayAllMembersInviteToggle = async (value: boolean) => {
try {
if (!currentOrg?.id) return;
await mutateAsync({
orgId: currentOrg?.id,
displayAllMembersInvite: value
});
createNotification({
text: `Successfully ${value ? "enabled" : "disabled"} display all members invite`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update setting",
type: "error"
});
}
};
if (!currentOrg) {
return <ContentLoader />;
}
return (
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
<div className="py-4">
<div className="mb-2 flex justify-between">
<h3 className="text-md text-mineshaft-100">
Display All Members Invite on Project Creation
</h3>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
{(isAllowed) => (
<Switch
id="display-all-members-invite"
onCheckedChange={(value) => handleDisplayAllMembersInviteToggle(value)}
isChecked={currentOrg?.displayAllMembersInvite ?? false}
isDisabled={!isAllowed}
/>
)}
</OrgPermissionCan>
</div>
<p className="text-sm text-mineshaft-300">
Control whether to display all members in the invite section on project creation
</p>
</div>
</div>
);
};
export const ProductSettings = () => {
return (
<div>
<ProductConfigSection />
</div>
);
};