Skip to content

Commit

Permalink
Add Confimration Modal and Service and update form blocking to use modal
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundito committed Apr 11, 2022
1 parent a2db11c commit c8e866f
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 66 deletions.
5 changes: 4 additions & 1 deletion airbyte-webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FeatureService } from "hooks/services/Feature";
import { ServicesProvider } from "core/servicesProvider";
import { ApiServices } from "core/ApiServices";
import { StoreProvider } from "views/common/StoreProvider";
import ConfirmationModalServiceProvider from "hooks/services/ConfirmationModal";

import en from "./locales/en.json";
import GlobalStyle from "./global-styles";
Expand Down Expand Up @@ -53,7 +54,9 @@ const Services: React.FC = ({ children }) => (
<WorkspaceServiceProvider>
<FeatureService>
<NotificationService>
<ApiServices>{children}</ApiServices>
<ConfirmationModalServiceProvider>
<ApiServices>{children}</ApiServices>
</ConfirmationModalServiceProvider>
</NotificationService>
</FeatureService>
</WorkspaceServiceProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react";
import styled from "styled-components";
import { FormattedMessage } from "react-intl";

import Modal from "components/Modal";
import { Button } from "components/base/Button";

const Content = styled.div`
width: 585px;
font-size: 14px;
line-height: 28px;
padding: 25px;
white-space: pre-line;
`;

const ButtonContent = styled.div`
padding-top: 28px;
display: flex;
justify-content: flex-end;
`;

const ButtonWithMargin = styled(Button)`
margin-right: 12px;
`;

interface Props {
onClose: () => void;
title: React.ReactNode;
text: React.ReactNode;
submitButtonText: React.ReactNode;
onSubmit: () => void;
}

const ConfirmationModal: React.FC<Props> = ({ onClose, title, text, onSubmit, submitButtonText }) => (
<Modal onClose={onClose} title={title}>
<Content>
{text}
<ButtonContent>
<ButtonWithMargin onClick={onClose} type="button" secondary>
<FormattedMessage id="form.cancel" />
</ButtonWithMargin>
<Button type="button" danger onClick={onSubmit} data-id="delete">
{submitButtonText}
</Button>
</ButtonContent>
</Content>
</Modal>
);

export default ConfirmationModal;
4 changes: 4 additions & 0 deletions airbyte-webapp/src/components/ConfirmationModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import ConfirmationModal from "./ConfirmationModal";

export default ConfirmationModal;
export { ConfirmationModal };
35 changes: 0 additions & 35 deletions airbyte-webapp/src/hooks/router/usePrompt.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useContext, useEffect, useMemo } from "react";

import ConfirmationModalComponent from "components/ConfirmationModal";

import useTypesafeReducer from "hooks/useTypesafeReducer";

import { ConfirmationModal, ConfirmationModalServiceApi, ConfirmationModalState } from "./types";
import { actions, initialState, confirmationModalServiceReducer } from "./reducer";

const confirmationModalServiceContext = React.createContext<ConfirmationModalServiceApi | undefined>(undefined);

export const useConfirmationModalService: (
confirmationModal?: ConfirmationModal,
dependencies?: []
) => {
openConfirmationModal: (confirmationModal: ConfirmationModal) => void;
closeConfirmationModal: () => void;
} = (confirmationModal, dependencies) => {
const confirmationModalService = useContext(confirmationModalServiceContext);
if (!confirmationModalService) {
throw new Error("useConfirmationModalService must be used within a ConfirmationModalService.");
}

useEffect(() => {
if (confirmationModal) {
confirmationModalService.openConfirmationModal(confirmationModal);
}
return () => {
if (confirmationModal) {
confirmationModalService.closeConfirmationModal();
}
};
// eslint-disable-next-line
}, [confirmationModal, confirmationModalService, ...(dependencies ?? [])]);

return {
openConfirmationModal: confirmationModalService.openConfirmationModal,
closeConfirmationModal: confirmationModalService.closeConfirmationModal,
};
};

const ConfirmationModalService = ({ children }: { children: React.ReactNode }) => {
const [state, { openConfirmationModal, closeConfirmationModal }] = useTypesafeReducer<
ConfirmationModalState,
typeof actions
>(confirmationModalServiceReducer, initialState, actions);

const confirmationModalService: ConfirmationModalServiceApi = useMemo(
() => ({
openConfirmationModal,
closeConfirmationModal,
}),
[closeConfirmationModal, openConfirmationModal]
);

return (
<>
<confirmationModalServiceContext.Provider value={confirmationModalService}>
{children}
</confirmationModalServiceContext.Provider>
{state.isOpen && state.confirmationModal ? (
<ConfirmationModalComponent
onClose={closeConfirmationModal}
title={state.confirmationModal.title}
text={state.confirmationModal.text}
onSubmit={state.confirmationModal.onSubmit}
submitButtonText={state.confirmationModal.submitButtonText}
/>
) : null}
</>
);
};

