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

feat: add onboarding for client #2144

Merged
merged 24 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
080a408
feat: init tourModal
JimmFly Apr 25, 2023
3257006
feat: init tourModal component
JimmFly Apr 26, 2023
2ce35f6
Merge branch 'master' into feat/add-onboarding
JimmFly Apr 27, 2023
0725616
merge master
JimmFly Apr 28, 2023
41dc359
Merge branch 'master' into feat/add-onboarding
JimmFly Apr 28, 2023
140852c
feat: add onboarding modal to modal provider
JimmFly Apr 28, 2023
5c72563
Merge branch 'master' into feat/add-onboarding
JimmFly Apr 28, 2023
550bf43
feat: add first render popup
JimmFly Apr 28, 2023
6f10a4f
chore: update unit test
JimmFly Apr 28, 2023
c341489
Merge branch 'master' into feat/add-onboarding
JimmFly Apr 28, 2023
53ceb48
Merge branch 'master' into feat/add-onboarding
JimmFly Apr 28, 2023
28c3985
fix: zindex error
JimmFly Apr 28, 2023
099ccf4
Merge branch 'master' into feat/add-onboarding
himself65 Apr 28, 2023
ab0ebdc
Merge branch 'master' into feat/add-onboarding
JimmFly May 4, 2023
9bcf6e0
feat: add onboardingModal to helpIsland
JimmFly May 4, 2023
5a6bee3
fix: disable onboarding button in web
JimmFly May 4, 2023
e201621
feat: add onboardingModal to storybook
JimmFly May 4, 2023
f5676f7
Merge branch 'master' into feat/add-onboarding
JimmFly May 4, 2023
27b0431
test: add onboarding modal test
JimmFly May 4, 2023
04826ba
Merge branch 'master' into feat/add-onboarding
JimmFly May 4, 2023
b43ecbd
chore: remove unused import
JimmFly May 4, 2023
d8d60bf
chore: remove translation
JimmFly May 4, 2023
7cd2852
chore: remove delay
JimmFly May 4, 2023
10ecd92
chore: move env check to modal provider
JimmFly May 4, 2023
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
21 changes: 21 additions & 0 deletions apps/electron/tests/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ test.beforeEach(async () => {
colorScheme: 'light',
});
page = await electronApp.firstWindow();
await page.getByTestId('onboarding-modal-close-button').click({
delay: 100,
JimmFly marked this conversation as resolved.
Show resolved Hide resolved
});
// cleanup page data
await page.evaluate(() => localStorage.clear());
});
Expand Down Expand Up @@ -76,3 +79,21 @@ test('affine cloud disabled', async () => {
state: 'visible',
});
});
test('affine onboarding button', async () => {
await page.getByTestId('help-island').click();
await page.getByTestId('easy-guide').click();
const onboardingModal = page.locator('[data-testid=onboarding-modal]');
expect(await onboardingModal.isVisible()).toEqual(true);
const switchVideo = page.locator(
'[data-testid=onboarding-modal-switch-video]'
);
expect(await switchVideo.isVisible()).toEqual(true);
await page.getByTestId('onboarding-modal-next-button').click();
const editingVideo = page.locator(
'[data-testid=onboarding-modal-editing-video]'
);
expect(await editingVideo.isVisible()).toEqual(true);
await page.getByTestId('onboarding-modal-ok-button').click();

expect(await onboardingModal.isVisible()).toEqual(false);
});
Binary file added apps/web/public/editingVideo.mp4
Binary file not shown.
Binary file added apps/web/public/switchVideo.mp4
Binary file not shown.
15 changes: 15 additions & 0 deletions apps/web/src/atoms/guide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,18 @@ export const guideChangeLogAtom = atom<
}));
}
);
export const guideOnboardingAtom = atom<
Guide['onBoarding'],
[open: boolean],
void
>(
get => {
return get(guidePrimitiveAtom).onBoarding;
},
(_, set, open) => {
set(guidePrimitiveAtom, tips => ({
...tips,
onBoarding: open,
}));
}
);
1 change: 1 addition & 0 deletions apps/web/src/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const currentEditorAtom = rootCurrentEditorAtom;
export const openWorkspacesModalAtom = atom(false);
export const openCreateWorkspaceModalAtom = atom(false);
export const openQuickSearchModalAtom = atom(false);
export const openOnboardingModalAtom = atom(false);

export const openDisableCloudAlertModalAtom = atom(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const CommonMenu = () => {
<Menu
width={276}
content={content}
// placement="bottom-end"
placement="bottom"
disablePortal={true}
trigger="click"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const StyledHeaderContainer = styled('div')<{
top: 0,
background: 'var(--affine-background-primary-color)',
WebkitAppRegion: 'drag',
zIndex: 1,
zIndex: 'var(--affine-z-index-popover)',
'@media (max-width: 768px)': {
'&[data-open="true"]': {
WebkitAppRegion: 'no-drag',
Expand Down
45 changes: 45 additions & 0 deletions apps/web/src/components/pure/OnboardingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { TourModal } from '@affine/component/tour-modal';
import { useAtom } from 'jotai';
import { useCallback, useEffect, useMemo } from 'react';

import { openOnboardingModalAtom } from '../../atoms';
import { guideOnboardingAtom } from '../../atoms/guide';

type OnboardingModalProps = {
onClose: () => void;
open: boolean;
};

const getHelperGuide = (): { onBoarding: boolean } | null => {
const helperGuide = localStorage.getItem('helper-guide');
if (helperGuide) {
return JSON.parse(helperGuide);
}
return null;
};

export const OnboardingModal: React.FC<OnboardingModalProps> = ({
open,
onClose,
}) => {
const [, setShowOnboarding] = useAtom(guideOnboardingAtom);
const [, setOpenOnboarding] = useAtom(openOnboardingModalAtom);
const onCloseTourModal = useCallback(() => {
setShowOnboarding(false);
onClose();
}, [onClose, setShowOnboarding]);

const shouldShow = useMemo(() => {
const helperGuide = getHelperGuide();
return helperGuide?.onBoarding ?? true;
}, []);

useEffect(() => {
if (shouldShow) {
setOpenOnboarding(true);
}
}, [shouldShow, setOpenOnboarding]);
return <TourModal open={open} onClose={onCloseTourModal} />;
};

export default OnboardingModal;
30 changes: 26 additions & 4 deletions apps/web/src/components/pure/help-island/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { MuiFade, Tooltip } from '@affine/component';
import { getEnvironment } from '@affine/env';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon } from '@blocksuite/icons';
import { useAtom } from 'jotai';
import { lazy, Suspense, useState } from 'react';

import { openOnboardingModalAtom } from '../../../atoms';
import { ShortcutsModal } from '../shortcuts-modal';
import { ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
import {
Expand All @@ -11,19 +14,25 @@ import {
StyledIsland,
StyledTriggerWrapper,
} from './style';

const env = getEnvironment();
const ContactModal = lazy(() =>
import('@affine/component/contact-modal').then(({ ContactModal }) => ({
default: ContactModal,
}))
);

export type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts';
const DEFAULT_SHOW_LIST: IslandItemNames[] = [
'whatNew',
'contact',
'shortcuts',
];
const DESKTOP_SHOW_LIST: IslandItemNames[] = [...DEFAULT_SHOW_LIST, 'guide'];
export type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts' | 'guide';
export const HelpIsland = ({
showList = ['whatNew', 'contact', 'shortcuts'],
showList = env.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST,
}: {
showList?: IslandItemNames[];
}) => {
const [, setOpenOnboarding] = useAtom(openOnboardingModalAtom);
const [spread, setShowSpread] = useState(false);
// const { triggerShortcutsModal, triggerContactModal } = useModal();
// const blockHub = useGlobalState(store => store.blockHub);
Expand Down Expand Up @@ -98,6 +107,19 @@ export const HelpIsland = ({
</StyledIconWrapper>
</Tooltip>
)}
{showList.includes('guide') && (
<Tooltip content={'Easy Guide'} placement="left-end">
<StyledIconWrapper
data-testid="easy-guide"
onClick={() => {
setShowSpread(false);
setOpenOnboarding(true);
}}
>
<HelpIcon />
</StyledIconWrapper>
</Tooltip>
)}
</StyledAnimateWrapper>

<Tooltip content={t['Help and Feedback']()} placement="left-end">
Expand Down
24 changes: 23 additions & 1 deletion apps/web/src/providers/ModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getEnvironment } from '@affine/env';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { arrayMove } from '@dnd-kit/sortable';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
Expand All @@ -9,6 +10,7 @@ import {
currentWorkspaceIdAtom,
openCreateWorkspaceModalAtom,
openDisableCloudAlertModalAtom,
openOnboardingModalAtom,
openWorkspacesModalAtom,
} from '../atoms';
import { useAffineLogIn } from '../hooks/affine/use-affine-log-in';
Expand Down Expand Up @@ -37,6 +39,11 @@ const TmpDisableAffineCloudModal = lazy(() =>
})
)
);
const OnboardingModalAtom = lazy(() =>
import('../components/pure/OnboardingModal').then(module => ({
default: module.OnboardingModal,
}))
);

export function Modals() {
const [openWorkspacesModal, setOpenWorkspacesModal] = useAtom(
Expand All @@ -49,6 +56,9 @@ export function Modals() {
const [openDisableCloudAlertModal, setOpenDisableCloudAlertModal] = useAtom(
openDisableCloudAlertModalAtom
);
const [openOnboardingModal, setOpenOnboardingModal] = useAtom(
openOnboardingModalAtom
);

const router = useRouter();
const { jumpToSubPath } = useRouterHelper(router);
Expand All @@ -59,7 +69,10 @@ export function Modals() {
const [, setCurrentWorkspace] = useCurrentWorkspace();
const { createLocalWorkspace } = useAppHelper();
const [transitioning, transition] = useTransition();

const env = getEnvironment();
const onCloseOnboardingModal = useCallback(() => {
setOpenOnboardingModal(false);
}, [setOpenOnboardingModal]);
return (
<>
<Suspense>
Expand All @@ -70,6 +83,15 @@ export function Modals() {
}, [setOpenDisableCloudAlertModal])}
/>
</Suspense>
{env.isDesktop && (
<Suspense>
<OnboardingModalAtom
open={openOnboardingModal}
onClose={onCloseOnboardingModal}
/>
</Suspense>
)}

<Suspense>
<WorkspaceListModal
disabled={transitioning}
Expand Down
103 changes: 103 additions & 0 deletions packages/component/src/components/tour-modal/TourModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { FC } from 'react';
import { useState } from 'react';

import { Button, Modal, ModalCloseButton, ModalWrapper } from '../..';
import {
buttonContainerStyle,
buttonStyle,
modalStyle,
titleStyle,
videoContainerStyle,
videoStyle,
} from './index.css';

type TourModalProps = {
open: boolean;
onClose: () => void;
};

export const TourModal: FC<TourModalProps> = ({ open, onClose }) => {
const [step, setStep] = useState(0);
const handleClose = () => {
setStep(0);
onClose();
};
return (
<Modal
open={open}
onClose={handleClose}
wrapperPosition={['center', 'center']}
hideBackdrop
>
<ModalWrapper width={545} height={442} data-testid="onboarding-modal">
<ModalCloseButton
top={10}
right={10}
onClick={handleClose}
data-testid="onboarding-modal-close-button"
/>
{step === 0 && (
<div className={modalStyle}>
<div className={titleStyle}>Hyper merged whiteboard and docs</div>
<div className={videoContainerStyle}>
<video
autoPlay
muted
loop
className={videoStyle}
data-testid="onboarding-modal-switch-video"
>
<source src="/switchVideo.mp4" type="video/mp4" />
<source src="/switchVideo.webm" type="video/webm" />
Easily switch between Page mode for structured document creation
and Whiteboard mode for the freeform visual expression of
creative ideas.
</video>
</div>
<div className={buttonContainerStyle}>
<Button
className={buttonStyle}
onClick={() => setStep(1)}
data-testid="onboarding-modal-next-button"
>
Next Tip Please !
</Button>
</div>
</div>
)}
{step === 1 && (
<div className={modalStyle}>
<div className={titleStyle}>
Intuitive & robust block-based editing
</div>
<div className={videoContainerStyle}>
<video
autoPlay
muted
loop
className={videoStyle}
data-testid="onboarding-modal-editing-video"
>
<source src="/editingVideo.mp4" type="video/mp4" />
<source src="/editingVideo.webm" type="video/webm" />
Create structured documents with ease, using a modular interface
to drag and drop blocks of text, images, and other content.
</video>
</div>
<div className={buttonContainerStyle}>
<Button
className={buttonStyle}
onClick={handleClose}
data-testid="onboarding-modal-ok-button"
>
Okay, I Like It !
</Button>
</div>
</div>
)}
</ModalWrapper>
</Modal>
);
};

export default TourModal;
44 changes: 44 additions & 0 deletions packages/component/src/components/tour-modal/index.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { style } from '@vanilla-extract/css';

export const modalStyle = style({
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative',
padding: '12px 36px',
backgroundColor: 'var(--affine-white)',
borderRadius: '12px',
boxShadow: 'var(--affine-popover-shadow)',
});
export const titleStyle = style({
fontSize: 'var(--affine-font-h6)',
fontWeight: '600',
});
export const videoContainerStyle = style({
paddingTop: '15px',
width: '100%',
});
export const videoStyle = style({
objectFit: 'fill',
height: '300px',
width: '100%',
});
export const buttonContainerStyle = style({
marginTop: '15px',
width: '100%',
display: 'flex',
justifyContent: 'flex-end',
});
export const buttonStyle = style({
borderRadius: '8px',
backgroundColor: 'var(--affine-primary-color)',
color: 'var(--affine-white)',
height: '32px',
padding: '4 20px',
':hover': {
backgroundColor: 'var(--affine-primary-color)',
color: 'var(--affine-text-primary-color)',
},
});
1 change: 1 addition & 0 deletions packages/component/src/components/tour-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './TourModal';
Loading