adserver-dashboard 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 (525) hide show
  1. package/.ci/staging.yml +191 -0
  2. package/.dockerignore +117 -0
  3. package/.env +40 -0
  4. package/.env.staging +38 -0
  5. package/.gitlab-ci.yml +16 -0
  6. package/DEMO_STATUS.md +579 -0
  7. package/Dockerfile +61 -0
  8. package/Influence-MW-AdServer-12-02-2026/client/index.html +17 -0
  9. package/Influence-MW-AdServer-12-02-2026/client/public/favicon.png +0 -0
  10. package/Influence-MW-AdServer-12-02-2026/client/src/App.tsx +91 -0
  11. package/Influence-MW-AdServer-12-02-2026/client/src/components/advanced-map-drawer.tsx +1131 -0
  12. package/Influence-MW-AdServer-12-02-2026/client/src/components/ai-recommendation-panel.tsx +379 -0
  13. package/Influence-MW-AdServer-12-02-2026/client/src/components/app-sidebar.tsx +183 -0
  14. package/Influence-MW-AdServer-12-02-2026/client/src/components/auto-optimize-button.tsx +184 -0
  15. package/Influence-MW-AdServer-12-02-2026/client/src/components/availability-drawer.tsx +385 -0
  16. package/Influence-MW-AdServer-12-02-2026/client/src/components/brand-insights-panel.tsx +87 -0
  17. package/Influence-MW-AdServer-12-02-2026/client/src/components/create-agency-drawer.tsx +198 -0
  18. package/Influence-MW-AdServer-12-02-2026/client/src/components/create-brand-drawer.tsx +275 -0
  19. package/Influence-MW-AdServer-12-02-2026/client/src/components/creative-assignment.tsx +526 -0
  20. package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table-toolbar.tsx +148 -0
  21. package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table.tsx +158 -0
  22. package/Influence-MW-AdServer-12-02-2026/client/src/components/filter-drawer.tsx +356 -0
  23. package/Influence-MW-AdServer-12-02-2026/client/src/components/form-insights-panel.tsx +82 -0
  24. package/Influence-MW-AdServer-12-02-2026/client/src/components/geography-selector.tsx +699 -0
  25. package/Influence-MW-AdServer-12-02-2026/client/src/components/header-user-menu.tsx +178 -0
  26. package/Influence-MW-AdServer-12-02-2026/client/src/components/history-drawer.tsx +313 -0
  27. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-availability-section.tsx +176 -0
  28. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-format-drawer.tsx +173 -0
  29. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-selector.tsx +401 -0
  30. package/Influence-MW-AdServer-12-02-2026/client/src/components/manual-inventory-drawer.tsx +368 -0
  31. package/Influence-MW-AdServer-12-02-2026/client/src/components/mapbox-map.tsx +368 -0
  32. package/Influence-MW-AdServer-12-02-2026/client/src/components/market-insights-panel.tsx +202 -0
  33. package/Influence-MW-AdServer-12-02-2026/client/src/components/media-owner-drawer.tsx +217 -0
  34. package/Influence-MW-AdServer-12-02-2026/client/src/components/metric-card.tsx +58 -0
  35. package/Influence-MW-AdServer-12-02-2026/client/src/components/page-header.tsx +27 -0
  36. package/Influence-MW-AdServer-12-02-2026/client/src/components/player-status-indicator.tsx +137 -0
  37. package/Influence-MW-AdServer-12-02-2026/client/src/components/poi-targeting-drawer.tsx +298 -0
  38. package/Influence-MW-AdServer-12-02-2026/client/src/components/recommendation-score-badge.tsx +102 -0
  39. package/Influence-MW-AdServer-12-02-2026/client/src/components/recommended-inventories-panel.tsx +248 -0
  40. package/Influence-MW-AdServer-12-02-2026/client/src/components/searchable-combobox.tsx +134 -0
  41. package/Influence-MW-AdServer-12-02-2026/client/src/components/signal-visualizations.tsx +407 -0
  42. package/Influence-MW-AdServer-12-02-2026/client/src/components/status-badge.tsx +35 -0
  43. package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-provider.tsx +73 -0
  44. package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-toggle.tsx +37 -0
  45. package/Influence-MW-AdServer-12-02-2026/client/src/components/traffic-slider.tsx +75 -0
  46. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/accordion.tsx +56 -0
  47. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert-dialog.tsx +139 -0
  48. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert.tsx +59 -0
  49. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/aspect-ratio.tsx +5 -0
  50. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/avatar.tsx +51 -0
  51. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/badge.tsx +38 -0
  52. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/breadcrumb.tsx +115 -0
  53. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/button.tsx +62 -0
  54. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/calendar.tsx +68 -0
  55. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/card.tsx +85 -0
  56. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/carousel.tsx +260 -0
  57. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/chart.tsx +365 -0
  58. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/checkbox.tsx +28 -0
  59. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/collapsible.tsx +11 -0
  60. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/command.tsx +151 -0
  61. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/context-menu.tsx +198 -0
  62. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dialog.tsx +122 -0
  63. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/drawer.tsx +118 -0
  64. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dropdown-menu.tsx +198 -0
  65. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/form.tsx +178 -0
  66. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/hover-card.tsx +29 -0
  67. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input-otp.tsx +69 -0
  68. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input.tsx +23 -0
  69. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/label.tsx +24 -0
  70. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/menubar.tsx +256 -0
  71. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/navigation-menu.tsx +128 -0
  72. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/pagination.tsx +117 -0
  73. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/popover.tsx +29 -0
  74. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/progress.tsx +28 -0
  75. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/radio-group.tsx +42 -0
  76. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/resizable.tsx +45 -0
  77. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/scroll-area.tsx +46 -0
  78. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/select.tsx +160 -0
  79. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/separator.tsx +29 -0
  80. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sheet.tsx +140 -0
  81. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sidebar.tsx +727 -0
  82. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/skeleton.tsx +15 -0
  83. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/slider.tsx +26 -0
  84. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/switch.tsx +27 -0
  85. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/table.tsx +117 -0
  86. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tabs.tsx +53 -0
  87. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/textarea.tsx +22 -0
  88. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toast.tsx +127 -0
  89. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toaster.tsx +33 -0
  90. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle-group.tsx +61 -0
  91. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle.tsx +43 -0
  92. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tooltip.tsx +30 -0
  93. package/Influence-MW-AdServer-12-02-2026/client/src/components/vendor-stores-modal.tsx +336 -0
  94. package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-drawer.tsx +359 -0
  95. package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-selector.tsx +436 -0
  96. package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-mobile.tsx +19 -0
  97. package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-toast.ts +191 -0
  98. package/Influence-MW-AdServer-12-02-2026/client/src/index.css +244 -0
  99. package/Influence-MW-AdServer-12-02-2026/client/src/lib/queryClient.ts +57 -0
  100. package/Influence-MW-AdServer-12-02-2026/client/src/lib/utils.ts +39 -0
  101. package/Influence-MW-AdServer-12-02-2026/client/src/lib/venue-taxonomy.ts +532 -0
  102. package/Influence-MW-AdServer-12-02-2026/client/src/main.tsx +5 -0
  103. package/Influence-MW-AdServer-12-02-2026/client/src/pages/assign-creative.tsx +781 -0
  104. package/Influence-MW-AdServer-12-02-2026/client/src/pages/content-hub.tsx +995 -0
  105. package/Influence-MW-AdServer-12-02-2026/client/src/pages/custom-pois.tsx +431 -0
  106. package/Influence-MW-AdServer-12-02-2026/client/src/pages/dashboard.tsx +620 -0
  107. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-detail.tsx +1062 -0
  108. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-form.tsx +1570 -0
  109. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deals.tsx +716 -0
  110. package/Influence-MW-AdServer-12-02-2026/client/src/pages/edit-creative-assignment.tsx +1051 -0
  111. package/Influence-MW-AdServer-12-02-2026/client/src/pages/geotargeting.tsx +675 -0
  112. package/Influence-MW-AdServer-12-02-2026/client/src/pages/integrations.tsx +425 -0
  113. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-creatives.tsx +622 -0
  114. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-form.tsx +3132 -0
  115. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-items.tsx +530 -0
  116. package/Influence-MW-AdServer-12-02-2026/client/src/pages/not-found.tsx +21 -0
  117. package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play-upload.tsx +479 -0
  118. package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play.tsx +880 -0
  119. package/Influence-MW-AdServer-12-02-2026/client/src/pages/reports.tsx +235 -0
  120. package/Influence-MW-AdServer-12-02-2026/client/src/pages/settings.tsx +652 -0
  121. package/Influence-MW-AdServer-12-02-2026/client/src/pages/signal-form.tsx +1117 -0
  122. package/Influence-MW-AdServer-12-02-2026/client/src/pages/signals.tsx +366 -0
  123. package/Influence-MW-AdServer-12-02-2026/client/src/pages/tags.tsx +332 -0
  124. package/Influence-MW-AdServer-12-02-2026/client/src/pages/venues.tsx +381 -0
  125. package/Influence-MW-AdServer-12-02-2026/client/src/types/mapbox-gl-draw.d.ts +37 -0
  126. package/Influence-MW-AdServer-12-02-2026/client/src/types/react-simple-maps.d.ts +57 -0
  127. package/Influence-MW-AdServer-12-02-2026/components.json +20 -0
  128. package/Influence-MW-AdServer-12-02-2026/docs/PRD.md +3373 -0
  129. package/Influence-MW-AdServer-12-02-2026/docs/influence-feature-mapping.csv +498 -0
  130. package/Influence-MW-AdServer-12-02-2026/drizzle.config.ts +14 -0
  131. package/Influence-MW-AdServer-12-02-2026/package-lock.json +9672 -0
  132. package/Influence-MW-AdServer-12-02-2026/package.json +118 -0
  133. package/Influence-MW-AdServer-12-02-2026/postcss.config.js +6 -0
  134. package/Influence-MW-AdServer-12-02-2026/replit.md +91 -0
  135. package/Influence-MW-AdServer-12-02-2026/script/build.ts +67 -0
  136. package/Influence-MW-AdServer-12-02-2026/scripts/create-miro-diagrams.cjs +318 -0
  137. package/Influence-MW-AdServer-12-02-2026/scripts/create-remaining-diagrams.cjs +270 -0
  138. package/Influence-MW-AdServer-12-02-2026/server/index.ts +103 -0
  139. package/Influence-MW-AdServer-12-02-2026/server/recommendation-service.ts +319 -0
  140. package/Influence-MW-AdServer-12-02-2026/server/routes.ts +1890 -0
  141. package/Influence-MW-AdServer-12-02-2026/server/static.ts +19 -0
  142. package/Influence-MW-AdServer-12-02-2026/server/storage.ts +2058 -0
  143. package/Influence-MW-AdServer-12-02-2026/server/vite.ts +58 -0
  144. package/Influence-MW-AdServer-12-02-2026/shared/schema.ts +1595 -0
  145. package/Influence-MW-AdServer-12-02-2026/tailwind.config.ts +107 -0
  146. package/Influence-MW-AdServer-12-02-2026/tsconfig.json +23 -0
  147. package/Influence-MW-AdServer-12-02-2026/vite.config.ts +40 -0
  148. package/LINE_ITEM_BUDGET_FIELD_MAPPING.md +178 -0
  149. package/PCM/.env.example +92 -0
  150. package/PCM/README.md +558 -0
  151. package/PCM/docs/TEST_CASES.md +422 -0
  152. package/PCM/index.js +106 -0
  153. package/PCM/package-lock.json +3282 -0
  154. package/PCM/package.json +32 -0
  155. package/PCM/replit.md +64 -0
  156. package/PCM/schema.sql +495 -0
  157. package/PCM/scripts/export-schema.js +183 -0
  158. package/PCM/scripts/seed-comprehensive.js +631 -0
  159. package/PCM/scripts/seed-production.js +477 -0
  160. package/PCM/src/config/db.js +56 -0
  161. package/PCM/src/config/swagger.js +5975 -0
  162. package/PCM/src/dto/EmailRequestDTO.js +166 -0
  163. package/PCM/src/middleware/errorHandler.js +52 -0
  164. package/PCM/src/middleware/logger.js +26 -0
  165. package/PCM/src/migrations/001_add_campaign_mode_fields.sql +36 -0
  166. package/PCM/src/migrations/002_create_deal_id_counters.sql +22 -0
  167. package/PCM/src/migrations/003_update_publishers_column.sql +15 -0
  168. package/PCM/src/migrations/004_add_direct_dealtype_and_advertiser.sql +5 -0
  169. package/PCM/src/migrations/005_add_programmatic_fields_and_update_enums.sql +31 -0
  170. package/PCM/src/migrations/006_add_line_item_programmatic_fields.sql +12 -0
  171. package/PCM/src/migrations/007_add_line_item_direct_fields.sql +15 -0
  172. package/PCM/src/migrations/008_add_inventory_fields.sql +45 -0
  173. package/PCM/src/migrations/009_move_inventory_fields_to_metadata.sql +32 -0
  174. package/PCM/src/migrations/010_add_draft_status_and_line_items_count.sql +23 -0
  175. package/PCM/src/migrations/011_add_planning_field.sql +21 -0
  176. package/PCM/src/migrations/012_fix_inventory_composite_pk.sql +17 -0
  177. package/PCM/src/migrations/013_make_external_id_optional.sql +3 -0
  178. package/PCM/src/migrations/014_create_change_history.sql +38 -0
  179. package/PCM/src/migrations/016_create_publisher_insertion_orders.sql +33 -0
  180. package/PCM/src/migrations/017_fix_line_item_id_fk_reference.sql +86 -0
  181. package/PCM/src/migrations/018_create_approval_tables.sql +44 -0
  182. package/PCM/src/migrations/019_add_encrypted_token_column.sql +2 -0
  183. package/PCM/src/migrations/020_add_rejection_reason_to_deals.sql +10 -0
  184. package/PCM/src/migrations/021_add_publisher_external_id_to_inventories.sql +12 -0
  185. package/PCM/src/migrations/022_add_line_item_extended_fields.sql +24 -0
  186. package/PCM/src/migrations/023_add_base_price_fields.sql +8 -0
  187. package/PCM/src/migrations/run-migrations.js +46 -0
  188. package/PCM/src/models/ApprovalOTP.js +51 -0
  189. package/PCM/src/models/ApprovalToken.js +79 -0
  190. package/PCM/src/models/ChangeHistory.js +107 -0
  191. package/PCM/src/models/Deal.js +186 -0
  192. package/PCM/src/models/DealIdCounter.js +28 -0
  193. package/PCM/src/models/LineItem.js +227 -0
  194. package/PCM/src/models/LineItemCreative.js +89 -0
  195. package/PCM/src/models/LineItemInventory.js +115 -0
  196. package/PCM/src/models/PublisherInsertionOrder.js +93 -0
  197. package/PCM/src/models/TransactionHistory.js +34 -0
  198. package/PCM/src/models/associations.js +81 -0
  199. package/PCM/src/routes/approval.js +321 -0
  200. package/PCM/src/routes/creatives.js +437 -0
  201. package/PCM/src/routes/deals.js +1638 -0
  202. package/PCM/src/routes/digitalSignage.js +242 -0
  203. package/PCM/src/routes/insertionOrders.js +380 -0
  204. package/PCM/src/routes/lineItems.js +926 -0
  205. package/PCM/src/routes/system.js +384 -0
  206. package/PCM/src/services/ApprovalService.js +885 -0
  207. package/PCM/src/services/CampaignImportConverter.js +631 -0
  208. package/PCM/src/services/CampaignModeService.js +273 -0
  209. package/PCM/src/services/CampaignStatusService.js +395 -0
  210. package/PCM/src/services/ChangeHistoryService.js +316 -0
  211. package/PCM/src/services/DealIdService.js +94 -0
  212. package/PCM/src/services/DealResponseFormatter.js +90 -0
  213. package/PCM/src/services/EmailNotificationService.js +315 -0
  214. package/PCM/src/services/LineItemResponseFormatter.js +122 -0
  215. package/PCM/src/services/LineItemStatusService.js +380 -0
  216. package/PCM/src/tests/comprehensiveTestRunner.js +360 -0
  217. package/PCM/src/tests/comprehensiveTests.js +1277 -0
  218. package/PCM/src/tests/dealTypeUnitTests.js +1058 -0
  219. package/PCM/src/tests/testRunner.js +248 -0
  220. package/PCM/src/utils/caseConverter.js +92 -0
  221. package/PCM/src/utils/dealCalculations.js +206 -0
  222. package/PCM/src/utils/lineItemPayloadNormalizer.js +41 -0
  223. package/PCM/src/utils/payloadNormalizer.js +34 -0
  224. package/PCM/src/utils/sourceNormalizer.js +56 -0
  225. package/PCM/src/validators/creativeValidator.js +27 -0
  226. package/PCM/src/validators/dealValidator.js +203 -0
  227. package/PCM/src/validators/lineItemValidator.js +489 -0
  228. package/PCM/tests/approval-flows.test.js +238 -0
  229. package/PCM/tests/approval-workflow.test.js +291 -0
  230. package/PCM/tests/campaign-import-converter.test.js +543 -0
  231. package/PCM/tests/campaign-import-e2e.test.js +520 -0
  232. package/PCM/tests/campaign-status.test.js +539 -0
  233. package/PCM/tests/direct-publisher-split-reimport.test.js +460 -0
  234. package/PCM/tests/e2e/digital-signage.test.js +145 -0
  235. package/PCM/tests/e2e/search-filter-pagination.test.js +399 -0
  236. package/PCM/tests/e2e-comprehensive.test.js +3446 -0
  237. package/PCM/tests/edge-cases.test.js +340 -0
  238. package/PCM/tests/line-item-status.test.js +340 -0
  239. package/PCM/tests/seller-account-external-ids.test.js +877 -0
  240. package/PCM/tests/source-validation.test.js +324 -0
  241. package/PRD.md +3373 -0
  242. package/README.md +186 -0
  243. package/client/index.html +35 -0
  244. package/client/public/DEMO_STATUS.md +579 -0
  245. package/client/public/img/MW-logo-trans_1754045676555.png +0 -0
  246. package/client/public/locales/ar/approval.json +144 -0
  247. package/client/public/locales/ar/buyer.json +61 -0
  248. package/client/public/locales/ar/campaigns.json +1 -0
  249. package/client/public/locales/ar/common.json +218 -0
  250. package/client/public/locales/ar/contentHub.json +266 -0
  251. package/client/public/locales/ar/creatives.json +79 -0
  252. package/client/public/locales/ar/dashboard.json +57 -0
  253. package/client/public/locales/ar/deals.json +886 -0
  254. package/client/public/locales/ar/dsp.json +131 -0
  255. package/client/public/locales/ar/inventory.json +201 -0
  256. package/client/public/locales/ar/lineItems.json +553 -0
  257. package/client/public/locales/ar/navigation.json +48 -0
  258. package/client/public/locales/ar/wizard.json +1 -0
  259. package/client/public/locales/en/approval.json +144 -0
  260. package/client/public/locales/en/buyer.json +65 -0
  261. package/client/public/locales/en/campaigns.json +1 -0
  262. package/client/public/locales/en/common.json +218 -0
  263. package/client/public/locales/en/contentHub.json +266 -0
  264. package/client/public/locales/en/creatives.json +79 -0
  265. package/client/public/locales/en/dashboard.json +57 -0
  266. package/client/public/locales/en/deals.json +886 -0
  267. package/client/public/locales/en/dsp.json +131 -0
  268. package/client/public/locales/en/inventory.json +201 -0
  269. package/client/public/locales/en/lineItems.json +659 -0
  270. package/client/public/locales/en/navigation.json +48 -0
  271. package/client/public/locales/en/wizard.json +1 -0
  272. package/client/public/locales/ja/approval.json +144 -0
  273. package/client/public/locales/ja/buyer.json +61 -0
  274. package/client/public/locales/ja/campaigns.json +1 -0
  275. package/client/public/locales/ja/common.json +218 -0
  276. package/client/public/locales/ja/contentHub.json +266 -0
  277. package/client/public/locales/ja/creatives.json +79 -0
  278. package/client/public/locales/ja/dashboard.json +57 -0
  279. package/client/public/locales/ja/deals.json +886 -0
  280. package/client/public/locales/ja/dsp.json +131 -0
  281. package/client/public/locales/ja/inventory.json +201 -0
  282. package/client/public/locales/ja/lineItems.json +553 -0
  283. package/client/public/locales/ja/navigation.json +48 -0
  284. package/client/public/locales/ja/wizard.json +1 -0
  285. package/client/public/locales/zh/approval.json +144 -0
  286. package/client/public/locales/zh/buyer.json +61 -0
  287. package/client/public/locales/zh/campaigns.json +1 -0
  288. package/client/public/locales/zh/common.json +218 -0
  289. package/client/public/locales/zh/contentHub.json +266 -0
  290. package/client/public/locales/zh/creatives.json +79 -0
  291. package/client/public/locales/zh/dashboard.json +57 -0
  292. package/client/public/locales/zh/deals.json +886 -0
  293. package/client/public/locales/zh/dsp.json +131 -0
  294. package/client/public/locales/zh/inventory.json +201 -0
  295. package/client/public/locales/zh/lineItems.json +553 -0
  296. package/client/public/locales/zh/navigation.json +48 -0
  297. package/client/public/locales/zh/wizard.json +1 -0
  298. package/client/public/manifest.json +36 -0
  299. package/client/src/App.tsx +464 -0
  300. package/client/src/components/app-sidebar.tsx +312 -0
  301. package/client/src/components/approval/approval-decision-form.test.tsx +294 -0
  302. package/client/src/components/approval/approval-decision-form.tsx +326 -0
  303. package/client/src/components/approval/approval-sheet.tsx +631 -0
  304. package/client/src/components/approval/line-item-details-sheet.tsx +371 -0
  305. package/client/src/components/approval/otp-verification.test.tsx +337 -0
  306. package/client/src/components/approval/otp-verification.tsx +180 -0
  307. package/client/src/components/content-hub/bulk-transcode-dialog.tsx +379 -0
  308. package/client/src/components/content-hub/content-hub-manager-v2.tsx +574 -0
  309. package/client/src/components/content-hub/content-hub-manager.tsx +330 -0
  310. package/client/src/components/content-hub/creative-card.tsx +456 -0
  311. package/client/src/components/content-hub/creative-detail-sheet.tsx +685 -0
  312. package/client/src/components/content-hub/creative-filters.tsx +457 -0
  313. package/client/src/components/content-hub/creative-grid.tsx +329 -0
  314. package/client/src/components/content-hub/creative-selector.tsx +415 -0
  315. package/client/src/components/content-hub/creative-upload.tsx +547 -0
  316. package/client/src/components/content-hub/folder-dialogs.tsx +445 -0
  317. package/client/src/components/content-hub/folder-list.tsx +280 -0
  318. package/client/src/components/content-hub/review-dialogs.tsx +268 -0
  319. package/client/src/components/content-hub/transcode-dialog.tsx +226 -0
  320. package/client/src/components/creative-library/creative-details-view.tsx +446 -0
  321. package/client/src/components/creative-library/creative-filters-panel.tsx +203 -0
  322. package/client/src/components/creative-library/creative-list.tsx +360 -0
  323. package/client/src/components/creative-library/creative-status-badge.tsx +71 -0
  324. package/client/src/components/creative-library/folder-card.tsx +78 -0
  325. package/client/src/components/creative-library/index.ts +27 -0
  326. package/client/src/components/creative-library/new-creative-card.tsx +211 -0
  327. package/client/src/components/creative-library/upload-creative-dialog.tsx +261 -0
  328. package/client/src/components/dashboard-overview.tsx +109 -0
  329. package/client/src/components/deals/approval-history-panel.test.tsx +240 -0
  330. package/client/src/components/deals/approval-history-panel.tsx +156 -0
  331. package/client/src/components/deals/deal-status-badge.tsx +92 -0
  332. package/client/src/components/deals/import-from-planner-dialog.tsx +399 -0
  333. package/client/src/components/deals/market-insights-panel.tsx +237 -0
  334. package/client/src/components/deals/reopen-deal-sheet.tsx +191 -0
  335. package/client/src/components/deals/request-approval-sheet.test.tsx +323 -0
  336. package/client/src/components/deals/request-approval-sheet.tsx +136 -0
  337. package/client/src/components/deals/resend-approval-sheet.tsx +201 -0
  338. package/client/src/components/direct-campaigns/campaign-card.tsx +283 -0
  339. package/client/src/components/direct-campaigns/deal-filter-panel.tsx +325 -0
  340. package/client/src/components/inventory/advanced-filters-panel.tsx +273 -0
  341. package/client/src/components/inventory/csv-upload-modal.tsx +639 -0
  342. package/client/src/components/inventory/inventory-availability-view.tsx +486 -0
  343. package/client/src/components/inventory/inventory-details-sheet.tsx +376 -0
  344. package/client/src/components/inventory/inventory-map-view.tsx +596 -0
  345. package/client/src/components/inventory/inventory-settings-menu.tsx +52 -0
  346. package/client/src/components/language-switcher.tsx +53 -0
  347. package/client/src/components/line-items/campaign-forecast-panel.tsx +138 -0
  348. package/client/src/components/line-items/form-insights.tsx +89 -0
  349. package/client/src/components/line-items/geofencing/LocationCsvUploadDrawer.tsx +100 -0
  350. package/client/src/components/line-items/geofencing/POIDropdown.tsx +379 -0
  351. package/client/src/components/line-items/geofencing/SelectedLocationsSidebar.tsx +436 -0
  352. package/client/src/components/line-items/geofencing/ViewFileLocationDrawer.tsx +199 -0
  353. package/client/src/components/line-items/geofencing/components/ExistingFilesTab.tsx +268 -0
  354. package/client/src/components/line-items/geofencing/components/TemplateDownloadSection.tsx +59 -0
  355. package/client/src/components/line-items/geofencing/components/UploadTab.tsx +215 -0
  356. package/client/src/components/line-items/geofencing-map.tsx +1270 -0
  357. package/client/src/components/line-items/inventory-availability-section.tsx +178 -0
  358. package/client/src/components/line-items/line-item-schedule-manager.tsx +313 -0
  359. package/client/src/components/line-items/manual-inventory-drawer.tsx +346 -0
  360. package/client/src/components/line-items/planner-inventory-card.tsx +495 -0
  361. package/client/src/components/line-items/planner-schedule-grid.tsx +495 -0
  362. package/client/src/components/line-items/schedule-rule-editor.tsx +649 -0
  363. package/client/src/components/line-items/schedule-rule-types.ts +122 -0
  364. package/client/src/components/line-items/steps/creatives-step.tsx +681 -0
  365. package/client/src/components/line-items/steps/inventory-schedule-step.tsx +1596 -0
  366. package/client/src/components/line-items/steps/inventory-step.tsx +1533 -0
  367. package/client/src/components/line-items/steps/line-item-details-step.tsx +916 -0
  368. package/client/src/components/line-items/steps/schedule-step.tsx +273 -0
  369. package/client/src/components/line-items/steps/summary-step.tsx +680 -0
  370. package/client/src/components/line-items/steps/targeting-step.tsx +1708 -0
  371. package/client/src/components/product-switcher.tsx +105 -0
  372. package/client/src/components/protected-route.tsx +49 -0
  373. package/client/src/components/skip-link.tsx +22 -0
  374. package/client/src/components/stat-card.tsx +53 -0
  375. package/client/src/components/status-badge.tsx +96 -0
  376. package/client/src/components/ui/hierarchical-venue-selector.tsx +389 -0
  377. package/client/src/components/ui/toaster.tsx +111 -0
  378. package/client/src/contexts/auth-context.tsx +181 -0
  379. package/client/src/contexts/sidebar-state.tsx +50 -0
  380. package/client/src/contexts/theme-context.tsx +66 -0
  381. package/client/src/data/campaign-data.json +107 -0
  382. package/client/src/data/countries.json +22 -0
  383. package/client/src/hooks/use-approval.ts +366 -0
  384. package/client/src/hooks/use-keyboard-shortcuts.ts +74 -0
  385. package/client/src/hooks/use-media-query.ts +46 -0
  386. package/client/src/hooks/use-mobile.tsx +19 -0
  387. package/client/src/hooks/use-page-title.ts +21 -0
  388. package/client/src/hooks/use-toast.ts +195 -0
  389. package/client/src/index.css +694 -0
  390. package/client/src/lib/__tests__/accessibility.test.ts +104 -0
  391. package/client/src/lib/__tests__/date-utils.test.ts +199 -0
  392. package/client/src/lib/__tests__/dsp-buyer-api.test.ts +127 -0
  393. package/client/src/lib/__tests__/dsp-buyer-integration.test.ts +247 -0
  394. package/client/src/lib/__tests__/storage-utils.test.ts +167 -0
  395. package/client/src/lib/__tests__/utils.test.ts +57 -0
  396. package/client/src/lib/accessibility.ts +141 -0
  397. package/client/src/lib/api-config.ts +9 -0
  398. package/client/src/lib/auth-service.ts +209 -0
  399. package/client/src/lib/campaign-creative-api.ts +82 -0
  400. package/client/src/lib/company-api.ts +61 -0
  401. package/client/src/lib/content-hub-api.ts +407 -0
  402. package/client/src/lib/creative-mapper.ts +61 -0
  403. package/client/src/lib/date-utils.ts +119 -0
  404. package/client/src/lib/deal-helpers.ts +220 -0
  405. package/client/src/lib/dsp-buyer-api.ts +196 -0
  406. package/client/src/lib/geo-import-api.ts +151 -0
  407. package/client/src/lib/google-poi-api.ts +305 -0
  408. package/client/src/lib/i18n/__tests__/formatting.test.ts +202 -0
  409. package/client/src/lib/i18n/formatting.ts +130 -0
  410. package/client/src/lib/i18n/index.ts +8 -0
  411. package/client/src/lib/i18n-compat.ts +76 -0
  412. package/client/src/lib/influence-deals-api.ts +896 -0
  413. package/client/src/lib/inventory-api.ts +399 -0
  414. package/client/src/lib/oauth-service.ts +678 -0
  415. package/client/src/lib/poi-types.ts +75 -0
  416. package/client/src/lib/queryClient.ts +144 -0
  417. package/client/src/lib/recommendation-api.ts +380 -0
  418. package/client/src/lib/storage-utils.ts +104 -0
  419. package/client/src/lib/tolgee.ts +85 -0
  420. package/client/src/lib/utils.ts +0 -0
  421. package/client/src/main.tsx +67 -0
  422. package/client/src/mapbox-draw-modes.d.ts +32 -0
  423. package/client/src/pages/all-folders.tsx +203 -0
  424. package/client/src/pages/auth-callback.tsx +115 -0
  425. package/client/src/pages/buyer-form.tsx +339 -0
  426. package/client/src/pages/buyer-list.tsx +622 -0
  427. package/client/src/pages/content-hub.tsx +1358 -0
  428. package/client/src/pages/create-deal.tsx +2093 -0
  429. package/client/src/pages/creative-assignment-page.tsx +548 -0
  430. package/client/src/pages/creatives.tsx +5 -0
  431. package/client/src/pages/custom-pois.tsx +425 -0
  432. package/client/src/pages/dashboard.tsx +615 -0
  433. package/client/src/pages/deal-history.tsx +434 -0
  434. package/client/src/pages/deal-line-items.tsx +1703 -0
  435. package/client/src/pages/demo-status.tsx +113 -0
  436. package/client/src/pages/direct-campaign-details.tsx +361 -0
  437. package/client/src/pages/direct-campaigns-new.tsx +824 -0
  438. package/client/src/pages/dsp-form.tsx +803 -0
  439. package/client/src/pages/dsp-list.tsx +239 -0
  440. package/client/src/pages/folder-content.tsx +336 -0
  441. package/client/src/pages/integrations.tsx +429 -0
  442. package/client/src/pages/line-item-creatives.tsx +789 -0
  443. package/client/src/pages/line-item-detail-page.tsx +684 -0
  444. package/client/src/pages/line-item-form-page.tsx +3261 -0
  445. package/client/src/pages/line-item-wizard.tsx +1207 -0
  446. package/client/src/pages/login.tsx +154 -0
  447. package/client/src/pages/not-found.tsx +23 -0
  448. package/client/src/pages/proof-of-play.tsx +397 -0
  449. package/client/src/pages/public-approval.tsx +551 -0
  450. package/client/src/pages/reports.tsx +231 -0
  451. package/client/src/pages/settings.tsx +760 -0
  452. package/client/src/pages/signals.tsx +389 -0
  453. package/client/src/pages/tags.tsx +318 -0
  454. package/client/src/pages/test-results.tsx +328 -0
  455. package/client/src/store/hooks.ts +5 -0
  456. package/client/src/store/index.ts +15 -0
  457. package/client/src/store/mapMarkerLocationsSlice.ts +241 -0
  458. package/client/src/styles/design-tokens.css +324 -0
  459. package/client/src/test/setup.ts +261 -0
  460. package/client/src/test/test-utils.tsx +40 -0
  461. package/client/src/types/approval.ts +221 -0
  462. package/client/src/types/content-hub.ts +209 -0
  463. package/client/src/types/geofencing.ts +67 -0
  464. package/client/src/types/transcoding.ts +140 -0
  465. package/client/src/vite-env.d.ts +18 -0
  466. package/components.json +20 -0
  467. package/creative-api.json +1 -0
  468. package/docs/AI_REFERENCE.md +459 -0
  469. package/docs/MWDesign-Prompt.md +132 -0
  470. package/docs/MWDesign-System.md +344 -0
  471. package/docs/test-plan.md +277 -0
  472. package/e2e/AUTONOMOUS-TESTING.md +406 -0
  473. package/e2e/README.md +219 -0
  474. package/e2e/autonomous-flow.spec.ts +308 -0
  475. package/e2e/debug-sso.spec.ts +163 -0
  476. package/e2e/direct-campaigns.spec.ts +219 -0
  477. package/e2e/explore-sso.spec.ts +149 -0
  478. package/e2e/fixtures/auth.ts +26 -0
  479. package/e2e/fixtures/enhanced-test.ts +331 -0
  480. package/e2e/pagination.spec.ts +280 -0
  481. package/e2e/view-toggle.spec.ts +312 -0
  482. package/generated-icon.png +0 -0
  483. package/i18next-scanner.config.cjs +46 -0
  484. package/package.json +141 -0
  485. package/playwright.config.ts +93 -0
  486. package/postcss.config.js +6 -0
  487. package/replit.md +196 -0
  488. package/screenshot-after-login.png +0 -0
  489. package/screenshot-contenthub-grid.png +0 -0
  490. package/screenshot-contenthub-list-fixed.png +0 -0
  491. package/screenshot-contenthub-list.png +0 -0
  492. package/screenshot-create-deal.png +0 -0
  493. package/screenshot-dashboard.png +0 -0
  494. package/screenshot-deals.png +0 -0
  495. package/screenshot-login-filled.png +0 -0
  496. package/screenshot-login.png +0 -0
  497. package/screenshot.mjs +24 -0
  498. package/scripts/deploy-stg.sh +185 -0
  499. package/shared/direct-io-schema.ts +383 -0
  500. package/shared/schema.ts +439 -0
  501. package/shared/screen-types.ts +149 -0
  502. package/springdocDefault.json +1 -0
  503. package/swagger-ui-bundle.js +2 -0
  504. package/swagger-ui-init.js +10316 -0
  505. package/tailwind.config.ts +282 -0
  506. package/terraform/README.md +306 -0
  507. package/terraform/cloudfront.tf +289 -0
  508. package/terraform/ecs.tf +727 -0
  509. package/terraform/environments/dev.tfvars +59 -0
  510. package/terraform/environments/production.tfvars +60 -0
  511. package/terraform/main.tf +47 -0
  512. package/terraform/outputs.tf +145 -0
  513. package/terraform/s3.tf +192 -0
  514. package/terraform/variables.tf +226 -0
  515. package/terraform/waf.tf +165 -0
  516. package/terraform-frontend/.terraform.lock.hcl +25 -0
  517. package/terraform-frontend/README.md +85 -0
  518. package/terraform-frontend/cloudfront.tf +125 -0
  519. package/terraform-frontend/main.tf +31 -0
  520. package/terraform-frontend/outputs.tf +24 -0
  521. package/terraform-frontend/terraform.tfvars +12 -0
  522. package/terraform-frontend/variables.tf +53 -0
  523. package/tsconfig.json +23 -0
  524. package/vite.config.ts +226 -0
  525. package/vitest.config.ts +56 -0
@@ -0,0 +1,149 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ /**
4
+ * SSO Flow Exploration Script
5
+ *
6
+ * Run this with: npm run test:e2e:headed explore-sso.spec.ts
7
+ * This will show you the actual flow and help understand the selectors
8
+ */
9
+
10
+ test('Explore SSO Login Flow', async ({ page }) => {
11
+ // Enable slow motion to see what's happening
12
+ await page.goto('/login', { waitUntil: 'networkidle' });
13
+
14
+ console.log('\n=== STEP 1: Login Page ===');
15
+ console.log('URL:', page.url());
16
+
17
+ // Take screenshot
18
+ await page.screenshot({ path: 'explore-01-login-page.png', fullPage: true });
19
+ console.log('Screenshot saved: explore-01-login-page.png');
20
+
21
+ // Find all buttons on the page
22
+ const buttons = await page.locator('button').all();
23
+ console.log(`\nFound ${buttons.length} buttons:`);
24
+ for (let i = 0; i < buttons.length; i++) {
25
+ const text = await buttons[i].textContent();
26
+ const isVisible = await buttons[i].isVisible();
27
+ console.log(` Button ${i + 1}: "${text}" (visible: ${isVisible})`);
28
+ }
29
+
30
+ // Look for SSO button
31
+ const ssoButton = page.locator('button:has-text("Sign in with MW ID")');
32
+ const ssoExists = await ssoButton.count();
33
+ console.log(`\nSSO Button exists: ${ssoExists > 0}`);
34
+
35
+ if (ssoExists > 0) {
36
+ console.log('\n=== STEP 2: Clicking SSO Button ===');
37
+ await ssoButton.click();
38
+
39
+ // Wait for navigation
40
+ await page.waitForLoadState('networkidle');
41
+ console.log('After SSO click URL:', page.url());
42
+
43
+ // Take screenshot
44
+ await page.screenshot({ path: 'explore-02-sso-provider.png', fullPage: true });
45
+ console.log('Screenshot saved: explore-02-sso-provider.png');
46
+
47
+ // Find all input fields
48
+ console.log('\n=== STEP 3: Analyzing SSO Form ===');
49
+ const inputs = await page.locator('input').all();
50
+ console.log(`Found ${inputs.length} input fields:`);
51
+ for (let i = 0; i < inputs.length; i++) {
52
+ const type = await inputs[i].getAttribute('type');
53
+ const name = await inputs[i].getAttribute('name');
54
+ const placeholder = await inputs[i].getAttribute('placeholder');
55
+ const isVisible = await inputs[i].isVisible();
56
+ console.log(` Input ${i + 1}: type="${type}", name="${name}", placeholder="${placeholder}", visible=${isVisible}`);
57
+ }
58
+
59
+ // Find email input
60
+ const emailInput = page.locator('input[type="email"]').first();
61
+ const emailExists = await emailInput.count();
62
+ console.log(`\nEmail input exists: ${emailExists > 0}`);
63
+
64
+ if (emailExists > 0) {
65
+ console.log('\n=== STEP 4: Filling Form ===');
66
+
67
+ // Get credentials from env
68
+ const testEmail = process.env.SSO_TEST_USER || 'jeki@jeki.com';
69
+ const testPassword = process.env.SSO_TEST_PASSWORD || 'jeki@123';
70
+
71
+ console.log(`Using email: ${testEmail}`);
72
+
73
+ // Try different methods to fill
74
+ const passwordInput = page.locator('input[type="password"]').first();
75
+
76
+ // Method 1: Direct fill
77
+ await emailInput.click();
78
+ await emailInput.fill(testEmail);
79
+ await page.waitForTimeout(500);
80
+
81
+ await passwordInput.click();
82
+ await passwordInput.fill(testPassword);
83
+ await page.waitForTimeout(500);
84
+
85
+ // Check if filled
86
+ const emailValue = await emailInput.inputValue();
87
+ const passwordValue = await passwordInput.inputValue();
88
+ console.log(`Email filled: "${emailValue}" (length: ${emailValue.length})`);
89
+ console.log(`Password filled: ${passwordValue.length} characters`);
90
+
91
+ // Take screenshot
92
+ await page.screenshot({ path: 'explore-03-form-filled.png', fullPage: true });
93
+ console.log('Screenshot saved: explore-03-form-filled.png');
94
+
95
+ // Find submit button
96
+ const submitButtons = await page.locator('button[type="submit"], button:has-text("Sign in")').all();
97
+ console.log(`\nFound ${submitButtons.length} submit buttons`);
98
+ for (let i = 0; i < submitButtons.length; i++) {
99
+ const text = await submitButtons[i].textContent();
100
+ console.log(` Submit button ${i + 1}: "${text}"`);
101
+ }
102
+
103
+ if (emailValue && passwordValue) {
104
+ console.log('\n=== STEP 5: Submitting Form ===');
105
+ const submitButton = page.locator('button[type="submit"]').first();
106
+ await submitButton.click();
107
+ console.log('Form submitted!');
108
+
109
+ // Wait for navigation
110
+ await page.waitForTimeout(3000);
111
+ console.log('After submit URL:', page.url());
112
+
113
+ // Take screenshot
114
+ await page.screenshot({ path: 'explore-04-after-submit.png', fullPage: true });
115
+ console.log('Screenshot saved: explore-04-after-submit.png');
116
+
117
+ // Check if we're authenticated
118
+ const isAuthenticated = !page.url().includes('login');
119
+ console.log(`\nAuthentication successful: ${isAuthenticated}`);
120
+
121
+ if (isAuthenticated) {
122
+ console.log('\n✅ SSO LOGIN SUCCESSFUL!');
123
+ console.log('Final URL:', page.url());
124
+ } else {
125
+ console.log('\n❌ Still on login page');
126
+
127
+ // Check for errors
128
+ const errorMessages = await page.locator('.error, [role="alert"], .text-red-500').all();
129
+ if (errorMessages.length > 0) {
130
+ console.log('\nError messages found:');
131
+ for (const error of errorMessages) {
132
+ const text = await error.textContent();
133
+ console.log(` - ${text}`);
134
+ }
135
+ }
136
+ }
137
+ } else {
138
+ console.log('\n❌ Form fields not filled properly!');
139
+ console.log('This is a React form - we need to use different methods');
140
+ }
141
+ }
142
+ }
143
+
144
+ // Keep browser open for inspection
145
+ await page.waitForTimeout(5000);
146
+
147
+ console.log('\n=== EXPLORATION COMPLETE ===');
148
+ console.log('Check the screenshots to understand the flow');
149
+ });
@@ -0,0 +1,26 @@
1
+ import { test as base, Page } from '@playwright/test';
2
+
3
+ /**
4
+ * Authentication fixture for tests
5
+ * Handles login and maintains authenticated state
6
+ */
7
+ export const test = base.extend<{ authenticatedPage: Page }>({
8
+ authenticatedPage: async ({ page }, use) => {
9
+ // Navigate to login page
10
+ await page.goto('/login');
11
+
12
+ // TODO: Add your authentication logic here
13
+ // For now, we'll just wait for navigation to complete
14
+ // You'll need to update this with actual login credentials
15
+
16
+ // Example login flow (update with your actual selectors):
17
+ // await page.fill('input[name="email"]', 'test@example.com');
18
+ // await page.fill('input[name="password"]', 'password');
19
+ // await page.click('button[type="submit"]');
20
+ // await page.waitForURL('/');
21
+
22
+ await use(page);
23
+ },
24
+ });
25
+
26
+ export { expect } from '@playwright/test';
@@ -0,0 +1,331 @@
1
+ import { test as base, expect, Page } from '@playwright/test';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ /**
6
+ * Enhanced test fixture with:
7
+ * - Automatic screenshot capture
8
+ * - Console log collection
9
+ * - Network monitoring
10
+ * - DOM snapshots
11
+ * - SSO authentication
12
+ */
13
+
14
+ interface EnhancedTestFixtures {
15
+ authenticatedPage: Page;
16
+ testInfo: {
17
+ screenshotDir: string;
18
+ logDir: string;
19
+ consoleLogs: string[];
20
+ networkLogs: Array<{ url: string; status: number; method: string }>;
21
+ errors: string[];
22
+ };
23
+ }
24
+
25
+ export const test = base.extend<EnhancedTestFixtures>({
26
+ testInfo: async ({}, use, testInfo) => {
27
+ // Create directories for test artifacts
28
+ const testDir = path.join('test-results', testInfo.titlePath.join('-').replace(/[^a-z0-9]/gi, '_'));
29
+ const screenshotDir = path.join(testDir, 'screenshots');
30
+ const logDir = path.join(testDir, 'logs');
31
+
32
+ fs.mkdirSync(screenshotDir, { recursive: true });
33
+ fs.mkdirSync(logDir, { recursive: true });
34
+
35
+ const info = {
36
+ screenshotDir,
37
+ logDir,
38
+ consoleLogs: [] as string[],
39
+ networkLogs: [] as Array<{ url: string; status: number; method: string }>,
40
+ errors: [] as string[],
41
+ };
42
+
43
+ await use(info);
44
+
45
+ // Save logs at the end of the test
46
+ fs.writeFileSync(
47
+ path.join(logDir, 'console.log'),
48
+ info.consoleLogs.join('\n')
49
+ );
50
+ fs.writeFileSync(
51
+ path.join(logDir, 'network.json'),
52
+ JSON.stringify(info.networkLogs, null, 2)
53
+ );
54
+ if (info.errors.length > 0) {
55
+ fs.writeFileSync(
56
+ path.join(logDir, 'errors.log'),
57
+ info.errors.join('\n')
58
+ );
59
+ }
60
+ },
61
+
62
+ authenticatedPage: async ({ page, testInfo: info }, use) => {
63
+ // Set up console log capture
64
+ page.on('console', (msg) => {
65
+ const logMessage = `[${msg.type()}] ${msg.text()}`;
66
+ info.consoleLogs.push(logMessage);
67
+ console.log('🖥️ Console:', logMessage);
68
+ });
69
+
70
+ // Set up error capture
71
+ page.on('pageerror', (error) => {
72
+ const errorMessage = `[Page Error] ${error.message}\n${error.stack}`;
73
+ info.errors.push(errorMessage);
74
+ console.error('❌ Page Error:', error.message);
75
+ });
76
+
77
+ // Set up network monitoring
78
+ page.on('request', (request) => {
79
+ console.log('🌐 Request:', request.method(), request.url());
80
+ });
81
+
82
+ page.on('response', async (response) => {
83
+ const url = response.url();
84
+ const status = response.status();
85
+ const method = response.request().method();
86
+
87
+ info.networkLogs.push({ url, status, method });
88
+
89
+ if (status >= 400) {
90
+ console.error(`❌ HTTP ${status}:`, method, url);
91
+ try {
92
+ const body = await response.text();
93
+ info.errors.push(`[HTTP ${status}] ${method} ${url}\nResponse: ${body}`);
94
+ } catch (e) {
95
+ // Some responses can't be read
96
+ }
97
+ } else {
98
+ console.log(`✅ HTTP ${status}:`, method, url);
99
+ }
100
+ });
101
+
102
+ // Navigate to login page
103
+ console.log('\n🔐 Starting authentication flow...');
104
+ await page.goto('/login', { waitUntil: 'networkidle' });
105
+
106
+ // Take screenshot of login page
107
+ await page.screenshot({
108
+ path: path.join(info.screenshotDir, '01-login-page.png'),
109
+ fullPage: true,
110
+ });
111
+ console.log('📸 Screenshot: login page');
112
+
113
+ // Check if we're already logged in (redirect to home)
114
+ const currentUrl = page.url();
115
+ if (!currentUrl.includes('/login')) {
116
+ console.log('✅ Already authenticated, skipping login');
117
+ await use(page);
118
+ return;
119
+ }
120
+
121
+ // Look for SSO login button or form
122
+ console.log('🔍 Looking for login form...');
123
+
124
+ // Get SSO credentials from environment
125
+ const ssoUser = process.env.SSO_TEST_USER || process.env.TEST_EMAIL || '';
126
+ const ssoPassword = process.env.SSO_TEST_PASSWORD || process.env.TEST_PASSWORD || '';
127
+ const ssoProvider = process.env.SSO_PROVIDER || 'Sign in with MW ID';
128
+
129
+ // Check for different login scenarios
130
+ const ssoButton = page.locator(`button:has-text("${ssoProvider}")`);
131
+ const emailInput = page.locator('input[name="email"], input[type="email"]');
132
+ const passwordInput = page.locator('input[name="password"], input[type="password"]');
133
+
134
+ // Scenario 1: SSO Button (primary method)
135
+ if (await ssoButton.isVisible({ timeout: 5000 }).catch(() => false)) {
136
+ console.log(`🎯 SSO button found: "${ssoProvider}", clicking...`);
137
+
138
+ // Click SSO button and wait for navigation to SSO provider
139
+ await ssoButton.click();
140
+ console.log('⏳ Waiting for SSO provider page...');
141
+ await page.waitForLoadState('networkidle', { timeout: 15000 });
142
+
143
+ await page.screenshot({
144
+ path: path.join(info.screenshotDir, '02-after-sso-click.png'),
145
+ fullPage: true,
146
+ });
147
+ console.log('📸 Screenshot: SSO provider page');
148
+
149
+ // Now on SSO provider page - enter credentials
150
+ console.log('🔍 Looking for SSO login form...');
151
+ const ssoEmailInput = page.locator('input[name="email"], input[type="email"], input[name="username"]').first();
152
+ const ssoPasswordInput = page.locator('input[name="password"], input[type="password"]').first();
153
+
154
+ if (await ssoEmailInput.isVisible({ timeout: 5000 }).catch(() => false)) {
155
+ console.log('📝 Filling SSO credentials...');
156
+
157
+ // Fill email field using pressSequentially for React forms
158
+ await ssoEmailInput.click();
159
+ await page.waitForTimeout(100);
160
+ await ssoEmailInput.clear();
161
+ await page.waitForTimeout(100);
162
+
163
+ // Type character by character for React forms
164
+ await ssoEmailInput.pressSequentially(ssoUser, { delay: 50 });
165
+ await page.waitForTimeout(200);
166
+ console.log(` Email filled: ${ssoUser}`);
167
+
168
+ // Fill password field using pressSequentially for React forms
169
+ await ssoPasswordInput.click();
170
+ await page.waitForTimeout(100);
171
+ await ssoPasswordInput.clear();
172
+ await page.waitForTimeout(100);
173
+
174
+ // Type character by character for React forms
175
+ await ssoPasswordInput.pressSequentially(ssoPassword, { delay: 50 });
176
+ await page.waitForTimeout(200);
177
+ console.log(' Password filled');
178
+
179
+ // Wait for form validation
180
+ await page.waitForTimeout(500);
181
+
182
+ await page.screenshot({
183
+ path: path.join(info.screenshotDir, '02-sso-form-filled.png'),
184
+ fullPage: true,
185
+ });
186
+ console.log('📸 Screenshot: SSO form filled');
187
+
188
+ // Verify fields are filled
189
+ const emailValue = await ssoEmailInput.inputValue();
190
+ const passwordValue = await ssoPasswordInput.inputValue();
191
+ console.log(` Email value: ${emailValue}`);
192
+ console.log(` Password length: ${passwordValue.length}`);
193
+
194
+ if (!emailValue || !passwordValue) {
195
+ console.error('❌ Form fields not properly filled!');
196
+ await page.screenshot({
197
+ path: path.join(info.screenshotDir, '02-sso-form-fill-failed.png'),
198
+ fullPage: true,
199
+ });
200
+ throw new Error('SSO form fields not properly filled');
201
+ }
202
+
203
+ // Find and click SSO submit button
204
+ const ssoSubmitButton = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Sign in"), button:has-text("Continue")').first();
205
+ console.log('🔍 Clicking submit button...');
206
+ await ssoSubmitButton.click();
207
+ console.log('🚀 SSO login form submitted');
208
+
209
+ // Wait for redirect back to application
210
+ console.log('⏳ Waiting for redirect back to application...');
211
+
212
+ // Wait for navigation with a more flexible condition
213
+ try {
214
+ await page.waitForNavigation({ timeout: 30000, waitUntil: 'networkidle' });
215
+ console.log('✅ Navigation detected');
216
+ } catch (e) {
217
+ console.log('⚠️ Navigation timeout, checking current URL...');
218
+ }
219
+
220
+ // Verify we're no longer on login page
221
+ const currentUrl = page.url();
222
+ console.log(` Current URL: ${currentUrl}`);
223
+
224
+ if (currentUrl.includes('login') || currentUrl.includes('sign')) {
225
+ console.error('❌ Still on login page after submit');
226
+ await page.screenshot({
227
+ path: path.join(info.screenshotDir, '02-sso-submit-failed.png'),
228
+ fullPage: true,
229
+ });
230
+ throw new Error('SSO authentication failed - still on login page');
231
+ }
232
+
233
+ console.log('✅ SSO authentication successful');
234
+ } else {
235
+ console.log('⚠️ SSO form not found on provider page');
236
+ }
237
+ }
238
+ // Scenario 2: Direct Email/Password form (no SSO)
239
+ else if (await emailInput.isVisible({ timeout: 5000 }).catch(() => false)) {
240
+ console.log('📝 Direct email/password form found');
241
+
242
+ await emailInput.fill(ssoUser);
243
+ await passwordInput.fill(ssoPassword);
244
+
245
+ await page.screenshot({
246
+ path: path.join(info.screenshotDir, '02-login-form-filled.png'),
247
+ fullPage: true,
248
+ });
249
+
250
+ // Find and click submit button
251
+ const submitButton = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Sign in")').first();
252
+ await submitButton.click();
253
+ console.log('🚀 Login form submitted');
254
+
255
+ // Wait for navigation to complete
256
+ await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
257
+ await page.waitForURL((url) => !url.toString().includes('login'), { timeout: 10000 }).catch(() => {});
258
+ }
259
+ // Scenario 3: No login required (dev mode)
260
+ else {
261
+ console.log('⚠️ No login form found - might be in dev mode');
262
+ await page.goto('/');
263
+ }
264
+
265
+ // Take screenshot after login
266
+ await page.screenshot({
267
+ path: path.join(info.screenshotDir, '03-authenticated-home.png'),
268
+ fullPage: true,
269
+ });
270
+ console.log('✅ Authentication complete\n');
271
+
272
+ await use(page);
273
+ },
274
+ });
275
+
276
+ export { expect };
277
+
278
+ /**
279
+ * Helper function to take annotated screenshots
280
+ */
281
+ export async function takeScreenshot(page: Page, name: string, info: EnhancedTestFixtures['testInfo']) {
282
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
283
+ const filename = `${timestamp}-${name}.png`;
284
+ const filepath = path.join(info.screenshotDir, filename);
285
+
286
+ await page.screenshot({ path: filepath, fullPage: true });
287
+ console.log(`📸 Screenshot: ${name}`);
288
+ return filepath;
289
+ }
290
+
291
+ /**
292
+ * Helper function to save DOM snapshot
293
+ */
294
+ export async function saveDOMSnapshot(page: Page, name: string, info: EnhancedTestFixtures['testInfo']) {
295
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
296
+ const filename = `${timestamp}-${name}.html`;
297
+ const filepath = path.join(info.logDir, filename);
298
+
299
+ const html = await page.content();
300
+ fs.writeFileSync(filepath, html);
301
+ console.log(`💾 DOM Snapshot: ${name}`);
302
+ return filepath;
303
+ }
304
+
305
+ /**
306
+ * Helper to wait and log network idle
307
+ */
308
+ export async function waitForNetworkIdle(page: Page) {
309
+ console.log('⏳ Waiting for network idle...');
310
+ await page.waitForLoadState('networkidle');
311
+ console.log('✅ Network idle');
312
+ }
313
+
314
+ /**
315
+ * Helper to check for specific API responses
316
+ */
317
+ export async function waitForAPIResponse(page: Page, urlPattern: string | RegExp) {
318
+ console.log(`⏳ Waiting for API response: ${urlPattern}`);
319
+ const response = await page.waitForResponse(
320
+ (resp) => {
321
+ const url = resp.url();
322
+ if (typeof urlPattern === 'string') {
323
+ return url.includes(urlPattern);
324
+ }
325
+ return urlPattern.test(url);
326
+ },
327
+ { timeout: 30000 }
328
+ );
329
+ console.log(`✅ API response received: ${response.status()}`);
330
+ return response;
331
+ }