@ucptools/validator 1.0.0 → 1.1.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 (330) hide show
  1. package/dist/auth/config.d.ts +20 -0
  2. package/dist/auth/config.d.ts.map +1 -0
  3. package/dist/auth/config.js +114 -0
  4. package/dist/auth/config.js.map +1 -0
  5. package/dist/auth/index.d.ts +5 -0
  6. package/dist/auth/index.d.ts.map +1 -0
  7. package/dist/auth/index.js +17 -0
  8. package/dist/auth/index.js.map +1 -0
  9. package/dist/auth/middleware.d.ts +45 -0
  10. package/dist/auth/middleware.d.ts.map +1 -0
  11. package/dist/auth/middleware.js +170 -0
  12. package/dist/auth/middleware.js.map +1 -0
  13. package/dist/auth/service.d.ts +80 -0
  14. package/dist/auth/service.d.ts.map +1 -0
  15. package/dist/auth/service.js +298 -0
  16. package/dist/auth/service.js.map +1 -0
  17. package/dist/cli/index.d.ts +6 -0
  18. package/dist/cli/index.d.ts.map +1 -0
  19. package/dist/cli/index.js +375 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/mock-server.d.ts +20 -0
  22. package/dist/cli/mock-server.d.ts.map +1 -0
  23. package/dist/cli/mock-server.js +261 -0
  24. package/dist/cli/mock-server.js.map +1 -0
  25. package/dist/compliance/compliance-generator.d.ts +34 -0
  26. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  27. package/dist/compliance/compliance-generator.js +320 -0
  28. package/dist/compliance/compliance-generator.js.map +1 -0
  29. package/dist/compliance/index.d.ts +8 -0
  30. package/dist/compliance/index.d.ts.map +1 -0
  31. package/dist/compliance/index.js +17 -0
  32. package/dist/compliance/index.js.map +1 -0
  33. package/dist/compliance/templates.d.ts +34 -0
  34. package/dist/compliance/templates.d.ts.map +1 -0
  35. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  36. package/dist/compliance/templates.js.map +1 -0
  37. package/dist/compliance/types.d.ts +64 -0
  38. package/dist/compliance/types.d.ts.map +1 -0
  39. package/dist/compliance/types.js +64 -0
  40. package/dist/compliance/types.js.map +1 -0
  41. package/dist/db/index.d.ts +17 -0
  42. package/dist/db/index.d.ts.map +1 -0
  43. package/dist/db/index.js +80 -0
  44. package/dist/db/index.js.map +1 -0
  45. package/dist/db/schema.d.ts +3886 -0
  46. package/dist/db/schema.d.ts.map +1 -0
  47. package/dist/db/schema.js +425 -0
  48. package/dist/db/schema.js.map +1 -0
  49. package/dist/db/utils.d.ts +252 -0
  50. package/dist/db/utils.d.ts.map +1 -0
  51. package/dist/db/utils.js +295 -0
  52. package/dist/db/utils.js.map +1 -0
  53. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  54. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  55. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +856 -726
  56. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  57. package/dist/feed-analyzer/index.d.ts +8 -0
  58. package/dist/feed-analyzer/index.d.ts.map +1 -0
  59. package/dist/feed-analyzer/index.js +19 -0
  60. package/dist/feed-analyzer/index.js.map +1 -0
  61. package/dist/feed-analyzer/types.d.ts +285 -0
  62. package/dist/feed-analyzer/types.d.ts.map +1 -0
  63. package/dist/feed-analyzer/types.js +175 -0
  64. package/dist/feed-analyzer/types.js.map +1 -0
  65. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  66. package/dist/generator/index.d.ts.map +1 -0
  67. package/dist/generator/index.js +13 -0
  68. package/dist/generator/index.js.map +1 -0
  69. package/dist/generator/key-generator.d.ts +24 -0
  70. package/dist/generator/key-generator.d.ts.map +1 -0
  71. package/dist/generator/key-generator.js +144 -0
  72. package/dist/generator/key-generator.js.map +1 -0
  73. package/dist/generator/profile-builder.d.ts +15 -0
  74. package/dist/generator/profile-builder.d.ts.map +1 -0
  75. package/dist/generator/profile-builder.js +338 -0
  76. package/dist/generator/profile-builder.js.map +1 -0
  77. package/dist/hosting/artifacts-generator.d.ts +10 -0
  78. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  79. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  80. package/dist/hosting/artifacts-generator.js.map +1 -0
  81. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  82. package/dist/hosting/index.d.ts.map +1 -0
  83. package/dist/hosting/index.js +10 -0
  84. package/dist/hosting/index.js.map +1 -0
  85. package/dist/index.d.ts +18 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +78 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/lib/analytics.d.ts +337 -0
  90. package/dist/lib/analytics.d.ts.map +1 -0
  91. package/dist/lib/analytics.js +188 -0
  92. package/dist/lib/analytics.js.map +1 -0
  93. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  94. package/dist/security/index.d.ts.map +1 -0
  95. package/dist/security/index.js +12 -0
  96. package/dist/security/index.js.map +1 -0
  97. package/dist/security/security-scanner.d.ts +10 -0
  98. package/dist/security/security-scanner.d.ts.map +1 -0
  99. package/dist/security/security-scanner.js +669 -0
  100. package/dist/security/security-scanner.js.map +1 -0
  101. package/dist/security/types.d.ts +80 -0
  102. package/dist/security/types.d.ts.map +1 -0
  103. package/dist/security/types.js +21 -0
  104. package/dist/security/types.js.map +1 -0
  105. package/dist/services/analytics.d.ts +114 -0
  106. package/dist/services/analytics.d.ts.map +1 -0
  107. package/dist/services/analytics.js +862 -0
  108. package/dist/services/analytics.js.map +1 -0
  109. package/dist/services/badge.d.ts +31 -0
  110. package/dist/services/badge.d.ts.map +1 -0
  111. package/dist/services/badge.js +152 -0
  112. package/dist/services/badge.js.map +1 -0
  113. package/dist/services/cron.d.ts +125 -0
  114. package/dist/services/cron.d.ts.map +1 -0
  115. package/dist/services/cron.js +613 -0
  116. package/dist/services/cron.js.map +1 -0
  117. package/dist/services/directory.d.ts +106 -0
  118. package/dist/services/directory.d.ts.map +1 -0
  119. package/dist/services/directory.js +351 -0
  120. package/dist/services/directory.js.map +1 -0
  121. package/dist/services/email.d.ts +112 -0
  122. package/dist/services/email.d.ts.map +1 -0
  123. package/dist/services/email.js +772 -0
  124. package/dist/services/email.js.map +1 -0
  125. package/dist/services/hosted-profiles.d.ts +77 -0
  126. package/dist/services/hosted-profiles.d.ts.map +1 -0
  127. package/dist/services/hosted-profiles.js +433 -0
  128. package/dist/services/hosted-profiles.js.map +1 -0
  129. package/dist/services/latency.d.ts +67 -0
  130. package/dist/services/latency.d.ts.map +1 -0
  131. package/dist/services/latency.js +274 -0
  132. package/dist/services/latency.js.map +1 -0
  133. package/dist/services/manifest-compliance.d.ts +64 -0
  134. package/dist/services/manifest-compliance.d.ts.map +1 -0
  135. package/dist/services/manifest-compliance.js +271 -0
  136. package/dist/services/manifest-compliance.js.map +1 -0
  137. package/dist/services/monitoring-diff.d.ts +31 -0
  138. package/dist/services/monitoring-diff.d.ts.map +1 -0
  139. package/dist/services/monitoring-diff.js +189 -0
  140. package/dist/services/monitoring-diff.js.map +1 -0
  141. package/dist/services/notifications.d.ts +46 -0
  142. package/dist/services/notifications.d.ts.map +1 -0
  143. package/dist/services/notifications.js +88 -0
  144. package/dist/services/notifications.js.map +1 -0
  145. package/dist/services/stripe.d.ts +93 -0
  146. package/dist/services/stripe.d.ts.map +1 -0
  147. package/dist/services/stripe.js +490 -0
  148. package/dist/services/stripe.js.map +1 -0
  149. package/dist/services/validation-history.d.ts +99 -0
  150. package/dist/services/validation-history.d.ts.map +1 -0
  151. package/dist/services/validation-history.js +344 -0
  152. package/dist/services/validation-history.js.map +1 -0
  153. package/dist/services/validation-logging.d.ts +103 -0
  154. package/dist/services/validation-logging.d.ts.map +1 -0
  155. package/dist/services/validation-logging.js +210 -0
  156. package/dist/services/validation-logging.js.map +1 -0
  157. package/dist/services/validation.d.ts +119 -0
  158. package/dist/services/validation.d.ts.map +1 -0
  159. package/dist/services/validation.js +1185 -0
  160. package/dist/services/validation.js.map +1 -0
  161. package/dist/simulator/agent-simulator.d.ts +69 -0
  162. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  163. package/dist/simulator/agent-simulator.js +870 -0
  164. package/dist/simulator/agent-simulator.js.map +1 -0
  165. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  166. package/dist/simulator/index.d.ts.map +1 -0
  167. package/dist/simulator/index.js +23 -0
  168. package/dist/simulator/index.js.map +1 -0
  169. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +171 -170
  170. package/dist/simulator/types.d.ts.map +1 -0
  171. package/dist/simulator/types.js +18 -0
  172. package/dist/simulator/types.js.map +1 -0
  173. package/dist/types/acp-validation.d.ts +87 -0
  174. package/dist/types/acp-validation.d.ts.map +1 -0
  175. package/dist/types/acp-validation.js +40 -0
  176. package/dist/types/acp-validation.js.map +1 -0
  177. package/dist/types/analytics.d.ts +182 -0
  178. package/dist/types/analytics.d.ts.map +1 -0
  179. package/dist/types/analytics.js +7 -0
  180. package/dist/types/analytics.js.map +1 -0
  181. package/dist/types/generator.d.ts +106 -0
  182. package/dist/types/generator.d.ts.map +1 -0
  183. package/dist/types/generator.js +6 -0
  184. package/dist/types/generator.js.map +1 -0
  185. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  186. package/dist/types/index.d.ts.map +1 -0
  187. package/dist/types/index.js +23 -0
  188. package/dist/types/index.js.map +1 -0
  189. package/dist/types/ucp-profile.d.ts +111 -0
  190. package/dist/types/ucp-profile.d.ts.map +1 -0
  191. package/dist/types/ucp-profile.js +45 -0
  192. package/dist/types/ucp-profile.js.map +1 -0
  193. package/dist/types/validation.d.ts +76 -0
  194. package/dist/types/validation.d.ts.map +1 -0
  195. package/dist/types/validation.js +42 -0
  196. package/dist/types/validation.js.map +1 -0
  197. package/dist/validator/acp/index.d.ts +31 -0
  198. package/dist/validator/acp/index.d.ts.map +1 -0
  199. package/dist/validator/acp/index.js +574 -0
  200. package/dist/validator/acp/index.js.map +1 -0
  201. package/dist/validator/index.d.ts +26 -0
  202. package/dist/validator/index.d.ts.map +1 -0
  203. package/dist/validator/index.js +161 -0
  204. package/dist/validator/index.js.map +1 -0
  205. package/dist/validator/network-validator.d.ts +28 -0
  206. package/dist/validator/network-validator.d.ts.map +1 -0
  207. package/dist/validator/network-validator.js +319 -0
  208. package/dist/validator/network-validator.js.map +1 -0
  209. package/dist/validator/rules-validator.d.ts +19 -0
  210. package/dist/validator/rules-validator.d.ts.map +1 -0
  211. package/dist/validator/rules-validator.js +306 -0
  212. package/dist/validator/rules-validator.js.map +1 -0
  213. package/dist/validator/sdk-validator.d.ts +58 -0
  214. package/dist/validator/sdk-validator.d.ts.map +1 -0
  215. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  216. package/dist/validator/sdk-validator.js.map +1 -0
  217. package/dist/validator/structural-validator.d.ts +11 -0
  218. package/dist/validator/structural-validator.d.ts.map +1 -0
  219. package/dist/validator/structural-validator.js +549 -0
  220. package/dist/validator/structural-validator.js.map +1 -0
  221. package/dist/validator/utils.d.ts +51 -0
  222. package/dist/validator/utils.d.ts.map +1 -0
  223. package/dist/validator/utils.js +132 -0
  224. package/dist/validator/utils.js.map +1 -0
  225. package/package.json +44 -12
  226. package/CLAUDE.md +0 -109
  227. package/api/analyze-feed.js +0 -140
  228. package/api/badge.js +0 -185
  229. package/api/benchmark.js +0 -177
  230. package/api/directory-stats.ts +0 -29
  231. package/api/directory.ts +0 -73
  232. package/api/generate-compliance.js +0 -143
  233. package/api/generate-schema.js +0 -457
  234. package/api/generate.js +0 -132
  235. package/api/security-scan.js +0 -133
  236. package/api/simulate.js +0 -187
  237. package/api/tsconfig.json +0 -10
  238. package/api/validate.js +0 -1351
  239. package/apify-actor/.actor/actor.json +0 -68
  240. package/apify-actor/.actor/input_schema.json +0 -32
  241. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  242. package/apify-actor/Dockerfile +0 -8
  243. package/apify-actor/README.md +0 -166
  244. package/apify-actor/main.ts +0 -111
  245. package/apify-actor/package.json +0 -17
  246. package/apify-actor/src/main.js +0 -199
  247. package/docs/BRAND-IDENTITY.md +0 -238
  248. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  249. package/drizzle/0000_black_king_cobra.sql +0 -39
  250. package/drizzle/meta/0000_snapshot.json +0 -309
  251. package/drizzle/meta/_journal.json +0 -13
  252. package/drizzle.config.ts +0 -10
  253. package/public/.well-known/ucp +0 -25
  254. package/public/android-chrome-192x192.png +0 -0
  255. package/public/android-chrome-512x512.png +0 -0
  256. package/public/apple-touch-icon.png +0 -0
  257. package/public/brand.css +0 -321
  258. package/public/directory.html +0 -701
  259. package/public/favicon-16x16.png +0 -0
  260. package/public/favicon-32x32.png +0 -0
  261. package/public/favicon.ico +0 -0
  262. package/public/guides/bigcommerce.html +0 -743
  263. package/public/guides/fastucp.html +0 -838
  264. package/public/guides/magento.html +0 -779
  265. package/public/guides/shopify.html +0 -726
  266. package/public/guides/squarespace.html +0 -749
  267. package/public/guides/wix.html +0 -747
  268. package/public/guides/woocommerce.html +0 -733
  269. package/public/index.html +0 -3835
  270. package/public/learn.html +0 -396
  271. package/public/logo.jpeg +0 -0
  272. package/public/og-image-icon.png +0 -0
  273. package/public/og-image.png +0 -0
  274. package/public/robots.txt +0 -6
  275. package/public/site.webmanifest +0 -31
  276. package/public/sitemap.xml +0 -69
  277. package/public/social/linkedin-banner-1128x191.png +0 -0
  278. package/public/social/temp.PNG +0 -0
  279. package/public/social/x-header-1500x500.png +0 -0
  280. package/public/verify.html +0 -410
  281. package/scripts/generate-favicons.js +0 -44
  282. package/scripts/generate-ico.js +0 -23
  283. package/scripts/generate-og-image.js +0 -45
  284. package/scripts/reset-db.ts +0 -77
  285. package/scripts/seed-db.ts +0 -71
  286. package/scripts/setup-benchmark-db.js +0 -70
  287. package/src/api/server.ts +0 -266
  288. package/src/cli/index.ts +0 -302
  289. package/src/compliance/compliance-generator.ts +0 -452
  290. package/src/compliance/index.ts +0 -28
  291. package/src/compliance/types.ts +0 -170
  292. package/src/db/index.ts +0 -28
  293. package/src/db/schema.ts +0 -84
  294. package/src/feed-analyzer/index.ts +0 -34
  295. package/src/feed-analyzer/types.ts +0 -354
  296. package/src/generator/key-generator.ts +0 -124
  297. package/src/generator/profile-builder.ts +0 -402
  298. package/src/index.ts +0 -105
  299. package/src/security/security-scanner.ts +0 -604
  300. package/src/security/types.ts +0 -55
  301. package/src/services/directory.ts +0 -434
  302. package/src/simulator/agent-simulator.ts +0 -941
  303. package/src/types/generator.ts +0 -140
  304. package/src/types/ucp-profile.ts +0 -140
  305. package/src/types/validation.ts +0 -89
  306. package/src/validator/index.ts +0 -194
  307. package/src/validator/network-validator.ts +0 -417
  308. package/src/validator/rules-validator.ts +0 -297
  309. package/src/validator/structural-validator.ts +0 -476
  310. package/tests/fixtures/non-compliant-profile.json +0 -25
  311. package/tests/fixtures/official-sample-profile.json +0 -75
  312. package/tests/integration/benchmark.test.ts +0 -207
  313. package/tests/integration/database.test.ts +0 -163
  314. package/tests/integration/directory-api.test.ts +0 -268
  315. package/tests/integration/simulate-api.test.ts +0 -230
  316. package/tests/integration/validate-api.test.ts +0 -269
  317. package/tests/setup.ts +0 -15
  318. package/tests/unit/agent-simulator.test.ts +0 -575
  319. package/tests/unit/compliance-generator.test.ts +0 -374
  320. package/tests/unit/directory-service.test.ts +0 -272
  321. package/tests/unit/feed-analyzer.test.ts +0 -517
  322. package/tests/unit/lint-suggestions.test.ts +0 -423
  323. package/tests/unit/official-samples.test.ts +0 -211
  324. package/tests/unit/pdf-report.test.ts +0 -390
  325. package/tests/unit/sdk-validator.test.ts +0 -531
  326. package/tests/unit/security-scanner.test.ts +0 -410
  327. package/tests/unit/validation.test.ts +0 -390
  328. package/tsconfig.json +0 -20
  329. package/vercel.json +0 -34
  330. package/vitest.config.ts +0 -22
