astryx-dracula 0.1.3 → 0.2.0
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.
- package/BRAND.md +1 -1
- package/README.md +2 -0
- package/USAGE.md +13 -0
- package/astryx-dracula.js +4 -4
- package/astryx.integration.mjs +3 -0
- package/package.json +4 -2
- package/templates/ai-chat-landing.template.mjs +6 -0
- package/templates/ai-chat-landing.tsx +476 -0
- package/templates/ai-chat.template.mjs +6 -0
- package/templates/ai-chat.tsx +762 -0
- package/templates/blank.template.mjs +6 -0
- package/templates/blank.tsx +16 -0
- package/templates/centered-hero.template.mjs +6 -0
- package/templates/centered-hero.tsx +188 -0
- package/templates/classic-gallery.template.mjs +6 -0
- package/templates/classic-gallery.tsx +181 -0
- package/templates/contact-form.template.mjs +6 -0
- package/templates/contact-form.tsx +292 -0
- package/templates/dashboard-portfolio.template.mjs +6 -0
- package/templates/dashboard-portfolio.tsx +800 -0
- package/templates/dashboard.template.mjs +6 -0
- package/templates/dashboard.tsx +774 -0
- package/templates/detail-page.template.mjs +6 -0
- package/templates/detail-page.tsx +695 -0
- package/templates/documentation-design.template.mjs +6 -0
- package/templates/documentation-design.tsx +751 -0
- package/templates/documentation-technical.template.mjs +6 -0
- package/templates/documentation-technical.tsx +292 -0
- package/templates/documentation.template.mjs +6 -0
- package/templates/documentation.tsx +347 -0
- package/templates/editor.template.mjs +6 -0
- package/templates/editor.tsx +959 -0
- package/templates/file-explorer.template.mjs +6 -0
- package/templates/file-explorer.tsx +577 -0
- package/templates/form-two-column.template.mjs +6 -0
- package/templates/form-two-column.tsx +314 -0
- package/templates/gallery-hero.template.mjs +6 -0
- package/templates/gallery-hero.tsx +287 -0
- package/templates/ide.template.mjs +6 -0
- package/templates/ide.tsx +533 -0
- package/templates/incident-console.template.mjs +6 -0
- package/templates/incident-console.tsx +584 -0
- package/templates/kanban-board.template.mjs +6 -0
- package/templates/kanban-board.tsx +746 -0
- package/templates/library.template.mjs +6 -0
- package/templates/library.tsx +489 -0
- package/templates/login-card.template.mjs +6 -0
- package/templates/login-card.tsx +200 -0
- package/templates/login-split.template.mjs +6 -0
- package/templates/login-split.tsx +318 -0
- package/templates/login-sso.template.mjs +6 -0
- package/templates/login-sso.tsx +277 -0
- package/templates/login.template.mjs +6 -0
- package/templates/login.tsx +98 -0
- package/templates/messaging-shell.template.mjs +6 -0
- package/templates/messaging-shell.tsx +675 -0
- package/templates/mixed-gallery.template.mjs +6 -0
- package/templates/mixed-gallery.tsx +190 -0
- package/templates/payment-form.template.mjs +6 -0
- package/templates/payment-form.tsx +990 -0
- package/templates/product-detail.template.mjs +6 -0
- package/templates/product-detail.tsx +366 -0
- package/templates/product-gallery.template.mjs +6 -0
- package/templates/product-gallery.tsx +173 -0
- package/templates/settings-dialog.template.mjs +6 -0
- package/templates/settings-dialog.tsx +920 -0
- package/templates/settings-sidebar.template.mjs +6 -0
- package/templates/settings-sidebar.tsx +952 -0
- package/templates/settings.template.mjs +6 -0
- package/templates/settings.tsx +235 -0
- package/templates/shell-nav.template.mjs +6 -0
- package/templates/shell-nav.tsx +306 -0
- package/templates/shell-side-nav.template.mjs +6 -0
- package/templates/shell-side-nav.tsx +238 -0
- package/templates/shell-top-nav.template.mjs +6 -0
- package/templates/shell-top-nav.tsx +243 -0
- package/templates/side-gallery.template.mjs +6 -0
- package/templates/side-gallery.tsx +179 -0
- package/templates/table-grouped.template.mjs +6 -0
- package/templates/table-grouped.tsx +1174 -0
- package/templates/table-page-chart.template.mjs +6 -0
- package/templates/table-page-chart.tsx +693 -0
- package/templates/table-page-heatmap-status.template.mjs +6 -0
- package/templates/table-page-heatmap-status.tsx +547 -0
- package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
- package/templates/table-page-shoe-store-heatmap.tsx +1038 -0
- package/templates/table-page.template.mjs +6 -0
- package/templates/table-page.tsx +445 -0
- package/templates/table.template.mjs +6 -0
- package/templates/table.tsx +84 -0
- package/templates/theme-showcase.template.mjs +6 -0
- package/templates/theme-showcase.tsx +1282 -0
- package/theme.css +4 -4
- package/tokens.css +4 -4
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {useState, type CSSProperties} from 'react';
|
|
4
|
+
import {
|
|
5
|
+
VStack,
|
|
6
|
+
HStack,
|
|
7
|
+
Stack,
|
|
8
|
+
StackItem,
|
|
9
|
+
Layout,
|
|
10
|
+
LayoutContent,
|
|
11
|
+
} from '@astryxdesign/core/Layout';
|
|
12
|
+
import {Grid} from '@astryxdesign/core/Grid';
|
|
13
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
14
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
15
|
+
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
16
|
+
import {Selector} from '@astryxdesign/core/Selector';
|
|
17
|
+
import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
|
|
18
|
+
import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
|
|
19
|
+
import {Link} from '@astryxdesign/core/Link';
|
|
20
|
+
import {TextArea} from '@astryxdesign/core/TextArea';
|
|
21
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
22
|
+
import {Banner} from '@astryxdesign/core/Banner';
|
|
23
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
24
|
+
import {Collapsible} from '@astryxdesign/core/Collapsible';
|
|
25
|
+
import {Badge} from '@astryxdesign/core/Badge';
|
|
26
|
+
import {NumberInput} from '@astryxdesign/core/NumberInput';
|
|
27
|
+
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
28
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
29
|
+
import {Center} from '@astryxdesign/core/Center';
|
|
30
|
+
import {Thumbnail} from '@astryxdesign/core/Thumbnail';
|
|
31
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
32
|
+
import {ShieldCheck, Lock, CircleCheck, Truck} from 'lucide-react';
|
|
33
|
+
|
|
34
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const MONTHS = [
|
|
37
|
+
'01',
|
|
38
|
+
'02',
|
|
39
|
+
'03',
|
|
40
|
+
'04',
|
|
41
|
+
'05',
|
|
42
|
+
'06',
|
|
43
|
+
'07',
|
|
44
|
+
'08',
|
|
45
|
+
'09',
|
|
46
|
+
'10',
|
|
47
|
+
'11',
|
|
48
|
+
'12',
|
|
49
|
+
];
|
|
50
|
+
const YEARS = Array.from({length: 12}, (_, i) => String(2025 + i));
|
|
51
|
+
|
|
52
|
+
const US_STATES = [
|
|
53
|
+
'Alabama',
|
|
54
|
+
'Alaska',
|
|
55
|
+
'Arizona',
|
|
56
|
+
'Arkansas',
|
|
57
|
+
'California',
|
|
58
|
+
'Colorado',
|
|
59
|
+
'Connecticut',
|
|
60
|
+
'Delaware',
|
|
61
|
+
'Florida',
|
|
62
|
+
'Georgia',
|
|
63
|
+
'Hawaii',
|
|
64
|
+
'Idaho',
|
|
65
|
+
'Illinois',
|
|
66
|
+
'Indiana',
|
|
67
|
+
'Iowa',
|
|
68
|
+
'Kansas',
|
|
69
|
+
'Kentucky',
|
|
70
|
+
'Louisiana',
|
|
71
|
+
'Maine',
|
|
72
|
+
'Maryland',
|
|
73
|
+
'Massachusetts',
|
|
74
|
+
'Michigan',
|
|
75
|
+
'Minnesota',
|
|
76
|
+
'Mississippi',
|
|
77
|
+
'Missouri',
|
|
78
|
+
'Montana',
|
|
79
|
+
'Nebraska',
|
|
80
|
+
'Nevada',
|
|
81
|
+
'New Hampshire',
|
|
82
|
+
'New Jersey',
|
|
83
|
+
'New Mexico',
|
|
84
|
+
'New York',
|
|
85
|
+
'North Carolina',
|
|
86
|
+
'North Dakota',
|
|
87
|
+
'Ohio',
|
|
88
|
+
'Oklahoma',
|
|
89
|
+
'Oregon',
|
|
90
|
+
'Pennsylvania',
|
|
91
|
+
'Rhode Island',
|
|
92
|
+
'South Carolina',
|
|
93
|
+
'South Dakota',
|
|
94
|
+
'Tennessee',
|
|
95
|
+
'Texas',
|
|
96
|
+
'Utah',
|
|
97
|
+
'Vermont',
|
|
98
|
+
'Virginia',
|
|
99
|
+
'Washington',
|
|
100
|
+
'West Virginia',
|
|
101
|
+
'Wisconsin',
|
|
102
|
+
'Wyoming',
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
// Product photos from the local template-assets set (committed to the
|
|
106
|
+
// docsite; the CLI swaps these for an inline placeholder on scaffold).
|
|
107
|
+
const ITEM_IMAGES: Record<string, {src: string}> = {
|
|
108
|
+
'1': {
|
|
109
|
+
src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
|
|
110
|
+
},
|
|
111
|
+
'2': {
|
|
112
|
+
src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
|
|
113
|
+
},
|
|
114
|
+
'3': {
|
|
115
|
+
src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const ORDER_ITEMS = [
|
|
120
|
+
{
|
|
121
|
+
id: '1',
|
|
122
|
+
name: 'Obsidian Ritual Chalice',
|
|
123
|
+
variant: 'Hand-carved · 12 oz',
|
|
124
|
+
price: 78,
|
|
125
|
+
qty: 1,
|
|
126
|
+
limited: false,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: '2',
|
|
130
|
+
name: 'Blood-Moon Offering Plate',
|
|
131
|
+
variant: 'Moon-glazed · 10 in',
|
|
132
|
+
price: 72,
|
|
133
|
+
qty: 1,
|
|
134
|
+
limited: false,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: '3',
|
|
138
|
+
name: 'Crypt Cereal Bowl',
|
|
139
|
+
variant: 'Speckled obsidian · 6 in',
|
|
140
|
+
price: 80,
|
|
141
|
+
qty: 1,
|
|
142
|
+
limited: true,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const TAX_RATE = 0.08;
|
|
147
|
+
const FREE_SHIPPING_THRESHOLD = 300;
|
|
148
|
+
const fmt = (n: number) => `$${n.toFixed(2)}`;
|
|
149
|
+
|
|
150
|
+
// ── Styles ────────────────────────────────────────────────────────────────────
|
|
151
|
+
// Plain inline styles using Astryx design-token CSS variables (declared at
|
|
152
|
+
// :root by `@astryxdesign/core/astryx.css`). No StyleX compiler required.
|
|
153
|
+
|
|
154
|
+
const fullWidth: CSSProperties = {width: '100%'};
|
|
155
|
+
// Form column flex-basis so the two checkout columns share width evenly.
|
|
156
|
+
const formColBasis: CSSProperties = {flexBasis: 0};
|
|
157
|
+
// Space the Order Summary content below its collapsible trigger title.
|
|
158
|
+
const summaryContent: CSSProperties = {paddingBlockStart: 'var(--spacing-2)'};
|
|
159
|
+
// Order-summary column: sticky beside the form on desktop.
|
|
160
|
+
const summarySticky: CSSProperties = {
|
|
161
|
+
flexBasis: 0,
|
|
162
|
+
position: 'sticky',
|
|
163
|
+
top: 'var(--spacing-4)',
|
|
164
|
+
alignSelf: 'flex-start',
|
|
165
|
+
};
|
|
166
|
+
// On mobile the summary moves above the form.
|
|
167
|
+
const summaryMobileOrder: CSSProperties = {order: -1};
|
|
168
|
+
// Express-checkout buttons (Dracula tokens stand in for brand colors).
|
|
169
|
+
const paypalButton: CSSProperties = {
|
|
170
|
+
backgroundColor: 'var(--color-warning)',
|
|
171
|
+
borderColor: 'var(--color-warning)',
|
|
172
|
+
};
|
|
173
|
+
// Google Pay button: widget-surface token stands in for the official dark
|
|
174
|
+
// button background (no raw hex on brand surfaces).
|
|
175
|
+
const gpayButton: CSSProperties = {
|
|
176
|
+
backgroundColor: 'var(--color-widget-background)',
|
|
177
|
+
borderColor: 'var(--color-widget-background)',
|
|
178
|
+
};
|
|
179
|
+
// Brand logos inside the express-checkout buttons.
|
|
180
|
+
const brandLogo: CSSProperties = {height: 'var(--spacing-5)', width: 'auto'};
|
|
181
|
+
// Accepted card-network marks (Visa/Mastercard/Amex), shared style.
|
|
182
|
+
const cardLogo: CSSProperties = {
|
|
183
|
+
height: 'var(--spacing-7)',
|
|
184
|
+
width: 'auto',
|
|
185
|
+
borderRadius: 'var(--radius-element)',
|
|
186
|
+
borderWidth: 'var(--border-width)',
|
|
187
|
+
borderStyle: 'solid',
|
|
188
|
+
borderColor: 'var(--color-border)',
|
|
189
|
+
backgroundColor: 'var(--color-background-surface)',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export default function PaymentForm() {
|
|
193
|
+
const isMobile = useMediaQuery('(max-width: 767px)');
|
|
194
|
+
const isStacked = useMediaQuery('(max-width: 1024px)');
|
|
195
|
+
const [firstName, setFirstName] = useState('');
|
|
196
|
+
const [lastName, setLastName] = useState('');
|
|
197
|
+
const [address, setAddress] = useState('');
|
|
198
|
+
const [city, setCity] = useState('');
|
|
199
|
+
const [zip, setZip] = useState('');
|
|
200
|
+
const [state, setState] = useState('');
|
|
201
|
+
const [phone, setPhone] = useState('');
|
|
202
|
+
const [saveInfo, setSaveInfo] = useState(false);
|
|
203
|
+
const [email, setEmail] = useState('');
|
|
204
|
+
const [emailOffers, setEmailOffers] = useState(false);
|
|
205
|
+
const [deliveryMethod, setDeliveryMethod] = useState('standard');
|
|
206
|
+
const [paymentMethod, _setPaymentMethod] = useState('card');
|
|
207
|
+
const [cardNumber, setCardNumber] = useState('');
|
|
208
|
+
const [expiry, setExpiry] = useState('');
|
|
209
|
+
const [expYear, setExpYear] = useState('');
|
|
210
|
+
const [billingMatchesShipping, setBillingMatchesShipping] = useState(true);
|
|
211
|
+
const [billingAddress, setBillingAddress] = useState('');
|
|
212
|
+
const [billingCity, setBillingCity] = useState('');
|
|
213
|
+
const [billingZip, setBillingZip] = useState('');
|
|
214
|
+
const [billingState, setBillingState] = useState('');
|
|
215
|
+
const [addGiftMessage, setAddGiftMessage] = useState(false);
|
|
216
|
+
const [giftTo, setGiftTo] = useState('');
|
|
217
|
+
const [giftFrom, setGiftFrom] = useState('');
|
|
218
|
+
const [giftMessage, setGiftMessage] = useState('');
|
|
219
|
+
const [cvc, setCvc] = useState('');
|
|
220
|
+
const [cardName, setCardName] = useState('');
|
|
221
|
+
const [promo, setPromo] = useState('');
|
|
222
|
+
const [quantities, setQuantities] = useState<Record<string, number>>({
|
|
223
|
+
'1': 1,
|
|
224
|
+
'2': 1,
|
|
225
|
+
'3': 1,
|
|
226
|
+
});
|
|
227
|
+
const [submitted, setSubmitted] = useState(false);
|
|
228
|
+
|
|
229
|
+
// Totals follow the editable quantities (and the free-shipping banner:
|
|
230
|
+
// orders at or over the threshold ship free).
|
|
231
|
+
const subtotal = ORDER_ITEMS.reduce(
|
|
232
|
+
(sum, item) => sum + item.price * (quantities[item.id] ?? item.qty),
|
|
233
|
+
0,
|
|
234
|
+
);
|
|
235
|
+
const shipping =
|
|
236
|
+
subtotal >= FREE_SHIPPING_THRESHOLD
|
|
237
|
+
? 0
|
|
238
|
+
: deliveryMethod === 'expedited'
|
|
239
|
+
? 9.95
|
|
240
|
+
: 4.95;
|
|
241
|
+
const tax = Math.round(subtotal * TAX_RATE * 100) / 100;
|
|
242
|
+
const total = subtotal + shipping + tax;
|
|
243
|
+
|
|
244
|
+
const errors = submitted
|
|
245
|
+
? {
|
|
246
|
+
firstName: !firstName.trim() ? 'Required' : undefined,
|
|
247
|
+
lastName: !lastName.trim() ? 'Required' : undefined,
|
|
248
|
+
address: !address.trim() ? 'Required' : undefined,
|
|
249
|
+
city: !city.trim() ? 'Required' : undefined,
|
|
250
|
+
zip: !zip.trim() ? 'Required' : undefined,
|
|
251
|
+
state: !state ? 'Required' : undefined,
|
|
252
|
+
email: !email.trim() ? 'Required' : undefined,
|
|
253
|
+
phone: !phone.trim() ? 'Required' : undefined,
|
|
254
|
+
expiry: !expiry ? 'Required' : undefined,
|
|
255
|
+
expYear: !expYear ? 'Required' : undefined,
|
|
256
|
+
cvc: !cvc.trim() ? 'Required' : undefined,
|
|
257
|
+
cardNumber:
|
|
258
|
+
paymentMethod === 'card' && !cardNumber.trim()
|
|
259
|
+
? 'Required'
|
|
260
|
+
: undefined,
|
|
261
|
+
cardName:
|
|
262
|
+
paymentMethod === 'card' && !cardName.trim() ? 'Required' : undefined,
|
|
263
|
+
billingAddress:
|
|
264
|
+
!billingMatchesShipping && !billingAddress.trim()
|
|
265
|
+
? 'Required'
|
|
266
|
+
: undefined,
|
|
267
|
+
billingCity:
|
|
268
|
+
!billingMatchesShipping && !billingCity.trim()
|
|
269
|
+
? 'Required'
|
|
270
|
+
: undefined,
|
|
271
|
+
billingZip:
|
|
272
|
+
!billingMatchesShipping && !billingZip.trim()
|
|
273
|
+
? 'Required'
|
|
274
|
+
: undefined,
|
|
275
|
+
billingState:
|
|
276
|
+
!billingMatchesShipping && !billingState ? 'Required' : undefined,
|
|
277
|
+
}
|
|
278
|
+
: {};
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<Layout
|
|
282
|
+
height="fill"
|
|
283
|
+
content={
|
|
284
|
+
<LayoutContent padding={0}>
|
|
285
|
+
<Center axis="horizontal">
|
|
286
|
+
<Section
|
|
287
|
+
variant="transparent"
|
|
288
|
+
maxWidth={1100}
|
|
289
|
+
width="100%"
|
|
290
|
+
padding={6}>
|
|
291
|
+
<VStack gap={5}>
|
|
292
|
+
{/* Page header */}
|
|
293
|
+
<VStack gap={6}>
|
|
294
|
+
<VStack gap={2}>
|
|
295
|
+
<Text type="display-1" as="h1">
|
|
296
|
+
Payment Request
|
|
297
|
+
</Text>
|
|
298
|
+
<Text type="body" color="secondary">
|
|
299
|
+
Review your order and complete your purchase. All
|
|
300
|
+
transactions are sealed with 256-bit SSL encryption.
|
|
301
|
+
</Text>
|
|
302
|
+
</VStack>
|
|
303
|
+
<Divider />
|
|
304
|
+
</VStack>
|
|
305
|
+
|
|
306
|
+
<Stack
|
|
307
|
+
direction={isStacked ? 'vertical' : 'horizontal'}
|
|
308
|
+
gap={8}
|
|
309
|
+
vAlign="start">
|
|
310
|
+
<StackItem
|
|
311
|
+
size="fill"
|
|
312
|
+
style={isStacked ? undefined : formColBasis}>
|
|
313
|
+
<VStack gap={8}>
|
|
314
|
+
{/* Sign in */}
|
|
315
|
+
<VStack gap={1}>
|
|
316
|
+
<HStack gap={2} hAlign="between" vAlign="center">
|
|
317
|
+
<Text type="large" weight="bold">
|
|
318
|
+
Sign in to check out
|
|
319
|
+
</Text>
|
|
320
|
+
<Button
|
|
321
|
+
label="Sign In"
|
|
322
|
+
variant="secondary"
|
|
323
|
+
size="sm"
|
|
324
|
+
onClick={() => {}}
|
|
325
|
+
/>
|
|
326
|
+
</HStack>
|
|
327
|
+
<Text type="body" color="secondary">
|
|
328
|
+
Sign in to track your order and save your information
|
|
329
|
+
for faster checkout.
|
|
330
|
+
</Text>
|
|
331
|
+
</VStack>
|
|
332
|
+
|
|
333
|
+
{/* Contact Information */}
|
|
334
|
+
<VStack gap={3}>
|
|
335
|
+
<Text type="large" weight="bold">
|
|
336
|
+
Contact Information
|
|
337
|
+
</Text>
|
|
338
|
+
<TextInput
|
|
339
|
+
size="lg"
|
|
340
|
+
label="Email"
|
|
341
|
+
placeholder="you@example.com"
|
|
342
|
+
value={email}
|
|
343
|
+
onChange={setEmail}
|
|
344
|
+
status={
|
|
345
|
+
errors.email
|
|
346
|
+
? {type: 'error', message: errors.email}
|
|
347
|
+
: undefined
|
|
348
|
+
}
|
|
349
|
+
/>
|
|
350
|
+
<CheckboxInput
|
|
351
|
+
label="Email me with news and offers"
|
|
352
|
+
value={emailOffers}
|
|
353
|
+
onChange={setEmailOffers}
|
|
354
|
+
/>
|
|
355
|
+
</VStack>
|
|
356
|
+
|
|
357
|
+
{/* Shipping Information */}
|
|
358
|
+
<VStack gap={3}>
|
|
359
|
+
<Text type="large" weight="bold">
|
|
360
|
+
Shipping Information
|
|
361
|
+
</Text>
|
|
362
|
+
<Grid columns={isMobile ? 1 : 2} gap={3}>
|
|
363
|
+
<TextInput
|
|
364
|
+
size="lg"
|
|
365
|
+
label="First Name"
|
|
366
|
+
placeholder="John"
|
|
367
|
+
value={firstName}
|
|
368
|
+
onChange={setFirstName}
|
|
369
|
+
status={
|
|
370
|
+
errors.firstName
|
|
371
|
+
? {type: 'error', message: errors.firstName}
|
|
372
|
+
: undefined
|
|
373
|
+
}
|
|
374
|
+
/>
|
|
375
|
+
<TextInput
|
|
376
|
+
size="lg"
|
|
377
|
+
label="Last Name"
|
|
378
|
+
placeholder="Doe"
|
|
379
|
+
value={lastName}
|
|
380
|
+
onChange={setLastName}
|
|
381
|
+
status={
|
|
382
|
+
errors.lastName
|
|
383
|
+
? {type: 'error', message: errors.lastName}
|
|
384
|
+
: undefined
|
|
385
|
+
}
|
|
386
|
+
/>
|
|
387
|
+
</Grid>
|
|
388
|
+
<TextInput
|
|
389
|
+
size="lg"
|
|
390
|
+
label="Address"
|
|
391
|
+
placeholder="123 Main Street"
|
|
392
|
+
value={address}
|
|
393
|
+
onChange={setAddress}
|
|
394
|
+
status={
|
|
395
|
+
errors.address
|
|
396
|
+
? {type: 'error', message: errors.address}
|
|
397
|
+
: undefined
|
|
398
|
+
}
|
|
399
|
+
/>
|
|
400
|
+
<Grid columns={isMobile ? 1 : 2} gap={3}>
|
|
401
|
+
<TextInput
|
|
402
|
+
size="lg"
|
|
403
|
+
label="City"
|
|
404
|
+
placeholder="New York"
|
|
405
|
+
value={city}
|
|
406
|
+
onChange={setCity}
|
|
407
|
+
status={
|
|
408
|
+
errors.city
|
|
409
|
+
? {type: 'error', message: errors.city}
|
|
410
|
+
: undefined
|
|
411
|
+
}
|
|
412
|
+
/>
|
|
413
|
+
<TextInput
|
|
414
|
+
size="lg"
|
|
415
|
+
label="ZIP Code"
|
|
416
|
+
placeholder="10001"
|
|
417
|
+
value={zip}
|
|
418
|
+
onChange={setZip}
|
|
419
|
+
status={
|
|
420
|
+
errors.zip
|
|
421
|
+
? {type: 'error', message: errors.zip}
|
|
422
|
+
: undefined
|
|
423
|
+
}
|
|
424
|
+
/>
|
|
425
|
+
</Grid>
|
|
426
|
+
<Selector
|
|
427
|
+
size="lg"
|
|
428
|
+
label="State"
|
|
429
|
+
placeholder="Select state"
|
|
430
|
+
options={US_STATES}
|
|
431
|
+
value={state}
|
|
432
|
+
onChange={setState}
|
|
433
|
+
status={
|
|
434
|
+
errors.state
|
|
435
|
+
? {type: 'error', message: errors.state}
|
|
436
|
+
: undefined
|
|
437
|
+
}
|
|
438
|
+
/>
|
|
439
|
+
<TextInput
|
|
440
|
+
size="lg"
|
|
441
|
+
label="Phone Number"
|
|
442
|
+
placeholder="+1 (555) 123-4567"
|
|
443
|
+
value={phone}
|
|
444
|
+
onChange={setPhone}
|
|
445
|
+
labelTooltip="We use your phone number to provide shipping updates and contact you about your delivery if needed."
|
|
446
|
+
status={
|
|
447
|
+
errors.phone
|
|
448
|
+
? {type: 'error', message: errors.phone}
|
|
449
|
+
: undefined
|
|
450
|
+
}
|
|
451
|
+
/>
|
|
452
|
+
<CheckboxInput
|
|
453
|
+
label="Save my information for a faster checkout"
|
|
454
|
+
value={saveInfo}
|
|
455
|
+
onChange={setSaveInfo}
|
|
456
|
+
/>
|
|
457
|
+
</VStack>
|
|
458
|
+
|
|
459
|
+
{/* Delivery */}
|
|
460
|
+
<VStack gap={3}>
|
|
461
|
+
<VStack gap={1}>
|
|
462
|
+
<Text type="large" weight="bold">
|
|
463
|
+
Delivery
|
|
464
|
+
</Text>
|
|
465
|
+
<Text type="body" color="secondary">
|
|
466
|
+
Please allow 1–3 business days processing time
|
|
467
|
+
before your order ships.
|
|
468
|
+
</Text>
|
|
469
|
+
</VStack>
|
|
470
|
+
<RadioList
|
|
471
|
+
label="Delivery method"
|
|
472
|
+
value={deliveryMethod}
|
|
473
|
+
onChange={setDeliveryMethod}>
|
|
474
|
+
<RadioListItem
|
|
475
|
+
value="standard"
|
|
476
|
+
label="Standard (3–7 business days)"
|
|
477
|
+
endContent={
|
|
478
|
+
<Text
|
|
479
|
+
type="body"
|
|
480
|
+
weight="medium"
|
|
481
|
+
hasTabularNumbers>
|
|
482
|
+
$4.95
|
|
483
|
+
</Text>
|
|
484
|
+
}
|
|
485
|
+
/>
|
|
486
|
+
<RadioListItem
|
|
487
|
+
value="expedited"
|
|
488
|
+
label="Expedited (1–2 business days)"
|
|
489
|
+
endContent={
|
|
490
|
+
<Text
|
|
491
|
+
type="body"
|
|
492
|
+
weight="medium"
|
|
493
|
+
hasTabularNumbers>
|
|
494
|
+
$9.95
|
|
495
|
+
</Text>
|
|
496
|
+
}
|
|
497
|
+
/>
|
|
498
|
+
</RadioList>
|
|
499
|
+
</VStack>
|
|
500
|
+
|
|
501
|
+
{/* Payment Method */}
|
|
502
|
+
<VStack gap={3}>
|
|
503
|
+
<VStack gap={1}>
|
|
504
|
+
<Text type="large" weight="bold">
|
|
505
|
+
Payment Method
|
|
506
|
+
</Text>
|
|
507
|
+
<Text type="body" color="secondary">
|
|
508
|
+
All transactions are secure and encrypted.
|
|
509
|
+
</Text>
|
|
510
|
+
</VStack>
|
|
511
|
+
|
|
512
|
+
{/* Express checkout */}
|
|
513
|
+
<VStack gap={3}>
|
|
514
|
+
<Grid columns={isMobile ? 1 : 2} gap={3}>
|
|
515
|
+
{/* PayPal */}
|
|
516
|
+
<Button
|
|
517
|
+
label="PayPal"
|
|
518
|
+
variant="primary"
|
|
519
|
+
size="lg"
|
|
520
|
+
onClick={() => {}}
|
|
521
|
+
style={paypalButton}>
|
|
522
|
+
<img
|
|
523
|
+
src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png"
|
|
524
|
+
alt="PayPal"
|
|
525
|
+
style={brandLogo}
|
|
526
|
+
/>
|
|
527
|
+
</Button>
|
|
528
|
+
{/* Google Pay */}
|
|
529
|
+
<Button
|
|
530
|
+
label="Google Pay"
|
|
531
|
+
variant="primary"
|
|
532
|
+
size="lg"
|
|
533
|
+
onClick={() => {}}
|
|
534
|
+
style={gpayButton}>
|
|
535
|
+
<img
|
|
536
|
+
src="https://pay.google.com/about/static_kcs/images/logos/google-pay-logo.svg"
|
|
537
|
+
alt="Google Pay"
|
|
538
|
+
style={brandLogo}
|
|
539
|
+
/>
|
|
540
|
+
</Button>
|
|
541
|
+
</Grid>
|
|
542
|
+
</VStack>
|
|
543
|
+
|
|
544
|
+
{/* OR divider */}
|
|
545
|
+
<HStack gap={3} vAlign="center">
|
|
546
|
+
<StackItem size="fill">
|
|
547
|
+
<Divider />
|
|
548
|
+
</StackItem>
|
|
549
|
+
<Text type="supporting" color="secondary">
|
|
550
|
+
OR
|
|
551
|
+
</Text>
|
|
552
|
+
<StackItem size="fill">
|
|
553
|
+
<Divider />
|
|
554
|
+
</StackItem>
|
|
555
|
+
</HStack>
|
|
556
|
+
|
|
557
|
+
{/* Credit card fields */}
|
|
558
|
+
<VStack gap={3}>
|
|
559
|
+
{/* Card type icons */}
|
|
560
|
+
<HStack gap={1.5} vAlign="center">
|
|
561
|
+
<img
|
|
562
|
+
src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/visa.svg"
|
|
563
|
+
alt="Visa"
|
|
564
|
+
style={cardLogo}
|
|
565
|
+
/>
|
|
566
|
+
<img
|
|
567
|
+
src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/mastercard.svg"
|
|
568
|
+
alt="Mastercard"
|
|
569
|
+
style={cardLogo}
|
|
570
|
+
/>
|
|
571
|
+
<img
|
|
572
|
+
src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/amex.svg"
|
|
573
|
+
alt="Amex"
|
|
574
|
+
style={cardLogo}
|
|
575
|
+
/>
|
|
576
|
+
</HStack>
|
|
577
|
+
<TextInput
|
|
578
|
+
size="lg"
|
|
579
|
+
label="Card Number"
|
|
580
|
+
placeholder="1234 5678 9012 3456"
|
|
581
|
+
value={cardNumber}
|
|
582
|
+
onChange={setCardNumber}
|
|
583
|
+
status={
|
|
584
|
+
errors.cardNumber
|
|
585
|
+
? {type: 'error', message: errors.cardNumber}
|
|
586
|
+
: undefined
|
|
587
|
+
}
|
|
588
|
+
/>
|
|
589
|
+
<Grid columns={isMobile ? 1 : 3} gap={3}>
|
|
590
|
+
<Selector
|
|
591
|
+
size="lg"
|
|
592
|
+
label="Expiry Month"
|
|
593
|
+
placeholder="MM"
|
|
594
|
+
options={MONTHS}
|
|
595
|
+
value={expiry}
|
|
596
|
+
onChange={setExpiry}
|
|
597
|
+
status={
|
|
598
|
+
errors.expiry
|
|
599
|
+
? {type: 'error', message: errors.expiry}
|
|
600
|
+
: undefined
|
|
601
|
+
}
|
|
602
|
+
/>
|
|
603
|
+
<Selector
|
|
604
|
+
size="lg"
|
|
605
|
+
label="Expiry Year"
|
|
606
|
+
placeholder="YY"
|
|
607
|
+
options={YEARS}
|
|
608
|
+
value={expYear}
|
|
609
|
+
onChange={setExpYear}
|
|
610
|
+
status={
|
|
611
|
+
errors.expYear
|
|
612
|
+
? {type: 'error', message: errors.expYear}
|
|
613
|
+
: undefined
|
|
614
|
+
}
|
|
615
|
+
/>
|
|
616
|
+
<TextInput
|
|
617
|
+
size="lg"
|
|
618
|
+
label="CVC"
|
|
619
|
+
placeholder="123"
|
|
620
|
+
value={cvc}
|
|
621
|
+
onChange={setCvc}
|
|
622
|
+
labelTooltip="3-digit security code usually found on the back of your card. American Express cards have a 4-digit code located on the front."
|
|
623
|
+
status={
|
|
624
|
+
errors.cvc
|
|
625
|
+
? {type: 'error', message: errors.cvc}
|
|
626
|
+
: undefined
|
|
627
|
+
}
|
|
628
|
+
/>
|
|
629
|
+
</Grid>
|
|
630
|
+
<TextInput
|
|
631
|
+
size="lg"
|
|
632
|
+
label="Name on Card"
|
|
633
|
+
placeholder="John Doe"
|
|
634
|
+
value={cardName}
|
|
635
|
+
onChange={setCardName}
|
|
636
|
+
status={
|
|
637
|
+
errors.cardName
|
|
638
|
+
? {type: 'error', message: errors.cardName}
|
|
639
|
+
: undefined
|
|
640
|
+
}
|
|
641
|
+
/>
|
|
642
|
+
<CheckboxInput
|
|
643
|
+
label="Use shipping address as billing address"
|
|
644
|
+
value={billingMatchesShipping}
|
|
645
|
+
onChange={setBillingMatchesShipping}
|
|
646
|
+
/>
|
|
647
|
+
{!billingMatchesShipping && (
|
|
648
|
+
<VStack gap={3}>
|
|
649
|
+
<TextInput
|
|
650
|
+
size="lg"
|
|
651
|
+
label="Address"
|
|
652
|
+
placeholder="123 Main Street"
|
|
653
|
+
value={billingAddress}
|
|
654
|
+
onChange={setBillingAddress}
|
|
655
|
+
status={
|
|
656
|
+
errors.billingAddress
|
|
657
|
+
? {
|
|
658
|
+
type: 'error',
|
|
659
|
+
message: errors.billingAddress,
|
|
660
|
+
}
|
|
661
|
+
: undefined
|
|
662
|
+
}
|
|
663
|
+
/>
|
|
664
|
+
<Grid columns={isMobile ? 1 : 2} gap={3}>
|
|
665
|
+
<TextInput
|
|
666
|
+
size="lg"
|
|
667
|
+
label="City"
|
|
668
|
+
placeholder="New York"
|
|
669
|
+
value={billingCity}
|
|
670
|
+
onChange={setBillingCity}
|
|
671
|
+
status={
|
|
672
|
+
errors.billingCity
|
|
673
|
+
? {
|
|
674
|
+
type: 'error',
|
|
675
|
+
message: errors.billingCity,
|
|
676
|
+
}
|
|
677
|
+
: undefined
|
|
678
|
+
}
|
|
679
|
+
/>
|
|
680
|
+
<TextInput
|
|
681
|
+
size="lg"
|
|
682
|
+
label="ZIP Code"
|
|
683
|
+
placeholder="10001"
|
|
684
|
+
value={billingZip}
|
|
685
|
+
onChange={setBillingZip}
|
|
686
|
+
status={
|
|
687
|
+
errors.billingZip
|
|
688
|
+
? {
|
|
689
|
+
type: 'error',
|
|
690
|
+
message: errors.billingZip,
|
|
691
|
+
}
|
|
692
|
+
: undefined
|
|
693
|
+
}
|
|
694
|
+
/>
|
|
695
|
+
</Grid>
|
|
696
|
+
<Selector
|
|
697
|
+
size="lg"
|
|
698
|
+
label="State"
|
|
699
|
+
placeholder="Select state"
|
|
700
|
+
options={US_STATES}
|
|
701
|
+
value={billingState}
|
|
702
|
+
onChange={setBillingState}
|
|
703
|
+
status={
|
|
704
|
+
errors.billingState
|
|
705
|
+
? {
|
|
706
|
+
type: 'error',
|
|
707
|
+
message: errors.billingState,
|
|
708
|
+
}
|
|
709
|
+
: undefined
|
|
710
|
+
}
|
|
711
|
+
/>
|
|
712
|
+
</VStack>
|
|
713
|
+
)}
|
|
714
|
+
</VStack>
|
|
715
|
+
</VStack>
|
|
716
|
+
|
|
717
|
+
{/* Promo Code */}
|
|
718
|
+
<VStack gap={3}>
|
|
719
|
+
<Text type="large" weight="bold">
|
|
720
|
+
Promo Code
|
|
721
|
+
</Text>
|
|
722
|
+
<HStack gap={2} vAlign="end">
|
|
723
|
+
<StackItem size="fill">
|
|
724
|
+
<TextInput
|
|
725
|
+
size="lg"
|
|
726
|
+
label="Promo code"
|
|
727
|
+
placeholder="Enter promo code"
|
|
728
|
+
value={promo}
|
|
729
|
+
onChange={setPromo}
|
|
730
|
+
style={fullWidth}
|
|
731
|
+
/>
|
|
732
|
+
</StackItem>
|
|
733
|
+
<Button
|
|
734
|
+
label="Apply"
|
|
735
|
+
variant="secondary"
|
|
736
|
+
size="lg"
|
|
737
|
+
onClick={() => {}}
|
|
738
|
+
/>
|
|
739
|
+
</HStack>
|
|
740
|
+
</VStack>
|
|
741
|
+
|
|
742
|
+
{/* Gift Options */}
|
|
743
|
+
<VStack gap={3}>
|
|
744
|
+
<Text type="large" weight="bold">
|
|
745
|
+
Gift Options
|
|
746
|
+
</Text>
|
|
747
|
+
<CheckboxInput
|
|
748
|
+
label="Add a gift message"
|
|
749
|
+
value={addGiftMessage}
|
|
750
|
+
onChange={setAddGiftMessage}
|
|
751
|
+
/>
|
|
752
|
+
{addGiftMessage && (
|
|
753
|
+
<VStack gap={3}>
|
|
754
|
+
<Grid columns={isMobile ? 1 : 2} gap={3}>
|
|
755
|
+
<TextInput
|
|
756
|
+
size="lg"
|
|
757
|
+
label="To"
|
|
758
|
+
isLabelHidden
|
|
759
|
+
placeholder="To"
|
|
760
|
+
value={giftTo}
|
|
761
|
+
onChange={setGiftTo}
|
|
762
|
+
/>
|
|
763
|
+
<TextInput
|
|
764
|
+
size="lg"
|
|
765
|
+
label="From"
|
|
766
|
+
isLabelHidden
|
|
767
|
+
placeholder="From"
|
|
768
|
+
value={giftFrom}
|
|
769
|
+
onChange={setGiftFrom}
|
|
770
|
+
/>
|
|
771
|
+
</Grid>
|
|
772
|
+
<TextArea
|
|
773
|
+
label="Gift message"
|
|
774
|
+
isLabelHidden
|
|
775
|
+
placeholder="Write something here"
|
|
776
|
+
value={giftMessage}
|
|
777
|
+
onChange={setGiftMessage}
|
|
778
|
+
/>
|
|
779
|
+
</VStack>
|
|
780
|
+
)}
|
|
781
|
+
</VStack>
|
|
782
|
+
|
|
783
|
+
{/* Trust bar + CTAs + policy links */}
|
|
784
|
+
<VStack gap={4}>
|
|
785
|
+
<HStack gap={5} hAlign="center" wrap="wrap">
|
|
786
|
+
<HStack gap={1} vAlign="center">
|
|
787
|
+
<Icon
|
|
788
|
+
icon={ShieldCheck}
|
|
789
|
+
size="sm"
|
|
790
|
+
color="secondary"
|
|
791
|
+
/>
|
|
792
|
+
<Text type="supporting" color="secondary">
|
|
793
|
+
Secure Payment
|
|
794
|
+
</Text>
|
|
795
|
+
</HStack>
|
|
796
|
+
<HStack gap={1} vAlign="center">
|
|
797
|
+
<Icon
|
|
798
|
+
icon={Lock}
|
|
799
|
+
size="sm"
|
|
800
|
+
color="secondary"
|
|
801
|
+
/>
|
|
802
|
+
<Text type="supporting" color="secondary">
|
|
803
|
+
SSL Encrypted
|
|
804
|
+
</Text>
|
|
805
|
+
</HStack>
|
|
806
|
+
<HStack gap={1} vAlign="center">
|
|
807
|
+
<Icon
|
|
808
|
+
icon={CircleCheck}
|
|
809
|
+
size="sm"
|
|
810
|
+
color="secondary"
|
|
811
|
+
/>
|
|
812
|
+
<Text type="supporting" color="secondary">
|
|
813
|
+
Free Returns
|
|
814
|
+
</Text>
|
|
815
|
+
</HStack>
|
|
816
|
+
</HStack>
|
|
817
|
+
<VStack gap={2}>
|
|
818
|
+
<Button
|
|
819
|
+
label="Place Order"
|
|
820
|
+
variant="primary"
|
|
821
|
+
size="lg"
|
|
822
|
+
style={fullWidth}
|
|
823
|
+
onClick={() => setSubmitted(true)}
|
|
824
|
+
/>
|
|
825
|
+
<Button
|
|
826
|
+
label="Continue Shopping"
|
|
827
|
+
variant="secondary"
|
|
828
|
+
size="lg"
|
|
829
|
+
style={fullWidth}
|
|
830
|
+
onClick={() => {}}
|
|
831
|
+
/>
|
|
832
|
+
</VStack>
|
|
833
|
+
<Divider />
|
|
834
|
+
<HStack gap={4} vAlign="center" wrap="wrap">
|
|
835
|
+
<Link href="#/templates/payment-form" type="supporting">
|
|
836
|
+
Refund policy
|
|
837
|
+
</Link>
|
|
838
|
+
<Link href="#/templates/payment-form" type="supporting">
|
|
839
|
+
Privacy policy
|
|
840
|
+
</Link>
|
|
841
|
+
<Link href="#/templates/payment-form" type="supporting">
|
|
842
|
+
Terms of service
|
|
843
|
+
</Link>
|
|
844
|
+
<Link href="#/templates/payment-form" type="supporting">
|
|
845
|
+
Cancellations
|
|
846
|
+
</Link>
|
|
847
|
+
</HStack>
|
|
848
|
+
</VStack>
|
|
849
|
+
</VStack>
|
|
850
|
+
</StackItem>
|
|
851
|
+
|
|
852
|
+
<StackItem
|
|
853
|
+
size="fill"
|
|
854
|
+
style={isStacked ? summaryMobileOrder : summarySticky}>
|
|
855
|
+
<Card padding={5}>
|
|
856
|
+
<VStack gap={4}>
|
|
857
|
+
{/* Accordion header — clickable on mobile only */}
|
|
858
|
+
<Collapsible
|
|
859
|
+
trigger="Order Summary"
|
|
860
|
+
defaultIsOpen={true}>
|
|
861
|
+
<VStack gap={4} style={summaryContent}>
|
|
862
|
+
{/* Line items */}
|
|
863
|
+
{ORDER_ITEMS.map(item => (
|
|
864
|
+
<VStack key={item.id} gap={3}>
|
|
865
|
+
<HStack gap={3} vAlign="start">
|
|
866
|
+
<Thumbnail
|
|
867
|
+
src={ITEM_IMAGES[item.id].src}
|
|
868
|
+
alt={item.name}
|
|
869
|
+
/>
|
|
870
|
+
<StackItem size="fill">
|
|
871
|
+
<VStack gap={1}>
|
|
872
|
+
<HStack
|
|
873
|
+
gap={2}
|
|
874
|
+
hAlign="between"
|
|
875
|
+
vAlign="start">
|
|
876
|
+
<HStack gap={2} vAlign="center" wrap="wrap">
|
|
877
|
+
<Text type="body" weight="medium">
|
|
878
|
+
{item.name}
|
|
879
|
+
</Text>
|
|
880
|
+
{item.limited && (
|
|
881
|
+
<Badge
|
|
882
|
+
variant="yellow"
|
|
883
|
+
label="LIMITED EDITION"
|
|
884
|
+
/>
|
|
885
|
+
)}
|
|
886
|
+
</HStack>
|
|
887
|
+
<Text
|
|
888
|
+
type="body"
|
|
889
|
+
weight="bold"
|
|
890
|
+
hasTabularNumbers>
|
|
891
|
+
{fmt(item.price)}
|
|
892
|
+
</Text>
|
|
893
|
+
</HStack>
|
|
894
|
+
<Text type="supporting" color="secondary">
|
|
895
|
+
{item.variant}
|
|
896
|
+
</Text>
|
|
897
|
+
<HStack gap={2} vAlign="end" wrap="wrap">
|
|
898
|
+
<NumberInput
|
|
899
|
+
label="Qty"
|
|
900
|
+
value={
|
|
901
|
+
quantities[item.id] ?? item.qty
|
|
902
|
+
}
|
|
903
|
+
onChange={v =>
|
|
904
|
+
setQuantities(q => ({
|
|
905
|
+
...q,
|
|
906
|
+
[item.id]: v,
|
|
907
|
+
}))
|
|
908
|
+
}
|
|
909
|
+
min={1}
|
|
910
|
+
max={10}
|
|
911
|
+
isIntegerOnly
|
|
912
|
+
/>
|
|
913
|
+
<Link href="#/templates/payment-form" type="supporting">
|
|
914
|
+
Remove
|
|
915
|
+
</Link>
|
|
916
|
+
<Link href="#/templates/payment-form" type="supporting">
|
|
917
|
+
Save
|
|
918
|
+
</Link>
|
|
919
|
+
</HStack>
|
|
920
|
+
</VStack>
|
|
921
|
+
</StackItem>
|
|
922
|
+
</HStack>
|
|
923
|
+
<Divider />
|
|
924
|
+
</VStack>
|
|
925
|
+
))}
|
|
926
|
+
|
|
927
|
+
{/* Order total subsection */}
|
|
928
|
+
<VStack gap={3}>
|
|
929
|
+
<Text type="large" weight="bold">
|
|
930
|
+
Order Total
|
|
931
|
+
</Text>
|
|
932
|
+
<VStack gap={2}>
|
|
933
|
+
<HStack hAlign="between" vAlign="center">
|
|
934
|
+
<Text type="body" color="secondary">
|
|
935
|
+
Subtotal
|
|
936
|
+
</Text>
|
|
937
|
+
<Text type="body" hasTabularNumbers>
|
|
938
|
+
{fmt(subtotal)}
|
|
939
|
+
</Text>
|
|
940
|
+
</HStack>
|
|
941
|
+
<HStack hAlign="between" vAlign="center">
|
|
942
|
+
<Text type="body" color="secondary">
|
|
943
|
+
Shipping
|
|
944
|
+
</Text>
|
|
945
|
+
<Text type="body" hasTabularNumbers>
|
|
946
|
+
{fmt(shipping)}
|
|
947
|
+
</Text>
|
|
948
|
+
</HStack>
|
|
949
|
+
<HStack hAlign="between" vAlign="center">
|
|
950
|
+
<Text type="body" color="secondary">
|
|
951
|
+
Tax
|
|
952
|
+
</Text>
|
|
953
|
+
<Text type="body" hasTabularNumbers>
|
|
954
|
+
{fmt(tax)}
|
|
955
|
+
</Text>
|
|
956
|
+
</HStack>
|
|
957
|
+
</VStack>
|
|
958
|
+
<Divider />
|
|
959
|
+
<HStack hAlign="between" vAlign="center">
|
|
960
|
+
<Text type="large" weight="bold">
|
|
961
|
+
Total
|
|
962
|
+
</Text>
|
|
963
|
+
<Text
|
|
964
|
+
type="large"
|
|
965
|
+
weight="bold"
|
|
966
|
+
hasTabularNumbers>
|
|
967
|
+
{fmt(total)}
|
|
968
|
+
</Text>
|
|
969
|
+
</HStack>
|
|
970
|
+
<Banner
|
|
971
|
+
status="info"
|
|
972
|
+
icon={<Icon icon={Truck} size="sm" />}
|
|
973
|
+
title="Free shipping on orders over $300"
|
|
974
|
+
/>
|
|
975
|
+
</VStack>
|
|
976
|
+
</VStack>
|
|
977
|
+
</Collapsible>
|
|
978
|
+
</VStack>
|
|
979
|
+
{/* end outer card VStack gap={4} */}
|
|
980
|
+
</Card>
|
|
981
|
+
</StackItem>
|
|
982
|
+
</Stack>
|
|
983
|
+
</VStack>
|
|
984
|
+
</Section>
|
|
985
|
+
</Center>
|
|
986
|
+
</LayoutContent>
|
|
987
|
+
}
|
|
988
|
+
/>
|
|
989
|
+
);
|
|
990
|
+
}
|