create-snapify-app 0.0.0-stage → 1.1.1

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 +35 -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 +123 -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,643 @@
1
+ /**
2
+ * Snapify Developers — portail de documentation (developers.snapify.tn)
3
+ * © 2026 NOVALABS WEB DESIGN — Tunisie
4
+ */
5
+ import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
6
+ import { Link, Navigate, NavLink, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom';
7
+ import {
8
+ ArrowRight,
9
+ ArrowUpRight,
10
+ BookOpen,
11
+ Boxes,
12
+ ChevronLeft,
13
+ ChevronRight,
14
+ Download,
15
+ FileCode2,
16
+ LayoutTemplate,
17
+ Menu,
18
+ PackageCheck,
19
+ Puzzle,
20
+ Search,
21
+ ShieldCheck,
22
+ Store,
23
+ X,
24
+ } from 'lucide-react';
25
+ import { SnapifyMark } from '../../shared/Logo';
26
+ import { API_VERSION, PAGES, SECTIONS, type DocPage } from './pages';
27
+ import { Code, COMMUNITY_URL, CONTACT_EMAIL, MARKETPLACE_URL, SITE_URL } from './ui';
28
+
29
+ const docPath = (p: DocPage) => `/docs/${p.slug}`;
30
+ const norm = (s: string) =>
31
+ s
32
+ .normalize('NFD')
33
+ .replace(/[\u0300-\u036f]/g, '')
34
+ .toLowerCase();
35
+
36
+ /* ------------------------------------------------------------------ En-tête */
37
+
38
+ function Brand() {
39
+ return (
40
+ <Link to="/" className="flex shrink-0 items-center gap-2.5">
41
+ <span className="flex h-9 w-9 items-center justify-center rounded-lg bg-navy">
42
+ <SnapifyMark className="h-6 w-6" />
43
+ </span>
44
+ <span className="text-[15px] font-semibold tracking-tight text-navy">Snapify</span>
45
+ <span className="rounded-md bg-teal-50 px-1.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-brand-dark ring-1 ring-teal-100">
46
+ Developers
47
+ </span>
48
+ </Link>
49
+ );
50
+ }
51
+
52
+ const TOP_LINKS = [
53
+ { to: '/docs/introduction', label: 'Documentation', match: (p: string) => p.startsWith('/docs/') && !p.startsWith('/docs/themes') && !p.startsWith('/docs/publier') && !p.startsWith('/docs/reference') },
54
+ { to: '/docs/themes/creer-un-theme', label: 'Thèmes', match: (p: string) => p.startsWith('/docs/themes') },
55
+ { to: '/docs/reference/permissions', label: 'Référence', match: (p: string) => p.startsWith('/docs/reference') },
56
+ { to: '/docs/publier', label: 'Publier', match: (p: string) => p.startsWith('/docs/publier') },
57
+ ];
58
+
59
+ function Header({ onSearch, onMenu, menuOpen }: { onSearch: () => void; onMenu: () => void; menuOpen: boolean }) {
60
+ const { pathname } = useLocation();
61
+ return (
62
+ <header className="sticky top-0 z-40 border-b border-slate-200/80 bg-white/90 backdrop-blur">
63
+ <div className="mx-auto flex h-16 max-w-[1440px] items-center gap-4 px-4 sm:px-6">
64
+ <button
65
+ type="button"
66
+ onClick={onMenu}
67
+ className="-ml-1 inline-flex h-9 w-9 items-center justify-center rounded-lg text-slate-600 hover:bg-slate-100 lg:hidden"
68
+ aria-label={menuOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
69
+ >
70
+ {menuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
71
+ </button>
72
+ <Brand />
73
+ <nav className="ml-4 hidden items-center gap-1 lg:flex">
74
+ {TOP_LINKS.map((l) => (
75
+ <Link
76
+ key={l.to}
77
+ to={l.to}
78
+ className={`rounded-md px-3 py-2 text-sm font-medium transition ${l.match(pathname) ? 'text-navy' : 'text-slate-500 hover:text-navy'}`}
79
+ >
80
+ {l.label}
81
+ </Link>
82
+ ))}
83
+ </nav>
84
+ <div className="ml-auto flex items-center gap-2">
85
+ <button
86
+ type="button"
87
+ onClick={onSearch}
88
+ className="hidden h-9 w-60 items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 text-sm text-slate-400 transition hover:border-slate-300 md:flex"
89
+ >
90
+ <Search className="h-4 w-4" />
91
+ Rechercher…
92
+ <kbd className="ml-auto rounded border border-slate-200 bg-white px-1.5 font-sans text-[11px] text-slate-400">Ctrl K</kbd>
93
+ </button>
94
+ <button
95
+ type="button"
96
+ onClick={onSearch}
97
+ className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-slate-600 hover:bg-slate-100 md:hidden"
98
+ aria-label="Rechercher"
99
+ >
100
+ <Search className="h-5 w-5" />
101
+ </button>
102
+ <a
103
+ href={MARKETPLACE_URL}
104
+ className="hidden items-center gap-1 rounded-md px-3 py-2 text-sm font-medium text-slate-500 hover:text-navy xl:inline-flex"
105
+ >
106
+ Marketplace <ArrowUpRight className="h-3.5 w-3.5" />
107
+ </a>
108
+ <a
109
+ href={`${MARKETPLACE_URL}/developer`}
110
+ className="hidden h-9 items-center rounded-lg bg-navy px-3.5 text-sm font-semibold text-white transition hover:bg-navy-2 sm:inline-flex"
111
+ >
112
+ Espace vendeur
113
+ </a>
114
+ </div>
115
+ </div>
116
+ </header>
117
+ );
118
+ }
119
+
120
+ /* ------------------------------------------------------------------ Navigation latérale */
121
+
122
+ function SideNav({ onNavigate }: { onNavigate?: () => void }) {
123
+ return (
124
+ <nav className="space-y-7 text-sm">
125
+ {SECTIONS.map((section) => (
126
+ <div key={section}>
127
+ <div className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-slate-400">{section}</div>
128
+ <ul className="space-y-0.5">
129
+ {PAGES.filter((p) => p.section === section).map((p) => (
130
+ <li key={p.slug}>
131
+ <NavLink
132
+ to={docPath(p)}
133
+ onClick={onNavigate}
134
+ className={({ isActive }) =>
135
+ `block rounded-md px-3 py-1.5 transition ${isActive ? 'bg-teal-50 font-medium text-brand-dark' : 'text-slate-600 hover:bg-slate-50 hover:text-navy'}`
136
+ }
137
+ >
138
+ {p.title}
139
+ </NavLink>
140
+ </li>
141
+ ))}
142
+ </ul>
143
+ </div>
144
+ ))}
145
+ <div className="border-t border-slate-100 pt-5">
146
+ <ul className="space-y-0.5">
147
+ {[
148
+ [MARKETPLACE_URL, 'Marketplace'],
149
+ [COMMUNITY_URL, 'Community'],
150
+ [SITE_URL, 'snapify.tn'],
151
+ ].map(([href, label]) => (
152
+ <li key={href}>
153
+ <a href={href} className="flex items-center gap-1 rounded-md px-3 py-1.5 text-slate-500 hover:bg-slate-50 hover:text-navy">
154
+ {label} <ArrowUpRight className="h-3.5 w-3.5" />
155
+ </a>
156
+ </li>
157
+ ))}
158
+ </ul>
159
+ </div>
160
+ </nav>
161
+ );
162
+ }
163
+
164
+ function MobileNav({ open, onClose }: { open: boolean; onClose: () => void }) {
165
+ return (
166
+ <div className={`fixed inset-0 top-16 z-30 lg:hidden ${open ? '' : 'pointer-events-none'}`} aria-hidden={!open}>
167
+ <div className={`absolute inset-0 bg-navy/30 transition-opacity ${open ? 'opacity-100' : 'opacity-0'}`} onClick={onClose} />
168
+ <div
169
+ className={`absolute inset-y-0 left-0 w-[86%] max-w-xs overflow-y-auto border-r border-slate-200 bg-white px-3 py-6 shadow-xl transition-transform ${open ? 'translate-x-0' : '-translate-x-full'}`}
170
+ >
171
+ <SideNav onNavigate={onClose} />
172
+ <a
173
+ href={`${MARKETPLACE_URL}/developer`}
174
+ className="mx-3 mt-6 flex h-10 items-center justify-center rounded-lg bg-navy text-sm font-semibold text-white"
175
+ >
176
+ Espace vendeur
177
+ </a>
178
+ </div>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ /* ------------------------------------------------------------------ Recherche */
184
+
185
+ function SearchDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
186
+ const [q, setQ] = useState('');
187
+ const [active, setActive] = useState(0);
188
+ const navigate = useNavigate();
189
+ const input = useRef<HTMLInputElement>(null);
190
+
191
+ const results = useMemo(() => {
192
+ const terms = norm(q).split(/\s+/).filter(Boolean);
193
+ if (!terms.length) return PAGES;
194
+ return PAGES.filter((p) => {
195
+ const hay = norm(`${p.title} ${p.section} ${p.description} ${p.keywords || ''} ${p.slug}`);
196
+ return terms.every((t) => hay.includes(t));
197
+ });
198
+ }, [q]);
199
+
200
+ useEffect(() => {
201
+ if (!open) return;
202
+ setQ('');
203
+ setActive(0);
204
+ window.setTimeout(() => input.current?.focus(), 20);
205
+ }, [open]);
206
+ useEffect(() => setActive(0), [q]);
207
+
208
+ useEffect(() => {
209
+ if (!open) return;
210
+ const onKey = (e: KeyboardEvent) => {
211
+ if (e.key === 'Escape') onClose();
212
+ };
213
+ window.addEventListener('keydown', onKey);
214
+ const prev = document.body.style.overflow;
215
+ document.body.style.overflow = 'hidden';
216
+ return () => {
217
+ window.removeEventListener('keydown', onKey);
218
+ document.body.style.overflow = prev;
219
+ };
220
+ }, [open, onClose]);
221
+
222
+ if (!open) return null;
223
+ const go = (p: DocPage) => {
224
+ navigate(docPath(p));
225
+ onClose();
226
+ };
227
+ return (
228
+ <div
229
+ className="fixed inset-0 z-50 flex items-start justify-center bg-navy/40 px-4 pt-[12vh] backdrop-blur-sm"
230
+ onClick={onClose}
231
+ role="presentation"
232
+ >
233
+ <div
234
+ role="dialog"
235
+ aria-modal="true"
236
+ aria-label="Rechercher dans la documentation"
237
+ className="w-full max-w-xl overflow-hidden rounded-2xl bg-white shadow-2xl ring-1 ring-slate-200"
238
+ onClick={(e) => e.stopPropagation()}
239
+ >
240
+ <div className="flex items-center gap-3 border-b border-slate-100 px-4">
241
+ <Search className="h-5 w-5 text-slate-400" aria-hidden />
242
+ <input
243
+ ref={input}
244
+ value={q}
245
+ onChange={(e) => setQ(e.target.value)}
246
+ aria-autocomplete="list"
247
+ aria-controls="dv-search-results"
248
+ onKeyDown={(e) => {
249
+ if (e.key === 'Escape') onClose();
250
+ if (e.key === 'ArrowDown') {
251
+ e.preventDefault();
252
+ setActive((a) => Math.min(a + 1, results.length - 1));
253
+ }
254
+ if (e.key === 'ArrowUp') {
255
+ e.preventDefault();
256
+ setActive((a) => Math.max(a - 1, 0));
257
+ }
258
+ if (e.key === 'Enter' && results[active]) go(results[active]);
259
+ }}
260
+ placeholder="Manifeste, widgets, permissions, thème…"
261
+ className="h-14 flex-1 bg-transparent text-[15px] text-navy outline-none placeholder:text-slate-400"
262
+ />
263
+ <kbd className="rounded border border-slate-200 px-1.5 text-[11px] text-slate-400">Échap</kbd>
264
+ </div>
265
+ <ul id="dv-search-results" role="listbox" className="max-h-[55vh] overflow-y-auto p-2">
266
+ {results.map((p, i) => (
267
+ <li key={p.slug}>
268
+ <button
269
+ type="button"
270
+ onMouseEnter={() => setActive(i)}
271
+ onClick={() => go(p)}
272
+ className={`flex w-full items-start gap-3 rounded-lg px-3 py-2.5 text-left ${i === active ? 'bg-teal-50' : ''}`}
273
+ >
274
+ <FileCode2 className={`mt-0.5 h-4 w-4 shrink-0 ${i === active ? 'text-brand-dark' : 'text-slate-400'}`} />
275
+ <span className="min-w-0">
276
+ <span className="block text-sm font-medium text-navy">
277
+ <span className="text-slate-400">{p.section} › </span>
278
+ {p.title}
279
+ </span>
280
+ <span className="block truncate text-[13px] text-slate-500">{p.description}</span>
281
+ </span>
282
+ </button>
283
+ </li>
284
+ ))}
285
+ {!results.length && <li className="px-3 py-8 text-center text-sm text-slate-500">Aucun résultat pour « {q} ».</li>}
286
+ </ul>
287
+ </div>
288
+ </div>
289
+ );
290
+ }
291
+
292
+ /* ------------------------------------------------------------------ Page de documentation */
293
+
294
+ function Toc({ deps }: { deps: string }) {
295
+ const [items, setItems] = useState<{ id: string; text: string; level: number }[]>([]);
296
+ const [active, setActive] = useState('');
297
+ useEffect(() => {
298
+ const heads = Array.from(document.querySelectorAll<HTMLElement>('article .dv-prose h2, article .dv-prose h3'));
299
+ setItems(heads.map((h) => ({ id: h.id, text: h.textContent || '', level: h.tagName === 'H3' ? 3 : 2 })));
300
+ const obs = new IntersectionObserver(
301
+ (entries) => {
302
+ const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
303
+ if (visible[0]) setActive(visible[0].target.id);
304
+ },
305
+ { rootMargin: '-80px 0px -70% 0px' }
306
+ );
307
+ heads.forEach((h) => obs.observe(h));
308
+ return () => obs.disconnect();
309
+ }, [deps]);
310
+ if (items.length < 2) return null;
311
+ return (
312
+ <div className="text-sm">
313
+ <div className="mb-3 text-[11px] font-semibold uppercase tracking-wider text-slate-400">Sur cette page</div>
314
+ <ul className="space-y-1.5 border-l border-slate-200">
315
+ {items.map((it) => (
316
+ <li key={it.id}>
317
+ <a
318
+ href={`#${it.id}`}
319
+ onClick={(e) => {
320
+ e.preventDefault();
321
+ document.getElementById(it.id)?.scrollIntoView({ behavior: 'smooth' });
322
+ history.replaceState(null, '', `#${it.id}`);
323
+ }}
324
+ className={`-ml-px block border-l py-0.5 transition ${it.level === 3 ? 'pl-6' : 'pl-4'} ${active === it.id ? 'border-brand font-medium text-brand-dark' : 'border-transparent text-slate-500 hover:text-navy'}`}
325
+ >
326
+ {it.text}
327
+ </a>
328
+ </li>
329
+ ))}
330
+ </ul>
331
+ </div>
332
+ );
333
+ }
334
+
335
+ function DocView() {
336
+ const params = useParams();
337
+ const slug = params['*'] || '';
338
+ const index = PAGES.findIndex((p) => p.slug === slug.replace(/\/$/, ''));
339
+ const page = PAGES[index];
340
+ const { hash } = useLocation();
341
+
342
+ useEffect(() => {
343
+ if (!page) return;
344
+ document.title = `${page.title} — Snapify Developers`;
345
+ if (hash) window.setTimeout(() => document.getElementById(hash.slice(1))?.scrollIntoView(), 50);
346
+ else window.scrollTo(0, 0);
347
+ }, [page, hash]);
348
+
349
+ if (!page) return <Navigate to="/docs/introduction" replace />;
350
+ const prev = PAGES[index - 1];
351
+ const next = PAGES[index + 1];
352
+ const Body = page.Body;
353
+
354
+ return (
355
+ <div className="mx-auto flex max-w-[1440px] gap-10 px-4 sm:px-6">
356
+ <aside className="sticky top-16 hidden h-[calc(100vh-4rem)] w-60 shrink-0 overflow-y-auto py-8 pr-2 lg:block">
357
+ <SideNav />
358
+ </aside>
359
+ <article className="min-w-0 flex-1 py-10 lg:py-12">
360
+ <div className="mx-auto max-w-3xl">
361
+ <div className="text-sm font-semibold text-brand-dark">{page.section}</div>
362
+ <h1 className="mt-2 text-3xl font-bold tracking-tight text-navy sm:text-[2.15rem]">{page.title}</h1>
363
+ <p className="mt-3 text-lg leading-relaxed text-slate-500">{page.description}</p>
364
+ <div className="dv-prose mt-8">
365
+ <Body />
366
+ </div>
367
+ <div className="mt-14 grid gap-3 border-t border-slate-200 pt-8 sm:grid-cols-2">
368
+ {prev ? (
369
+ <Link to={docPath(prev)} className="group rounded-xl border border-slate-200 p-4 transition hover:border-brand">
370
+ <div className="flex items-center gap-1 text-xs text-slate-400">
371
+ <ChevronLeft className="h-3.5 w-3.5" /> Précédent
372
+ </div>
373
+ <div className="mt-1 font-semibold text-navy group-hover:text-brand-dark">{prev.title}</div>
374
+ </Link>
375
+ ) : (
376
+ <span />
377
+ )}
378
+ {next && (
379
+ <Link to={docPath(next)} className="group rounded-xl border border-slate-200 p-4 text-right transition hover:border-brand">
380
+ <div className="flex items-center justify-end gap-1 text-xs text-slate-400">
381
+ Suivant <ChevronRight className="h-3.5 w-3.5" />
382
+ </div>
383
+ <div className="mt-1 font-semibold text-navy group-hover:text-brand-dark">{next.title}</div>
384
+ </Link>
385
+ )}
386
+ </div>
387
+ </div>
388
+ </article>
389
+ <aside className="sticky top-16 hidden h-[calc(100vh-4rem)] w-56 shrink-0 overflow-y-auto py-12 xl:block">
390
+ <Toc deps={page.slug} />
391
+ </aside>
392
+ </div>
393
+ );
394
+ }
395
+
396
+ /* ------------------------------------------------------------------ Accueil */
397
+
398
+ const HERO_CODE = `import { definePublicModule } from '@snapify/sdk';
399
+
400
+ function Bubble({ settings, api }) {
401
+ return (
402
+ <button onClick={() => api.post('/hello')}>
403
+ {settings.label}
404
+ </button>
405
+ );
406
+ }
407
+
408
+ export default definePublicModule({
409
+ slots: { floating: Bubble },
410
+ });`;
411
+
412
+ function Card({ to, icon, title, children }: { to: string; icon: ReactNode; title: string; children: ReactNode }) {
413
+ return (
414
+ <Link to={to} className="group flex flex-col rounded-2xl border border-slate-200 bg-white p-6 transition hover:-translate-y-0.5 hover:border-brand hover:shadow-lg hover:shadow-teal-900/5">
415
+ <span className="flex h-11 w-11 items-center justify-center rounded-xl bg-teal-50 text-brand-dark ring-1 ring-teal-100">{icon}</span>
416
+ <span className="mt-5 text-lg font-semibold text-navy">{title}</span>
417
+ <span className="mt-2 flex-1 text-[15px] leading-relaxed text-slate-500">{children}</span>
418
+ <span className="mt-5 inline-flex items-center gap-1 text-sm font-semibold text-brand-dark">
419
+ Lire le guide <ArrowRight className="h-4 w-4 transition group-hover:translate-x-0.5" />
420
+ </span>
421
+ </Link>
422
+ );
423
+ }
424
+
425
+ function Home() {
426
+ useEffect(() => {
427
+ document.title = 'Snapify Developers — Créer des modules et des thèmes';
428
+ }, []);
429
+ return (
430
+ <>
431
+ <section className="dv-grid-bg relative overflow-hidden bg-navy">
432
+ <div className="pointer-events-none absolute -top-40 right-[-10%] h-[520px] w-[520px] rounded-full bg-brand/20 blur-3xl" />
433
+ <div className="relative mx-auto grid max-w-6xl items-center gap-12 px-4 py-16 sm:px-6 lg:grid-cols-[1.1fr_1fr] lg:py-24">
434
+ <div>
435
+ <span className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3 py-1 text-xs font-medium text-teal-200">
436
+ <span className="h-1.5 w-1.5 rounded-full bg-brand" /> SDK Snapify · API {API_VERSION}
437
+ </span>
438
+ <h1 className="mt-6 text-4xl font-extrabold leading-[1.08] tracking-tight text-white sm:text-5xl lg:text-[3.4rem]">
439
+ Construisez pour
440
+ <br />
441
+ <span className="text-brand">le CMS tunisien.</span>
442
+ </h1>
443
+ <p className="mt-5 max-w-xl text-lg leading-relaxed text-slate-300">
444
+ Créez des modules et des thèmes en React et TypeScript, puis vendez-les à tous les sites Snapify depuis la marketplace.
445
+ </p>
446
+ <div className="mt-8 flex flex-wrap gap-3">
447
+ <Link to="/docs/demarrage-rapide" className="inline-flex h-11 items-center gap-2 rounded-lg bg-brand px-5 text-sm font-semibold text-navy transition hover:bg-teal-300">
448
+ Créer mon premier module <ArrowRight className="h-4 w-4" />
449
+ </Link>
450
+ <Link to="/docs/kit-developpeur" className="inline-flex h-11 items-center gap-2 rounded-lg border border-white/20 px-5 text-sm font-semibold text-white transition hover:bg-white/10">
451
+ <Download className="h-4 w-4" /> Installer le kit
452
+ </Link>
453
+ </div>
454
+ <div className="mt-6 flex flex-col items-start gap-4">
455
+ <div className="inline-flex items-center gap-3 rounded-lg border border-white/10 bg-white/5 px-4 py-2.5 font-mono text-sm text-slate-200">
456
+ <span className="select-none text-brand">$</span> npm create snapify@latest
457
+ </div>
458
+ <Link to="/docs/introduction" className="inline-flex items-center gap-2 text-sm font-medium text-slate-300 transition hover:text-white">
459
+ <BookOpen className="h-4 w-4" /> Lire la documentation
460
+ </Link>
461
+ </div>
462
+ </div>
463
+ <div className="min-w-0 lg:pl-6">
464
+ <Code lang="tsx" title="src/public.tsx">
465
+ {HERO_CODE}
466
+ </Code>
467
+ </div>
468
+ </div>
469
+ </section>
470
+
471
+ <section className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:py-20">
472
+ <h2 className="text-2xl font-bold tracking-tight text-navy sm:text-3xl">Par où commencer ?</h2>
473
+ <p className="mt-2 text-slate-500">Des guides pas à pas, du premier composant jusqu’à la publication.</p>
474
+ <div className="mt-8 grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
475
+ <Card to="/docs/demarrage-rapide" icon={<Puzzle className="h-5 w-5" />} title="Modules">
476
+ Widgets, pages, API serveur, base de données et pages d’administration.
477
+ </Card>
478
+ <Card to="/docs/themes/creer-un-theme" icon={<LayoutTemplate className="h-5 w-5" />} title="Thèmes">
479
+ En-tête, pied de page et rendu des 26 blocs de l’éditeur, avec options personnalisables.
480
+ </Card>
481
+ <Card to="/docs/paquets" icon={<PackageCheck className="h-5 w-5" />} title="Paquets">
482
+ Construire un paquet, le vérifier, comprendre la signature et les licences.
483
+ </Card>
484
+ <Card to="/docs/publier" icon={<Store className="h-5 w-5" />} title="Marketplace">
485
+ Devenir développeur, publier, mettre à jour et suivre vos ventes.
486
+ </Card>
487
+ </div>
488
+ </section>
489
+
490
+ <section className="border-y border-slate-200 bg-slate-50">
491
+ <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:py-20">
492
+ <h2 className="text-2xl font-bold tracking-tight text-navy sm:text-3xl">Du code à la marketplace</h2>
493
+ <div className="mt-10 grid gap-8 md:grid-cols-4">
494
+ {[
495
+ { icon: <FileCode2 className="h-5 w-5" />, t: 'Développer', d: <><code className="font-mono text-[13px]">npm create snapify</code> prépare le projet : un manifeste, des composants React, du TypeScript côté serveur.</> },
496
+ { icon: <Boxes className="h-5 w-5" />, t: 'Empaqueter', d: <><code className="font-mono text-[13px]">npm run build</code> produit le <code className="font-mono text-[13px]">.snapmod</code> et rejoue l’analyse de la marketplace.</> },
497
+ { icon: <ShieldCheck className="h-5 w-5" />, t: 'Revue et signature', d: 'NOVALABS relit votre code puis signe la version validée.' },
498
+ { icon: <Store className="h-5 w-5" />, t: 'Publier', d: 'Vos clients l’installent en un clic depuis leur administration.' },
499
+ ].map((s, i) => (
500
+ <div key={s.t}>
501
+ <div className="flex items-center gap-3">
502
+ <span className="flex h-10 w-10 items-center justify-center rounded-full bg-navy text-white">{s.icon}</span>
503
+ <span className="text-xs font-semibold text-slate-400">ÉTAPE {i + 1}</span>
504
+ </div>
505
+ <div className="mt-4 font-semibold text-navy">{s.t}</div>
506
+ <div className="mt-1 text-[15px] leading-relaxed text-slate-500">{s.d}</div>
507
+ </div>
508
+ ))}
509
+ </div>
510
+ </div>
511
+ </section>
512
+
513
+ <section className="mx-auto max-w-6xl px-4 py-16 sm:px-6 lg:py-20">
514
+ <div className="grid items-center gap-10 lg:grid-cols-2">
515
+ <div>
516
+ <h2 className="text-2xl font-bold tracking-tight text-navy sm:text-3xl">Un contexte serveur sûr</h2>
517
+ <p className="mt-3 text-[15px] leading-relaxed text-slate-500">
518
+ Votre module déclare ce dont il a besoin ; le site lui donne exactement cela. Routes HTTP, collections, e-mails, appels vers une API
519
+ externe : chaque capacité est liée à une permission affichée au client avant l’installation.
520
+ </p>
521
+ <div className="mt-6 flex flex-wrap gap-2">
522
+ {['http:routes', 'database', 'storage', 'files', 'mail:send', 'admin:routes', 'net:api.exemple.tn'].map((p) => (
523
+ <span key={p} className="rounded-md bg-slate-100 px-2 py-1 font-mono text-xs text-slate-600">
524
+ {p}
525
+ </span>
526
+ ))}
527
+ </div>
528
+ <Link to="/docs/modules/serveur" className="mt-6 inline-flex items-center gap-1 text-sm font-semibold text-brand-dark hover:underline">
529
+ Découvrir l’API serveur <ArrowRight className="h-4 w-4" />
530
+ </Link>
531
+ </div>
532
+ <div className="min-w-0">
533
+ <Code lang="ts" title="src/server.ts">{`ctx.http.post('/inscription', async (req) => {
534
+ const email = String(req.body?.email || '').trim();
535
+ if (!email.includes('@')) throw ctx.error(422, 'E-mail invalide');
536
+
537
+ const list = ctx.db.collection('inscrits');
538
+ await list.insert({ email });
539
+
540
+ const { html } = ctx.mail.render({ title: 'Bienvenue', intro: 'Merci !' });
541
+ await ctx.mail.send({ to: email, subject: 'Bienvenue', html });
542
+ return { ok: true };
543
+ });`}</Code>
544
+ </div>
545
+ </div>
546
+ </section>
547
+
548
+ <section className="mx-auto max-w-6xl px-4 pb-20 sm:px-6">
549
+ <div className="flex flex-col items-start justify-between gap-6 rounded-2xl bg-navy p-8 sm:flex-row sm:items-center sm:p-10">
550
+ <div>
551
+ <h2 className="text-2xl font-bold text-white">Prêt à publier ?</h2>
552
+ <p className="mt-2 max-w-xl text-slate-300">Ouvrez votre espace développeur sur la marketplace et proposez votre première extension.</p>
553
+ </div>
554
+ <div className="flex flex-wrap gap-3">
555
+ <a href={`${MARKETPLACE_URL}/developer`} className="inline-flex h-11 items-center rounded-lg bg-brand px-5 text-sm font-semibold text-navy hover:bg-teal-300">
556
+ Devenir développeur
557
+ </a>
558
+ <Link to="/docs/publier" className="inline-flex h-11 items-center rounded-lg border border-white/20 px-5 text-sm font-semibold text-white hover:bg-white/10">
559
+ Comment ça marche
560
+ </Link>
561
+ </div>
562
+ </div>
563
+ </section>
564
+ </>
565
+ );
566
+ }
567
+
568
+ /* ------------------------------------------------------------------ Pied de page */
569
+
570
+ function Footer() {
571
+ return (
572
+ <footer className="border-t border-slate-200 bg-white">
573
+ <div className="mx-auto flex max-w-[1440px] flex-col gap-4 px-4 py-8 text-sm text-slate-500 sm:px-6 md:flex-row md:items-center md:justify-between">
574
+ <div className="flex items-center gap-2">
575
+ <span className="flex h-7 w-7 items-center justify-center rounded-md bg-navy">
576
+ <SnapifyMark className="h-5 w-5" />
577
+ </span>
578
+ © {new Date().getFullYear()} NOVALABS WEB DESIGN — Tunisie
579
+ </div>
580
+ <div className="flex flex-wrap gap-x-5 gap-y-2">
581
+ <a href={MARKETPLACE_URL} className="hover:text-navy">
582
+ Marketplace
583
+ </a>
584
+ <a href={COMMUNITY_URL} className="hover:text-navy">
585
+ Community
586
+ </a>
587
+ <a href={SITE_URL} className="hover:text-navy">
588
+ snapify.tn
589
+ </a>
590
+ <a href="https://snapify.fr" className="hover:text-navy">
591
+ snapify.fr
592
+ </a>
593
+ <a href="https://novalabs.tn" className="hover:text-navy" rel="noopener noreferrer">
594
+ NOVALABS
595
+ </a>
596
+ <a href={`mailto:${CONTACT_EMAIL}`} className="hover:text-navy">
597
+ {CONTACT_EMAIL}
598
+ </a>
599
+ </div>
600
+ </div>
601
+ </footer>
602
+ );
603
+ }
604
+
605
+ /* ------------------------------------------------------------------ Application */
606
+
607
+ export function App() {
608
+ const [searchOpen, setSearchOpen] = useState(false);
609
+ const [menuOpen, setMenuOpen] = useState(false);
610
+ const { pathname } = useLocation();
611
+
612
+ useEffect(() => setMenuOpen(false), [pathname]);
613
+ useEffect(() => {
614
+ document.body.style.overflow = menuOpen ? 'hidden' : '';
615
+ }, [menuOpen]);
616
+ useEffect(() => {
617
+ const onKey = (e: KeyboardEvent) => {
618
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
619
+ e.preventDefault();
620
+ setSearchOpen(true);
621
+ }
622
+ };
623
+ window.addEventListener('keydown', onKey);
624
+ return () => window.removeEventListener('keydown', onKey);
625
+ }, []);
626
+
627
+ return (
628
+ <div className="flex min-h-screen flex-col">
629
+ <Header onSearch={() => setSearchOpen(true)} onMenu={() => setMenuOpen((o) => !o)} menuOpen={menuOpen} />
630
+ <MobileNav open={menuOpen} onClose={() => setMenuOpen(false)} />
631
+ <main className="flex-1">
632
+ <Routes>
633
+ <Route path="/" element={<Home />} />
634
+ <Route path="/docs" element={<Navigate to="/docs/introduction" replace />} />
635
+ <Route path="/docs/*" element={<DocView />} />
636
+ <Route path="*" element={<Navigate to="/" replace />} />
637
+ </Routes>
638
+ </main>
639
+ <Footer />
640
+ <SearchDialog open={searchOpen} onClose={() => setSearchOpen(false)} />
641
+ </div>
642
+ );
643
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Snapify Developers
3
+ * © 2026 NOVALABS WEB DESIGN — Tunisie
4
+ */
5
+ import React from 'react';
6
+ import { createRoot } from 'react-dom/client';
7
+ import { BrowserRouter } from 'react-router-dom';
8
+ import { App } from './App';
9
+ import './styles.css';
10
+
11
+ createRoot(document.getElementById('root')!).render(
12
+ <React.StrictMode>
13
+ <BrowserRouter>
14
+ <App />
15
+ </BrowserRouter>
16
+ </React.StrictMode>
17
+ );