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,278 @@
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
+ import React, { useEffect, useState } from 'react';
8
+ import { CheckCircle2, Copy, LogOut, Monitor, Smartphone, Sparkles, XCircle } from 'lucide-react';
9
+ import { toast } from 'sonner';
10
+ import type { AuditEntry, LinkDelivery, Paged, PasswordPolicy, PublicUser, RoleRow, SessionRow } from '../lib/api';
11
+ import { auditDetail, auditLabel } from '../lib/audit';
12
+ import { fmtDateTime, timeAgo } from '../lib/format';
13
+ import { Modal } from '../ui/overlays';
14
+ import { Alert, Badge, Button, cx, Input, Spinner } from '../ui/primitives';
15
+ import { policyChecks } from '../account/SecurityForms';
16
+ import { Pagination, usePagination } from '../ui/Pagination';
17
+
18
+ const AVATAR_TONES = ['bg-sky-100 text-sky-700', 'bg-violet-100 text-violet-700', 'bg-emerald-100 text-emerald-700', 'bg-amber-100 text-amber-800', 'bg-rose-100 text-rose-700', 'bg-brand-100 text-brand-700'];
19
+
20
+ export function initials(name: string) {
21
+ return (
22
+ name
23
+ .split(/\s+/)
24
+ .filter(Boolean)
25
+ .slice(0, 2)
26
+ .map((p) => p[0]!.toUpperCase())
27
+ .join('') || '?'
28
+ );
29
+ }
30
+
31
+ const AVATAR_SIZES = { sm: 'h-7 w-7 text-[11px]', md: 'h-9 w-9 text-xs', lg: 'h-14 w-14 text-lg', xl: 'h-20 w-20 text-2xl' };
32
+
33
+ export function Avatar({ name, email, src, size = 'md' }: { name: string; email: string; src?: string; size?: keyof typeof AVATAR_SIZES }) {
34
+ const [broken, setBroken] = useState(false);
35
+ useEffect(() => setBroken(false), [src]);
36
+ if (src && !broken) {
37
+ return <img src={src} alt="" onError={() => setBroken(true)} className={cx('shrink-0 rounded-full object-cover ring-1 ring-slate-200', AVATAR_SIZES[size])} />;
38
+ }
39
+ const tone = AVATAR_TONES[[...email].reduce((a, c) => a + c.charCodeAt(0), 0) % AVATAR_TONES.length];
40
+ return <span className={cx('inline-flex shrink-0 items-center justify-center rounded-full font-semibold', tone, AVATAR_SIZES[size])}>{initials(name)}</span>;
41
+ }
42
+
43
+ export function RoleBadge({ roleId, roles, fallback }: { roleId: string; roles?: RoleRow[] | null; fallback?: string }) {
44
+ const role = roles?.find((r) => r.id === roleId);
45
+ return <Badge tone={role?.tone ?? 'neutral'}>{role?.name ?? fallback ?? roleId}</Badge>;
46
+ }
47
+
48
+ export function UserStatusBadge({ user, inviteExpiresAt }: { user: Pick<PublicUser, 'status'>; inviteExpiresAt?: string }) {
49
+ if (user.status === 'suspended')
50
+ return (
51
+ <Badge tone="danger" dot>
52
+ Suspendu
53
+ </Badge>
54
+ );
55
+ if (user.status === 'invited') {
56
+ const expired = !inviteExpiresAt;
57
+ return (
58
+ <span title={inviteExpiresAt ? `Lien valable jusqu’au ${fmtDateTime(inviteExpiresAt)}` : 'Le lien d’invitation a expiré : renvoyez-le'}>
59
+ <Badge tone="warning" dot>
60
+ {expired ? 'Invitation expirée' : 'Invitation envoyée'}
61
+ </Badge>
62
+ </span>
63
+ );
64
+ }
65
+ return (
66
+ <Badge tone="success" dot>
67
+ Actif
68
+ </Badge>
69
+ );
70
+ }
71
+
72
+ /** Mot de passe aléatoire respectant la politique (caractères ambigus exclus). */
73
+ export function generatePassword(policy: PasswordPolicy): string {
74
+ const lower = 'abcdefghijkmnopqrstuvwxyz';
75
+ const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
76
+ const digits = '23456789';
77
+ const symbols = '!@#$%&*?-_+=';
78
+ const all = lower + upper + digits + symbols;
79
+ const length = Math.max(16, policy.passwordMinLength);
80
+ const pick = (set: string) => set[crypto.getRandomValues(new Uint32Array(1))[0]! % set.length];
81
+ for (;;) {
82
+ const chars = [pick(lower), pick(upper), pick(digits), pick(symbols)];
83
+ while (chars.length < length) chars.push(pick(all));
84
+ for (let i = chars.length - 1; i > 0; i--) {
85
+ const j = crypto.getRandomValues(new Uint32Array(1))[0]! % (i + 1);
86
+ [chars[i], chars[j]] = [chars[j]!, chars[i]!];
87
+ }
88
+ const pw = chars.join('');
89
+ if (policyChecks(pw, policy).every((c) => c.ok)) return pw;
90
+ }
91
+ }
92
+
93
+ export function GeneratePasswordButton({ policy, onGenerate }: { policy: PasswordPolicy; onGenerate: (pw: string) => void }) {
94
+ return (
95
+ <Button
96
+ size="sm"
97
+ variant="ghost"
98
+ icon={Sparkles}
99
+ onClick={() => {
100
+ const pw = generatePassword(policy);
101
+ onGenerate(pw);
102
+ void navigator.clipboard?.writeText(pw).then(() => toast.success('Mot de passe généré et copié'));
103
+ }}
104
+ >
105
+ Générer
106
+ </Button>
107
+ );
108
+ }
109
+
110
+ export function CopyField({ value }: { value: string }) {
111
+ return (
112
+ <div className="flex gap-2">
113
+ <Input readOnly value={value} onFocus={(e) => e.target.select()} className="font-mono text-xs" />
114
+ <Button
115
+ icon={Copy}
116
+ onClick={() => {
117
+ void navigator.clipboard.writeText(value);
118
+ toast.success('Lien copié');
119
+ }}
120
+ >
121
+ Copier
122
+ </Button>
123
+ </div>
124
+ );
125
+ }
126
+
127
+ /** Résultat d'une invitation ou d'un lien de réinitialisation : état de l'e-mail et lien à transmettre. */
128
+ export function LinkResultModal({
129
+ result,
130
+ onClose,
131
+ }: {
132
+ /** `manual` : lien demandé sans envoi d'e-mail. */
133
+ result: { title: string; email: string; delivery: LinkDelivery; manual?: boolean } | null;
134
+ onClose: () => void;
135
+ }) {
136
+ const d = result?.delivery;
137
+ return (
138
+ <Modal open={!!result} onClose={onClose} title={result?.title ?? ''} size="md" footer={<Button variant="primary" onClick={onClose}>Terminé</Button>}>
139
+ {result && d ? (
140
+ <div className="space-y-4">
141
+ {result.manual ? (
142
+ <Alert tone="info">Transmettez ce lien à {result.email} par un canal sûr : il donne accès au compte.</Alert>
143
+ ) : d.emailSent ? (
144
+ <Alert tone="success" icon={CheckCircle2} title={`E-mail envoyé à ${result.email}`}>
145
+ Vous pouvez aussi transmettre le lien ci-dessous par un autre moyen.
146
+ </Alert>
147
+ ) : (
148
+ <Alert tone="warning" icon={XCircle} title="Aucun e-mail n’a été envoyé">
149
+ {d.emailError ? <span className="block break-words">{d.emailError}</span> : null}
150
+ Transmettez ce lien à {result.email} par un canal sûr. Configurez l’envoi d’e-mails dans Paramètres › E-mails.
151
+ </Alert>
152
+ )}
153
+ <CopyField value={d.url} />
154
+ <p className="text-xs text-slate-500">
155
+ Lien personnel à usage unique, valable jusqu’au {fmtDateTime(d.expiresAt)}. Un nouveau lien annule le précédent.
156
+ </p>
157
+ </div>
158
+ ) : null}
159
+ </Modal>
160
+ );
161
+ }
162
+
163
+ export function SessionList({
164
+ sessions,
165
+ onRevoke,
166
+ revoking,
167
+ emptyLabel = 'Aucune session ouverte.',
168
+ }: {
169
+ sessions: SessionRow[] | null;
170
+ onRevoke?: (s: SessionRow) => void;
171
+ revoking?: string | null;
172
+ emptyLabel?: string;
173
+ }) {
174
+ const pg = usePagination(sessions ?? [], 'sessions', { defaultSize: 10 });
175
+ if (!sessions) return <Spinner className="mx-auto my-6 block" />;
176
+ if (!sessions.length) return <p className="py-6 text-center text-sm text-slate-500">{emptyLabel}</p>;
177
+ return (
178
+ <div>
179
+ <ul className="divide-y divide-slate-100">
180
+ {pg.pageItems.map((s) => {
181
+ const Icon = /iOS|Android/.test(s.device) ? Smartphone : Monitor;
182
+ return (
183
+ <li key={s.id} className="flex items-start gap-3 py-3">
184
+ <span className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-500">
185
+ <Icon className="h-4 w-4" />
186
+ </span>
187
+ <div className="min-w-0 flex-1">
188
+ <p className="flex flex-wrap items-center gap-2 text-sm font-medium text-slate-800">
189
+ {s.device}
190
+ {s.current ? <Badge tone="success">Cette session</Badge> : null}
191
+ {s.remember ? <Badge>Rester connecté</Badge> : null}
192
+ </p>
193
+ <p className="mt-0.5 text-xs text-slate-500" title={s.userAgent}>
194
+ {s.ip ?? 'IP inconnue'} · active {timeAgo(s.lastSeenAt)} · ouverte le {fmtDateTime(s.createdAt)}
195
+ </p>
196
+ <p className="text-xs text-slate-400">Expire le {fmtDateTime(s.expiresAt)}</p>
197
+ </div>
198
+ {onRevoke && !s.current ? (
199
+ <Button size="sm" variant="ghost" icon={LogOut} loading={revoking === s.id} onClick={() => onRevoke(s)}>
200
+ Fermer
201
+ </Button>
202
+ ) : null}
203
+ </li>
204
+ );
205
+ })}
206
+ </ul>
207
+ <Pagination {...pg.props} noun="sessions" flush />
208
+ </div>
209
+ );
210
+ }
211
+
212
+ export function ActivityFeed({
213
+ data,
214
+ page,
215
+ onPage,
216
+ onPageSize,
217
+ roleName,
218
+ showUser,
219
+ }: {
220
+ data: Paged<AuditEntry> | null;
221
+ page: number;
222
+ onPage: (p: number) => void;
223
+ onPageSize?: (n: number) => void;
224
+ roleName?: (id: string) => string;
225
+ showUser?: boolean;
226
+ }) {
227
+ const [open, setOpen] = useState<number | null>(null);
228
+ if (!data) return <Spinner className="mx-auto my-6 block" />;
229
+ if (!data.items.length) return <p className="py-6 text-center text-sm text-slate-500">Aucune activité enregistrée.</p>;
230
+ return (
231
+ <div>
232
+ <ul className="divide-y divide-slate-100">
233
+ {data.items.map((e) => {
234
+ const { label, tone } = auditLabel(e.action);
235
+ const detail = auditDetail(e, roleName);
236
+ return (
237
+ <li key={e.id} className="py-2.5">
238
+ <button type="button" className="flex w-full items-start gap-3 text-left" onClick={() => setOpen(open === e.id ? null : e.id)}>
239
+ <span
240
+ className={cx(
241
+ 'mt-1.5 h-2 w-2 shrink-0 rounded-full',
242
+ { neutral: 'bg-slate-300', success: 'bg-emerald-500', warning: 'bg-amber-500', danger: 'bg-rose-500', info: 'bg-sky-500', brand: 'bg-brand-500' }[tone]
243
+ )}
244
+ />
245
+ <span className="min-w-0 flex-1">
246
+ <span className="block text-sm text-slate-800">
247
+ {label}
248
+ {e.target ? <span className="text-slate-500"> — {e.target}</span> : null}
249
+ </span>
250
+ <span className="block text-xs text-slate-500">
251
+ {showUser && e.userEmail ? `${e.userEmail} · ` : ''}
252
+ {fmtDateTime(e.ts)}
253
+ {e.ip ? ` · ${e.ip}` : ''}
254
+ {detail ? ` · ${detail}` : ''}
255
+ </span>
256
+ </span>
257
+ </button>
258
+ {open === e.id ? (
259
+ <div className="ml-5 mt-2 space-y-1 rounded-lg bg-slate-50 p-3 text-xs text-slate-600">
260
+ <p>
261
+ <span className="text-slate-400">Action :</span> <code className="font-mono">{e.action}</code>
262
+ </p>
263
+ {e.userAgent ? (
264
+ <p className="break-all">
265
+ <span className="text-slate-400">Navigateur :</span> {e.userAgent}
266
+ </p>
267
+ ) : null}
268
+ {e.detail && Object.keys(e.detail).length ? <pre className="overflow-x-auto whitespace-pre-wrap font-mono text-[11px]">{JSON.stringify(e.detail, null, 2)}</pre> : null}
269
+ </div>
270
+ ) : null}
271
+ </li>
272
+ );
273
+ })}
274
+ </ul>
275
+ <Pagination page={page} pageSize={data.pageSize} total={data.total} onPage={onPage} onPageSize={onPageSize} noun="entrées" flush />
276
+ </div>
277
+ );
278
+ }
@@ -0,0 +1,7 @@
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
+ export * from '../../sdk/icons';
@@ -0,0 +1,166 @@
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
+ import React, { useState, useEffect } from 'react';
8
+ import { X, Check } from 'lucide-react';
9
+
10
+ interface CookieBannerProps {
11
+ isOpen: boolean;
12
+ onClose: () => void;
13
+ onNavigateToCookies: () => void;
14
+ }
15
+
16
+ export const CookieBanner: React.FC<CookieBannerProps> = ({
17
+ isOpen,
18
+ onClose,
19
+ onNavigateToCookies
20
+ }) => {
21
+ const [showPreferences, setShowPreferences] = useState(false);
22
+ const [analyticsConsent, setAnalyticsConsent] = useState(false);
23
+
24
+ useEffect(() => {
25
+ const saved = localStorage.getItem('snapify_cookie_consent');
26
+ if (saved) {
27
+ try {
28
+ const parsed = JSON.parse(saved);
29
+ setAnalyticsConsent(parsed.analytics === true);
30
+ } catch (e) {
31
+ // ignore
32
+ }
33
+ }
34
+ }, []);
35
+
36
+ const handleRefuseAll = () => {
37
+ localStorage.setItem(
38
+ 'snapify_cookie_consent',
39
+ JSON.stringify({ necessary: true, analytics: false, timestamp: Date.now() })
40
+ );
41
+ setShowPreferences(false);
42
+ onClose();
43
+ };
44
+
45
+ const handleAcceptAll = () => {
46
+ localStorage.setItem(
47
+ 'snapify_cookie_consent',
48
+ JSON.stringify({ necessary: true, analytics: true, timestamp: Date.now() })
49
+ );
50
+ setAnalyticsConsent(true);
51
+ setShowPreferences(false);
52
+ onClose();
53
+ };
54
+
55
+ const handleSaveCustom = () => {
56
+ localStorage.setItem(
57
+ 'snapify_cookie_consent',
58
+ JSON.stringify({ necessary: true, analytics: analyticsConsent, timestamp: Date.now() })
59
+ );
60
+ setShowPreferences(false);
61
+ onClose();
62
+ };
63
+
64
+ if (!isOpen) return null;
65
+
66
+ return (
67
+ <aside aria-label="Gestion des cookies" className="fixed bottom-4 left-4 right-4 sm:right-auto sm:max-w-md z-50 animate-in fade-in slide-in-from-bottom-4 duration-300">
68
+ <div className="bg-white rounded-2xl p-5 shadow-2xl border border-px-border text-px-navy">
69
+ {/* Top header row */}
70
+ <div className="flex items-start justify-between gap-3 mb-2">
71
+ <h3 className="text-sm font-semibold text-px-navy tracking-tight">
72
+ Lorem ipsum dolor sit amet.
73
+ </h3>
74
+ <button
75
+ onClick={handleRefuseAll}
76
+ className="text-[11px] text-slate-500 hover:text-px-navy underline underline-offset-2 shrink-0"
77
+ >
78
+ Continuer sans accepter
79
+ </button>
80
+ </div>
81
+
82
+ <p className="text-xs text-slate-600 leading-relaxed mb-4">
83
+ Avec votre accord, nous utilisons Google Analytics pour comprendre comment ce site est consulté et l'améliorer. Aucun cookie de mesure n'est déposé sans votre choix.{' '}
84
+ <button
85
+ type="button"
86
+ onClick={onNavigateToCookies}
87
+ className="text-px-primary-dark underline font-medium hover:text-px-navy"
88
+ >
89
+ En savoir plus
90
+ </button>
91
+ </p>
92
+
93
+ {showPreferences ? (
94
+ <div className="space-y-3 pt-2 border-t border-slate-100 mb-4">
95
+ {/* Category 1 */}
96
+ <div className="flex items-center justify-between gap-2 p-2.5 rounded-lg bg-slate-50 text-xs">
97
+ <div>
98
+ <div className="font-semibold text-slate-800">Cookies nécessaires</div>
99
+ <div className="text-[11px] text-slate-500 mt-0.5">
100
+ Indispensables au fonctionnement du site et à la mémorisation de vos choix.
101
+ </div>
102
+ </div>
103
+ <span className="text-[10px] font-semibold text-emerald-700 bg-emerald-50 px-2 py-0.5 rounded border border-emerald-200">
104
+ Toujours actifs
105
+ </span>
106
+ </div>
107
+
108
+ {/* Category 2 */}
109
+ <div className="flex items-center justify-between gap-2 p-2.5 rounded-lg bg-slate-50 text-xs">
110
+ <div>
111
+ <div className="font-semibold text-slate-800">Mesure d'audience (GA4)</div>
112
+ <div className="text-[11px] text-slate-500 mt-0.5">
113
+ Google Analytics nous indique les pages consultées de façon globale.
114
+ </div>
115
+ </div>
116
+ <button
117
+ type="button"
118
+ onClick={() => setAnalyticsConsent(!analyticsConsent)}
119
+ className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full transition-colors duration-200 ease-in-out border-2 border-transparent ${
120
+ analyticsConsent ? 'bg-px-primary' : 'bg-slate-300'
121
+ }`}
122
+ >
123
+ <span
124
+ className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out ${
125
+ analyticsConsent ? 'translate-x-4' : 'translate-x-0'
126
+ }`}
127
+ />
128
+ </button>
129
+ </div>
130
+
131
+ <button
132
+ onClick={handleSaveCustom}
133
+ className="w-full py-2 px-3 text-xs font-semibold bg-px-navy text-white rounded-lg hover:bg-slate-800 transition-colors"
134
+ >
135
+ Enregistrer mes choix
136
+ </button>
137
+ </div>
138
+ ) : (
139
+ <div className="grid grid-cols-3 gap-2">
140
+ <button
141
+ type="button"
142
+ onClick={handleRefuseAll}
143
+ className="py-2 px-2 text-xs font-medium text-slate-700 bg-slate-100 hover:bg-slate-200 rounded-lg text-center transition-colors"
144
+ >
145
+ Tout refuser
146
+ </button>
147
+ <button
148
+ type="button"
149
+ onClick={() => setShowPreferences(true)}
150
+ className="py-2 px-2 text-xs font-medium text-slate-700 bg-slate-100 hover:bg-slate-200 rounded-lg text-center transition-colors"
151
+ >
152
+ Personnaliser
153
+ </button>
154
+ <button
155
+ type="button"
156
+ onClick={handleAcceptAll}
157
+ className="py-2 px-2 text-xs font-semibold text-slate-900 bg-slate-100 hover:bg-slate-200 rounded-lg text-center transition-colors border border-slate-300/80"
158
+ >
159
+ Tout accepter
160
+ </button>
161
+ </div>
162
+ )}
163
+ </div>
164
+ </aside>
165
+ );
166
+ };
@@ -0,0 +1,66 @@
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
+ import React, { useEffect, useState } from 'react';
8
+ import { Wrench } from 'lucide-react';
9
+
10
+ interface MaintenanceNotice {
11
+ title: string;
12
+ message: string;
13
+ logo?: string;
14
+ siteName?: string;
15
+ }
16
+
17
+ /** État annoncé par le serveur dans le HTML (voir `injectMaintenance`). */
18
+ function initialNotice(): MaintenanceNotice | null {
19
+ try {
20
+ const raw = document.getElementById('snapify-maintenance')?.textContent;
21
+ return raw ? (JSON.parse(raw) as MaintenanceNotice) : null;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Le serveur tranche au chargement ; l'état est revérifié à chaque navigation et au retour
29
+ * sur l'onglet, pour qu'un visiteur déjà sur le site bascule dès l'activation.
30
+ */
31
+ export function MaintenanceGate({ path, children }: { path: string; children: React.ReactNode }) {
32
+ const [notice, setNotice] = useState<MaintenanceNotice | null>(initialNotice);
33
+
34
+ useEffect(() => {
35
+ let alive = true;
36
+ const check = () =>
37
+ fetch('/api/public/maintenance-status', { cache: 'no-store', credentials: 'same-origin' })
38
+ .then((r) => (r.ok ? r.json() : null))
39
+ .then((s: ({ enabled: boolean } & MaintenanceNotice) | null) => {
40
+ if (alive && s) setNotice(s.enabled ? s : null);
41
+ })
42
+ .catch(() => undefined);
43
+ check();
44
+ const onVisible = () => document.visibilityState === 'visible' && check();
45
+ document.addEventListener('visibilitychange', onVisible);
46
+ return () => {
47
+ alive = false;
48
+ document.removeEventListener('visibilitychange', onVisible);
49
+ };
50
+ }, [path]);
51
+
52
+ if (!notice) return <>{children}</>;
53
+ return (
54
+ <div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-px-navy px-6 text-center text-white">
55
+ <div className="absolute -right-40 -top-40 h-[520px] w-[520px] rounded-full bg-px-primary/15 blur-3xl" />
56
+ <div className="relative max-w-xl">
57
+ {notice.logo ? <img src={notice.logo} alt={notice.siteName || ''} className="mx-auto mb-10 h-10 object-contain" /> : null}
58
+ <span className="mx-auto mb-6 flex h-14 w-14 items-center justify-center rounded-2xl bg-white/5 ring-1 ring-white/10">
59
+ <Wrench className="h-6 w-6 text-px-primary" />
60
+ </span>
61
+ <h1 className="text-3xl font-semibold tracking-tight md:text-4xl">{notice.title || 'Site en maintenance'}</h1>
62
+ {notice.message ? <p className="mt-4 text-lg leading-relaxed text-slate-300">{notice.message}</p> : null}
63
+ </div>
64
+ </div>
65
+ );
66
+ }
@@ -0,0 +1,64 @@
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
+ import { useEffect, useRef } from 'react';
8
+
9
+ const SESSION_KEY = 'snapify_analytics_session';
10
+
11
+ function getSessionId(): string {
12
+ let id = sessionStorage.getItem(SESSION_KEY);
13
+ if (!id) {
14
+ id = crypto.randomUUID();
15
+ sessionStorage.setItem(SESSION_KEY, id);
16
+ }
17
+ return id;
18
+ }
19
+
20
+ function detectDevice(): 'mobile' | 'tablet' | 'desktop' | 'unknown' {
21
+ const w = window.innerWidth;
22
+ if (w < 768) return 'mobile';
23
+ if (w < 1024) return 'tablet';
24
+ if (w >= 1024) return 'desktop';
25
+ return 'unknown';
26
+ }
27
+
28
+ export function SiteAnalytics({ path }: { path: string }) {
29
+ const startRef = useRef(Date.now());
30
+ const sentRef = useRef(false);
31
+
32
+ useEffect(() => {
33
+ startRef.current = Date.now();
34
+ sentRef.current = false;
35
+
36
+ const payload = {
37
+ sessionId: getSessionId(),
38
+ path,
39
+ referrer: document.referrer || undefined,
40
+ device: detectDevice(),
41
+ };
42
+
43
+ fetch('/api/public/analytics/event', {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ ...payload, kind: 'view' }),
47
+ keepalive: true,
48
+ }).catch(() => {});
49
+
50
+ return () => {
51
+ if (sentRef.current) return;
52
+ sentRef.current = true;
53
+ const durationSec = Math.round((Date.now() - startRef.current) / 1000);
54
+ fetch('/api/public/analytics/event', {
55
+ method: 'POST',
56
+ headers: { 'Content-Type': 'application/json' },
57
+ body: JSON.stringify({ ...payload, kind: 'leave', durationSec }),
58
+ keepalive: true,
59
+ }).catch(() => {});
60
+ };
61
+ }, [path]);
62
+
63
+ return null;
64
+ }
@@ -0,0 +1,44 @@
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
+ import React from 'react';
8
+
9
+ interface SnapifyMarkProps {
10
+ className?: string;
11
+ /** Couleur de l’étincelle et des pastilles de la fenêtre. */
12
+ accent?: string;
13
+ /** Couleur du cadre de la fenêtre. */
14
+ frame?: string;
15
+ }
16
+
17
+ /** Pictogramme seul : une fenêtre de navigateur dans laquelle une étincelle génère le site. */
18
+ export const SnapifyMark: React.FC<SnapifyMarkProps> = ({
19
+ className = 'w-6 h-6',
20
+ accent = 'var(--px-primary)',
21
+ frame = '#FFFFFF',
22
+ }) => (
23
+ <svg viewBox="0 0 40 40" className={className} aria-hidden="true">
24
+ <rect x="7" y="9" width="26" height="22" rx="4" fill="none" stroke={frame} strokeWidth="2.4" />
25
+ <path d="M7 15.5 H33" stroke={frame} strokeWidth="2.4" />
26
+ <circle cx="11" cy="12.3" r="1.1" fill={accent} />
27
+ <circle cx="14.4" cy="12.3" r="1.1" fill={accent} />
28
+ <path d="M20 18.2 L21.6 22 L25.4 23.6 L21.6 25.2 L20 29 L18.4 25.2 L14.6 23.6 L18.4 22 Z" fill={accent} />
29
+ </svg>
30
+ );
31
+
32
+ interface SnapifyLogoProps {
33
+ className?: string;
34
+ }
35
+
36
+ /** Pictogramme dans son badge carré sombre, utilisé en en-tête et pied de page du site public. */
37
+ export const SnapifyLogo: React.FC<SnapifyLogoProps> = ({ className = 'w-10 h-10' }) => (
38
+ <div
39
+ className={`flex items-center justify-center rounded-xl bg-px-navy border border-slate-700/60 shadow-sm ${className}`}
40
+ title="Snapify — Générateur de sites web"
41
+ >
42
+ <SnapifyMark className="w-[70%] h-[70%]" />
43
+ </div>
44
+ );