@@ -1,701 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <!-- Google Analytics -->
5
- <script async src="https://www.googletagmanager.com/gtag/js?id=G-J5JSHV7H1E"></script>
6
- <script>
7
- window.dataLayer = window.dataLayer || [];
8
- function gtag(){dataLayer.push(arguments);}
9
- gtag('js', new Date());
10
- gtag('config', 'G-J5JSHV7H1E');
11
- </script>
12
- <!-- Ahrefs Analytics -->
13
- <script src="https://analytics.ahrefs.com/analytics.js" data-key="w/KDij6w81HzA8oXaw60JA" async></script>
14
- <meta charset="UTF-8">
15
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
- <title>UCP Merchant Directory - AI-Ready E-commerce Stores | Universal Commerce Protocol</title>
17
- <meta name="description" content="Browse the public directory of Universal Commerce Protocol (UCP) enabled merchants. Discover AI-ready e-commerce stores compatible with Google AI Mode, ChatGPT Shopping, and Microsoft Copilot checkout.">
18
- <meta name="keywords" content="UCP Directory, AI-Ready Stores, Universal Commerce Protocol Merchants, AI Commerce Directory, UCP Enabled Stores, AI Shopping Compatible">
19
- <meta name="robots" content="index, follow">
20
- <link rel="canonical" href="https://ucptools.dev/directory">
21
- <link rel="icon" type="image/x-icon" href="/favicon.ico">
22
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
23
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
24
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
25
- <meta name="theme-color" content="#2E86AB">
26
-
27
- <!-- Open Graph / Facebook -->
28
- <meta property="og:type" content="website">
29
- <meta property="og:url" content="https://ucptools.dev/directory">
30
- <meta property="og:title" content="UCP Merchant Directory - AI-Ready E-commerce Stores">
31
- <meta property="og:description" content="Browse Universal Commerce Protocol (UCP) enabled merchants ready for AI shopping agents.">
32
- <meta property="og:image" content="https://ucptools.dev/og-image.png">
33
- <meta property="og:site_name" content="UCP.tools">
34
-
35
- <!-- Twitter Card -->
36
- <meta name="twitter:card" content="summary_large_image">
37
- <meta name="twitter:url" content="https://ucptools.dev/directory">
38
- <meta name="twitter:title" content="UCP Merchant Directory - AI-Ready E-commerce Stores">
39
- <meta name="twitter:description" content="Discover UCP-enabled e-commerce stores compatible with AI shopping agents.">
40
- <meta name="twitter:image" content="https://ucptools.dev/og-image.png">
41
-
42
- <!-- JSON-LD Structured Data -->
43
- <script type="application/ld+json">
44
- {
45
- "@context": "https://schema.org",
46
- "@type": "CollectionPage",
47
- "name": "UCP Merchant Directory",
48
- "description": "Directory of Universal Commerce Protocol (UCP) enabled merchants ready for AI commerce.",
49
- "url": "https://ucptools.dev/directory",
50
- "isPartOf": {
51
- "@type": "WebSite",
52
- "name": "UCP.tools",
53
- "url": "https://ucptools.dev/"
54
- },
55
- "breadcrumb": {
56
- "@type": "BreadcrumbList",
57
- "itemListElement": [
58
- {"@type": "ListItem", "position": 1, "name": "Home", "item": "https://ucptools.dev/"},
59
- {"@type": "ListItem", "position": 2, "name": "Merchant Directory", "item": "https://ucptools.dev/directory"}
60
- ]
61
- }
62
- }
63
- </script>
64
- <style>
65
- :root {
66
- --brand-blue: #2E86AB;
67
- --brand-teal: #36B5A2;
68
- --brand-green: #47C97A;
69
- --brand-gradient: linear-gradient(135deg, #2E86AB 0%, #36B5A2 50%, #47C97A 100%);
70
- --brand-gradient-hover: linear-gradient(135deg, #267593 0%, #2EA18F 50%, #3BB86B 100%);
71
- --color-dark: #1A2B3C;
72
- --color-medium: #5A6978;
73
- --color-light: #94A3B8;
74
- --color-border: #E2E8F0;
75
- --color-background: #F8FAFC;
76
- --color-card: #FFFFFF;
77
- --color-success: #47C97A;
78
- --color-warning: #F59E0B;
79
- --color-error: #EF4444;
80
- --color-info: #2E86AB;
81
- --grade-a-bg: #DCFCE7; --grade-a-text: #16A34A;
82
- --grade-b-bg: #DBEAFE; --grade-b-text: #2563EB;
83
- --grade-c-bg: #FEF9C3; --grade-c-text: #CA8A04;
84
- --grade-d-bg: #FED7AA; --grade-d-text: #EA580C;
85
- --grade-f-bg: #FEE2E2; --grade-f-text: #DC2626;
86
- }
87
-
88
- * { box-sizing: border-box; margin: 0; padding: 0; }
89
- body {
90
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
91
- background: var(--color-background);
92
- color: var(--color-dark);
93
- line-height: 1.6;
94
- }
95
- .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
96
-
97
- /* Header */
98
- header {
99
- background: var(--color-card);
100
- border-bottom: 1px solid var(--color-border);
101
- padding: 16px 0;
102
- position: sticky;
103
- top: 0;
104
- z-index: 1000;
105
- }
106
- .header-inner { display: flex; justify-content: space-between; align-items: center; }
107
- .logo { display: flex; align-items: center; gap: 12px; font-size: 24px; font-weight: 700; text-decoration: none; }
108
- .logo-icon { width: 40px; height: 40px; border-radius: 8px; }
109
- .logo-text { background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
110
- .logo-suffix { font-weight: 400; -webkit-text-fill-color: var(--color-medium); }
111
- nav a { margin-left: 24px; color: var(--color-medium); text-decoration: none; transition: color 0.2s; }
112
- nav a:hover, nav a.active { color: var(--brand-blue); }
113
- /* Dropdown */
114
- .dropdown { position: relative; display: inline-block; margin-left: 24px; }
115
- .dropdown-toggle { color: var(--color-medium); cursor: pointer; background: none; border: none; font-size: inherit; font-family: inherit; padding: 0; transition: color 0.2s; }
116
- .dropdown-toggle:hover, .dropdown:hover .dropdown-toggle { color: var(--brand-blue); }
117
- .dropdown-toggle::after { content: '▼'; font-size: 0.6em; margin-left: 4px; vertical-align: middle; }
118
- .dropdown-menu { display: none; position: absolute; top: 100%; left: 0; background: var(--color-card); border: 1px solid var(--color-border); border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); min-width: 180px; z-index: 1001; padding: 8px 0; margin-top: 8px; }
119
- .dropdown:hover .dropdown-menu { display: block; }
120
- .dropdown-menu a { display: block; padding: 10px 16px; color: var(--color-medium); text-decoration: none; margin-left: 0; }
121
- .dropdown-menu a:hover { background: rgba(46,134,171,0.08); color: var(--brand-blue); }
122
- .dropdown-divider { height: 1px; background: var(--color-border); margin: 8px 0; }
123
-
124
- /* Hero */
125
- .hero {
126
- text-align: center;
127
- padding: 48px 20px;
128
- background: linear-gradient(135deg, rgba(46, 134, 171, 0.1) 0%, rgba(54, 181, 162, 0.05) 50%, rgba(71, 201, 122, 0.1) 100%);
129
- }
130
- .hero h1 { font-size: 36px; margin-bottom: 12px; }
131
- .hero h1 .gradient-text { background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
132
- .hero p { font-size: 18px; color: var(--color-medium); max-width: 600px; margin: 0 auto 24px; }
133
-
134
- /* Stats Bar */
135
- .stats-bar {
136
- display: flex;
137
- justify-content: center;
138
- gap: 32px;
139
- flex-wrap: wrap;
140
- }
141
- .stat-item { text-align: center; }
142
- .stat-value { font-size: 28px; font-weight: 700; background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
143
- .stat-label { font-size: 14px; color: var(--color-medium); }
144
-
145
- /* Main Content */
146
- .main-content { padding: 40px 0; }
147
- .content-grid { display: grid; grid-template-columns: 280px 1fr; gap: 32px; }
148
- @media (max-width: 900px) { .content-grid { grid-template-columns: 1fr; } }
149
-
150
- /* Sidebar */
151
- .sidebar { display: flex; flex-direction: column; gap: 24px; }
152
- .filter-card {
153
- background: var(--color-card);
154
- border: 1px solid var(--color-border);
155
- border-radius: 12px;
156
- padding: 20px;
157
- }
158
- .filter-card h3 { font-size: 14px; font-weight: 600; color: var(--color-medium); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
159
- .search-input {
160
- width: 100%;
161
- padding: 12px 16px;
162
- border: 2px solid var(--color-border);
163
- border-radius: 8px;
164
- font-size: 14px;
165
- transition: border-color 0.2s;
166
- }
167
- .search-input:focus { outline: none; border-color: var(--brand-blue); }
168
- .filter-list { list-style: none; }
169
- .filter-item {
170
- display: flex;
171
- align-items: center;
172
- padding: 8px 0;
173
- cursor: pointer;
174
- transition: color 0.2s;
175
- }
176
- .filter-item:hover { color: var(--brand-blue); }
177
- .filter-item input { margin-right: 10px; }
178
- .filter-count { margin-left: auto; font-size: 12px; color: var(--color-light); background: var(--color-background); padding: 2px 8px; border-radius: 10px; }
179
-
180
- /* Submit CTA */
181
- .submit-cta {
182
- background: var(--brand-gradient);
183
- border-radius: 12px;
184
- padding: 20px;
185
- color: white;
186
- text-align: center;
187
- }
188
- .submit-cta h3 { font-size: 16px; margin-bottom: 8px; }
189
- .submit-cta p { font-size: 14px; opacity: 0.9; margin-bottom: 16px; }
190
- .submit-btn {
191
- display: inline-block;
192
- padding: 10px 20px;
193
- background: white;
194
- color: var(--brand-blue);
195
- border-radius: 6px;
196
- font-weight: 600;
197
- text-decoration: none;
198
- transition: transform 0.2s;
199
- }
200
- .submit-btn:hover { transform: translateY(-2px); }
201
-
202
- /* Merchant Grid */
203
- .merchant-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
204
- .merchant-card {
205
- background: var(--color-card);
206
- border: 1px solid var(--color-border);
207
- border-radius: 12px;
208
- padding: 20px;
209
- transition: all 0.2s;
210
- text-decoration: none;
211
- color: inherit;
212
- display: block;
213
- }
214
- .merchant-card:hover { border-color: var(--brand-blue); box-shadow: 0 4px 12px rgba(46, 134, 171, 0.15); }
215
- .merchant-header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
216
- .merchant-logo {
217
- width: 48px;
218
- height: 48px;
219
- border-radius: 8px;
220
- background: var(--color-background);
221
- display: flex;
222
- align-items: center;
223
- justify-content: center;
224
- font-size: 20px;
225
- font-weight: 700;
226
- color: var(--brand-blue);
227
- }
228
- .merchant-logo img { width: 100%; height: 100%; object-fit: cover; border-radius: 8px; }
229
- .merchant-info { flex: 1; min-width: 0; }
230
- .merchant-name { font-weight: 600; font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
231
- .merchant-domain { font-size: 13px; color: var(--color-medium); }
232
- .merchant-grade {
233
- padding: 4px 10px;
234
- border-radius: 6px;
235
- font-weight: 700;
236
- font-size: 14px;
237
- }
238
- .grade-A { background: var(--grade-a-bg); color: var(--grade-a-text); }
239
- .grade-B { background: var(--grade-b-bg); color: var(--grade-b-text); }
240
- .grade-C { background: var(--grade-c-bg); color: var(--grade-c-text); }
241
- .grade-D { background: var(--grade-d-bg); color: var(--grade-d-text); }
242
- .grade-F { background: var(--grade-f-bg); color: var(--grade-f-text); }
243
- .merchant-desc { font-size: 14px; color: var(--color-medium); margin-bottom: 12px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
244
- .merchant-tags { display: flex; flex-wrap: wrap; gap: 6px; }
245
- .merchant-tag { font-size: 11px; padding: 3px 8px; background: var(--color-background); border-radius: 4px; color: var(--color-medium); }
246
- .transport-tag { background: rgba(46, 134, 171, 0.1); color: var(--brand-blue); }
247
-
248
- /* Pagination */
249
- .pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 32px; }
250
- .page-btn {
251
- padding: 8px 16px;
252
- border: 1px solid var(--color-border);
253
- background: var(--color-card);
254
- border-radius: 6px;
255
- cursor: pointer;
256
- transition: all 0.2s;
257
- }
258
- .page-btn:hover:not(:disabled) { border-color: var(--brand-blue); color: var(--brand-blue); }
259
- .page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
260
- .page-info { color: var(--color-medium); font-size: 14px; }
261
-
262
- /* Loading & Empty States */
263
- .loading, .empty-state { text-align: center; padding: 60px 20px; color: var(--color-medium); }
264
- .loading::after { content: ''; display: inline-block; width: 24px; height: 24px; border: 3px solid var(--color-border); border-top-color: var(--brand-blue); border-radius: 50%; animation: spin 1s linear infinite; margin-left: 12px; vertical-align: middle; }
265
- @keyframes spin { to { transform: rotate(360deg); } }
266
- .empty-state h3 { font-size: 18px; color: var(--color-dark); margin-bottom: 8px; }
267
-
268
- /* Modal */
269
- .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 2000; align-items: center; justify-content: center; }
270
- .modal-overlay.active { display: flex; }
271
- .modal {
272
- background: var(--color-card);
273
- border-radius: 16px;
274
- padding: 32px;
275
- max-width: 500px;
276
- width: 90%;
277
- max-height: 90vh;
278
- overflow-y: auto;
279
- }
280
- .modal h2 { font-size: 24px; margin-bottom: 8px; }
281
- .modal p.subtitle { color: var(--color-medium); margin-bottom: 24px; }
282
- .form-group { margin-bottom: 16px; }
283
- .form-group label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; }
284
- .form-group input, .form-group select, .form-group textarea {
285
- width: 100%;
286
- padding: 12px;
287
- border: 2px solid var(--color-border);
288
- border-radius: 8px;
289
- font-size: 14px;
290
- }
291
- .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: var(--brand-blue); }
292
- .form-actions { display: flex; gap: 12px; margin-top: 24px; }
293
- .btn { padding: 12px 24px; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.2s; border: none; }
294
- .btn-primary { background: var(--brand-gradient); color: white; }
295
- .btn-primary:hover { background: var(--brand-gradient-hover); }
296
- .btn-secondary { background: var(--color-background); color: var(--color-dark); }
297
- .btn-secondary:hover { background: var(--color-border); }
298
- .form-message { padding: 12px; border-radius: 8px; margin-bottom: 16px; font-size: 14px; }
299
- .form-message.error { background: var(--grade-f-bg); color: var(--grade-f-text); }
300
- .form-message.success { background: var(--grade-a-bg); color: var(--grade-a-text); }
301
-
302
- /* Footer */
303
- footer { text-align: center; padding: 40px 20px; color: var(--color-medium); font-size: 14px; border-top: 1px solid var(--color-border); margin-top: 60px; }
304
- footer a { color: var(--brand-blue); text-decoration: none; }
305
- </style>
306
- </head>
307
- <body>
308
- <header>
309
- <div class="container header-inner">
310
- <a href="/" class="logo">
311
- <img src="/logo.jpeg" alt="UCP.tools" class="logo-icon">
312
- <span class="logo-text">UCP<span class="logo-suffix">.tools</span></span>
313
- </a>
314
- <nav>
315
- <a href="/">Validator</a>
316
- <a href="/learn">Learn</a>
317
- <a href="/directory" class="active">Directory</a>
318
- <div class="dropdown">
319
- <span class="dropdown-toggle">Guides</span>
320
- <div class="dropdown-menu">
321
- <a href="/guides/fastucp">⚡ FastUCP</a>
322
- <div class="dropdown-divider"></div>
323
- <a href="/guides/shopify">🛒 Shopify</a>
324
- <a href="/guides/woocommerce">🔌 WooCommerce</a>
325
- <a href="/guides/magento">🧱 Magento</a>
326
- <a href="/guides/wix">✨ Wix</a>
327
- <a href="/guides/bigcommerce">📦 BigCommerce</a>
328
- <a href="/guides/squarespace">◼️ Squarespace</a>
329
- </div>
330
- </div>
331
- </nav>
332
- </div>
333
- </header>
334
-
335
- <section class="hero">
336
- <div class="container">
337
- <h1><span class="gradient-text">UCP Merchant</span> Directory</h1>
338
- <p>Discover stores ready for AI commerce. Browse verified UCP-enabled merchants compatible with AI shopping agents.</p>
339
- <div class="stats-bar" id="stats-bar">
340
- <div class="stat-item">
341
- <div class="stat-value" id="stat-merchants">-</div>
342
- <div class="stat-label">Merchants</div>
343
- </div>
344
- <div class="stat-item">
345
- <div class="stat-value" id="stat-categories">-</div>
346
- <div class="stat-label">Categories</div>
347
- </div>
348
- <div class="stat-item">
349
- <div class="stat-value" id="stat-countries">-</div>
350
- <div class="stat-label">Countries</div>
351
- </div>
352
- <div class="stat-item">
353
- <div class="stat-value" id="stat-avg-score">-</div>
354
- <div class="stat-label">Avg Score</div>
355
- </div>
356
- </div>
357
- </div>
358
- </section>
359
-
360
- <main class="main-content">
361
- <div class="container">
362
- <div class="content-grid">
363
- <aside class="sidebar">
364
- <div class="filter-card">
365
- <h3>Search</h3>
366
- <input type="text" class="search-input" id="search-input" placeholder="Search merchants...">
367
- </div>
368
-
369
- <div class="filter-card">
370
- <h3>Categories</h3>
371
- <ul class="filter-list" id="category-filters">
372
- <li class="filter-item"><span>Loading...</span></li>
373
- </ul>
374
- </div>
375
-
376
- <div class="filter-card">
377
- <h3>Grade</h3>
378
- <ul class="filter-list" id="grade-filters">
379
- <li class="filter-item"><label><input type="checkbox" value="A"> Grade A</label></li>
380
- <li class="filter-item"><label><input type="checkbox" value="B"> Grade B</label></li>
381
- <li class="filter-item"><label><input type="checkbox" value="C"> Grade C</label></li>
382
- <li class="filter-item"><label><input type="checkbox" value="D"> Grade D</label></li>
383
- </ul>
384
- </div>
385
-
386
- <div class="submit-cta">
387
- <h3>List Your Store</h3>
388
- <p>Get discovered by AI agents and join the directory.</p>
389
- <a href="#" class="submit-btn" id="open-submit-modal">Submit Store</a>
390
- </div>
391
- </aside>
392
-
393
- <section class="merchants-section">
394
- <div class="merchant-grid" id="merchant-grid">
395
- <div class="loading">Loading merchants</div>
396
- </div>
397
-
398
- <div class="pagination" id="pagination" style="display: none;">
399
- <button class="page-btn" id="prev-page" disabled>&larr; Previous</button>
400
- <span class="page-info" id="page-info">Page 1 of 1</span>
401
- <button class="page-btn" id="next-page" disabled>Next &rarr;</button>
402
- </div>
403
- </section>
404
- </div>
405
- </div>
406
- </main>
407
-
408
- <!-- Submit Modal -->
409
- <div class="modal-overlay" id="submit-modal">
410
- <div class="modal">
411
- <h2>Submit Your Store</h2>
412
- <p class="subtitle">Add your UCP-enabled store to the directory.</p>
413
-
414
- <div class="form-message" id="form-message" style="display: none;"></div>
415
-
416
- <form id="submit-form">
417
- <div class="form-group">
418
- <label for="submit-domain">Domain *</label>
419
- <input type="text" id="submit-domain" placeholder="example.com" required>
420
- </div>
421
- <div class="form-group">
422
- <label for="submit-name">Display Name</label>
423
- <input type="text" id="submit-name" placeholder="Your Store Name">
424
- </div>
425
- <div class="form-group">
426
- <label for="submit-description">Description</label>
427
- <textarea id="submit-description" rows="3" placeholder="Brief description of your store..."></textarea>
428
- </div>
429
- <div class="form-group">
430
- <label for="submit-category">Category</label>
431
- <select id="submit-category">
432
- <option value="">Select a category</option>
433
- <option value="fashion">Fashion & Apparel</option>
434
- <option value="electronics">Electronics</option>
435
- <option value="home">Home & Garden</option>
436
- <option value="beauty">Beauty & Health</option>
437
- <option value="food">Food & Grocery</option>
438
- <option value="sports">Sports & Outdoors</option>
439
- <option value="toys">Toys & Games</option>
440
- <option value="books">Books & Media</option>
441
- <option value="automotive">Automotive</option>
442
- <option value="other">Other</option>
443
- </select>
444
- </div>
445
- <div class="form-group">
446
- <label for="submit-country">Country</label>
447
- <select id="submit-country">
448
- <option value="">Select a country</option>
449
- <option value="US">United States</option>
450
- <option value="GB">United Kingdom</option>
451
- <option value="DE">Germany</option>
452
- <option value="FR">France</option>
453
- <option value="CA">Canada</option>
454
- <option value="AU">Australia</option>
455
- <option value="NL">Netherlands</option>
456
- <option value="ES">Spain</option>
457
- <option value="IT">Italy</option>
458
- <option value="JP">Japan</option>
459
- </select>
460
- </div>
461
- <div class="form-actions">
462
- <button type="button" class="btn btn-secondary" id="close-modal">Cancel</button>
463
- <button type="submit" class="btn btn-primary" id="submit-btn">Submit Store</button>
464
- </div>
465
- </form>
466
- </div>
467
- </div>
468
-
469
- <footer>
470
- <div class="container">
471
- <p>&copy; 2025 UCP.tools - <a href="https://ucp.dev" target="_blank">Universal Commerce Protocol</a></p>
472
- </div>
473
- </footer>
474
-
475
- <script>
476
- const API_BASE = '/api';
477
- let currentPage = 1;
478
- let totalPages = 1;
479
- let currentFilters = { search: '', category: '', country: '' };
480
-
481
- // Load stats
482
- async function loadStats() {
483
- try {
484
- const res = await fetch(`${API_BASE}/directory-stats`);
485
- const data = await res.json();
486
-
487
- document.getElementById('stat-merchants').textContent = data.stats.totalMerchants;
488
- document.getElementById('stat-categories').textContent = data.stats.totalCategories;
489
- document.getElementById('stat-countries').textContent = data.stats.totalCountries;
490
- document.getElementById('stat-avg-score').textContent = data.stats.avgScore;
491
-
492
- // Update category filters
493
- if (data.topCategories.length > 0) {
494
- const categoryList = document.getElementById('category-filters');
495
- categoryList.innerHTML = data.topCategories.map(cat => `
496
- <li class="filter-item">
497
- <label>
498
- <input type="checkbox" value="${cat.name}">
499
- ${formatCategory(cat.name)}
500
- </label>
501
- <span class="filter-count">${cat.count}</span>
502
- </li>
503
- `).join('');
504
-
505
- // Add event listeners
506
- categoryList.querySelectorAll('input').forEach(input => {
507
- input.addEventListener('change', () => {
508
- currentFilters.category = input.checked ? input.value : '';
509
- currentPage = 1;
510
- loadMerchants();
511
- });
512
- });
513
- }
514
- } catch (error) {
515
- console.error('Failed to load stats:', error);
516
- }
517
- }
518
-
519
- // Load merchants
520
- async function loadMerchants() {
521
- const grid = document.getElementById('merchant-grid');
522
- grid.innerHTML = '<div class="loading">Loading merchants</div>';
523
-
524
- try {
525
- const params = new URLSearchParams({
526
- page: currentPage,
527
- limit: 12,
528
- ...(currentFilters.search && { search: currentFilters.search }),
529
- ...(currentFilters.category && { category: currentFilters.category }),
530
- });
531
-
532
- const res = await fetch(`${API_BASE}/directory?${params}`);
533
- const data = await res.json();
534
-
535
- totalPages = data.pagination.totalPages;
536
-
537
- if (data.merchants.length === 0) {
538
- grid.innerHTML = `
539
- <div class="empty-state">
540
- <h3>No merchants found</h3>
541
- <p>Try adjusting your filters or be the first to submit your store!</p>
542
- </div>
543
- `;
544
- } else {
545
- grid.innerHTML = data.merchants.map(renderMerchantCard).join('');
546
- }
547
-
548
- updatePagination(data.pagination);
549
- } catch (error) {
550
- console.error('Failed to load merchants:', error);
551
- grid.innerHTML = '<div class="empty-state"><h3>Error loading merchants</h3><p>Please try again later.</p></div>';
552
- }
553
- }
554
-
555
- function renderMerchantCard(merchant) {
556
- const initials = (merchant.displayName || merchant.domain).substring(0, 2).toUpperCase();
557
- const transports = merchant.transports || [];
558
-
559
- return `
560
- <a href="https://${merchant.domain}" target="_blank" class="merchant-card">
561
- <div class="merchant-header">
562
- <div class="merchant-logo">
563
- ${merchant.logoUrl ? `<img src="${merchant.logoUrl}" alt="${merchant.displayName}">` : initials}
564
- </div>
565
- <div class="merchant-info">
566
- <div class="merchant-name">${merchant.displayName}</div>
567
- <div class="merchant-domain">${merchant.domain}</div>
568
- </div>
569
- ${merchant.ucpGrade ? `<div class="merchant-grade grade-${merchant.ucpGrade}">${merchant.ucpGrade}</div>` : ''}
570
- </div>
571
- ${merchant.description ? `<div class="merchant-desc">${merchant.description}</div>` : ''}
572
- <div class="merchant-tags">
573
- ${merchant.category ? `<span class="merchant-tag">${formatCategory(merchant.category)}</span>` : ''}
574
- ${merchant.countryCode ? `<span class="merchant-tag">${merchant.countryCode}</span>` : ''}
575
- ${transports.map(t => `<span class="merchant-tag transport-tag">${t}</span>`).join('')}
576
- </div>
577
- </a>
578
- `;
579
- }
580
-
581
- function formatCategory(cat) {
582
- if (!cat) return '';
583
- return cat.charAt(0).toUpperCase() + cat.slice(1).replace(/-/g, ' ');
584
- }
585
-
586
- function updatePagination(pagination) {
587
- const paginationEl = document.getElementById('pagination');
588
- const pageInfo = document.getElementById('page-info');
589
- const prevBtn = document.getElementById('prev-page');
590
- const nextBtn = document.getElementById('next-page');
591
-
592
- if (pagination.totalPages <= 1) {
593
- paginationEl.style.display = 'none';
594
- return;
595
- }
596
-
597
- paginationEl.style.display = 'flex';
598
- pageInfo.textContent = `Page ${pagination.page} of ${pagination.totalPages}`;
599
- prevBtn.disabled = pagination.page <= 1;
600
- nextBtn.disabled = pagination.page >= pagination.totalPages;
601
- }
602
-
603
- // Search handler
604
- let searchTimeout;
605
- document.getElementById('search-input').addEventListener('input', (e) => {
606
- clearTimeout(searchTimeout);
607
- searchTimeout = setTimeout(() => {
608
- currentFilters.search = e.target.value;
609
- currentPage = 1;
610
- loadMerchants();
611
- }, 300);
612
- });
613
-
614
- // Pagination handlers
615
- document.getElementById('prev-page').addEventListener('click', () => {
616
- if (currentPage > 1) {
617
- currentPage--;
618
- loadMerchants();
619
- }
620
- });
621
-
622
- document.getElementById('next-page').addEventListener('click', () => {
623
- if (currentPage < totalPages) {
624
- currentPage++;
625
- loadMerchants();
626
- }
627
- });
628
-
629
- // Modal handlers
630
- const modal = document.getElementById('submit-modal');
631
- document.getElementById('open-submit-modal').addEventListener('click', (e) => {
632
- e.preventDefault();
633
- modal.classList.add('active');
634
- });
635
-
636
- document.getElementById('close-modal').addEventListener('click', () => {
637
- modal.classList.remove('active');
638
- });
639
-
640
- modal.addEventListener('click', (e) => {
641
- if (e.target === modal) modal.classList.remove('active');
642
- });
643
-
644
- // Submit form handler
645
- document.getElementById('submit-form').addEventListener('submit', async (e) => {
646
- e.preventDefault();
647
-
648
- const formMessage = document.getElementById('form-message');
649
- const submitBtn = document.getElementById('submit-btn');
650
-
651
- formMessage.style.display = 'none';
652
- submitBtn.disabled = true;
653
- submitBtn.textContent = 'Submitting...';
654
-
655
- try {
656
- const res = await fetch(`${API_BASE}/directory`, {
657
- method: 'POST',
658
- headers: { 'Content-Type': 'application/json' },
659
- body: JSON.stringify({
660
- domain: document.getElementById('submit-domain').value,
661
- displayName: document.getElementById('submit-name').value || undefined,
662
- description: document.getElementById('submit-description').value || undefined,
663
- category: document.getElementById('submit-category').value || undefined,
664
- countryCode: document.getElementById('submit-country').value || undefined,
665
- }),
666
- });
667
-
668
- const data = await res.json();
669
-
670
- if (!res.ok) {
671
- throw new Error(data.details || data.error || 'Submission failed');
672
- }
673
-
674
- formMessage.className = 'form-message success';
675
- formMessage.textContent = 'Store submitted successfully! It will appear in the directory shortly.';
676
- formMessage.style.display = 'block';
677
-
678
- // Reset form and reload
679
- document.getElementById('submit-form').reset();
680
- setTimeout(() => {
681
- modal.classList.remove('active');
682
- loadMerchants();
683
- loadStats();
684
- }, 2000);
685
-
686
- } catch (error) {
687
- formMessage.className = 'form-message error';
688
- formMessage.textContent = error.message;
689
- formMessage.style.display = 'block';
690
- } finally {
691
- submitBtn.disabled = false;
692
- submitBtn.textContent = 'Submit Store';
693
- }
694
- });
695
-
696
- // Initialize
697
- loadStats();
698
- loadMerchants();
699
- </script>
700
- </body>
701
- </html>