mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 16:11:45 +03:00
Fix Subscribe form UX (#10744)
This commit is contained in:
parent
e94974a0a8
commit
c179701a00
@ -16,8 +16,8 @@ import {
|
||||
import { mergeRefs } from '#/utilities/mergeRefs'
|
||||
|
||||
import RadioGroup from '#/components/styled/RadioGroup'
|
||||
import { tv } from '#/utilities/tailwindVariants'
|
||||
import { Controller } from 'react-hook-form'
|
||||
import { SELECTOR_STYLES } from '../variants'
|
||||
import { SelectorOption } from './SelectorOption'
|
||||
|
||||
/** * Props for the Selector component. */
|
||||
@ -44,8 +44,45 @@ export interface SelectorProps<
|
||||
readonly readOnly?: boolean
|
||||
}
|
||||
|
||||
export const SELECTOR_STYLES = tv({
|
||||
base: 'block w-full bg-transparent transition-[border-color,outline] duration-200',
|
||||
variants: {
|
||||
disabled: {
|
||||
true: { base: 'cursor-default opacity-50', textArea: 'cursor-default' },
|
||||
false: { base: 'cursor-text', textArea: 'cursor-text' },
|
||||
},
|
||||
readOnly: { true: 'cursor-default' },
|
||||
size: {
|
||||
medium: { base: '' },
|
||||
},
|
||||
rounded: {
|
||||
none: 'rounded-none',
|
||||
small: 'rounded-sm',
|
||||
medium: 'rounded-md',
|
||||
large: 'rounded-lg',
|
||||
xlarge: 'rounded-xl',
|
||||
xxlarge: 'rounded-2xl',
|
||||
xxxlarge: 'rounded-3xl',
|
||||
full: 'rounded-full',
|
||||
},
|
||||
variant: {
|
||||
outline: {
|
||||
base: 'border-[0.5px] border-primary/20',
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'medium',
|
||||
rounded: 'xxxlarge',
|
||||
variant: 'outline',
|
||||
},
|
||||
slots: {
|
||||
radioGroup: 'flex',
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Basic input component. Input component is a component that is used to get user input in a text field.
|
||||
* A horizontal selector.
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const Selector = React.forwardRef(function Selector<
|
||||
|
@ -1,8 +1,9 @@
|
||||
/** @file An option in a selector. */
|
||||
import { Radio, type RadioProps } from '#/components/aria'
|
||||
import { tv } from '#/utilities/tailwindVariants'
|
||||
import * as React from 'react'
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
import { SELECTOR_OPTION_STYLES } from '../variants'
|
||||
import { TEXT_STYLE } from '../../Text'
|
||||
|
||||
/** Props for a {@link SelectorOption}. */
|
||||
export interface SelectorOptionProps
|
||||
@ -11,6 +12,39 @@ export interface SelectorOptionProps
|
||||
readonly label: string
|
||||
}
|
||||
|
||||
export const SELECTOR_OPTION_STYLES = tv({
|
||||
base: TEXT_STYLE({
|
||||
className:
|
||||
'flex flex-1 items-center justify-center min-h-8 relative overflow-clip cursor-pointer transition-[background-color,color,outline-offset] duration-200',
|
||||
variant: 'body',
|
||||
}),
|
||||
variants: {
|
||||
rounded: {
|
||||
none: 'rounded-none',
|
||||
small: 'rounded-sm',
|
||||
medium: 'rounded-md',
|
||||
large: 'rounded-lg',
|
||||
xlarge: 'rounded-xl',
|
||||
xxlarge: 'rounded-2xl',
|
||||
xxxlarge: 'rounded-3xl',
|
||||
full: 'rounded-full',
|
||||
},
|
||||
size: {
|
||||
medium: { base: 'px-[11px] pb-1.5 pt-2' },
|
||||
small: { base: 'px-[11px] pb-0.5 pt-1' },
|
||||
},
|
||||
variant: {
|
||||
primary:
|
||||
'selected:bg-primary selected:text-white hover:bg-primary/5 pressed:bg-primary/10 outline outline-2 outline-transparent outline-offset-[-2px] focus-visible:outline-primary focus-visible:outline-offset-0',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'medium',
|
||||
rounded: 'xxxlarge',
|
||||
variant: 'primary',
|
||||
},
|
||||
})
|
||||
|
||||
export const SelectorOption = React.forwardRef(function SelectorOption(
|
||||
props: SelectorOptionProps,
|
||||
ref: React.ForwardedRef<HTMLLabelElement>,
|
||||
|
@ -76,73 +76,3 @@ export const INPUT_STYLES = tv({
|
||||
variant: 'outline',
|
||||
},
|
||||
})
|
||||
|
||||
export const SELECTOR_STYLES = tv({
|
||||
base: 'block w-full overflow-hidden bg-transparent transition-[border-color,outline] duration-200',
|
||||
variants: {
|
||||
disabled: {
|
||||
true: { base: 'cursor-default opacity-50', textArea: 'cursor-default' },
|
||||
false: { base: 'cursor-text', textArea: 'cursor-text' },
|
||||
},
|
||||
readOnly: { true: 'cursor-default' },
|
||||
size: {
|
||||
medium: { base: '' },
|
||||
},
|
||||
rounded: {
|
||||
none: 'rounded-none',
|
||||
small: 'rounded-sm',
|
||||
medium: 'rounded-md',
|
||||
large: 'rounded-lg',
|
||||
xlarge: 'rounded-xl',
|
||||
xxlarge: 'rounded-2xl',
|
||||
xxxlarge: 'rounded-3xl',
|
||||
full: 'rounded-full',
|
||||
},
|
||||
variant: {
|
||||
outline: {
|
||||
base: 'border-[0.5px] outline-offset-2 border-primary/20 focus-within:outline focus-within:outline-2 focus-within:outline-offset-[-1px] focus-within:outline-primary focus-within:border-primary/50',
|
||||
textArea: 'border-transparent focus-within:border-transparent',
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'medium',
|
||||
rounded: 'xlarge',
|
||||
variant: 'outline',
|
||||
},
|
||||
slots: {
|
||||
radioGroup: 'flex',
|
||||
},
|
||||
})
|
||||
|
||||
export const SELECTOR_OPTION_STYLES = tv({
|
||||
base: TEXT_STYLE({
|
||||
className: 'flex flex-1 items-center min-h-6 relative overflow-auto',
|
||||
variant: 'body',
|
||||
}),
|
||||
variants: {
|
||||
rounded: {
|
||||
none: 'first:rounded-l-none last:rounded-r-none',
|
||||
small: 'first:rounded-l-sm last:rounded-r-sm',
|
||||
medium: 'first:rounded-l-md last:rounded-r-md',
|
||||
large: 'first:rounded-l-lg last:rounded-r-lg',
|
||||
xlarge: 'first:rounded-l-xl last:rounded-r-xl',
|
||||
xxlarge: 'first:rounded-l-2xl last:rounded-r-2xl',
|
||||
xxxlarge: 'first:rounded-l-3xl last:rounded-r-3xl',
|
||||
full: 'first:rounded-l-full last:rounded-r-full',
|
||||
},
|
||||
size: {
|
||||
medium: { base: 'px-[11px] pb-1.5 pt-2' },
|
||||
small: { base: 'px-[11px] pb-0.5 pt-1' },
|
||||
},
|
||||
variant: {
|
||||
primary:
|
||||
'selected:bg-primary selected:text-white not-selected:hover:bg-primary/20 border-r-[0.5px] border-primary/20',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'medium',
|
||||
rounded: 'xlarge',
|
||||
variant: 'primary',
|
||||
},
|
||||
})
|
||||
|
@ -30,7 +30,7 @@ export const TEXT_STYLE = twv.tv({
|
||||
custom: '',
|
||||
primary: 'text-primary',
|
||||
danger: 'text-danger',
|
||||
success: 'text-share',
|
||||
success: 'text-accent-dark',
|
||||
disabled: 'text-primary/30',
|
||||
invert: 'text-white',
|
||||
inherit: 'text-inherit',
|
||||
|
@ -209,7 +209,7 @@ export function Stepper(props: StepperProps) {
|
||||
}}
|
||||
transition={{
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
x: { type: 'spring', stiffness: 500, damping: 50, bounce: 10, duration: 0.35 },
|
||||
x: { type: 'spring', stiffness: 500, damping: 50, duration: 0.2 },
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
opacity: { duration: 0.2 },
|
||||
}}
|
||||
@ -362,7 +362,7 @@ function Step(props: StepProps) {
|
||||
}}
|
||||
transition={{
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
rotate: { type: 'spring', stiffness: 500, damping: 100, bounce: 0, duration: 0.35 },
|
||||
rotate: { type: 'spring', stiffness: 500, damping: 100, bounce: 0, duration: 0.2 },
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
|
@ -129,7 +129,16 @@ export function PlanSelectorDialog(props: PlanSelectorDialogProps) {
|
||||
<div>
|
||||
<Text variant="subtitle">{getText('adjustYourPlan')}</Text>
|
||||
|
||||
<Form form={form} className="flex flex-row">
|
||||
<Form form={form} className="mt-1">
|
||||
<Selector
|
||||
form={form}
|
||||
name="period"
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
items={[1, 12, 36]}
|
||||
itemToString={(item) => billingPeriodToString(getText, item)}
|
||||
label={getText('billingPeriod')}
|
||||
/>
|
||||
|
||||
<Input
|
||||
isRequired
|
||||
readOnly={maxSeats === 1}
|
||||
@ -142,14 +151,6 @@ export function PlanSelectorDialog(props: PlanSelectorDialogProps) {
|
||||
label={getText('seats')}
|
||||
description={getText(`${plan}PlanSeatsDescription`, maxSeats)}
|
||||
/>
|
||||
<Selector
|
||||
form={form}
|
||||
name="period"
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
items={[1, 12, 36]}
|
||||
itemToString={(item) => billingPeriodToString(getText, item)}
|
||||
label={getText('billingPeriod')}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
@ -205,11 +206,7 @@ function Summary(props: SummaryProps) {
|
||||
const { getText } = useText()
|
||||
|
||||
const { data, isLoading, isError, refetch, error } = useQuery({
|
||||
...createSubscriptionPriceQuery({
|
||||
plan,
|
||||
seats,
|
||||
period,
|
||||
}),
|
||||
...createSubscriptionPriceQuery({ plan, seats, period }),
|
||||
enabled: !isInvalid,
|
||||
})
|
||||
|
||||
@ -260,37 +257,38 @@ function Summary(props: SummaryProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{data && data.discount !== 0 && (
|
||||
<div className="table-row">
|
||||
<Text className="table-cell w-[0%]" variant="body" nowrap>
|
||||
{getText('originalPrice')}
|
||||
</Text>
|
||||
<Text className="table-cell text-danger" variant="body">
|
||||
<div className="table-row">
|
||||
<Text className="table-cell w-[0%]" variant="body" nowrap>
|
||||
{getText('originalPrice')}
|
||||
</Text>
|
||||
{data && (
|
||||
<Text className="table-cell" variant="body">
|
||||
{formatter.format(data.fullPrice)}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data && data.discount !== 0 && (
|
||||
<div className="table-row">
|
||||
<Text className="table-cell w-[0%]" variant="body" nowrap>
|
||||
{getText('youSave')}
|
||||
</Text>
|
||||
<Text className="table-cell" variant="body">
|
||||
{formatter.format(data.discount)}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="table-row">
|
||||
<Text className="table-cell w-[0%]" variant="body" nowrap>
|
||||
{getText('totalPrice')}
|
||||
{getText('youSave')}
|
||||
</Text>
|
||||
{data && (
|
||||
<Text
|
||||
className={twMerge('table-cell', data.discount !== 0 && 'text-accent-dark')}
|
||||
className="table-cell"
|
||||
color={data.discount > 0 ? 'success' : 'primary'}
|
||||
variant="body"
|
||||
>
|
||||
{formatter.format(data.discount)}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="table-row">
|
||||
<Text className="table-cell w-[0%]" variant="body" nowrap>
|
||||
{getText('subtotalPrice')}
|
||||
</Text>
|
||||
{data && (
|
||||
<Text className="table-cell" variant="body">
|
||||
{formatter.format(data.totalPrice)}
|
||||
</Text>
|
||||
)}
|
||||
|
@ -535,6 +535,7 @@
|
||||
"originalPrice": "Original Price",
|
||||
"youSave": "You Save",
|
||||
"totalPrice": "Total Price",
|
||||
"subtotalPrice": "Subtotal Price",
|
||||
"returnToDashboard": "Return to Dashboard",
|
||||
"subscribeTitle": "Enso Pricing",
|
||||
"subscribeSuccessSubmit": "Go to Dashboard",
|
||||
|
Loading…
Reference in New Issue
Block a user