feat(core): check user's subscription at ai onboarding stage (#6608)

This commit is contained in:
CatsJuice
2024-04-18 11:28:06 +00:00
parent e232b0b285
commit 9cb6dcd93d
3 changed files with 126 additions and 25 deletions

View File

@@ -47,15 +47,19 @@ export const video = style({
height: 'calc(100% + 4px)', height: 'calc(100% + 4px)',
}); });
export const mainContent = style({
display: 'flex',
flexDirection: 'column',
gap: 4,
padding: '20px 24px 0px 24px',
});
export const title = style({ export const title = style({
padding: '20px 24px 8px 24px',
fontSize: cssVar('fontH6'), fontSize: cssVar('fontH6'),
fontWeight: 600, fontWeight: 600,
lineHeight: '26px', lineHeight: '26px',
color: cssVar('textPrimaryColor'), color: cssVar('textPrimaryColor'),
}); });
export const description = style({ export const description = style({
padding: '0px 24px',
fontSize: cssVar('fontBase'), fontSize: cssVar('fontBase'),
lineHeight: '24px', lineHeight: '24px',
minHeight: 48, minHeight: 48,
@@ -66,14 +70,45 @@ export const link = style({
color: cssVar('textEmphasisColor'), color: cssVar('textEmphasisColor'),
textDecoration: 'underline', textDecoration: 'underline',
}); });
export const privacy = style({
padding: '20px 24px 0px 24px',
color: cssVar('textSecondaryColor'),
fontSize: cssVar('fontXs'),
fontWeight: 400,
lineHeight: '20px',
height: 44,
transition: 'all 0.3s',
overflow: 'hidden',
selectors: {
'&[aria-hidden="true"]': {
paddingTop: 0,
height: 0,
},
},
});
export const privacyLink = style({
color: 'inherit',
textDecoration: 'underline',
});
export const footer = style({ export const footer = style({
width: '100%',
padding: '20px 28px', padding: '20px 28px',
gap: 12, gap: 12,
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'space-between',
selectors: {
'&[data-is-last="true"], &[data-is-first="true"]': {
justifyContent: 'flex-end',
},
},
}); });
export const baseActionButton = style({
export const skipButton = style({ fontSize: cssVar('fontBase'),
fontWeight: 500, selectors: {
'&.large': {
fontWeight: 500,
},
},
}); });

View File

@@ -1,9 +1,11 @@
import { Button, Modal } from '@affine/component'; import { Button, Modal } from '@affine/component';
import { openSettingModalAtom } from '@affine/core/atoms'; import { openSettingModalAtom } from '@affine/core/atoms';
import { useBlurRoot } from '@affine/core/hooks/use-blur-root'; import { useBlurRoot } from '@affine/core/hooks/use-blur-root';
import { SubscriptionService } from '@affine/core/modules/cloud';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans } from '@affine/i18n'; import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowLeftSmallIcon } from '@blocksuite/icons';
import { import {
useLiveData, useLiveData,
useServices, useServices,
@@ -68,7 +70,10 @@ const getPlayList = (t: Translate): Array<PlayListItem> => [
export const AIOnboardingGeneral = ({ export const AIOnboardingGeneral = ({
onDismiss, onDismiss,
}: BaseAIOnboardingDialogProps) => { }: BaseAIOnboardingDialogProps) => {
const { workspaceService } = useServices({ WorkspaceService }); const { workspaceService, subscriptionService } = useServices({
WorkspaceService,
SubscriptionService,
});
const videoWrapperRef = useRef<HTMLDivElement | null>(null); const videoWrapperRef = useRef<HTMLDivElement | null>(null);
const prevVideoRef = useRef<HTMLVideoElement | null>(null); const prevVideoRef = useRef<HTMLVideoElement | null>(null);
@@ -76,6 +81,7 @@ export const AIOnboardingGeneral = ({
workspaceService.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD; workspaceService.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const open = useLiveData(showAIOnboardingGeneral$); const open = useLiveData(showAIOnboardingGeneral$);
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const list = useMemo(() => getPlayList(t), [t]); const list = useMemo(() => getPlayList(t), [t]);
const setSettingModal = useSetAtom(openSettingModalAtom); const setSettingModal = useSetAtom(openSettingModalAtom);
@@ -96,7 +102,6 @@ export const AIOnboardingGeneral = ({
}); });
closeAndDismiss(); closeAndDismiss();
}, [closeAndDismiss, setSettingModal]); }, [closeAndDismiss, setSettingModal]);
const onClose = useCallback(() => showAIOnboardingGeneral$.next(false), []);
const onPrev = useCallback(() => { const onPrev = useCallback(() => {
setIndex(i => Math.max(0, i - 1)); setIndex(i => Math.max(0, i - 1));
}, []); }, []);
@@ -104,6 +109,10 @@ export const AIOnboardingGeneral = ({
setIndex(i => Math.min(list.length - 1, i + 1)); setIndex(i => Math.min(list.length - 1, i + 1));
}, [list.length]); }, [list.length]);
useEffect(() => {
subscriptionService.subscription.revalidate();
}, [subscriptionService]);
const videoRenderer = useCallback( const videoRenderer = useCallback(
({ video }: PlayListItem, index: number) => ( ({ video }: PlayListItem, index: number) => (
<div className={styles.videoWrapper}> <div className={styles.videoWrapper}>
@@ -152,7 +161,10 @@ export const AIOnboardingGeneral = ({
return isCloud ? ( return isCloud ? (
<Modal <Modal
open={open} open={open}
onOpenChange={v => showAIOnboardingGeneral$.next(v)} onOpenChange={v => {
showAIOnboardingGeneral$.next(v);
if (!v && isLast) onDismiss();
}}
contentOptions={{ className: styles.dialog }} contentOptions={{ className: styles.dialog }}
overlayOptions={{ className: baseStyles.dialogOverlay }} overlayOptions={{ className: baseStyles.dialogOverlay }}
> >
@@ -166,7 +178,7 @@ export const AIOnboardingGeneral = ({
itemRenderer={videoRenderer} itemRenderer={videoRenderer}
/> />
<main> <main className={styles.mainContent}>
<Slider<PlayListItem> <Slider<PlayListItem>
items={list} items={list}
activeIndex={index} activeIndex={index}
@@ -181,28 +193,76 @@ export const AIOnboardingGeneral = ({
/> />
</main> </main>
<footer className={styles.footer}> <section
className={styles.privacy}
aria-hidden={!isLast || !!aiSubscription}
>
<Trans
i18nKey="com.affine.ai-onboarding.general.privacy"
components={{
a: (
<a
className={styles.privacyLink}
href="https://ai.affine.pro"
/>
),
}}
/>
</section>
<footer
className={styles.footer}
data-is-last={isLast}
data-is-first={isFirst}
>
{isLast ? ( {isLast ? (
<> aiSubscription ? (
<Button onClick={closeAndDismiss}> <Button
{t['com.affine.ai-onboarding.general.try-for-free']()} className={styles.baseActionButton}
size="large"
onClick={closeAndDismiss}
type="primary"
>
{t['com.affine.ai-onboarding.general.get-started']()}
</Button> </Button>
<Button onClick={goToPricingPlans} type="primary"> ) : (
{t['com.affine.ai-onboarding.general.purchase']()} <>
</Button> <Button
</> className={styles.baseActionButton}
size="large"
onClick={closeAndDismiss}
>
{t['com.affine.ai-onboarding.general.try-for-free']()}
</Button>
<Button
className={styles.baseActionButton}
size="large"
onClick={goToPricingPlans}
type="primary"
>
{t['com.affine.ai-onboarding.general.purchase']()}
</Button>
</>
)
) : ( ) : (
<> <>
{isFirst ? ( {isFirst ? null : (
<Button onClick={onClose} className={styles.skipButton}> <Button
{t['com.affine.ai-onboarding.general.skip']()} icon={<ArrowLeftSmallIcon />}
</Button> className={styles.baseActionButton}
) : ( onClick={onPrev}
<Button onClick={onPrev}> type="plain"
size="large"
>
{t['com.affine.ai-onboarding.general.prev']()} {t['com.affine.ai-onboarding.general.prev']()}
</Button> </Button>
)} )}
<Button type="primary" onClick={onNext}> <Button
className={styles.baseActionButton}
size="large"
type="primary"
onClick={onNext}
>
{t['com.affine.ai-onboarding.general.next']()} {t['com.affine.ai-onboarding.general.next']()}
</Button> </Button>
</> </>

View File

@@ -1297,6 +1297,12 @@
"com.affine.ai-onboarding.general.prev": "Back", "com.affine.ai-onboarding.general.prev": "Back",
"com.affine.ai-onboarding.general.try-for-free": "Try for Free", "com.affine.ai-onboarding.general.try-for-free": "Try for Free",
"com.affine.ai-onboarding.general.purchase": "Get Unlimited Usage", "com.affine.ai-onboarding.general.purchase": "Get Unlimited Usage",
"com.affine.ai-onboarding.general.privacy": "By continuing, you should agree our <a>Terms of Services</a>.",
"com.affine.ai-onboarding.general.get-started": "Get Started",
"com.affine.ai-onboarding.local.title": "Meet AFFiNE AI",
"com.affine.ai-onboarding.local.message": "Lets you think bigger, create faster, work smarter and save time for every project.",
"com.affine.ai-onboarding.local.action-dismiss": "Dismiss",
"com.affine.ai-onboarding.local.action-learn-more": "Learn More",
"com.affine.ai-onboarding.edgeless.title": "Meet AFFiNE AI", "com.affine.ai-onboarding.edgeless.title": "Meet AFFiNE AI",
"com.affine.ai-onboarding.edgeless.message": "Lets you think bigger, create faster, work smarter and save time for every project." "com.affine.ai-onboarding.edgeless.message": "Lets you think bigger, create faster, work smarter and save time for every project."
} }