feat(core): auto scroll to current payment plan (#4714)

This commit is contained in:
Cats Juice
2023-10-25 10:58:19 +08:00
committed by GitHub
parent 3749125907
commit e8a88da9e4
3 changed files with 91 additions and 26 deletions

View File

@@ -0,0 +1,19 @@
export function BulledListIcon({ color = 'currentColor' }: { color: string }) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<ellipse
cx="4.00008"
cy="7.99984"
rx="1.33333"
ry="1.33333"
fill={color}
/>
</svg>
);
}

View File

@@ -9,6 +9,7 @@ import {
updateSubscriptionMutation,
} from '@affine/graphql';
import { useMutation, useQuery } from '@affine/workspace/affine/gql';
import { DoneIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import {
type PropsWithChildren,
@@ -24,6 +25,7 @@ import {
type SubscriptionMutator,
useUserSubscription,
} from '../../../../../hooks/use-subscription';
import { BulledListIcon } from './icons/bulled-list';
import * as styles from './style.css';
interface FixedPrice {
@@ -108,6 +110,7 @@ const planDetail = new Map<SubscriptionPlan, FixedPrice | DynamicPrice>([
const Settings = () => {
const [subscription, mutateSubscription] = useUserSubscription();
const loggedIn = useCurrentLoginStatus() === 'authenticated';
const scrollWrapper = useRef<HTMLDivElement>(null);
const {
data: { prices },
@@ -139,6 +142,32 @@ const Settings = () => {
planDetail.get(SubscriptionPlan.Pro) as FixedPrice | undefined
)?.discount;
// auto scroll to current plan card
useEffect(() => {
if (!scrollWrapper.current) return;
const currentPlanCard = scrollWrapper.current?.querySelector(
'[data-current="true"]'
);
const wrapperComputedStyle = getComputedStyle(scrollWrapper.current);
const left = currentPlanCard
? currentPlanCard.getBoundingClientRect().left -
scrollWrapper.current.getBoundingClientRect().left -
parseInt(wrapperComputedStyle.paddingLeft)
: 0;
const appeared =
scrollWrapper.current.getAttribute('data-appeared') === 'true';
const animationFrameId = requestAnimationFrame(() => {
scrollWrapper.current?.scrollTo({
behavior: appeared ? 'smooth' : 'instant',
left,
});
scrollWrapper.current?.setAttribute('data-appeared', 'true');
});
return () => {
cancelAnimationFrame(animationFrameId);
};
}, [recurring]);
return (
<>
<SettingHeader
@@ -175,18 +204,19 @@ const Settings = () => {
</RadioButton>
))}
</RadioButtonGroup>
{/* TODO: plan cards horizontal scroll behavior is not the same as design */}
{/* TODO: may scroll current plan into view when first loading? */}
<div className={styles.planCardsWrapper}>
<div className={styles.planCardsWrapper} ref={scrollWrapper}>
{Array.from(planDetail.values()).map(detail => {
const isCurrent =
loggedIn &&
detail.plan === currentPlan &&
(currentPlan === SubscriptionPlan.Free
? true
: currentRecurring === recurring);
return (
<div
data-current={isCurrent}
key={detail.plan}
className={
loggedIn && currentPlan === detail.plan
? styles.currentPlanCard
: styles.planCard
}
className={isCurrent ? styles.currentPlanCard : styles.planCard}
>
<div className={styles.planTitle}>
<p>
@@ -198,23 +228,27 @@ const Settings = () => {
</span>
)}
</p>
<p>
{detail.type === 'dynamic' ? (
<span className={styles.planPriceDesc}>
Coming soon...
</span>
) : (
<>
<span className={styles.planPrice}>
$
{recurring === SubscriptionRecurring.Monthly
? detail.price
: detail.yearlyPrice}
<div className={styles.planPriceWrapper}>
<p>
{detail.type === 'dynamic' ? (
<span className={styles.planPriceDesc}>
Coming soon...
</span>
<span className={styles.planPriceDesc}>per month</span>
</>
)}
</p>
) : (
<>
<span className={styles.planPrice}>
$
{recurring === SubscriptionRecurring.Monthly
? detail.price
: detail.yearlyPrice}
</span>
<span className={styles.planPriceDesc}>
per month
</span>
</>
)}
</p>
</div>
{
// branches:
// if contact => 'Contact Sales'
@@ -264,8 +298,11 @@ const Settings = () => {
{detail.benefits.map((content, i) => (
<div key={i} className={styles.planBenefit}>
<div className={styles.planBenefitIcon}>
{/* TODO: icons */}
{detail.type == 'dynamic' ? '·' : '✅'}
{detail.type == 'dynamic' ? (
<BulledListIcon color="var(--affine-primary-color)" />
) : (
<DoneIcon color="var(--affine-primary-color)" />
)}
</div>
{content}
</div>

View File

@@ -22,6 +22,8 @@ export const planCardsWrapper = style({
display: 'flex',
overflowX: 'auto',
scrollSnapType: 'x mandatory',
// TODO: should display the horizontal scrollbar, ensure the box-shadow is not clipped
paddingBottom: '21px',
});
export const planCard = style({
@@ -75,6 +77,13 @@ export const planTitle = style({
fontWeight: 600,
});
export const planPriceWrapper = style({
minHeight: '28px',
lineHeight: 1,
display: 'flex',
alignItems: 'flex-end',
});
export const planPrice = style({
fontSize: 'var(--affine-font-h-5)',
marginRight: '8px',