diff --git a/packages/manager/src/managers/telemetry/types.ts b/packages/manager/src/managers/telemetry/types.ts
index a5aa65198a..193321ed4d 100644
--- a/packages/manager/src/managers/telemetry/types.ts
+++ b/packages/manager/src/managers/telemetry/types.ts
@@ -94,7 +94,11 @@ type CommandInitEndSegmentEvent = SegmentEvent<
type ReviewSegmentEvent = SegmentEvent<
typeof SegmentEventType.review,
- { rating: number; comment: string }
+ {
+ rating: number;
+ comment: string;
+ type: "onboarding" | "advanced repository";
+ }
>;
type SliceSimulatorSetupSegmentEvent = SegmentEvent<
diff --git a/packages/slice-machine/components/App/index.tsx b/packages/slice-machine/components/App/index.tsx
index 63a9eca08f..dee2cfaf82 100644
--- a/packages/slice-machine/components/App/index.tsx
+++ b/packages/slice-machine/components/App/index.tsx
@@ -4,7 +4,7 @@ import { BaseStyles } from "theme-ui";
import { AppLayout, AppLayoutContent } from "@components/AppLayout";
import LoginModal from "@components/LoginModal";
-import ReviewModal from "@components/ReviewModal";
+import { ReviewModal } from "@components/ReviewModal";
import { MissingLibraries } from "@components/MissingLibraries";
import useServerState from "@src/hooks/useServerState";
import { SliceMachineStoreType } from "@src/redux/type";
diff --git a/packages/slice-machine/components/ReviewModal/index.tsx b/packages/slice-machine/components/ReviewModal/ReviewForm.tsx
similarity index 56%
rename from packages/slice-machine/components/ReviewModal/index.tsx
rename to packages/slice-machine/components/ReviewModal/ReviewForm.tsx
index 4a37d77118..08b3e9aef8 100644
--- a/packages/slice-machine/components/ReviewModal/index.tsx
+++ b/packages/slice-machine/components/ReviewModal/ReviewForm.tsx
@@ -1,6 +1,5 @@
-import Modal from "react-modal";
-import SliceMachineModal from "@components/SliceMachineModal";
-import { Field, FieldProps, Form, Formik } from "formik";
+import { FC } from "react";
+import { Field, Form, Formik } from "formik";
import {
Box,
Button,
@@ -11,107 +10,50 @@ import {
Text,
Textarea,
} from "theme-ui";
+import Modal from "react-modal";
import { useSelector } from "react-redux";
+
+import SliceMachineModal from "@components/SliceMachineModal";
import { SliceMachineStoreType } from "@src/redux/type";
import { isModalOpen } from "@src/modules/modal";
import { isLoading } from "@src/modules/loading";
import { LoadingKeysEnum } from "@src/modules/loading/types";
-import {
- getLastSyncChange,
- userHasSendAReview,
-} from "@src/modules/userContext";
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
import { ModalKeysEnum } from "@src/modules/modal/types";
import { telemetry } from "@src/apiClient";
-import { selectAllCustomTypes } from "@src/modules/availableCustomTypes";
-import { getLibraries } from "@src/modules/slices";
-import { hasLocal } from "@lib/models/common/ModelData";
+import { UserReviewType } from "@src/modules/userContext/types";
-Modal.setAppElement("#__next");
+import { ReviewFormSelect } from "./ReviewFormSelect";
-const ratingSelectable = [1, 2, 3, 4, 5];
+Modal.setAppElement("#__next");
-const SelectReviewComponent = ({ field, form }: FieldProps) => {
- return (
-
- {ratingSelectable.map((rating, index) => (
-
- ))}
-
- );
+type ReviewFormProps = {
+ reviewType: UserReviewType;
};
-const ReviewModal: React.FunctionComponent = () => {
- const {
- isReviewLoading,
- isLoginModalOpen,
- hasSendAReview,
- customTypes,
- libraries,
- lastSyncChange,
- } = useSelector((store: SliceMachineStoreType) => ({
- isReviewLoading: isLoading(store, LoadingKeysEnum.REVIEW),
- isLoginModalOpen: isModalOpen(store, ModalKeysEnum.LOGIN),
- hasSendAReview: userHasSendAReview(store),
- customTypes: selectAllCustomTypes(store),
- libraries: getLibraries(store),
- lastSyncChange: getLastSyncChange(store),
- }));
-
+export const ReviewForm: FC = (props) => {
+ const { reviewType } = props;
+ const { isReviewLoading, isLoginModalOpen } = useSelector(
+ (store: SliceMachineStoreType) => ({
+ isReviewLoading: isLoading(store, LoadingKeysEnum.REVIEW),
+ isLoginModalOpen: isModalOpen(store, ModalKeysEnum.LOGIN),
+ })
+ );
const { skipReview, sendAReview, startLoadingReview, stopLoadingReview } =
useSliceMachineActions();
- const sliceCount =
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- libraries && libraries.length
- ? libraries.reduce((count, lib) => {
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (!lib) return count;
- return count + lib.components.length;
- }, 0)
- : 0;
-
- const hasSliceWithinCustomType = customTypes.some(
- (customType) =>
- hasLocal(customType) &&
- customType.local.tabs.some(
- (tab) => tab.sliceZone && tab.sliceZone?.value.length > 0
- )
- );
-
- const hasPushedAnHourAgo = Boolean(
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- lastSyncChange && Date.now() - lastSyncChange >= 3600000
- );
-
- const userHasCreatedEnoughContent =
- sliceCount >= 1 &&
- customTypes.length >= 1 &&
- hasSliceWithinCustomType &&
- hasPushedAnHourAgo;
-
const onSendAReview = (rating: number, comment: string): void => {
startLoadingReview();
- void telemetry.track({ event: "review", rating, comment });
- sendAReview();
+ void telemetry.track({
+ event: "review",
+ rating,
+ comment,
+ type:
+ reviewType === "advancedRepository"
+ ? "advanced repository"
+ : "onboarding",
+ });
+ sendAReview(reviewType);
stopLoadingReview();
};
@@ -123,9 +65,9 @@ const ReviewModal: React.FunctionComponent = () => {
return (
skipReview()}
+ onRequestClose={() => skipReview(reviewType)}
closeTimeoutMS={500}
contentLabel={"Review Modal"}
portalClassName={"ReviewModal"}
@@ -178,9 +120,9 @@ const ReviewModal: React.FunctionComponent = () => {
}}
>
- Share Feedback
+ Share feedback
- skipReview()} />
+ skipReview(reviewType)} />
{
}}
>
- Overall, how satisfied are you with your Slice Machine
- experience?
+ Overall, how satisfied or dissatisfied are you with your Slice
+ Machine experience so far?
{
Very satisfied
-
+
{
);
};
-
-export default ReviewModal;
diff --git a/packages/slice-machine/components/ReviewModal/ReviewFormSelect.tsx b/packages/slice-machine/components/ReviewModal/ReviewFormSelect.tsx
new file mode 100644
index 0000000000..b0d5b9acba
--- /dev/null
+++ b/packages/slice-machine/components/ReviewModal/ReviewFormSelect.tsx
@@ -0,0 +1,35 @@
+import { FC } from "react";
+import { FieldProps } from "formik";
+import { Box, Button } from "theme-ui";
+
+const ratingSelectable = [1, 2, 3, 4, 5];
+
+export const ReviewFormSelect: FC = (props) => {
+ const { field, form } = props;
+
+ return (
+
+ {ratingSelectable.map((rating, index) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/slice-machine/components/ReviewModal/ReviewModal.tsx b/packages/slice-machine/components/ReviewModal/ReviewModal.tsx
new file mode 100644
index 0000000000..d6581ecf55
--- /dev/null
+++ b/packages/slice-machine/components/ReviewModal/ReviewModal.tsx
@@ -0,0 +1,70 @@
+import { FC } from "react";
+import { useSelector } from "react-redux";
+
+import { SliceMachineStoreType } from "@src/redux/type";
+import { getLastSyncChange, getUserReview } from "@src/modules/userContext";
+import { selectAllCustomTypes } from "@src/modules/availableCustomTypes";
+import { getLibraries } from "@src/modules/slices";
+import { hasLocal } from "@lib/models/common/ModelData";
+
+import { ReviewForm } from "./ReviewForm";
+
+export const ReviewModal: FC = () => {
+ const { userReview, customTypes, libraries, lastSyncChange } = useSelector(
+ (store: SliceMachineStoreType) => ({
+ userReview: getUserReview(store),
+ customTypes: selectAllCustomTypes(store),
+ libraries: getLibraries(store),
+ lastSyncChange: getLastSyncChange(store),
+ })
+ );
+
+ const sliceCount =
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ libraries && libraries.length
+ ? libraries.reduce((count, lib) => {
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (!lib) return count;
+ return count + lib.components.length;
+ }, 0)
+ : 0;
+
+ const hasSliceWithinCustomType = customTypes.some(
+ (customType) =>
+ hasLocal(customType) &&
+ customType.local.tabs.some(
+ (tab) => tab.sliceZone && tab.sliceZone?.value.length > 0
+ )
+ );
+
+ const hasPushedAnHourAgo = Boolean(
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ lastSyncChange && Date.now() - lastSyncChange >= 3600000
+ );
+
+ const isAdvancedRepository =
+ sliceCount >= 6 &&
+ customTypes.length >= 6 &&
+ hasSliceWithinCustomType &&
+ hasPushedAnHourAgo;
+
+ if (!userReview.advancedRepository && isAdvancedRepository) {
+ return ;
+ }
+
+ const isOnboardingDone =
+ sliceCount >= 1 &&
+ customTypes.length >= 1 &&
+ hasSliceWithinCustomType &&
+ hasPushedAnHourAgo;
+
+ if (
+ !userReview.onboarding &&
+ !userReview.advancedRepository &&
+ isOnboardingDone
+ ) {
+ return ;
+ }
+
+ return null;
+};
diff --git a/packages/slice-machine/components/ReviewModal/index.ts b/packages/slice-machine/components/ReviewModal/index.ts
new file mode 100644
index 0000000000..2349b69178
--- /dev/null
+++ b/packages/slice-machine/components/ReviewModal/index.ts
@@ -0,0 +1 @@
+export { ReviewModal } from "./ReviewModal";
diff --git a/packages/slice-machine/src/modules/useSliceMachineActions.ts b/packages/slice-machine/src/modules/useSliceMachineActions.ts
index c61d5d4982..c271994095 100644
--- a/packages/slice-machine/src/modules/useSliceMachineActions.ts
+++ b/packages/slice-machine/src/modules/useSliceMachineActions.ts
@@ -23,7 +23,7 @@ import {
renameAvailableCustomType,
} from "./availableCustomTypes";
import { createSlice, deleteSliceCreator, renameSliceCreator } from "./slices";
-import { UserContextStoreType } from "./userContext/types";
+import { UserContextStoreType, UserReviewType } from "./userContext/types";
import { GenericToastTypes, openToasterCreator } from "./toaster";
import {
initCustomTypeStoreCreator,
@@ -134,8 +134,18 @@ const useSliceMachineActions = () => {
dispatch(stopLoadingActionCreator({ loadingKey: LoadingKeysEnum.LOGIN }));
// UserContext module
- const skipReview = () => dispatch(skipReviewCreator());
- const sendAReview = () => dispatch(sendAReviewCreator());
+ const skipReview = (reviewType: UserReviewType) =>
+ dispatch(
+ skipReviewCreator({
+ reviewType,
+ })
+ );
+ const sendAReview = (reviewType: UserReviewType) =>
+ dispatch(
+ sendAReviewCreator({
+ reviewType,
+ })
+ );
const setUpdatesViewed = (versions: UserContextStoreType["updatesViewed"]) =>
dispatch(updatesViewedCreator(versions));
const setSeenSimulatorToolTip = () =>
diff --git a/packages/slice-machine/src/modules/userContext/index.ts b/packages/slice-machine/src/modules/userContext/index.ts
index 9d616f53d5..45191cf2ae 100644
--- a/packages/slice-machine/src/modules/userContext/index.ts
+++ b/packages/slice-machine/src/modules/userContext/index.ts
@@ -4,6 +4,8 @@ import { ActionType, createAction, getType } from "typesafe-actions";
import {
AuthStatus,
UserContextStoreType,
+ UserReviewState,
+ UserReviewType,
} from "@src/modules/userContext/types";
import { refreshStateCreator } from "../environment";
import ErrorWithStatus from "@lib/models/common/ErrorWithStatus";
@@ -12,7 +14,10 @@ import { changesPushCreator } from "../pushChangesSaga";
// NOTE: Be careful every key written in this store is persisted in the localstorage
const initialState: UserContextStoreType = {
- hasSendAReview: false,
+ userReview: {
+ onboarding: false,
+ advancedRepository: false,
+ },
updatesViewed: {
latest: null,
latestNonBreaking: null,
@@ -25,9 +30,13 @@ const initialState: UserContextStoreType = {
};
// Actions Creators
-export const sendAReviewCreator = createAction("USER_CONTEXT/SEND_REVIEW")();
+export const sendAReviewCreator = createAction("USER_CONTEXT/SEND_REVIEW")<{
+ reviewType: UserReviewType;
+}>();
-export const skipReviewCreator = createAction("USER_CONTEXT/SKIP_REVIEW")();
+export const skipReviewCreator = createAction("USER_CONTEXT/SKIP_REVIEW")<{
+ reviewType: UserReviewType;
+}>();
export const updatesViewedCreator = createAction("USER_CONTEXT/VIEWED_UPDATES")<
UserContextStoreType["updatesViewed"]
@@ -57,8 +66,11 @@ type userContextActions = ActionType<
>;
// Selectors
-export const userHasSendAReview = (state: SliceMachineStoreType): boolean =>
- state.userContext.hasSendAReview;
+export const getUserReview = (state: SliceMachineStoreType): UserReviewState =>
+ state.userContext.userReview ?? {
+ onboarding: state.userContext.hasSendAReview ?? false,
+ advancedRepository: false,
+ };
export const getUpdatesViewed = (
state: SliceMachineStoreType
@@ -90,7 +102,10 @@ export const userContextReducer: Reducer<
case getType(skipReviewCreator):
return {
...state,
- hasSendAReview: true,
+ userReview: {
+ ...state.userReview,
+ [action.payload.reviewType]: true,
+ },
};
case getType(updatesViewedCreator): {
return {
diff --git a/packages/slice-machine/src/modules/userContext/types.ts b/packages/slice-machine/src/modules/userContext/types.ts
index b9ef1109f8..83af8ec241 100644
--- a/packages/slice-machine/src/modules/userContext/types.ts
+++ b/packages/slice-machine/src/modules/userContext/types.ts
@@ -6,7 +6,7 @@ export enum AuthStatus {
}
export type UserContextStoreType = {
- hasSendAReview: boolean;
+ userReview: UserReviewState;
updatesViewed: {
latest: string | null;
latestNonBreaking: string | null;
@@ -16,4 +16,17 @@ export type UserContextStoreType = {
hasSeenChangesToolTip: boolean;
authStatus: AuthStatus;
lastSyncChange: number | null;
+} & LegacyUserContextStoreType;
+
+export type UserReviewState = {
+ onboarding: boolean;
+ advancedRepository: boolean;
+};
+
+export type UserReviewType = keyof UserReviewState;
+
+// Allow to handle old property that users can have
+// in their local storage
+type LegacyUserContextStoreType = {
+ hasSendAReview?: boolean;
};
diff --git a/packages/slice-machine/test/src/modules/userContext.test.ts b/packages/slice-machine/test/src/modules/userContext.test.ts
index d69c77d088..d1b7fe41f3 100644
--- a/packages/slice-machine/test/src/modules/userContext.test.ts
+++ b/packages/slice-machine/test/src/modules/userContext.test.ts
@@ -20,41 +20,57 @@ describe("[UserContext module]", () => {
expect(userContextReducer({}, { type: "NO.MATCH" })).toEqual({});
});
- it("should update hasSendAReview to true when given USER_CONTEXT/SEND_REVIEW action", () => {
+ it("should update user review onboarding to true when given USER_CONTEXT/SEND_REVIEW action", () => {
// @ts-expect-error TS(2739) FIXME: Type '{ hasSendAReview: false;... Remove this comment to see the full error message
const initialState: UserContextStoreType = {
- hasSendAReview: false,
+ userReview: {
+ onboarding: false,
+ advancedRepository: false,
+ },
updatesViewed: {
latest: null,
latestNonBreaking: null,
},
};
- const action = sendAReviewCreator();
+ const action = sendAReviewCreator({
+ reviewType: "onboarding",
+ });
- const expectedState = {
+ const expectedState: UserContextStoreType = {
...initialState,
- hasSendAReview: true,
+ userReview: {
+ onboarding: true,
+ advancedRepository: false,
+ },
};
expect(userContextReducer(initialState, action)).toEqual(expectedState);
});
- it("should update hasSendAReview to true when given USER_CONTEXT/SKIP_REVIEW action", () => {
+ it("should update user review onboarding to true when given USER_CONTEXT/SKIP_REVIEW action", () => {
// @ts-expect-error TS(2739) FIXME: Type '{ hasSendAReview: false;... Remove this comment to see the full error message
const initialState: UserContextStoreType = {
- hasSendAReview: false,
+ userReview: {
+ onboarding: false,
+ advancedRepository: false,
+ },
updatesViewed: {
latest: null,
latestNonBreaking: null,
},
};
- const action = skipReviewCreator();
+ const action = skipReviewCreator({
+ reviewType: "onboarding",
+ });
- const expectedState = {
+ const expectedState: UserContextStoreType = {
...initialState,
- hasSendAReview: true,
+ userReview: {
+ onboarding: true,
+ advancedRepository: false,
+ },
};
expect(userContextReducer(initialState, action)).toEqual(expectedState);