Update comments onboarding tour (#5903)
This commit is contained in:
parent
374e8aec4a
commit
8f7165b5fe
|
|
@ -1,11 +1,12 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { SaveOrgLogo, SaveOrgRequest } from "api/OrgApi";
|
||||
|
||||
export const fetchOrg = (orgId: string) => {
|
||||
export const fetchOrg = (orgId: string, skipValidation?: boolean) => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_CURRENT_ORG,
|
||||
payload: {
|
||||
orgId,
|
||||
skipValidation,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export interface FetchAllRolesResponse extends ApiResponse {
|
|||
|
||||
export interface FetchOrgRequest {
|
||||
orgId: string;
|
||||
skipValidation?: boolean;
|
||||
}
|
||||
|
||||
export interface FetchAllUsersRequest {
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body_1" width="32" height="32">
|
||||
|
||||
<g transform="matrix(1.4545455 0 0 1.4545455 0 0)">
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
</g>
|
||||
<path transform="matrix(1 0 0 1 0 0)" d="M4 8C 4 4.13401 7.13401 1 11 1C 14.866 1 18 4.13401 18 8L18 8L18 15L11 15C 7.13401 15 4 11.866 4 8z" stroke="none" fill="#FFFFFF" fill-rule="nonzero" />
|
||||
<g transform="matrix(1 0 0 1 0 0)">
|
||||
</g>
|
||||
<path transform="matrix(1 0 0 1 0 0)" d="M4.46667 8C 4.46667 4.39174 7.39174 1.46667 11 1.46667C 14.6083 1.46667 17.5333 4.39174 17.5333 8L17.5333 8L17.5333 14.5333L11 14.5333C 7.39174 14.5333 4.46667 11.6083 4.46667 8z" stroke="#E0DEDE" stroke-width="0.933333" fill="none" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 895 B |
|
|
@ -52,6 +52,8 @@ import { Variant } from "components/ads/common";
|
|||
import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper";
|
||||
import { TourType } from "entities/Tour";
|
||||
import { getCurrentApplicationId } from "selectors/editorSelectors";
|
||||
import useProceedToNextTourStep from "utils/hooks/useProceedToNextTourStep";
|
||||
import { commentsTourStepsEditModeTypes } from "comments/tour/commentsTourSteps";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
|
|
@ -202,18 +204,19 @@ const reduceReactions = (
|
|||
(Array.isArray(reactions) &&
|
||||
reactions.reduce(
|
||||
(res: Record<string, ComponentReaction>, reaction: Reaction) => {
|
||||
const { byUsername, emoji } = reaction;
|
||||
const { byName, byUsername, emoji } = reaction;
|
||||
const sameAsCurrent = byUsername === username;
|
||||
const name = byName || byUsername;
|
||||
if (res[reaction.emoji]) {
|
||||
res[reaction.emoji].count++;
|
||||
if (!sameAsCurrent) {
|
||||
res[reaction.emoji].users = [
|
||||
...(res[reaction.emoji].users || []),
|
||||
byUsername,
|
||||
name,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
const users = !sameAsCurrent ? [byUsername] : [];
|
||||
const users = !sameAsCurrent ? [name] : [];
|
||||
res[emoji] = {
|
||||
count: 1,
|
||||
reactionEmoji: emoji,
|
||||
|
|
@ -263,6 +266,10 @@ function CommentCard({
|
|||
inline?: boolean;
|
||||
visible?: boolean;
|
||||
}) {
|
||||
const proceedToNextTourStep = useProceedToNextTourStep({
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]: commentsTourStepsEditModeTypes.RESOLVE,
|
||||
});
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [cardMode, setCardMode] = useState(CommentCardModes.VIEW);
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -427,11 +434,16 @@ function CommentCard({
|
|||
<ResolveButtonContainer>
|
||||
{inline ? (
|
||||
<TourTooltipWrapper
|
||||
tourIndex={2}
|
||||
tourType={TourType.COMMENTS_TOUR}
|
||||
activeStepConfig={{
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]:
|
||||
commentsTourStepsEditModeTypes.RESOLVE,
|
||||
}}
|
||||
>
|
||||
<ResolveCommentButton
|
||||
handleClick={toggleResolved as () => void}
|
||||
handleClick={() => {
|
||||
toggleResolved && toggleResolved();
|
||||
proceedToNextTourStep();
|
||||
}}
|
||||
resolved={!!resolved}
|
||||
/>
|
||||
</TourTooltipWrapper>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const ThreadContainer = styled(animated.div)<{
|
|||
max-height: ${(props) =>
|
||||
props.inline ? `calc(100vh - ${props.theme.smallHeaderHeight})` : "unset"};
|
||||
/* overflow: auto collapses the comment threads in the sidebar */
|
||||
overflow: ${(props) => (props.inline ? "auto" : "unset")};
|
||||
overflow: ${(props) => (props.inline ? "visible" : "unset")};
|
||||
`;
|
||||
|
||||
const CommentsContainer = styled.div<{ inline?: boolean }>`
|
||||
|
|
|
|||
|
|
@ -23,40 +23,50 @@ import { updateUserDetails } from "actions/userActions";
|
|||
|
||||
import { S3_BUCKET_URL } from "constants/ThirdPartyConstants";
|
||||
|
||||
import {
|
||||
isPermitted,
|
||||
PERMISSION_TYPE,
|
||||
} from "pages/Applications/permissionHelpers";
|
||||
import { getCurrentAppOrg } from "selectors/organizationSelectors";
|
||||
import { Org } from "constants/orgConstants";
|
||||
import useOrg from "utils/hooks/useOrg";
|
||||
|
||||
const getBanner = (step: number) =>
|
||||
`${S3_BUCKET_URL}/comments/step-${step}.png`;
|
||||
|
||||
const introSteps = [
|
||||
const introStepsEditor = [
|
||||
{
|
||||
title: "Introducing Live Comments",
|
||||
content:
|
||||
"We are introducing live comments. From now on you will be able to comment on your apps, tag other people and exchange thoughts in threads. Click ‘Next’ to learn more about comments and start commenting.",
|
||||
"You can now collaborate with your users to build apps faster. Invite your team to comment on your apps, exchange thoughts & ship your ideas.",
|
||||
banner: getBanner(1),
|
||||
hideBackBtn: true,
|
||||
bannerProps: { style: { height: 284 } },
|
||||
},
|
||||
{
|
||||
title: "Give feedback",
|
||||
title: "Give Contextual Feedback",
|
||||
content:
|
||||
"Comment on your co-worker’s work and share your thoughts on what works and what needs change.",
|
||||
"Drop a comment on a widget to suggest an improvement. Comments are tagged to the widget and move along with it. Update the widget and iterate your way to shipping your ideas!",
|
||||
banner: getBanner(2),
|
||||
bannerProps: { style: { height: 284 } },
|
||||
},
|
||||
];
|
||||
|
||||
const introStepsViewer = [
|
||||
{
|
||||
title: "Introducing Live Comments",
|
||||
content:
|
||||
"You can now collaborate with your developers to build apps faster. Exchange thoughts, leave feedback & ship your ideas.",
|
||||
banner: getBanner(1),
|
||||
hideBackBtn: true,
|
||||
bannerProps: { style: { height: 284 } },
|
||||
},
|
||||
{
|
||||
title: "Invite other people to your conversations",
|
||||
title: "Give Contextual Feedback",
|
||||
content:
|
||||
"When leaving a comment you can tag other people by writing ‘@’ and their name. This way the person you tagged will get a notification and an e-mail that you tagged them in a comment.",
|
||||
banner: getBanner(3),
|
||||
},
|
||||
{
|
||||
title: "Tag a comment to a widget",
|
||||
content:
|
||||
"If you click on a component while in a comment mode you will tag that comment to that widget. This way if the widget is moved the comment will be moved as well. You can disconnect the comment and widget y simply moving the the comment away from the widget.",
|
||||
banner: getBanner(4),
|
||||
},
|
||||
{
|
||||
title: "You are all set!",
|
||||
content:
|
||||
"By clicking on the comments icon in the top right corner you will activate the ‘collaboration mode’ and will be able to start a thread or answer to someone else’s comment.",
|
||||
banner: getBanner(5),
|
||||
"Drop a comment on a widget to suggest an improvement or report an issue. Comments are tagged to the widget, resolve them once the updates are live!",
|
||||
banner: getBanner(2),
|
||||
bannerProps: { style: { height: 284 } },
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -74,10 +84,11 @@ function IntroStep(props: {
|
|||
content: string;
|
||||
banner: string;
|
||||
theme: Theme;
|
||||
bannerProps: any;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<StyledImg alt="" src={props.banner} />
|
||||
<StyledImg alt="" src={props.banner} {...props.bannerProps} />
|
||||
<IntroContentContainer>
|
||||
<div style={{ marginBottom: props.theme.spaces[4] }}>
|
||||
<Text
|
||||
|
|
@ -108,30 +119,40 @@ const getSteps = (
|
|||
startTutorial: () => void,
|
||||
initialProfileFormValues: { emailAddress?: string; displayName?: string },
|
||||
emailDisabled: boolean,
|
||||
) => [
|
||||
...introSteps.slice(0, 4).map((stepConfig: any) => ({
|
||||
props: stepConfig,
|
||||
component: IntroStepThemed,
|
||||
})),
|
||||
{
|
||||
component: ProfileForm,
|
||||
props: {
|
||||
isSubmitDisabled: isSubmitProfileFormDisabled,
|
||||
onSubmit: onSubmitProfileForm,
|
||||
initialValues: initialProfileFormValues,
|
||||
emailDisabled,
|
||||
showEditorSteps: boolean,
|
||||
) => {
|
||||
const introSteps = showEditorSteps ? introStepsEditor : introStepsViewer;
|
||||
|
||||
return [
|
||||
...introSteps.map((stepConfig: any) => ({
|
||||
props: stepConfig,
|
||||
component: IntroStepThemed,
|
||||
})),
|
||||
{
|
||||
component: ProfileForm,
|
||||
props: {
|
||||
isSubmitDisabled: isSubmitProfileFormDisabled,
|
||||
initialValues: initialProfileFormValues,
|
||||
emailDisabled,
|
||||
nextBtnText: "Start Tutorial",
|
||||
onSubmit: () => {
|
||||
startTutorial();
|
||||
onSubmitProfileForm();
|
||||
},
|
||||
hideBackBtn: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
component: IntroStepThemed,
|
||||
props: {
|
||||
...introSteps[4],
|
||||
hideBackBtn: true,
|
||||
nextBtnText: "Start Tutorial",
|
||||
onSubmit: startTutorial,
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
};
|
||||
|
||||
const getCanManage = (currentOrg: Org) => {
|
||||
const userOrgPermissions = currentOrg.userPermissions || [];
|
||||
const canManage = isPermitted(
|
||||
userOrgPermissions,
|
||||
PERMISSION_TYPE.MANAGE_ORGANIZATION,
|
||||
);
|
||||
return canManage;
|
||||
};
|
||||
|
||||
export default function CommentsShowcaseCarousel() {
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -145,15 +166,24 @@ export default function CommentsShowcaseCarousel() {
|
|||
|
||||
const initialProfileFormValues = { emailAddress: email, displayName: name };
|
||||
const onSubmitProfileForm = () => {
|
||||
const { displayName: name, emailAddress: email } = profileFormValues as {
|
||||
displayName: string;
|
||||
emailAddress: string;
|
||||
};
|
||||
const { displayName: name, emailAddress: email } =
|
||||
(profileFormValues as {
|
||||
displayName: string;
|
||||
emailAddress: string;
|
||||
}) || {};
|
||||
dispatch(updateUserDetails({ name, email }));
|
||||
};
|
||||
|
||||
const { id } = useSelector(getCurrentAppOrg) || {};
|
||||
const currentOrg = useOrg(id);
|
||||
const canManage = getCanManage(currentOrg);
|
||||
|
||||
const startTutorial = () => {
|
||||
dispatch(setActiveTour(TourType.COMMENTS_TOUR));
|
||||
const tourType = canManage
|
||||
? TourType.COMMENTS_TOUR_EDIT_MODE
|
||||
: TourType.COMMENTS_TOUR_PUBLISHED_MODE;
|
||||
|
||||
dispatch(setActiveTour(tourType));
|
||||
dispatch(hideCommentsIntroCarousel());
|
||||
setCommentsIntroSeen(true);
|
||||
};
|
||||
|
|
@ -164,9 +194,10 @@ export default function CommentsShowcaseCarousel() {
|
|||
startTutorial,
|
||||
initialProfileFormValues,
|
||||
!!email,
|
||||
canManage,
|
||||
);
|
||||
|
||||
if (!isIntroCarouselVisible) return null;
|
||||
if (steps.length === 0 || !isIntroCarouselVisible) return null;
|
||||
|
||||
return (
|
||||
<CommentsCarouselModal>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ import { change } from "redux-form";
|
|||
import { INVITE_USERS_TO_ORG_FORM } from "constants/forms";
|
||||
|
||||
import { isEmail } from "utils/formhelpers";
|
||||
import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper";
|
||||
import { TourType } from "entities/Tour";
|
||||
import useProceedToNextTourStep from "utils/hooks/useProceedToNextTourStep";
|
||||
import {
|
||||
commentsTourStepsEditModeTypes,
|
||||
commentsTourStepsPublishedModeTypes,
|
||||
} from "comments/tour/commentsTourSteps";
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
width: 100%;
|
||||
|
|
@ -62,8 +69,6 @@ const Row = styled.div`
|
|||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
// Trigger tests
|
||||
|
||||
const insertCharacter = (
|
||||
characterToInsert: string,
|
||||
editorState: EditorState,
|
||||
|
|
@ -136,14 +141,26 @@ function AddCommentInput({
|
|||
onSave: (state: RawDraftContentState) => void;
|
||||
onCancel?: () => void;
|
||||
}) {
|
||||
const proceedToNextTourStep = useProceedToNextTourStep({
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]:
|
||||
commentsTourStepsEditModeTypes.SAY_HELLO,
|
||||
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
|
||||
commentsTourStepsPublishedModeTypes.SAY_HELLO,
|
||||
});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const users = useOrgUsers();
|
||||
const [suggestions, setSuggestions] = useState<Array<MentionData>>([]);
|
||||
const [trigger, setTrigger] = useState<Trigger>();
|
||||
useUserSuggestions(users, setSuggestions);
|
||||
const [editorState, setEditorState] = useState(
|
||||
const [editorState, setEditorStateInState] = useState(
|
||||
initialEditorState || EditorState.createEmpty(),
|
||||
);
|
||||
|
||||
const setEditorState = useCallback((editorState: EditorState) => {
|
||||
setEditorStateInState(editorState);
|
||||
}, []);
|
||||
|
||||
const [suggestionsQuery, setSuggestionsQuery] = useState("");
|
||||
|
||||
const clearEditor = useCallback(() => {
|
||||
|
|
@ -166,6 +183,7 @@ function AddCommentInput({
|
|||
const rawContent = convertToRaw(contentState);
|
||||
clearEditor();
|
||||
onSave(rawContent);
|
||||
proceedToNextTourStep();
|
||||
},
|
||||
[editorState],
|
||||
);
|
||||
|
|
@ -216,44 +234,53 @@ function AddCommentInput({
|
|||
};
|
||||
|
||||
return (
|
||||
<PaddingContainer removePadding={removePadding}>
|
||||
<Row>
|
||||
<StyledInputContainer>
|
||||
<MentionsInput
|
||||
autoFocus
|
||||
editorState={editorState}
|
||||
onAddMention={onAddMention}
|
||||
onSearchSuggestions={onSearchChange}
|
||||
onSubmit={onSaveComment}
|
||||
placeholder={createMessage(ADD_COMMENT_PLACEHOLDER)}
|
||||
setEditorState={setEditorState}
|
||||
suggestions={filteredSuggestions}
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
</Row>
|
||||
<Row>
|
||||
<StyledEmojiTrigger>
|
||||
<EmojiPicker onSelectEmoji={handleEmojiClick} />
|
||||
</StyledEmojiTrigger>
|
||||
<TourTooltipWrapper
|
||||
activeStepConfig={{
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]:
|
||||
commentsTourStepsEditModeTypes.SAY_HELLO,
|
||||
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
|
||||
commentsTourStepsPublishedModeTypes.SAY_HELLO,
|
||||
}}
|
||||
>
|
||||
<PaddingContainer removePadding={removePadding}>
|
||||
<Row>
|
||||
<Button
|
||||
category={Category.tertiary}
|
||||
className={"cancel-button"}
|
||||
onClick={_onCancel}
|
||||
text={createMessage(CANCEL)}
|
||||
type="button"
|
||||
/>
|
||||
<Button
|
||||
category={Category.primary}
|
||||
data-cy="add-comment-submit"
|
||||
disabled={!editorState.getCurrentContent().hasText()}
|
||||
onClick={handleSubmit}
|
||||
text={createMessage(POST)}
|
||||
type="button"
|
||||
/>
|
||||
<StyledInputContainer>
|
||||
<MentionsInput
|
||||
autoFocus
|
||||
editorState={editorState}
|
||||
onAddMention={onAddMention}
|
||||
onSearchSuggestions={onSearchChange}
|
||||
onSubmit={onSaveComment}
|
||||
placeholder={createMessage(ADD_COMMENT_PLACEHOLDER)}
|
||||
setEditorState={setEditorState}
|
||||
suggestions={filteredSuggestions}
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
</Row>
|
||||
</Row>
|
||||
</PaddingContainer>
|
||||
<Row>
|
||||
<StyledEmojiTrigger>
|
||||
<EmojiPicker onSelectEmoji={handleEmojiClick} />
|
||||
</StyledEmojiTrigger>
|
||||
<Row>
|
||||
<Button
|
||||
category={Category.tertiary}
|
||||
className={"cancel-button"}
|
||||
onClick={_onCancel}
|
||||
text={createMessage(CANCEL)}
|
||||
type="button"
|
||||
/>
|
||||
<Button
|
||||
category={Category.primary}
|
||||
data-cy="add-comment-submit"
|
||||
disabled={!editorState.getCurrentContent().hasText()}
|
||||
onClick={handleSubmit}
|
||||
text={createMessage(POST)}
|
||||
type="button"
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
</PaddingContainer>
|
||||
</TourTooltipWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,20 @@ import { useSelector, useDispatch } from "react-redux";
|
|||
import Comments from "./Comments";
|
||||
import { commentModeSelector } from "selectors/commentsSelectors";
|
||||
import { createUnpublishedCommentThreadRequest } from "actions/commentActions";
|
||||
import commentIcon from "assets/icons/comments/commentCursor.png";
|
||||
import commentIcon from "assets/icons/comments/commentCursor.svg";
|
||||
import { getOffsetPos } from "comments/utils";
|
||||
import useProceedToNextTourStep from "utils/hooks/useProceedToNextTourStep";
|
||||
import { TourType } from "entities/Tour";
|
||||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import {
|
||||
commentsTourStepsEditModeTypes,
|
||||
commentsTourStepsPublishedModeTypes,
|
||||
} from "comments/tour/commentsTourSteps";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
refId: string;
|
||||
widgetType: WidgetType;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
|
|
@ -25,15 +31,17 @@ const Container = styled.div`
|
|||
* 1. Renders inline comment threads down the tree
|
||||
* 2. Calculates pin offset while creating a new comment
|
||||
*/
|
||||
function OverlayCommentsWrapper({ children, refId }: Props) {
|
||||
function OverlayCommentsWrapper({ children, refId, widgetType }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCommentMode = useSelector(commentModeSelector);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const proceedToNextTourStep = useProceedToNextTourStep(
|
||||
TourType.COMMENTS_TOUR,
|
||||
1,
|
||||
);
|
||||
const proceedToNextTourStep = useProceedToNextTourStep({
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]:
|
||||
commentsTourStepsEditModeTypes.CREATE_UNPUBLISHED_COMMENT,
|
||||
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
|
||||
commentsTourStepsPublishedModeTypes.CREATE_UNPUBLISHED_COMMENT,
|
||||
});
|
||||
|
||||
// create new unpublished thread
|
||||
const clickHandler = (e: any) => {
|
||||
|
|
@ -45,6 +53,7 @@ function OverlayCommentsWrapper({ children, refId }: Props) {
|
|||
dispatch(
|
||||
createUnpublishedCommentThreadRequest({
|
||||
refId,
|
||||
widgetType,
|
||||
position,
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,37 @@
|
|||
import { Org } from "constants/orgConstants";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import {
|
||||
isPermitted,
|
||||
PERMISSION_TYPE,
|
||||
} from "pages/Applications/permissionHelpers";
|
||||
import { useEffect } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getAllUsers, getCurrentOrgId } from "selectors/organizationSelectors";
|
||||
import {
|
||||
getAllUsers,
|
||||
getCurrentAppOrg,
|
||||
getCurrentOrgId,
|
||||
} from "selectors/organizationSelectors";
|
||||
import useOrg from "utils/hooks/useOrg";
|
||||
|
||||
const getCanManage = (currentOrg: Org) => {
|
||||
const userOrgPermissions = currentOrg.userPermissions || [];
|
||||
const canManage = isPermitted(
|
||||
userOrgPermissions,
|
||||
PERMISSION_TYPE.MANAGE_ORGANIZATION,
|
||||
);
|
||||
return canManage;
|
||||
};
|
||||
|
||||
const useOrgUsers = () => {
|
||||
const dispatch = useDispatch();
|
||||
const orgId = useSelector(getCurrentOrgId);
|
||||
const orgUsers = useSelector(getAllUsers);
|
||||
const { id } = useSelector(getCurrentAppOrg) || {};
|
||||
const currentOrg = useOrg(id);
|
||||
const canManage = getCanManage(currentOrg);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orgUsers || !orgUsers.length) {
|
||||
if ((!orgUsers || !orgUsers.length) && canManage) {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.FETCH_ALL_USERS_INIT,
|
||||
payload: {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ import React from "react";
|
|||
import styled from "styled-components";
|
||||
import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper";
|
||||
import { TourType } from "entities/Tour";
|
||||
import {
|
||||
commentsTourStepsEditModeTypes,
|
||||
commentsTourStepsPublishedModeTypes,
|
||||
} from "./commentsTourSteps";
|
||||
|
||||
const Dot = styled.div`
|
||||
position: fixed;
|
||||
|
|
@ -14,7 +18,14 @@ const Dot = styled.div`
|
|||
export default function AddCommentTourComponent() {
|
||||
return (
|
||||
<Dot>
|
||||
<TourTooltipWrapper tourIndex={1} tourType={TourType.COMMENTS_TOUR}>
|
||||
<TourTooltipWrapper
|
||||
activeStepConfig={{
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]:
|
||||
commentsTourStepsEditModeTypes.CREATE_UNPUBLISHED_COMMENT,
|
||||
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
|
||||
commentsTourStepsPublishedModeTypes.CREATE_UNPUBLISHED_COMMENT,
|
||||
}}
|
||||
>
|
||||
<div />
|
||||
</TourTooltipWrapper>
|
||||
</Dot>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,55 @@
|
|||
const steps = [
|
||||
import { Position } from "@blueprintjs/core";
|
||||
|
||||
export enum commentsTourStepsEditModeTypes {
|
||||
ENTER_COMMENTS_MODE,
|
||||
CREATE_UNPUBLISHED_COMMENT,
|
||||
SAY_HELLO,
|
||||
RESOLVE,
|
||||
}
|
||||
|
||||
export const commentsTourStepsEditMode = [
|
||||
{
|
||||
id: "ENTER_COMMENTS_MODE",
|
||||
data: { message: "Click on the comment icon \n to enter comments mode" },
|
||||
id: commentsTourStepsEditModeTypes.ENTER_COMMENTS_MODE,
|
||||
data: { message: "Click on the icon to access \n comments." },
|
||||
},
|
||||
{
|
||||
id: "CREATE_UNPUBLISHED_COMMENT",
|
||||
data: { message: "Click anywhere on the canvas \n and leave a comment." },
|
||||
id: commentsTourStepsEditModeTypes.CREATE_UNPUBLISHED_COMMENT,
|
||||
data: { message: "Click anywhere on the canvas \n to leave a comment." },
|
||||
},
|
||||
{
|
||||
id: commentsTourStepsEditModeTypes.SAY_HELLO,
|
||||
data: {
|
||||
message: "Say hello to team appsmith",
|
||||
tooltipProps: { position: Position.TOP },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: commentsTourStepsEditModeTypes.RESOLVE,
|
||||
data: { message: "Resolve this comment \n when you're done" },
|
||||
},
|
||||
];
|
||||
|
||||
export enum commentsTourStepsPublishedModeTypes {
|
||||
ENTER_COMMENTS_MODE,
|
||||
CREATE_UNPUBLISHED_COMMENT,
|
||||
SAY_HELLO,
|
||||
}
|
||||
|
||||
export const commentsTourStepsPublishedMode = [
|
||||
{
|
||||
id: commentsTourStepsPublishedModeTypes.ENTER_COMMENTS_MODE,
|
||||
data: { message: "Click on the icon to access \n comments." },
|
||||
},
|
||||
{
|
||||
id: commentsTourStepsPublishedModeTypes.CREATE_UNPUBLISHED_COMMENT,
|
||||
data: { message: "Click anywhere on the canvas \n to leave a comment." },
|
||||
},
|
||||
{
|
||||
id: commentsTourStepsPublishedModeTypes.SAY_HELLO,
|
||||
data: {
|
||||
message:
|
||||
"Tag your teammate and leave them \n some suggestions for the application!",
|
||||
tooltipProps: { position: Position.TOP },
|
||||
},
|
||||
},
|
||||
];
|
||||
export default steps;
|
||||
|
|
|
|||
|
|
@ -17,12 +17,11 @@ import { Indices } from "constants/Layers";
|
|||
type Props = {
|
||||
children: React.ReactNode;
|
||||
hasOverlay?: boolean;
|
||||
tourType: TourType;
|
||||
tourIndex: number;
|
||||
modifiers?: Modifiers;
|
||||
onClick?: () => void;
|
||||
pulseStyles?: CSSProperties;
|
||||
showPulse?: boolean;
|
||||
activeStepConfig: { [key in TourType]?: any };
|
||||
};
|
||||
|
||||
const Overlay = styled.div`
|
||||
|
|
@ -47,16 +46,18 @@ const Container = styled.div`
|
|||
`;
|
||||
|
||||
function TourTooltipWrapper(props: Props) {
|
||||
const { children, tourIndex, tourType } = props;
|
||||
const { activeStepConfig, children } = props;
|
||||
|
||||
const activeTourType = useSelector(getActiveTourType) as TourType;
|
||||
const expectedActiveStep = activeStepConfig[activeTourType];
|
||||
|
||||
const isCurrentStepActive = useSelector(
|
||||
(state: AppState) => getActiveTourIndex(state) === tourIndex,
|
||||
(state: AppState) => getActiveTourIndex(state) === expectedActiveStep,
|
||||
);
|
||||
const isCurrentTourActive = useSelector(
|
||||
(state: AppState) => getActiveTourType(state) === tourType,
|
||||
);
|
||||
const tourStepsConfig = TourStepsByType[tourType as TourType];
|
||||
const tourStepConfig = tourStepsConfig[tourIndex];
|
||||
const isOpen = isCurrentStepActive && isCurrentTourActive;
|
||||
|
||||
const tourStepsConfig = TourStepsByType[activeTourType as TourType];
|
||||
const tourStepConfig = tourStepsConfig && tourStepsConfig[expectedActiveStep];
|
||||
const isOpen = isCurrentStepActive;
|
||||
const dotRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -78,9 +79,6 @@ function TourTooltipWrapper(props: Props) {
|
|||
{/* A crude overlay which won't work with containers having overflow hidden */}
|
||||
{isOpen && props.hasOverlay && <Overlay />}
|
||||
<Container onClick={props.onClick ? props.onClick : noop}>
|
||||
{isOpen && props.showPulse && (
|
||||
<PulseDot ref={dotRef} style={props.pulseStyles} />
|
||||
)}
|
||||
<TooltipComponent
|
||||
boundary={"viewport"}
|
||||
content={
|
||||
|
|
@ -99,9 +97,13 @@ function TourTooltipWrapper(props: Props) {
|
|||
isOpen={!!isOpen}
|
||||
modifiers={props.modifiers}
|
||||
position={Position.BOTTOM}
|
||||
{...tourStepConfig?.data.tooltipProps}
|
||||
>
|
||||
{children}
|
||||
</TooltipComponent>
|
||||
{isOpen && props.showPulse && (
|
||||
<PulseDot ref={dotRef} style={props.pulseStyles} />
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { TourType } from "entities/Tour";
|
||||
import commentsTourSteps from "comments/tour/commentsTourSteps";
|
||||
import {
|
||||
commentsTourStepsEditMode,
|
||||
commentsTourStepsPublishedMode,
|
||||
} from "comments/tour/commentsTourSteps";
|
||||
|
||||
const TourStepsByType = {
|
||||
[TourType.COMMENTS_TOUR]: commentsTourSteps,
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]: commentsTourStepsEditMode,
|
||||
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]: commentsTourStepsPublishedMode,
|
||||
};
|
||||
|
||||
export default TourStepsByType;
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ export const MARK_ALL_AS_READ = () => "Mark all as read";
|
|||
export const INVITE_A_NEW_USER = () => "Invite a new user";
|
||||
export const REMOVE = () => "Remove";
|
||||
export const NO_NOTIFICATIONS_TO_SHOW = () => "No notifications to show";
|
||||
export const ONE_UNREAD_MESSAGE = () => "1 unread conversation";
|
||||
export const UNREAD_MESSAGE = () => "Unread conversation";
|
||||
|
||||
// Showcase Carousel
|
||||
export const NEXT = () => "NEXT";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { COMMENT_EVENTS } from "constants/CommentConstants";
|
||||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import { APP_MODE } from "reducers/entityReducers/appReducer";
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ export type CreateCommentThreadRequest = {
|
|||
};
|
||||
isViewed?: boolean;
|
||||
mode?: APP_MODE;
|
||||
widgetType?: WidgetType;
|
||||
};
|
||||
|
||||
export type Reaction = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export enum TourType {
|
||||
COMMENTS_TOUR = "COMMENTS_TOUR",
|
||||
COMMENTS_TOUR_EDIT_MODE = "COMMENTS_TOUR_EDIT_MODE",
|
||||
COMMENTS_TOUR_PUBLISHED_MODE = "COMMENTS_TOUR_PUBLISHED_MODE",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ const UnreadIndicator = styled.div`
|
|||
|
||||
const getModeFromUserRole = async (orgId: string) => {
|
||||
try {
|
||||
const response = await OrgApi.fetchOrg({ orgId });
|
||||
const userOrgPermissions = response?.data?.data?.userPermissions || [];
|
||||
const response = (await OrgApi.fetchOrg({ orgId })) as any;
|
||||
const userOrgPermissions = response?.data?.userPermissions || [];
|
||||
const canPublish = isPermitted(
|
||||
userOrgPermissions,
|
||||
PERMISSION_TYPE.PUBLISH_APPLICATION,
|
||||
|
|
|
|||
|
|
@ -47,15 +47,6 @@ const StyledHeader = styled.div`
|
|||
${(props) => getTypographyByKey(props, "p1")};
|
||||
color: ${(props) => props.theme.colors.notifications.listHeaderTitle};
|
||||
}
|
||||
|
||||
& .mark-all-as-read {
|
||||
border-color: ${(props) =>
|
||||
props.theme.colors.notifications.markAllAsReadButtonBackground};
|
||||
background-color: ${(props) =>
|
||||
props.theme.colors.notifications.markAllAsReadButtonBackground};
|
||||
color: ${(props) =>
|
||||
props.theme.colors.notifications.markAllAsReadButtonText};
|
||||
}
|
||||
`;
|
||||
|
||||
const Label = styled.div`
|
||||
|
|
@ -81,7 +72,7 @@ function EmptyNotificationsState() {
|
|||
);
|
||||
}
|
||||
|
||||
function NotificationsListHeader() {
|
||||
function NotificationsListHeader(props: { markAllAsReadDisabled: boolean }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
|
|
@ -90,6 +81,7 @@ function NotificationsListHeader() {
|
|||
<Button
|
||||
category={Category.primary}
|
||||
className={"mark-all-as-read"}
|
||||
disabled={props.markAllAsReadDisabled}
|
||||
onClick={() => {
|
||||
dispatch(markAllNotificationsAsReadRequest());
|
||||
}}
|
||||
|
|
@ -111,11 +103,11 @@ function NotificationsList() {
|
|||
const dispatch = useDispatch();
|
||||
const notifications = useSelector(notificationsSelector);
|
||||
const fetchingNotifications = useSelector(fetchingNotificationsSelector);
|
||||
const height = Math.min(4, notifications.length) * NOTIFICATION_HEIGHT;
|
||||
const height = Math.min(3.5, notifications.length) * NOTIFICATION_HEIGHT;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<NotificationsListHeader />
|
||||
<NotificationsListHeader markAllAsReadDisabled={!notifications.length} />
|
||||
{notifications.length > 0 ? (
|
||||
<Virtuoso
|
||||
components={{
|
||||
|
|
|
|||
|
|
@ -24,18 +24,32 @@ import { useLocation } from "react-router";
|
|||
import history from "utils/history";
|
||||
import { Position } from "@blueprintjs/core/lib/esm/common/position";
|
||||
import { TourType } from "entities/Tour";
|
||||
import useProceedToNextTourStep from "utils/hooks/useProceedToNextTourStep";
|
||||
import useProceedToNextTourStep, {
|
||||
useIsTourStepActive,
|
||||
} from "utils/hooks/useProceedToNextTourStep";
|
||||
import { getCommentsIntroSeen } from "utils/storage";
|
||||
import { User } from "constants/userConstants";
|
||||
import { ANONYMOUS_USERNAME, User } from "constants/userConstants";
|
||||
import { AppState } from "reducers";
|
||||
import { APP_MODE } from "reducers/entityReducers/appReducer";
|
||||
|
||||
import { matchBuilderPath, matchViewerPath } from "constants/routes";
|
||||
import {
|
||||
AUTH_LOGIN_URL,
|
||||
matchBuilderPath,
|
||||
matchViewerPath,
|
||||
} from "constants/routes";
|
||||
|
||||
import { createMessage, ONE_UNREAD_MESSAGE } from "constants/messages";
|
||||
import { createMessage, UNREAD_MESSAGE } from "constants/messages";
|
||||
|
||||
import localStorage from "utils/localStorage";
|
||||
|
||||
import { getAppMode } from "selectors/applicationSelectors";
|
||||
|
||||
import { noop } from "lodash";
|
||||
import {
|
||||
commentsTourStepsEditModeTypes,
|
||||
commentsTourStepsPublishedModeTypes,
|
||||
} from "comments/tour/commentsTourSteps";
|
||||
|
||||
const getShowCommentsButtonToolTip = () => {
|
||||
const flag = localStorage.getItem("ShowCommentsButtonToolTip");
|
||||
return flag === null || !!flag;
|
||||
|
|
@ -115,11 +129,25 @@ const useUpdateCommentMode = async (currentUser?: User) => {
|
|||
);
|
||||
|
||||
const handleLocationUpdate = async () => {
|
||||
if (!currentUser) return;
|
||||
|
||||
const searchParams = new URL(window.location.href).searchParams;
|
||||
const isCommentMode = searchParams.get("isCommentMode");
|
||||
const isCommentsIntroSeen = await getCommentsIntroSeen();
|
||||
const updatedIsCommentMode = isCommentMode === "true" ? true : false;
|
||||
|
||||
const notLoggedId = currentUser?.username === ANONYMOUS_USERNAME;
|
||||
|
||||
if (notLoggedId && updatedIsCommentMode) {
|
||||
const currentUrl = window.location.href;
|
||||
const path = `${AUTH_LOGIN_URL}?redirectUrl=${encodeURIComponent(
|
||||
currentUrl,
|
||||
)}`;
|
||||
history.push(path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (updatedIsCommentMode && !isCommentsIntroSeen) {
|
||||
dispatch(showCommentsIntroCarousel());
|
||||
} else {
|
||||
|
|
@ -132,7 +160,7 @@ const useUpdateCommentMode = async (currentUser?: User) => {
|
|||
if (window.location.href) {
|
||||
handleLocationUpdate();
|
||||
}
|
||||
}, [location]);
|
||||
}, [location, !!currentUser]);
|
||||
|
||||
// fetch applications comments when comment mode is turned on
|
||||
useEffect(() => {
|
||||
|
|
@ -216,8 +244,12 @@ const tourToolTipProps = {
|
|||
width: 30,
|
||||
},
|
||||
showPulse: true,
|
||||
tourIndex: 0,
|
||||
tourType: TourType.COMMENTS_TOUR,
|
||||
activeStepConfig: {
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]:
|
||||
commentsTourStepsEditModeTypes.ENTER_COMMENTS_MODE,
|
||||
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
|
||||
commentsTourStepsPublishedModeTypes.ENTER_COMMENTS_MODE,
|
||||
},
|
||||
};
|
||||
|
||||
function ViewOrEditMode({ mode }: { mode?: APP_MODE }) {
|
||||
|
|
@ -261,6 +293,44 @@ function CommentModeBtn({
|
|||
);
|
||||
}
|
||||
|
||||
const useShowCommentDiscoveryTooltip = (): [boolean, typeof noop] => {
|
||||
const currentUser = useSelector(getCurrentUser);
|
||||
const appMode = useSelector(getAppMode);
|
||||
|
||||
const initShowCommentButtonDiscoveryTooltip =
|
||||
getShowCommentsButtonToolTip() &&
|
||||
appMode === APP_MODE.PUBLISHED &&
|
||||
currentUser?.username !== ANONYMOUS_USERNAME;
|
||||
|
||||
const [
|
||||
showCommentButtonDiscoveryTooltip,
|
||||
setShowCommentButtonDiscoveryTooltipInState,
|
||||
] = useState(initShowCommentButtonDiscoveryTooltip);
|
||||
|
||||
useEffect(() => {
|
||||
setShowCommentButtonDiscoveryTooltipInState(
|
||||
initShowCommentButtonDiscoveryTooltip,
|
||||
);
|
||||
}, [appMode, currentUser]);
|
||||
|
||||
return [
|
||||
showCommentButtonDiscoveryTooltip,
|
||||
setShowCommentButtonDiscoveryTooltipInState,
|
||||
];
|
||||
};
|
||||
|
||||
const useShouldHide = () => {
|
||||
const [shouldHide, setShouldHide] = useState(false);
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
const pathName = window.location.pathname;
|
||||
const shouldShow = matchBuilderPath(pathName) || matchViewerPath(pathName);
|
||||
setShouldHide(!shouldShow);
|
||||
}, [location]);
|
||||
|
||||
return shouldHide;
|
||||
};
|
||||
|
||||
type ToggleCommentModeButtonProps = {
|
||||
showSelectedMode?: boolean;
|
||||
};
|
||||
|
|
@ -271,20 +341,28 @@ function ToggleCommentModeButton({
|
|||
const commentsEnabled = useSelector(areCommentsEnabledForUserAndAppSelector);
|
||||
const isCommentMode = useSelector(commentModeSelector);
|
||||
const currentUser = useSelector(getCurrentUser);
|
||||
|
||||
const [
|
||||
showCommentButtonDiscoveryTooltip,
|
||||
setShowCommentButtonDiscoveryTooltipInState,
|
||||
] = useState(getShowCommentsButtonToolTip());
|
||||
] = useShowCommentDiscoveryTooltip();
|
||||
|
||||
const showUnreadIndicator =
|
||||
useSelector(showUnreadIndicatorSelector) ||
|
||||
showCommentButtonDiscoveryTooltip;
|
||||
|
||||
useUpdateCommentMode(currentUser);
|
||||
const proceedToNextTourStep = useProceedToNextTourStep(
|
||||
TourType.COMMENTS_TOUR,
|
||||
0,
|
||||
);
|
||||
|
||||
const activeStepConfig = {
|
||||
[TourType.COMMENTS_TOUR_EDIT_MODE]:
|
||||
commentsTourStepsEditModeTypes.ENTER_COMMENTS_MODE,
|
||||
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
|
||||
commentsTourStepsPublishedModeTypes.ENTER_COMMENTS_MODE,
|
||||
};
|
||||
|
||||
const proceedToNextTourStep = useProceedToNextTourStep(activeStepConfig);
|
||||
|
||||
const isTourStepActive = useIsTourStepActive(activeStepConfig);
|
||||
|
||||
const mode = useSelector((state: AppState) => state.entities.app.mode);
|
||||
|
||||
|
|
@ -296,16 +374,9 @@ function ToggleCommentModeButton({
|
|||
}, [proceedToNextTourStep, setShowCommentButtonDiscoveryTooltipInState]);
|
||||
|
||||
// Show comment mode button only on the canvas editor and viewer
|
||||
const [shouldHide, setShouldHide] = useState(false);
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
const pathName = window.location.pathname;
|
||||
const shouldShow = matchBuilderPath(pathName) || matchViewerPath(pathName);
|
||||
setShouldHide(!shouldShow);
|
||||
}, [location]);
|
||||
if (shouldHide) return null;
|
||||
const shouldHide = useShouldHide();
|
||||
|
||||
if (!commentsEnabled) return null;
|
||||
if (!commentsEnabled || shouldHide) return null;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
|
|
@ -320,13 +391,13 @@ function ToggleCommentModeButton({
|
|||
<ViewOrEditMode mode={mode} />
|
||||
</ModeButton>
|
||||
<TooltipComponent
|
||||
content={createMessage(ONE_UNREAD_MESSAGE)}
|
||||
content={createMessage(UNREAD_MESSAGE)}
|
||||
isOpen={showCommentButtonDiscoveryTooltip}
|
||||
>
|
||||
<CommentModeBtn
|
||||
{...{
|
||||
handleSetCommentModeButton,
|
||||
isCommentMode,
|
||||
isCommentMode: isCommentMode || isTourStepActive, // Highlight the button during the tour
|
||||
showUnreadIndicator,
|
||||
showSelectedMode,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,16 @@ const handleNewCommentThreadEvent = (
|
|||
|
||||
const showUnreadIndicator = !state.isCommentMode;
|
||||
|
||||
/**
|
||||
* Private threads are a part of the comments onboarding
|
||||
* These are termed bot threads, triggered from the backend based on the onboarding flow
|
||||
* We set these as visible and hide the currently visible thread, following the create event
|
||||
*/
|
||||
|
||||
if (thread.isPrivate) {
|
||||
state.visibleCommentThreadId = thread._id;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
showUnreadIndicator,
|
||||
|
|
|
|||
|
|
@ -60,11 +60,12 @@ export function* fetchOrgSaga(action: ReduxAction<FetchOrgRequest>) {
|
|||
try {
|
||||
const request: FetchOrgRequest = action.payload;
|
||||
const response: FetchOrgResponse = yield call(OrgApi.fetchOrg, request);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
const isValidResponse = yield request.skipValidation ||
|
||||
validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_ORG_SUCCESS,
|
||||
payload: response.data,
|
||||
payload: response.data || {},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -358,3 +358,5 @@ export const getAllPageWidgets = createSelector(
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const getAppMode = (state: AppState) => state.entities.app.mode;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { fetchOrg } from "actions/orgActions";
|
||||
import { useEffect } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getCurrentUser } from "selectors/usersSelectors";
|
||||
|
||||
import { getCurrentAppOrg } from "selectors/organizationSelectors";
|
||||
import { ANONYMOUS_USERNAME } from "constants/userConstants";
|
||||
|
||||
const useOrg = (orgId: string) => {
|
||||
const dispatch = useDispatch();
|
||||
const org = useSelector(getCurrentAppOrg);
|
||||
const currentUser = useSelector(getCurrentUser);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser || currentUser.username === ANONYMOUS_USERNAME) return;
|
||||
|
||||
if ((!org || !org.userPermissions) && orgId) {
|
||||
dispatch(fetchOrg(orgId, true));
|
||||
}
|
||||
}, [orgId, currentUser && currentUser.username]);
|
||||
|
||||
return org;
|
||||
};
|
||||
|
||||
export default useOrg;
|
||||
|
|
@ -4,17 +4,28 @@ import { AppState } from "reducers";
|
|||
import { getActiveTourIndex, getActiveTourType } from "selectors/tourSelectors";
|
||||
import { proceedToNextTourStep } from "actions/tourActions";
|
||||
|
||||
const useProceedToNextTourStep = (tourType: TourType, tourIndex: number) => {
|
||||
const dispatch = useDispatch();
|
||||
export const useIsTourStepActive = (
|
||||
activeTourStepConfig: { [key in TourType]?: any },
|
||||
) => {
|
||||
const activeTourType = useSelector(getActiveTourType);
|
||||
const expectedActiveStep =
|
||||
activeTourType &&
|
||||
activeTourStepConfig &&
|
||||
activeTourStepConfig[activeTourType];
|
||||
|
||||
const isCurrentStepActive = useSelector(
|
||||
(state: AppState) => getActiveTourIndex(state) === tourIndex,
|
||||
);
|
||||
const isCurrentTourActive = useSelector(
|
||||
(state: AppState) => getActiveTourType(state) === tourType,
|
||||
(state: AppState) => getActiveTourIndex(state) === expectedActiveStep,
|
||||
);
|
||||
|
||||
const isActive = isCurrentStepActive && isCurrentTourActive;
|
||||
return isCurrentStepActive;
|
||||
};
|
||||
|
||||
const useProceedToNextTourStep = (
|
||||
activeTourStepConfig: { [key in TourType]?: any },
|
||||
) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isActive = useIsTourStepActive(activeTourStepConfig);
|
||||
|
||||
return () => isActive && dispatch(proceedToNextTourStep());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -276,7 +276,10 @@ abstract class BaseWidget<
|
|||
*/
|
||||
addOverlayComments(content: ReactNode) {
|
||||
return (
|
||||
<OverlayCommentsWrapper refId={this.props.widgetId}>
|
||||
<OverlayCommentsWrapper
|
||||
refId={this.props.widgetId}
|
||||
widgetType={this.props.type}
|
||||
>
|
||||
{content}
|
||||
</OverlayCommentsWrapper>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue