Skip to content

Commit abd1b9b

Browse files
committed
feat: subscriptionCofnig centerSuffix, WorkflowCalculatorDialog trigger
button placement
1 parent c2e5bfe commit abd1b9b

9 files changed

Lines changed: 13429 additions & 31 deletions

src/components/SubscriptionConfigurator.tsx

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ function getPaymentPeriodDiscount(period: PaymentPeriod, paymentPeriod: Subscrip
248248
return 0
249249
}
250250

251+
function getPaymentPeriodSuffix(period: PaymentPeriod, paymentPeriod: SubscriptionConfigData["paymentPeriod"]) {
252+
if (period === "quarterly") return paymentPeriod.quarterlyPeriodSuffix
253+
if (period === "yearly") return paymentPeriod.yearlyPeriodSuffix
254+
return paymentPeriod.monthlyPeriodSuffix
255+
}
256+
251257
export function SubscriptionConfigurator({ locale, content, icons }: { locale: AppLocale; content: SubscriptionConfigData; icons: SubscriptionIcons }) {
252258
const workflowExecutions = content.workflowExecutions
253259
const aiTokens = content.aiTokens
@@ -273,6 +279,7 @@ export function SubscriptionConfigurator({ locale, content, icons }: { locale: A
273279
const aiTokenPrice = content.aiTokenPriceFactor * selection.aiTokens
274280
const additionalFeaturesPrice = Array.from(selectedFeatures).reduce((acc, idx) => acc + (content.additionalFeatures?.[idx]?.price ?? 0), 0)
275281
const paymentPeriodDiscount = getPaymentPeriodDiscount(selection.paymentPeriod, content.paymentPeriod)
282+
const paymentPeriodSuffix = getPaymentPeriodSuffix(selection.paymentPeriod, content.paymentPeriod)
276283
const totalBeforeDiscount = workflowExecutionPrice + aiTokenPrice + additionalFeaturesPrice
277284
const totalPrice = totalBeforeDiscount * (1 - paymentPeriodDiscount)
278285

@@ -388,17 +395,6 @@ export function SubscriptionConfigurator({ locale, content, icons }: { locale: A
388395
<p className="text-lg font-semibold tracking-wider text-white">{workflowExecutions.title}</p>
389396
<p className="text-sm text-secondary">{workflowExecutions.description}</p>
390397
</div>
391-
<WorkflowCalculatorDialog
392-
locale={locale}
393-
content={content.workflowCalculator}
394-
businessTypeIcons={icons.workflowBusinessTypes}
395-
value={selection.workflowExecutions}
396-
min={workflowExecutionRange.min}
397-
max={workflowExecutionRange.max}
398-
step={workflowExecutionRange.step}
399-
suffix={workflowExecutions.suffix}
400-
onApply={(workflowExecutionsValue) => setSelection((current) => ({ ...current, workflowExecutions: workflowExecutionsValue }))}
401-
/>
402398
</div>
403399
<Slider
404400
min={workflowExecutionRange.min}
@@ -409,12 +405,27 @@ export function SubscriptionConfigurator({ locale, content, icons }: { locale: A
409405
ariaLabel={workflowExecutions.title}
410406
className="mt-4"
411407
valueLabelSuffix={workflowExecutions.suffix}
408+
centerLabelSuffix={paymentPeriodSuffix}
412409
/>
413-
<div className="mt-2 flex flex-wrap items-center justify-end gap-2">
414-
<p className="text-sm font-medium text-tertiary">{content.contactSales.prompt}</p>
415-
<LinkButton href={content.contactSales.href} className="border-b-0 text-secondary" showArrow={false}>
416-
{content.contactSales.label}
417-
</LinkButton>
410+
<div className="flex items-center w-full justify-between gap-4">
411+
<WorkflowCalculatorDialog
412+
locale={locale}
413+
content={content.workflowCalculator}
414+
businessTypeIcons={icons.workflowBusinessTypes}
415+
value={selection.workflowExecutions}
416+
min={workflowExecutionRange.min}
417+
max={workflowExecutionRange.max}
418+
step={workflowExecutionRange.step}
419+
suffix={workflowExecutions.suffix}
420+
centerLabelSuffix={paymentPeriodSuffix}
421+
onApply={(workflowExecutionsValue) => setSelection((current) => ({ ...current, workflowExecutions: workflowExecutionsValue }))}
422+
/>
423+
<div className="mt-2 flex flex-wrap items-center justify-end gap-2">
424+
<p className="text-sm font-medium text-tertiary">{content.contactSales.prompt}</p>
425+
<LinkButton href={content.contactSales.href} className="border-b-0 text-secondary" showArrow={false}>
426+
{content.contactSales.label}
427+
</LinkButton>
428+
</div>
418429
</div>
419430
</div>
420431

@@ -434,6 +445,7 @@ export function SubscriptionConfigurator({ locale, content, icons }: { locale: A
434445
ariaLabel={aiTokens.title}
435446
className="mt-4"
436447
valueLabelSuffix={aiTokens.suffix}
448+
centerLabelSuffix={paymentPeriodSuffix}
437449
/>
438450
</div>
439451

src/components/dialogs/WorkflowCalculatorDialog.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
MenuTrigger,
2424
} from "@code0-tech/pictor"
2525
import { Slider } from "@/components/ui/Slider"
26-
import { IconCalculator, IconCheck, IconChevronDown, IconSearch, IconX } from "@tabler/icons-react"
26+
import { IconCheck, IconChevronDown, IconSearch, IconX } from "@tabler/icons-react"
2727
import { type ReactNode, useEffect, useState } from "react"
2828

2929
interface WorkflowCalculatorContent {
@@ -58,6 +58,7 @@ interface WorkflowCalculatorDialogProps {
5858
max: number
5959
step: number
6060
suffix: string
61+
centerLabelSuffix?: string
6162
onApply: (value: number) => void
6263
}
6364

@@ -67,7 +68,7 @@ function clampToStep(value: number, min: number, max: number, step: number) {
6768
return Math.min(max, Math.max(min, steppedValue))
6869
}
6970

70-
export function WorkflowCalculatorDialog({ locale, content, businessTypeIcons, value, min, max, step, suffix, onApply }: WorkflowCalculatorDialogProps) {
71+
export function WorkflowCalculatorDialog({ locale, content, businessTypeIcons, value, min, max, step, suffix, centerLabelSuffix, onApply }: WorkflowCalculatorDialogProps) {
7172
const [businessTypeMenuOpen, setBusinessTypeMenuOpen] = useState(false)
7273
const [selectedBusinessTypeIndex, setSelectedBusinessTypeIndex] = useState(0)
7374
const [runsPerDay, setRunsPerDay] = useState(value)
@@ -86,9 +87,13 @@ export function WorkflowCalculatorDialog({ locale, content, businessTypeIcons, v
8687
return (
8788
<Dialog>
8889
<DialogTrigger asChild>
89-
<Button type="button" variant="filled" className="shrink-0 gap-2" aria-label={content.title}>
90-
<IconCalculator size={18} />
91-
<span className="hidden sm:inline">{content.triggerLabel}</span>
90+
<Button
91+
type="button"
92+
variant="none"
93+
className="group/link relative h-auto! min-w-0 shrink-0 rounded-none! border-0! bg-transparent! px-0! py-0! text-sm! tracking-normal text-tertiary shadow-none! transition-colors hover:border-transparent! hover:bg-transparent! hover:text-brand! hover:shadow-none! focus:border-transparent! focus:bg-transparent! focus:shadow-none! [&_*]:transition-colors hover:[&_*]:text-brand! after:pointer-events-none after:absolute after:bottom-0 after:left-0 after:h-px after:w-0 after:bg-current after:transition-[width] after:duration-300 after:ease-out hover:after:w-full"
94+
aria-label={content.title}
95+
>
96+
<span className="flex min-w-0 items-center gap-1 truncate">{content.triggerLabel}</span>
9297
</Button>
9398
</DialogTrigger>
9499

@@ -152,7 +157,7 @@ export function WorkflowCalculatorDialog({ locale, content, businessTypeIcons, v
152157
</Menu>
153158
</div>
154159

155-
<Slider min={min} max={max} step={step} value={runsPerDay} onChange={setRunsPerDay} ariaLabel={unitLabel} lines={48} valueLabelSuffix={unit} />
160+
<Slider min={min} max={max} step={step} value={runsPerDay} onChange={setRunsPerDay} ariaLabel={unitLabel} lines={48} valueLabelSuffix={unit} centerLabelSuffix={centerLabelSuffix} />
156161
</div>
157162

158163
<DialogFooter className="pt-4! items-end! justify-between!">
@@ -163,8 +168,7 @@ export function WorkflowCalculatorDialog({ locale, content, businessTypeIcons, v
163168
</p>
164169
</div>
165170
<DialogClose asChild>
166-
<Button type="button" variant="filled" onClick={() => onApply(applicableExecutions)} className="gap-2 bg-white/80! text-primary! hover:bg-white! transition-colors">
167-
<IconCheck size={17} />
171+
<Button type="button" variant="filled" onClick={() => onApply(applicableExecutions)} className="bg-white/80! text-primary! hover:bg-white! transition-colors">
168172
{content.applyLabel}
169173
</Button>
170174
</DialogClose>

src/components/ui/Slider.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import { cn } from "@/lib/utils"
44
import { useCallback, useMemo, useRef } from "react"
55

6-
type SliderAccent = "aqua" | "blue" | "pink" | "yellow" | "brand"
7-
86
type SliderProps = {
97
min: number
108
max: number
@@ -18,6 +16,7 @@ type SliderProps = {
1816
centerLabel?: string
1917
ariaLabel?: string
2018
valueLabelSuffix?: string
19+
centerLabelSuffix?: string
2120
}
2221

2322
function formatCompactSliderValue(value: number) {
@@ -37,16 +36,16 @@ function formatCompactSliderValue(value: number) {
3736
return `${compactValue.toLocaleString("en-US", { maximumFractionDigits })}${unit.suffix}`
3837
}
3938

40-
function formatSliderLabel(value: number, suffix?: string) {
41-
return `${formatCompactSliderValue(value)}${suffix ? ` ${suffix}` : ""}`
39+
function formatSliderLabel(value: number, suffix?: string, trailingSuffix = "") {
40+
return `${formatCompactSliderValue(value)}${suffix ? ` ${suffix}` : ""}${trailingSuffix ? ` ${trailingSuffix}` : ""}`
4241
}
4342

44-
export function Slider({ min, max, step = 1, value, onChange, className, lines = 72, minLabel, maxLabel, centerLabel, ariaLabel, valueLabelSuffix }: SliderProps) {
43+
export function Slider({ min, max, step = 1, value, onChange, className, lines = 72, minLabel, maxLabel, centerLabel, ariaLabel, valueLabelSuffix, centerLabelSuffix = "" }: SliderProps) {
4544
const trackRef = useRef<HTMLDivElement>(null)
4645
const clampedValue = Math.min(max, Math.max(min, value))
4746
const progress = ((clampedValue - min) / (max - min)) * 100
4847
const resolvedMinLabel = minLabel ?? formatSliderLabel(min, valueLabelSuffix)
49-
const resolvedCenterLabel = centerLabel ?? formatSliderLabel(clampedValue, valueLabelSuffix)
48+
const resolvedCenterLabel = centerLabel ?? formatSliderLabel(clampedValue, valueLabelSuffix, centerLabelSuffix)
5049
const resolvedMaxLabel = maxLabel ?? formatSliderLabel(max, valueLabelSuffix)
5150

5251
const ticks = useMemo(() => Array.from({ length: lines }, (_, index) => index), [lines])

src/globals/subscriptionConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,9 @@ export const SubscriptionCollection: GlobalConfig = {
348348
{ name: "monthlyText", type: "text", required: false, localized: true, defaultValue: "Monthly" },
349349
{ name: "quarterlyText", type: "text", required: false, localized: true, defaultValue: "Quarterly" },
350350
{ name: "yearlyText", type: "text", required: false, localized: true, defaultValue: "Yearly" },
351+
{ name: "monthlyPeriodSuffix", type: "text", required: false, localized: true, defaultValue: "per month" },
352+
{ name: "quarterlyPeriodSuffix", type: "text", required: false, localized: true, defaultValue: "per quarter" },
353+
{ name: "yearlyPeriodSuffix", type: "text", required: false, localized: true, defaultValue: "per year" },
351354
{
352355
name: "quarterlyDiscount",
353356
label: "Quarterly Discount",

src/lib/cms.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ export interface SubscriptionConfigData {
155155
monthlyText: string
156156
quarterlyText: string
157157
yearlyText: string
158+
monthlyPeriodSuffix: string
159+
quarterlyPeriodSuffix: string
160+
yearlyPeriodSuffix: string
158161
quarterlyDiscount: number
159162
yearlyDiscount: number
160163
}

0 commit comments

Comments
 (0)