mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-23 04:04:27 +08:00
feat(core): impl subscription plans setting
This commit is contained in:
+22
-1
@@ -9,6 +9,7 @@ import type { ReactElement, SVGProps } from 'react';
|
|||||||
|
|
||||||
import { AboutAffine } from './about';
|
import { AboutAffine } from './about';
|
||||||
import { AppearanceSettings } from './appearance';
|
import { AppearanceSettings } from './appearance';
|
||||||
|
import { AFFiNECloudPlans } from './plans';
|
||||||
import { Plugins } from './plugins';
|
import { Plugins } from './plugins';
|
||||||
import { Shortcuts } from './shortcuts';
|
import { Shortcuts } from './shortcuts';
|
||||||
|
|
||||||
@@ -16,7 +17,9 @@ export type GeneralSettingKeys =
|
|||||||
| 'shortcuts'
|
| 'shortcuts'
|
||||||
| 'appearance'
|
| 'appearance'
|
||||||
| 'plugins'
|
| 'plugins'
|
||||||
| 'about';
|
| 'about'
|
||||||
|
| 'plans'
|
||||||
|
| 'billing';
|
||||||
|
|
||||||
interface GeneralSettingListItem {
|
interface GeneralSettingListItem {
|
||||||
key: GeneralSettingKeys;
|
key: GeneralSettingKeys;
|
||||||
@@ -43,6 +46,22 @@ export const useGeneralSettingList = (): GeneralSettingList => {
|
|||||||
icon: KeyboardIcon,
|
icon: KeyboardIcon,
|
||||||
testId: 'shortcuts-panel-trigger',
|
testId: 'shortcuts-panel-trigger',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'plans',
|
||||||
|
// TODO: i18n
|
||||||
|
title: 'AFFiNE Cloud Plans',
|
||||||
|
// TODO: icon
|
||||||
|
icon: KeyboardIcon,
|
||||||
|
testId: 'plans-panel-trigger',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'billing',
|
||||||
|
// TODO: i18n
|
||||||
|
title: 'Billing',
|
||||||
|
// TODO: icon
|
||||||
|
icon: KeyboardIcon,
|
||||||
|
testId: 'billing-panel-trigger',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'plugins',
|
key: 'plugins',
|
||||||
title: 'Plugins',
|
title: 'Plugins',
|
||||||
@@ -72,6 +91,8 @@ export const GeneralSetting = ({ generalKey }: GeneralSettingProps) => {
|
|||||||
return <Plugins />;
|
return <Plugins />;
|
||||||
case 'about':
|
case 'about':
|
||||||
return <AboutAffine />;
|
return <AboutAffine />;
|
||||||
|
case 'plans':
|
||||||
|
return <AFFiNECloudPlans />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+417
@@ -0,0 +1,417 @@
|
|||||||
|
import { RadioButton, RadioButtonGroup } from '@affine/component';
|
||||||
|
import { SettingHeader } from '@affine/component/setting-components';
|
||||||
|
import {
|
||||||
|
cancelSubscriptionMutation,
|
||||||
|
checkoutMutation,
|
||||||
|
pricesQuery,
|
||||||
|
SubscriptionPlan,
|
||||||
|
subscriptionQuery,
|
||||||
|
SubscriptionRecurring,
|
||||||
|
updateSubscriptionMutation,
|
||||||
|
} from '@affine/graphql';
|
||||||
|
import { useMutation, useQuery } from '@affine/workspace/affine/gql';
|
||||||
|
import { Button } from '@toeverything/components/button';
|
||||||
|
import {
|
||||||
|
type PropsWithChildren,
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import * as styles from './style.css';
|
||||||
|
|
||||||
|
interface FixedPrice {
|
||||||
|
type: 'fixed';
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
price: string;
|
||||||
|
yearlyPrice: string;
|
||||||
|
discount?: string;
|
||||||
|
benefits: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DynamicPrice {
|
||||||
|
type: 'dynamic';
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
contact: boolean;
|
||||||
|
benefits: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: i18n all things
|
||||||
|
const planDetail = new Map<SubscriptionPlan, FixedPrice | DynamicPrice>([
|
||||||
|
[
|
||||||
|
SubscriptionPlan.Free,
|
||||||
|
{
|
||||||
|
type: 'fixed',
|
||||||
|
plan: SubscriptionPlan.Free,
|
||||||
|
price: '0',
|
||||||
|
yearlyPrice: '0',
|
||||||
|
benefits: [
|
||||||
|
'Unlimited local workspace',
|
||||||
|
'Unlimited login devices',
|
||||||
|
'Unlimited blocks',
|
||||||
|
'AFFiNE Cloud Storage 10GB',
|
||||||
|
'The maximum file size is 10M',
|
||||||
|
'Number of members per Workspace ≤ 3',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
SubscriptionPlan.Pro,
|
||||||
|
{
|
||||||
|
type: 'fixed',
|
||||||
|
plan: SubscriptionPlan.Pro,
|
||||||
|
price: '1',
|
||||||
|
yearlyPrice: '1',
|
||||||
|
benefits: [
|
||||||
|
'Unlimited local workspace',
|
||||||
|
'Unlimited login devices',
|
||||||
|
'Unlimited blocks',
|
||||||
|
'AFFiNE Cloud Storage 100GB',
|
||||||
|
'The maximum file size is 500M',
|
||||||
|
'Number of members per Workspace ≤ 10',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
SubscriptionPlan.Team,
|
||||||
|
{
|
||||||
|
type: 'dynamic',
|
||||||
|
plan: SubscriptionPlan.Team,
|
||||||
|
contact: true,
|
||||||
|
benefits: [
|
||||||
|
'Best team workspace for collaboration and knowledge distilling.',
|
||||||
|
'Focusing on what really matters with team project management and automation.',
|
||||||
|
'Pay for seats, fits all team size.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
SubscriptionPlan.Enterprise,
|
||||||
|
{
|
||||||
|
type: 'dynamic',
|
||||||
|
plan: SubscriptionPlan.Enterprise,
|
||||||
|
contact: true,
|
||||||
|
benefits: [
|
||||||
|
'Solutions & best practices for dedicated needs.',
|
||||||
|
'Embedable & interrogations with IT support.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const Settings = () => {
|
||||||
|
const { data, mutate } = useQuery({
|
||||||
|
query: subscriptionQuery,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { prices },
|
||||||
|
} = useQuery({
|
||||||
|
query: pricesQuery,
|
||||||
|
});
|
||||||
|
|
||||||
|
prices.forEach(price => {
|
||||||
|
const detail = planDetail.get(price.plan);
|
||||||
|
|
||||||
|
if (detail?.type === 'fixed') {
|
||||||
|
detail.price = (price.amount / 100).toFixed(2);
|
||||||
|
detail.yearlyPrice = (price.yearlyAmount / 100 / 12).toFixed(2);
|
||||||
|
detail.discount = (
|
||||||
|
(1 - price.yearlyAmount / 12 / price.amount) *
|
||||||
|
100
|
||||||
|
).toFixed(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const loggedIn = !!data.currentUser;
|
||||||
|
const subscription = data.currentUser?.subscription;
|
||||||
|
|
||||||
|
const [recurring, setRecurring] = useState<string>(
|
||||||
|
subscription?.recurring ?? SubscriptionRecurring.Monthly
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPlan = subscription?.plan ?? SubscriptionPlan.Free;
|
||||||
|
const currentRecurring =
|
||||||
|
subscription?.recurring ?? SubscriptionRecurring.Monthly;
|
||||||
|
|
||||||
|
const refresh = useCallback(() => {
|
||||||
|
mutate();
|
||||||
|
}, [mutate]);
|
||||||
|
|
||||||
|
const yearlyDiscount = (
|
||||||
|
planDetail.get(SubscriptionPlan.Pro) as FixedPrice | undefined
|
||||||
|
)?.discount;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingHeader
|
||||||
|
title="Plans"
|
||||||
|
subtitle={
|
||||||
|
// TODO: different subtitle for un-logged user
|
||||||
|
<p>
|
||||||
|
You are current on the {currentPlan} plan. If you have any
|
||||||
|
questions, please contact our{' '}
|
||||||
|
<span>{/*TODO: add action*/}customer support</span>.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<RadioButtonGroup
|
||||||
|
className={styles.recurringRadioGroup}
|
||||||
|
value={recurring}
|
||||||
|
onValueChange={setRecurring}
|
||||||
|
>
|
||||||
|
{Object.values(SubscriptionRecurring).map(plan => (
|
||||||
|
<RadioButton key={plan} value={plan}>
|
||||||
|
{plan}
|
||||||
|
{plan === SubscriptionRecurring.Yearly && yearlyDiscount && (
|
||||||
|
<span className={styles.radioButtonDiscount}>
|
||||||
|
{yearlyDiscount}% off
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</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}>
|
||||||
|
{Array.from(planDetail.values()).map(detail => {
|
||||||
|
const isCurrent =
|
||||||
|
currentPlan === detail.plan && currentRecurring === recurring;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={detail.plan}
|
||||||
|
className={
|
||||||
|
loggedIn && currentPlan === detail.plan
|
||||||
|
? styles.currentPlanCard
|
||||||
|
: styles.planCard
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={styles.planTitle}>
|
||||||
|
<p>
|
||||||
|
{detail.plan}{' '}
|
||||||
|
{'discount' in detail && (
|
||||||
|
<span className={styles.discountLabel}>
|
||||||
|
{detail.discount}% off
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className={styles.planPrice}>
|
||||||
|
$
|
||||||
|
{detail.type === 'dynamic'
|
||||||
|
? '?'
|
||||||
|
: recurring === SubscriptionRecurring.Monthly
|
||||||
|
? detail.price
|
||||||
|
: detail.yearlyPrice}
|
||||||
|
</span>
|
||||||
|
<span className={styles.planPriceDesc}>per month</span>
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
// branches:
|
||||||
|
// if contact => 'Contact Sales'
|
||||||
|
// if not signed in:
|
||||||
|
// if free => 'Sign up free'
|
||||||
|
// else => 'Buy Pro'
|
||||||
|
// else
|
||||||
|
// if isCurrent => 'Current Plan'
|
||||||
|
// else if free => 'Downgrade'
|
||||||
|
// else if currentRecurring !== recurring => 'Change to {recurring} Billing'
|
||||||
|
// else => 'Upgrade'
|
||||||
|
// TODO: should replace with components with proper actions
|
||||||
|
detail.type === 'dynamic' ? (
|
||||||
|
<ContactSales />
|
||||||
|
) : loggedIn ? (
|
||||||
|
isCurrent ? (
|
||||||
|
<CurrentPlan />
|
||||||
|
) : detail.plan === SubscriptionPlan.Free ? (
|
||||||
|
<Downgrade onActionDone={refresh} />
|
||||||
|
) : currentRecurring !== recurring ? (
|
||||||
|
<ChangeRecurring
|
||||||
|
from={currentRecurring}
|
||||||
|
to={recurring as SubscriptionRecurring}
|
||||||
|
onActionDone={refresh}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Upgrade recurring={recurring} onActionDone={refresh} />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<SignupAction>
|
||||||
|
{detail.plan === SubscriptionPlan.Free
|
||||||
|
? 'Sign up free'
|
||||||
|
: 'Buy Pro'}
|
||||||
|
</SignupAction>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={styles.planBenefits}>
|
||||||
|
{detail.benefits.map((content, i) => (
|
||||||
|
<div key={i} className={styles.planBenefit}>
|
||||||
|
<div className={styles.planBenefitIcon}>
|
||||||
|
{/* TODO: icons */}
|
||||||
|
{detail.type == 'dynamic' ? '·' : '✅'}
|
||||||
|
</div>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
className={styles.allPlansLink}
|
||||||
|
href="https://affine.pro/pricing"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
See all plans →{/* TODO: icon */}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Downgrade = ({ onActionDone }: { onActionDone: () => void }) => {
|
||||||
|
const { isMutating, trigger } = useMutation({
|
||||||
|
mutation: cancelSubscriptionMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const downgrade = useCallback(() => {
|
||||||
|
trigger(null, { onSuccess: onActionDone });
|
||||||
|
}, [trigger, onActionDone]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={styles.planAction}
|
||||||
|
type="primary"
|
||||||
|
onClick={downgrade /* TODO: poppup confirmation modal instead */}
|
||||||
|
disabled={isMutating}
|
||||||
|
loading={isMutating}
|
||||||
|
>
|
||||||
|
Downgrade
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Upgrade = ({
|
||||||
|
recurring,
|
||||||
|
onActionDone,
|
||||||
|
}: {
|
||||||
|
recurring: SubscriptionRecurring;
|
||||||
|
onActionDone: () => void;
|
||||||
|
}) => {
|
||||||
|
const { isMutating, trigger, data } = useMutation({
|
||||||
|
mutation: checkoutMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const upgrade = useCallback(() => {
|
||||||
|
trigger({ recurring });
|
||||||
|
}, [trigger, recurring]);
|
||||||
|
|
||||||
|
const newTabRef = useRef<Window | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.checkout) {
|
||||||
|
if (newTabRef.current) {
|
||||||
|
newTabRef.current.focus();
|
||||||
|
} else {
|
||||||
|
// FIXME: safari prevents from opening new tab by window api
|
||||||
|
// TODO(@xp): what if electron?
|
||||||
|
const newTab = window.open(
|
||||||
|
data.checkout,
|
||||||
|
'_blank',
|
||||||
|
'noopener noreferrer'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newTab) {
|
||||||
|
newTabRef.current = newTab;
|
||||||
|
const update = () => {
|
||||||
|
onActionDone();
|
||||||
|
};
|
||||||
|
newTab.addEventListener('close', update);
|
||||||
|
|
||||||
|
return () => newTab.removeEventListener('close', update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}, [data?.checkout, onActionDone]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={styles.planAction}
|
||||||
|
type="primary"
|
||||||
|
onClick={upgrade}
|
||||||
|
disabled={isMutating}
|
||||||
|
loading={isMutating}
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChangeRecurring = ({
|
||||||
|
from: _from /* TODO: from can be useful when showing confirmation modal */,
|
||||||
|
to,
|
||||||
|
onActionDone,
|
||||||
|
}: {
|
||||||
|
from: SubscriptionRecurring;
|
||||||
|
to: SubscriptionRecurring;
|
||||||
|
onActionDone: () => void;
|
||||||
|
}) => {
|
||||||
|
const { isMutating, trigger } = useMutation({
|
||||||
|
mutation: updateSubscriptionMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const change = useCallback(() => {
|
||||||
|
trigger({ recurring: to }, { onSuccess: onActionDone });
|
||||||
|
}, [trigger, onActionDone, to]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={styles.planAction}
|
||||||
|
type="primary"
|
||||||
|
onClick={change /* TODO: popup confirmation modal instead */}
|
||||||
|
disabled={isMutating}
|
||||||
|
loading={isMutating}
|
||||||
|
>
|
||||||
|
Change to {to} Billing
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContactSales = () => {
|
||||||
|
return (
|
||||||
|
// TODO: add action
|
||||||
|
<Button className={styles.planAction} type="primary">
|
||||||
|
Contact Sales
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CurrentPlan = () => {
|
||||||
|
return <Button className={styles.planAction}>Current Plan</Button>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SignupAction = ({ children }: PropsWithChildren) => {
|
||||||
|
// TODO: add login action
|
||||||
|
return (
|
||||||
|
<Button className={styles.planAction} type="primary">
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AFFiNECloudPlans = () => {
|
||||||
|
return (
|
||||||
|
// TODO: loading skeleton
|
||||||
|
// TODO: Error Boundary
|
||||||
|
<Suspense>
|
||||||
|
<Settings />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const wrapper = style({
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
export const recurringRadioGroup = style({
|
||||||
|
width: '256px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const radioButtonDiscount = style({
|
||||||
|
marginLeft: '4px',
|
||||||
|
color: 'var(--affine-primary-color)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planCardsWrapper = style({
|
||||||
|
marginTop: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
overflowX: 'auto',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planCard = style({
|
||||||
|
minHeight: '426px',
|
||||||
|
minWidth: '258px',
|
||||||
|
borderRadius: '16px',
|
||||||
|
padding: '20px',
|
||||||
|
border: '1px solid var(--affine-border-color)',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'&:not(:last-child)': {
|
||||||
|
marginRight: '16px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const currentPlanCard = style([
|
||||||
|
planCard,
|
||||||
|
{
|
||||||
|
borderWidth: '2px',
|
||||||
|
borderColor: 'var(--affine-primary-color)',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const discountLabel = style({
|
||||||
|
color: 'var(--affine-primary-color)',
|
||||||
|
marginLeft: '8px',
|
||||||
|
lineHeight: '20px',
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
fontWeight: 500,
|
||||||
|
padding: '0 4px',
|
||||||
|
backgroundColor: 'var(--affine-blue-50)',
|
||||||
|
borderRadius: '4px',
|
||||||
|
display: 'inline-block',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planTitle = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: '10px',
|
||||||
|
fontWeight: 600,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planPrice = style({
|
||||||
|
fontSize: 'var(--affine-font-h-5)',
|
||||||
|
marginRight: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planPriceDesc = style({
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planAction = style({
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planBenefits = style({
|
||||||
|
marginTop: '20px',
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planBenefit = style({
|
||||||
|
display: 'flex',
|
||||||
|
selectors: {
|
||||||
|
'&:not(:last-child)': {
|
||||||
|
marginBottom: '8px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const planBenefitIcon = style({
|
||||||
|
display: 'inline-block',
|
||||||
|
marginRight: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const allPlansLink = style({
|
||||||
|
display: 'block',
|
||||||
|
marginTop: '36px',
|
||||||
|
color: 'var(--affine-primary-color)',
|
||||||
|
background: 'transparent',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation cancelSubscription {
|
||||||
|
cancelSubscription {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
nextBillAt
|
||||||
|
canceledAt
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mutation checkout($recurring: SubscriptionRecurring!) {
|
||||||
|
checkout(recurring: $recurring)
|
||||||
|
}
|
||||||
@@ -79,6 +79,22 @@ query allBlobSizes {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cancelSubscriptionMutation = {
|
||||||
|
id: 'cancelSubscriptionMutation' as const,
|
||||||
|
operationName: 'cancelSubscription',
|
||||||
|
definitionName: 'cancelSubscription',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
mutation cancelSubscription {
|
||||||
|
cancelSubscription {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
nextBillAt
|
||||||
|
canceledAt
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const changeEmailMutation = {
|
export const changeEmailMutation = {
|
||||||
id: 'changeEmailMutation' as const,
|
id: 'changeEmailMutation' as const,
|
||||||
operationName: 'changeEmail',
|
operationName: 'changeEmail',
|
||||||
@@ -111,6 +127,17 @@ mutation changePassword($token: String!, $newPassword: String!) {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkoutMutation = {
|
||||||
|
id: 'checkoutMutation' as const,
|
||||||
|
operationName: 'checkout',
|
||||||
|
definitionName: 'checkout',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
mutation checkout($recurring: SubscriptionRecurring!) {
|
||||||
|
checkout(recurring: $recurring)
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const createWorkspaceMutation = {
|
export const createWorkspaceMutation = {
|
||||||
id: 'createWorkspaceMutation' as const,
|
id: 'createWorkspaceMutation' as const,
|
||||||
operationName: 'createWorkspace',
|
operationName: 'createWorkspace',
|
||||||
@@ -321,6 +348,29 @@ query getWorkspaces {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const invoicesQuery = {
|
||||||
|
id: 'invoicesQuery' as const,
|
||||||
|
operationName: 'invoices',
|
||||||
|
definitionName: 'currentUser',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
query invoices($take: Int!, $skip: Int!) {
|
||||||
|
currentUser {
|
||||||
|
invoices(take: $take, skip: $skip) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
plan
|
||||||
|
recurring
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
reason
|
||||||
|
lastPaymentError
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const leaveWorkspaceMutation = {
|
export const leaveWorkspaceMutation = {
|
||||||
id: 'leaveWorkspaceMutation' as const,
|
id: 'leaveWorkspaceMutation' as const,
|
||||||
operationName: 'leaveWorkspace',
|
operationName: 'leaveWorkspace',
|
||||||
@@ -336,6 +386,23 @@ mutation leaveWorkspace($workspaceId: String!, $workspaceName: String!, $sendLea
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const pricesQuery = {
|
||||||
|
id: 'pricesQuery' as const,
|
||||||
|
operationName: 'prices',
|
||||||
|
definitionName: 'prices',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
query prices {
|
||||||
|
prices {
|
||||||
|
type
|
||||||
|
plan
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
yearlyAmount
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const removeAvatarMutation = {
|
export const removeAvatarMutation = {
|
||||||
id: 'removeAvatarMutation' as const,
|
id: 'removeAvatarMutation' as const,
|
||||||
operationName: 'removeAvatar',
|
operationName: 'removeAvatar',
|
||||||
@@ -469,6 +536,44 @@ mutation signUp($name: String!, $email: String!, $password: String!) {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const subscriptionQuery = {
|
||||||
|
id: 'subscriptionQuery' as const,
|
||||||
|
operationName: 'subscription',
|
||||||
|
definitionName: 'currentUser',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
query subscription {
|
||||||
|
currentUser {
|
||||||
|
subscription {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
plan
|
||||||
|
recurring
|
||||||
|
start
|
||||||
|
end
|
||||||
|
nextBillAt
|
||||||
|
canceledAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateSubscriptionMutation = {
|
||||||
|
id: 'updateSubscriptionMutation' as const,
|
||||||
|
operationName: 'updateSubscription',
|
||||||
|
definitionName: 'updateSubscriptionRecurring',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
mutation updateSubscription($recurring: SubscriptionRecurring!) {
|
||||||
|
updateSubscriptionRecurring(recurring: $recurring) {
|
||||||
|
id
|
||||||
|
plan
|
||||||
|
recurring
|
||||||
|
nextBillAt
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const uploadAvatarMutation = {
|
export const uploadAvatarMutation = {
|
||||||
id: 'uploadAvatarMutation' as const,
|
id: 'uploadAvatarMutation' as const,
|
||||||
operationName: 'uploadAvatar',
|
operationName: 'uploadAvatar',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
query invoices($take: Int!, $skip: Int!) {
|
||||||
|
currentUser {
|
||||||
|
invoices(take: $take, skip: $skip) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
plan
|
||||||
|
recurring
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
reason
|
||||||
|
lastPaymentError
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
query prices {
|
||||||
|
prices {
|
||||||
|
type
|
||||||
|
plan
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
yearlyAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
query subscription {
|
||||||
|
currentUser {
|
||||||
|
subscription {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
plan
|
||||||
|
recurring
|
||||||
|
start
|
||||||
|
end
|
||||||
|
nextBillAt
|
||||||
|
canceledAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation updateSubscription($recurring: SubscriptionRecurring!) {
|
||||||
|
updateSubscriptionRecurring(recurring: $recurring) {
|
||||||
|
id
|
||||||
|
plan
|
||||||
|
recurring
|
||||||
|
nextBillAt
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -130,6 +130,21 @@ export type AllBlobSizesQuery = {
|
|||||||
collectAllBlobSizes: { __typename?: 'WorkspaceBlobSizes'; size: number };
|
collectAllBlobSizes: { __typename?: 'WorkspaceBlobSizes'; size: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CancelSubscriptionMutationVariables = Exact<{
|
||||||
|
[key: string]: never;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type CancelSubscriptionMutation = {
|
||||||
|
__typename?: 'Mutation';
|
||||||
|
cancelSubscription: {
|
||||||
|
__typename?: 'UserSubscription';
|
||||||
|
id: string;
|
||||||
|
status: SubscriptionStatus;
|
||||||
|
nextBillAt: string | null;
|
||||||
|
canceledAt: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type ChangeEmailMutationVariables = Exact<{
|
export type ChangeEmailMutationVariables = Exact<{
|
||||||
token: Scalars['String']['input'];
|
token: Scalars['String']['input'];
|
||||||
}>;
|
}>;
|
||||||
@@ -161,6 +176,12 @@ export type ChangePasswordMutation = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CheckoutMutationVariables = Exact<{
|
||||||
|
recurring: SubscriptionRecurring;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type CheckoutMutation = { __typename?: 'Mutation'; checkout: string };
|
||||||
|
|
||||||
export type CreateWorkspaceMutationVariables = Exact<{
|
export type CreateWorkspaceMutationVariables = Exact<{
|
||||||
init: Scalars['Upload']['input'];
|
init: Scalars['Upload']['input'];
|
||||||
}>;
|
}>;
|
||||||
@@ -328,6 +349,30 @@ export type GetWorkspacesQuery = {
|
|||||||
workspaces: Array<{ __typename?: 'WorkspaceType'; id: string }>;
|
workspaces: Array<{ __typename?: 'WorkspaceType'; id: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InvoicesQueryVariables = Exact<{
|
||||||
|
take: Scalars['Int']['input'];
|
||||||
|
skip: Scalars['Int']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type InvoicesQuery = {
|
||||||
|
__typename?: 'Query';
|
||||||
|
currentUser: {
|
||||||
|
__typename?: 'UserType';
|
||||||
|
invoices: Array<{
|
||||||
|
__typename?: 'UserInvoice';
|
||||||
|
id: string;
|
||||||
|
status: InvoiceStatus;
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
recurring: SubscriptionRecurring;
|
||||||
|
currency: string;
|
||||||
|
amount: number;
|
||||||
|
reason: string;
|
||||||
|
lastPaymentError: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}>;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type LeaveWorkspaceMutationVariables = Exact<{
|
export type LeaveWorkspaceMutationVariables = Exact<{
|
||||||
workspaceId: Scalars['String']['input'];
|
workspaceId: Scalars['String']['input'];
|
||||||
workspaceName: Scalars['String']['input'];
|
workspaceName: Scalars['String']['input'];
|
||||||
@@ -339,6 +384,20 @@ export type LeaveWorkspaceMutation = {
|
|||||||
leaveWorkspace: boolean;
|
leaveWorkspace: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PricesQueryVariables = Exact<{ [key: string]: never }>;
|
||||||
|
|
||||||
|
export type PricesQuery = {
|
||||||
|
__typename?: 'Query';
|
||||||
|
prices: Array<{
|
||||||
|
__typename?: 'SubscriptionPrice';
|
||||||
|
type: string;
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
currency: string;
|
||||||
|
amount: number;
|
||||||
|
yearlyAmount: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
export type RemoveAvatarMutationVariables = Exact<{ [key: string]: never }>;
|
export type RemoveAvatarMutationVariables = Exact<{ [key: string]: never }>;
|
||||||
|
|
||||||
export type RemoveAvatarMutation = {
|
export type RemoveAvatarMutation = {
|
||||||
@@ -451,6 +510,41 @@ export type SignUpMutation = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SubscriptionQueryVariables = Exact<{ [key: string]: never }>;
|
||||||
|
|
||||||
|
export type SubscriptionQuery = {
|
||||||
|
__typename?: 'Query';
|
||||||
|
currentUser: {
|
||||||
|
__typename?: 'UserType';
|
||||||
|
subscription: {
|
||||||
|
__typename?: 'UserSubscription';
|
||||||
|
id: string;
|
||||||
|
status: SubscriptionStatus;
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
recurring: SubscriptionRecurring;
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
nextBillAt: string | null;
|
||||||
|
canceledAt: string | null;
|
||||||
|
} | null;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateSubscriptionMutationVariables = Exact<{
|
||||||
|
recurring: SubscriptionRecurring;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type UpdateSubscriptionMutation = {
|
||||||
|
__typename?: 'Mutation';
|
||||||
|
updateSubscriptionRecurring: {
|
||||||
|
__typename?: 'UserSubscription';
|
||||||
|
id: string;
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
recurring: SubscriptionRecurring;
|
||||||
|
nextBillAt: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type UploadAvatarMutationVariables = Exact<{
|
export type UploadAvatarMutationVariables = Exact<{
|
||||||
avatar: Scalars['Upload']['input'];
|
avatar: Scalars['Upload']['input'];
|
||||||
}>;
|
}>;
|
||||||
@@ -570,6 +664,21 @@ export type Queries =
|
|||||||
name: 'getWorkspacesQuery';
|
name: 'getWorkspacesQuery';
|
||||||
variables: GetWorkspacesQueryVariables;
|
variables: GetWorkspacesQueryVariables;
|
||||||
response: GetWorkspacesQuery;
|
response: GetWorkspacesQuery;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: 'invoicesQuery';
|
||||||
|
variables: InvoicesQueryVariables;
|
||||||
|
response: InvoicesQuery;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: 'pricesQuery';
|
||||||
|
variables: PricesQueryVariables;
|
||||||
|
response: PricesQuery;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: 'subscriptionQuery';
|
||||||
|
variables: SubscriptionQueryVariables;
|
||||||
|
response: SubscriptionQuery;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Mutations =
|
export type Mutations =
|
||||||
@@ -583,6 +692,11 @@ export type Mutations =
|
|||||||
variables: SetBlobMutationVariables;
|
variables: SetBlobMutationVariables;
|
||||||
response: SetBlobMutation;
|
response: SetBlobMutation;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'cancelSubscriptionMutation';
|
||||||
|
variables: CancelSubscriptionMutationVariables;
|
||||||
|
response: CancelSubscriptionMutation;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'changeEmailMutation';
|
name: 'changeEmailMutation';
|
||||||
variables: ChangeEmailMutationVariables;
|
variables: ChangeEmailMutationVariables;
|
||||||
@@ -593,6 +707,11 @@ export type Mutations =
|
|||||||
variables: ChangePasswordMutationVariables;
|
variables: ChangePasswordMutationVariables;
|
||||||
response: ChangePasswordMutation;
|
response: ChangePasswordMutation;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'checkoutMutation';
|
||||||
|
variables: CheckoutMutationVariables;
|
||||||
|
response: CheckoutMutation;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'createWorkspaceMutation';
|
name: 'createWorkspaceMutation';
|
||||||
variables: CreateWorkspaceMutationVariables;
|
variables: CreateWorkspaceMutationVariables;
|
||||||
@@ -668,6 +787,11 @@ export type Mutations =
|
|||||||
variables: SignUpMutationVariables;
|
variables: SignUpMutationVariables;
|
||||||
response: SignUpMutation;
|
response: SignUpMutation;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'updateSubscriptionMutation';
|
||||||
|
variables: UpdateSubscriptionMutationVariables;
|
||||||
|
response: UpdateSubscriptionMutation;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'uploadAvatarMutation';
|
name: 'uploadAvatarMutation';
|
||||||
variables: UploadAvatarMutationVariables;
|
variables: UploadAvatarMutationVariables;
|
||||||
|
|||||||
Reference in New Issue
Block a user