mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 09:26:47 +00:00
feat: updated settings page
This commit is contained in:
@@ -4,7 +4,7 @@ import { OrganizationsSchema } from "@app/db/schemas";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags, SUB_ORGANIZATIONS } from "@app/lib/api-docs";
|
import { ApiDocsTags, SUB_ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { GenericResourceNameSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export const registerSubOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: GenericResourceNameSchema.describe(SUB_ORGANIZATIONS.CREATE.name)
|
name: slugSchema().describe(SUB_ORGANIZATIONS.CREATE.name)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -108,4 +108,55 @@ export const registerSubOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { organizations };
|
return { organizations };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:subOrgId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SubOrganizations],
|
||||||
|
description: "Update a sub organization",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
subOrgId: z.string().trim().describe(SUB_ORGANIZATIONS.UPDATE.subOrgId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: slugSchema().describe(SUB_ORGANIZATIONS.UPDATE.name)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organization: sanitizedSubOrganizationSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { organization } = await server.services.subOrganization.updateSubOrg({
|
||||||
|
subOrgId: req.params.subOrgId,
|
||||||
|
name: req.body.name,
|
||||||
|
permissionActor: req.permission
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_SUB_ORGANIZATION,
|
||||||
|
metadata: {
|
||||||
|
name: req.body.name,
|
||||||
|
organizationId: organization.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { organization };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -173,7 +173,8 @@ export enum EventType {
|
|||||||
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
|
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
|
||||||
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
|
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
|
||||||
|
|
||||||
CREATE_SUB_ORGANIZATION = "create-child-organization",
|
CREATE_SUB_ORGANIZATION = "create-sub-organization",
|
||||||
|
UPDATE_SUB_ORGANIZATION = "update-sub-organization",
|
||||||
|
|
||||||
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
|
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
|
||||||
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
|
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
|
||||||
@@ -617,6 +618,14 @@ interface CreateSubOrganizationEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpdateSubOrganizationEvent {
|
||||||
|
type: EventType.UPDATE_SUB_ORGANIZATION;
|
||||||
|
metadata: {
|
||||||
|
name: string;
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type TSecretMetadata = { key: string; value: string }[];
|
type TSecretMetadata = { key: string; value: string }[];
|
||||||
|
|
||||||
interface GetSecretEvent {
|
interface GetSecretEvent {
|
||||||
@@ -3874,6 +3883,7 @@ interface PamResourceDeleteEvent {
|
|||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| CreateSubOrganizationEvent
|
| CreateSubOrganizationEvent
|
||||||
|
| UpdateSubOrganizationEvent
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
| CreateSecretEvent
|
| CreateSecretEvent
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ import { TMembershipRoleDALFactory } from "@app/services/membership/membership-r
|
|||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionChildOrgActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionChildOrgActions,
|
||||||
|
OrgPermissionSubjects
|
||||||
|
} from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||||
import { TCreateSubOrgDTO, TListSubOrgDTO } from "./sub-org-types";
|
import { TCreateSubOrgDTO, TListSubOrgDTO, TUpdateSubOrgDTO } from "./sub-org-types";
|
||||||
|
|
||||||
type TSubOrgServiceFactoryDep = {
|
type TSubOrgServiceFactoryDep = {
|
||||||
orgDAL: Pick<TOrgDALFactory, "findOne" | "create" | "transaction" | "listSubOrganizations">;
|
orgDAL: Pick<
|
||||||
|
TOrgDALFactory,
|
||||||
|
"findOne" | "create" | "transaction" | "listSubOrganizations" | "updateById" | "findById"
|
||||||
|
>;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
membershipDAL: Pick<TMembershipDALFactory, "create">;
|
membershipDAL: Pick<TMembershipDALFactory, "create">;
|
||||||
@@ -113,8 +120,45 @@ export const subOrgServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateSubOrg = async ({ subOrgId, name, permissionActor }: TUpdateSubOrgDTO) => {
|
||||||
|
const subOrg = await orgDAL.findOne({
|
||||||
|
rootOrgId: permissionActor.rootOrgId,
|
||||||
|
id: subOrgId
|
||||||
|
});
|
||||||
|
if (!subOrg) {
|
||||||
|
throw new BadRequestError({ message: "Sub-organization not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission({
|
||||||
|
actorId: permissionActor.id,
|
||||||
|
actor: permissionActor.type,
|
||||||
|
orgId: subOrgId,
|
||||||
|
actorOrgId: subOrgId,
|
||||||
|
actorAuthMethod: permissionActor.authMethod,
|
||||||
|
scope: OrganizationActionScope.ChildOrganization
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
|
const existingSubOrg = await orgDAL.findOne({
|
||||||
|
parentOrgId: subOrg.parentOrgId,
|
||||||
|
slug: name
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingSubOrg && existingSubOrg.id !== subOrgId) {
|
||||||
|
throw new BadRequestError({ message: `Sub-organization with name ${name} already exists` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const organization = await orgDAL.updateById(subOrgId, { name, slug: name });
|
||||||
|
|
||||||
|
return {
|
||||||
|
organization
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createSubOrg,
|
createSubOrg,
|
||||||
listSubOrgs
|
listSubOrgs,
|
||||||
|
updateSubOrg
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,3 +14,9 @@ export type TListSubOrgDTO = {
|
|||||||
isAccessible?: boolean;
|
isAccessible?: boolean;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TUpdateSubOrgDTO = {
|
||||||
|
subOrgId: string;
|
||||||
|
name: string;
|
||||||
|
permissionActor: OrgServiceActor;
|
||||||
|
};
|
||||||
|
|||||||
@@ -721,6 +721,10 @@ export const SUB_ORGANIZATIONS = {
|
|||||||
CREATE: {
|
CREATE: {
|
||||||
name: "The name of the sub organization to create."
|
name: "The name of the sub organization to create."
|
||||||
},
|
},
|
||||||
|
UPDATE: {
|
||||||
|
name: "The name of the sub organization to update.",
|
||||||
|
subOrgId: "The id of the sub organization to update."
|
||||||
|
},
|
||||||
LIST: {
|
LIST: {
|
||||||
limit: "The number of sub organizations to return.",
|
limit: "The number of sub organizations to return.",
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th sub organization.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th sub organization.",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { GenericResourceNameSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
@@ -149,7 +149,7 @@ export const injectIdentity = fp(
|
|||||||
|
|
||||||
const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined;
|
const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined;
|
||||||
if (subOrganizationSelector) {
|
if (subOrganizationSelector) {
|
||||||
await GenericResourceNameSchema.parseAsync(subOrganizationSelector);
|
await slugSchema().parseAsync(subOrganizationSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (authMode) {
|
switch (authMode) {
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ export * from "./SecretScanningSecretsDetectedTemplate";
|
|||||||
export * from "./SecretSyncFailedTemplate";
|
export * from "./SecretSyncFailedTemplate";
|
||||||
export * from "./ServiceTokenExpiryNoticeTemplate";
|
export * from "./ServiceTokenExpiryNoticeTemplate";
|
||||||
export * from "./SignupEmailVerificationTemplate";
|
export * from "./SignupEmailVerificationTemplate";
|
||||||
export * from "./UnlockAccountTemplate";
|
|
||||||
export * from "./SubOrganizationInvitationTemplate";
|
export * from "./SubOrganizationInvitationTemplate";
|
||||||
|
export * from "./UnlockAccountTemplate";
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import {
|
|||||||
SecretSyncFailedTemplate,
|
SecretSyncFailedTemplate,
|
||||||
ServiceTokenExpiryNoticeTemplate,
|
ServiceTokenExpiryNoticeTemplate,
|
||||||
SignupEmailVerificationTemplate,
|
SignupEmailVerificationTemplate,
|
||||||
UnlockAccountTemplate,
|
SubOrganizationInvitationTemplate,
|
||||||
SubOrganizationInvitationTemplate
|
UnlockAccountTemplate
|
||||||
} from "./emails";
|
} from "./emails";
|
||||||
|
|
||||||
export type TSmtpConfig = SMTPTransport.Options;
|
export type TSmtpConfig = SMTPTransport.Options;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { useRouteContext, useSearch } from "@tanstack/react-router";
|
import { useRouteContext, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
export const useOrganization = () => {
|
export const useOrganization = () => {
|
||||||
const organizationId = useRouteContext({
|
const organizationId = useRouteContext({
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
export { useCreateSubOrganization } from "./mutations";
|
export { useCreateSubOrganization, useUpdateSubOrganization } from "./mutations";
|
||||||
export { subOrganizationsQuery } from "./queries";
|
export { subOrganizationsQuery } from "./queries";
|
||||||
export type {
|
export type {
|
||||||
TCreateSubOrganizationDTO,
|
TCreateSubOrganizationDTO,
|
||||||
TListSubOrganizationsDTO,
|
TListSubOrganizationsDTO,
|
||||||
TSubOrganization
|
TSubOrganization,
|
||||||
|
TUpdateSubOrganizationDTO
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { subOrganizationsQuery } from "./queries";
|
import { subOrganizationsQuery } from "./queries";
|
||||||
import { TCreateSubOrganizationDTO, TSubOrganization } from "./types";
|
import { TCreateSubOrganizationDTO, TSubOrganization, TUpdateSubOrganizationDTO } from "./types";
|
||||||
|
|
||||||
export const useCreateSubOrganization = () => {
|
export const useCreateSubOrganization = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -20,3 +20,19 @@ export const useCreateSubOrganization = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdateSubOrganization = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({ subOrgId, name }: TUpdateSubOrganizationDTO) => {
|
||||||
|
const { data } = await apiRequest.patch<{ organization: TSubOrganization }>(
|
||||||
|
`/api/v1/sub-organizations/${subOrgId}`,
|
||||||
|
{ name }
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: subOrganizationsQuery.allKey() });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,3 +15,8 @@ export type TListSubOrganizationsDTO = {
|
|||||||
offset?: number;
|
offset?: number;
|
||||||
isAccessible?: boolean;
|
isAccessible?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TUpdateSubOrganizationDTO = {
|
||||||
|
subOrgId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ export const MyProjectView = ({
|
|||||||
<div className="flex w-full flex-row">
|
<div className="flex w-full flex-row">
|
||||||
<ProjectListToggle value={projectListView} onChange={onProjectListViewChange} />
|
<ProjectListToggle value={projectListView} onChange={onProjectListViewChange} />
|
||||||
<Input
|
<Input
|
||||||
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50/60 duration-200 focus:bg-mineshaft-700/80"
|
||||||
containerClassName="w-full ml-2"
|
containerClassName="w-full ml-2"
|
||||||
placeholder="Search by project name..."
|
placeholder="Search by project name..."
|
||||||
value={searchFilter}
|
value={searchFilter}
|
||||||
|
|||||||
+4
-3
@@ -1,15 +1,16 @@
|
|||||||
import { useOrgPermission } from "@app/context";
|
import { useOrganization, useOrgPermission } from "@app/context";
|
||||||
import { OrgMembershipRole } from "@app/helpers/roles";
|
import { OrgMembershipRole } from "@app/helpers/roles";
|
||||||
|
|
||||||
import { OrgDeleteSection } from "../OrgDeleteSection";
|
import { OrgDeleteSection } from "../OrgDeleteSection";
|
||||||
import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection";
|
import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection";
|
||||||
import { OrgNameChangeSection } from "../OrgNameChangeSection";
|
import { OrgNameChangeSection, SubOrgNameChangeSection } from "../OrgNameChangeSection";
|
||||||
|
|
||||||
export const OrgGeneralTab = () => {
|
export const OrgGeneralTab = () => {
|
||||||
const { hasOrgRole } = useOrgPermission();
|
const { hasOrgRole } = useOrgPermission();
|
||||||
|
const { isSubOrganization } = useOrganization();
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
<OrgNameChangeSection />
|
{isSubOrganization ? <SubOrgNameChangeSection /> : <OrgNameChangeSection />}
|
||||||
<OrgIncidentContactsSection />
|
<OrgIncidentContactsSection />
|
||||||
{hasOrgRole(OrgMembershipRole.Admin) && <OrgDeleteSection />}
|
{hasOrgRole(OrgMembershipRole.Admin) && <OrgDeleteSection />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useNavigate, useRouter } from "@tanstack/react-router";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
|
useOrgPermission
|
||||||
|
} from "@app/context";
|
||||||
|
import { useUpdateSubOrganization } from "@app/hooks/api";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.regex(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const SubOrgNameChangeSection = (): JSX.Element => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { permission } = useOrgPermission();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const router = useRouter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { handleSubmit, control } = useForm<FormData>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: currentOrg?.subOrganization?.name || ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { mutateAsync, isPending } = useUpdateSubOrganization();
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ name }: FormData) => {
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
name,
|
||||||
|
subOrgId: currentOrg.id
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate({ to: "/organization/settings", search: { subOrganization: name } });
|
||||||
|
queryClient.clear();
|
||||||
|
await router.invalidate({ sync: true });
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully updated sub-organization details",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to update sub-organization details",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)} className="py-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-md mb-2 text-mineshaft-100">Organization Name</h2>
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
|
||||||
|
<Input
|
||||||
|
isDisabled={permission.cannot(
|
||||||
|
OrgPermissionActions.Edit,
|
||||||
|
OrgPermissionSubjects.Settings
|
||||||
|
)}
|
||||||
|
placeholder="Acme Corp"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
isLoading={isPending}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { OrgNameChangeSection } from "./OrgNameChangeSection";
|
export { OrgNameChangeSection } from "./OrgNameChangeSection";
|
||||||
|
export { SubOrgNameChangeSection } from "./SubOrgNameChangeSection";
|
||||||
|
|||||||
Reference in New Issue
Block a user