nitrostack 1.0.55 → 1.0.57

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 (222) hide show
  1. package/dist/cli/commands/init.d.ts.map +1 -1
  2. package/dist/cli/commands/init.js +14 -9
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/widgets/hooks/index.d.ts +7 -0
  5. package/dist/widgets/hooks/index.d.ts.map +1 -0
  6. package/dist/widgets/hooks/index.js +7 -0
  7. package/dist/widgets/hooks/index.js.map +1 -0
  8. package/dist/widgets/hooks/use-display-mode.d.ts +11 -0
  9. package/dist/widgets/hooks/use-display-mode.d.ts.map +1 -0
  10. package/dist/widgets/hooks/use-display-mode.js +13 -0
  11. package/dist/widgets/hooks/use-display-mode.js.map +1 -0
  12. package/dist/widgets/hooks/use-max-height.d.ts +10 -0
  13. package/dist/widgets/hooks/use-max-height.d.ts.map +1 -0
  14. package/dist/widgets/hooks/use-max-height.js +13 -0
  15. package/dist/widgets/hooks/use-max-height.js.map +1 -0
  16. package/dist/widgets/hooks/use-openai-global.d.ts +12 -0
  17. package/dist/widgets/hooks/use-openai-global.d.ts.map +1 -0
  18. package/dist/widgets/hooks/use-openai-global.js +32 -0
  19. package/dist/widgets/hooks/use-openai-global.js.map +1 -0
  20. package/dist/widgets/hooks/use-theme.d.ts +10 -0
  21. package/dist/widgets/hooks/use-theme.d.ts.map +1 -0
  22. package/dist/widgets/hooks/use-theme.js +12 -0
  23. package/dist/widgets/hooks/use-theme.js.map +1 -0
  24. package/dist/widgets/hooks/use-widget-state.d.ts +18 -0
  25. package/dist/widgets/hooks/use-widget-state.d.ts.map +1 -0
  26. package/dist/widgets/hooks/use-widget-state.js +27 -0
  27. package/dist/widgets/hooks/use-widget-state.js.map +1 -0
  28. package/dist/widgets/hooks/useWidgetSDK.d.ts +47 -0
  29. package/dist/widgets/hooks/useWidgetSDK.d.ts.map +1 -0
  30. package/dist/widgets/hooks/useWidgetSDK.js +67 -0
  31. package/dist/widgets/hooks/useWidgetSDK.js.map +1 -0
  32. package/dist/widgets/index.d.ts +7 -1
  33. package/dist/widgets/index.d.ts.map +1 -1
  34. package/dist/widgets/index.js +11 -1
  35. package/dist/widgets/index.js.map +1 -1
  36. package/dist/widgets/runtime/WidgetLayout.d.ts +32 -0
  37. package/dist/widgets/runtime/WidgetLayout.d.ts.map +1 -0
  38. package/dist/widgets/runtime/WidgetLayout.js +143 -0
  39. package/dist/widgets/runtime/WidgetLayout.js.map +1 -0
  40. package/dist/widgets/runtime/widget-polyfill.d.ts +1 -0
  41. package/dist/widgets/runtime/widget-polyfill.d.ts.map +1 -0
  42. package/dist/widgets/runtime/widget-polyfill.js +28 -0
  43. package/dist/widgets/runtime/widget-polyfill.js.map +1 -0
  44. package/dist/widgets/sdk.d.ts +109 -0
  45. package/dist/widgets/sdk.d.ts.map +1 -0
  46. package/dist/widgets/sdk.js +221 -0
  47. package/dist/widgets/sdk.js.map +1 -0
  48. package/dist/widgets/types.d.ts +89 -0
  49. package/dist/widgets/types.d.ts.map +1 -0
  50. package/dist/widgets/types.js +8 -0
  51. package/dist/widgets/types.js.map +1 -0
  52. package/dist/widgets/utils/media-queries.d.ts +34 -0
  53. package/dist/widgets/utils/media-queries.d.ts.map +1 -0
  54. package/dist/widgets/utils/media-queries.js +42 -0
  55. package/dist/widgets/utils/media-queries.js.map +1 -0
  56. package/package.json +2 -2
  57. package/src/studio/app/chat/page.tsx +274 -137
  58. package/src/studio/app/globals.css +140 -64
  59. package/src/studio/branding.md +807 -0
  60. package/src/studio/components/WidgetRenderer.tsx +222 -16
  61. package/src/studio/lib/llm-service.ts +39 -39
  62. package/templates/typescript-oauth/{env.example → .env.example} +4 -10
  63. package/templates/typescript-oauth/README.md +226 -306
  64. package/templates/typescript-oauth/package-lock.json +4253 -0
  65. package/templates/typescript-oauth/package.json +10 -5
  66. package/templates/typescript-oauth/src/app.module.ts +39 -36
  67. package/templates/typescript-oauth/src/guards/oauth.guard.ts +0 -1
  68. package/templates/typescript-oauth/src/index.ts +22 -30
  69. package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
  70. package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
  71. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +231 -0
  72. package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
  73. package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
  74. package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
  75. package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
  76. package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
  77. package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
  78. package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
  79. package/templates/typescript-oauth/src/widgets/app/layout.tsx +6 -2
  80. package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
  81. package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
  82. package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
  83. package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
  84. package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
  85. package/templates/typescript-oauth/src/widgets/package-lock.json +155 -126
  86. package/templates/typescript-oauth/src/widgets/widget-manifest.json +374 -27
  87. package/templates/typescript-pizzaz/IMPLEMENTATION.md +98 -0
  88. package/templates/typescript-pizzaz/README.md +233 -0
  89. package/templates/typescript-pizzaz/package.json +31 -0
  90. package/templates/typescript-pizzaz/src/app.module.ts +28 -0
  91. package/templates/typescript-pizzaz/src/index.ts +30 -0
  92. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
  93. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
  94. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
  95. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
  96. package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
  97. package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
  98. package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
  99. package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
  100. package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
  101. package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
  102. package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
  103. package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
  104. package/templates/typescript-pizzaz/tsconfig.json +30 -0
  105. package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +0 -1
  106. package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +102 -56
  107. package/templates/typescript-starter/src/widgets/app/layout.tsx +6 -2
  108. package/templates/typescript-auth/AI_AGENT_CLI_REFERENCE.md +0 -702
  109. package/templates/typescript-auth/AI_AGENT_SDK_REFERENCE.md +0 -1260
  110. package/templates/typescript-auth/README.md +0 -402
  111. package/templates/typescript-auth/env.example +0 -25
  112. package/templates/typescript-auth/package.json +0 -36
  113. package/templates/typescript-auth/src/app.module.ts +0 -103
  114. package/templates/typescript-auth/src/db/database.ts +0 -160
  115. package/templates/typescript-auth/src/db/seed.ts +0 -374
  116. package/templates/typescript-auth/src/db/setup.ts +0 -87
  117. package/templates/typescript-auth/src/events/analytics.service.ts +0 -52
  118. package/templates/typescript-auth/src/events/notification.service.ts +0 -40
  119. package/templates/typescript-auth/src/filters/global-exception.filter.ts +0 -28
  120. package/templates/typescript-auth/src/guards/README.md +0 -75
  121. package/templates/typescript-auth/src/guards/jwt.guard.ts +0 -105
  122. package/templates/typescript-auth/src/health/database.health.ts +0 -41
  123. package/templates/typescript-auth/src/index.ts +0 -29
  124. package/templates/typescript-auth/src/interceptors/transform.interceptor.ts +0 -24
  125. package/templates/typescript-auth/src/middleware/logging.middleware.ts +0 -42
  126. package/templates/typescript-auth/src/modules/addresses/addresses.module.ts +0 -16
  127. package/templates/typescript-auth/src/modules/addresses/addresses.prompts.ts +0 -114
  128. package/templates/typescript-auth/src/modules/addresses/addresses.resources.ts +0 -40
  129. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +0 -284
  130. package/templates/typescript-auth/src/modules/auth/auth.module.ts +0 -16
  131. package/templates/typescript-auth/src/modules/auth/auth.prompts.ts +0 -147
  132. package/templates/typescript-auth/src/modules/auth/auth.resources.ts +0 -84
  133. package/templates/typescript-auth/src/modules/auth/auth.tools.ts +0 -139
  134. package/templates/typescript-auth/src/modules/cart/cart.module.ts +0 -16
  135. package/templates/typescript-auth/src/modules/cart/cart.prompts.ts +0 -95
  136. package/templates/typescript-auth/src/modules/cart/cart.resources.ts +0 -44
  137. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +0 -277
  138. package/templates/typescript-auth/src/modules/orders/orders.module.ts +0 -16
  139. package/templates/typescript-auth/src/modules/orders/orders.prompts.ts +0 -88
  140. package/templates/typescript-auth/src/modules/orders/orders.resources.ts +0 -48
  141. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +0 -303
  142. package/templates/typescript-auth/src/modules/products/products.module.ts +0 -16
  143. package/templates/typescript-auth/src/modules/products/products.prompts.ts +0 -146
  144. package/templates/typescript-auth/src/modules/products/products.resources.ts +0 -98
  145. package/templates/typescript-auth/src/modules/products/products.tools.ts +0 -266
  146. package/templates/typescript-auth/src/pipes/validation.pipe.ts +0 -42
  147. package/templates/typescript-auth/src/services/database.service.ts +0 -90
  148. package/templates/typescript-auth/src/widgets/app/add-to-cart/page.tsx +0 -122
  149. package/templates/typescript-auth/src/widgets/app/address-added/page.tsx +0 -116
  150. package/templates/typescript-auth/src/widgets/app/address-deleted/page.tsx +0 -105
  151. package/templates/typescript-auth/src/widgets/app/address-list/page.tsx +0 -139
  152. package/templates/typescript-auth/src/widgets/app/address-updated/page.tsx +0 -153
  153. package/templates/typescript-auth/src/widgets/app/cart-cleared/page.tsx +0 -86
  154. package/templates/typescript-auth/src/widgets/app/cart-updated/page.tsx +0 -116
  155. package/templates/typescript-auth/src/widgets/app/categories/page.tsx +0 -134
  156. package/templates/typescript-auth/src/widgets/app/layout.tsx +0 -21
  157. package/templates/typescript-auth/src/widgets/app/login-result/page.tsx +0 -129
  158. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +0 -231
  159. package/templates/typescript-auth/src/widgets/app/order-details/page.tsx +0 -225
  160. package/templates/typescript-auth/src/widgets/app/order-history/page.tsx +0 -218
  161. package/templates/typescript-auth/src/widgets/app/product-card/page.tsx +0 -121
  162. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +0 -198
  163. package/templates/typescript-auth/src/widgets/app/shopping-cart/page.tsx +0 -187
  164. package/templates/typescript-auth/src/widgets/app/whoami/page.tsx +0 -165
  165. package/templates/typescript-auth/src/widgets/next.config.js +0 -38
  166. package/templates/typescript-auth/src/widgets/package.json +0 -18
  167. package/templates/typescript-auth/src/widgets/styles/ecommerce.ts +0 -169
  168. package/templates/typescript-auth/src/widgets/tsconfig.json +0 -28
  169. package/templates/typescript-auth/src/widgets/types/tool-data.ts +0 -141
  170. package/templates/typescript-auth/src/widgets/widget-manifest.json +0 -464
  171. package/templates/typescript-auth/tsconfig.json +0 -27
  172. package/templates/typescript-auth-api-key/AI_AGENT_CLI_REFERENCE.md +0 -701
  173. package/templates/typescript-auth-api-key/AI_AGENT_SDK_REFERENCE.md +0 -1260
  174. package/templates/typescript-auth-api-key/README.md +0 -485
  175. package/templates/typescript-auth-api-key/env.example +0 -17
  176. package/templates/typescript-auth-api-key/package.json +0 -21
  177. package/templates/typescript-auth-api-key/src/app.module.ts +0 -38
  178. package/templates/typescript-auth-api-key/src/guards/apikey.guard.ts +0 -47
  179. package/templates/typescript-auth-api-key/src/guards/multi-auth.guard.ts +0 -157
  180. package/templates/typescript-auth-api-key/src/index.ts +0 -47
  181. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.module.ts +0 -12
  182. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.prompts.ts +0 -73
  183. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.resources.ts +0 -60
  184. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.tools.ts +0 -71
  185. package/templates/typescript-auth-api-key/src/modules/demo/demo.module.ts +0 -18
  186. package/templates/typescript-auth-api-key/src/modules/demo/demo.tools.ts +0 -155
  187. package/templates/typescript-auth-api-key/src/modules/demo/multi-auth.tools.ts +0 -123
  188. package/templates/typescript-auth-api-key/src/widgets/app/calculator-operations/page.tsx +0 -133
  189. package/templates/typescript-auth-api-key/src/widgets/app/calculator-result/page.tsx +0 -134
  190. package/templates/typescript-auth-api-key/src/widgets/app/layout.tsx +0 -14
  191. package/templates/typescript-auth-api-key/src/widgets/package.json +0 -24
  192. package/templates/typescript-auth-api-key/src/widgets/widget-manifest.json +0 -48
  193. package/templates/typescript-auth-api-key/tsconfig.json +0 -23
  194. package/templates/typescript-oauth/OAUTH_SETUP.md +0 -592
  195. package/templates/typescript-oauth/src/modules/demo/demo.module.ts +0 -16
  196. package/templates/typescript-oauth/src/modules/demo/demo.tools.ts +0 -190
  197. package/templates/typescript-oauth/src/widgets/app/calculator-operations/page.tsx +0 -133
  198. package/templates/typescript-oauth/src/widgets/app/calculator-result/page.tsx +0 -134
  199. package/templates/typescript-oauth/src/widgets/out/404.html +0 -1
  200. package/templates/typescript-oauth/src/widgets/out/_next/static/WU9THacVqL52RZbrZOLS1/_buildManifest.js +0 -1
  201. package/templates/typescript-oauth/src/widgets/out/_next/static/WU9THacVqL52RZbrZOLS1/_ssgManifest.js +0 -1
  202. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/117-eb57c7ef86f964a4.js +0 -2
  203. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/_not-found/page-dcb83ba3e4d0aafd.js +0 -1
  204. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/calculator-operations/page-b8913a740073ea8a.js +0 -1
  205. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/calculator-result/page-ddaaab2fce95dea2.js +0 -1
  206. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/layout-cbd3ebdc4ecc5247.js +0 -1
  207. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/fd9d1056-749e5812300142af.js +0 -1
  208. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/framework-f66176bb897dc684.js +0 -1
  209. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/main-76df43fcef3db344.js +0 -1
  210. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/main-app-f9c40224d04023c5.js +0 -1
  211. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +0 -1
  212. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +0 -1
  213. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  214. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/webpack-100b9e646d9c912e.js +0 -1
  215. package/templates/typescript-oauth/src/widgets/out/calculator-operations.html +0 -1
  216. package/templates/typescript-oauth/src/widgets/out/calculator-operations.txt +0 -7
  217. package/templates/typescript-oauth/src/widgets/out/calculator-result.html +0 -1
  218. package/templates/typescript-oauth/src/widgets/out/calculator-result.txt +0 -7
  219. package/templates/typescript-starter/src/widgets/app/calculator-operations/page.tsx +0 -133
  220. /package/templates/{typescript-auth-api-key → typescript-oauth}/src/health/system.health.ts +0 -0
  221. /package/templates/{typescript-auth-api-key → typescript-pizzaz}/src/widgets/next.config.js +0 -0
  222. /package/templates/{typescript-auth-api-key → typescript-pizzaz}/src/widgets/tsconfig.json +0 -0
