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,280 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ /**
4
+ * Pagination E2E Tests
5
+ * Tests the pagination functionality matching MW Planner design
6
+ */
7
+ test.describe('Pagination Functionality', () => {
8
+ test.beforeEach(async ({ page }) => {
9
+ // Navigate to the Direct Campaigns page
10
+ await page.goto('/direct-campaigns');
11
+ await page.waitForLoadState('networkidle');
12
+ });
13
+
14
+ test.describe('Pagination Visibility', () => {
15
+ test('should display pagination controls when campaigns are loaded', async ({ page }) => {
16
+ // Wait for campaigns to load
17
+ await page.waitForSelector('table tbody tr');
18
+
19
+ // Check for pagination section
20
+ const paginationSection = page.locator('.bg-white.dark\\:bg-mw-neutral-900').filter({ hasText: /Page \d+ of \d+/ });
21
+ await expect(paginationSection).toBeVisible();
22
+ });
23
+
24
+ test('should display item count (X to Y of Z)', async ({ page }) => {
25
+ // Wait for campaigns to load
26
+ await page.waitForSelector('table tbody tr');
27
+
28
+ // Check for item count text (e.g., "1 to 12 of 28")
29
+ const itemCountText = page.getByText(/\d+ to \d+ of \d+/);
30
+ await expect(itemCountText).toBeVisible();
31
+ });
32
+
33
+ test('should display all pagination buttons', async ({ page }) => {
34
+ // Wait for campaigns to load
35
+ await page.waitForSelector('table tbody tr');
36
+
37
+ // Check for First button (ChevronFirst icon)
38
+ const firstButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-first') }).first();
39
+ await expect(firstButton).toBeVisible();
40
+
41
+ // Check for Previous button (ChevronLeft icon)
42
+ const prevButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-left') }).first();
43
+ await expect(prevButton).toBeVisible();
44
+
45
+ // Check for page info
46
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
47
+ await expect(pageInfo).toBeVisible();
48
+
49
+ // Check for Next button (ChevronRight icon)
50
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
51
+ await expect(nextButton).toBeVisible();
52
+
53
+ // Check for Last button (ChevronLast icon)
54
+ const lastButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-last') }).first();
55
+ await expect(lastButton).toBeVisible();
56
+ });
57
+
58
+ test('should not display pagination when no campaigns', async ({ page }) => {
59
+ // Navigate with search that returns no results
60
+ const searchInput = page.getByPlaceholder('Search here...');
61
+ await searchInput.fill('XYZ_NONEXISTENT_CAMPAIGN_999');
62
+ await page.waitForTimeout(600);
63
+ await page.waitForLoadState('networkidle');
64
+
65
+ // Pagination should not be visible
66
+ const paginationSection = page.locator('.bg-white.dark\\:bg-mw-neutral-900').filter({ hasText: /Page \d+ of \d+/ });
67
+ await expect(paginationSection).not.toBeVisible();
68
+ });
69
+ });
70
+
71
+ test.describe('Pagination Navigation', () => {
72
+ test('should disable First and Previous buttons on first page', async ({ page }) => {
73
+ // Wait for campaigns to load
74
+ await page.waitForSelector('table tbody tr');
75
+
76
+ // First and Previous buttons should be disabled on page 1
77
+ const firstButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-first') }).first();
78
+ const prevButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-left') }).first();
79
+
80
+ await expect(firstButton).toBeDisabled();
81
+ await expect(prevButton).toBeDisabled();
82
+ });
83
+
84
+ test('should navigate to next page when clicking Next button', async ({ page }) => {
85
+ // Wait for campaigns to load
86
+ await page.waitForSelector('table tbody tr');
87
+
88
+ // Get current page number
89
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
90
+ const currentPageText = await pageInfo.textContent();
91
+ const currentPage = parseInt(currentPageText?.match(/Page (\d+)/)?.[1] || '1');
92
+
93
+ // Click Next button
94
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
95
+
96
+ // Only test if not on last page
97
+ if (!(await nextButton.isDisabled())) {
98
+ await nextButton.click();
99
+
100
+ // Wait for new data to load
101
+ await page.waitForLoadState('networkidle');
102
+
103
+ // Verify page number increased
104
+ const newPageText = await pageInfo.textContent();
105
+ const newPage = parseInt(newPageText?.match(/Page (\d+)/)?.[1] || '1');
106
+ expect(newPage).toBe(currentPage + 1);
107
+ }
108
+ });
109
+
110
+ test('should navigate to previous page when clicking Previous button', async ({ page }) => {
111
+ // Wait for campaigns to load
112
+ await page.waitForSelector('table tbody tr');
113
+
114
+ // Click Next to go to page 2
115
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
116
+
117
+ if (!(await nextButton.isDisabled())) {
118
+ await nextButton.click();
119
+ await page.waitForLoadState('networkidle');
120
+
121
+ // Now click Previous to go back
122
+ const prevButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-left') }).first();
123
+ await prevButton.click();
124
+ await page.waitForLoadState('networkidle');
125
+
126
+ // Should be back on page 1
127
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
128
+ await expect(pageInfo).toContainText('Page 1 of');
129
+ }
130
+ });
131
+
132
+ test('should navigate to first page when clicking First button', async ({ page }) => {
133
+ // Wait for campaigns to load
134
+ await page.waitForSelector('table tbody tr');
135
+
136
+ // Navigate to a later page
137
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
138
+
139
+ if (!(await nextButton.isDisabled())) {
140
+ // Click next a few times
141
+ await nextButton.click();
142
+ await page.waitForLoadState('networkidle');
143
+ await nextButton.click();
144
+ await page.waitForLoadState('networkidle');
145
+
146
+ // Click First button
147
+ const firstButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-first') }).first();
148
+ await firstButton.click();
149
+ await page.waitForLoadState('networkidle');
150
+
151
+ // Should be on page 1
152
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
153
+ await expect(pageInfo).toContainText('Page 1 of');
154
+ }
155
+ });
156
+
157
+ test('should navigate to last page when clicking Last button', async ({ page }) => {
158
+ // Wait for campaigns to load
159
+ await page.waitForSelector('table tbody tr');
160
+
161
+ // Get total pages
162
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
163
+ const pageText = await pageInfo.textContent();
164
+ const totalPages = parseInt(pageText?.match(/of (\d+)/)?.[1] || '1');
165
+
166
+ // Only test if there are multiple pages
167
+ if (totalPages > 1) {
168
+ // Click Last button
169
+ const lastButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-last') }).first();
170
+ await lastButton.click();
171
+ await page.waitForLoadState('networkidle');
172
+
173
+ // Should be on last page
174
+ await expect(pageInfo).toContainText(`Page ${totalPages} of ${totalPages}`);
175
+
176
+ // Last and Next buttons should be disabled
177
+ await expect(lastButton).toBeDisabled();
178
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
179
+ await expect(nextButton).toBeDisabled();
180
+ }
181
+ });
182
+ });
183
+
184
+ test.describe('Pagination Data Accuracy', () => {
185
+ test('should display correct item range on first page', async ({ page }) => {
186
+ // Wait for campaigns to load
187
+ await page.waitForSelector('table tbody tr');
188
+
189
+ // Get item count text
190
+ const itemCountText = page.getByText(/\d+ to \d+ of \d+/);
191
+ const text = await itemCountText.textContent();
192
+
193
+ // Should start from 1
194
+ expect(text).toMatch(/^1 to/);
195
+ });
196
+
197
+ test('should update item range when navigating pages', async ({ page }) => {
198
+ // Wait for campaigns to load
199
+ await page.waitForSelector('table tbody tr');
200
+
201
+ // Get item count on first page
202
+ const itemCountText = page.getByText(/\d+ to \d+ of \d+/);
203
+ const firstPageText = await itemCountText.textContent();
204
+ const endItem = parseInt(firstPageText?.match(/to (\d+)/)?.[1] || '0');
205
+
206
+ // Click next page
207
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
208
+
209
+ if (!(await nextButton.isDisabled())) {
210
+ await nextButton.click();
211
+ await page.waitForLoadState('networkidle');
212
+
213
+ // Get new item count
214
+ const secondPageText = await itemCountText.textContent();
215
+ const newStartItem = parseInt(secondPageText?.match(/^(\d+) to/)?.[1] || '0');
216
+
217
+ // New start should be previous end + 1
218
+ expect(newStartItem).toBe(endItem + 1);
219
+ }
220
+ });
221
+
222
+ test('should maintain correct total count across pages', async ({ page }) => {
223
+ // Wait for campaigns to load
224
+ await page.waitForSelector('table tbody tr');
225
+
226
+ // Get total from first page
227
+ const itemCountText = page.getByText(/\d+ to \d+ of \d+/);
228
+ const firstPageText = await itemCountText.textContent();
229
+ const totalFirst = parseInt(firstPageText?.match(/of (\d+)/)?.[1] || '0');
230
+
231
+ // Navigate to next page
232
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
233
+
234
+ if (!(await nextButton.isDisabled())) {
235
+ await nextButton.click();
236
+ await page.waitForLoadState('networkidle');
237
+
238
+ // Get total from second page
239
+ const secondPageText = await itemCountText.textContent();
240
+ const totalSecond = parseInt(secondPageText?.match(/of (\d+)/)?.[1] || '0');
241
+
242
+ // Total should remain the same
243
+ expect(totalSecond).toBe(totalFirst);
244
+ }
245
+ });
246
+ });
247
+
248
+ test.describe('Mobile Pagination', () => {
249
+ test.use({ viewport: { width: 375, height: 667 } });
250
+
251
+ test('should display mobile pagination controls', async ({ page }) => {
252
+ // Navigate to the Direct Campaigns page
253
+ await page.goto('/direct-campaigns');
254
+ await page.waitForLoadState('networkidle');
255
+
256
+ // Wait for campaigns to load
257
+ await page.waitForSelector('table tbody tr');
258
+
259
+ // Mobile should show Previous and Next buttons
260
+ const previousButton = page.getByRole('button', { name: 'Previous' });
261
+ const nextButton = page.getByRole('button', { name: 'Next' });
262
+
263
+ await expect(previousButton).toBeVisible();
264
+ await expect(nextButton).toBeVisible();
265
+ });
266
+
267
+ test('should not display desktop pagination on mobile', async ({ page }) => {
268
+ // Navigate to the Direct Campaigns page
269
+ await page.goto('/direct-campaigns');
270
+ await page.waitForLoadState('networkidle');
271
+
272
+ // Wait for campaigns to load
273
+ await page.waitForSelector('table tbody tr');
274
+
275
+ // Desktop pagination with icons should not be visible
276
+ const firstButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-first') });
277
+ await expect(firstButton).not.toBeVisible();
278
+ });
279
+ });
280
+ });
@@ -0,0 +1,312 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ /**
4
+ * View Toggle E2E Tests
5
+ * Tests switching between List and Grid views
6
+ */
7
+ test.describe('View Toggle Functionality', () => {
8
+ test.beforeEach(async ({ page }) => {
9
+ // Navigate to the Direct Campaigns page
10
+ await page.goto('/direct-campaigns');
11
+ await page.waitForLoadState('networkidle');
12
+ });
13
+
14
+ test.describe('View Toggle Buttons', () => {
15
+ test('should display both view toggle buttons', async ({ page }) => {
16
+ const listViewButton = page.locator('button[title="List View"]');
17
+ const gridViewButton = page.locator('button[title="Grid View"]');
18
+
19
+ await expect(listViewButton).toBeVisible();
20
+ await expect(gridViewButton).toBeVisible();
21
+ });
22
+
23
+ test('should have grid view selected by default', async ({ page }) => {
24
+ // Grid view should be active by default (based on code)
25
+ const gridViewButton = page.locator('button[title="Grid View"]');
26
+
27
+ // Check if button has active classes
28
+ await expect(gridViewButton).toHaveClass(/bg-mw-neutral-200/);
29
+ });
30
+
31
+ test('should highlight active view button', async ({ page }) => {
32
+ const listViewButton = page.locator('button[title="List View"]');
33
+ const gridViewButton = page.locator('button[title="Grid View"]');
34
+
35
+ // Grid should be active initially
36
+ await expect(gridViewButton).toHaveClass(/bg-mw-neutral-200/);
37
+
38
+ // Click list view
39
+ await listViewButton.click();
40
+ await page.waitForTimeout(300);
41
+
42
+ // List should now be active
43
+ await expect(listViewButton).toHaveClass(/bg-mw-neutral-200/);
44
+ await expect(gridViewButton).not.toHaveClass(/bg-mw-neutral-200/);
45
+ });
46
+ });
47
+
48
+ test.describe('Grid View', () => {
49
+ test('should display campaigns in grid layout', async ({ page }) => {
50
+ // Ensure we're in grid view
51
+ const gridViewButton = page.locator('button[title="Grid View"]');
52
+ await gridViewButton.click();
53
+ await page.waitForTimeout(300);
54
+
55
+ // Wait for campaigns to load
56
+ await page.waitForSelector('[class*="grid"]', { timeout: 10000 });
57
+
58
+ // Check for grid container
59
+ const gridContainer = page.locator('.grid.grid-cols-1');
60
+ await expect(gridContainer).toBeVisible();
61
+
62
+ // Check for campaign cards
63
+ const campaignCards = page.locator('[class*="rounded-lg"][class*="border"]');
64
+ await expect(campaignCards.first()).toBeVisible();
65
+ });
66
+
67
+ test('should display campaign cards with correct information', async ({ page }) => {
68
+ // Ensure we're in grid view
69
+ const gridViewButton = page.locator('button[title="Grid View"]');
70
+ await gridViewButton.click();
71
+ await page.waitForTimeout(300);
72
+
73
+ // Wait for campaign cards
74
+ await page.waitForSelector('.grid.grid-cols-1');
75
+
76
+ // Get first campaign card
77
+ const firstCard = page.locator('[class*="rounded-lg"][class*="border"]').first();
78
+ await expect(firstCard).toBeVisible();
79
+
80
+ // Card should contain campaign name, brand, dates, status
81
+ // (Based on CampaignCard component structure)
82
+ await expect(firstCard).toContainText(/.+/); // Should have some text content
83
+ });
84
+
85
+ test('should display grid in responsive columns', async ({ page }) => {
86
+ // Desktop: should have lg:grid-cols-3
87
+ const gridContainer = page.locator('.grid.grid-cols-1');
88
+ await expect(gridContainer).toHaveClass(/lg:grid-cols-3/);
89
+ });
90
+ });
91
+
92
+ test.describe('List View', () => {
93
+ test('should display campaigns in table layout', async ({ page }) => {
94
+ // Click list view
95
+ const listViewButton = page.locator('button[title="List View"]');
96
+ await listViewButton.click();
97
+ await page.waitForTimeout(300);
98
+
99
+ // Wait for table to be visible
100
+ await page.waitForSelector('table', { timeout: 10000 });
101
+
102
+ // Check for table
103
+ const table = page.locator('table');
104
+ await expect(table).toBeVisible();
105
+
106
+ // Check for table rows
107
+ const rows = page.locator('table tbody tr');
108
+ await expect(rows.first()).toBeVisible();
109
+ });
110
+
111
+ test('should display all table columns in list view', async ({ page }) => {
112
+ // Click list view
113
+ const listViewButton = page.locator('button[title="List View"]');
114
+ await listViewButton.click();
115
+ await page.waitForTimeout(300);
116
+
117
+ // Wait for table
118
+ await page.waitForSelector('table');
119
+
120
+ // Check for all column headers
121
+ await expect(page.getByRole('columnheader', { name: 'Campaign Name' })).toBeVisible();
122
+ await expect(page.getByRole('columnheader', { name: 'Brand' })).toBeVisible();
123
+ await expect(page.getByRole('columnheader', { name: 'Flight Dates' })).toBeVisible();
124
+ await expect(page.getByRole('columnheader', { name: 'Client Type' })).toBeVisible();
125
+ await expect(page.getByRole('columnheader', { name: 'Status' })).toBeVisible();
126
+ await expect(page.getByRole('columnheader', { name: 'Actions' })).toBeVisible();
127
+ });
128
+
129
+ test('should show checkbox column in list view', async ({ page }) => {
130
+ // Click list view
131
+ const listViewButton = page.locator('button[title="List View"]');
132
+ await listViewButton.click();
133
+ await page.waitForTimeout(300);
134
+
135
+ // Wait for table
136
+ await page.waitForSelector('table tbody tr');
137
+
138
+ // Check for checkbox in header
139
+ const headerCheckbox = page.locator('#table-select-all-checkbox');
140
+ await expect(headerCheckbox).toBeVisible();
141
+
142
+ // Check for checkbox in first row
143
+ const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
144
+ await expect(firstRowCheckbox).toBeVisible();
145
+ });
146
+ });
147
+
148
+ test.describe('View Switch Behavior', () => {
149
+ test('should switch from grid to list view', async ({ page }) => {
150
+ // Start in grid view
151
+ const gridViewButton = page.locator('button[title="Grid View"]');
152
+ await gridViewButton.click();
153
+ await page.waitForTimeout(300);
154
+
155
+ // Verify grid is visible
156
+ await expect(page.locator('.grid.grid-cols-1')).toBeVisible();
157
+
158
+ // Switch to list view
159
+ const listViewButton = page.locator('button[title="List View"]');
160
+ await listViewButton.click();
161
+ await page.waitForTimeout(300);
162
+
163
+ // Grid should be hidden, table should be visible
164
+ await expect(page.locator('table')).toBeVisible();
165
+ await expect(page.locator('.grid.grid-cols-1')).not.toBeVisible();
166
+ });
167
+
168
+ test('should switch from list to grid view', async ({ page }) => {
169
+ // Start in list view
170
+ const listViewButton = page.locator('button[title="List View"]');
171
+ await listViewButton.click();
172
+ await page.waitForTimeout(300);
173
+
174
+ // Verify table is visible
175
+ await expect(page.locator('table')).toBeVisible();
176
+
177
+ // Switch to grid view
178
+ const gridViewButton = page.locator('button[title="Grid View"]');
179
+ await gridViewButton.click();
180
+ await page.waitForTimeout(300);
181
+
182
+ // Table should be hidden, grid should be visible
183
+ await expect(page.locator('.grid.grid-cols-1')).toBeVisible();
184
+ await expect(page.locator('table')).not.toBeVisible();
185
+ });
186
+
187
+ test('should persist view selection in localStorage', async ({ page }) => {
188
+ // Switch to list view
189
+ const listViewButton = page.locator('button[title="List View"]');
190
+ await listViewButton.click();
191
+ await page.waitForTimeout(300);
192
+
193
+ // Reload the page
194
+ await page.reload();
195
+ await page.waitForLoadState('networkidle');
196
+
197
+ // Should still be in list view
198
+ await expect(page.locator('table')).toBeVisible();
199
+ await expect(listViewButton).toHaveClass(/bg-mw-neutral-200/);
200
+ });
201
+
202
+ test('should clear selected rows when switching views', async ({ page }) => {
203
+ // Start in list view and select a row
204
+ const listViewButton = page.locator('button[title="List View"]');
205
+ await listViewButton.click();
206
+ await page.waitForTimeout(300);
207
+
208
+ // Wait for table
209
+ await page.waitForSelector('table tbody tr');
210
+
211
+ // Select first row
212
+ const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
213
+ await firstRowCheckbox.check();
214
+ await expect(firstRowCheckbox).toBeChecked();
215
+
216
+ // Switch to grid view
217
+ const gridViewButton = page.locator('button[title="Grid View"]');
218
+ await gridViewButton.click();
219
+ await page.waitForTimeout(300);
220
+
221
+ // Switch back to list view
222
+ await listViewButton.click();
223
+ await page.waitForTimeout(300);
224
+
225
+ // Selection should be cleared
226
+ await expect(firstRowCheckbox).not.toBeChecked();
227
+ });
228
+ });
229
+
230
+ test.describe('View Persistence', () => {
231
+ test('should remember last selected view after page reload', async ({ page }) => {
232
+ // Select list view
233
+ const listViewButton = page.locator('button[title="List View"]');
234
+ await listViewButton.click();
235
+ await page.waitForTimeout(300);
236
+
237
+ // Get current URL
238
+ const url = page.url();
239
+
240
+ // Reload page
241
+ await page.goto(url);
242
+ await page.waitForLoadState('networkidle');
243
+
244
+ // Should still show list view
245
+ await expect(page.locator('table')).toBeVisible();
246
+ await expect(listViewButton).toHaveClass(/bg-mw-neutral-200/);
247
+ });
248
+
249
+ test('should remember last selected view after navigation', async ({ page }) => {
250
+ // Select list view
251
+ const listViewButton = page.locator('button[title="List View"]');
252
+ await listViewButton.click();
253
+ await page.waitForTimeout(300);
254
+
255
+ // Navigate away
256
+ await page.goto('/');
257
+ await page.waitForLoadState('networkidle');
258
+
259
+ // Navigate back
260
+ await page.goto('/direct-campaigns');
261
+ await page.waitForLoadState('networkidle');
262
+
263
+ // Should still show list view
264
+ await expect(page.locator('table')).toBeVisible();
265
+ await expect(listViewButton).toHaveClass(/bg-mw-neutral-200/);
266
+ });
267
+ });
268
+
269
+ test.describe('Pagination with View Toggle', () => {
270
+ test('should show pagination in both grid and list views', async ({ page }) => {
271
+ // Check pagination in grid view
272
+ const gridViewButton = page.locator('button[title="Grid View"]');
273
+ await gridViewButton.click();
274
+ await page.waitForTimeout(300);
275
+
276
+ await page.waitForSelector('.grid.grid-cols-1');
277
+ const paginationInGrid = page.getByText(/Page \d+ of \d+/);
278
+ await expect(paginationInGrid).toBeVisible();
279
+
280
+ // Switch to list view
281
+ const listViewButton = page.locator('button[title="List View"]');
282
+ await listViewButton.click();
283
+ await page.waitForTimeout(300);
284
+
285
+ await page.waitForSelector('table');
286
+ const paginationInList = page.getByText(/Page \d+ of \d+/);
287
+ await expect(paginationInList).toBeVisible();
288
+ });
289
+
290
+ test('should maintain page number when switching views', async ({ page }) => {
291
+ // Navigate to page 2 in grid view
292
+ const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
293
+
294
+ if (!(await nextButton.isDisabled())) {
295
+ await nextButton.click();
296
+ await page.waitForLoadState('networkidle');
297
+
298
+ // Verify on page 2
299
+ const pageInfo = page.getByText(/Page \d+ of \d+/);
300
+ await expect(pageInfo).toContainText('Page 2');
301
+
302
+ // Switch to list view
303
+ const listViewButton = page.locator('button[title="List View"]');
304
+ await listViewButton.click();
305
+ await page.waitForTimeout(300);
306
+
307
+ // Should still be on page 2
308
+ await expect(pageInfo).toContainText('Page 2');
309
+ }
310
+ });
311
+ });
312
+ });
Binary file
@@ -0,0 +1,46 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ input: [
5
+ 'client/src/**/*.{js,jsx,ts,tsx}',
6
+ '!client/src/**/*.test.{js,jsx,ts,tsx}',
7
+ '!client/src/**/*.spec.{js,jsx,ts,tsx}',
8
+ '!**/node_modules/**',
9
+ ],
10
+ output: './client/public/locales',
11
+ options: {
12
+ debug: true,
13
+ removeUnusedKeys: false,
14
+ sort: true,
15
+ func: {
16
+ list: ['t', 'i18next.t', 'i18n.t'],
17
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
18
+ },
19
+ trans: {
20
+ component: 'Trans',
21
+ i18nKey: 'i18nKey',
22
+ defaultsKey: 'defaults',
23
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
24
+ fallbackKey: function(ns, value) {
25
+ return value;
26
+ },
27
+ },
28
+ lngs: ['en', 'zh', 'ja', 'ar'],
29
+ ns: ['common', 'deals', 'creatives', 'navigation', 'contentHub', 'wizard', 'campaigns'],
30
+ defaultLng: 'en',
31
+ defaultNs: 'common',
32
+ defaultValue: '__NOT_TRANSLATED__',
33
+ resource: {
34
+ loadPath: 'client/public/locales/{{lng}}/{{ns}}.json',
35
+ savePath: '{{lng}}/{{ns}}.json',
36
+ jsonIndent: 2,
37
+ lineEnding: '\n',
38
+ },
39
+ nsSeparator: ':',
40
+ keySeparator: '.',
41
+ interpolation: {
42
+ prefix: '{{',
43
+ suffix: '}}',
44
+ },
45
+ },
46
+ };