@thorprovider/blocks 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +15 -15
  2. package/.turbo/turbo-test.log +32 -5
  3. package/AGENTS.md +1 -1
  4. package/README.md +52 -0
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/contracts.d.mts +1111 -90
  7. package/dist/contracts.d.ts +1111 -90
  8. package/dist/contracts.js +189 -6
  9. package/dist/contracts.js.map +1 -1
  10. package/dist/contracts.mjs +173 -7
  11. package/dist/contracts.mjs.map +1 -1
  12. package/dist/index.d.mts +467 -89
  13. package/dist/index.d.ts +467 -89
  14. package/dist/index.js +2967 -2250
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +2989 -2282
  17. package/dist/index.mjs.map +1 -1
  18. package/docs/standards/theming.md +32 -5
  19. package/package.json +2 -2
  20. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +136 -93
  21. package/src/blocks/e-commerce/category-filters/category-filter-2.test.tsx +513 -0
  22. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +728 -148
  23. package/src/blocks/e-commerce/checkout-forms/checkout-confirmation-fulfilment-progress.tsx +14 -8
  24. package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.test.tsx +136 -0
  25. package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.tsx +145 -99
  26. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +215 -102
  27. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +6 -2
  28. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-shipping-step.tsx +51 -2
  29. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +216 -0
  30. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +114 -11
  31. package/src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx +42 -2
  32. package/src/blocks/e-commerce/navigation/mobile-category-drawer.tsx +10 -8
  33. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +51 -0
  34. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +56 -36
  35. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +182 -0
  36. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +298 -129
  37. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +137 -0
  38. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.tsx +231 -89
  39. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +65 -0
  40. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +19 -0
  41. package/src/blocks/marketing/footers/footer-section-1.test.tsx +117 -0
  42. package/src/blocks/marketing/footers/footer-section-1.tsx +46 -9
  43. package/src/blocks/marketing/navbars/navbar-4-cart-sheet.test.tsx +54 -0
  44. package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +43 -13
  45. package/src/blocks/marketing/navbars/navbar-4.test.tsx +112 -0
  46. package/src/blocks/marketing/navbars/navbar-4.tsx +101 -28
  47. package/src/index.ts +38 -0
  48. package/src/lib/cart.test.ts +15 -0
  49. package/src/lib/cart.ts +11 -0
  50. package/src/lib/contracts-curated.test.ts +207 -0
  51. package/src/lib/contracts-server-boundary.test.ts +52 -0
  52. package/src/lib/contracts.ts +182 -4
  53. package/src/lib/money.test.ts +64 -0
  54. package/src/lib/money.ts +65 -0
  55. package/tsup.config.ts +0 -1
  56. package/vitest.config.ts +9 -1
@@ -1,139 +1,252 @@
1
1
  'use client'
2
2
 
3
- import { useState } from 'react'
4
- import { Gift, Shield, Truck } from 'lucide-react'
3
+ import { Shield, Truck } from 'lucide-react'
5
4
  import { Badge } from '../../../ui/badge'
6
5
  import { Button } from '../../../ui/button'
7
6
  import { Card, CardContent, CardTitle } from '../../../ui/card'
8
7
  import { Field, FieldLabel } from '../../../ui/field'
9
8
  import { Input } from '../../../ui/input'
10
9
  import { Separator } from '../../../ui/separator'
11
- import type { CheckoutForm1OrderSummary } from './checkout-form-1'
10
+ import { formatMoney, isAvailableMoney } from '../../../lib/money'
11
+ import type { Money } from '@thorprovider/types'
12
+ import type {
13
+ CheckoutForm1OrderSummary,
14
+ CheckoutForm1OrderSummaryItem,
15
+ CheckoutForm1TotalsState,
16
+ } from './checkout-form-1'
12
17
 
