create-snapify-app 0.0.0-stage → 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 (614) hide show
  1. package/LICENSE.md +24 -0
  2. package/README.md +55 -2
  3. package/bin/create-snapify-app.mjs +174 -0
  4. package/lib/args.mjs +59 -0
  5. package/lib/copy.mjs +44 -0
  6. package/lib/env.mjs +84 -0
  7. package/lib/prompt.mjs +63 -0
  8. package/package.json +33 -4
  9. package/template/.env.example +79 -0
  10. package/template/.github/workflows/ci.yml +23 -0
  11. package/template/.github/workflows/deploy.yml +26 -0
  12. package/template/.github/workflows/ping.yml +8 -0
  13. package/template/.github/workflows/release.yml +67 -0
  14. package/template/.snapify-install.json +5 -0
  15. package/template/CHANGELOG.md +28 -0
  16. package/template/LICENSE +14 -0
  17. package/template/README.md +287 -0
  18. package/template/administrator.html +23 -0
  19. package/template/apps/client/src/App.tsx +765 -0
  20. package/template/apps/client/src/auth.css +400 -0
  21. package/template/apps/client/src/lib/api.ts +22 -0
  22. package/template/apps/client/src/main.tsx +20 -0
  23. package/template/apps/community/src/App.tsx +592 -0
  24. package/template/apps/community/src/main.tsx +20 -0
  25. package/template/apps/community/src/styles.css +24 -0
  26. package/template/apps/developers/src/App.tsx +643 -0
  27. package/template/apps/developers/src/main.tsx +17 -0
  28. package/template/apps/developers/src/pages.tsx +1587 -0
  29. package/template/apps/developers/src/styles.css +98 -0
  30. package/template/apps/developers/src/ui.tsx +179 -0
  31. package/template/apps/landing/src/App.tsx +598 -0
  32. package/template/apps/landing/src/LegalPages.tsx +158 -0
  33. package/template/apps/landing/src/OrderPage.tsx +381 -0
  34. package/template/apps/landing/src/SeoPages.tsx +923 -0
  35. package/template/apps/landing/src/copy.ts +195 -0
  36. package/template/apps/landing/src/main.tsx +20 -0
  37. package/template/apps/landing/src/styles.css +803 -0
  38. package/template/apps/landing/src/types.ts +19 -0
  39. package/template/apps/marketplace/src/App.tsx +1229 -0
  40. package/template/apps/marketplace/src/developer/DeveloperApp.tsx +1266 -0
  41. package/template/apps/marketplace/src/main.tsx +20 -0
  42. package/template/apps/marketplace/src/styles.css +1001 -0
  43. package/template/apps/shared/AuthContext.tsx +65 -0
  44. package/template/apps/shared/Logo.tsx +33 -0
  45. package/template/apps/shared/api.ts +47 -0
  46. package/template/apps/shared/ui.tsx +128 -0
  47. package/template/apps/superadmin/src/App.tsx +1264 -0
  48. package/template/apps/superadmin/src/lib/api.ts +20 -0
  49. package/template/apps/superadmin/src/lib/hooks.ts +20 -0
  50. package/template/apps/superadmin/src/main.tsx +16 -0
  51. package/template/apps/superadmin/src/pages/ClientsPage.tsx +185 -0
  52. package/template/apps/superadmin/src/pages/InvoicesPage.tsx +157 -0
  53. package/template/apps/superadmin/src/pages/NewsletterPage.tsx +212 -0
  54. package/template/apps/superadmin/src/pages/OrdersPage.tsx +195 -0
  55. package/template/apps/superadmin/src/pages/PaymentsPage.tsx +150 -0
  56. package/template/apps/superadmin/src/pages/PayoutsPage.tsx +133 -0
  57. package/template/apps/superadmin/src/pages/PublishersPage.tsx +151 -0
  58. package/template/apps/superadmin/src/pages/SeoControlPage.tsx +692 -0
  59. package/template/apps/superadmin/src/pages/SettingsPage.tsx +199 -0
  60. package/template/apps/superadmin/src/pages/SystemPage.tsx +109 -0
  61. package/template/apps/superadmin/src/pages/TicketsPage.tsx +201 -0
  62. package/template/apps/superadmin/src/pages/UsersPage.tsx +167 -0
  63. package/template/client.html +21 -0
  64. package/template/community/hamdiayari/akhbar/README.md +23 -0
  65. package/template/community/hamdiayari/akhbar/screenshot.jpg +0 -0
  66. package/template/community/hamdiayari/akhbar/snapify.theme.json +76 -0
  67. package/template/community/hamdiayari/akhbar/src/Brand.tsx +26 -0
  68. package/template/community/hamdiayari/akhbar/src/Footer.tsx +182 -0
  69. package/template/community/hamdiayari/akhbar/src/Header.tsx +191 -0
  70. package/template/community/hamdiayari/akhbar/src/Layout.tsx +49 -0
  71. package/template/community/hamdiayari/akhbar/src/Social.tsx +33 -0
  72. package/template/community/hamdiayari/akhbar/src/Ticker.tsx +86 -0
  73. package/template/community/hamdiayari/akhbar/src/Views.tsx +285 -0
  74. package/template/community/hamdiayari/akhbar/src/blocks/CollectionBlocks.tsx +672 -0
  75. package/template/community/hamdiayari/akhbar/src/blocks/ContentBlocks.tsx +305 -0
  76. package/template/community/hamdiayari/akhbar/src/blocks/ConversionBlocks.tsx +569 -0
  77. package/template/community/hamdiayari/akhbar/src/blocks/HeaderBlocks.tsx +287 -0
  78. package/template/community/hamdiayari/akhbar/src/index.tsx +51 -0
  79. package/template/community/hamdiayari/akhbar/src/kit.tsx +357 -0
  80. package/template/community/hamdiayari/akhbar/src/site.ts +96 -0
  81. package/template/community/hamdiayari/akhbar/src/styles.css +566 -0
  82. package/template/community/hamdiayari/akhbar-actualites/README.md +28 -0
  83. package/template/community/hamdiayari/akhbar-actualites/screenshot.jpg +0 -0
  84. package/template/community/hamdiayari/akhbar-actualites/snapify.module.json +107 -0
  85. package/template/community/hamdiayari/akhbar-actualites/src/admin/AudiencePage.tsx +185 -0
  86. package/template/community/hamdiayari/akhbar-actualites/src/admin/index.tsx +11 -0
  87. package/template/community/hamdiayari/akhbar-actualites/src/public/Archive.tsx +142 -0
  88. package/template/community/hamdiayari/akhbar-actualites/src/public/cards.tsx +152 -0
  89. package/template/community/hamdiayari/akhbar-actualites/src/public/index.tsx +71 -0
  90. package/template/community/hamdiayari/akhbar-actualites/src/public/posts.ts +103 -0
  91. package/template/community/hamdiayari/akhbar-actualites/src/public/styles.css +568 -0
  92. package/template/community/hamdiayari/akhbar-actualites/src/public/widgets.tsx +226 -0
  93. package/template/community/hamdiayari/akhbar-actualites/src/server.ts +119 -0
  94. package/template/community/hamdiayari/akhbar-actualites/src/types.ts +25 -0
  95. package/template/community/hamdiayari/akhbar-urgent/README.md +36 -0
  96. package/template/community/hamdiayari/akhbar-urgent/screenshot.jpg +0 -0
  97. package/template/community/hamdiayari/akhbar-urgent/snapify.module.json +64 -0
  98. package/template/community/hamdiayari/akhbar-urgent/src/admin/FlashesPage.tsx +229 -0
  99. package/template/community/hamdiayari/akhbar-urgent/src/admin/index.tsx +11 -0
  100. package/template/community/hamdiayari/akhbar-urgent/src/public/Ticker.tsx +81 -0
  101. package/template/community/hamdiayari/akhbar-urgent/src/public/index.tsx +63 -0
  102. package/template/community/hamdiayari/akhbar-urgent/src/public/styles.css +142 -0
  103. package/template/community/hamdiayari/akhbar-urgent/src/server.ts +101 -0
  104. package/template/community/hamdiayari/akhbar-urgent/src/types.ts +35 -0
  105. package/template/community.html +21 -0
  106. package/template/developers.html +24 -0
  107. package/template/docker-compose.db.yml +24 -0
  108. package/template/docs/BACKOFFICE_SNAPIFY.md +130 -0
  109. package/template/docs/CI_CD.md +66 -0
  110. package/template/docs/MODULES.md +346 -0
  111. package/template/docs/PLATFORM_DEPLOY.md +54 -0
  112. package/template/docs/logo.svg +10 -0
  113. package/template/drizzle/0000_init.sql +147 -0
  114. package/template/drizzle/0001_page_project.sql +5 -0
  115. package/template/drizzle/0002_modules.sql +23 -0
  116. package/template/drizzle/0003_themes.sql +4 -0
  117. package/template/drizzle/0004_security.sql +82 -0
  118. package/template/drizzle/0005_module_records.sql +16 -0
  119. package/template/drizzle/0006_avatars.sql +4 -0
  120. package/template/drizzle/0007_platform.sql +264 -0
  121. package/template/drizzle/0008_mp_validation.sql +3 -0
  122. package/template/drizzle/0009_saas_ops.sql +77 -0
  123. package/template/drizzle/0010_cms_chrome.sql +13 -0
  124. package/template/drizzle/0011_client_portal.sql +26 -0
  125. package/template/drizzle/meta/0000_snapshot.json +998 -0
  126. package/template/drizzle/meta/0001_snapshot.json +1004 -0
  127. package/template/drizzle/meta/_journal.json +90 -0
  128. package/template/drizzle.config.ts +16 -0
  129. package/template/ecosystem.snapify.cjs +24 -0
  130. package/template/for.html +1 -0
  131. package/template/index.html +29 -0
  132. package/template/landing.html +37 -0
  133. package/template/marketplace/catalog.json +373 -0
  134. package/template/marketplace/gallery/hamdiayari.preveris/01-home.jpg +0 -0
  135. package/template/marketplace/gallery/hamdiayari.recaptcha/01-reglages.jpg +0 -0
  136. package/template/marketplace/gallery/hamdiayari.recaptcha/02-logo.jpg +0 -0
  137. package/template/marketplace/gallery/novalabs.ai-articles/01-generer.jpg +0 -0
  138. package/template/marketplace/gallery/novalabs.ai-articles/02-fournisseur-ia.jpg +0 -0
  139. package/template/marketplace/gallery/novalabs.calendar/01-agenda-site.jpg +0 -0
  140. package/template/marketplace/gallery/novalabs.calendar/02-evenements-admin.jpg +0 -0
  141. package/template/marketplace/gallery/novalabs.calendar/03-reglages.jpg +0 -0
  142. package/template/marketplace/screenshots/hamdiayari.preveris-1.0.6.jpg +0 -0
  143. package/template/marketplace/screenshots/hamdiayari.recaptcha-1.0.0.jpg +0 -0
  144. package/template/marketplace/screenshots/hamdiayari.wp-seo-migrator-1.1.1.jpg +0 -0
  145. package/template/marketplace/screenshots/novalabs.ai-articles-1.0.1.jpg +0 -0
  146. package/template/marketplace/screenshots/novalabs.ai-chat-1.0.1.jpg +0 -0
  147. package/template/marketplace/screenshots/novalabs.calendar-1.0.1.jpg +0 -0
  148. package/template/marketplace/screenshots/novalabs.cart-1.0.0.jpg +0 -0
  149. package/template/marketplace/screenshots/novalabs.checkout-1.0.0.jpg +0 -0
  150. package/template/marketplace/screenshots/novalabs.customers-1.1.0.jpg +0 -0
  151. package/template/marketplace/screenshots/novalabs.dogga-1.0.0.jpg +0 -0
  152. package/template/marketplace/screenshots/novalabs.mahdia-1.0.0.jpg +0 -0
  153. package/template/marketplace/screenshots/novalabs.shop-1.0.0.jpg +0 -0
  154. package/template/marketplace/screenshots/novalabs.sidibou-1.0.0.jpg +0 -0
  155. package/template/marketplace/screenshots/novalabs.whatsapp-1.0.1.jpg +0 -0
  156. package/template/marketplace.html +21 -0
  157. package/template/metadata.json +7 -0
  158. package/template/modules/ai-articles/screenshot.jpg +0 -0
  159. package/template/modules/ai-articles/snapify.module.json +74 -0
  160. package/template/modules/ai-articles/src/admin/ArticlesPage.tsx +162 -0
  161. package/template/modules/ai-articles/src/admin/GeneratePage.tsx +118 -0
  162. package/template/modules/ai-articles/src/admin/index.tsx +12 -0
  163. package/template/modules/ai-articles/src/public/index.tsx +119 -0
  164. package/template/modules/ai-articles/src/server.ts +287 -0
  165. package/template/modules/ai-articles/src/types.ts +30 -0
  166. package/template/modules/ai-chat/screenshot.jpg +0 -0
  167. package/template/modules/ai-chat/snapify.module.json +217 -0
  168. package/template/modules/ai-chat/src/public.tsx +198 -0
  169. package/template/modules/ai-chat/src/server.ts +142 -0
  170. package/template/modules/ai-theme-studio/README.md +15 -0
  171. package/template/modules/ai-theme-studio/screenshot.jpg +0 -0
  172. package/template/modules/ai-theme-studio/snapify.module.json +175 -0
  173. package/template/modules/ai-theme-studio/src/admin/HistoryPage.tsx +91 -0
  174. package/template/modules/ai-theme-studio/src/admin/StudioPage.tsx +414 -0
  175. package/template/modules/ai-theme-studio/src/admin/index.tsx +11 -0
  176. package/template/modules/ai-theme-studio/src/lib/images.ts +240 -0
  177. package/template/modules/ai-theme-studio/src/lib/llm.ts +123 -0
  178. package/template/modules/ai-theme-studio/src/lib/normalize.ts +714 -0
  179. package/template/modules/ai-theme-studio/src/lib/prompts.ts +198 -0
  180. package/template/modules/ai-theme-studio/src/server.ts +137 -0
  181. package/template/modules/ai-theme-studio/src/types.ts +112 -0
  182. package/template/modules/calendar/screenshot.jpg +0 -0
  183. package/template/modules/calendar/snapify.module.json +45 -0
  184. package/template/modules/calendar/src/admin/EventsPage.tsx +217 -0
  185. package/template/modules/calendar/src/admin/index.tsx +11 -0
  186. package/template/modules/calendar/src/public/index.tsx +231 -0
  187. package/template/modules/calendar/src/server.ts +143 -0
  188. package/template/modules/calendar/src/types.ts +25 -0
  189. package/template/modules/cart/screenshot.jpg +0 -0
  190. package/template/modules/cart/snapify.module.json +90 -0
  191. package/template/modules/cart/src/admin/index.tsx +265 -0
  192. package/template/modules/cart/src/public/CartPage.tsx +198 -0
  193. package/template/modules/cart/src/public/index.tsx +261 -0
  194. package/template/modules/cart/src/public/useQuote.ts +67 -0
  195. package/template/modules/cart/src/server.ts +148 -0
  196. package/template/modules/cart/src/types.ts +31 -0
  197. package/template/modules/checkout/screenshot.jpg +0 -0
  198. package/template/modules/checkout/snapify.module.json +264 -0
  199. package/template/modules/checkout/src/admin/OrderDetailPage.tsx +304 -0
  200. package/template/modules/checkout/src/admin/badges.tsx +28 -0
  201. package/template/modules/checkout/src/admin/index.tsx +236 -0
  202. package/template/modules/checkout/src/public/CheckoutPage.tsx +461 -0
  203. package/template/modules/checkout/src/public/OrderPages.tsx +153 -0
  204. package/template/modules/checkout/src/public/index.tsx +23 -0
  205. package/template/modules/checkout/src/server.ts +383 -0
  206. package/template/modules/checkout/src/types.ts +70 -0
  207. package/template/modules/customers/screenshot.jpg +0 -0
  208. package/template/modules/customers/snapify.module.json +115 -0
  209. package/template/modules/customers/src/admin/CustomerDetailPage.tsx +274 -0
  210. package/template/modules/customers/src/admin/index.tsx +194 -0
  211. package/template/modules/customers/src/public/AccountPages.tsx +470 -0
  212. package/template/modules/customers/src/public/AddressForm.tsx +115 -0
  213. package/template/modules/customers/src/public/AuthPages.tsx +349 -0
  214. package/template/modules/customers/src/public/Avatar.tsx +29 -0
  215. package/template/modules/customers/src/public/ProfileSection.tsx +291 -0
  216. package/template/modules/customers/src/public/index.tsx +137 -0
  217. package/template/modules/customers/src/public/session.ts +51 -0
  218. package/template/modules/customers/src/server.ts +421 -0
  219. package/template/modules/customers/src/types.ts +53 -0
  220. package/template/modules/recaptcha/logo.jpg +0 -0
  221. package/template/modules/recaptcha/screenshot.jpg +0 -0
  222. package/template/modules/recaptcha/snapify.module.json +99 -0
  223. package/template/modules/recaptcha/src/public/index.tsx +94 -0
  224. package/template/modules/recaptcha/src/server.ts +65 -0
  225. package/template/modules/recaptcha/src/types.ts +18 -0
  226. package/template/modules/shop/screenshot.jpg +0 -0
  227. package/template/modules/shop/snapify.module.json +340 -0
  228. package/template/modules/shop/src/admin/CategoriesPage.tsx +224 -0
  229. package/template/modules/shop/src/admin/ProductEditor.tsx +313 -0
  230. package/template/modules/shop/src/admin/ProductsPage.tsx +307 -0
  231. package/template/modules/shop/src/admin/index.tsx +14 -0
  232. package/template/modules/shop/src/public/Catalog.tsx +244 -0
  233. package/template/modules/shop/src/public/NotFound.tsx +22 -0
  234. package/template/modules/shop/src/public/ProductPage.tsx +195 -0
  235. package/template/modules/shop/src/public/index.tsx +134 -0
  236. package/template/modules/shop/src/public/useFetch.ts +26 -0
  237. package/template/modules/shop/src/server.ts +493 -0
  238. package/template/modules/shop/src/shared.ts +41 -0
  239. package/template/modules/shop/src/types.ts +60 -0
  240. package/template/modules/whatsapp/screenshot.jpg +0 -0
  241. package/template/modules/whatsapp/snapify.module.json +121 -0
  242. package/template/modules/whatsapp/src/public.tsx +119 -0
  243. package/template/modules/wp-seo-migrator/ARCHITECTURE.md +113 -0
  244. package/template/modules/wp-seo-migrator/README.md +21 -0
  245. package/template/modules/wp-seo-migrator/fixtures/aioseo-sample.csv +3 -0
  246. package/template/modules/wp-seo-migrator/fixtures/sample-exporter.json +39 -0
  247. package/template/modules/wp-seo-migrator/fixtures/sample-page.html +12 -0
  248. package/template/modules/wp-seo-migrator/fixtures/sample.htaccess +3 -0
  249. package/template/modules/wp-seo-migrator/screenshot.jpg +0 -0
  250. package/template/modules/wp-seo-migrator/snapify.module.json +87 -0
  251. package/template/modules/wp-seo-migrator/src/admin/HistoryPage.tsx +64 -0
  252. package/template/modules/wp-seo-migrator/src/admin/WizardPage.tsx +788 -0
  253. package/template/modules/wp-seo-migrator/src/admin/i18n.ts +156 -0
  254. package/template/modules/wp-seo-migrator/src/admin/index.tsx +11 -0
  255. package/template/modules/wp-seo-migrator/src/connectors/CUSTOM_CONNECTOR.md +31 -0
  256. package/template/modules/wp-seo-migrator/src/connectors/csvSeoFactory.ts +106 -0
  257. package/template/modules/wp-seo-migrator/src/connectors/exporter.ts +96 -0
  258. package/template/modules/wp-seo-migrator/src/connectors/gscCsv.ts +53 -0
  259. package/template/modules/wp-seo-migrator/src/connectors/htaccess.ts +65 -0
  260. package/template/modules/wp-seo-migrator/src/connectors/htmlCrawler.ts +90 -0
  261. package/template/modules/wp-seo-migrator/src/connectors/index.ts +113 -0
  262. package/template/modules/wp-seo-migrator/src/connectors/pluginCsv.ts +77 -0
  263. package/template/modules/wp-seo-migrator/src/connectors/rankMathCsv.ts +77 -0
  264. package/template/modules/wp-seo-migrator/src/connectors/redirection.ts +62 -0
  265. package/template/modules/wp-seo-migrator/src/connectors/sitemap.ts +44 -0
  266. package/template/modules/wp-seo-migrator/src/connectors/utils.ts +111 -0
  267. package/template/modules/wp-seo-migrator/src/connectors/wxr.ts +103 -0
  268. package/template/modules/wp-seo-migrator/src/connectors/yoastCsv.ts +56 -0
  269. package/template/modules/wp-seo-migrator/src/pipeline/analyze.ts +158 -0
  270. package/template/modules/wp-seo-migrator/src/pipeline/htmlParse.ts +58 -0
  271. package/template/modules/wp-seo-migrator/src/pipeline/remoteAuto.ts +328 -0
  272. package/template/modules/wp-seo-migrator/src/server.ts +492 -0
  273. package/template/modules/wp-seo-migrator/src/types.ts +151 -0
  274. package/template/modules/wp-seo-migrator/tests/connectors-extra.test.ts +39 -0
  275. package/template/modules/wp-seo-migrator/tests/merge.test.ts +25 -0
  276. package/template/package-lock.json +6016 -0
  277. package/template/package.json +74 -0
  278. package/template/public/images/photos/caravane-chameaux-desert.webp +0 -0
  279. package/template/public/images/photos/hammamet-medina-plage.jpg +0 -0
  280. package/template/public/images/photos/maison-campagne-collines.webp +0 -0
  281. package/template/public/images/photos/maisons-troglodytes-desert.jpg +0 -0
  282. package/template/public/images/photos/panorama-ville.jpg +0 -0
  283. package/template/public/images/photos/preveris-accessibilite-erp.jpg +0 -0
  284. package/template/public/images/photos/preveris-collectivites.jpg +0 -0
  285. package/template/public/images/photos/preveris-etudes-plans.jpg +0 -0
  286. package/template/public/images/photos/preveris-hero-erp.jpg +0 -0
  287. package/template/public/images/photos/preveris-ssi-incendie.jpg +0 -0
  288. package/template/public/images/photos/preveris-suivi-hotel.jpg +0 -0
  289. package/template/public/images/photos/ruelle-bleue-medina.jpg +0 -0
  290. package/template/public/images/photos/sidi-bou-said-port.webp +0 -0
  291. package/template/public/images/photos/terrasse-blanche-mer-voilier.jpg +0 -0
  292. package/template/public/landing/hero-ai-cms.jpg +0 -0
  293. package/template/public/landing/shot-dogga.jpg +0 -0
  294. package/template/public/landing/shot-mahdia.jpg +0 -0
  295. package/template/public/landing/shot-sidibou.jpg +0 -0
  296. package/template/public/llms.txt +35 -0
  297. package/template/public/logo.svg +10 -0
  298. package/template/scripts/app-pack.ts +155 -0
  299. package/template/scripts/app-publish.ts +100 -0
  300. package/template/scripts/apply-ecommerce-home.ts +196 -0
  301. package/template/scripts/auto-deploy-poll.sh +13 -0
  302. package/template/scripts/db-backup.sh +16 -0
  303. package/template/scripts/db-restore.sh +11 -0
  304. package/template/scripts/deploy.sh +34 -0
  305. package/template/scripts/github-webhook-deploy.sh +7 -0
  306. package/template/scripts/install-new-modules.ts +96 -0
  307. package/template/scripts/refresh-demo-baseline.ts +9 -0
  308. package/template/scripts/reinstall-marketplace-packs.ts +51 -0
  309. package/template/scripts/restore-classic-home.ts +13 -0
  310. package/template/scripts/sdk-pack.ts +289 -0
  311. package/template/scripts/sdk-publish.ts +75 -0
  312. package/template/scripts/seed-demo-products.ts +120 -0
  313. package/template/scripts/seed-theme-content-packs.ts +42 -0
  314. package/template/scripts/snapify-module.ts +207 -0
  315. package/template/scripts/test-theme-switch.ts +24 -0
  316. package/template/sdk/admin.css +18 -0
  317. package/template/sdk/admin.ts +95 -0
  318. package/template/sdk/build.ts +249 -0
  319. package/template/sdk/commerce.ts +512 -0
  320. package/template/sdk/content.ts +76 -0
  321. package/template/sdk/icons.ts +101 -0
  322. package/template/sdk/image.ts +60 -0
  323. package/template/sdk/index.ts +500 -0
  324. package/template/sdk/package.ts +298 -0
  325. package/template/sdk/recaptcha.tsx +122 -0
  326. package/template/sdk/settings.ts +84 -0
  327. package/template/sdk/theme.css +29 -0
  328. package/template/sdk/theme.ts +100 -0
  329. package/template/sdk/ui/commerce.tsx +385 -0
  330. package/template/sdk/ui/orders.tsx +136 -0
  331. package/template/sdk-kit/CHANGELOG.md +17 -0
  332. package/template/sdk-kit/LICENSE.md +47 -0
  333. package/template/sdk-kit/README.md +81 -0
  334. package/template/sdk-kit/cli.ts +339 -0
  335. package/template/sdk-kit/create-snapify/README.md +28 -0
  336. package/template/sdk-kit/create-snapify/create-snapify.mjs +11 -0
  337. package/template/sdk-kit/create-snapify/package.template.json +16 -0
  338. package/template/sdk-kit/package.template.json +95 -0
  339. package/template/sdk-kit/templates/common/README.md.tpl +27 -0
  340. package/template/sdk-kit/templates/common/_gitignore +3 -0
  341. package/template/sdk-kit/templates/common/package.json.tpl +19 -0
  342. package/template/sdk-kit/templates/common/src/snapify-env.d.ts +2 -0
  343. package/template/sdk-kit/templates/common/tsconfig.json +15 -0
  344. package/template/sdk-kit/templates/module/snapify.module.json.tpl +33 -0
  345. package/template/sdk-kit/templates/module/src/admin.tsx +53 -0
  346. package/template/sdk-kit/templates/module/src/public.tsx +49 -0
  347. package/template/sdk-kit/templates/module/src/server.ts +30 -0
  348. package/template/sdk-kit/templates/module/src/types.ts +19 -0
  349. package/template/sdk-kit/templates/theme/snapify.theme.json.tpl +22 -0
  350. package/template/sdk-kit/templates/theme/src/Footer.tsx +40 -0
  351. package/template/sdk-kit/templates/theme/src/Header.tsx +47 -0
  352. package/template/sdk-kit/templates/theme/src/Views.tsx +36 -0
  353. package/template/sdk-kit/templates/theme/src/blocks.tsx +93 -0
  354. package/template/sdk-kit/templates/theme/src/index.tsx +30 -0
  355. package/template/sdk-kit/templates/theme/src/site.tsx +59 -0
  356. package/template/sdk-kit/templates/theme/src/styles.css +71 -0
  357. package/template/server/client/routes.ts +252 -0
  358. package/template/server/cms/analyticsStore.ts +180 -0
  359. package/template/server/cms/auth.ts +155 -0
  360. package/template/server/cms/blockCatalog.ts +342 -0
  361. package/template/server/cms/blocks.ts +521 -0
  362. package/template/server/cms/contentMigration.ts +1119 -0
  363. package/template/server/cms/defaults.ts +303 -0
  364. package/template/server/cms/permissions.ts +154 -0
  365. package/template/server/cms/restoreClassicHome.ts +183 -0
  366. package/template/server/cms/revisions.ts +99 -0
  367. package/template/server/cms/scheduler.ts +78 -0
  368. package/template/server/cms/seed.ts +72 -0
  369. package/template/server/cms/seoAnalyzer.ts +213 -0
  370. package/template/server/cms/store.ts +122 -0
  371. package/template/server/cms/themeContent.ts +1386 -0
  372. package/template/server/cms/themePacks/sidibou-branding.json +1 -0
  373. package/template/server/cms/themePacks/sidibou-home.json +1 -0
  374. package/template/server/cms/themePacks/sidibou-menus.json +1 -0
  375. package/template/server/cms/types.ts +432 -0
  376. package/template/server/community/routes.ts +410 -0
  377. package/template/server/db/client.ts +43 -0
  378. package/template/server/db/repository.ts +389 -0
  379. package/template/server/db/schema.ts +726 -0
  380. package/template/server/demo/cmsReset.ts +196 -0
  381. package/template/server/developers/sdk.ts +85 -0
  382. package/template/server/geoip.ts +15 -0
  383. package/template/server/http.ts +53 -0
  384. package/template/server/mail.ts +216 -0
  385. package/template/server/marketplace/catalog.ts +74 -0
  386. package/template/server/marketplace/packageValidate.ts +475 -0
  387. package/template/server/marketplace/payments.ts +193 -0
  388. package/template/server/marketplace/routes.ts +850 -0
  389. package/template/server/marketplace/signing.ts +116 -0
  390. package/template/server/marketplace/syncOfficial.ts +282 -0
  391. package/template/server/media/images.ts +63 -0
  392. package/template/server/middleware/adminAuth.ts +134 -0
  393. package/template/server/modules/legacy.ts +44 -0
  394. package/template/server/modules/manager.ts +1122 -0
  395. package/template/server/modules/marketplace.ts +178 -0
  396. package/template/server/modules/outbound.ts +178 -0
  397. package/template/server/modules/purchases.ts +149 -0
  398. package/template/server/modules/records.ts +168 -0
  399. package/template/server/modules/themeHtml.ts +32 -0
  400. package/template/server/modules/trust.ts +16 -0
  401. package/template/server/modules/types.ts +147 -0
  402. package/template/server/platform/auth.ts +223 -0
  403. package/template/server/platform/clientWelcome.ts +168 -0
  404. package/template/server/platform/clients.ts +139 -0
  405. package/template/server/platform/dailyImprove.ts +356 -0
  406. package/template/server/platform/flouci.ts +126 -0
  407. package/template/server/platform/host.ts +69 -0
  408. package/template/server/platform/hostingProviders.ts +143 -0
  409. package/template/server/platform/licenseOrders.ts +291 -0
  410. package/template/server/platform/money.ts +42 -0
  411. package/template/server/platform/passwords.ts +41 -0
  412. package/template/server/platform/region.ts +86 -0
  413. package/template/server/platform/routes.ts +225 -0
  414. package/template/server/platform/seed.ts +82 -0
  415. package/template/server/platform/settings.ts +72 -0
  416. package/template/server/routes/adminAccount.ts +403 -0
  417. package/template/server/routes/adminCms.ts +808 -0
  418. package/template/server/routes/adminSystem.ts +243 -0
  419. package/template/server/routes/adminUsers.ts +449 -0
  420. package/template/server/routes/googleOAuth.ts +327 -0
  421. package/template/server/routes/modules.ts +323 -0
  422. package/template/server/routes/publicCms.ts +313 -0
  423. package/template/server/routes/seoPublic.ts +158 -0
  424. package/template/server/security/accounts.ts +121 -0
  425. package/template/server/security/audit.ts +143 -0
  426. package/template/server/security/authTokens.ts +74 -0
  427. package/template/server/security/ip.ts +68 -0
  428. package/template/server/security/rateLimit.ts +85 -0
  429. package/template/server/security/retention.ts +46 -0
  430. package/template/server/security/secrets.ts +44 -0
  431. package/template/server/security/sessions.ts +140 -0
  432. package/template/server/security/totp.ts +101 -0
  433. package/template/server/superadmin/analytics.ts +109 -0
  434. package/template/server/superadmin/auth.ts +92 -0
  435. package/template/server/superadmin/backlinksCatalog.ts +210 -0
  436. package/template/server/superadmin/frSeoContent.ts +165 -0
  437. package/template/server/superadmin/landingSeo.ts +675 -0
  438. package/template/server/superadmin/ops.ts +238 -0
  439. package/template/server/superadmin/routes.ts +1095 -0
  440. package/template/server/superadmin/tnSeoContent.ts +130 -0
  441. package/template/server.ts +522 -0
  442. package/template/src/App.tsx +263 -0
  443. package/template/src/admin/AdminApp.tsx +162 -0
  444. package/template/src/admin/account/AuthLayout.tsx +57 -0
  445. package/template/src/admin/account/SecurityForms.tsx +276 -0
  446. package/template/src/admin/admin.css +127 -0
  447. package/template/src/admin/context/AuthContext.tsx +147 -0
  448. package/template/src/admin/context/SiteContext.tsx +50 -0
  449. package/template/src/admin/editor/BlockLibrary.tsx +173 -0
  450. package/template/src/admin/editor/BlocksPanel.tsx +396 -0
  451. package/template/src/admin/editor/EditorBits.tsx +106 -0
  452. package/template/src/admin/editor/PreviewFrame.tsx +145 -0
  453. package/template/src/admin/editor/RichTextEditor.tsx +276 -0
  454. package/template/src/admin/editor/SeoPanel.tsx +349 -0
  455. package/template/src/admin/editor/StyleEditor.tsx +114 -0
  456. package/template/src/admin/editor/blockSchemas.ts +699 -0
  457. package/template/src/admin/editor/fields.tsx +486 -0
  458. package/template/src/admin/layout/AdminShell.tsx +418 -0
  459. package/template/src/admin/layout/nav.ts +95 -0
  460. package/template/src/admin/lib/api.ts +408 -0
  461. package/template/src/admin/lib/audit.ts +141 -0
  462. package/template/src/admin/lib/format.ts +84 -0
  463. package/template/src/admin/lib/hooks.ts +125 -0
  464. package/template/src/admin/main.tsx +16 -0
  465. package/template/src/admin/modules/InstallDialog.tsx +160 -0
  466. package/template/src/admin/modules/SettingsForm.tsx +145 -0
  467. package/template/src/admin/modules/kit.tsx +127 -0
  468. package/template/src/admin/modules/labels.tsx +226 -0
  469. package/template/src/admin/pages/AccountSetupPage.tsx +62 -0
  470. package/template/src/admin/pages/AnalyticsPage.tsx +206 -0
  471. package/template/src/admin/pages/BrandingPage.tsx +154 -0
  472. package/template/src/admin/pages/DashboardPage.tsx +239 -0
  473. package/template/src/admin/pages/EngagementPage.tsx +168 -0
  474. package/template/src/admin/pages/FormsPage.tsx +435 -0
  475. package/template/src/admin/pages/GeneralPage.tsx +120 -0
  476. package/template/src/admin/pages/LoginPage.tsx +249 -0
  477. package/template/src/admin/pages/MaintenancePage.tsx +125 -0
  478. package/template/src/admin/pages/MarketplacePage.tsx +426 -0
  479. package/template/src/admin/pages/MediaPage.tsx +251 -0
  480. package/template/src/admin/pages/MenusPage.tsx +446 -0
  481. package/template/src/admin/pages/ModuleAdminPage.tsx +113 -0
  482. package/template/src/admin/pages/ModuleDetailPage.tsx +293 -0
  483. package/template/src/admin/pages/ModulesPage.tsx +135 -0
  484. package/template/src/admin/pages/NewsletterPage.tsx +193 -0
  485. package/template/src/admin/pages/PageEditorPage.tsx +322 -0
  486. package/template/src/admin/pages/PagesListPage.tsx +277 -0
  487. package/template/src/admin/pages/PostEditorPage.tsx +292 -0
  488. package/template/src/admin/pages/PostsListPage.tsx +187 -0
  489. package/template/src/admin/pages/ProfilePage.tsx +489 -0
  490. package/template/src/admin/pages/ResetPasswordPage.tsx +110 -0
  491. package/template/src/admin/pages/SeoPage.tsx +670 -0
  492. package/template/src/admin/pages/SettingsPage.tsx +189 -0
  493. package/template/src/admin/pages/ThemeCustomizePage.tsx +170 -0
  494. package/template/src/admin/pages/ThemesPage.tsx +292 -0
  495. package/template/src/admin/pages/UsersPage.tsx +57 -0
  496. package/template/src/admin/pages/WidgetsPage.tsx +200 -0
  497. package/template/src/admin/seo/GoogleSetup.tsx +529 -0
  498. package/template/src/admin/settings/EmailTab.tsx +293 -0
  499. package/template/src/admin/settings/OtherTabs.tsx +270 -0
  500. package/template/src/admin/settings/SecurityTab.tsx +172 -0
  501. package/template/src/admin/settings/controls.tsx +133 -0
  502. package/template/src/admin/ui/Pagination.tsx +170 -0
  503. package/template/src/admin/ui/SaveBar.tsx +77 -0
  504. package/template/src/admin/ui/TagInput.tsx +54 -0
  505. package/template/src/admin/ui/charts.tsx +108 -0
  506. package/template/src/admin/ui/media.tsx +265 -0
  507. package/template/src/admin/ui/overlays.tsx +239 -0
  508. package/template/src/admin/ui/primitives.tsx +544 -0
  509. package/template/src/admin/users/AuditTab.tsx +104 -0
  510. package/template/src/admin/users/RolesTab.tsx +368 -0
  511. package/template/src/admin/users/UsersTab.tsx +723 -0
  512. package/template/src/admin/users/shared.tsx +278 -0
  513. package/template/src/cms/icons.ts +7 -0
  514. package/template/src/components/CookieBanner.tsx +166 -0
  515. package/template/src/components/MaintenanceGate.tsx +66 -0
  516. package/template/src/components/SiteAnalytics.tsx +64 -0
  517. package/template/src/components/SnapifyLogo.tsx +44 -0
  518. package/template/src/components/cms/BlockRenderer.tsx +154 -0
  519. package/template/src/components/cms/WidgetArea.tsx +193 -0
  520. package/template/src/context/SiteConfigContext.tsx +130 -0
  521. package/template/src/demoBase.ts +23 -0
  522. package/template/src/index.css +90 -0
  523. package/template/src/main.tsx +16 -0
  524. package/template/src/modules/ModuleHost.tsx +197 -0
  525. package/template/src/preview.ts +63 -0
  526. package/template/src/runtime.ts +38 -0
  527. package/template/src/themes/ThemeHost.tsx +168 -0
  528. package/template/src/themes/fallback.tsx +71 -0
  529. package/template/src/types.ts +94 -0
  530. package/template/src/views/BlogPostView.tsx +68 -0
  531. package/template/src/views/CmsPageView.tsx +34 -0
  532. package/template/start-pm2.sh +3 -0
  533. package/template/superadmin.html +20 -0
  534. package/template/themes/ai-canvas/screenshot.jpg +0 -0
  535. package/template/themes/ai-canvas/snapify.theme.json +96 -0
  536. package/template/themes/ai-canvas/src/CeeSimulator.tsx +247 -0
  537. package/template/themes/ai-canvas/src/Footer.tsx +190 -0
  538. package/template/themes/ai-canvas/src/Header.tsx +324 -0
  539. package/template/themes/ai-canvas/src/Layout.tsx +56 -0
  540. package/template/themes/ai-canvas/src/NotFound.tsx +58 -0
  541. package/template/themes/ai-canvas/src/Post.tsx +26 -0
  542. package/template/themes/ai-canvas/src/SnapifyLogo.tsx +44 -0
  543. package/template/themes/ai-canvas/src/blocks/CollectionBlocks.tsx +711 -0
  544. package/template/themes/ai-canvas/src/blocks/ContentBlocks.tsx +318 -0
  545. package/template/themes/ai-canvas/src/blocks/ConversionBlocks.tsx +559 -0
  546. package/template/themes/ai-canvas/src/blocks/HeaderBlocks.tsx +341 -0
  547. package/template/themes/ai-canvas/src/index.tsx +54 -0
  548. package/template/themes/ai-canvas/src/kit.tsx +398 -0
  549. package/template/themes/ai-canvas/src/site.ts +29 -0
  550. package/template/themes/ai-canvas/src/styles.css +317 -0
  551. package/template/themes/dogga/screenshot.jpg +0 -0
  552. package/template/themes/dogga/snapify.theme.json +55 -0
  553. package/template/themes/dogga/src/Brand.tsx +41 -0
  554. package/template/themes/dogga/src/Footer.tsx +202 -0
  555. package/template/themes/dogga/src/Header.tsx +239 -0
  556. package/template/themes/dogga/src/Layout.tsx +43 -0
  557. package/template/themes/dogga/src/Views.tsx +71 -0
  558. package/template/themes/dogga/src/blocks/CollectionBlocks.tsx +674 -0
  559. package/template/themes/dogga/src/blocks/ContentBlocks.tsx +307 -0
  560. package/template/themes/dogga/src/blocks/ConversionBlocks.tsx +571 -0
  561. package/template/themes/dogga/src/blocks/HeaderBlocks.tsx +289 -0
  562. package/template/themes/dogga/src/index.tsx +53 -0
  563. package/template/themes/dogga/src/kit.tsx +362 -0
  564. package/template/themes/dogga/src/site.ts +56 -0
  565. package/template/themes/dogga/src/styles.css +396 -0
  566. package/template/themes/mahdia/screenshot.jpg +0 -0
  567. package/template/themes/mahdia/snapify.theme.json +65 -0
  568. package/template/themes/mahdia/src/Brand.tsx +39 -0
  569. package/template/themes/mahdia/src/Footer.tsx +210 -0
  570. package/template/themes/mahdia/src/Header.tsx +291 -0
  571. package/template/themes/mahdia/src/Layout.tsx +44 -0
  572. package/template/themes/mahdia/src/Views.tsx +79 -0
  573. package/template/themes/mahdia/src/blocks/CollectionBlocks.tsx +674 -0
  574. package/template/themes/mahdia/src/blocks/ContentBlocks.tsx +307 -0
  575. package/template/themes/mahdia/src/blocks/ConversionBlocks.tsx +571 -0
  576. package/template/themes/mahdia/src/blocks/HeaderBlocks.tsx +289 -0
  577. package/template/themes/mahdia/src/index.tsx +53 -0
  578. package/template/themes/mahdia/src/kit.tsx +362 -0
  579. package/template/themes/mahdia/src/site.ts +75 -0
  580. package/template/themes/mahdia/src/styles.css +446 -0
  581. package/template/themes/preveris/screenshot.jpg +0 -0
  582. package/template/themes/preveris/snapify.theme.json +126 -0
  583. package/template/themes/preveris/src/Brand.tsx +44 -0
  584. package/template/themes/preveris/src/Footer.tsx +202 -0
  585. package/template/themes/preveris/src/Header.tsx +239 -0
  586. package/template/themes/preveris/src/Layout.tsx +42 -0
  587. package/template/themes/preveris/src/Views.tsx +71 -0
  588. package/template/themes/preveris/src/blocks/CollectionBlocks.tsx +678 -0
  589. package/template/themes/preveris/src/blocks/ContentBlocks.tsx +307 -0
  590. package/template/themes/preveris/src/blocks/ConversionBlocks.tsx +571 -0
  591. package/template/themes/preveris/src/blocks/HeaderBlocks.tsx +299 -0
  592. package/template/themes/preveris/src/index.tsx +53 -0
  593. package/template/themes/preveris/src/kit.tsx +365 -0
  594. package/template/themes/preveris/src/site.ts +79 -0
  595. package/template/themes/preveris/src/styles.css +446 -0
  596. package/template/themes/sidibou/screenshot.jpg +0 -0
  597. package/template/themes/sidibou/snapify.theme.json +21 -0
  598. package/template/themes/sidibou/src/CeeSimulator.tsx +247 -0
  599. package/template/themes/sidibou/src/Footer.tsx +203 -0
  600. package/template/themes/sidibou/src/Header.tsx +324 -0
  601. package/template/themes/sidibou/src/Layout.tsx +53 -0
  602. package/template/themes/sidibou/src/NotFound.tsx +58 -0
  603. package/template/themes/sidibou/src/Post.tsx +26 -0
  604. package/template/themes/sidibou/src/SnapifyLogo.tsx +44 -0
  605. package/template/themes/sidibou/src/blocks/CollectionBlocks.tsx +711 -0
  606. package/template/themes/sidibou/src/blocks/ContentBlocks.tsx +318 -0
  607. package/template/themes/sidibou/src/blocks/ConversionBlocks.tsx +559 -0
  608. package/template/themes/sidibou/src/blocks/HeaderBlocks.tsx +341 -0
  609. package/template/themes/sidibou/src/index.tsx +54 -0
  610. package/template/themes/sidibou/src/kit.tsx +398 -0
  611. package/template/themes/sidibou/src/site.ts +28 -0
  612. package/template/themes/sidibou/src/styles.css +317 -0
  613. package/template/tsconfig.json +33 -0
  614. package/template/vite.config.ts +42 -0
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Snapify
3
+ * © 2026 NOVALABS WEB DESIGN — Tunisie
4
+ * Contact : contact@novalabs.tn · +216 24 734 647
5
+ * Tous droits réservés. Toute reproduction, modification ou diffusion sans autorisation écrite est interdite.
6
+ */
7
+ /**
8
+ * Éléments d'interface communs aux modules e-commerce (`@snapify/sdk/ui/commerce`) : cartes produit,
9
+ * prix, champs de formulaire… Couleurs et polices viennent du site (jetons `px-*`), si bien que
10
+ * boutique, panier, commande et espace client gardent l'identité du thème actif.
11
+ */
12
+ import React, { forwardRef, useEffect, useState } from 'react';
13
+ import { Check, Heart, Loader2, Minus, Plus, ShoppingBag } from 'lucide-react';
14
+ import { isModuleActive } from '../index';
15
+ import { cart, formatMoney, MODULE_IDS, COMMERCE_PATHS, MAX_QTY, wishlist, useWishlist, type ProductCard } from '../commerce';
16
+
17
+ export const cn = (...c: (string | false | null | undefined)[]) => c.filter(Boolean).join(' ');
18
+
19
+ export function Container({ children, className }: { children: React.ReactNode; className?: string }) {
20
+ return <div className={cn('mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8', className)}>{children}</div>;
21
+ }
22
+
23
+ /** Lien interne : navigation sans rechargement, clic molette / Ctrl conservés. */
24
+ export function Link({
25
+ to,
26
+ navigate,
27
+ className,
28
+ children,
29
+ onClick,
30
+ ...rest
31
+ }: { to: string; navigate: (p: string) => void } & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>) {
32
+ return (
33
+ <a
34
+ href={to}
35
+ className={className}
36
+ onClick={(e) => {
37
+ onClick?.(e);
38
+ if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
39
+ e.preventDefault();
40
+ navigate(to);
41
+ }}
42
+ {...rest}
43
+ >
44
+ {children}
45
+ </a>
46
+ );
47
+ }
48
+
49
+ type Variant = 'primary' | 'dark' | 'outline' | 'ghost' | 'danger';
50
+ const VARIANTS: Record<Variant, string> = {
51
+ primary: 'bg-px-primary text-white hover:bg-px-primary-dark shadow-sm',
52
+ dark: 'bg-px-navy text-white hover:bg-px-navy-2 shadow-sm',
53
+ outline: 'border border-px-border bg-white text-px-navy hover:border-px-navy/40 hover:bg-px-surface',
54
+ ghost: 'text-px-navy hover:bg-px-surface',
55
+ danger: 'bg-rose-600 text-white hover:bg-rose-700',
56
+ };
57
+ const SIZES = { sm: 'h-9 px-3.5 text-sm gap-1.5', md: 'h-11 px-5 text-sm gap-2', lg: 'h-12 px-6 text-[15px] gap-2' } as const;
58
+
59
+ export const Button = forwardRef<
60
+ HTMLButtonElement,
61
+ React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: Variant; size?: keyof typeof SIZES; loading?: boolean; block?: boolean }
62
+ >(function Button({ variant = 'primary', size = 'md', loading, block, className, children, disabled, type = 'button', ...rest }, ref) {
63
+ return (
64
+ <button
65
+ ref={ref}
66
+ type={type}
67
+ disabled={disabled || loading}
68
+ className={cn(
69
+ 'inline-flex shrink-0 items-center justify-center rounded-xl font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-px-primary/40 disabled:cursor-not-allowed disabled:opacity-50',
70
+ VARIANTS[variant],
71
+ SIZES[size],
72
+ block && 'w-full',
73
+ className
74
+ )}
75
+ {...rest}
76
+ >
77
+ {loading ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
78
+ {children}
79
+ </button>
80
+ );
81
+ });
82
+
83
+ export const discountPercent = (price: number, compareAt: number | null | undefined) =>
84
+ compareAt && compareAt > price ? Math.round(((compareAt - price) / compareAt) * 100) : 0;
85
+
86
+ export function Price({ amount, compareAt, currency, className, size = 'md' }: { amount: number; compareAt?: number | null; currency?: string; className?: string; size?: 'sm' | 'md' | 'lg' }) {
87
+ const off = discountPercent(amount, compareAt);
88
+ return (
89
+ <span className={cn('inline-flex flex-wrap items-baseline gap-x-2', className)}>
90
+ <span className={cn('font-semibold tabular-nums text-px-navy', size === 'lg' ? 'text-2xl sm:text-3xl' : size === 'sm' ? 'text-sm' : 'text-base')}>{formatMoney(amount, currency)}</span>
91
+ {off ? <span className={cn('tabular-nums text-px-muted line-through', size === 'lg' ? 'text-base' : 'text-xs')}>{formatMoney(compareAt!, currency)}</span> : null}
92
+ </span>
93
+ );
94
+ }
95
+
96
+ export function QtyStepper({ value, onChange, max = MAX_QTY, size = 'md' }: { value: number; onChange: (n: number) => void; max?: number; size?: 'sm' | 'md' }) {
97
+ const h = size === 'sm' ? 'h-8' : 'h-11';
98
+ const w = size === 'sm' ? 'w-8' : 'w-10';
99
+ return (
100
+ <div className={cn('inline-flex items-stretch overflow-hidden rounded-xl border border-px-border bg-white', h)}>
101
+ <button type="button" aria-label="Diminuer" onClick={() => onChange(Math.max(1, value - 1))} disabled={value <= 1} className={cn(w, 'flex items-center justify-center text-px-navy transition hover:bg-px-surface disabled:opacity-30')}>
102
+ <Minus className="h-3.5 w-3.5" />
103
+ </button>
104
+ <input
105
+ aria-label="Quantité"
106
+ inputMode="numeric"
107
+ value={value}
108
+ onChange={(e) => {
109
+ const n = Number(e.target.value.replace(/\D/g, ''));
110
+ if (n > 0) onChange(Math.min(max, n));
111
+ }}
112
+ className={cn('w-10 border-x border-px-border bg-transparent text-center text-sm font-semibold tabular-nums text-px-navy outline-none', size === 'sm' && 'w-9 text-[13px]')}
113
+ />
114
+ <button type="button" aria-label="Augmenter" onClick={() => onChange(Math.min(max, value + 1))} disabled={value >= max} className={cn(w, 'flex items-center justify-center text-px-navy transition hover:bg-px-surface disabled:opacity-30')}>
115
+ <Plus className="h-3.5 w-3.5" />
116
+ </button>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ export function Spinner({ className }: { className?: string }) {
122
+ return <Loader2 className={cn('h-5 w-5 animate-spin text-px-muted', className)} />;
123
+ }
124
+
125
+ export function WishlistButton({ productId, className, withLabel }: { productId: string; className?: string; withLabel?: boolean }) {
126
+ const ids = useWishlist();
127
+ const on = ids.includes(productId);
128
+ return (
129
+ <button
130
+ type="button"
131
+ aria-pressed={on}
132
+ aria-label={on ? 'Retirer des favoris' : 'Ajouter aux favoris'}
133
+ onClick={(e) => {
134
+ e.preventDefault();
135
+ e.stopPropagation();
136
+ wishlist.toggle(productId);
137
+ }}
138
+ className={cn(
139
+ 'inline-flex items-center justify-center gap-2 transition',
140
+ withLabel ? 'h-11 rounded-xl border border-px-border bg-white px-4 text-sm font-semibold text-px-navy hover:border-px-navy/40' : 'h-9 w-9 rounded-full bg-white/95 shadow-sm ring-1 ring-black/5 hover:scale-105',
141
+ className
142
+ )}
143
+ >
144
+ <Heart className={cn('h-4 w-4', on ? 'fill-rose-500 text-rose-500' : 'text-px-navy')} />
145
+ {withLabel ? (on ? 'Dans vos favoris' : 'Ajouter aux favoris') : null}
146
+ </button>
147
+ );
148
+ }
149
+
150
+ /** Ajout au panier depuis une carte produit, avec confirmation visuelle. */
151
+ export function AddToCartButton({ p, qty = 1, size = 'md', block, label = 'Ajouter au panier' }: { p: ProductCard; qty?: number; size?: 'sm' | 'md' | 'lg'; block?: boolean; label?: string }) {
152
+ const [done, setDone] = useState(false);
153
+ useEffect(() => {
154
+ if (!done) return;
155
+ const t = setTimeout(() => setDone(false), 1600);
156
+ return () => clearTimeout(t);
157
+ }, [done]);
158
+ if (!p.inStock) {
159
+ return (
160
+ <Button variant="outline" size={size} block={block} disabled>
161
+ Épuisé
162
+ </Button>
163
+ );
164
+ }
165
+ return (
166
+ <Button
167
+ size={size}
168
+ block={block}
169
+ onClick={(e) => {
170
+ e.preventDefault();
171
+ e.stopPropagation();
172
+ cart.add({ productId: p.id, name: p.name, slug: p.slug, price: p.price, image: p.image, currency: p.currency, qty });
173
+ setDone(true);
174
+ }}
175
+ >
176
+ {done ? <Check className="h-4 w-4" /> : <ShoppingBag className="h-4 w-4" />}
177
+ {done ? 'Ajouté' : label}
178
+ </Button>
179
+ );
180
+ }
181
+
182
+ export function ProductTile({ p, navigate, showAdd = true }: { p: ProductCard; navigate: (path: string) => void; showAdd?: boolean }) {
183
+ const off = discountPercent(p.price, p.compareAtPrice);
184
+ const canBuy = showAdd && isModuleActive(MODULE_IDS.cart);
185
+ const second = p.images?.[1];
186
+ return (
187
+ <article className="group relative flex flex-col">
188
+ <div className="relative aspect-[4/5] overflow-hidden rounded-2xl bg-px-surface">
189
+ <Link to={COMMERCE_PATHS.product(p.slug)} navigate={navigate} className="block h-full w-full" aria-label={p.name}>
190
+ {p.image ? (
191
+ <>
192
+ <img src={p.image} alt={p.name} loading="lazy" className={cn('h-full w-full object-cover transition duration-500 group-hover:scale-[1.04]', second && 'group-hover:opacity-0')} />
193
+ {second ? <img src={second} alt="" loading="lazy" className="absolute inset-0 h-full w-full object-cover opacity-0 transition duration-500 group-hover:opacity-100" /> : null}
194
+ </>
195
+ ) : (
196
+ <span className="flex h-full items-center justify-center text-px-muted">
197
+ <ShoppingBag className="h-10 w-10 opacity-40" />
198
+ </span>
199
+ )}
200
+ </Link>
201
+ <div className="pointer-events-none absolute left-3 top-3 flex flex-col items-start gap-1.5">
202
+ {p.badge ? <span className="rounded-full bg-px-navy px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wide text-white">{p.badge}</span> : null}
203
+ {off ? <span className="rounded-full bg-rose-500 px-2.5 py-1 text-[11px] font-semibold text-white">−{off} %</span> : null}
204
+ {!p.inStock ? <span className="rounded-full bg-white/95 px-2.5 py-1 text-[11px] font-semibold text-px-navy">Épuisé</span> : null}
205
+ </div>
206
+ <WishlistButton productId={p.id} className="absolute right-3 top-3" />
207
+ {canBuy && p.inStock ? (
208
+ <div className="absolute inset-x-3 bottom-3 translate-y-2 opacity-0 transition duration-300 group-hover:translate-y-0 group-hover:opacity-100 max-lg:hidden">
209
+ <AddToCartButton p={p} size="sm" block />
210
+ </div>
211
+ ) : null}
212
+ </div>
213
+ <div className="mt-3 flex flex-1 flex-col gap-1 px-0.5">
214
+ <Link to={COMMERCE_PATHS.product(p.slug)} navigate={navigate} className="line-clamp-2 text-[15px] font-medium leading-snug text-px-navy hover:text-px-primary">
215
+ {p.name}
216
+ </Link>
217
+ <Price amount={p.price} compareAt={p.compareAtPrice} currency={p.currency} />
218
+ {canBuy && p.inStock ? (
219
+ <div className="mt-2 lg:hidden">
220
+ <AddToCartButton p={p} size="sm" block label="Ajouter" />
221
+ </div>
222
+ ) : null}
223
+ </div>
224
+ </article>
225
+ );
226
+ }
227
+
228
+ export function ProductGrid({ items, navigate, columns = 4, showAdd }: { items: ProductCard[]; navigate: (p: string) => void; columns?: 2 | 3 | 4 | 5; showAdd?: boolean }) {
229
+ const cols = { 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-4', 5: 'lg:grid-cols-5' }[columns];
230
+ return (
231
+ <div className={cn('grid grid-cols-2 gap-x-4 gap-y-8 sm:gap-x-6 md:grid-cols-3', cols)}>
232
+ {items.map((p) => (
233
+ <ProductTile key={p.id} p={p} navigate={navigate} showAdd={showAdd} />
234
+ ))}
235
+ </div>
236
+ );
237
+ }
238
+
239
+ export function GridSkeleton({ count = 8, columns = 4 }: { count?: number; columns?: 2 | 3 | 4 | 5 }) {
240
+ const cols = { 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-4', 5: 'lg:grid-cols-5' }[columns];
241
+ return (
242
+ <div className={cn('grid grid-cols-2 gap-x-4 gap-y-8 sm:gap-x-6 md:grid-cols-3', cols)} aria-busy="true">
243
+ {Array.from({ length: count }, (_, i) => (
244
+ <div key={i}>
245
+ <div className="aspect-[4/5] animate-pulse rounded-2xl bg-px-surface" />
246
+ <div className="mt-3 h-4 w-3/4 animate-pulse rounded bg-px-surface" />
247
+ <div className="mt-2 h-4 w-1/3 animate-pulse rounded bg-px-surface" />
248
+ </div>
249
+ ))}
250
+ </div>
251
+ );
252
+ }
253
+
254
+ export function Breadcrumbs({ items, navigate }: { items: { label: string; to?: string }[]; navigate: (p: string) => void }) {
255
+ return (
256
+ <nav aria-label="Fil d’Ariane" className="flex flex-wrap items-center gap-1.5 text-[13px] text-px-muted">
257
+ {items.map((it, i) => (
258
+ <React.Fragment key={i}>
259
+ {i ? <span aria-hidden="true">/</span> : null}
260
+ {it.to ? (
261
+ <Link to={it.to} navigate={navigate} className="hover:text-px-navy">
262
+ {it.label}
263
+ </Link>
264
+ ) : (
265
+ <span className="text-px-navy">{it.label}</span>
266
+ )}
267
+ </React.Fragment>
268
+ ))}
269
+ </nav>
270
+ );
271
+ }
272
+
273
+ export function EmptyState({ icon: Icon, title, text, action }: { icon: React.ComponentType<{ className?: string }>; title: string; text?: string; action?: React.ReactNode }) {
274
+ return (
275
+ <div className="flex flex-col items-center px-6 py-16 text-center">
276
+ <div className="mb-5 flex h-16 w-16 items-center justify-center rounded-2xl bg-px-surface text-px-primary">
277
+ <Icon className="h-7 w-7" />
278
+ </div>
279
+ <p className="font-heading text-xl font-semibold text-px-navy">{title}</p>
280
+ {text ? <p className="mt-2 max-w-md text-sm leading-relaxed text-px-muted">{text}</p> : null}
281
+ {action ? <div className="mt-6">{action}</div> : null}
282
+ </div>
283
+ );
284
+ }
285
+
286
+ export function Notice({ tone = 'info', children, className }: { tone?: 'info' | 'success' | 'warning' | 'danger'; children: React.ReactNode; className?: string }) {
287
+ const cls = {
288
+ info: 'border-sky-200 bg-sky-50 text-sky-900',
289
+ success: 'border-emerald-200 bg-emerald-50 text-emerald-900',
290
+ warning: 'border-amber-200 bg-amber-50 text-amber-900',
291
+ danger: 'border-rose-200 bg-rose-50 text-rose-900',
292
+ }[tone];
293
+ return <div className={cn('rounded-xl border px-4 py-3 text-sm leading-relaxed', cls, className)}>{children}</div>;
294
+ }
295
+
296
+ const controlBaseCls =
297
+ 'rounded-xl border border-px-border bg-white px-3.5 text-[15px] text-px-navy placeholder:text-px-muted/70 transition focus:border-px-primary focus:outline-none focus:ring-3 focus:ring-px-primary/15';
298
+
299
+ /** Tailwind classe `w-full` après `w-auto` : une largeur passée en `className` ne s'appliquerait jamais. */
300
+ const controlClsFor = (className?: string) => (className && /(^|\s)w-/.test(className) ? controlBaseCls : `w-full ${controlBaseCls}`);
301
+
302
+ export function FormField({ label, error, hint, children, className, required }: { label?: string; error?: string; hint?: string; children: React.ReactNode; className?: string; required?: boolean }) {
303
+ return (
304
+ <label className={cn('block space-y-1.5', className)}>
305
+ {label ? (
306
+ <span className="block text-[13px] font-medium text-px-navy">
307
+ {label}
308
+ {required ? <span className="text-rose-500"> *</span> : null}
309
+ </span>
310
+ ) : null}
311
+ {children}
312
+ {error ? <span className="block text-xs text-rose-600">{error}</span> : hint ? <span className="block text-xs text-px-muted">{hint}</span> : null}
313
+ </label>
314
+ );
315
+ }
316
+
317
+ export const TextInput = forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement> & { invalid?: boolean }>(function TextInput({ className, invalid, ...rest }, ref) {
318
+ return <input ref={ref} className={cn(controlClsFor(className), 'h-11', invalid && 'border-rose-400', className)} {...rest} />;
319
+ });
320
+
321
+ export function TextArea({ className, ...rest }: React.TextareaHTMLAttributes<HTMLTextAreaElement>) {
322
+ return <textarea className={cn(controlClsFor(className), 'py-2.5 leading-relaxed', className)} rows={3} {...rest} />;
323
+ }
324
+
325
+ export function SelectInput({ value, onChange, options, className, invalid, placeholder }: { value: string; onChange: (v: string) => void; options: readonly (string | { value: string; label: string })[]; className?: string; invalid?: boolean; placeholder?: string }) {
326
+ return (
327
+ <select value={value} onChange={(e) => onChange(e.target.value)} className={cn(controlClsFor(className), 'h-11 appearance-none pr-9', invalid && 'border-rose-400', className)} style={{ backgroundImage: 'url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 20 20%27 fill=%27%2394a3b8%27%3E%3Cpath d=%27M5.5 7.5l4.5 4.5 4.5-4.5%27/%3E%3C/svg%3E")', backgroundSize: '18px', backgroundPosition: 'right 10px center', backgroundRepeat: 'no-repeat' }}>
328
+ {placeholder !== undefined ? <option value="">{placeholder}</option> : null}
329
+ {options.map((o) => {
330
+ const opt = typeof o === 'string' ? { value: o, label: o } : o;
331
+ return (
332
+ <option key={opt.value} value={opt.value}>
333
+ {opt.label}
334
+ </option>
335
+ );
336
+ })}
337
+ </select>
338
+ );
339
+ }
340
+
341
+ export function Checkbox({ checked, onChange, children }: { checked: boolean; onChange: (v: boolean) => void; children: React.ReactNode }) {
342
+ return (
343
+ <label className="flex cursor-pointer items-start gap-3 text-sm text-px-text">
344
+ <input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} className="mt-0.5 h-4 w-4 shrink-0 rounded border-px-border accent-[var(--px-primary)]" />
345
+ <span className="leading-relaxed">{children}</span>
346
+ </label>
347
+ );
348
+ }
349
+
350
+ /** Choix sous forme de cartes (mode de livraison, de paiement…). */
351
+ export function OptionCard({ checked, onSelect, title, description, aside, icon: Icon }: { checked: boolean; onSelect: () => void; title: string; description?: React.ReactNode; aside?: React.ReactNode; icon?: React.ComponentType<{ className?: string }> }) {
352
+ return (
353
+ <button
354
+ type="button"
355
+ role="radio"
356
+ aria-checked={checked}
357
+ onClick={onSelect}
358
+ className={cn('flex w-full items-start gap-3 rounded-2xl border p-4 text-left transition', checked ? 'border-px-primary bg-px-primary/5 ring-1 ring-px-primary' : 'border-px-border bg-white hover:border-px-navy/30')}
359
+ >
360
+ <span className={cn('mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2', checked ? 'border-px-primary' : 'border-px-border')}>
361
+ {checked ? <span className="h-2.5 w-2.5 rounded-full bg-px-primary" /> : null}
362
+ </span>
363
+ {Icon ? <Icon className="mt-0.5 h-5 w-5 shrink-0 text-px-navy" /> : null}
364
+ <span className="min-w-0 flex-1">
365
+ <span className="block text-[15px] font-semibold text-px-navy">{title}</span>
366
+ {description ? <span className="mt-0.5 block text-[13px] leading-relaxed text-px-muted">{description}</span> : null}
367
+ </span>
368
+ {aside ? <span className="shrink-0 text-sm font-semibold text-px-navy">{aside}</span> : null}
369
+ </button>
370
+ );
371
+ }
372
+
373
+ export function SectionHeading({ eyebrow, title, subtitle, action, align = 'left' }: { eyebrow?: string; title?: string; subtitle?: string; action?: React.ReactNode; align?: 'left' | 'center' }) {
374
+ if (!title && !eyebrow && !subtitle && !action) return null;
375
+ return (
376
+ <div className={cn('mb-8 flex flex-col gap-4 sm:mb-10', align === 'center' ? 'items-center text-center' : 'sm:flex-row sm:items-end sm:justify-between')}>
377
+ <div className={cn('max-w-2xl', align === 'center' && 'mx-auto')}>
378
+ {eyebrow ? <p className="mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-px-primary">{eyebrow}</p> : null}
379
+ {title ? <h2 className="font-heading text-2xl font-semibold tracking-tight text-px-navy sm:text-3xl">{title}</h2> : null}
380
+ {subtitle ? <p className="mt-2 text-[15px] leading-relaxed text-px-muted">{subtitle}</p> : null}
381
+ </div>
382
+ {action}
383
+ </div>
384
+ );
385
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Snapify
3
+ * © 2026 NOVALABS WEB DESIGN — Tunisie
4
+ * Contact : contact@novalabs.tn · +216 24 734 647
5
+ * Tous droits réservés. Toute reproduction, modification ou diffusion sans autorisation écrite est interdite.
6
+ */
7
+ /** Affichage d'une commande, commun à la page de confirmation, au suivi et à l'espace client. */
8
+ import { Check, Landmark } from 'lucide-react';
9
+ import { COMMERCE_PATHS, formatAddress, formatMoney, ORDER_STATUS_LABELS, PAYMENT_LABELS, PAYMENT_STATUS_LABELS, type OrderDetail, type OrderStatus } from '../commerce';
10
+ import { Link, cn } from './commerce';
11
+
12
+ const STATUS_TONE: Record<OrderStatus, string> = {
13
+ pending: 'bg-amber-50 text-amber-800 ring-amber-200',
14
+ confirmed: 'bg-sky-50 text-sky-800 ring-sky-200',
15
+ shipped: 'bg-indigo-50 text-indigo-800 ring-indigo-200',
16
+ delivered: 'bg-emerald-50 text-emerald-800 ring-emerald-200',
17
+ cancelled: 'bg-rose-50 text-rose-800 ring-rose-200',
18
+ };
19
+
20
+ export function OrderStatusBadge({ status }: { status: OrderStatus }) {
21
+ return <span className={cn('inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold ring-1 ring-inset', STATUS_TONE[status])}>{ORDER_STATUS_LABELS[status]}</span>;
22
+ }
23
+
24
+ const dateFmt = new Intl.DateTimeFormat('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' });
25
+ const dateTimeFmt = new Intl.DateTimeFormat('fr-FR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });
26
+ export const formatOrderDate = (iso: string) => dateFmt.format(new Date(iso));
27
+
28
+ const STEPS: OrderStatus[] = ['pending', 'confirmed', 'shipped', 'delivered'];
29
+
30
+ export function OrderProgress({ order }: { order: OrderDetail }) {
31
+ if (order.status === 'cancelled') return null;
32
+ const current = STEPS.indexOf(order.status);
33
+ return (
34
+ <ol className="grid grid-cols-4 gap-2">
35
+ {STEPS.map((st, i) => {
36
+ const at = order.history.find((h) => h.status === st)?.at;
37
+ const done = i <= current;
38
+ return (
39
+ <li key={st} className="min-w-0">
40
+ <div className={cn('h-1.5 rounded-full', done ? 'bg-px-primary' : 'bg-px-border-soft')} />
41
+ <p className={cn('mt-2 flex items-center gap-1 truncate text-xs font-semibold sm:text-[13px]', done ? 'text-px-navy' : 'text-px-muted')}>
42
+ {done ? <Check className="h-3.5 w-3.5 shrink-0 text-px-primary" /> : null}
43
+ {ORDER_STATUS_LABELS[st]}
44
+ </p>
45
+ {at ? <p className="truncate text-[11px] text-px-muted">{dateTimeFmt.format(new Date(at))}</p> : null}
46
+ </li>
47
+ );
48
+ })}
49
+ </ol>
50
+ );
51
+ }
52
+
53
+ export function OrderDetailView({ order, navigate }: { order: OrderDetail; navigate: (p: string) => void }) {
54
+ const cur = order.currency;
55
+ return (
56
+ <div className="space-y-6">
57
+ <OrderProgress order={order} />
58
+
59
+ {order.bankDetails ? (
60
+ <div className="rounded-2xl border border-px-primary/30 bg-px-primary/5 p-5">
61
+ <p className="flex items-center gap-2 font-semibold text-px-navy">
62
+ <Landmark className="h-4 w-4 text-px-primary" /> Réglez votre commande par virement
63
+ </p>
64
+ <p className="mt-1 text-sm text-px-text">
65
+ Montant : <strong>{formatMoney(order.total, cur)}</strong> · Référence : <strong>{order.number}</strong>
66
+ </p>
67
+ <p className="mt-3 whitespace-pre-line rounded-xl bg-white p-4 font-mono text-[13px] leading-relaxed text-px-navy">{order.bankDetails}</p>
68
+ </div>
69
+ ) : null}
70
+
71
+ <div className="overflow-hidden rounded-2xl border border-px-border-soft bg-white">
72
+ <ul className="divide-y divide-px-border-soft">
73
+ {order.lines.map((l) => (
74
+ <li key={l.productId} className="flex items-center gap-4 p-4">
75
+ <div className="h-16 w-14 shrink-0 overflow-hidden rounded-lg bg-px-surface">{l.image ? <img src={l.image} alt="" className="h-full w-full object-cover" /> : null}</div>
76
+ <div className="min-w-0 flex-1">
77
+ <Link to={COMMERCE_PATHS.product(l.slug)} navigate={navigate} className="line-clamp-2 text-sm font-medium text-px-navy hover:text-px-primary">
78
+ {l.name}
79
+ </Link>
80
+ <p className="text-xs text-px-muted">
81
+ {l.qty} × {formatMoney(l.price, cur)}
82
+ </p>
83
+ </div>
84
+ <span className="text-sm font-semibold tabular-nums text-px-navy">{formatMoney(l.lineTotal, cur)}</span>
85
+ </li>
86
+ ))}
87
+ </ul>
88
+ <dl className="space-y-2 border-t border-px-border-soft bg-px-surface/50 p-4 text-sm">
89
+ <div className="flex justify-between">
90
+ <dt className="text-px-text">Sous-total</dt>
91
+ <dd className="tabular-nums text-px-navy">{formatMoney(order.subtotal, cur)}</dd>
92
+ </div>
93
+ {order.discount ? (
94
+ <div className="flex justify-between text-emerald-700">
95
+ <dt>Remise{order.couponCode ? ` (${order.couponCode})` : ''}</dt>
96
+ <dd className="tabular-nums">−{formatMoney(order.discount, cur)}</dd>
97
+ </div>
98
+ ) : null}
99
+ <div className="flex justify-between">
100
+ <dt className="text-px-text">{order.shippingLabel}</dt>
101
+ <dd className="tabular-nums text-px-navy">{order.shippingCost ? formatMoney(order.shippingCost, cur) : 'Offerte'}</dd>
102
+ </div>
103
+ <div className="flex justify-between border-t border-px-border pt-3 text-base font-semibold text-px-navy">
104
+ <dt>Total</dt>
105
+ <dd className="tabular-nums">{formatMoney(order.total, cur)}</dd>
106
+ </div>
107
+ </dl>
108
+ </div>
109
+
110
+ <div className="grid gap-4 sm:grid-cols-2">
111
+ <div className="rounded-2xl border border-px-border-soft p-5">
112
+ <p className="text-xs font-semibold uppercase tracking-[0.14em] text-px-muted">Livraison</p>
113
+ <p className="mt-2 text-sm font-semibold text-px-navy">{order.shippingLabel}</p>
114
+ <p className="mt-1 text-sm leading-relaxed text-px-text">
115
+ {formatAddress(order.address).map((l, i) => (
116
+ <span key={i} className="block">
117
+ {l}
118
+ </span>
119
+ ))}
120
+ </p>
121
+ </div>
122
+ <div className="rounded-2xl border border-px-border-soft p-5">
123
+ <p className="text-xs font-semibold uppercase tracking-[0.14em] text-px-muted">Paiement</p>
124
+ <p className="mt-2 text-sm font-semibold text-px-navy">{PAYMENT_LABELS[order.paymentMethod]}</p>
125
+ <p className="mt-1 text-sm text-px-text">{PAYMENT_STATUS_LABELS[order.paymentStatus]}</p>
126
+ {order.notes ? (
127
+ <>
128
+ <p className="mt-4 text-xs font-semibold uppercase tracking-[0.14em] text-px-muted">Votre note</p>
129
+ <p className="mt-1 whitespace-pre-line text-sm text-px-text">{order.notes}</p>
130
+ </>
131
+ ) : null}
132
+ </div>
133
+ </div>
134
+ </div>
135
+ );
136
+ }
@@ -0,0 +1,17 @@
1
+ # Journal des versions
2
+
3
+ ## 1.3.1 — 15 octobre 2026
4
+
5
+ - Le kit est publié sur le registre npm sous le nom `snapify-sdk` ; les projets l’installent sous l’alias `@snapify/sdk`
6
+ (`"@snapify/sdk": "npm:snapify-sdk@^1.3.1"`), sans changer les imports.
7
+ - Nouveau paquet `create-snapify` : `npm create snapify@latest`.
8
+ - `snapify create` sans dossier pose ses questions dans le terminal (module ou thème, dossier, éditeur, nom, auteur, e-mail).
9
+
10
+ ## 1.3.0 — 15 octobre 2026
11
+
12
+ Première version publique du kit développeur.
13
+
14
+ - SDK des modules et des thèmes (API Snapify 1.3) avec déclarations TypeScript.
15
+ - Outil `snapify` : `create` (modèles de module et de thème), `build`, `check`, `verify`.
16
+ - `snapify build` et `snapify check` rejouent l’analyse de la marketplace sur le poste du développeur.
17
+ - Paquets non signés : la signature NOVALABS est apposée lors de la revue marketplace.
@@ -0,0 +1,47 @@
1
+ # Licence du kit développeur Snapify
2
+
3
+ © 2026 NOVALABS WEB DESIGN — Tunisie. Tous droits réservés, sous réserve de la licence ci-dessous.
4
+
5
+ Le « Kit » désigne les paquets `snapify-sdk` (importé sous le nom `@snapify/sdk`) et `create-snapify` : bibliothèques,
6
+ déclarations de types, feuilles de style, modèles de projet, outil en ligne de commande `snapify` et documentation associée.
7
+
8
+ ## 1. Licence accordée
9
+
10
+ NOVALABS WEB DESIGN (« NOVALABS ») vous accorde, gratuitement, une licence mondiale, non exclusive et non transférable
11
+ d’installer, d’utiliser et de reproduire le Kit **dans le seul but de développer, tester, compiler et distribuer des
12
+ extensions (modules et thèmes) destinées au CMS Snapify**.
13
+
14
+ ## 2. Vos extensions
15
+
16
+ Les extensions que vous créez avec le Kit vous appartiennent. Les parties du Kit intégrées automatiquement à vos paquets
17
+ lors de la compilation peuvent être distribuées avec eux, sous forme compilée uniquement, pour fonctionner sur Snapify.
18
+
19
+ Les modèles de projet générés par `snapify create` peuvent être modifiés et utilisés librement dans vos extensions.
20
+
21
+ ## 3. Restrictions
22
+
23
+ Sauf accord écrit de NOVALABS, vous ne pouvez pas :
24
+
25
+ - redistribuer le Kit, modifié ou non, en dehors de vos extensions compilées ;
26
+ - utiliser le Kit pour développer un produit concurrent de Snapify ou des extensions destinées à un autre logiciel ;
27
+ - contourner la signature des paquets, la vérification des licences ou l’analyse de sécurité de Snapify ;
28
+ - utiliser les identifiants réservés `novalabs.*` et `snapify.*`, ni présenter vos extensions comme officielles.
29
+
30
+ ## 4. Marketplace
31
+
32
+ La publication sur la marketplace Snapify est soumise aux conditions de la marketplace (revue, commission, signature).
33
+
34
+ ## 5. Absence de garantie
35
+
36
+ Le Kit est fourni « en l’état », sans garantie d’aucune sorte. NOVALABS ne peut être tenue responsable des dommages
37
+ résultant de son utilisation, dans les limites permises par la loi applicable.
38
+
39
+ ## 6. Résiliation
40
+
41
+ La licence prend fin automatiquement en cas de manquement à ses conditions. Vous devez alors cesser d’utiliser le Kit.
42
+
43
+ ## 7. Droit applicable
44
+
45
+ La présente licence est régie par le droit tunisien.
46
+
47
+ Contact : contact@novalabs.tn
@@ -0,0 +1,81 @@
1
+ # snapify-sdk — kit développeur Snapify
2
+
3
+ SDK, outil de compilation et analyse marketplace pour créer des **modules** (`.snapmod`) et des **thèmes** (`.snaptheme`)
4
+ pour le CMS [Snapify](https://snapify.tn).
5
+
6
+ - Documentation : https://developers.snapify.tn/docs
7
+ - Kit, versions et sommes de contrôle : https://developers.snapify.tn/docs/kit-developpeur
8
+ - Marketplace : https://marketplace.snapify.tn
9
+
10
+ ## Prérequis
11
+
12
+ Node.js 20.19+ ou 22.12+, npm 10+.
13
+
14
+ ## Démarrer
15
+
16
+ ```bash
17
+ npm create snapify@latest
18
+ ```
19
+
20
+ L’outil demande s’il s’agit d’un module ou d’un thème, le dossier et votre nom d’éditeur, puis prépare le projet.
21
+
22
+ ```bash
23
+ cd mon-module
24
+ npm install
25
+ npm run build
26
+ ```
27
+
28
+ `npm run build` produit `dist/<id>-<version>.snapmod` puis lance **la même analyse que la marketplace** :
29
+ manifeste, permissions, code, sécurité, présentation. Un paquet qui passe ici passe à l’envoi.
30
+
31
+ Sans questions (intégration continue, scripts) :
32
+
33
+ ```bash
34
+ npm create snapify@latest mon-module -- --id monagence.mon-module
35
+ npm create snapify@latest mon-theme -- --theme --id monagence.mon-theme
36
+ ```
37
+
38
+ ## Ajouter le SDK à un projet existant
39
+
40
+ Le code des extensions importe le SDK sous le nom `@snapify/sdk`. Installez le paquet `snapify-sdk` sous ce nom :
41
+
42
+ ```bash
43
+ npm install --save-dev @snapify/sdk@npm:snapify-sdk@^__SDK_VERSION__
44
+ ```
45
+
46
+ > Le nom `@snapify/sdk` du registre npm n’appartient pas à NOVALABS : n’installez jamais `npm install @snapify/sdk` seul.
47
+ > Le paquet officiel est `snapify-sdk`, publié par NOVALABS.
48
+
49
+ ## Commandes
50
+
51
+ | Commande | Rôle |
52
+ | --- | --- |
53
+ | `snapify create [dossier] [--theme] [--id editeur.nom] [--name "Nom"] [--author "Nom"] [--email adresse]` | Nouveau projet de module ou de thème (sans dossier : questions) |
54
+ | `snapify build [dossier] [--out dist] [--no-check]` | Compile le paquet puis l’analyse |
55
+ | `snapify check <paquet>` | Analyse d’un paquet, identique à celle de la marketplace |
56
+ | `snapify verify <paquet>` | Vérifie la signature NOVALABS et la licence d’un paquet reçu |
57
+
58
+ Dans un projet, lancez-les avec `npx snapify …` ou les scripts `npm run build` et `npm run typecheck`.
59
+
60
+ ## Imports
61
+
62
+ | Import | Contenu |
63
+ | --- | --- |
64
+ | `@snapify/sdk` | Manifeste, partie publique (`definePublicModule`) et serveur (`defineServerModule`), types |
65
+ | `@snapify/sdk/admin` | Pages d’administration (`defineAdminModule`) et composants du back-office |
66
+ | `@snapify/sdk/theme` | Contrat des thèmes : `defineTheme`, `useSnapify`, `ModuleSlot` |
67
+ | `@snapify/sdk/content` | Types des blocs, pages, articles et configuration du site |
68
+ | `@snapify/sdk/commerce` | Contrat e-commerce partagé |
69
+ | `@snapify/sdk/image` | Préparation d’images dans le navigateur |
70
+ | `@snapify/sdk/icons` | Icônes de l’éditeur de blocs |
71
+
72
+ React est fourni par le site hôte à l’exécution : il n’est jamais inclus dans vos paquets.
73
+
74
+ ## Signature et publication
75
+
76
+ Les paquets produits par le kit ne sont **pas signés**. Ils s’installent uniquement sur un site Snapify en mode développeur.
77
+ Pour publier, envoyez le paquet depuis votre espace vendeur sur la marketplace : après la revue, NOVALABS signe la version validée.
78
+
79
+ ## Licence
80
+
81
+ Voir [LICENSE.md](LICENSE.md). © 2026 NOVALABS WEB DESIGN — Tunisie · contact@novalabs.tn