diff --git a/app/client/src/git/components/DisconnectModal/index.test.tsx b/app/client/src/git/components/DisconnectModal/index.test.tsx
new file mode 100644
index 000000000000..70f6fb14ea26
--- /dev/null
+++ b/app/client/src/git/components/DisconnectModal/index.test.tsx
@@ -0,0 +1,165 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import AnalyticsUtil from "ee/utils/AnalyticsUtil";
+import DisconnectModal from ".";
+
+jest.mock("ee/utils/AnalyticsUtil", () => ({
+ logEvent: jest.fn(),
+}));
+
+describe("DisconnectModal", () => {
+ const defaultProps = {
+ isModalOpen: true,
+ disconnectingApp: {
+ id: "app123",
+ name: "TestApp",
+ },
+ closeModal: jest.fn(),
+ onBackClick: jest.fn(),
+ onDisconnect: jest.fn(),
+ };
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("should render the modal when isModalOpen is true", () => {
+ render();
+ expect(screen.getByTestId("t--disconnect-git-modal")).toBeInTheDocument();
+ });
+
+ it("should not render the modal when isModalOpen is false", () => {
+ render();
+ expect(
+ screen.queryByTestId("t--disconnect-git-modal"),
+ ).not.toBeInTheDocument();
+ });
+
+ it("should display the correct modal header", () => {
+ render();
+ expect(screen.getByText("Revoke access to TestApp")).toBeInTheDocument();
+ });
+
+ it("should display the correct instruction text", () => {
+ render();
+ expect(
+ screen.getByText("Type “TestApp” in the input box to revoke access."),
+ ).toBeInTheDocument();
+ });
+
+ it("should update appName state when input changes", () => {
+ render();
+ const input = screen.getByLabelText("Application name");
+
+ fireEvent.change(input, { target: { value: "TestApp" } });
+ expect(input).toHaveValue("TestApp");
+ });
+
+ it("should enable Revoke button when appName matches disconnectingApp.name", () => {
+ render();
+ const input = screen.getByLabelText("Application name");
+ const revokeButton = document.getElementsByClassName(
+ "t--git-revoke-button",
+ )[0];
+
+ expect(revokeButton).toBeDisabled();
+
+ fireEvent.change(input, { target: { value: "TestApp" } });
+ expect(revokeButton).toBeEnabled();
+ });
+
+ it("should disable Revoke button when appName does not match disconnectingApp.name", () => {
+ render();
+ const input = screen.getByLabelText("Application name");
+ const revokeButton = document.getElementsByClassName(
+ "t--git-revoke-button",
+ )[0];
+
+ fireEvent.change(input, { target: { value: "WrongAppName" } });
+ expect(revokeButton).toBeDisabled();
+ });
+
+ it("should call onBackClick when Go Back button is clicked", () => {
+ render();
+ const goBackButton = document.getElementsByClassName(
+ "t--git-revoke-back-button",
+ )[0];
+
+ fireEvent.click(goBackButton);
+ expect(defaultProps.onBackClick).toHaveBeenCalledTimes(1);
+ });
+
+ it("should call onDisconnect when Revoke button is clicked", () => {
+ render();
+ const input = screen.getByLabelText("Application name");
+ const revokeButton = document.getElementsByClassName(
+ "t--git-revoke-button",
+ )[0];
+
+ fireEvent.change(input, { target: { value: "TestApp" } });
+ fireEvent.click(revokeButton);
+
+ expect(defaultProps.onDisconnect).toHaveBeenCalledTimes(1);
+ });
+
+ it("should disable Revoke button when isRevoking is true", () => {
+ const { rerender } = render();
+ const input = screen.getByLabelText("Application name");
+ const revokeButton = document.getElementsByClassName(
+ "t--git-revoke-button",
+ )[0];
+
+ fireEvent.change(input, { target: { value: "TestApp" } });
+ expect(revokeButton).toBeEnabled();
+
+ fireEvent.click(revokeButton);
+ // Rerender to reflect state change
+ rerender();
+
+ expect(defaultProps.onDisconnect).toHaveBeenCalledTimes(1);
+ expect(revokeButton).toBeDisabled();
+ });
+
+ it("should log analytics event on input blur", () => {
+ render();
+ const input = screen.getByLabelText("Application name");
+
+ fireEvent.change(input, { target: { value: "SomeValue" } });
+ fireEvent.blur(input);
+
+ expect(AnalyticsUtil.logEvent).toHaveBeenCalledWith(
+ "GS_MATCHING_REPO_NAME_ON_GIT_DISCONNECT_MODAL",
+ {
+ value: "SomeValue",
+ expecting: "TestApp",
+ },
+ );
+ });
+
+ it("should display callout with non-reversible message and learn more link", () => {
+ render();
+ expect(
+ screen.getByText(
+ "This action is non-reversible. Please proceed with caution.",
+ ),
+ ).toBeInTheDocument();
+ const learnMoreLink = screen.getByText("Learn more").parentElement;
+
+ expect(learnMoreLink).toBeInTheDocument();
+ expect(learnMoreLink).toHaveAttribute(
+ "href",
+ "https://docs.appsmith.com/advanced-concepts/version-control-with-git/disconnect-the-git-repository",
+ );
+ });
+
+ it("should not call onDisconnect when Revoke button is clicked and appName does not match", () => {
+ render();
+ const revokeButton = document.getElementsByClassName(
+ "t--git-revoke-button",
+ )[0];
+
+ fireEvent.click(revokeButton);
+ expect(defaultProps.onDisconnect).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/client/src/git/components/DisconnectModal/index.tsx b/app/client/src/git/components/DisconnectModal/index.tsx
new file mode 100644
index 000000000000..b18750a38422
--- /dev/null
+++ b/app/client/src/git/components/DisconnectModal/index.tsx
@@ -0,0 +1,146 @@
+import React, { useCallback, useState } from "react";
+import {
+ Button,
+ Callout,
+ Flex,
+ Input,
+ Modal,
+ ModalBody,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ Text,
+} from "@appsmith/ads";
+import {
+ APPLICATION_NAME,
+ createMessage,
+ GIT_REVOKE_ACCESS,
+ GIT_TYPE_REPO_NAME_FOR_REVOKING_ACCESS,
+ GO_BACK,
+ NONE_REVERSIBLE_MESSAGE,
+ REVOKE,
+} from "ee/constants/messages";
+import AnalyticsUtil from "ee/utils/AnalyticsUtil";
+import styled from "styled-components";
+
+const DOCS_URL =
+ "https://docs.appsmith.com/advanced-concepts/version-control-with-git/disconnect-the-git-repository";
+const DOCS_LINK_PROPS = [
+ {
+ children: "Learn more",
+ to: DOCS_URL,
+ className: "t--disconnect-learn-more",
+ },
+];
+const MODAL_WIDTH = 640;
+
+interface DisconnectModalProps {
+ isModalOpen: boolean;
+ disconnectingApp: {
+ id: string;
+ name: string;
+ };
+ closeModal: () => void;
+ onBackClick: () => void;
+ onDisconnect: () => void;
+}
+
+const StyledModalContent = styled(ModalContent)`
+ width: ${MODAL_WIDTH}px;
+`;
+
+function DisconnectModal({
+ closeModal,
+ disconnectingApp,
+ isModalOpen,
+ onBackClick,
+ onDisconnect,
+}: DisconnectModalProps) {
+ const [appName, setAppName] = useState("");
+ const [isRevoking, setIsRevoking] = useState(false);
+
+ const onDisconnectGit = useCallback(() => {
+ setIsRevoking(true);
+ onDisconnect();
+ }, [onDisconnect]);
+
+ const shouldDisableRevokeButton =
+ disconnectingApp.id === "" ||
+ appName !== disconnectingApp.name ||
+ isRevoking;
+
+ const onModalOpenValueChange = useCallback(
+ (open: boolean) => {
+ if (!open) {
+ closeModal();
+ }
+ },
+ [closeModal],
+ );
+
+ const inputOnBlur = useCallback(
+ (event: React.FocusEvent) => {
+ AnalyticsUtil.logEvent("GS_MATCHING_REPO_NAME_ON_GIT_DISCONNECT_MODAL", {
+ value: "value" in event.target ? event.target.value : "",
+ expecting: disconnectingApp.name,
+ });
+ },
+ [disconnectingApp.name],
+ );
+
+ const inputOnChange = useCallback((value: string) => {
+ setAppName(value);
+ }, []);
+
+ return (
+
+
+
+ {createMessage(GIT_REVOKE_ACCESS, disconnectingApp.name)}
+
+
+
+
+ {createMessage(
+ GIT_TYPE_REPO_NAME_FOR_REVOKING_ACCESS,
+ disconnectingApp.name,
+ )}
+
+
+
+ {createMessage(NONE_REVERSIBLE_MESSAGE)}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default DisconnectModal;