@@ -0,0 +1,285 @@
1
+ import { Duffel } from '@duffel/api';
2
+ import { Injectable } from 'nitrostack';
3
+
4
+ /**
5
+ * Duffel API Service
6
+ *
7
+ * Handles all interactions with the Duffel API for flight search and booking.
8
+ * Implements best practices from Duffel documentation.
9
+ */
10
+ @Injectable()
11
+ export class DuffelService {
12
+ private duffel: Duffel;
13
+
14
+ constructor() {
15
+ const apiKey = process.env.DUFFEL_API_KEY;
16
+ if (!apiKey) {
17
+ throw new Error('DUFFEL_API_KEY environment variable is required');
18
+ }
19
+
20
+ this.duffel = new Duffel({
21
+ token: apiKey
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Search for flights using offer requests
27
+ *
28
+ * @param params Search parameters
29
+ * @returns Flight offers
30
+ */
31
+ async searchFlights(params: {
32
+ origin: string;
33
+ destination: string;
34
+ departureDate: string;
35
+ returnDate?: string;
36
+ passengers: Array<{ type: 'adult' } | { type: 'child'; age: number } | { type: 'infant_without_seat' }>;
37
+ cabinClass?: 'economy' | 'premium_economy' | 'business' | 'first';
38
+ maxConnections?: number;
39
+ departureTime?: { from: string; to: string };
40
+ arrivalTime?: { from: string; to: string };
41
+ }) {
42
+ try {
43
+ const slices: any[] = [
44
+ {
45
+ origin: params.origin,
46
+ destination: params.destination,
47
+ departure_date: params.departureDate,
48
+ ...(params.departureTime && { departure_time: params.departureTime }),
49
+ ...(params.arrivalTime && { arrival_time: params.arrivalTime })
50
+ }
51
+ ];
52
+
53
+ // Add return slice if round trip
54
+ if (params.returnDate) {
55
+ slices.push({
56
+ origin: params.destination,
57
+ destination: params.origin,
58
+ departure_date: params.returnDate
59
+ });
60
+ }
61
+
62
+ const offerRequest = await this.duffel.offerRequests.create({
63
+ slices,
64
+ passengers: params.passengers as any,
65
+ cabin_class: params.cabinClass,
66
+ max_connections: params.maxConnections as 0 | 1 | 2 | undefined,
67
+ return_offers: true
68
+ });
69
+
70
+ return {
71
+ id: offerRequest.data.id,
72
+ offers: offerRequest.data.offers || [],
73
+ passengers: offerRequest.data.passengers,
74
+ slices: offerRequest.data.slices
75
+ };
76
+ } catch (error: any) {
77
+ throw new Error(`Flight search failed: ${error.message || JSON.stringify(error.errors || error)}`);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get a specific offer by ID
83
+ */
84
+ async getOffer(offerId: string) {
85
+ try {
86
+ const offer = await this.duffel.offers.get(offerId);
87
+ return offer.data;
88
+ } catch (error: any) {
89
+ throw new Error(`Failed to get offer: ${error.message}`);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Get available seats for an offer
95
+ */
96
+ async getAvailableSeats(offerId: string) {
97
+ try {
98
+ const seatMaps = await this.duffel.seatMaps.get({ offer_id: offerId });
99
+ return seatMaps.data;
100
+ } catch (error: any) {
101
+ throw new Error(`Failed to get seat maps: ${error.message}`);
102
+ }
103
+ }
104
+
105
+
106
+ /**
107
+ * Generate a simple UUID-like identifier
108
+ */
109
+ private generateId(): string {
110
+ return 'pas_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
111
+ }
112
+
113
+ /**
114
+ * Create an order (book a flight) with optional hold
115
+ * This creates a new offer request with passenger details to get passenger IDs,
116
+ * then uses those IDs to create the order
117
+ */
118
+ async createOrder(params: {
119
+ selectedOffers: string[];
120
+ passengers: Array<{
121
+ title: 'mr' | 'ms' | 'mrs' | 'miss' | 'dr';
122
+ given_name: string;
123
+ family_name: string;
124
+ gender: 'M' | 'F';
125
+ born_on: string;
126
+ email: string;
127
+ phone_number: string;
128
+ }>;
129
+ }) {
130
+ try {
131
+ // Step 1: Get the offer to extract flight details
132
+ const offer = await this.duffel.offers.get(params.selectedOffers[0]);
133
+ const offerData = offer.data;
134
+
135
+ // Step 2: Create a new offer request with passenger details to get passenger IDs
136
+ const offerRequest = await this.duffel.offerRequests.create({
137
+ slices: offerData.slices.map((slice: any) => ({
138
+ origin: slice.origin.iata_code,
139
+ destination: slice.destination.iata_code,
140
+ departure_date: slice.segments[0].departing_at.split('T')[0]
141
+ })),
142
+ passengers: params.passengers.map(p => ({
143
+ type: 'adult', // Simplified - you can enhance this based on age
144
+ given_name: p.given_name,
145
+ family_name: p.family_name,
146
+ title: p.title,
147
+ gender: p.gender.toLowerCase() as 'm' | 'f',
148
+ born_on: p.born_on,
149
+ email: p.email,
150
+ phone_number: p.phone_number
151
+ })),
152
+ cabin_class: (offerData as any).cabin_class || 'economy',
153
+ return_offers: true // We need offers to be returned!
154
+ } as any);
155
+
156
+ const passengerIds = offerRequest.data.passengers.map((p: any) => p.id);
157
+
158
+ // Step 3: Create order using passenger IDs from the NEW offer request
159
+ // We need to use an offer from THIS offer request, not the original one
160
+ const newOfferId = (offerRequest.data as any).offers?.[0]?.id;
161
+
162
+ if (!newOfferId) {
163
+ throw new Error('No offers returned from offer request with passenger details');
164
+ }
165
+
166
+ const orderData: any = {
167
+ selected_offers: [newOfferId], // Use offer from the NEW request
168
+ passengers: passengerIds.map((id: string, index: number) => ({
169
+ id: id,
170
+ ...params.passengers[index],
171
+ gender: params.passengers[index].gender.toLowerCase()
172
+ })),
173
+ type: 'hold' // Always create hold orders
174
+ };
175
+
176
+ const order = await this.duffel.orders.create(orderData);
177
+ return order.data;
178
+ } catch (error: any) {
179
+ // Log detailed error information
180
+ const errorDetails = {
181
+ message: error.message,
182
+ errors: error.errors,
183
+ response: error.response?.data,
184
+ status: error.response?.status
185
+ };
186
+ console.error('Duffel order creation failed:', JSON.stringify(errorDetails, null, 2));
187
+
188
+ throw new Error(`Failed to create order: ${error.message || JSON.stringify(error.errors || error.response?.data || error)}`);
189
+ }
190
+ }
191
+
192
+
193
+
194
+ /**
195
+ * Get available seats for an offer
196
+ */
197
+ async getSeatsForOffer(offerId: string) {
198
+ try {
199
+ const seatMaps = await this.duffel.seatMaps.get({ offer_id: offerId });
200
+ return seatMaps.data;
201
+ } catch (error: any) {
202
+ throw new Error(`Failed to get seats: ${error.message || JSON.stringify(error.errors || error)}`);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Create order change request for seats
208
+ */
209
+ async createOrderChangeForSeats(orderId: string) {
210
+ try {
211
+ const changeRequest = await this.duffel.orderChangeRequests.create({
212
+ order_id: orderId
213
+ } as any);
214
+ return changeRequest.data;
215
+ } catch (error: any) {
216
+ throw new Error(`Failed to create order change: ${error.message || JSON.stringify(error.errors || error)}`);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Get available services (baggage, seats) for an order
222
+ */
223
+ async getAvailableServices(orderId: string) {
224
+ try {
225
+ const services = await this.duffel.orderChangeRequests.create({
226
+ order_id: orderId
227
+ } as any);
228
+ return services.data;
229
+ } catch (error: any) {
230
+ throw new Error(`Failed to get available services: ${error.message || JSON.stringify(error.errors || error)}`);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Get order details
236
+ */
237
+ async getOrder(orderId: string) {
238
+ try {
239
+ const order = await this.duffel.orders.get(orderId);
240
+ return order.data;
241
+ } catch (error: any) {
242
+ throw new Error(`Failed to get order: ${error.message}`);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Cancel an order
248
+ */
249
+ async cancelOrder(orderId: string) {
250
+ try {
251
+ const cancellation = await this.duffel.orderCancellations.create({
252
+ order_id: orderId
253
+ });
254
+ return cancellation.data;
255
+ } catch (error: any) {
256
+ throw new Error(`Failed to cancel order: ${error.message}`);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Search for airports by query
262
+ */
263
+ async searchAirports(query: string) {
264
+ try {
265
+ const suggestions = await this.duffel.suggestions.list({
266
+ query: query
267
+ });
268
+ return suggestions.data;
269
+ } catch (error: any) {
270
+ throw new Error(`Failed to search airports: ${error.message}`);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Get airlines list
276
+ */
277
+ async getAirlines() {
278
+ try {
279
+ const airlines = await this.duffel.airlines.list();
280
+ return airlines.data;
281
+ } catch (error: any) {
282
+ throw new Error(`Failed to get airlines: ${error.message}`);
283
+ }
284
+ }
285
+ }
@@ -0,0 +1,270 @@
1
+ 'use client';
2
+
3
+ import { useWidgetSDK, useTheme } from 'nitrostack/widgets';
4
+
5
+ /**
6
+ * Airport Search Widget
7
+ *
8
+ * Compact display of airport search results with IATA codes and locations.
9
+ */
10
+
11
+ interface AirportResult {
12
+ id: string;
13
+ name: string;
14
+ iataCode: string;
15
+ icaoCode?: string;
16
+ cityName?: string;
17
+ type: string;
18
+ latitude?: number;
19
+ longitude?: number;
20
+ timeZone?: string;
21
+ }
22
+
23
+ interface AirportSearchData {
24
+ query: string;
25
+ results: AirportResult[];
26
+ }
27
+
28
+ export default function AirportSearch() {
29
+ const { getToolOutput } = useWidgetSDK();
30
+ const theme = useTheme();
31
+ const data = getToolOutput<AirportSearchData>();
32
+
33
+ const isDark = theme === 'dark';
34
+
35
+ const getTypeIcon = (type: string) => {
36
+ const icons: Record<string, string> = {
37
+ 'airport': '✈️',
38
+ 'city': '🏙️',
39
+ 'station': '🚉',
40
+ 'bus_station': '🚌',
41
+ 'heliport': '🚁'
42
+ };
43
+ return icons[type] || '📍';
44
+ };
45
+
46
+ if (!data) {
47
+ return (
48
+ <div style={{ padding: '24px', textAlign: 'center', color: isDark ? '#F8FAFC' : '#020617' }}>
49
+ Loading...
50
+ </div>
51
+ );
52
+ }
53
+
54
+ return (
55
+ <div className={isDark ? 'dark' : ''} style={{
56
+ padding: '16px',
57
+ background: isDark ? '#020617' : '#FFFFFF',
58
+ color: isDark ? '#F8FAFC' : '#020617'
59
+ }}>
60
+ {/* Header */}
61
+ <div style={{
62
+ background: isDark ? '#0F172A' : '#F8FAFC',
63
+ borderRadius: '12px',
64
+ padding: '16px',
65
+ marginBottom: '16px',
66
+ border: `1px solid ${isDark ? '#334155' : '#E2E8F0'}`
67
+ }}>
68
+ <div style={{
69
+ display: 'flex',
70
+ alignItems: 'center',
71
+ gap: '10px',
72
+ marginBottom: '8px'
73
+ }}>
74
+ <span style={{ fontSize: '24px' }}>🔍</span>
75
+ <h2 style={{
76
+ margin: 0,
77
+ fontSize: '18px',
78
+ fontWeight: 700
79
+ }}>
80
+ Airport Search
81
+ </h2>
82
+ </div>
83
+ <p style={{
84
+ margin: '8px 0 0 34px',
85
+ fontSize: '14px',
86
+ color: isDark ? '#94A3B8' : '#64748B'
87
+ }}>
88
+ Searching: <strong>"{data.query}"</strong>
89
+ </p>
90
+ <div style={{
91
+ marginTop: '8px',
92
+ marginLeft: '34px',
93
+ fontSize: '12px',
94
+ color: isDark ? '#94A3B8' : '#64748B'
95
+ }}>
96
+ {data.results.length} result{data.results.length !== 1 ? 's' : ''}
97
+ </div>
98
+ </div>
99
+
100
+ {/* Results */}
101
+ {data.results.length > 0 ? (
102
+ <div style={{
103
+ display: 'flex',
104
+ gap: '12px',
105
+ overflowX: 'auto',
106
+ paddingBottom: '12px',
107
+ scrollbarWidth: 'thin',
108
+ scrollbarColor: isDark ? '#334155 #0F172A' : '#CBD5E1 #F1F5F9'
109
+ }}>
110
+ {data.results.map((airport) => (
111
+ <div key={airport.id} style={{
112
+ minWidth: '300px',
113
+ maxWidth: '300px',
114
+ background: isDark ? '#1a1a1a' : '#ffffff',
115
+ border: `1px solid ${isDark ? '#333' : '#e5e7eb'}`,
116
+ borderRadius: '12px',
117
+ padding: '16px',
118
+ boxShadow: isDark ? '0 2px 8px rgba(0,0,0,0.3)' : '0 2px 8px rgba(0,0,0,0.1)',
119
+ transition: 'all 0.2s ease',
120
+ cursor: 'pointer'
121
+ }}
122
+ onMouseEnter={(e) => {
123
+ e.currentTarget.style.transform = 'translateY(-2px)';
124
+ e.currentTarget.style.boxShadow = isDark
125
+ ? '0 4px 12px rgba(59, 159, 255, 0.2)'
126
+ : '0 4px 12px rgba(59, 159, 255, 0.15)';
127
+ }}
128
+ onMouseLeave={(e) => {
129
+ e.currentTarget.style.transform = 'translateY(0)';
130
+ e.currentTarget.style.boxShadow = isDark
131
+ ? '0 2px 8px rgba(0,0,0,0.3)'
132
+ : '0 4px 12px rgba(0,0,0,0.1)';
133
+ }}>
134
+ <div style={{
135
+ display: 'flex',
136
+ justifyContent: 'space-between',
137
+ alignItems: 'flex-start',
138
+ gap: '12px'
139
+ }}>
140
+ {/* Left side */}
141
+ <div style={{ flex: 1 }}>
142
+ <div style={{
143
+ display: 'flex',
144
+ alignItems: 'center',
145
+ gap: '10px',
146
+ marginBottom: '6px'
147
+ }}>
148
+ <span style={{ fontSize: '20px' }}>
149
+ {getTypeIcon(airport.type)}
150
+ </span>
151
+ <div>
152
+ <h3 style={{
153
+ margin: 0,
154
+ fontSize: '16px',
155
+ fontWeight: 600
156
+ }}>
157
+ {airport.name}
158
+ </h3>
159
+ {airport.cityName && (
160
+ <p style={{
161
+ margin: '2px 0 0 0',
162
+ fontSize: '12px',
163
+ color: isDark ? '#94A3B8' : '#64748B'
164
+ }}>
165
+ 📍 {airport.cityName}
166
+ </p>
167
+ )}
168
+ </div>
169
+ </div>
170
+
171
+ {/* Additional details */}
172
+ <div style={{
173
+ display: 'flex',
174
+ gap: '12px',
175
+ marginTop: '8px',
176
+ flexWrap: 'wrap',
177
+ fontSize: '11px',
178
+ color: isDark ? '#94A3B8' : '#64748B'
179
+ }}>
180
+ {airport.timeZone && (
181
+ <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
182
+ <span>🕐</span>
183
+ <span>{airport.timeZone}</span>
184
+ </div>
185
+ )}
186
+ {airport.latitude && airport.longitude && (
187
+ <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
188
+ <span>🌍</span>
189
+ <span>{airport.latitude.toFixed(2)}, {airport.longitude.toFixed(2)}</span>
190
+ </div>
191
+ )}
192
+ </div>
193
+ </div>
194
+
195
+ {/* Right side - Codes */}
196
+ <div style={{
197
+ display: 'flex',
198
+ flexDirection: 'column',
199
+ gap: '6px',
200
+ alignItems: 'flex-end'
201
+ }}>
202
+ {/* IATA Code */}
203
+ <div style={{
204
+ background: 'var(--primary)',
205
+ color: 'white',
206
+ padding: '6px 12px',
207
+ borderRadius: '8px',
208
+ fontWeight: 700,
209
+ fontSize: '16px',
210
+ letterSpacing: '0.5px'
211
+ }}>
212
+ {airport.iataCode || 'N/A'}
213
+ </div>
214
+
215
+ {/* ICAO Code */}
216
+ {airport.icaoCode && (
217
+ <div style={{
218
+ background: isDark ? '#1E293B' : '#F1F5F9',
219
+ color: isDark ? '#CBD5E1' : '#475569',
220
+ padding: '3px 10px',
221
+ borderRadius: '6px',
222
+ fontSize: '10px',
223
+ fontWeight: 600
224
+ }}>
225
+ ICAO: {airport.icaoCode}
226
+ </div>
227
+ )}
228
+
229
+ {/* Type badge */}
230
+ <div className="badge badge-info" style={{ fontSize: '10px' }}>
231
+ {airport.type.replace('_', ' ')}
232
+ </div>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ ))}
237
+ </div>
238
+ ) : (
239
+ <div style={{
240
+ background: isDark ? '#0F172A' : '#F8FAFC',
241
+ borderRadius: '12px',
242
+ padding: '32px',
243
+ textAlign: 'center'
244
+ }}>
245
+ <div style={{ fontSize: '48px', marginBottom: '12px' }}>🔍</div>
246
+ <div style={{ fontSize: '16px', marginBottom: '6px' }}>
247
+ No airports found
248
+ </div>
249
+ <div style={{ fontSize: '12px', color: isDark ? '#94A3B8' : '#64748B' }}>
250
+ Try a different city or airport code
251
+ </div>
252
+ </div>
253
+ )}
254
+
255
+ {/* Help text */}
256
+ <div style={{
257
+ background: isDark ? '#0F172A' : '#F8FAFC',
258
+ borderRadius: '8px',
259
+ padding: '12px',
260
+ marginTop: '12px',
261
+ fontSize: '11px',
262
+ color: isDark ? '#94A3B8' : '#64748B',
263
+ textAlign: 'center',
264
+ border: `1px solid ${isDark ? '#334155' : '#E2E8F0'}`
265
+ }}>
266
+ 💡 <strong>Tip:</strong> Use the IATA code (3-letter) for flight searches
267
+ </div>
268
+ </div>
269
+ );
270
+ }