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

Save local state in sessionStorage or shared state #274

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const config: StorybookConfig = {
configFile: configFileMap[CHROMATIC_BASE_URL || '"https://www.chromatic.com"'],
},
},
"@storybook/addon-mdx-gfm"
"@storybook/addon-mdx-gfm",
],
docs: {
autodocs: "tag",
Expand All @@ -43,8 +43,14 @@ const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
refs: {
"@storybook/components": {
title: "@storybook/components",
url: "https://next--635781f3500dd2c49e189caf.chromatic.com",
title: "Storybook Components",
url: "https://next--635781f3500dd2c49e189caf.chromatic.com/",
expanded: false,
},
"@storybook/icons": {
title: "Storybook Icons",
url: "https://main--64b56e737c0aeefed9d5e675.chromatic.com/",
expanded: false,
},
},
async viteFinal(config, { configType }) {
Expand Down
10 changes: 7 additions & 3 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import {
} from "@storybook/theming";
import { HttpResponse, graphql } from "msw";
import { initialize, mswLoader } from "msw-storybook-addon";
import React, { useState } from "react";
import React from "react";

import { AuthProvider } from "../src/AuthContext";
import { baseModes } from "../src/modes";
import { UninstallProvider } from "../src/screens/Uninstalled/UninstallContext";
import { RunBuildProvider } from "../src/screens/VisualTests/RunBuildContext";
import { GraphQLClientProvider } from "../src/utils/graphQLClient";
import { storyWrapper } from "../src/utils/storyWrapper";
import { useSessionState } from "../src/utils/useSessionState";

// Initialize MSW
initialize({
Expand Down Expand Up @@ -137,9 +138,12 @@ const withManagerApi = storyWrapper(ManagerContext.Provider, ({ argsByTarget })
}));

const withUninstall: Decorator = (Story) => {
const [addonInstalled, setAddonInstalled] = useState(false);
const [addonUninstalled, setAddonUninstalled] = useSessionState("addonUninstalled", false);
return (
<UninstallProvider addonUninstalled={addonInstalled} setAddonUninstalled={setAddonInstalled}>
<UninstallProvider
addonUninstalled={addonUninstalled}
setAddonUninstalled={setAddonUninstalled}
>
<Story />
</UninstallProvider>
);
Expand Down
15 changes: 11 additions & 4 deletions src/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { API } from "@storybook/manager-api";
import { useChannel, useStorybookState } from "@storybook/manager-api";
import React, { useCallback, useState } from "react";
import React, { useCallback } from "react";

import { AuthProvider } from "./AuthContext";
import { Spinner } from "./components/design-system";
Expand All @@ -15,7 +15,6 @@ import {
START_BUILD,
STOP_BUILD,
} from "./constants";
import { Project } from "./gql/graphql";
import { Authentication } from "./screens/Authentication/Authentication";
import { GitNotFound } from "./screens/Errors/GitNotFound";
import { LinkedProject } from "./screens/LinkProject/LinkedProject";
Expand All @@ -30,6 +29,7 @@ import { VisualTests } from "./screens/VisualTests/VisualTests";
import { GitInfoPayload, LocalBuildProgress, UpdateStatusFunction } from "./types";
import { client, Provider, useAccessToken } from "./utils/graphQLClient";
import { useProjectId } from "./utils/useProjectId";
import { clearSessionState, useSessionState } from "./utils/useSessionState";
import { useSharedState } from "./utils/useSharedState";

interface PanelProps {
Expand All @@ -38,7 +38,14 @@ interface PanelProps {
}

export const Panel = ({ active, api }: PanelProps) => {
const [accessToken, setAccessToken] = useAccessToken();
const [accessToken, updateAccessToken] = useAccessToken();
const setAccessToken = useCallback(
(token: string | null) => {
updateAccessToken(token);
if (!token) clearSessionState("authenticationScreen", "exchangeParameters");
},
[updateAccessToken]
);
const { storyId } = useStorybookState();

const [gitInfo] = useSharedState<GitInfoPayload>(GIT_INFO);
Expand All @@ -64,7 +71,7 @@ export const Panel = ({ active, api }: PanelProps) => {
} = useProjectId();

// If the user creates a project in a dialog (either during login or later, it get set here)
const [createdProjectId, setCreatedProjectId] = useState<Project["id"]>();
const [createdProjectId, setCreatedProjectId] = useSessionState<string>("createdProjectId");
const [addonUninstalled, setAddonUninstalled] = useSharedState<boolean>(REMOVE_ADDON);

const startBuild = () => emit(START_BUILD, { accessToken });
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ async function serverChannel(channel: Channel, options: Options & { configFile?:
projectInfoState.value = {
...projectInfoState.value,
written: true,
dismissed: false,
configFile: targetConfigFile,
};
} catch (err) {
Expand All @@ -170,6 +171,7 @@ async function serverChannel(channel: Channel, options: Options & { configFile?:
projectInfoState.value = {
...projectInfoState.value,
written: false,
dismissed: false,
configFile: writtenConfigFile,
};
}
Expand Down
4 changes: 3 additions & 1 deletion src/screens/Authentication/Authentication.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { panelModes } from "../../modes";
import { GraphQLClientProvider } from "../../utils/graphQLClient";
import { playAll } from "../../utils/playAll";
import { storyWrapper } from "../../utils/storyWrapper";
import { clearSessionState } from "../../utils/useSessionState";
import { withFigmaDesign } from "../../utils/withFigmaDesign";
import { withSetup } from "../../utils/withSetup";
import { Authentication } from "./Authentication";

const meta = {
component: Authentication,
decorators: [storyWrapper(GraphQLClientProvider)],
decorators: [withSetup(clearSessionState), storyWrapper(GraphQLClientProvider)],
args: {
setAccessToken: action("setAccessToken"),
hasProjectId: false,
Expand Down
11 changes: 8 additions & 3 deletions src/screens/Authentication/Authentication.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useCallback, useState } from "react";
import { Project } from "../../gql/graphql";
import { initiateSignin, TokenExchangeParameters } from "../../utils/requestAccessToken";
import { useErrorNotification } from "../../utils/useErrorNotification";
import { useSessionState } from "../../utils/useSessionState";
import { useUninstallAddon } from "../Uninstalled/UninstallContext";
import { SetSubdomain } from "./SetSubdomain";
import { SignIn } from "./SignIn";
Expand All @@ -22,8 +23,12 @@ export const Authentication = ({
setCreatedProjectId,
hasProjectId,
}: AuthenticationProps) => {
const [screen, setScreen] = useState<AuthenticationScreen>(hasProjectId ? "signin" : "welcome");
const [exchangeParameters, setExchangeParameters] = useState<TokenExchangeParameters>();
const [screen, setScreen] = useSessionState<AuthenticationScreen>(
"authenticationScreen",
hasProjectId ? "signin" : "welcome"
);
const [exchangeParameters, setExchangeParameters] =
useSessionState<TokenExchangeParameters>("exchangeParameters");
const onError = useErrorNotification();
const { uninstallAddon } = useUninstallAddon();

Expand All @@ -36,7 +41,7 @@ export const Authentication = ({
onError("Sign in Error", err);
}
},
[onError]
[onError, setExchangeParameters, setScreen]
);

if (screen === "welcome" && !hasProjectId) {
Expand Down
13 changes: 6 additions & 7 deletions src/screens/GuidedTour/GuidedTour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Joyride from "react-joyride";

import { PANEL_ID } from "../../constants";
import { ENABLE_FILTER } from "../../SidebarBottom";
import { useSessionState } from "../../utils/useSessionState";
import { useSelectedStoryState } from "../VisualTests/BuildContext";
import { Confetti } from "./Confetti";
import { Tooltip, TooltipProps } from "./Tooltip";
Expand Down Expand Up @@ -56,11 +57,9 @@ export const GuidedTour = ({
managerApi.setSelectedPanel(PANEL_ID);
}, [managerApi]);

const [showConfetti, setShowConfetti] = React.useState(false);
const [stepIndex, setStepIndex] = React.useState<number>(0);
const nextStep = () => {
setStepIndex((prev) => prev + 1);
};
const [showConfetti, setShowConfetti] = useSessionState("showConfetti", false);
const [stepIndex, setStepIndex] = useSessionState("stepIndex", 0);
const nextStep = () => setStepIndex((prev = 0) => prev + 1);

useEffect(() => {
// Listen for internal event to indicate a filter was set before moving to next step.
Expand All @@ -71,15 +70,15 @@ export const GuidedTour = ({
window.dispatchEvent(new Event("resize"));
}, 100);
});
}, [managerApi]);
}, [managerApi, setStepIndex]);

useEffect(() => {
// Listen for the test status to change to ACCEPTED and move to the completed step.
if (selectedStory?.selectedTest?.status === "ACCEPTED" && stepIndex === 5) {
setShowConfetti(true);
setStepIndex(6);
}
}, [selectedStory?.selectedTest?.status, showConfetti, setShowConfetti, stepIndex]);
}, [selectedStory?.selectedTest?.status, showConfetti, setShowConfetti, stepIndex, setStepIndex]);

const steps: Partial<GuidedTourStep>[] = [
{
Expand Down
14 changes: 7 additions & 7 deletions src/screens/LinkProject/LinkProject.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AddIcon } from "@storybook/icons";
import { styled } from "@storybook/theming";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect } from "react";
import { useQuery } from "urql";

import { Container } from "../../components/Container";
Expand All @@ -10,8 +10,9 @@ import { Screen } from "../../components/Screen";
import { Stack } from "../../components/Stack";
import { Text } from "../../components/Text";
import { graphql } from "../../gql";
import type { Account, Project, SelectProjectsQueryQuery } from "../../gql/graphql";
import type { Project, SelectProjectsQueryQuery } from "../../gql/graphql";
import { DialogHandler, useChromaticDialog } from "../../utils/useChromaticDialog";
import { useSessionState } from "../../utils/useSessionState";

const SelectProjectsQuery = graphql(/* GraphQL */ `
query SelectProjectsQuery {
Expand Down Expand Up @@ -130,13 +131,12 @@ function SelectProject({
return () => clearInterval(interval);
}, [rerunProjectsQuery]);

const [selectedAccountId, setSelectedAccountId] = useState<Account["id"]>();
const [selectedAccountId, setSelectedAccountId] = useSessionState<string>("selectedAccountId");
const selectedAccount = data?.viewer?.accounts.find((a) => a.id === selectedAccountId);

const onSelectAccount = React.useCallback(
(account: NonNullable<SelectProjectsQueryQuery["viewer"]>["accounts"][number]) => {
setSelectedAccountId(account.id);
},
(account: NonNullable<SelectProjectsQueryQuery["viewer"]>["accounts"][number]) =>
setSelectedAccountId(account.id),
[setSelectedAccountId]
);

Expand All @@ -146,7 +146,7 @@ function SelectProject({
}
}, [data, selectedAccountId, onSelectAccount]);

const [isSelectingProject, setSelectingProject] = useState(false);
const [isSelectingProject, setSelectingProject] = useSessionState("isSelectingProject", false);

const handleSelectProject = React.useCallback(
(
Expand Down
3 changes: 3 additions & 0 deletions src/screens/Onboarding/Onboarding.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { LocalBuildProgress } from "../../types";
import { GraphQLClientProvider } from "../../utils/graphQLClient";
import { playAll } from "../../utils/playAll";
import { storyWrapper } from "../../utils/storyWrapper";
import { clearSessionState } from "../../utils/useSessionState";
import { withFigmaDesign } from "../../utils/withFigmaDesign";
import { withSetup } from "../../utils/withSetup";
import { BuildProvider } from "../VisualTests/BuildContext";
import { acceptedBuild, acceptedTests, buildInfo, withTests } from "../VisualTests/mocks";
import { RunBuildProvider } from "../VisualTests/RunBuildContext";
Expand Down Expand Up @@ -43,6 +45,7 @@ const RunBuildWrapper = ({
const meta = {
component: Onboarding,
decorators: [
withSetup(clearSessionState),
storyWrapper(BuildProvider, (ctx) => ({ watchState: buildInfo(ctx.parameters.selectedBuild) })),
storyWrapper(GraphQLClientProvider),
],
Expand Down
26 changes: 17 additions & 9 deletions src/screens/Onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PlayIcon } from "@storybook/icons";
import { styled } from "@storybook/theming";
import { lighten } from "polished";
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";

import { BuildProgressInline } from "../../components/BuildProgressBarInline";
import { Button } from "../../components/Button";
Expand All @@ -16,6 +16,7 @@ import { Stack } from "../../components/Stack";
import { Text } from "../../components/Text";
import { AccountSuspensionReason, SelectedBuildFieldsFragment } from "../../gql/graphql";
import { GitInfoPayload, LocalBuildProgress } from "../../types";
import { useSessionState } from "../../utils/useSessionState";
import { AccountSuspended } from "../Errors/AccountSuspended";
import { BuildError } from "../Errors/BuildError";
import { useBuildState, useSelectedStoryState } from "../VisualTests/BuildContext";
Expand Down Expand Up @@ -78,23 +79,30 @@ export const Onboarding = ({
// The initial build screen is only necessary if this is a brand new project with no builds at all. Instead, !selectedBuild would appear on any new branch, even if there are other builds on the project.
// TODO: Removed this entirely to solve for the most common case of an existing user with some builds to use as a baseline.
// Removing instead of fixing to avoid additional work as this project is past due. We need to revisit this later.
const [showInitialBuild, setShowInitialBuild] = useState(showInitialBuildScreen);
const [showInitialBuild, setShowInitialBuild] = useSessionState(
"showInitialBuild",
showInitialBuildScreen
);
useEffect(() => {
// Watch the value of showInitialBuildScreen, and if it becomes true, set the state to true. This is necessary because Onboarding may render before there is data to determine if there are any builds.
if (showInitialBuildScreen) {
setShowInitialBuild(true);
}
}, [showInitialBuildScreen]);
if (showInitialBuildScreen) setShowInitialBuild(true);
}, [showInitialBuildScreen, setShowInitialBuild]);

const [showCatchAChange, setShowCatchAChange] = useState(() => !showInitialBuild);
const [initialGitHash, setInitialGitHash] = React.useState(gitInfo.uncommittedHash);
const [showCatchAChange, setShowCatchAChange] = useSessionState(
"showCatchAChange",
!showInitialBuild
);
const [initialGitHash, setInitialGitHash] = useSessionState(
"initialGitHash",
gitInfo.uncommittedHash
);

const onCatchAChange = () => {
setInitialGitHash(gitInfo.uncommittedHash);
setShowCatchAChange(true);
};

const [runningSecondBuild, setRunningSecondBuild] = React.useState(false);
const [runningSecondBuild, setRunningSecondBuild] = useSessionState("runningSecondBuild", false);

// TODO: This design for an error in the Onboarding is incomplete
if (localBuildProgress && localBuildProgress.currentStep === "error") {
Expand Down
9 changes: 6 additions & 3 deletions src/screens/VisualTests/VisualTests.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useStorybookApi, useStorybookState } from "@storybook/manager-api";
import type { API_StatusState } from "@storybook/types";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect } from "react";
import { useMutation } from "urql";

import { PANEL_ID } from "../../constants";
Expand All @@ -15,6 +15,7 @@ import {
import { GitInfoPayload, LocalBuildProgress, UpdateStatusFunction } from "../../types";
import { testsToStatusUpdate } from "../../utils/testsToStatusUpdate";
import { SelectedBuildInfo, updateSelectedBuildInfo } from "../../utils/updateSelectedBuildInfo";
import { useSessionState } from "../../utils/useSessionState";
import { AccountSuspended } from "../Errors/AccountSuspended";
import { GuidedTour } from "../GuidedTour/GuidedTour";
import { Onboarding } from "../Onboarding/Onboarding";
Expand All @@ -31,7 +32,7 @@ const createEmptyStoryStatusUpdate = (state: API_StatusState) => {
interface VisualTestsProps {
isOutdated: boolean;
selectedBuildInfo?: SelectedBuildInfo;
setSelectedBuildInfo: ReturnType<typeof useState<SelectedBuildInfo>>[1];
setSelectedBuildInfo: ReturnType<typeof useSessionState<SelectedBuildInfo | undefined>>[1];
dismissBuildError: () => void;
localBuildProgress?: LocalBuildProgress;
setOutdated: (isOutdated: boolean) => void;
Expand Down Expand Up @@ -394,7 +395,9 @@ export const VisualTestsWithoutSelectedBuildId = ({
export const VisualTests = (
props: Omit<VisualTestsProps, "selectedBuildInfo" | "setSelectedBuildInfo">
) => {
const [selectedBuildInfo, setSelectedBuildInfo] = useState<SelectedBuildInfo>();
const [selectedBuildInfo, setSelectedBuildInfo] = useSessionState<SelectedBuildInfo | undefined>(
"selectedBuildInfo"
);

return (
<VisualTestsWithoutSelectedBuildId {...{ selectedBuildInfo, setSelectedBuildInfo, ...props }} />
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type GitInfoPayload = Omit<GitInfo, "committerEmail" | "committerName">;
export type ProjectInfoPayload = {
projectId?: string;
written?: boolean;
dismissed?: boolean;
configFile?: string;
};

Expand Down