mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat(core): auto scroll to current payment plan (#4714)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user