@nitrostack/cli 1.0.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.
Files changed (100) hide show
  1. package/README.md +131 -0
  2. package/dist/commands/build.d.ts +6 -0
  3. package/dist/commands/build.d.ts.map +1 -0
  4. package/dist/commands/build.js +185 -0
  5. package/dist/commands/dev.d.ts +7 -0
  6. package/dist/commands/dev.d.ts.map +1 -0
  7. package/dist/commands/dev.js +365 -0
  8. package/dist/commands/generate-types.d.ts +8 -0
  9. package/dist/commands/generate-types.d.ts.map +1 -0
  10. package/dist/commands/generate-types.js +219 -0
  11. package/dist/commands/generate.d.ts +12 -0
  12. package/dist/commands/generate.d.ts.map +1 -0
  13. package/dist/commands/generate.js +375 -0
  14. package/dist/commands/init.d.ts +7 -0
  15. package/dist/commands/init.d.ts.map +1 -0
  16. package/dist/commands/init.js +324 -0
  17. package/dist/commands/install.d.ts +10 -0
  18. package/dist/commands/install.d.ts.map +1 -0
  19. package/dist/commands/install.js +80 -0
  20. package/dist/commands/start.d.ts +6 -0
  21. package/dist/commands/start.d.ts.map +1 -0
  22. package/dist/commands/start.js +70 -0
  23. package/dist/commands/upgrade.d.ts +10 -0
  24. package/dist/commands/upgrade.d.ts.map +1 -0
  25. package/dist/commands/upgrade.js +214 -0
  26. package/dist/index.d.ts +11 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +94 -0
  29. package/dist/mcp-dev-wrapper.d.ts +15 -0
  30. package/dist/mcp-dev-wrapper.d.ts.map +1 -0
  31. package/dist/mcp-dev-wrapper.js +187 -0
  32. package/dist/ui/branding.d.ts +31 -0
  33. package/dist/ui/branding.d.ts.map +1 -0
  34. package/dist/ui/branding.js +136 -0
  35. package/package.json +69 -0
  36. package/templates/typescript-oauth/.env.example +27 -0
  37. package/templates/typescript-oauth/OAUTH_SETUP.md +592 -0
  38. package/templates/typescript-oauth/README.md +263 -0
  39. package/templates/typescript-oauth/package.json +29 -0
  40. package/templates/typescript-oauth/src/app.module.ts +92 -0
  41. package/templates/typescript-oauth/src/guards/oauth.guard.ts +126 -0
  42. package/templates/typescript-oauth/src/health/system.health.ts +55 -0
  43. package/templates/typescript-oauth/src/index.ts +63 -0
  44. package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
  45. package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
  46. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +228 -0
  47. package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
  48. package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
  49. package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
  50. package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
  51. package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
  52. package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
  53. package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
  54. package/templates/typescript-oauth/src/widgets/app/layout.tsx +18 -0
  55. package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
  56. package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
  57. package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
  58. package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
  59. package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
  60. package/templates/typescript-oauth/src/widgets/next.config.js +45 -0
  61. package/templates/typescript-oauth/src/widgets/package-lock.json +4493 -0
  62. package/templates/typescript-oauth/src/widgets/package.json +24 -0
  63. package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
  64. package/templates/typescript-oauth/src/widgets/widget-manifest.json +395 -0
  65. package/templates/typescript-oauth/tsconfig.json +23 -0
  66. package/templates/typescript-pizzaz/README.md +252 -0
  67. package/templates/typescript-pizzaz/package.json +34 -0
  68. package/templates/typescript-pizzaz/src/app.module.ts +28 -0
  69. package/templates/typescript-pizzaz/src/index.ts +30 -0
  70. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
  71. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
  72. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
  73. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
  74. package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
  75. package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
  76. package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
  77. package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
  78. package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
  79. package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
  80. package/templates/typescript-pizzaz/src/widgets/next.config.js +45 -0
  81. package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
  82. package/templates/typescript-pizzaz/src/widgets/tsconfig.json +28 -0
  83. package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
  84. package/templates/typescript-pizzaz/tsconfig.json +30 -0
  85. package/templates/typescript-starter/README.md +320 -0
  86. package/templates/typescript-starter/package.json +25 -0
  87. package/templates/typescript-starter/src/app.module.ts +34 -0
  88. package/templates/typescript-starter/src/health/system.health.ts +55 -0
  89. package/templates/typescript-starter/src/index.ts +29 -0
  90. package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
  91. package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
  92. package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +59 -0
  93. package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +166 -0
  94. package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +180 -0
  95. package/templates/typescript-starter/src/widgets/app/layout.tsx +18 -0
  96. package/templates/typescript-starter/src/widgets/next.config.js +45 -0
  97. package/templates/typescript-starter/src/widgets/package.json +24 -0
  98. package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
  99. package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
  100. package/templates/typescript-starter/tsconfig.json +23 -0
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+
3
+ import { useWidgetSDK, useTheme } from '@nitrostack/widgets';
4
+
5
+ /**
6
+ * Payment Confirmation Widget - Success state with booking details
7
+ */
8
+
9
+ interface PaymentData {
10
+ orderId: string;
11
+ status: string;
12
+ totalAmount: string;
13
+ totalCurrency: string;
14
+ bookingReference?: string;
15
+ message?: string;
16
+ }
17
+
18
+ export default function PaymentConfirmation() {
19
+ const { getToolOutput } = useWidgetSDK();
20
+ const theme = useTheme();
21
+ const data = getToolOutput<PaymentData>();
22
+
23
+ const isDark = theme === 'dark';
24
+
25
+ if (!data) {
26
+ return <div style={{ padding: '24px', textAlign: 'center' }}>Loading...</div>;
27
+ }
28
+
29
+ const isConfirmed = data.status === 'confirmed';
30
+
31
+ return (
32
+ <div className={isDark ? 'dark' : ''} style={{
33
+ padding: '24px',
34
+ background: isDark ? '#020617' : '#FFFFFF',
35
+ color: isDark ? '#F8FAFC' : '#020617',
36
+ minHeight: '300px',
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ justifyContent: 'center'
40
+ }}>
41
+ <div style={{ maxWidth: '500px', width: '100%', textAlign: 'center' }}>
42
+ {/* Success Icon */}
43
+ <div style={{
44
+ width: '100px',
45
+ height: '100px',
46
+ margin: '0 auto 24px',
47
+ background: isConfirmed ? 'linear-gradient(135deg, #22C55E 0%, #16A34A 100%)' : 'linear-gradient(135deg, #3B9FFF 0%, #2563EB 100%)',
48
+ borderRadius: '50%',
49
+ display: 'flex',
50
+ alignItems: 'center',
51
+ justifyContent: 'center',
52
+ boxShadow: isConfirmed ? '0 8px 24px rgba(34, 197, 94, 0.3)' : '0 8px 24px rgba(59, 159, 255, 0.3)'
53
+ }}>
54
+ <div style={{ fontSize: '48px', color: 'white' }}>
55
+ {isConfirmed ? '✓' : '💳'}
56
+ </div>
57
+ </div>
58
+
59
+ {/* Title */}
60
+ <h2 style={{ margin: '0 0 12px 0', fontSize: '24px', fontWeight: 700 }}>
61
+ {isConfirmed ? 'Payment Successful!' : 'Complete Payment'}
62
+ </h2>
63
+
64
+ <p style={{ margin: '0 0 24px 0', fontSize: '14px', color: isDark ? '#94A3B8' : '#64748B', lineHeight: '1.6' }}>
65
+ {data.message || (isConfirmed ? 'Your booking has been confirmed.' : 'Review and confirm your payment.')}
66
+ </p>
67
+
68
+ {/* Booking Details */}
69
+ <div style={{
70
+ background: isDark ? '#0F172A' : '#F8FAFC',
71
+ borderRadius: '12px',
72
+ padding: '20px',
73
+ marginBottom: '24px',
74
+ border: `1px solid ${isDark ? '#334155' : '#E2E8F0'}`
75
+ }}>
76
+ {data.bookingReference && (
77
+ <div style={{ marginBottom: '16px' }}>
78
+ <div style={{ fontSize: '11px', color: isDark ? '#94A3B8' : '#64748B', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
79
+ Booking Reference
80
+ </div>
81
+ <div style={{ fontSize: '20px', fontWeight: 700, color: '#22C55E', letterSpacing: '1px', fontFamily: 'monospace' }}>
82
+ {data.bookingReference}
83
+ </div>
84
+ </div>
85
+ )}
86
+
87
+ <div style={{
88
+ display: 'grid',
89
+ gap: '12px',
90
+ paddingTop: data.bookingReference ? '16px' : '0',
91
+ borderTop: data.bookingReference ? `1px solid ${isDark ? '#334155' : '#E2E8F0'}` : 'none'
92
+ }}>
93
+ <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px' }}>
94
+ <span style={{ color: isDark ? '#94A3B8' : '#64748B' }}>Order ID:</span>
95
+ <span style={{ fontWeight: 600, fontFamily: 'monospace' }}>{data.orderId}</span>
96
+ </div>
97
+
98
+ <div style={{
99
+ display: 'flex',
100
+ justifyContent: 'space-between',
101
+ paddingTop: '12px',
102
+ borderTop: `1px solid ${isDark ? '#334155' : '#E2E8F0'}`
103
+ }}>
104
+ <span style={{ fontSize: '14px', fontWeight: 600 }}>
105
+ {isConfirmed ? 'Amount Paid:' : 'Total Amount:'}
106
+ </span>
107
+ <span style={{ color: 'var(--primary)', fontSize: '20px', fontWeight: 700 }}>
108
+ {data.totalCurrency} {parseFloat(data.totalAmount).toFixed(2)}
109
+ </span>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ {/* Action Buttons */}
115
+ {isConfirmed ? (
116
+ <div style={{ display: 'grid', gap: '10px' }}>
117
+ <button className="btn-primary" style={{ width: '100%' }}>
118
+ 📧 Email Confirmation
119
+ </button>
120
+ <button className="btn-secondary" style={{ width: '100%' }}>
121
+ 📄 Download Receipt
122
+ </button>
123
+ </div>
124
+ ) : (
125
+ <div>
126
+ <button className="btn-primary" style={{ width: '100%', marginBottom: '12px' }}>
127
+ 🔒 Confirm & Pay {data.totalCurrency} {parseFloat(data.totalAmount).toFixed(2)}
128
+ </button>
129
+ <div style={{ fontSize: '11px', color: isDark ? '#94A3B8' : '#64748B', lineHeight: '1.5' }}>
130
+ 🔒 Your payment is secure and encrypted
131
+ </div>
132
+ </div>
133
+ )}
134
+
135
+ {/* Footer Note */}
136
+ {isConfirmed && (
137
+ <div style={{
138
+ marginTop: '24px',
139
+ padding: '12px',
140
+ background: '#FEF3C7',
141
+ borderRadius: '8px',
142
+ fontSize: '11px',
143
+ color: '#92400E',
144
+ lineHeight: '1.5'
145
+ }}>
146
+ <strong>📱 Important:</strong> A confirmation email has been sent to your registered email address.
147
+ </div>
148
+ )}
149
+ </div>
150
+ </div>
151
+ );
152
+ }
@@ -0,0 +1,486 @@
1
+ 'use client';
2
+
3
+ import { useWidgetSDK, useTheme } from '@nitrostack/widgets';
4
+ import { useState } from 'react';
5
+
6
+ /**
7
+ * Seat Selection Widget
8
+ *
9
+ * Interactive seat map with real-time selection for multiple passengers.
10
+ */
11
+
12
+ interface Seat {
13
+ id: string;
14
+ column: string;
15
+ available: boolean;
16
+ price?: string;
17
+ type: string;
18
+ }
19
+
20
+ interface Row {
21
+ rowNumber: number;
22
+ seats: Seat[];
23
+ }
24
+
25
+ interface Cabin {
26
+ cabinClass: string;
27
+ rows: Row[];
28
+ }
29
+
30
+ interface SeatMapData {
31
+ offerId: string;
32
+ cabins: Cabin[];
33
+ message?: string;
34
+ }
35
+
36
+ export default function SeatSelection() {
37
+ const { getToolOutput } = useWidgetSDK();
38
+ const theme = useTheme();
39
+ const data = getToolOutput<SeatMapData>();
40
+
41
+ const isDark = theme === 'dark';
42
+ const [selectedSeats, setSelectedSeats] = useState<Record<string, string>>({});
43
+ const [activePassenger, setActivePassenger] = useState(0);
44
+ const [hoveredSeat, setHoveredSeat] = useState<string | null>(null);
45
+
46
+ const passengers = [
47
+ { id: 'pax_1', name: 'Passenger 1' },
48
+ { id: 'pax_2', name: 'Passenger 2' }
49
+ ];
50
+
51
+ const handleSeatClick = (seatId: string, seat: Seat) => {
52
+ if (!seat.available) return;
53
+
54
+ const currentPassengerId = passengers[activePassenger].id;
55
+ const seatOwner = Object.entries(selectedSeats).find(([_, id]) => id === seatId)?.[0];
56
+
57
+ if (seatOwner && seatOwner !== currentPassengerId) return;
58
+
59
+ setSelectedSeats(prev => {
60
+ const newSelections = { ...prev };
61
+ if (newSelections[currentPassengerId] === seatId) {
62
+ delete newSelections[currentPassengerId];
63
+ } else {
64
+ delete newSelections[currentPassengerId];
65
+ newSelections[currentPassengerId] = seatId;
66
+ if (activePassenger < passengers.length - 1) {
67
+ setTimeout(() => setActivePassenger(activePassenger + 1), 200);
68
+ }
69
+ }
70
+ return newSelections;
71
+ });
72
+ };
73
+
74
+ const getSeatStatus = (seatId: string, seat: Seat) => {
75
+ if (!seat.available) return 'unavailable';
76
+ const owner = Object.entries(selectedSeats).find(([_, id]) => id === seatId)?.[0];
77
+ if (owner) {
78
+ const passengerIndex = passengers.findIndex(p => p.id === owner);
79
+ return passengerIndex === activePassenger ? 'selected-active' : 'selected-other';
80
+ }
81
+ return 'available';
82
+ };
83
+
84
+ const getSeatColor = (status: string) => {
85
+ if (isDark) {
86
+ return {
87
+ 'available': '#334155',
88
+ 'selected-active': '#3B9FFF',
89
+ 'selected-other': '#22C55E',
90
+ 'unavailable': '#1E293B'
91
+ }[status] || '#334155';
92
+ }
93
+ return {
94
+ 'available': '#E2E8F0',
95
+ 'selected-active': '#3B9FFF',
96
+ 'selected-other': '#22C55E',
97
+ 'unavailable': '#CBD5E1'
98
+ }[status] || '#E2E8F0';
99
+ };
100
+
101
+ const calculateTotalPrice = () => {
102
+ let total = 0;
103
+ Object.values(selectedSeats).forEach(seatId => {
104
+ data?.cabins.forEach(cabin => {
105
+ cabin.rows.forEach(row => {
106
+ const seat = row.seats.find(s => s.id === seatId);
107
+ if (seat?.price) total += parseFloat(seat.price);
108
+ });
109
+ });
110
+ });
111
+ return total;
112
+ };
113
+
114
+ const getSelectedSeatInfo = (passengerId: string) => {
115
+ const seatId = selectedSeats[passengerId];
116
+ if (!seatId || !data) return null;
117
+
118
+ for (const cabin of data.cabins) {
119
+ for (const row of cabin.rows) {
120
+ const seat = row.seats.find(s => s.id === seatId);
121
+ if (seat) return { seat, row: row.rowNumber };
122
+ }
123
+ }
124
+ return null;
125
+ };
126
+
127
+ if (!data) {
128
+ return <div style={{ padding: '24px', textAlign: 'center' }}>Loading...</div>;
129
+ }
130
+
131
+ return (
132
+ <div className={isDark ? 'dark' : ''} style={{
133
+ padding: '16px',
134
+ background: isDark ? '#020617' : '#FFFFFF',
135
+ color: isDark ? '#F8FAFC' : '#020617'
136
+ }}>
137
+ {/* Header */}
138
+ <div className="card" style={{ marginBottom: '16px' }}>
139
+ <div style={{
140
+ display: 'flex',
141
+ alignItems: 'center',
142
+ justifyContent: 'space-between',
143
+ flexWrap: 'wrap',
144
+ gap: '12px'
145
+ }}>
146
+ <div>
147
+ <h2 style={{
148
+ margin: 0,
149
+ fontSize: '18px',
150
+ fontWeight: 700,
151
+ display: 'flex',
152
+ alignItems: 'center',
153
+ gap: '8px'
154
+ }}>
155
+ <span>💺</span>
156
+ <span>Select Seats</span>
157
+ </h2>
158
+ <p style={{
159
+ margin: '4px 0 0 0',
160
+ fontSize: '12px',
161
+ color: isDark ? '#94A3B8' : '#64748B'
162
+ }}>
163
+ {data.message || 'Choose seats for all passengers'}
164
+ </p>
165
+ </div>
166
+
167
+ {Object.keys(selectedSeats).length > 0 && (
168
+ <div style={{
169
+ background: 'var(--primary)',
170
+ color: 'white',
171
+ padding: '8px 16px',
172
+ borderRadius: '8px',
173
+ textAlign: 'center'
174
+ }}>
175
+ <div style={{ fontSize: '10px', opacity: 0.9 }}>Total</div>
176
+ <div style={{ fontSize: '18px', fontWeight: 700 }}>
177
+ ${calculateTotalPrice().toFixed(2)}
178
+ </div>
179
+ </div>
180
+ )}
181
+ </div>
182
+ </div>
183
+
184
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 280px', gap: '16px' }}>
185
+ {/* Seat Map */}
186
+ <div className="card" style={{ minHeight: '400px' }}>
187
+ {/* Front Indicator */}
188
+ <div style={{
189
+ textAlign: 'center',
190
+ marginBottom: '20px',
191
+ paddingBottom: '12px',
192
+ borderBottom: `2px dashed ${isDark ? '#334155' : '#E2E8F0'}`
193
+ }}>
194
+ <div style={{ fontSize: '24px', marginBottom: '6px' }}>✈️</div>
195
+ <div style={{ fontSize: '12px', fontWeight: 600, color: '#3B9FFF' }}>
196
+ FRONT
197
+ </div>
198
+ </div>
199
+
200
+ {data.cabins.map((cabin, cabinIndex) => (
201
+ <div key={cabinIndex} style={{ marginBottom: '20px' }}>
202
+ <div style={{
203
+ background: 'var(--primary)',
204
+ color: 'white',
205
+ padding: '8px 16px',
206
+ borderRadius: '6px',
207
+ marginBottom: '12px',
208
+ fontSize: '12px',
209
+ fontWeight: 600,
210
+ textTransform: 'uppercase'
211
+ }}>
212
+ {cabin.cabinClass.replace('_', ' ')}
213
+ </div>
214
+
215
+ <div style={{ display: 'grid', gap: '6px' }}>
216
+ {cabin.rows.map((row) => (
217
+ <div key={row.rowNumber} style={{
218
+ display: 'grid',
219
+ gridTemplateColumns: 'auto 1fr auto',
220
+ gap: '12px',
221
+ alignItems: 'center'
222
+ }}>
223
+ <div style={{
224
+ width: '32px',
225
+ textAlign: 'center',
226
+ fontSize: '12px',
227
+ fontWeight: 600,
228
+ color: isDark ? '#94A3B8' : '#64748B'
229
+ }}>
230
+ {row.rowNumber}
231
+ </div>
232
+
233
+ <div style={{
234
+ display: 'flex',
235
+ gap: '6px',
236
+ justifyContent: 'center',
237
+ flexWrap: 'wrap'
238
+ }}>
239
+ {row.seats.map((seat) => {
240
+ const status = getSeatStatus(seat.id, seat);
241
+ const isHovered = hoveredSeat === seat.id;
242
+
243
+ return (
244
+ <div
245
+ key={seat.id}
246
+ onClick={() => handleSeatClick(seat.id, seat)}
247
+ onMouseEnter={() => setHoveredSeat(seat.id)}
248
+ onMouseLeave={() => setHoveredSeat(null)}
249
+ style={{
250
+ width: '40px',
251
+ height: '40px',
252
+ background: getSeatColor(status),
253
+ borderRadius: '6px',
254
+ display: 'flex',
255
+ alignItems: 'center',
256
+ justifyContent: 'center',
257
+ cursor: seat.available ? 'pointer' : 'not-allowed',
258
+ transition: 'all 0.2s ease',
259
+ transform: isHovered && seat.available ? 'scale(1.1)' : 'scale(1)',
260
+ border: status === 'selected-active' ? '2px solid #fff' : 'none',
261
+ position: 'relative',
262
+ opacity: seat.available ? 1 : 0.4
263
+ }}
264
+ >
265
+ <span style={{ fontSize: '16px' }}>💺</span>
266
+ <div style={{
267
+ position: 'absolute',
268
+ bottom: '2px',
269
+ fontSize: '8px',
270
+ fontWeight: 600,
271
+ color: status.includes('selected') ? 'white' : isDark ? '#94A3B8' : '#64748B'
272
+ }}>
273
+ {seat.column}
274
+ </div>
275
+ {seat.price && parseFloat(seat.price) > 0 && isHovered && (
276
+ <div style={{
277
+ position: 'absolute',
278
+ top: '-20px',
279
+ background: '#1a202c',
280
+ color: 'white',
281
+ padding: '3px 6px',
282
+ borderRadius: '4px',
283
+ fontSize: '10px',
284
+ fontWeight: 600,
285
+ whiteSpace: 'nowrap'
286
+ }}>
287
+ ${seat.price}
288
+ </div>
289
+ )}
290
+ </div>
291
+ );
292
+ })}
293
+ </div>
294
+
295
+ <div style={{
296
+ width: '32px',
297
+ textAlign: 'center',
298
+ fontSize: '12px',
299
+ fontWeight: 600,
300
+ color: isDark ? '#94A3B8' : '#64748B'
301
+ }}>
302
+ {row.rowNumber}
303
+ </div>
304
+ </div>
305
+ ))}
306
+ </div>
307
+ </div>
308
+ ))}
309
+
310
+ {/* Legend */}
311
+ <div style={{
312
+ marginTop: '20px',
313
+ paddingTop: '16px',
314
+ borderTop: `2px dashed ${isDark ? '#334155' : '#E2E8F0'}`,
315
+ display: 'flex',
316
+ gap: '16px',
317
+ flexWrap: 'wrap',
318
+ justifyContent: 'center',
319
+ fontSize: '11px'
320
+ }}>
321
+ {[
322
+ { label: 'Available', color: isDark ? '#334155' : '#E2E8F0' },
323
+ { label: 'Your Seat', color: '#3B9FFF' },
324
+ { label: 'Other', color: '#22C55E' },
325
+ { label: 'Taken', color: isDark ? '#1E293B' : '#CBD5E1' }
326
+ ].map(item => (
327
+ <div key={item.label} style={{
328
+ display: 'flex',
329
+ alignItems: 'center',
330
+ gap: '6px'
331
+ }}>
332
+ <div style={{
333
+ width: '20px',
334
+ height: '20px',
335
+ background: item.color,
336
+ borderRadius: '4px'
337
+ }} />
338
+ <span style={{ color: isDark ? '#94A3B8' : '#64748B' }}>
339
+ {item.label}
340
+ </span>
341
+ </div>
342
+ ))}
343
+ </div>
344
+ </div>
345
+
346
+ {/* Sidebar */}
347
+ <div style={{ display: 'grid', gap: '12px', alignContent: 'start' }}>
348
+ {/* Passengers */}
349
+ <div className="card">
350
+ <h3 style={{
351
+ margin: '0 0 12px 0',
352
+ fontSize: '14px',
353
+ fontWeight: 600
354
+ }}>
355
+ Passengers
356
+ </h3>
357
+
358
+ <div style={{ display: 'grid', gap: '8px' }}>
359
+ {passengers.map((passenger, index) => {
360
+ const seatInfo = getSelectedSeatInfo(passenger.id);
361
+ const isActive = activePassenger === index;
362
+
363
+ return (
364
+ <div
365
+ key={passenger.id}
366
+ onClick={() => setActivePassenger(index)}
367
+ className={isActive ? 'nitro-gradient' : ''}
368
+ style={{
369
+ padding: '12px',
370
+ background: isActive ? undefined : (isDark ? '#0F172A' : '#F8FAFC'),
371
+ borderRadius: '8px',
372
+ cursor: 'pointer',
373
+ transition: 'all 0.2s ease',
374
+ border: isActive ? '2px solid #fff' : '2px solid transparent'
375
+ }}
376
+ >
377
+ <div style={{
378
+ display: 'flex',
379
+ justifyContent: 'space-between',
380
+ alignItems: 'center'
381
+ }}>
382
+ <div>
383
+ <div style={{
384
+ fontWeight: 600,
385
+ fontSize: '13px',
386
+ color: isActive ? 'white' : (isDark ? '#F8FAFC' : '#020617'),
387
+ marginBottom: '2px'
388
+ }}>
389
+ {passenger.name}
390
+ </div>
391
+ {seatInfo ? (
392
+ <div style={{
393
+ fontSize: '11px',
394
+ color: isActive ? 'rgba(255,255,255,0.9)' : (isDark ? '#94A3B8' : '#64748B')
395
+ }}>
396
+ {seatInfo.row}{seatInfo.seat.column}
397
+ {seatInfo.seat.price && ` • $${seatInfo.seat.price}`}
398
+ </div>
399
+ ) : (
400
+ <div style={{
401
+ fontSize: '11px',
402
+ color: isActive ? 'rgba(255,255,255,0.8)' : (isDark ? '#64748B' : '#94A3B8')
403
+ }}>
404
+ No seat
405
+ </div>
406
+ )}
407
+ </div>
408
+
409
+ {seatInfo && (
410
+ <div style={{
411
+ width: '24px',
412
+ height: '24px',
413
+ background: isActive ? 'rgba(255,255,255,0.2)' : '#22C55E',
414
+ borderRadius: '50%',
415
+ display: 'flex',
416
+ alignItems: 'center',
417
+ justifyContent: 'center',
418
+ color: 'white',
419
+ fontSize: '12px'
420
+ }}>
421
+
422
+ </div>
423
+ )}
424
+ </div>
425
+ </div>
426
+ );
427
+ })}
428
+ </div>
429
+ </div>
430
+
431
+ {/* Summary */}
432
+ <div className="card">
433
+ <h3 style={{
434
+ margin: '0 0 12px 0',
435
+ fontSize: '14px',
436
+ fontWeight: 600
437
+ }}>
438
+ Summary
439
+ </h3>
440
+
441
+ <div style={{ display: 'grid', gap: '10px', marginBottom: '12px' }}>
442
+ <div style={{
443
+ display: 'flex',
444
+ justifyContent: 'space-between',
445
+ fontSize: '12px'
446
+ }}>
447
+ <span style={{ color: isDark ? '#94A3B8' : '#64748B' }}>Selected:</span>
448
+ <span style={{ fontWeight: 600 }}>
449
+ {Object.keys(selectedSeats).length} / {passengers.length}
450
+ </span>
451
+ </div>
452
+
453
+ <div style={{
454
+ display: 'flex',
455
+ justifyContent: 'space-between',
456
+ fontSize: '12px',
457
+ paddingTop: '10px',
458
+ borderTop: `1px solid ${isDark ? '#334155' : '#E2E8F0'}`
459
+ }}>
460
+ <span style={{ color: isDark ? '#94A3B8' : '#64748B' }}>Total:</span>
461
+ <span style={{ color: 'var(--primary)', fontWeight: 700, fontSize: '16px' }}>
462
+ ${calculateTotalPrice().toFixed(2)}
463
+ </span>
464
+ </div>
465
+ </div>
466
+
467
+ <button
468
+ disabled={Object.keys(selectedSeats).length !== passengers.length}
469
+ className={Object.keys(selectedSeats).length === passengers.length ? 'btn-primary' : 'btn-secondary'}
470
+ style={{
471
+ width: '100%',
472
+ opacity: Object.keys(selectedSeats).length === passengers.length ? 1 : 0.5,
473
+ cursor: Object.keys(selectedSeats).length === passengers.length ? 'pointer' : 'not-allowed'
474
+ }}
475
+ >
476
+ {Object.keys(selectedSeats).length === passengers.length
477
+ ? 'Confirm Selection'
478
+ : `Select ${passengers.length - Object.keys(selectedSeats).length} More`
479
+ }
480
+ </button>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ </div>
485
+ );
486
+ }
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.