feat: added default product switch in project settings

This commit is contained in:
=
2025-07-02 01:16:51 +05:30
parent 0b2c967e63
commit 8bdf294a34
12 changed files with 56 additions and 24 deletions
@@ -5,12 +5,14 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; 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 { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useUpdateProject } from "@app/hooks/api"; import { useUpdateProject } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
const baseFormSchema = z.object({ const baseFormSchema = z.object({
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"), name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
defaultProduct: z.nativeEnum(ProjectType).default(ProjectType.SecretManager),
description: z description: z
.string() .string()
.trim() .trim()
@@ -50,6 +52,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
reset({ reset({
name: currentWorkspace.name, name: currentWorkspace.name,
description: currentWorkspace.description ?? "", description: currentWorkspace.description ?? "",
defaultProduct: currentWorkspace.defaultProduct,
...(showSlugField && { slug: currentWorkspace.slug }) ...(showSlugField && { slug: currentWorkspace.slug })
}); });
} }
@@ -63,6 +66,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
projectID: currentWorkspace.id, projectID: currentWorkspace.id,
newProjectName: data.name, newProjectName: data.name,
newProjectDescription: data.description, newProjectDescription: data.description,
defaultProduct: data.defaultProduct,
...(showSlugField && ...(showSlugField &&
"slug" in data && { "slug" in data && {
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
@@ -212,6 +216,31 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
</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> <div>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
@@ -120,7 +120,10 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
createNotification({ text: "Project created", type: "success" }); createNotification({ text: "Project created", type: "success" });
reset(); reset();
onOpenChange(false); onOpenChange(false);
navigate({ to: getProjectHomePage(project.defaultType), params: { projectId: project.id } }); navigate({
to: getProjectHomePage(project.defaultProduct),
params: { projectId: project.id }
});
} catch (err) { } catch (err) {
console.error(err); console.error(err);
createNotification({ text: "Failed to create project", type: "error" }); createNotification({ text: "Failed to create project", type: "error" });
@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React from "react"; 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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, ReactNode } from "@tanstack/react-router"; import { Link, ReactNode } from "@tanstack/react-router";
import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link"; 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) => ( ({ className, ...props }, ref) => (
<li <li
ref={ref} 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} {...props}
/> />
) )
@@ -79,9 +79,7 @@ BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => ( const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
<li role="presentation" aria-hidden="true" className={twMerge("", className)} {...props}> <li role="presentation" aria-hidden="true" className={twMerge("", className)} {...props}>
{children ?? ( {children ?? <p className="px-2 text-lg text-mineshaft-400/70">/</p>}
<p className="text-lg text-mineshaft-400/70 px-2">/</p>
)}
</li> </li>
); );
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"; BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
@@ -136,7 +134,7 @@ const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[]
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<BreadcrumbItem> <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" /> {el.label} <FontAwesomeIcon icon={faSort} size="sm" />
</BreadcrumbSegment> </BreadcrumbSegment>
</BreadcrumbItem> </BreadcrumbItem>
+1 -1
View File
@@ -51,7 +51,7 @@ export type IdentityMembershipOrg = {
export type IdentityMembership = { export type IdentityMembership = {
id: string; id: string;
identity: Identity; identity: Identity;
project: Pick<Workspace, "id" | "name" | "defaultType">; project: Pick<Workspace, "id" | "name" | "defaultProduct">;
roles: Array< roles: Array<
{ {
id: string; id: string;
+3 -1
View File
@@ -280,7 +280,8 @@ export const useUpdateProject = () => {
newProjectDescription, newProjectDescription,
newSlug, newSlug,
secretSharing, secretSharing,
showSnapshotsLegacy showSnapshotsLegacy,
defaultProduct
}) => { }) => {
const { data } = await apiRequest.patch<{ workspace: Workspace }>( const { data } = await apiRequest.patch<{ workspace: Workspace }>(
`/api/v1/workspace/${projectID}`, `/api/v1/workspace/${projectID}`,
@@ -288,6 +289,7 @@ export const useUpdateProject = () => {
name: newProjectName, name: newProjectName,
description: newProjectDescription, description: newProjectDescription,
slug: newSlug, slug: newSlug,
defaultProduct,
secretSharing, secretSharing,
showSnapshotsLegacy showSnapshotsLegacy
} }
+2 -1
View File
@@ -24,7 +24,7 @@ export type Workspace = {
__v: number; __v: number;
id: string; id: string;
name: string; name: string;
defaultType: ProjectType; defaultProduct: ProjectType;
description?: string; description?: string;
orgId: string; orgId: string;
version: ProjectVersion; version: ProjectVersion;
@@ -80,6 +80,7 @@ export type UpdateProjectDTO = {
newSlug?: string; newSlug?: string;
secretSharing?: boolean; secretSharing?: boolean;
showSnapshotsLegacy?: boolean; showSnapshotsLegacy?: boolean;
defaultProduct?: ProjectType;
}; };
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number }; export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
@@ -168,11 +168,10 @@ export const Navbar = () => {
<p className="pl-2 pr-3 text-lg text-mineshaft-400/70">/</p> <p className="pl-2 pr-3 text-lg text-mineshaft-400/70">/</p>
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<Link to="/organization/projects"> <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> <div>
<FontAwesomeIcon icon={faCubes} className="pr-1 text-xs" /> <FontAwesomeIcon icon={faCubes} className="pr-1 text-xs" />
</div> </div>
<div className="max-w-32 overflow-hidden text-ellipsis">{currentOrg?.name}</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"> <div className="mr-2 rounded border border-mineshaft-500 px-1 text-xs text-bunker-300">
{getPlan(subscription)} {getPlan(subscription)}
@@ -102,13 +102,13 @@ export const ProjectSelect = () => {
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<Link <Link
to={getProjectHomePage( to={getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager getCurrentProductFromUrl(window.location.href) || currentWorkspace.defaultProduct
)} )}
params={{ params={{
projectId: currentWorkspace.id 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> <div>
<FontAwesomeIcon icon={faCube} className="text-xs" /> <FontAwesomeIcon icon={faCube} className="text-xs" />
</div> </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 // to reproduce change this back to router.push and switch between two projects with different env count
// look into this on dashboard revamp // look into this on dashboard revamp
const url = linkOptions({ const url = linkOptions({
to: getProjectHomePage(workspace.defaultType), to: getProjectHomePage(workspace.defaultProduct),
params: { params: {
projectId: workspace.id projectId: workspace.id
} }
@@ -105,7 +105,7 @@ export const OrgAdminProjects = withPermission(
<TBody> <TBody>
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />} {isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
{!isProjectsLoading && {!isProjectsLoading &&
projects?.map(({ name, slug, createdAt, id, defaultType }) => ( projects?.map(({ name, slug, createdAt, id, defaultProduct }) => (
<Tr key={`project-${id}`} className="group w-full"> <Tr key={`project-${id}`} className="group w-full">
<Td>{name}</Td> <Td>{name}</Td>
<Td>{slug}</Td> <Td>{slug}</Td>
@@ -126,7 +126,7 @@ export const OrgAdminProjects = withPermission(
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
handleAccessProject(defaultType, id); handleAccessProject(defaultProduct, id);
}} }}
icon={<FontAwesomeIcon icon={faSignIn} />} icon={<FontAwesomeIcon icon={faSignIn} />}
disabled={ disabled={
@@ -418,10 +418,10 @@ export const LogsFilter = ({
onChange(e); onChange(e);
}} }}
placeholder="All projects" placeholder="All projects"
options={workspacesInOrg.map(({ name, id, defaultType }) => ({ options={workspacesInOrg.map(({ name, id, defaultProduct }) => ({
name, name,
id, id,
type: defaultType type: defaultProduct
}))} }))}
getOptionValue={(option) => option.id} getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name} getOptionLabel={(option) => option.name}
@@ -222,7 +222,7 @@ export const AllProjectView = ({
onKeyDown={(evt) => { onKeyDown={(evt) => {
if (evt.key === "Enter" && workspace.isMember) { if (evt.key === "Enter" && workspace.isMember) {
navigate({ navigate({
to: getProjectHomePage(workspace.defaultType), to: getProjectHomePage(workspace.defaultProduct),
params: { params: {
projectId: workspace.id projectId: workspace.id
} }
@@ -232,7 +232,7 @@ export const AllProjectView = ({
onClick={() => { onClick={() => {
if (workspace.isMember) { if (workspace.isMember) {
navigate({ navigate({
to: getProjectHomePage(workspace.defaultType), to: getProjectHomePage(workspace.defaultProduct),
params: { params: {
projectId: workspace.id projectId: workspace.id
} }
@@ -153,7 +153,7 @@ export const MyProjectView = ({
<div <div
onClick={() => { onClick={() => {
navigate({ navigate({
to: getProjectHomePage(workspace.defaultType), to: getProjectHomePage(workspace.defaultProduct),
params: { params: {
projectId: workspace.id projectId: workspace.id
} }
@@ -215,7 +215,7 @@ export const MyProjectView = ({
<div <div
onClick={() => { onClick={() => {
navigate({ navigate({
to: getProjectHomePage(workspace.defaultType), to: getProjectHomePage(workspace.defaultProduct),
params: { params: {
projectId: workspace.id projectId: workspace.id
} }