13
18
  export interface CheckoutForm1OrderSummaryProps {
14
19
  orderSummary: CheckoutForm1OrderSummary
20
+ totalsState?: CheckoutForm1TotalsState
21
+ totalsErrorLabel?: string
22
+ /** Shown when a required total amount is absent or malformed (never as zero). */
23
+ totalsUnavailableLabel?: string
24
+ /** Shown for a line whose supplied line total is absent or malformed. */
25
+ amountUnavailableLabel?: string
26
+ retryLabel?: string
27
+ onRetryTotals?: () => void
28
+ promoCode?: string
29
+ onPromoCodeChange?: (value: string) => void
30
+ onApplyPromo?: (code: string) => void
31
+ promoPending?: boolean
32
+ promoApplyLabel?: string
33
+ promoPlaceholder?: string
34
+ trustNotes?: string[]
35
+ /** Override money formatting (locale, currency). Defaults to `Money.currencyCode`. */
36
+ formatMoney?: (money: Money) => string
15
37
  }
16
38
 
17
- export function CheckoutForm1OrderSummary({ orderSummary }: CheckoutForm1OrderSummaryProps) {
18
- const [promoCode, setPromoCode] = useState('')
39
+ export function CheckoutForm1OrderSummary({
40
+ orderSummary,
41
+ totalsState = 'idle',
42
+ totalsErrorLabel = 'No se pudieron actualizar los totales.',
43
+ totalsUnavailableLabel = 'Totales no disponibles.',
44
+ amountUnavailableLabel = '—',
45
+ retryLabel = 'Reintentar',
46
+ onRetryTotals,
47
+ promoCode,
48
+ onPromoCodeChange,
49
+ onApplyPromo,
50
+ promoPending = false,
51
+ promoApplyLabel = 'Aplicar',
52
+ promoPlaceholder = 'Ingresa el código',
53
+ trustNotes,
54
+ formatMoney: formatMoneyProp,
55
+ }: CheckoutForm1OrderSummaryProps) {
56
+ const format = (value: Money | null | undefined) => formatMoney(value, formatMoneyProp)
57
+ const lineAmount = (item: CheckoutForm1OrderSummaryItem): string | null => {
58
+ // A supplied line total that is absent/malformed must not fall back to a
59
+ // numeric guess; only an entirely absent line total uses the legacy field.
60
+ if (item.lineTotal !== undefined) return format(item.lineTotal)
61
+ if (typeof item.price === 'number') return `$${(item.price * item.quantity).toFixed(2)}`
62
+ return null
63
+ }
19
64
 
20
- const subtotal = orderSummary.items.reduce(
21
- (sum, item) => sum + item.price * item.quantity,
22
- 0
23
- )
24
- const total =
25
- subtotal +
26
- orderSummary.shipping +
27
- orderSummary.tax -
28
- (orderSummary.discount ?? 0) -
29
- (orderSummary.promoDiscount ?? 0)
30
-
31
- return (
32
- <Card className="sticky top-8">
33
- <CardContent className="flex flex-col gap-4 p-6">
34
- <CardTitle>Order Summary</CardTitle>
35
-
36
- <div className="flex flex-col gap-4">
37
- {orderSummary.items.map((item) => (
38
- <div key={item.id} className="flex items-center gap-3">
39
- <div className="relative">
40
- <img
41
- src={item.image || undefined}
42
- alt={item.name}
43
- width={64}
44
- height={64}
45
- className="size-16 rounded-lg object-cover"
46
- />
47
- <Badge
48
- variant="secondary"
49
- className="absolute -right-2 -top-2 size-6 justify-center rounded-full p-0 text-xs"
50
- >
51
- {item.quantity}
52
- </Badge>
53
- </div>
54
- <div className="flex-1">
55
- <p className="text-sm font-medium">{item.name}</p>
56
- <p className="text-sm text-muted-foreground">{item.variant}</p>
57
- </div>
58
- <p className="text-sm font-medium">${item.price.toFixed(2)}</p>
59
- </div>
60
- ))}
65
+ const renderTotals = () => {
66
+ if (totalsState === 'pending') {
67
+ return <p className="text-muted-foreground text-sm" aria-live="polite">Actualizando totales…</p>
68
+ }
69
+ if (totalsState === 'error') {
70
+ return (
71
+ <div className="flex flex-col gap-2" role="alert">
72
+ <p className="text-destructive text-sm">{totalsErrorLabel}</p>
73
+ {onRetryTotals ? (
74
+ <Button type="button" variant="outline" size="sm" className="w-fit" onClick={onRetryTotals}>
75
+ {retryLabel}
76
+ </Button>
77
+ ) : null}
61
78
  </div>
79
+ )
80
+ }
62
81
 
63
- <Separator />
64
-
65
- <Field className="gap-2">
66
- <FieldLabel>Promo code</FieldLabel>
67
- <div className="flex gap-2">
68
- <Input
69
- placeholder="Enter code"
70
- value={promoCode}
71
- onChange={(e) => setPromoCode(e.target.value)}
72
- />
73
- <Button type="button" variant="outline">
74
- Apply
75
- </Button>
82
+ const totals = orderSummary.totals
83
+ if (totals) {
84
+ const subtotalText = format(totals.subtotal)
85
+ const totalText = format(totals.total)
86
+ // Required totals: if either is absent/malformed, show an explicit
87
+ // unavailable state (with recovery) rather than a fabricated zero.
88
+ if (subtotalText === null || totalText === null) {
89
+ return (
90
+ <div className="flex flex-col gap-2" role="status">
91
+ <p className="text-muted-foreground text-sm">{totalsUnavailableLabel}</p>
92
+ {onRetryTotals ? (
93
+ <Button type="button" variant="outline" size="sm" className="w-fit" onClick={onRetryTotals}>
94
+ {retryLabel}
95
+ </Button>
96
+ ) : null}
76
97
  </div>
77
- </Field>
78
-
79
- <Separator />
80
-
98
+ )
99
+ }
100
+ return (
81
101
  <div className="flex flex-col gap-2 text-sm">
82
102
  <div className="flex items-center justify-between">
83
103
  <span className="text-muted-foreground">Subtotal</span>
84
- <span className="font-medium">${subtotal.toFixed(2)}</span>
104
+ <span className="font-medium tabular-nums">{subtotalText}</span>
85
105
  </div>
86
- <div className="flex items-center justify-between">
87
- <span className="flex items-center gap-1.5 text-muted-foreground">
88
- <Truck className="size-4" />
89
- Shipping
90
- </span>
91
- <span className="font-medium">${orderSummary.shipping.toFixed(2)}</span>
92
- </div>
93
- <div className="flex items-center justify-between">
94
- <span className="text-muted-foreground">Tax</span>
95
- <span className="font-medium">${orderSummary.tax.toFixed(2)}</span>
96
- </div>
97
- {(orderSummary.discount ?? 0) > 0 && (
106
+ {isAvailableMoney(totals.discount) ? (
98
107
  <div className="flex items-center justify-between">
99
- <span className="text-muted-foreground">Discount</span>
100
- <span className="font-medium text-destructive">
101
- -${(orderSummary.discount ?? 0).toFixed(2)}
102
- </span>
108
+ <span className="text-muted-foreground">Descuento</span>
109
+ <span className="font-medium tabular-nums">{format(totals.discount)}</span>
110
+ </div>
111
+ ) : null}
112
+ {isAvailableMoney(totals.tax) ? (
113
+ <div className="flex items-center justify-between">
114
+ <span className="text-muted-foreground">Impuestos</span>
115
+ <span className="font-medium tabular-nums">{format(totals.tax)}</span>
103
116
  </div>
104
- )}
105
- {(orderSummary.promoDiscount ?? 0) > 0 && (
117
+ ) : null}
118
+ {isAvailableMoney(totals.shipping) ? (
106
119
  <div className="flex items-center justify-between">
107
- <span className="text-muted-foreground">Promo discount</span>
108
- <span className="font-medium text-success">
109
- -${(orderSummary.promoDiscount ?? 0).toFixed(2)}
120
+ <span className="flex items-center gap-1.5 text-muted-foreground">
121
+ <Truck className="size-4" />
122
+ Envío
110
123
  </span>
124
+ <span className="font-medium tabular-nums">{format(totals.shipping)}</span>
111
125
  </div>
112
- )}
126
+ ) : null}
127
+ <Separator />
128
+ <div className="flex items-center justify-between">
129
+ <span className="font-semibold">Total</span>
130
+ <span className="font-semibold tabular-nums">{totalText}</span>
131
+ </div>
113
132
  </div>
133
+ )
134
+ }
114
135
 
136
+ // Legacy numeric fallback for consumers that have not moved to `totals`.
137
+ const legacySubtotal = orderSummary.items.reduce(
138
+ (sum, item) => sum + (item.price ?? 0) * item.quantity,
139
+ 0
140
+ )
141
+ const legacyTotal =
142
+ legacySubtotal +
143
+ (orderSummary.shipping ?? 0) +
144
+ (orderSummary.tax ?? 0) -
145
+ (orderSummary.discount ?? 0) -
146
+ (orderSummary.promoDiscount ?? 0)
147
+ return (
148
+ <div className="flex flex-col gap-2 text-sm">
149
+ <div className="flex items-center justify-between">
150
+ <span className="text-muted-foreground">Subtotal</span>
151
+ <span className="font-medium tabular-nums">${legacySubtotal.toFixed(2)}</span>
152
+ </div>
153
+ <div className="flex items-center justify-between">
154
+ <span className="text-muted-foreground">Envío</span>
155
+ <span className="font-medium tabular-nums">${(orderSummary.shipping ?? 0).toFixed(2)}</span>
156
+ </div>
157
+ <div className="flex items-center justify-between">
158
+ <span className="text-muted-foreground">Impuestos</span>
159
+ <span className="font-medium tabular-nums">${(orderSummary.tax ?? 0).toFixed(2)}</span>
160
+ </div>
115
161
  <Separator />
116
-
117
162
  <div className="flex items-center justify-between">
118
163
  <span className="font-semibold">Total</span>
119
- <span className="font-semibold">${total.toFixed(2)}</span>
164
+ <span className="font-semibold tabular-nums">${legacyTotal.toFixed(2)}</span>
120
165
  </div>
166
+ </div>
167
+ )
168
+ }
121
169
 
122
- <div className="flex flex-col gap-2 text-sm text-muted-foreground">
123
- <span className="flex items-center gap-2">
124
- <Shield className="size-4" />
125
- SSL encrypted checkout
126
- </span>
127
- <span className="flex items-center gap-2">
128
- <Truck className="size-4" />
129
- Free shipping on orders over $75
130
- </span>
131
- <span className="flex items-center gap-2">
132
- <Gift className="size-4" />
133
- 30-day return policy
134
- </span>
170
+ return (
171
+ <Card className="sticky top-8">
172
+ <CardContent className="flex flex-col gap-4 p-6">
173
+ <CardTitle>Order Summary</CardTitle>
174
+
175
+ <div className="flex flex-col gap-4">
176
+ {orderSummary.items.map((item) => {
177
+ const amount = lineAmount(item)
178
+ return (
179
+ <div key={String(item.id)} className="flex items-center gap-3">
180
+ <div className="relative">
181
+ {item.image ? (
182
+ <img
183
+ src={item.image || undefined}
184
+ alt={item.name}
185
+ width={64}
186
+ height={64}
187
+ className="bg-muted size-16 rounded-lg object-cover"
188
+ />
189
+ ) : (
190
+ <div aria-hidden="true" className="bg-muted size-16 rounded-lg" />
191
+ )}
192
+ <Badge
193
+ variant="secondary"
194
+ className="absolute -right-2 -top-2 size-6 justify-center rounded-full p-0 text-xs"
195
+ >
196
+ {item.quantity}
197
+ </Badge>
198
+ </div>
199
+ <div className="flex-1">
200
+ <p className="text-sm font-medium">{item.name}</p>
201
+ {item.variant ? <p className="text-sm text-muted-foreground">{item.variant}</p> : null}
202
+ </div>
203
+ <p className="text-sm font-medium tabular-nums">{amount ?? amountUnavailableLabel}</p>
204
+ </div>
205
+ )
206
+ })}
135
207
  </div>
208
+
209
+ <Separator />
210
+
211
+ {onPromoCodeChange ? (
212
+ <Field className="gap-2">
213
+ <FieldLabel htmlFor="checkout-promo-code">Código promocional</FieldLabel>
214
+ <div className="flex gap-2">
215
+ <Input
216
+ id="checkout-promo-code"
217
+ placeholder={promoPlaceholder}
218
+ value={promoCode ?? ''}
219
+ onChange={(e) => onPromoCodeChange(e.target.value)}
220
+ />
221
+ <Button
222
+ type="button"
223
+ variant="outline"
224
+ disabled={promoPending || !onApplyPromo}
225
+ onClick={() => onApplyPromo?.(promoCode ?? '')}
226
+ >
227
+ {promoApplyLabel}
228
+ </Button>
229
+ </div>
230
+ </Field>
231
+ ) : null}
232
+
233
+ <Separator />
234
+
235
+ {renderTotals()}
236
+
237
+ {trustNotes && trustNotes.length > 0 ? (
238
+ <div className="flex flex-col gap-2 text-sm text-muted-foreground">
239
+ {trustNotes.map((note, index) => (
240
+ <span key={`trust:${index}:${note}`} className="flex items-center gap-2">
241
+ <Shield className="size-4" />
242
+ {note}
243
+ </span>
244
+ ))}
245
+ </div>
246
+ ) : null}
136
247
  </CardContent>
137
248
  </Card>
138
249
  )
139
- }
250
+ }
251
+
252
+ export default CheckoutForm1OrderSummary
@@ -28,6 +28,8 @@ export interface CheckoutForm1PaymentStepProps {
28
28
  onExpiryChange: (value: string) => void
29
29
  paymentMethods?: PaymentMethod[]
30
30
  noPaymentMethodsLabel?: string
31
+ /** Raw card fields are legacy-only; the launch flow defaults to `false`. */
32
+ collectCardFields?: boolean
31
33
  fieldErrors?: CheckoutForm1FieldErrors
32
34
  fieldDescriptions?: CheckoutForm1FieldDescriptions
33
35
  }
@@ -39,6 +41,7 @@ export function CheckoutForm1PaymentStep({
39
41
  onExpiryChange,
40
42
  paymentMethods = [],
41
43
  noPaymentMethodsLabel = 'No payment methods are available for this region.',
44
+ collectCardFields = false,
42
45
  fieldErrors,
43
46
  fieldDescriptions,
44
47
  }: CheckoutForm1PaymentStepProps) {
@@ -46,6 +49,7 @@ export function CheckoutForm1PaymentStep({
46
49
 
47
50
  const selectedMethod = paymentMethods.find(method => method.id === formData.paymentMethod)
48
51
  const isCard = selectedMethod?.type === 'card'
52
+ const showCardFields = collectCardFields && isCard
49
53
 
50
54
  return (
51
55
  <FieldSet>
@@ -70,14 +74,14 @@ export function CheckoutForm1PaymentStep({
70
74
  </RadioGroup>
71
75
  )}
72
76
 
73
- {selectedMethod && !isCard ? (
77
+ {selectedMethod && !showCardFields ? (
74
78
  <div className="bg-muted/50 rounded-lg p-4 text-sm">
75
79
  <p className="font-medium">{selectedMethod.name}</p>
76
80
  {selectedMethod.description ? <p className="text-muted-foreground mt-1">{selectedMethod.description}</p> : null}
77
81
  </div>
78
82
  ) : null}
79
83
 
80
- {isCard ? (
84
+ {showCardFields ? (
81
85
  <>
82
86
  <Field data-invalid={!!fieldErrors?.cardNumber?.length}>
83
87
  <FieldLabel htmlFor="checkout-card-number">Card number</FieldLabel>
@@ -1,7 +1,10 @@
1
1
  'use client'
2
2
 
3
- import { Field, FieldGroup, FieldLabel } from '../../../ui/field'
3
+ import { Field, FieldContent, FieldGroup, FieldLabel, FieldLegend, FieldSet } from '../../../ui/field'
4
4
  import { Input } from '../../../ui/input'
5
+ import { RadioGroup, RadioGroupItem } from '../../../ui/radio-group'
6
+ import { formatMoney, isAvailableMoney } from '../../../lib/money'
7
+ import type { Money } from '@thorprovider/types'
5
8
  import { CheckoutFieldFeedback, checkoutFieldAria } from './checkout-field-feedback'
6
9
  import type {
7
10
  CheckoutForm1ChangeHandler,
@@ -9,6 +12,7 @@ import type {
9
12
  CheckoutForm1FieldDescriptions,
10
13
  CheckoutForm1FieldErrors,
11
14
  CheckoutForm1FormData,
15
+ CheckoutForm1ShippingOption,
12
16
  } from './checkout-form-1'
13
17
  import { CheckoutForm1CountryField } from './checkout-form-1-country-field'
14
18
 
@@ -16,6 +20,11 @@ export interface CheckoutForm1ShippingStepProps {
16
20
  formData: CheckoutForm1FormData
17
21
  onChange: CheckoutForm1ChangeHandler
18
22
  countries?: CheckoutForm1CountryOption[]
23
+ shippingOptions?: CheckoutForm1ShippingOption[]
24
+ selectedShippingOptionId?: string
25
+ onSelectShippingOption?: (id: string) => void
26
+ /** Override money formatting (locale, currency). Defaults to `Money.currencyCode`. */
27
+ formatMoney?: (money: Money) => string
19
28
  fieldErrors?: CheckoutForm1FieldErrors
20
29
  fieldDescriptions?: CheckoutForm1FieldDescriptions
21
30
  }
@@ -24,6 +33,10 @@ export function CheckoutForm1ShippingStep({
24
33
  formData,
25
34
  onChange,
26
35
  countries,
36
+ shippingOptions,
37
+ selectedShippingOptionId,
38
+ onSelectShippingOption,
39
+ formatMoney: formatMoneyProp,
27
40
  fieldErrors,
28
41
  fieldDescriptions,
29
42
  }: CheckoutForm1ShippingStepProps) {
@@ -102,6 +115,42 @@ export function CheckoutForm1ShippingStep({
102
115
  />
103
116
  )}
104
117
  </div>
118
+
119
+ {shippingOptions && shippingOptions.length > 0 ? (
120
+ <FieldSet>
121
+ <FieldLegend>Shipping method</FieldLegend>
122
+ <RadioGroup
123
+ value={selectedShippingOptionId ?? ''}
124
+ onValueChange={(value) => value && onSelectShippingOption?.(String(value))}
125
+ className="grid gap-3"
126
+ >
127
+ {shippingOptions.map((option) => (
128
+ <Field
129
+ key={option.id}
130
+ orientation="horizontal"
131
+ className="justify-between rounded-lg border p-4"
132
+ >
133
+ <FieldContent>
134
+ <FieldLabel htmlFor={`shipping-${option.id}`} className="flex flex-col gap-0.5">
135
+ <span>{option.name}</span>
136
+ {option.description ? (
137
+ <span className="text-muted-foreground text-xs font-normal">
138
+ {option.description}
139
+ </span>
140
+ ) : null}
141
+ </FieldLabel>
142
+ </FieldContent>
143
+ <div className="flex items-center gap-3">
144
+ {isAvailableMoney(option.price) ? (
145
+ <span className="text-sm tabular-nums">{formatMoney(option.price, formatMoneyProp)}</span>
146
+ ) : null}
147
+ <RadioGroupItem value={option.id} id={`shipping-${option.id}`} />
148
+ </div>
149
+ </Field>
150
+ ))}
151
+ </RadioGroup>
152
+ </FieldSet>
153
+ ) : null}
105
154
  </FieldGroup>
106
155
  )
107
- }
156
+ }