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.
@@ -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 { LoadingSpinner } from '@/components/shared/loading-spinner';
9
-
10
- function OrderConfirmationContent() {
11
- const searchParams = useSearchParams();
12
- const checkoutId = searchParams.get('checkout_id');
13
-
14
- const [result, setResult] = useState<WaitForOrderResult | null>(null);
15
- const [loading, setLoading] = useState(true);
16
- const [error, setError] = useState<string | null>(null);
17
-
18
- useEffect(() => {
19
- if (!checkoutId) {
20
- setError('Missing checkout information.');
21
- setLoading(false);
22
- return;
23
- }
24
-
25
- async function waitForOrder() {
26
- try {
27
- const client = getClient();
28
- const orderResult = await client.waitForOrder(checkoutId!, {
29
- maxWaitMs: 30000,
30
- });
31
- setResult(orderResult);
32
- } catch (err) {
33
- const message = err instanceof Error ? err.message : 'Failed to confirm order';
34
- setError(message);
35
- } finally {
36
- setLoading(false);
37
- }
38
- }
39
-
40
- waitForOrder();
41
- }, [checkoutId]);
42
-
43
- if (loading) {
44
- return (
45
- <div className="flex min-h-[60vh] flex-col items-center justify-center">
46
- <LoadingSpinner size="lg" />
47
- <p className="text-muted-foreground mt-4">Confirming your order...</p>
48
- <p className="text-muted-foreground mt-1 text-xs">This may take a few seconds.</p>
49
- </div>
50
- );
51
- }
52
-
53
- if (error) {
54
- return (
55
- <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
56
- <svg
57
- className="text-destructive mx-auto mb-4 h-16 w-16"
58
- fill="none"
59
- viewBox="0 0 24 24"
60
- stroke="currentColor"
61
- >
62
- <path
63
- strokeLinecap="round"
64
- strokeLinejoin="round"
65
- strokeWidth={1.5}
66
- 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"
67
- />
68
- </svg>
69
- <h1 className="text-foreground text-2xl font-bold">Something went wrong</h1>
70
- <p className="text-muted-foreground mt-2">{error}</p>
71
- <p className="text-muted-foreground mt-1 text-sm">
72
- If you were charged, your order may still be processing. Please check your email for a
73
- confirmation.
74
- </p>
75
- <Link
76
- href="/"
77
- className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
78
- >
79
- Return Home
80
- </Link>
81
- </div>
82
- );
83
- }
84
-
85
- // Order was created successfully
86
- if (result?.success) {
87
- const orderNumber = result.status.orderNumber;
88
-
89
- return (
90
- <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
91
- <svg
92
- className="text-primary mx-auto mb-4 h-16 w-16"
93
- fill="none"
94
- viewBox="0 0 24 24"
95
- stroke="currentColor"
96
- >
97
- <path
98
- strokeLinecap="round"
99
- strokeLinejoin="round"
100
- strokeWidth={1.5}
101
- d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
102
- />
103
- </svg>
104
-
105
- <h1 className="text-foreground text-2xl font-bold">Thank you for your order!</h1>
106
-
107
- {orderNumber && (
108
- <p className="text-foreground mt-3 text-lg">
109
- Order Number: <span className="font-semibold">{orderNumber}</span>
110
- </p>
111
- )}
112
-
113
- <p className="text-muted-foreground mt-2">
114
- We&apos;ve sent a confirmation email with your order details.
115
- </p>
116
-
117
- <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
118
- <Link
119
- href="/products"
120
- className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
121
- >
122
- Continue Shopping
123
- </Link>
124
-
125
- <Link
126
- href="/account"
127
- className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
128
- >
129
- View Orders
130
- </Link>
131
- </div>
132
- </div>
133
- );
134
- }
135
-
136
- // Order not yet confirmed (polling timed out) - still show success
137
- return (
138
- <div className="mx-auto max-w-2xl px-4 py-16 text-center sm:px-6 lg:px-8">
139
- <svg
140
- className="text-primary mx-auto mb-4 h-16 w-16"
141
- fill="none"
142
- viewBox="0 0 24 24"
143
- stroke="currentColor"
144
- >
145
- <path
146
- strokeLinecap="round"
147
- strokeLinejoin="round"
148
- strokeWidth={1.5}
149
- d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
150
- />
151
- </svg>
152
-
153
- <h1 className="text-foreground text-2xl font-bold">Payment received!</h1>
154
-
155
- <p className="text-muted-foreground mt-2">
156
- Your order is being processed. You&apos;ll receive a confirmation email shortly with your
157
- order details.
158
- </p>
159
-
160
- <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
161
- <Link
162
- href="/products"
163
- className="bg-primary text-primary-foreground inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
164
- >
165
- Continue Shopping
166
- </Link>
167
-
168
- <Link
169
- href="/account"
170
- className="border-border text-foreground hover:bg-muted inline-flex items-center rounded border px-6 py-3 font-medium transition-colors"
171
- >
172
- View Orders
173
- </Link>
174
- </div>
175
- </div>
176
- );
177
- }
178
-
179
- export default function OrderConfirmationPage() {
180
- return (
181
- <Suspense
182
- fallback={
183
- <div className="flex min-h-[60vh] items-center justify-center">
184
- <LoadingSpinner size="lg" />
185
- </div>
186
- }
187
- >
188
- <OrderConfirmationContent />
189
- </Suspense>
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&apos;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&apos;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
+ }