create-brainerce-store 1.2.0 → 1.3.1
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/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/nextjs/base/src/app/.well-known/apple-developer-merchantid-domain-association/route.ts +26 -0
- package/templates/nextjs/base/src/app/checkout/page.tsx +151 -17
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +198 -191
- package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +62 -0
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +292 -124
- package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +195 -0
- package/templates/nextjs/base/src/components/checkout/tax-display.tsx +62 -62
|
@@ -1,191 +1,198 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Suspense, useEffect, useState } from 'react';
|
|
4
|
-
import { useSearchParams } from 'next/navigation';
|
|
5
|
-
import Link from 'next/link';
|
|
6
|
-
import type { WaitForOrderResult } from 'brainerce';
|
|
7
|
-
import { getClient } from '@/lib/brainerce';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<Link
|
|
126
|
-
href="/
|
|
127
|
-
className="
|
|
128
|
-
>
|
|
129
|
-
|
|
130
|
-
</Link>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
</
|
|
159
|
-
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
<Link
|
|
169
|
-
href="/
|
|
170
|
-
className="
|
|
171
|
-
>
|
|
172
|
-
|
|
173
|
-
</Link>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Suspense, useEffect, useState } from 'react';
|
|
4
|
+
import { useSearchParams } from 'next/navigation';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import type { WaitForOrderResult } from 'brainerce';
|
|
7
|
+
import { getClient } from '@/lib/brainerce';
|
|
8
|
+
import { useCart } from '@/providers/store-provider';
|
|
9
|
+
import { LoadingSpinner } from '@/components/shared/loading-spinner';
|
|
10
|
+
|
|
11
|
+
function OrderConfirmationContent() {
|
|
12
|
+
const searchParams = useSearchParams();
|
|
13
|
+
const checkoutId = searchParams.get('checkout_id');
|
|
14
|
+
|
|
15
|
+
const { refreshCart } = useCart();
|
|
16
|
+
const [result, setResult] = useState<WaitForOrderResult | null>(null);
|
|
17
|
+
const [loading, setLoading] = useState(true);
|
|
18
|
+
const [error, setError] = useState<string | null>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!checkoutId) {
|
|
22
|
+
setError('Missing checkout information.');
|
|
23
|
+
setLoading(false);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function waitForOrder() {
|
|
28
|
+
try {
|
|
29
|
+
const client = getClient();
|
|
30
|
+
|
|
31
|
+
// Clear cart state after successful payment
|
|
32
|
+
client.handlePaymentSuccess(checkoutId!);
|
|
33
|
+
await refreshCart();
|
|
34
|
+
|
|
35
|
+
const orderResult = await client.waitForOrder(checkoutId!, {
|
|
36
|
+
maxWaitMs: 30000,
|
|
37
|
+
});
|
|
38
|
+
setResult(orderResult);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
const message = err instanceof Error ? err.message : 'Failed to confirm order';
|
|
41
|
+
setError(message);
|
|
42
|
+
} finally {
|
|
43
|
+
setLoading(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
waitForOrder();
|
|
48
|
+
}, [checkoutId, refreshCart]);
|
|
49
|
+
|
|
50
|
+
if (loading) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="flex min-h-[60vh] flex-col items-center justify-center">
|
|
53
|
+
<LoadingSpinner size="lg" />
|
|
54
|
+
<p className="text-muted-foreground mt-4">Confirming your order...</p>
|
|
55
|
+
<p className="text-muted-foreground mt-1 text-xs">This may take a few seconds.</p>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (error) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
63
|
+
<svg
|
|
64
|
+
className="text-destructive mx-auto mb-4 h-16 w-16"
|
|
65
|
+
fill="none"
|
|
66
|
+
viewBox="0 0 24 24"
|
|
67
|
+
stroke="currentColor"
|
|
68
|
+
>
|
|
69
|
+
<path
|
|
70
|
+
strokeLinecap="round"
|
|
71
|
+
strokeLinejoin="round"
|
|
72
|
+
strokeWidth={1.5}
|
|
73
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.834-2.694-.834-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
76
|
+
<h1 className="text-foreground text-2xl font-bold">Something went wrong</h1>
|
|
77
|
+
<p className="text-muted-foreground mt-2">{error}</p>
|
|
78
|
+
<p className="text-muted-foreground mt-1 text-sm">
|
|
79
|
+
If you were charged, your order may still be processing. Please check your email for a
|
|
80
|
+
confirmation.
|
|
81
|
+
</p>
|
|
82
|
+
<Link
|
|
83
|
+
href="/"
|
|
84
|
+
className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
85
|
+
>
|
|
86
|
+
Return Home
|
|
87
|
+
</Link>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Order was created successfully
|
|
93
|
+
if (result?.success) {
|
|
94
|
+
const orderNumber = result.status.orderNumber;
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
98
|
+
<svg
|
|
99
|
+
className="text-primary mx-auto mb-4 h-16 w-16"
|
|
100
|
+
fill="none"
|
|
101
|
+
viewBox="0 0 24 24"
|
|
102
|
+
stroke="currentColor"
|
|
103
|
+
>
|
|
104
|
+
<path
|
|
105
|
+
strokeLinecap="round"
|
|
106
|
+
strokeLinejoin="round"
|
|
107
|
+
strokeWidth={1.5}
|
|
108
|
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
109
|
+
/>
|
|
110
|
+
</svg>
|
|
111
|
+
|
|
112
|
+
<h1 className="text-foreground text-2xl font-bold">Thank you for your order!</h1>
|
|
113
|
+
|
|
114
|
+
{orderNumber && (
|
|
115
|
+
<p className="text-foreground mt-3 text-lg">
|
|
116
|
+
Order Number: <span className="font-semibold">{orderNumber}</span>
|
|
117
|
+
</p>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
<p className="text-muted-foreground mt-2">
|
|
121
|
+
We've sent a confirmation email with your order details.
|
|
122
|
+
</p>
|
|
123
|
+
|
|
124
|
+
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
|
125
|
+
<Link
|
|
126
|
+
href="/products"
|
|
127
|
+
className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
128
|
+
>
|
|
129
|
+
Continue Shopping
|
|
130
|
+
</Link>
|
|
131
|
+
|
|
132
|
+
<Link
|
|
133
|
+
href="/account"
|
|
134
|
+
className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
|
|
135
|
+
>
|
|
136
|
+
View Orders
|
|
137
|
+
</Link>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Order not yet confirmed (polling timed out) - still show success
|
|
144
|
+
return (
|
|
145
|
+
<div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
146
|
+
<svg
|
|
147
|
+
className="text-primary mx-auto mb-4 h-16 w-16"
|
|
148
|
+
fill="none"
|
|
149
|
+
viewBox="0 0 24 24"
|
|
150
|
+
stroke="currentColor"
|
|
151
|
+
>
|
|
152
|
+
<path
|
|
153
|
+
strokeLinecap="round"
|
|
154
|
+
strokeLinejoin="round"
|
|
155
|
+
strokeWidth={1.5}
|
|
156
|
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
157
|
+
/>
|
|
158
|
+
</svg>
|
|
159
|
+
|
|
160
|
+
<h1 className="text-foreground text-2xl font-bold">Payment received!</h1>
|
|
161
|
+
|
|
162
|
+
<p className="text-muted-foreground mt-2">
|
|
163
|
+
Your order is being processed. You'll receive a confirmation email shortly with your
|
|
164
|
+
order details.
|
|
165
|
+
</p>
|
|
166
|
+
|
|
167
|
+
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
|
168
|
+
<Link
|
|
169
|
+
href="/products"
|
|
170
|
+
className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
171
|
+
>
|
|
172
|
+
Continue Shopping
|
|
173
|
+
</Link>
|
|
174
|
+
|
|
175
|
+
<Link
|
|
176
|
+
href="/account"
|
|
177
|
+
className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
|
|
178
|
+
>
|
|
179
|
+
View Orders
|
|
180
|
+
</Link>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default function OrderConfirmationPage() {
|
|
187
|
+
return (
|
|
188
|
+
<Suspense
|
|
189
|
+
fallback={
|
|
190
|
+
<div className="flex min-h-[60vh] items-center justify-center">
|
|
191
|
+
<LoadingSpinner size="lg" />
|
|
192
|
+
</div>
|
|
193
|
+
}
|
|
194
|
+
>
|
|
195
|
+
<OrderConfirmationContent />
|
|
196
|
+
</Suspense>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
|
|
5
|
+
interface DeliveryMethodStepProps {
|
|
6
|
+
onSelect: (method: 'shipping' | 'pickup') => void;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function DeliveryMethodStep({ onSelect, className }: DeliveryMethodStepProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className={cn('space-y-3', className)}>
|
|
13
|
+
<button
|
|
14
|
+
type="button"
|
|
15
|
+
onClick={() => onSelect('shipping')}
|
|
16
|
+
className="border-border hover:border-primary flex w-full items-center gap-4 rounded border px-4 py-4 text-start transition-colors"
|
|
17
|
+
>
|
|
18
|
+
<svg
|
|
19
|
+
className="text-muted-foreground h-6 w-6 flex-shrink-0"
|
|
20
|
+
fill="none"
|
|
21
|
+
viewBox="0 0 24 24"
|
|
22
|
+
stroke="currentColor"
|
|
23
|
+
>
|
|
24
|
+
<path
|
|
25
|
+
strokeLinecap="round"
|
|
26
|
+
strokeLinejoin="round"
|
|
27
|
+
strokeWidth={1.5}
|
|
28
|
+
d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
<div>
|
|
32
|
+
<p className="text-foreground text-sm font-medium">Ship to address</p>
|
|
33
|
+
<p className="text-muted-foreground mt-0.5 text-xs">Delivered to your shipping address</p>
|
|
34
|
+
</div>
|
|
35
|
+
</button>
|
|
36
|
+
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => onSelect('pickup')}
|
|
40
|
+
className="border-border hover:border-primary flex w-full items-center gap-4 rounded border px-4 py-4 text-start transition-colors"
|
|
41
|
+
>
|
|
42
|
+
<svg
|
|
43
|
+
className="text-muted-foreground h-6 w-6 flex-shrink-0"
|
|
44
|
+
fill="none"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
stroke="currentColor"
|
|
47
|
+
>
|
|
48
|
+
<path
|
|
49
|
+
strokeLinecap="round"
|
|
50
|
+
strokeLinejoin="round"
|
|
51
|
+
strokeWidth={1.5}
|
|
52
|
+
d="M13.5 21v-7.5a.75.75 0 01.75-.75h3a.75.75 0 01.75.75V21m-4.5 0H2.36m11.14 0H18m0 0h3.64m-1.39 0V9.349m-16.5 11.65V9.35m0 0a3.001 3.001 0 003.75-.615A2.993 2.993 0 009.75 9.75c.896 0 1.7-.393 2.25-1.016a2.993 2.993 0 002.25 1.016c.896 0 1.7-.393 2.25-1.016a3.001 3.001 0 003.75.614m-16.5 0a3.004 3.004 0 01-.621-4.72L4.318 3.44A1.5 1.5 0 015.378 3h13.243a1.5 1.5 0 011.06.44l1.19 1.189a3 3 0 01-.621 4.72m-13.5 8.65h3.75a.75.75 0 00.75-.75V13.5a.75.75 0 00-.75-.75H6.75a.75.75 0 00-.75.75v3.15c0 .415.336.75.75.75z"
|
|
53
|
+
/>
|
|
54
|
+
</svg>
|
|
55
|
+
<div>
|
|
56
|
+
<p className="text-foreground text-sm font-medium">Pick up in store</p>
|
|
57
|
+
<p className="text-muted-foreground mt-0.5 text-xs">Collect from a pickup location</p>
|
|
58
|
+
</div>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|