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,308 @@
1
+ import { test, expect, takeScreenshot, saveDOMSnapshot, waitForNetworkIdle, waitForAPIResponse } from './fixtures/enhanced-test';
2
+
3
+ /**
4
+ * Autonomous End-to-End Flow Test
5
+ * Complete test with SSO login, screenshots, DOM snapshots, console logs, and network monitoring
6
+ */
7
+ test.describe('Autonomous Complete Flow Test', () => {
8
+ test('should complete full Direct Campaigns workflow with full observability', async ({ authenticatedPage: page, testInfo }) => {
9
+ console.log('\n🚀 Starting Autonomous Complete Flow Test\n');
10
+ console.log('=' .repeat(80));
11
+
12
+ // Step 1: Navigate to Direct Campaigns
13
+ console.log('\n📍 Step 1: Navigate to Direct Campaigns page');
14
+ await page.goto('/direct-campaigns');
15
+ await waitForNetworkIdle(page);
16
+ await takeScreenshot(page, 'step-01-campaigns-page', testInfo);
17
+ await saveDOMSnapshot(page, 'step-01-campaigns-page', testInfo);
18
+
19
+ // Verify page loaded
20
+ await expect(page.getByRole('heading', { name: 'Direct Campaigns' })).toBeVisible();
21
+ console.log('✅ Direct Campaigns page loaded');
22
+
23
+ // Step 2: Wait for campaigns API response
24
+ console.log('\n📍 Step 2: Wait for campaigns data to load');
25
+ const campaignsResponse = await waitForAPIResponse(page, '/direct-campaigns');
26
+ const campaignsData = await campaignsResponse.json();
27
+ console.log('📊 Campaigns loaded:', campaignsData.result?.pagination || campaignsData.pagination);
28
+ await page.waitForSelector('table tbody tr, .grid.grid-cols-1', { timeout: 10000 });
29
+ await takeScreenshot(page, 'step-02-campaigns-loaded', testInfo);
30
+
31
+ // Step 3: Test Search Functionality
32
+ console.log('\n📍 Step 3: Test search functionality');
33
+ const searchInput = page.getByPlaceholder('Search here...');
34
+ await searchInput.fill('Campaign');
35
+ console.log('⌨️ Typed search query: "Campaign"');
36
+
37
+ await page.waitForTimeout(600); // Wait for debounce
38
+ await waitForNetworkIdle(page);
39
+ await takeScreenshot(page, 'step-03-search-results', testInfo);
40
+ console.log('✅ Search results displayed');
41
+
42
+ // Clear search
43
+ await searchInput.clear();
44
+ await page.waitForTimeout(600);
45
+ await waitForNetworkIdle(page);
46
+ console.log('🧹 Search cleared');
47
+
48
+ // Step 4: Test View Toggle - Switch to List View
49
+ console.log('\n📍 Step 4: Switch to List View');
50
+ const listViewButton = page.locator('button[title="List View"]');
51
+ await listViewButton.click();
52
+ await page.waitForTimeout(300);
53
+ await page.waitForSelector('table', { timeout: 5000 });
54
+ await takeScreenshot(page, 'step-04-list-view', testInfo);
55
+ await saveDOMSnapshot(page, 'step-04-list-view', testInfo);
56
+ console.log('✅ Switched to List View');
57
+
58
+ // Verify table columns
59
+ const columns = ['Campaign Name', 'Brand', 'Flight Dates', 'Client Type', 'Status', 'Actions'];
60
+ for (const column of columns) {
61
+ await expect(page.getByRole('columnheader', { name: column })).toBeVisible();
62
+ }
63
+ console.log('✅ All table columns visible');
64
+
65
+ // Step 5: Test Row Selection
66
+ console.log('\n📍 Step 5: Test row selection');
67
+ const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
68
+ await firstRowCheckbox.check();
69
+ await expect(firstRowCheckbox).toBeChecked();
70
+ await takeScreenshot(page, 'step-05-row-selected', testInfo);
71
+ console.log('✅ Row selected');
72
+
73
+ await firstRowCheckbox.uncheck();
74
+ console.log('✅ Row unselected');
75
+
76
+ // Step 6: Test Select All
77
+ console.log('\n📍 Step 6: Test select all functionality');
78
+ const selectAllCheckbox = page.locator('#table-select-all-checkbox');
79
+ await selectAllCheckbox.check();
80
+ await expect(selectAllCheckbox).toBeChecked();
81
+ await takeScreenshot(page, 'step-06-all-selected', testInfo);
82
+ console.log('✅ All rows selected');
83
+
84
+ await selectAllCheckbox.uncheck();
85
+ console.log('✅ All rows unselected');
86
+
87
+ // Step 7: Test Pagination
88
+ console.log('\n📍 Step 7: Test pagination controls');
89
+ const paginationSection = page.locator('.bg-white.dark\\:bg-mw-neutral-900').filter({ hasText: /Page \d+ of \d+/ });
90
+ await expect(paginationSection).toBeVisible();
91
+ console.log('✅ Pagination controls visible');
92
+
93
+ // Check page info
94
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
95
+ const pageText = await pageInfo.textContent();
96
+ console.log('📄 Current page info:', pageText);
97
+
98
+ // Test Next button if available
99
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
100
+ if (!(await nextButton.isDisabled())) {
101
+ console.log('⏭️ Clicking Next button');
102
+ await nextButton.click();
103
+ await waitForNetworkIdle(page);
104
+ await takeScreenshot(page, 'step-07-next-page', testInfo);
105
+
106
+ const newPageText = await pageInfo.textContent();
107
+ console.log('📄 New page info:', newPageText);
108
+ console.log('✅ Pagination navigation successful');
109
+
110
+ // Go back to first page
111
+ const firstButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-first') }).first();
112
+ await firstButton.click();
113
+ await waitForNetworkIdle(page);
114
+ console.log('⏮️ Returned to first page');
115
+ } else {
116
+ console.log('ℹ️ Only one page available, skipping pagination test');
117
+ }
118
+
119
+ // Step 8: Test Campaign Actions Menu
120
+ console.log('\n📍 Step 8: Test campaign actions menu');
121
+ const firstRow = page.locator('table tbody tr').first();
122
+ const actionsButton = firstRow.getByRole('button').last();
123
+ await actionsButton.click();
124
+ await page.waitForTimeout(300);
125
+ await takeScreenshot(page, 'step-08-actions-menu', testInfo);
126
+
127
+ // Verify menu items
128
+ await expect(page.getByText('View Details')).toBeVisible();
129
+ await expect(page.getByText('Edit Campaign')).toBeVisible();
130
+ await expect(page.getByText('Delete')).toBeVisible();
131
+ console.log('✅ Actions menu opened with all options');
132
+
133
+ // Close menu by clicking outside
134
+ await page.click('body');
135
+ await page.waitForTimeout(300);
136
+ console.log('✅ Actions menu closed');
137
+
138
+ // Step 9: Test Campaign Row Click Navigation
139
+ console.log('\n📍 Step 9: Test campaign row click navigation');
140
+ const campaignNameCell = firstRow.locator('td').nth(1);
141
+ const campaignName = await campaignNameCell.textContent();
142
+ console.log('🎯 Clicking on campaign:', campaignName);
143
+
144
+ await campaignNameCell.click();
145
+ await page.waitForURL(/\/direct-campaigns\/.+/, { timeout: 5000 });
146
+ await waitForNetworkIdle(page);
147
+ await takeScreenshot(page, 'step-09-campaign-details', testInfo);
148
+ await saveDOMSnapshot(page, 'step-09-campaign-details', testInfo);
149
+
150
+ const detailsUrl = page.url();
151
+ console.log('✅ Navigated to campaign details:', detailsUrl);
152
+
153
+ // Go back to campaigns list
154
+ await page.goBack();
155
+ await waitForNetworkIdle(page);
156
+ console.log('⬅️ Navigated back to campaigns list');
157
+
158
+ // Step 10: Test Grid View
159
+ console.log('\n📍 Step 10: Switch to Grid View');
160
+ const gridViewButton = page.locator('button[title="Grid View"]');
161
+ await gridViewButton.click();
162
+ await page.waitForTimeout(300);
163
+ await page.waitForSelector('.grid.grid-cols-1', { timeout: 5000 });
164
+ await takeScreenshot(page, 'step-10-grid-view', testInfo);
165
+ await saveDOMSnapshot(page, 'step-10-grid-view', testInfo);
166
+ console.log('✅ Switched to Grid View');
167
+
168
+ // Verify campaign cards are visible
169
+ const campaignCards = page.locator('[class*="rounded-lg"][class*="border"]');
170
+ const cardCount = await campaignCards.count();
171
+ console.log(`✅ ${cardCount} campaign cards visible in grid view`);
172
+
173
+ // Step 11: Test Filter Modal
174
+ console.log('\n📍 Step 11: Test filter modal');
175
+ const filterButton = page.getByRole('button', { name: /Filter/i });
176
+ await filterButton.click();
177
+ await page.waitForTimeout(500);
178
+ await takeScreenshot(page, 'step-11-filter-modal', testInfo);
179
+
180
+ // Check if filter modal opened (if available)
181
+ const filterModal = page.locator('[role="dialog"]');
182
+ if (await filterModal.isVisible().catch(() => false)) {
183
+ console.log('✅ Filter modal opened');
184
+ await saveDOMSnapshot(page, 'step-11-filter-modal', testInfo);
185
+
186
+ // Close modal
187
+ const closeButton = page.locator('button:has-text("Close"), button[aria-label="Close"]');
188
+ if (await closeButton.isVisible().catch(() => false)) {
189
+ await closeButton.click();
190
+ console.log('✅ Filter modal closed');
191
+ } else {
192
+ // Click outside to close
193
+ await page.keyboard.press('Escape');
194
+ console.log('✅ Filter modal closed with Escape');
195
+ }
196
+ } else {
197
+ console.log('ℹ️ Filter modal not implemented yet');
198
+ }
199
+
200
+ // Step 12: Test Create Campaign Button
201
+ console.log('\n📍 Step 12: Test Create Campaign button');
202
+ const createButton = page.getByRole('button', { name: /Create Campaign/i });
203
+ await expect(createButton).toBeVisible();
204
+ await takeScreenshot(page, 'step-12-create-button', testInfo);
205
+ console.log('✅ Create Campaign button visible');
206
+
207
+ // Optional: Click if you want to test the create flow
208
+ // await createButton.click();
209
+ // await waitForNetworkIdle(page);
210
+ // console.log('✅ Navigated to Create Campaign page');
211
+
212
+ // Final Step: Summary
213
+ console.log('\n📍 Final Step: Test Summary');
214
+ console.log('=' .repeat(80));
215
+ console.log('✅ Test completed successfully!');
216
+ console.log(`📸 Screenshots saved to: ${testInfo.screenshotDir}`);
217
+ console.log(`📝 Logs saved to: ${testInfo.logDir}`);
218
+ console.log(`🌐 Network requests: ${testInfo.networkLogs.length}`);
219
+ console.log(`🖥️ Console logs: ${testInfo.consoleLogs.length}`);
220
+ console.log(`❌ Errors: ${testInfo.errors.length}`);
221
+
222
+ // Take final screenshot
223
+ await takeScreenshot(page, 'step-13-final-state', testInfo);
224
+ await saveDOMSnapshot(page, 'step-13-final-state', testInfo);
225
+
226
+ console.log('\n🎉 All steps completed successfully!');
227
+ console.log('=' .repeat(80) + '\n');
228
+ });
229
+
230
+ test('should verify pagination maintains state correctly across navigation', async ({ authenticatedPage: page, testInfo }) => {
231
+ console.log('\n🚀 Testing Pagination State Persistence\n');
232
+
233
+ // Navigate to campaigns
234
+ await page.goto('/direct-campaigns');
235
+ await waitForNetworkIdle(page);
236
+ await takeScreenshot(page, 'pagination-test-01-initial', testInfo);
237
+
238
+ // Switch to list view
239
+ const listViewButton = page.locator('button[title="List View"]');
240
+ await listViewButton.click();
241
+ await page.waitForTimeout(300);
242
+
243
+ // Navigate to page 2 if available
244
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
245
+ if (!(await nextButton.isDisabled())) {
246
+ await nextButton.click();
247
+ await waitForNetworkIdle(page);
248
+ await takeScreenshot(page, 'pagination-test-02-page-2', testInfo);
249
+
250
+ // Verify we're on page 2
251
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
252
+ await expect(pageInfo).toContainText('Page 2');
253
+ console.log('✅ Navigated to page 2');
254
+
255
+ // Navigate to a campaign detail page
256
+ const firstCampaignCell = page.locator('table tbody tr').first().locator('td').nth(1);
257
+ await firstCampaignCell.click();
258
+ await page.waitForURL(/\/direct-campaigns\/.+/);
259
+ await waitForNetworkIdle(page);
260
+ console.log('✅ Opened campaign details');
261
+
262
+ // Go back
263
+ await page.goBack();
264
+ await waitForNetworkIdle(page);
265
+ await takeScreenshot(page, 'pagination-test-03-returned', testInfo);
266
+
267
+ // Verify still on page 2
268
+ await expect(pageInfo).toContainText('Page 2');
269
+ console.log('✅ Pagination state maintained after navigation');
270
+ }
271
+
272
+ console.log('✅ Pagination state persistence test completed');
273
+ });
274
+
275
+ test('should handle empty search results gracefully', async ({ authenticatedPage: page, testInfo }) => {
276
+ console.log('\n🚀 Testing Empty Search Results\n');
277
+
278
+ await page.goto('/direct-campaigns');
279
+ await waitForNetworkIdle(page);
280
+
281
+ // Search for non-existent campaign
282
+ const searchInput = page.getByPlaceholder('Search here...');
283
+ await searchInput.fill('NONEXISTENT_CAMPAIGN_XYZ_999');
284
+ await page.waitForTimeout(600);
285
+ await waitForNetworkIdle(page);
286
+ await takeScreenshot(page, 'empty-search-01-no-results', testInfo);
287
+
288
+ // Verify empty state message
289
+ const emptyMessage = page.getByText(/No campaigns found/i);
290
+ await expect(emptyMessage).toBeVisible();
291
+ console.log('✅ Empty state message displayed');
292
+
293
+ // Verify pagination is hidden
294
+ const paginationSection = page.locator('.bg-white.dark\\:bg-mw-neutral-900').filter({ hasText: /Page \d+ of \d+/ });
295
+ await expect(paginationSection).not.toBeVisible();
296
+ console.log('✅ Pagination hidden for empty results');
297
+
298
+ // Clear search
299
+ await searchInput.clear();
300
+ await page.waitForTimeout(600);
301
+ await waitForNetworkIdle(page);
302
+ await takeScreenshot(page, 'empty-search-02-results-restored', testInfo);
303
+
304
+ // Verify campaigns reappeared
305
+ await expect(page.locator('table tbody tr, .grid.grid-cols-1')).toBeVisible();
306
+ console.log('✅ Results restored after clearing search');
307
+ });
308
+ });
@@ -0,0 +1,163 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ /**
4
+ * Debug SSO Login with Network and Console Monitoring
5
+ */
6
+
7
+ test('Debug SSO with Network/Console Logs', async ({ page }) => {
8
+ // Capture console messages
9
+ const consoleLogs: string[] = [];
10
+ page.on('console', (msg) => {
11
+ const logMsg = `[${msg.type()}] ${msg.text()}`;
12
+ consoleLogs.push(logMsg);
13
+ console.log('🖥️ CONSOLE:', logMsg);
14
+ });
15
+
16
+ // Capture network requests
17
+ const networkLogs: any[] = [];
18
+ page.on('response', async (response) => {
19
+ const url = response.url();
20
+ const status = response.status();
21
+ const method = response.request().method();
22
+
23
+ const log = { method, status, url };
24
+ networkLogs.push(log);
25
+
26
+ console.log(`🌐 ${method} ${status}: ${url}`);
27
+
28
+ // Log response body for failed requests
29
+ if (status >= 400) {
30
+ try {
31
+ const body = await response.text();
32
+ console.log(` ❌ Response body:`, body.substring(0, 500));
33
+ } catch (e) {
34
+ console.log(` ❌ Could not read response body`);
35
+ }
36
+ }
37
+ });
38
+
39
+ // Capture page errors
40
+ page.on('pageerror', (error) => {
41
+ console.log('❌ PAGE ERROR:', error.message);
42
+ });
43
+
44
+ console.log('\n=== STEP 1: Navigate to Login ===');
45
+ await page.goto('/login', { waitUntil: 'networkidle' });
46
+ await page.screenshot({ path: 'debug-01-login.png', fullPage: true });
47
+
48
+ console.log('\n=== STEP 2: Click SSO Button ===');
49
+ const ssoButton = page.locator('button:has-text("Sign in with MW ID")');
50
+ await ssoButton.click();
51
+ await page.waitForLoadState('networkidle');
52
+ await page.screenshot({ path: 'debug-02-sso-page.png', fullPage: true });
53
+
54
+ console.log('\n=== STEP 3: Fill Credentials ===');
55
+ const emailInput = page.locator('input[type="email"]').first();
56
+ const passwordInput = page.locator('input[type="password"]').first();
57
+
58
+ const testEmail = process.env.SSO_TEST_USER || 'jeki@jeki.com';
59
+ const testPassword = process.env.SSO_TEST_PASSWORD || 'jeki@123';
60
+
61
+ console.log(` Using: ${testEmail} / ${testPassword}`);
62
+
63
+ await emailInput.click();
64
+ await emailInput.fill(testEmail);
65
+ await passwordInput.click();
66
+ await passwordInput.fill(testPassword);
67
+
68
+ await page.waitForTimeout(1000);
69
+ await page.screenshot({ path: 'debug-03-filled.png', fullPage: true });
70
+
71
+ const emailValue = await emailInput.inputValue();
72
+ const passwordValue = await passwordInput.inputValue();
73
+ console.log(` Email: "${emailValue}"`);
74
+ console.log(` Password: ${passwordValue.length} chars`);
75
+
76
+ console.log('\n=== STEP 4: Submit Form (watching network) ===');
77
+ const submitButton = page.locator('button[type="submit"]').first();
78
+
79
+ // Listen for any form submission errors
80
+ const requestPromise = page.waitForResponse(
81
+ (response) => response.url().includes('login') || response.url().includes('auth'),
82
+ { timeout: 10000 }
83
+ ).catch(() => null);
84
+
85
+ await submitButton.click();
86
+ console.log(' ✅ Submit button clicked');
87
+
88
+ // Wait for response
89
+ const response = await requestPromise;
90
+ if (response) {
91
+ console.log(` 📥 Response: ${response.status()} ${response.url()}`);
92
+ try {
93
+ const contentType = response.headers()['content-type'];
94
+ console.log(` Content-Type: ${contentType}`);
95
+
96
+ if (contentType?.includes('json')) {
97
+ const body = await response.json();
98
+ console.log(` Response body:`, JSON.stringify(body, null, 2));
99
+ } else {
100
+ const text = await response.text();
101
+ console.log(` Response text (first 500 chars):`, text.substring(0, 500));
102
+ }
103
+ } catch (e) {
104
+ console.log(` Could not read response`);
105
+ }
106
+ }
107
+
108
+ // Wait for navigation or error
109
+ await page.waitForTimeout(5000);
110
+ await page.screenshot({ path: 'debug-04-after-submit.png', fullPage: true });
111
+
112
+ console.log('\n=== STEP 5: Check Current State ===');
113
+ console.log(` Current URL: ${page.url()}`);
114
+
115
+ // Check for error messages
116
+ const errorSelectors = [
117
+ '.error',
118
+ '[role="alert"]',
119
+ '.text-red-500',
120
+ '.alert-error',
121
+ '[class*="error"]',
122
+ '[class*="Error"]'
123
+ ];
124
+
125
+ for (const selector of errorSelectors) {
126
+ const errors = await page.locator(selector).all();
127
+ if (errors.length > 0) {
128
+ console.log(`\n ❌ Found errors with selector "${selector}":`);
129
+ for (const error of errors) {
130
+ const text = await error.textContent();
131
+ const isVisible = await error.isVisible();
132
+ if (isVisible && text) {
133
+ console.log(` - ${text.trim()}`);
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ // Check if still on login page
140
+ const isAuthenticated = !page.url().includes('login');
141
+ console.log(`\n Authentication successful: ${isAuthenticated}`);
142
+
143
+ console.log('\n=== NETWORK LOG SUMMARY ===');
144
+ const authRequests = networkLogs.filter(log =>
145
+ log.url.includes('login') || log.url.includes('auth') || log.url.includes('oauth')
146
+ );
147
+ console.log(`Total auth-related requests: ${authRequests.length}`);
148
+ authRequests.forEach(log => {
149
+ console.log(` ${log.method} ${log.status}: ${log.url}`);
150
+ });
151
+
152
+ console.log('\n=== CONSOLE LOG SUMMARY ===');
153
+ const errorLogs = consoleLogs.filter(log => log.includes('error') || log.includes('Error'));
154
+ if (errorLogs.length > 0) {
155
+ console.log('Error logs found:');
156
+ errorLogs.forEach(log => console.log(` ${log}`));
157
+ } else {
158
+ console.log('No error logs found');
159
+ }
160
+
161
+ console.log('\n=== DEBUG COMPLETE ===');
162
+ console.log('Check screenshots: debug-*.png');
163
+ });
@@ -0,0 +1,219 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ /**
4
+ * Direct Campaigns Page E2E Tests
5
+ * Tests the complete functionality of the Direct Campaigns listing page
6
+ */
7
+ test.describe('Direct Campaigns Page', () => {
8
+ test.beforeEach(async ({ page }) => {
9
+ // Navigate to the Direct Campaigns page
10
+ await page.goto('/direct-campaigns');
11
+
12
+ // Wait for the page to load
13
+ await page.waitForLoadState('networkidle');
14
+ });
15
+
16
+ test.describe('Page Layout and Elements', () => {
17
+ test('should display page header with title and description', async ({ page }) => {
18
+ await expect(page.getByRole('heading', { name: 'Direct Campaigns' })).toBeVisible();
19
+ await expect(page.getByText('Manage your direct IO campaigns')).toBeVisible();
20
+ });
21
+
22
+ test('should display Create Campaign button', async ({ page }) => {
23
+ const createButton = page.getByRole('button', { name: /Create Campaign/i });
24
+ await expect(createButton).toBeVisible();
25
+ await expect(createButton).toBeEnabled();
26
+ });
27
+
28
+ test('should display search input', async ({ page }) => {
29
+ const searchInput = page.getByPlaceholder('Search here...');
30
+ await expect(searchInput).toBeVisible();
31
+ });
32
+
33
+ test('should display view toggle buttons (list/grid)', async ({ page }) => {
34
+ // Check for list view button
35
+ const listViewButton = page.locator('button[title="List View"]');
36
+ await expect(listViewButton).toBeVisible();
37
+
38
+ // Check for grid view button
39
+ const gridViewButton = page.locator('button[title="Grid View"]');
40
+ await expect(gridViewButton).toBeVisible();
41
+ });
42
+
43
+ test('should display filter button', async ({ page }) => {
44
+ const filterButton = page.getByRole('button', { name: /Filter/i });
45
+ await expect(filterButton).toBeVisible();
46
+ });
47
+ });
48
+
49
+ test.describe('Campaign List Display', () => {
50
+ test('should display campaigns in the table', async ({ page }) => {
51
+ // Wait for campaigns to load
52
+ await page.waitForSelector('table tbody tr', { timeout: 10000 });
53
+
54
+ // Check if at least one campaign row is visible
55
+ const rows = page.locator('table tbody tr');
56
+ await expect(rows.first()).toBeVisible();
57
+ });
58
+
59
+ test('should display correct table columns', async ({ page }) => {
60
+ // Check for table headers
61
+ await expect(page.getByRole('columnheader', { name: 'Campaign Name' })).toBeVisible();
62
+ await expect(page.getByRole('columnheader', { name: 'Brand' })).toBeVisible();
63
+ await expect(page.getByRole('columnheader', { name: 'Flight Dates' })).toBeVisible();
64
+ await expect(page.getByRole('columnheader', { name: 'Client Type' })).toBeVisible();
65
+ await expect(page.getByRole('columnheader', { name: 'Status' })).toBeVisible();
66
+ await expect(page.getByRole('columnheader', { name: 'Actions' })).toBeVisible();
67
+ });
68
+
69
+ test('should display campaign data in rows', async ({ page }) => {
70
+ // Wait for first campaign row
71
+ const firstRow = page.locator('table tbody tr').first();
72
+ await expect(firstRow).toBeVisible();
73
+
74
+ // Verify campaign has required data displayed
75
+ await expect(firstRow.locator('td').nth(1)).not.toBeEmpty();
76
+ });
77
+ });
78
+
79
+ test.describe('Search Functionality', () => {
80
+ test('should filter campaigns based on search input', async ({ page }) => {
81
+ const searchInput = page.getByPlaceholder('Search here...');
82
+
83
+ // Wait for initial campaigns to load
84
+ await page.waitForSelector('table tbody tr');
85
+
86
+ // Get initial count
87
+ const initialCount = await page.locator('table tbody tr').count();
88
+
89
+ // Type search query (minimum 3 characters required based on code)
90
+ await searchInput.fill('Campaign');
91
+
92
+ // Wait for debounce (500ms based on code)
93
+ await page.waitForTimeout(600);
94
+
95
+ // Wait for network request to complete
96
+ await page.waitForLoadState('networkidle');
97
+
98
+ // Verify search was applied (results may change)
99
+ const searchResults = page.locator('table tbody tr');
100
+ await expect(searchResults.first()).toBeVisible();
101
+ });
102
+
103
+ test('should not search with less than 3 characters', async ({ page }) => {
104
+ const searchInput = page.getByPlaceholder('Search here...');
105
+
106
+ // Type less than 3 characters
107
+ await searchInput.fill('Ca');
108
+
109
+ // Wait for debounce
110
+ await page.waitForTimeout(600);
111
+
112
+ // Should still show all campaigns (no filtering)
113
+ const rows = page.locator('table tbody tr');
114
+ await expect(rows.first()).toBeVisible();
115
+ });
116
+
117
+ test('should clear search results when input is cleared', async ({ page }) => {
118
+ const searchInput = page.getByPlaceholder('Search here...');
119
+
120
+ // Search for something
121
+ await searchInput.fill('Campaign');
122
+ await page.waitForTimeout(600);
123
+ await page.waitForLoadState('networkidle');
124
+
125
+ // Clear search
126
+ await searchInput.clear();
127
+ await page.waitForTimeout(600);
128
+ await page.waitForLoadState('networkidle');
129
+
130
+ // Should show all campaigns again
131
+ const rows = page.locator('table tbody tr');
132
+ await expect(rows.first()).toBeVisible();
133
+ });
134
+ });
135
+
136
+ test.describe('Row Selection', () => {
137
+ test('should allow selecting individual campaigns', async ({ page }) => {
138
+ // Wait for campaigns to load
139
+ await page.waitForSelector('table tbody tr');
140
+
141
+ // Find first checkbox in tbody (skip header checkbox)
142
+ const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
143
+ await expect(firstRowCheckbox).toBeVisible();
144
+
145
+ // Select the row
146
+ await firstRowCheckbox.check();
147
+ await expect(firstRowCheckbox).toBeChecked();
148
+
149
+ // Unselect the row
150
+ await firstRowCheckbox.uncheck();
151
+ await expect(firstRowCheckbox).not.toBeChecked();
152
+ });
153
+
154
+ test('should allow selecting all campaigns', async ({ page }) => {
155
+ // Wait for campaigns to load
156
+ await page.waitForSelector('table tbody tr');
157
+
158
+ // Find select all checkbox in header
159
+ const selectAllCheckbox = page.locator('#table-select-all-checkbox');
160
+ await expect(selectAllCheckbox).toBeVisible();
161
+
162
+ // Select all
163
+ await selectAllCheckbox.check();
164
+ await expect(selectAllCheckbox).toBeChecked();
165
+
166
+ // Verify first row is selected
167
+ const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
168
+ await expect(firstRowCheckbox).toBeChecked();
169
+
170
+ // Unselect all
171
+ await selectAllCheckbox.uncheck();
172
+ await expect(firstRowCheckbox).not.toBeChecked();
173
+ });
174
+ });
175
+
176
+ test.describe('Campaign Actions', () => {
177
+ test('should open actions menu for a campaign', async ({ page }) => {
178
+ // Wait for campaigns to load
179
+ await page.waitForSelector('table tbody tr');
180
+
181
+ // Find first actions button (three dots)
182
+ const actionsButton = page.locator('table tbody tr').first().getByRole('button').last();
183
+ await actionsButton.click();
184
+
185
+ // Wait for dropdown menu to appear
186
+ await expect(page.getByText('View Details')).toBeVisible();
187
+ await expect(page.getByText('Edit Campaign')).toBeVisible();
188
+ await expect(page.getByText('Delete')).toBeVisible();
189
+ });
190
+
191
+ test('should navigate to campaign details when clicking View Details', async ({ page }) => {
192
+ // Wait for campaigns to load
193
+ await page.waitForSelector('table tbody tr');
194
+
195
+ // Open actions menu
196
+ const actionsButton = page.locator('table tbody tr').first().getByRole('button').last();
197
+ await actionsButton.click();
198
+
199
+ // Click View Details
200
+ await page.getByText('View Details').click();
201
+
202
+ // Should navigate to campaign details page
203
+ await expect(page).toHaveURL(/\/direct-campaigns\/.+/);
204
+ });
205
+
206
+ test('should navigate to campaign details when clicking on row', async ({ page }) => {
207
+ // Wait for campaigns to load
208
+ await page.waitForSelector('table tbody tr');
209
+
210
+ // Click on first row (not on checkbox or action button)
211
+ const firstRow = page.locator('table tbody tr').first();
212
+ const campaignNameCell = firstRow.locator('td').nth(1);
213
+ await campaignNameCell.click();
214
+
215
+ // Should navigate to campaign details page
216
+ await expect(page).toHaveURL(/\/direct-campaigns\/.+/);
217
+ });
218
+ });
219
+ });