feat: added default product switch in project settings

This commit is contained in:
=
2025-07-01 17:16:02 +05:30
parent 0b2c967e63
commit 8bdf294a34
12 changed files with 56 additions and 24 deletions

View File

@@ -5,12 +5,14 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, FormControl, Input, TextArea } from "@app/components/v2";
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useUpdateProject } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
const baseFormSchema = z.object({
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
defaultProduct: z.nativeEnum(ProjectType).default(ProjectType.SecretManager),
description: z
.string()
.trim()
@@ -50,6 +52,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
reset({
name: currentWorkspace.name,
description: currentWorkspace.description ?? "",
defaultProduct: currentWorkspace.defaultProduct,
...(showSlugField && { slug: currentWorkspace.slug })
});
}
@@ -63,6 +66,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
projectID: currentWorkspace.id,
newProjectName: data.name,
newProjectDescription: data.description,
defaultProduct: data.defaultProduct,
...(showSlugField &&
"slug" in data && {
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
@@ -212,6 +216,31 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
</ProjectPermissionCan>
</div>
</div>
<Controller
control={control}
name="defaultProduct"
defaultValue={ProjectType.SecretManager}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Default Product"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full max-w-md bg-mineshaft-800"
>
<SelectItem value={ProjectType.SecretManager}>Secrets Manager</SelectItem>
<SelectItem value={ProjectType.CertificateManager}>PKI Manager</SelectItem>
<SelectItem value={ProjectType.KMS}>KMS</SelectItem>
<SelectItem value={ProjectType.SSH}>SSH</SelectItem>
<SelectItem value={ProjectType.SecretScanning}>Secret Scanning</SelectItem>
</Select>
</FormControl>
)}
/>
<div>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}

View File

@@ -120,7 +120,10 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
createNotification({ text: "Project created", type: "success" });
reset();
onOpenChange(false);
navigate({ to: getProjectHomePage(project.defaultType), params: { projectId: project.id } });
navigate({
to: getProjectHomePage(project.defaultProduct),
params: { projectId: project.id }
});
} catch (err) {
console.error(err);
createNotification({ text: "Failed to create project", type: "error" });

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */
import React from "react";
import { faEllipsis, faSlash, faSort } from "@fortawesome/free-solid-svg-icons";
import { faEllipsis, faSort } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, ReactNode } from "@tanstack/react-router";
import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link";
@@ -40,7 +40,7 @@ const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWitho
({ className, ...props }, ref) => (
<li
ref={ref}
className={twMerge("inline-flex text-sm items-center gap-1.5", className)}
className={twMerge("inline-flex items-center gap-1.5 text-sm", className)}
{...props}
/>
)
@@ -79,9 +79,7 @@ BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
<li role="presentation" aria-hidden="true" className={twMerge("", className)} {...props}>
{children ?? (
<p className="text-lg text-mineshaft-400/70 px-2">/</p>
)}
{children ?? <p className="px-2 text-lg text-mineshaft-400/70">/</p>}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
@@ -136,7 +134,7 @@ const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[]
<DropdownMenu>
<DropdownMenuTrigger>
<BreadcrumbItem>
<BreadcrumbSegment className="px-2 py-1 rounded-md py-2 hover:bg-mineshaft-600">
<BreadcrumbSegment className="rounded-md px-2 py-1 py-2 hover:bg-mineshaft-600">
{el.label} <FontAwesomeIcon icon={faSort} size="sm" />
</BreadcrumbSegment>
</BreadcrumbItem>

View File

