diff --git a/app/client/src/pages/AppViewer/PageMenu.tsx b/app/client/src/pages/AppViewer/PageMenu.tsx
index 5365e9f58e..a51812ec32 100644
--- a/app/client/src/pages/AppViewer/PageMenu.tsx
+++ b/app/client/src/pages/AppViewer/PageMenu.tsx
@@ -140,7 +140,9 @@ export function PageMenu(props: AppViewerHeaderProps) {
workspaceId={workspaceID}
/>
)}
-
+ {isOpen && (
+
+ )}
{!hideWatermark && (
({
+ ...jest.requireActual("react-router"),
+ useLocation: () => ({
+ pathname: "/app/test-3/page1-63cccd44463c535b9fbc297c",
+ search: "?fork=true",
+ }),
+}));
+
+const mockDispatch = jest.fn();
+jest.mock("react-redux", () => ({
+ ...jest.requireActual("react-redux"),
+ useDispatch: () => mockDispatch,
+}));
+
+export const initialState: any = {
+ entities: {
+ pageList: {
+ applicationId: 1,
+ currentPageId: 1,
+ pages: [
+ {
+ pageId: 1,
+ slug: "pageSlug",
+ },
+ ],
+ },
+ datasources: {
+ list: [],
+ },
+ actions: [],
+ canvasWidgets: {
+ main_component: {},
+ },
+ },
+ ui: {
+ appTheming: {
+ selectedTheme: {
+ properties: {
+ colors: {
+ primaryColor: "",
+ },
+ borderRadius: {
+ appBorderRadius: "",
+ },
+ },
+ },
+ },
+ applications: {
+ currentApplication: {
+ id: "605c435a91dea93f0eaf91b8",
+ name: "My Application",
+ slug: "my-application",
+ workspaceId: "",
+ evaluationVersion: 1,
+ appIsExample: false,
+ gitApplicationMetadata: undefined,
+ applicationVersion: 2,
+ forkingEnabled: true,
+ isPublic: true,
+ },
+ userWorkspaces: [],
+ },
+ theme: {
+ theme: {
+ colors: {
+ applications: {
+ iconColor: "#f2f2f2",
+ },
+ success: {
+ main: "#e2e2e2",
+ },
+ },
+ },
+ },
+ users: {
+ currentUser: undefined,
+ },
+ },
+};
+const state = JSON.parse(JSON.stringify(initialState));
+const mockStore = configureStore();
+
+export function getStore(action?: string) {
+ switch (action) {
+ case "SET_CURRENT_USER_DETAILS":
+ state.ui.users.currentUser = {
+ username: "test",
+ };
+ break;
+ }
+ return mockStore(state);
+}
+
+export const fetchApplicationMockResponse = {
+ responseMeta: {
+ status: 200,
+ success: true,
+ },
+ data: {
+ application: {
+ id: "605c435a91dea93f0eaf91b8",
+ name: "My Application",
+ slug: "my-application",
+ workspaceId: "",
+ evaluationVersion: 1,
+ appIsExample: false,
+ gitApplicationMetadata: undefined,
+ applicationVersion: 2,
+ forkingEnabled: true,
+ isPublic: true,
+ },
+ pages: [
+ {
+ id: "605c435a91dea93f0eaf91ba",
+ name: "Page1",
+ isDefault: true,
+ slug: "page-1",
+ },
+ {
+ id: "605c435a91dea93f0eaf91bc",
+ name: "Page2",
+ isDefault: false,
+ slug: "page-2",
+ },
+ ],
+ workspaceId: "",
+ },
+};
+
+describe("App viewer fork button", () => {
+ afterEach(cleanup);
+
+ it("Fork modal trigger should not be displayed until user details are fetched", () => {
+ render(
+
+
+
+
+ ,
+ );
+ expect(screen.queryAllByTestId("fork-modal-trigger").length).toEqual(0);
+ expect(mockDispatch).toHaveBeenCalledTimes(0);
+ mockDispatch.mockClear();
+ });
+ it("Fork modal trigger should be displayed when user details are defined and user is not anonymous", () => {
+ render(
+
+
+
+
+ ,
+ );
+ expect(screen.queryAllByTestId("fork-modal-trigger").length).toEqual(1);
+ expect(mockDispatch).toHaveBeenCalledTimes(1);
+ mockDispatch.mockClear();
+ });
+});
diff --git a/app/client/src/pages/AppViewer/PrimaryCTA.tsx b/app/client/src/pages/AppViewer/PrimaryCTA.tsx
index 996e3019a0..cc43603b2b 100644
--- a/app/client/src/pages/AppViewer/PrimaryCTA.tsx
+++ b/app/client/src/pages/AppViewer/PrimaryCTA.tsx
@@ -1,5 +1,5 @@
import React, { useMemo } from "react";
-import { useDispatch, useSelector } from "react-redux";
+import { useSelector } from "react-redux";
import Button from "./AppViewerButton";
import { AUTH_LOGIN_URL } from "constants/routes";
@@ -21,7 +21,6 @@ import {
import { getCurrentUser } from "selectors/usersSelectors";
import { ANONYMOUS_USERNAME } from "constants/userConstants";
import ForkApplicationModal from "pages/Applications/ForkApplicationModal";
-import { getAllApplications } from "actions/applicationActions";
import { viewerURL } from "RouteBuilder";
import { useHistory } from "react-router";
import { useHref } from "pages/Editor/utils";
@@ -44,7 +43,6 @@ type Props = {
function PrimaryCTA(props: Props) {
const { className, url } = props;
- const dispatch = useDispatch();
const currentUser = useSelector(getCurrentUser);
const currentPageID = useSelector(getCurrentPageId);
const selectedTheme = useSelector(getSelectedAppTheme);
@@ -56,7 +54,9 @@ function PrimaryCTA(props: Props) {
const appViewerURL = useHref(viewerURL, {
pageId: currentPageID,
- suffix: "fork",
+ params: {
+ fork: "true",
+ },
});
// get the fork url
@@ -94,6 +94,7 @@ function PrimaryCTA(props: Props) {
);
}
+ if (!currentUser) return;
if (
currentApplication?.forkingEnabled &&
currentApplication?.isPublic &&
@@ -104,7 +105,7 @@ function PrimaryCTA(props: Props) {
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
buttonColor={selectedTheme.properties.colors.primaryColor}
buttonVariant="PRIMARY"
- className="t--fork-app"
+ className={`t--fork-app w-full md:w-auto ${className}`}
icon="fork"
onClick={() => {
history.push(forkURL);
@@ -126,9 +127,9 @@ function PrimaryCTA(props: Props) {
}
buttonColor={selectedTheme.properties.colors.primaryColor}
buttonVariant="PRIMARY"
- className="t--fork-app"
+ className={`t--fork-app w-full md:w-auto ${className}`}
+ data-testid="fork-modal-trigger"
icon="fork"
- onClick={() => dispatch(getAllApplications())}
text={createMessage(FORK_APP)}
/>
}
@@ -157,6 +158,8 @@ function PrimaryCTA(props: Props) {
}, [
url,
canEdit,
+ forkURL,
+ currentUser?.username,
selectedTheme.properties.colors.primaryColor,
selectedTheme.properties.borderRadius.appBorderRadius,
]);
diff --git a/app/client/src/pages/Applications/ForkApplicationModal.tsx b/app/client/src/pages/Applications/ForkApplicationModal.tsx
index b20647ca98..698312907c 100644
--- a/app/client/src/pages/Applications/ForkApplicationModal.tsx
+++ b/app/client/src/pages/Applications/ForkApplicationModal.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useMemo, useEffect } from "react";
+import React, { useState, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getUserApplicationsWorkspaces } from "selectors/applicationSelectors";
import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers";
@@ -15,7 +15,6 @@ import {
import { StyledDialog, ButtonWrapper, SpinnerWrapper } from "./ForkModalStyles";
import { getIsFetchingApplications } from "selectors/applicationSelectors";
import { useLocation } from "react-router";
-import { matchViewerForkPath } from "constants/routes";
import { Colors } from "constants/Colors";
import {
CANCEL,
@@ -26,6 +25,7 @@ import {
FORK_APP_MODAL_SUCCESS_TITLE,
} from "@appsmith/constants/messages";
import { getAllApplications } from "actions/applicationActions";
+import history from "utils/history";
type ForkApplicationModalProps = {
applicationId: string;
@@ -48,16 +48,9 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
(state: AppState) => state.ui.applications.forkingApplication,
);
- useEffect(() => {
- if (!userWorkspaces.length) {
- dispatch(getAllApplications());
- }
- }, [userWorkspaces.length]);
-
const isFetchingApplications = useSelector(getIsFetchingApplications);
- const { pathname } = useLocation();
-
- const showBasedOnURL = matchViewerForkPath(pathname);
+ const location = useLocation();
+ const queryParams = new URLSearchParams(location.search);
const forkApplication = () => {
dispatch({
@@ -98,12 +91,35 @@ function ForkApplicationModal(props: ForkApplicationModalProps) {
? createMessage(FORK_APP_MODAL_EMPTY_TITLE)
: createMessage(FORK_APP_MODAL_SUCCESS_TITLE);
+ const handleClose = () => {
+ if (!props.setModalClose) {
+ const url = new URL(window.location.href);
+ if (url.searchParams.has("fork")) {
+ url.searchParams.delete("fork");
+ history.push(url.toString().slice(url.origin.length));
+ }
+ }
+ };
+
+ const handleOpen = () => {
+ if (!props.setModalClose) {
+ const url = new URL(window.location.href);
+ if (!url.searchParams.has("fork")) {
+ url.searchParams.append("fork", "true");
+ history.push(url.toString().slice(url.origin.length));
+ }
+ dispatch(getAllApplications());
+ }
+ };
+
return (
setModalClose && setModalClose(false)}
+ onClick={() => {
+ setModalClose && setModalClose(false);
+ handleClose();
+ }}
size={Size.large}
tag="button"
text={createMessage(CANCEL)}