Skip to content

Commit

Permalink
feat(review): Satisfaction modal for advanced repository (#1167)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrutayisire authored Oct 16, 2023
1 parent f3f7a5d commit 46e0c16
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 118 deletions.
6 changes: 5 additions & 1 deletion packages/manager/src/managers/telemetry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
2 changes: 1 addition & 1 deletion packages/slice-machine/components/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 (
<Box sx={{ mb: 3, display: "flex", justifyContent: "space-between" }}>
{ratingSelectable.map((rating, index) => (
<Button
variant="secondary"
type="button"
key={index}
onClick={() => void form.setFieldValue("rating", rating)}
className={field.value === rating ? "selected" : ""}
sx={{
"&:not(:last-of-type)": {
mr: 1,
},
"&.selected": {
backgroundColor: "code.gray",
color: "white",
},
}}
data-cy={`review-form-score-${rating}`}
>
{rating}
</Button>
))}
</Box>
);
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<ReviewFormProps> = (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();
};

Expand All @@ -123,9 +65,9 @@ const ReviewModal: React.FunctionComponent = () => {

return (
<SliceMachineModal
isOpen={userHasCreatedEnoughContent && !hasSendAReview}
isOpen
shouldCloseOnOverlayClick={false}
onRequestClose={() => skipReview()}
onRequestClose={() => skipReview(reviewType)}
closeTimeoutMS={500}
contentLabel={"Review Modal"}
portalClassName={"ReviewModal"}
Expand Down Expand Up @@ -178,9 +120,9 @@ const ReviewModal: React.FunctionComponent = () => {
}}
>
<Heading sx={{ fontSize: "20px", mr: 4 }}>
Share Feedback
Share feedback
</Heading>
<Close type="button" onClick={() => skipReview()} />
<Close type="button" onClick={() => skipReview(reviewType)} />
</Flex>
<Flex
sx={{
Expand All @@ -190,8 +132,8 @@ const ReviewModal: React.FunctionComponent = () => {
}}
>
<Text variant={"xs"} as={"p"} sx={{ maxWidth: 302, mb: 3 }}>
Overall, how satisfied are you with your Slice Machine
experience?
Overall, how satisfied or dissatisfied are you with your Slice
Machine experience so far?
</Text>
<Box
mb={2}
Expand All @@ -208,11 +150,11 @@ const ReviewModal: React.FunctionComponent = () => {
Very satisfied
</Text>
</Box>
<Field name={"rating"} component={SelectReviewComponent} />
<Field name={"rating"} component={ReviewFormSelect} />
<Field
name={"comment"}
type="text"
placeholder="Share your thoughts. What can we improve?"
placeholder="Tell us more..."
as={Textarea}
autoComplete="off"
sx={{ height: 80, mb: 3 }}
Expand All @@ -233,5 +175,3 @@ const ReviewModal: React.FunctionComponent = () => {
</SliceMachineModal>
);
};

export default ReviewModal;
35 changes: 35 additions & 0 deletions packages/slice-machine/components/ReviewModal/ReviewFormSelect.tsx
Original file line number Diff line number Diff line change
@@ -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<FieldProps> = (props) => {
const { field, form } = props;

return (
<Box sx={{ mb: 3, display: "flex", justifyContent: "space-between" }}>
{ratingSelectable.map((rating, index) => (
<Button
variant="secondary"
type="button"
key={index}
onClick={() => void form.setFieldValue("rating", rating)}
className={field.value === rating ? "selected" : ""}
sx={{
"&:not(:last-of-type)": {
mr: 1,
},
"&.selected": {
backgroundColor: "code.gray",
color: "white",
},
}}
data-cy={`review-form-score-${rating}`}
>
{rating}
</Button>
))}
</Box>
);
};
70 changes: 70 additions & 0 deletions packages/slice-machine/components/ReviewModal/ReviewModal.tsx
Original file line number Diff line number Diff line change
@@ -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 <ReviewForm reviewType="advancedRepository" />;
}

const isOnboardingDone =
sliceCount >= 1 &&
customTypes.length >= 1 &&
hasSliceWithinCustomType &&
hasPushedAnHourAgo;

if (
!userReview.onboarding &&
!userReview.advancedRepository &&
isOnboardingDone
) {
return <ReviewForm reviewType="onboarding" />;
}

return null;
};
1 change: 1 addition & 0 deletions packages/slice-machine/components/ReviewModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ReviewModal } from "./ReviewModal";
16 changes: 13 additions & 3 deletions packages/slice-machine/src/modules/useSliceMachineActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 = () =>
Expand Down
Loading

0 comments on commit 46e0c16

Please sign in to comment.