export default React.memo(ConfirmationModalService);
4 changes: 4 additions & 0 deletions airbyte-webapp/src/hooks/services/ConfirmationModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import ConfirmationModalService from "./ConfirmationModalService";

export default ConfirmationModalService;
export { ConfirmationModalService };
31 changes: 31 additions & 0 deletions airbyte-webapp/src/hooks/services/ConfirmationModal/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ActionType, createAction, createReducer } from "typesafe-actions";

import { ConfirmationModal, ConfirmationModalState } from "./types";

export const actions = {
openConfirmationModal: createAction("OPEN_CONFIRMATION_MODAL")<ConfirmationModal>(),
closeConfirmationModal: createAction("CLOSE_CONFIRMATION_MODAL")(),
};

type Actions = ActionType<typeof actions>;

export const initialState: ConfirmationModalState = {
isOpen: false,
confirmationModal: null,
};

export const confirmationModalServiceReducer = createReducer<ConfirmationModalState, Actions>(initialState)
.handleAction(actions.openConfirmationModal, (state, action): ConfirmationModalState => {
return {
...state,
isOpen: true,
confirmationModal: action.payload,
};
})
.handleAction(actions.closeConfirmationModal, (state): ConfirmationModalState => {
return {
...state,
isOpen: false,
confirmationModal: null,
};
});
18 changes: 18 additions & 0 deletions airbyte-webapp/src/hooks/services/ConfirmationModal/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";

export interface ConfirmationModal {
submitButtonText: React.ReactNode;
title: React.ReactNode;
text: React.ReactNode;
onSubmit: () => Promise<void>;
}

export interface ConfirmationModalServiceApi {
openConfirmationModal: (confirmationModal: ConfirmationModal) => void;
closeConfirmationModal: () => void;
}

export interface ConfirmationModalState {
isOpen: boolean;
confirmationModal: ConfirmationModal | null;
}
23 changes: 0 additions & 23 deletions airbyte-webapp/src/hooks/useFormNavigationBlocking.ts

This file was deleted.

42 changes: 42 additions & 0 deletions airbyte-webapp/src/hooks/useFormNavigationBlocking.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Transition } from "history";

import { useCallback, useMemo } from "react";
import { createGlobalState } from "react-use";

import { useBlocker } from "./router/useBlocker";
import { useConfirmationModalService } from "./services/ConfirmationModal/ConfirmationModalService";
import { ConfirmationModal } from "./services/ConfirmationModal/types";

export const useBlockingFormsById = createGlobalState<Record<string, boolean>>({});

const useFormNavigationBlocking = () => {
const [blockingFormsById, setBlockingFormsById] = useBlockingFormsById();
const { openConfirmationModal, closeConfirmationModal } = useConfirmationModalService();

const blocker = useCallback(
(tx: Transition) => {
const modalData: ConfirmationModal = {
title: "Discard changes",
text: "There are unsaved changes. Are you sure you want to discard your changes?",
submitButtonText: "Discard changes",
onSubmit: async () => {
setBlockingFormsById({});
closeConfirmationModal();
tx.retry();
},
};

openConfirmationModal(modalData);
},
[closeConfirmationModal, openConfirmationModal, setBlockingFormsById]
);

const isFormBlocking = useMemo(
() => Object.values(blockingFormsById ?? {}).reduce((acc, value) => acc || value, false),
[blockingFormsById]
);

useBlocker(blocker, isFormBlocking);
};

export default useFormNavigationBlocking;
17 changes: 10 additions & 7 deletions airbyte-webapp/src/packages/cloud/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AnalyticsProvider } from "views/common/AnalyticsProvider";
import { FeatureService } from "hooks/services/Feature";
import { AuthenticationProvider } from "packages/cloud/services/auth/AuthService";
import { StoreProvider } from "views/common/StoreProvider";
import ConfirmationModalServiceProvider from "hooks/services/ConfirmationModal";

import { AppServicesProvider } from "./services/AppServicesProvider";
import { IntercomProvider } from "./services/thirdParty/intercom/IntercomProvider";
Expand Down Expand Up @@ -46,13 +47,15 @@ const Services: React.FC = ({ children }) => (
<AnalyticsProvider>
<ApiErrorBoundary>
<NotificationServiceProvider>
<FeatureService>
<AppServicesProvider>
<AuthenticationProvider>
<IntercomProvider>{children}</IntercomProvider>
</AuthenticationProvider>
</AppServicesProvider>
</FeatureService>
<ConfirmationModalServiceProvider>
<FeatureService>
<AppServicesProvider>
<AuthenticationProvider>
<IntercomProvider>{children}</IntercomProvider>
</AuthenticationProvider>
</AppServicesProvider>
</FeatureService>
</ConfirmationModalServiceProvider>
</NotificationServiceProvider>
</ApiErrorBoundary>
</AnalyticsProvider>
Expand Down

0 comments on commit c8e866f

Please sign in to comment.