@@ -51,7 +51,7 @@ export type IdentityMembershipOrg = {
export type IdentityMembership = {
id: string;
identity: Identity;
project: Pick<Workspace, "id" | "name" | "defaultType">;
project: Pick<Workspace, "id" | "name" | "defaultProduct">;
roles: Array<
{
id: string;

View File

@@ -280,7 +280,8 @@ export const useUpdateProject = () => {
newProjectDescription,
newSlug,
secretSharing,
showSnapshotsLegacy
showSnapshotsLegacy,
defaultProduct
}) => {
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
`/api/v1/workspace/${projectID}`,
@@ -288,6 +289,7 @@ export const useUpdateProject = () => {
name: newProjectName,
description: newProjectDescription,
slug: newSlug,
defaultProduct,
secretSharing,
showSnapshotsLegacy
}

View File

@@ -24,7 +24,7 @@ export type Workspace = {
__v: number;
id: string;
name: string;
defaultType: ProjectType;
defaultProduct: ProjectType;
description?: string;
orgId: string;
version: ProjectVersion;
@@ -80,6 +80,7 @@ export type UpdateProjectDTO = {
newSlug?: string;
secretSharing?: boolean;
showSnapshotsLegacy?: boolean;
defaultProduct?: ProjectType;
};
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };

View File

@@ -168,11 +168,10 @@ export const Navbar = () => {
<p className="pl-2 pr-3 text-lg text-mineshaft-400/70">/</p>
<DropdownMenu modal={false}>
<Link to="/organization/projects">
<div className="flex cursor-pointer items-center gap-2 text-sm text-white">
<div className="flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary-400">
<div>
<FontAwesomeIcon icon={faCubes} className="pr-1 text-xs" />
</div>
<div className="max-w-32 overflow-hidden text-ellipsis">{currentOrg?.name}</div>
<div className="mr-2 rounded border border-mineshaft-500 px-1 text-xs text-bunker-300">
{getPlan(subscription)}

View File

@@ -102,13 +102,13 @@ export const ProjectSelect = () => {
<DropdownMenu modal={false}>
<Link
to={getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
getCurrentProductFromUrl(window.location.href) || currentWorkspace.defaultProduct
)}
params={{
projectId: currentWorkspace.id
}}
>
<div className="flex cursor-pointer items-center gap-2 text-sm text-white">
<div className="flex cursor-pointer items-center gap-2 text-sm text-white duration-100 hover:text-primary-400">
<div>
<FontAwesomeIcon icon={faCube} className="text-xs" />
</div>
@@ -156,7 +156,7 @@ export const ProjectSelect = () => {
// to reproduce change this back to router.push and switch between two projects with different env count
// look into this on dashboard revamp
const url = linkOptions({
to: getProjectHomePage(workspace.defaultType),
to: getProjectHomePage(workspace.defaultProduct),
params: {
projectId: workspace.id
}

View File

@@ -105,7 +105,7 @@ export const OrgAdminProjects = withPermission(
<TBody>
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
{!isProjectsLoading &&
projects?.map(({ name, slug, createdAt, id, defaultType }) => (
projects?.map(({ name, slug, createdAt, id, defaultProduct }) => (
<Tr key={`project-${id}`} className="group w-full">
<Td>{name}</Td>
<Td>{slug}</Td>
@@ -126,7 +126,7 @@ export const OrgAdminProjects = withPermission(
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleAccessProject(defaultType, id);
handleAccessProject(defaultProduct, id);
}}
icon={<FontAwesomeIcon icon={faSignIn} />}
disabled={

View File

@@ -418,10 +418,10 @@ export const LogsFilter = ({
onChange(e);
}}
placeholder="All projects"
options={workspacesInOrg.map(({ name, id, defaultType }) => ({
options={workspacesInOrg.map(({ name, id, defaultProduct }) => ({
name,
id,
type: defaultType
type: defaultProduct
}))}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}

View File

@@ -222,7 +222,7 @@ export const AllProjectView = ({
onKeyDown={(evt) => {
if (evt.key === "Enter" && workspace.isMember) {
navigate({
to: getProjectHomePage(workspace.defaultType),
to: getProjectHomePage(workspace.defaultProduct),
params: {
projectId: workspace.id
}
@@ -232,7 +232,7 @@ export const AllProjectView = ({
onClick={() => {
if (workspace.isMember) {
navigate({
to: getProjectHomePage(workspace.defaultType),
to: getProjectHomePage(workspace.defaultProduct),
params: {
projectId: workspace.id
}

View File

@@ -153,7 +153,7 @@ export const MyProjectView = ({
<div
onClick={() => {
navigate({
to: getProjectHomePage(workspace.defaultType),
to: getProjectHomePage(workspace.defaultProduct),
params: {
projectId: workspace.id
}
@@ -215,7 +215,7 @@ export const MyProjectView = ({
<div
onClick={() => {
navigate({
to: getProjectHomePage(workspace.defaultType),
to: getProjectHomePage(workspace.defaultProduct),
params: {
projectId: workspace.id
}