Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: demo config file flow with automatic login #336

Merged
merged 4 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
"shelljs": "^0.8.5",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"tailwindcss": "^3.0.23",
"tailwindcss": "^3.4.3",
"testcafe": "^2.3.0",
"tmp": "^0.2.1",
"ts-node": "^10.4.0",
Expand Down
8 changes: 6 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import { history } from "./history";

export const App: React.FunctionComponent = () => {
const { configFile } = usePersistedConfigFile();
const { setConfig, config } = useConfigContext();
const { setConfig, config, setIsDemo, isDemo } = useConfigContext();
const { setForms, setActiveFormIndex } = useFormsContext();

const { setConfigFile } = usePersistedConfigFile();
const logout = (): void => {
setForms([]);
setActiveFormIndex(undefined);
setConfig(undefined);
if (isDemo) {
setIsDemo(false);
setConfigFile(undefined);
}
};

return (
Expand Down
8 changes: 7 additions & 1 deletion src/common/context/config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,29 @@ import { Config } from "../../../types";
interface ConfigContext {
config?: Config;
setConfig: (config?: Config) => void;
isDemo: boolean;
setIsDemo: (isDemo: boolean) => void;
}

export const ConfigContext = createContext<ConfigContext>({
config: undefined,
setConfig: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
isDemo: false,
setIsDemo: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
});

export const useConfigContext = (): ConfigContext => useContext<ConfigContext>(ConfigContext);

export const ConfigContextProvider: FunctionComponent = ({ children }) => {
const [config, setConfig] = useState<Config>();

const [isDemo, setIsDemo] = useState<boolean>(false);
return (
<ConfigContext.Provider
value={{
config,
setConfig,
isDemo,
setIsDemo,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function */
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import React from "react";
import { ConfigFileDropZone } from "./ConfigFileDropZone";
import { createFileTransferEvent } from "../../../utils/utils";
import { useConfigContext } from "../../../common/context/config";
import { DEMO_CONFIG } from "../../../constants/demo-config";
jest.mock("../../../common/context/config");

describe("configFileDropZone", () => {
beforeEach(() => {
const setIsDemo = jest.fn();
const useConfigContextMock = useConfigContext as jest.Mock;
useConfigContextMock.mockResolvedValue({ setIsDemo });
});
it("should have the right text", () => {
render(<ConfigFileDropZone onConfigFile={() => {}} />);
expect(screen.queryByText(/Create and Revoke Document/)).not.toBeNull();
Expand All @@ -26,4 +33,26 @@ describe("configFileDropZone", () => {
await waitFor(() => expect(onConfigFile).toHaveBeenCalledWith(configContent));
});
});

it("should call onConfigFile method with DEMO_CONFIG and load config file button is clicked", async () => {
const setIsDemo = jest.fn();
const onConfigFile = jest.fn().mockResolvedValue(true);
const useConfigContextMock = useConfigContext as jest.Mock;
useConfigContextMock.mockResolvedValue({ setIsDemo });
// Mock the context to return the setIsDemo function
(useConfigContext as jest.Mock).mockReturnValue({
setIsDemo,
});

render(<ConfigFileDropZone onConfigFile={onConfigFile} errorMessage="" />);

// Find the load-demo-config-button
const loadDemoConfigButton = screen.getByTestId("load-demo-config-button");

// Click the load-demo-config-button
fireEvent.click(loadDemoConfigButton);

expect(setIsDemo).toHaveBeenCalledWith(true);
expect(onConfigFile).toHaveBeenCalledWith(DEMO_CONFIG);
});
});
55 changes: 38 additions & 17 deletions src/components/Home/ConfigFileDropZone/ConfigFileDropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { ConfigFile } from "../../../types";
import { getLogger } from "../../../utils/logger";
import { ContentFrame } from "../../UI/ContentFrame";
import { StyledDropZone } from "../../UI/StyledDropZone";

import { DEMO_CONFIG } from "../../../constants/demo-config";
import { useConfigContext } from "../../../common/context/config";
const { stack } = getLogger("ConfigFileDropZone");
interface ConfigFileDropZone {
errorMessage?: string;
Expand All @@ -14,7 +15,7 @@ interface ConfigFileDropZone {

export const ConfigFileDropZone: FunctionComponent<ConfigFileDropZone> = ({ onConfigFile, errorMessage }) => {
const [fileErrors, setFileErrors] = useState<Error[]>([]);

const { setIsDemo } = useConfigContext();
useEffect(() => {
if (errorMessage) {
const malformedError = new Error(errorMessage);
Expand Down Expand Up @@ -62,21 +63,41 @@ export const ConfigFileDropZone: FunctionComponent<ConfigFileDropZone> = ({ onCo
dropzoneIcon={"/dropzone-graphic.png"}
dataTestId="config-file-dropzone"
>
<h4 data-testid="home-description">Drag and drop your configuration file here</h4>
<p className="my-4">or</p>
<Button className="bg-cerulean-500 text-white hover:bg-cerulean-800 border-cloud-800 block mx-auto mb-5">
Select Document
</Button>
<a
onClick={(e) => e.stopPropagation()}
className="text-cerulean-300 mt-8 hover:text-cerulean-500"
href="https://docs.tradetrust.io/docs/reference/document-creator/config-file"
target="_blank"
rel="noopener noreferrer"
data-testid="no-config-file-button"
>
<h5>Don’t have a config file? Learn how to create one</h5>
</a>
<>
<h4 data-testid="home-description">Drag and drop your configuration file here</h4>
<p className="my-4">or</p>
<Button className="bg-cerulean-500 text-white hover:bg-cerulean-800 border-cloud-800 block mx-auto mb-12">
Select Document
</Button>
<h5>{`Don't have config file?`}</h5>
<div className="inline-flex sm:flex justify-between flex-col sm:flex-row mt-8">
<div className="flex items-center flex-col sm:flex-row">
<a
onClick={(e) => e.stopPropagation()}
className="text-cerulean-300 hover:text-cerulean-500"
href="https://docs.tradetrust.io/docs/reference/document-creator/config-file"
target="_blank"
rel="noopener noreferrer"
data-testid="no-config-file-button"
>
<h5>Learn how to create one</h5>
</a>
</div>
<div className="border-2 border-gray-200 border-dashed h-10 hidden sm:block" />
<Button
data-testid="load-demo-config-button"
onClick={(e) => {
e.stopPropagation();
setFileErrors([]);
setIsDemo(true);
onConfigFile(DEMO_CONFIG as ConfigFile);
}}
className="bg-white text-cerulean-500 hover:bg-cloud-100 rounded-xl sm:mt-0 mt-4"
>
Load Demo Config
</Button>
</div>
</>
</StyledDropZone>
</ContentFrame>
</>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { ConfigFileDropZoneContainer } from "./ConfigFileDropZone";
import { WalletDecryptionContainer } from "./WalletDecryption/WalletDecryptionContainer";

export const HomeContainer: FunctionComponent = () => {
const { config } = useConfigContext();
const { config, isDemo } = useConfigContext();
const { configFile } = usePersistedConfigFile();

console.log(`Isdemo from Home Container: ${isDemo}`);
// If wallet has been decrypted, redirect to forms
if (config) return <Redirect to="/forms-selection" />;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react";
import { render, waitFor } from "@testing-library/react";
import { WalletDecryptionContainer } from "./WalletDecryptionContainer";
import { useConfigContext } from "../../../common/context/config";
import { usePersistedConfigFile } from "../../../common/hook/usePersistedConfigFile";
import { DEMO_PASSWD } from "../../../constants/demo-config";
import { decryptWalletOrSigner } from "../../../common/config/decrypt";

jest.mock("../../../common/context/config");
jest.mock("../../../common/hook/usePersistedConfigFile");
jest.mock("../../../common/config/decrypt", () => ({
decryptWalletOrSigner: jest.fn().mockResolvedValue({}),
}));

describe("WalletDecryptionContainer", () => {
afterEach(() => {
jest.clearAllMocks();
});
it("should call onDecryptConfigFile with DEMO_PASSWD if isDemo is true and configFile exists", async () => {
const setConfig = jest.fn();
const configFile = { network: "mainnet", forms: [], documentStorage: {} };

// Mock useConfigContext to return isDemo as true
(useConfigContext as jest.Mock).mockReturnValue({
setConfig,
isDemo: true,
});

// Mock usePersistedConfigFile to return a configFile
(usePersistedConfigFile as jest.Mock).mockReturnValue({
configFile,
setConfigFile: jest.fn(),
});

render(<WalletDecryptionContainer />);

// Wait for useEffect to run
await waitFor(() => {
expect(decryptWalletOrSigner).toHaveBeenCalledWith(configFile, DEMO_PASSWD, expect.any(Function));
});
});
});
51 changes: 30 additions & 21 deletions src/components/Home/WalletDecryption/WalletDecryptionContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { FunctionComponent, useState } from "react";
import { FunctionComponent, useCallback, useEffect, useState } from "react";
import { decryptWalletOrSigner } from "../../../common/config/decrypt";
import { useConfigContext } from "../../../common/context/config";
import { usePersistedConfigFile } from "../../../common/hook/usePersistedConfigFile";
import { WalletDecryption } from "./WalletDecryption";
import { getLogger } from "../../../utils/logger";
import { DEMO_PASSWD } from "../../../constants/demo-config";

const { stack } = getLogger("Wallet Decryption Container");

export const WalletDecryptionContainer: FunctionComponent = () => {
const { setConfig } = useConfigContext();
const { setConfig, isDemo } = useConfigContext();
const [isIncorrectPassword, setIsIncorrectPassword] = useState(false);
const { configFile, setConfigFile } = usePersistedConfigFile();
const [decryptProgress, setDecryptProgress] = useState<number>(0);
Expand All @@ -17,27 +18,35 @@ export const WalletDecryptionContainer: FunctionComponent = () => {
setConfigFile();
};

const onDecryptConfigFile = async (password: string): Promise<void> => {
if (!configFile) return;
try {
setIsIncorrectPassword(false);
const walletOrSigner = await decryptWalletOrSigner(configFile, password, (progress) => {
setDecryptProgress(progress);
});
setConfig({
network: configFile.network,
wallet: walletOrSigner,
forms: configFile.forms,
documentStorage: configFile.documentStorage,
});
} catch (e) {
if (e instanceof Error) {
setIsIncorrectPassword(true);
stack(e);
const onDecryptConfigFile = useCallback(
async (password: string): Promise<void> => {
if (!configFile) return;
try {
setIsIncorrectPassword(false);
const walletOrSigner = await decryptWalletOrSigner(configFile, password, (progress) => {
setDecryptProgress(progress);
});
setConfig({
network: configFile.network,
wallet: walletOrSigner,
forms: configFile.forms,
documentStorage: configFile.documentStorage,
});
} catch (e) {
if (e instanceof Error) {
setIsIncorrectPassword(true);
stack(e);
}
}
}
};
},
[configFile, setConfig, setDecryptProgress, setIsIncorrectPassword]
);

useEffect(() => {
if (isDemo && configFile) {
onDecryptConfigFile(DEMO_PASSWD);
}
}, [isDemo, configFile, onDecryptConfigFile]);
return (
<WalletDecryption
decryptProgress={decryptProgress}
Expand Down
3 changes: 1 addition & 2 deletions src/components/UI/StyledDropZone/StyledDropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const StyledDropZone: FunctionComponent<DropZoneProps> = ({
}, [isDragActive, activeStyle, isDragAccept, acceptStyle, isDragReject, rejectStyle]);

return (
<div className={`${baseStyle} ${dragStyle || currentStyle} `} data-testid={dataTestId} {...getRootProps()}>
<div className={` ${baseStyle} ${dragStyle || currentStyle} `} data-testid={dataTestId} {...getRootProps()}>
<input {...getInputProps()} />
{dropzoneIcon && <img className="mx-auto mb-8" src={dropzoneIcon} />}
{error && <p className="max-w-lg text-scarlet-500 text-lg leading-none font-gilroy-bold mb-2">Error</p>}
Expand Down Expand Up @@ -102,7 +102,6 @@ export const StyledDropZone: FunctionComponent<DropZoneProps> = ({
fileErrors.map((fileError, index: number) => {
return <ErrorMessage key={`file-errors-${index}`} id="file-error" message={fileError.message} />;
})}

<div className={error ? "mt-10" : ""}>{children}</div>
</div>
);
Expand Down
Loading
Loading