diff --git a/ui/src/features/project/settings/components/delete-project-modal.tsx b/ui/src/features/project/settings/components/delete-project-modal.tsx
new file mode 100644
index 000000000..98fe9d278
--- /dev/null
+++ b/ui/src/features/project/settings/components/delete-project-modal.tsx
@@ -0,0 +1,70 @@
+import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query';
+import { useQueryClient } from '@tanstack/react-query';
+import { Alert, Form, Input, Modal } from 'antd';
+import React from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import yaml from 'yaml';
+
+import { paths } from '@ui/config/paths';
+import { ModalComponentProps } from '@ui/features/common/modal/modal-context';
+import {
+ deleteResource,
+ listProjects
+} from '@ui/gen/service/v1alpha1/service-KargoService_connectquery';
+
+import { projectYAMLExample } from '../../list/utils/project-yaml-example';
+
+export const DeleteProjectModal = ({ visible, hide }: ModalComponentProps) => {
+ const { name } = useParams();
+ const queryClient = useQueryClient();
+ const navigate = useNavigate();
+ const [inputValue, setInputValue] = React.useState('');
+
+ const { mutate, isPending } = useMutation(deleteResource, {
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: createConnectQueryKey(listProjects) });
+ navigate(paths.projects);
+ }
+ });
+
+ const onDelete = () => {
+ const textEncoder = new TextEncoder();
+ const manifest = {
+ ...projectYAMLExample,
+ metadata: {
+ name
+ }
+ };
+
+ mutate({ manifest: textEncoder.encode(yaml.stringify(manifest)) });
+ };
+
+ return (
+
+
+
+ setInputValue(e.target.value)}
+ value={inputValue}
+ placeholder={name}
+ />
+
+
+
+ );
+};
diff --git a/ui/src/features/project/settings/components/edit-project-modal.tsx b/ui/src/features/project/settings/components/edit-project-modal.tsx
new file mode 100644
index 000000000..b2cde6125
--- /dev/null
+++ b/ui/src/features/project/settings/components/edit-project-modal.tsx
@@ -0,0 +1,77 @@
+import { useMutation, useQuery } from '@connectrpc/connect-query';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Modal } from 'antd';
+import type { JSONSchema4 } from 'json-schema';
+import { useForm } from 'react-hook-form';
+import { useParams } from 'react-router-dom';
+import yaml from 'yaml';
+import { z } from 'zod';
+
+import { YamlEditor } from '@ui/features/common/code-editor/yaml-editor';
+import { FieldContainer } from '@ui/features/common/form/field-container';
+import { ModalComponentProps } from '@ui/features/common/modal/modal-context';
+import schema from '@ui/gen/schema/projects.kargo.akuity.io_v1alpha1.json';
+import {
+ getProject,
+ updateResource
+} from '@ui/gen/service/v1alpha1/service-KargoService_connectquery';
+import { RawFormat } from '@ui/gen/service/v1alpha1/service_pb';
+import { decodeRawData } from '@ui/utils/decode-raw-data';
+import { zodValidators } from '@ui/utils/validators';
+
+import { projectYAMLExample } from '../../list/utils/project-yaml-example';
+
+const formSchema = z.object({
+ value: zodValidators.requiredString
+});
+
+export const EditProjectModal = ({ visible, hide }: ModalComponentProps) => {
+ const { name } = useParams();
+ const { data, isLoading } = useQuery(getProject, { name, format: RawFormat.YAML });
+
+ const { mutateAsync, isPending } = useMutation(updateResource, {
+ onSuccess: () => hide()
+ });
+
+ const { control, handleSubmit } = useForm({
+ values: {
+ value: decodeRawData(data)
+ },
+ resolver: zodResolver(formSchema)
+ });
+
+ const onSubmit = handleSubmit(async (data) => {
+ const textEncoder = new TextEncoder();
+ await mutateAsync({
+ manifest: textEncoder.encode(data.value)
+ });
+ });
+
+ return (
+
+
+ {({ field: { value, onChange } }) => (
+ onChange(e || '')}
+ height='500px'
+ schema={schema as JSONSchema4}
+ placeholder={yaml.stringify(projectYAMLExample)}
+ isLoading={isLoading}
+ isHideManagedFieldsDisplayed
+ />
+ )}
+
+
+ );
+};
diff --git a/ui/src/features/project/settings/project-settings.tsx b/ui/src/features/project/settings/project-settings.tsx
new file mode 100644
index 000000000..c666a57bb
--- /dev/null
+++ b/ui/src/features/project/settings/project-settings.tsx
@@ -0,0 +1,50 @@
+import { faChevronDown, faCog, faPencil, faTrash } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, Dropdown, Space } from 'antd';
+
+import { useModal } from '@ui/features/common/modal/use-modal';
+
+import { DeleteProjectModal } from './components/delete-project-modal';
+import { EditProjectModal } from './components/edit-project-modal';
+
+export const ProjectSettings = () => {
+ const { show: showEditModal } = useModal(EditProjectModal);
+ const { show: showDeleteModal } = useModal(DeleteProjectModal);
+
+ return (
+
+ Edit
+ >
+ ),
+ onClick: () => showEditModal()
+ },
+ {
+ key: '2',
+ danger: true,
+ label: (
+ <>
+ Delete
+ >
+ ),
+ onClick: () => showDeleteModal()
+ }
+ ]
+ }}
+ placement='bottomRight'
+ trigger={['click']}
+ >
+ }>
+
+ Project Settings
+
+
+
+
+ );
+};
diff --git a/ui/src/pages/project.tsx b/ui/src/pages/project.tsx
index 66acb58ac..0c10a86a3 100644
--- a/ui/src/pages/project.tsx
+++ b/ui/src/pages/project.tsx
@@ -13,6 +13,7 @@ import { AnalysisTemplatesList } from '@ui/features/project/analysis-templates/a
import { CredentialsList } from '@ui/features/project/credentials/credentials-list';
import { Events } from '@ui/features/project/events/events';
import { Pipelines } from '@ui/features/project/pipelines/pipelines';
+import { ProjectSettings } from '@ui/features/project/settings/project-settings';
const tabs = {
pipelines: {
@@ -67,6 +68,7 @@ export const Project = ({ tab = 'pipelines' }: { tab?: ProjectTab }) => {
PROJECT
{name}
+