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,21 @@
1
+ <!doctype html>
2
+ <!--
3
+ Snapify — Espace client
4
+ © 2026 NOVALABS WEB DESIGN — Tunisie
5
+ -->
6
+ <html lang="fr">
7
+ <head>
8
+ <meta charset="UTF-8" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <meta name="robots" content="noindex,nofollow" />
11
+ <title>Espace client — Snapify</title>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
15
+ <link rel="icon" type="image/svg+xml" href="/logo.svg" />
16
+ </head>
17
+ <body class="bg-slate-50 text-slate-900 antialiased">
18
+ <div id="root"></div>
19
+ <script type="module" src="/apps/client/src/main.tsx"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,23 @@
1
+ # Akhbar — thème magazine d’actualités
2
+
3
+ Thème de presse pour Snapify : manchette avec la date du jour, barre des rubriques, bandeau « Urgent », pages d’article soignées et typographie de journal. Prend en charge les 25 blocs du constructeur de pages.
4
+
5
+ ## Installation
6
+
7
+ 1. Achetez le thème sur la marketplace Snapify, puis installez-le depuis **Apparence > Thèmes** de votre administration avec votre clé de licence.
8
+ 2. Installez les modules gratuits **Akhbar Actualités** (widgets « À la une », « Dernières actualités », « Rubrique », « Les plus lus », archives `/actualites/`) et **Akhbar Urgent** (flashs info).
9
+ 3. Créez vos rubriques dans le menu principal (ex. `/rubrique/politique/`) : la rubrique d’un article est sa **première étiquette**.
10
+
11
+ ## Réglages
12
+
13
+ - **Couleurs** : fond papier journal, blanc ou identité visuelle ; accent rouge dépêche, bleu presse, vert olive ou couleur du site.
14
+ - **Typographie** : Classique (Playfair Display), Éditorial (Newsreader), Moderne (Inter) ou polices de l’identité visuelle.
15
+ - **Manchette** : mention d’édition, date du jour, logo centré ou à gauche, barre des rubriques fixe, bandeau « Urgent ».
16
+ - **Articles** : temps de lecture, boutons de partage, articles liés, barre de progression de lecture.
17
+ - **Pied de page** : lettre d’information (si activée dans les paramètres du site) et réseaux sociaux.
18
+
19
+ ## Page d’accueil conseillée
20
+
21
+ Dans l’éditeur de pages, ajoutez des blocs **Module** dans cet ordre : « À la une » (mosaïque), « Dernières actualités » (ignorer les 5 premiers), puis un bloc « Rubrique » par rubrique et « Les plus lus ».
22
+
23
+ © 2026 Hamdi Ayari
@@ -0,0 +1,76 @@
1
+ {
2
+ "id": "hamdiayari.akhbar",
3
+ "kind": "theme",
4
+ "name": "Akhbar",
5
+ "version": "1.0.0",
6
+ "description": "Thème magazine d’actualités : manchette avec la date du jour, barre des rubriques, bandeau « Urgent », mise en page d’article soignée (chapô, temps de lecture, partage, articles liés, barre de progression) et typographie de presse. Conçu pour les modules Akhbar Actualités et Akhbar Urgent ; prend en charge les 25 blocs du constructeur.",
7
+ "author": { "name": "Hamdi Ayari", "email": "hamdi.ayari@novalabs.tn" },
8
+ "pricing": "paid",
9
+ "category": "Presse",
10
+ "engines": { "snapify": "^1.2.0" },
11
+ "entries": { "public": "src/index.tsx" },
12
+ "screenshot": "screenshot.jpg",
13
+ "settings": [
14
+ {
15
+ "key": "palette", "type": "select", "group": "Couleurs", "label": "Fond", "default": "paper",
16
+ "help": "« Identité visuelle » reprend les couleurs choisies dans Apparence > Identité visuelle.",
17
+ "options": [
18
+ { "value": "paper", "label": "Papier journal (blanc cassé)" },
19
+ { "value": "white", "label": "Blanc pur" },
20
+ { "value": "brand", "label": "Identité visuelle du site" }
21
+ ]
22
+ },
23
+ {
24
+ "key": "accent", "type": "select", "group": "Couleurs", "label": "Couleur d’accent", "default": "red",
25
+ "help": "Rubriques, bandeau « Urgent », liens et boutons.",
26
+ "options": [
27
+ { "value": "red", "label": "Rouge dépêche" },
28
+ { "value": "blue", "label": "Bleu presse" },
29
+ { "value": "green", "label": "Vert olive" },
30
+ { "value": "brand", "label": "Couleur principale du site" }
31
+ ]
32
+ },
33
+ {
34
+ "key": "typography", "type": "select", "group": "Typographie", "label": "Style des titres", "default": "classic",
35
+ "options": [
36
+ { "value": "classic", "label": "Classique (Playfair Display)" },
37
+ { "value": "editorial", "label": "Éditorial (Newsreader)" },
38
+ { "value": "modern", "label": "Moderne sans empattements (Inter)" },
39
+ { "value": "brand", "label": "Polices de l’identité visuelle" }
40
+ ]
41
+ },
42
+ {
43
+ "key": "corners", "type": "select", "group": "Style", "label": "Angles", "default": "sharp",
44
+ "options": [
45
+ { "value": "sharp", "label": "Nets (presse)" },
46
+ { "value": "soft", "label": "Adoucis" }
47
+ ]
48
+ },
49
+ { "key": "photoFrames", "type": "boolean", "group": "Style", "label": "Filet décalé sur les visuels", "help": "Les grands visuels reçoivent un filet couleur d’accent légèrement décalé.", "default": false },
50
+ { "key": "animations", "type": "boolean", "group": "Style", "label": "Animations", "help": "Apparition des sections, défilements et compteurs.", "default": true },
51
+
52
+ { "key": "edition", "type": "text", "group": "Manchette", "label": "Mention d’édition", "help": "Affichée à côté de la date, ex. « Édition Tunisie ».", "default": "Édition Tunisie", "maxLength": 60 },
53
+ { "key": "showDate", "type": "boolean", "group": "Manchette", "label": "Date du jour", "default": true },
54
+ { "key": "mastheadAlign", "type": "select", "group": "Manchette", "label": "Logo", "default": "center",
55
+ "options": [
56
+ { "value": "center", "label": "Centré (quotidien)" },
57
+ { "value": "left", "label": "À gauche (magazine)" }
58
+ ]
59
+ },
60
+ { "key": "stickyNav", "type": "boolean", "group": "Manchette", "label": "Barre des rubriques fixe", "help": "La barre des rubriques reste visible pendant la lecture.", "default": true },
61
+ { "key": "ticker", "type": "boolean", "group": "Manchette", "label": "Bandeau « Urgent »", "help": "Flashs info du module Akhbar Urgent, sous la barre des rubriques.", "default": true },
62
+
63
+ { "key": "readingTime", "type": "boolean", "group": "Articles", "label": "Temps de lecture", "default": true },
64
+ { "key": "shareButtons", "type": "boolean", "group": "Articles", "label": "Boutons de partage", "help": "Facebook, X, WhatsApp, LinkedIn et copie du lien.", "default": true },
65
+ { "key": "relatedPosts", "type": "boolean", "group": "Articles", "label": "Articles liés", "help": "Trois articles de la même rubrique en fin d’article.", "default": true },
66
+ { "key": "progressBar", "type": "boolean", "group": "Articles", "label": "Barre de progression de lecture", "default": true },
67
+
68
+ { "key": "newsletterTitle", "type": "text", "group": "Pied de page", "label": "Titre de la lettre d’information", "default": "La lettre du matin", "maxLength": 80 },
69
+ { "key": "newsletterText", "type": "text", "group": "Pied de page", "label": "Texte de la lettre d’information", "default": "L’essentiel de l’actualité chaque matin dans votre boîte mail, gratuitement.", "maxLength": 200 },
70
+ { "key": "facebook", "type": "text", "group": "Réseaux sociaux", "label": "Facebook", "default": "", "placeholder": "https://facebook.com/…", "maxLength": 200 },
71
+ { "key": "instagram", "type": "text", "group": "Réseaux sociaux", "label": "Instagram", "default": "", "placeholder": "https://instagram.com/…", "maxLength": 200 },
72
+ { "key": "x", "type": "text", "group": "Réseaux sociaux", "label": "X (Twitter)", "default": "", "placeholder": "https://x.com/…", "maxLength": 200 },
73
+ { "key": "youtube", "type": "text", "group": "Réseaux sociaux", "label": "YouTube", "default": "", "placeholder": "https://youtube.com/@…", "maxLength": 200 },
74
+ { "key": "linkedin", "type": "text", "group": "Réseaux sociaux", "label": "LinkedIn", "default": "", "placeholder": "https://linkedin.com/company/…", "maxLength": 200 }
75
+ ]
76
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Akhbar — thème presse pour Snapify
3
+ * © 2026 Hamdi Ayari
4
+ */
5
+ import React from 'react';
6
+ import { useSnapify } from '@snapify/sdk/theme';
7
+ import { cx } from './kit';
8
+
9
+ /** Titre du journal : logo du site ou, à défaut, nom composé en manchette. */
10
+ export function Brand({ size = 'lg', dark, onClick, tagline }: { size?: 'sm' | 'lg'; dark?: boolean; onClick: () => void; tagline?: boolean }) {
11
+ const { site, info } = useSnapify();
12
+ const logo = dark ? site.branding.logoDarkUrl || '' : site.branding.logoUrl;
13
+ const name = (site.branding.siteName || info.name).replace(/®$/, '');
14
+ return (
15
+ <button type="button" onClick={onClick} className="group flex min-w-0 flex-col items-center text-center" aria-label={`${name} — accueil`}>
16
+ {logo ? (
17
+ <img src={logo} alt={name} className={cx('w-auto object-contain', size === 'lg' ? 'h-12 max-w-[280px] sm:h-16' : 'h-7 max-w-[140px]')} />
18
+ ) : (
19
+ <span className={cx('ak-masthead-title truncate', size === 'lg' ? 'text-[2.6rem] sm:text-6xl lg:text-7xl' : 'text-[1.35rem]', dark && 'text-(--ak-on-dark)!')}>{name}</span>
20
+ )}
21
+ {tagline && site.branding.tagline ? (
22
+ <span className={cx('mt-2 hidden text-[11px] font-semibold uppercase tracking-[0.24em] sm:block', dark ? 'text-(--ak-on-dark)/60' : 'text-(--ak-muted)')}>{site.branding.tagline}</span>
23
+ ) : null}
24
+ </button>
25
+ );
26
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Akhbar — thème presse pour Snapify
3
+ * © 2026 Hamdi Ayari
4
+ */
5
+ import React, { useState } from 'react';
6
+ import { ArrowUp, Mail, MapPin, Phone } from 'lucide-react';
7
+ import { useSnapify, type ThemeChromeProps } from '@snapify/sdk/theme';
8
+ import { followLink, IS_PREVIEW, newsActive, socialLinks, useMenus, useOptions } from './site';
9
+ import { Brand } from './Brand';
10
+ import { SocialIcons } from './Social';
11
+ import { cx } from './kit';
12
+
13
+ const LEGAL = ['/mentions-legales/', '/politique-de-confidentialite/', '/cookies/'];
14
+
15
+ function Newsletter({ title, text }: { title: string; text: string }) {
16
+ const [email, setEmail] = useState('');
17
+ const [msg, setMsg] = useState<{ ok: boolean; text: string } | null>(null);
18
+ const [busy, setBusy] = useState(false);
19
+ return (
20
+ <section className="border-b border-(--ak-rule)">
21
+ <div className="mx-auto grid max-w-7xl gap-6 px-5 py-12 sm:px-8 lg:grid-cols-[1fr_1.1fr] lg:items-center lg:gap-16">
22
+ <div>
23
+ <p className="ak-kicker">Newsletter</p>
24
+ <p className="ak-display mt-3 text-3xl text-(--ak-fg) sm:text-4xl">{title || 'La lettre du matin'}</p>
25
+ {text ? <p className="mt-3 max-w-lg text-[15px] leading-relaxed text-(--ak-fg-2)">{text}</p> : null}
26
+ </div>
27
+ <form
28
+ onSubmit={async (e) => {
29
+ e.preventDefault();
30
+ if (IS_PREVIEW || busy) return;
31
+ setBusy(true);
32
+ setMsg(null);
33
+ const r = await fetch('/api/public/newsletter/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }).catch(() => null);
34
+ const j = await r?.json().catch(() => ({}));
35
+ setBusy(false);
36
+ if (r?.ok) {
37
+ setEmail('');
38
+ setMsg({ ok: true, text: j?.pending ? 'Vérifiez votre boîte mail pour confirmer votre inscription.' : 'Inscription enregistrée, merci !' });
39
+ } else setMsg({ ok: false, text: j?.error || 'Inscription impossible pour le moment.' });
40
+ }}
41
+ >
42
+ <div className="flex flex-col gap-2 sm:flex-row">
43
+ <label htmlFor="ak-news" className="sr-only">
44
+ Adresse e-mail
45
+ </label>
46
+ <input
47
+ id="ak-news"
48
+ type="email"
49
+ required
50
+ value={email}
51
+ onChange={(e) => setEmail(e.target.value)}
52
+ placeholder="Votre adresse e-mail"
53
+ className="min-w-0 flex-1 border border-white/25 bg-white/5 px-4 py-3.5 text-sm text-white placeholder:text-white/45 focus:border-white focus:outline-none"
54
+ />
55
+ <button type="submit" disabled={busy} className="ak-btn ak-btn-accent shrink-0 disabled:opacity-60">
56
+ S’inscrire
57
+ </button>
58
+ </div>
59
+ {msg ? <p className={cx('mt-2 text-xs', msg.ok ? 'text-(--ak-fg-2)' : 'text-rose-300')}>{msg.text}</p> : null}
60
+ </form>
61
+ </div>
62
+ </section>
63
+ );
64
+ }
65
+
66
+ export function Footer({ navigate, openCookies }: ThemeChromeProps) {
67
+ const { info, site } = useSnapify();
68
+ const o = useOptions();
69
+ const { primary, footer } = useMenus();
70
+ const go = (path: string, newTab?: boolean) => followLink(path, navigate, newTab);
71
+ const social = socialLinks(o);
72
+
73
+ const sections = primary.filter((i) => i.path !== '/').slice(0, 10);
74
+ const pages = footer.filter((i) => !LEGAL.includes(i.path));
75
+ const legal = footer.filter((i) => LEGAL.includes(i.path));
76
+ const legalLinks = legal.length
77
+ ? legal
78
+ : [
79
+ { id: 'ml', label: 'Mentions légales', path: '/mentions-legales/' },
80
+ { id: 'pc', label: 'Politique de confidentialité', path: '/politique-de-confidentialite/' },
81
+ ];
82
+ const name = (site.branding.siteName || info.name).replace(/®$/, '');
83
+ const journal = [...(newsActive() ? [{ id: 'all', label: 'Toutes les actualités', path: '/actualites/' }] : []), ...pages];
84
+
85
+ return (
86
+ <footer className="ak-dark mt-16 bg-(--ak-ink) text-(--ak-fg)">
87
+ {site.newsletterEnabled ? <Newsletter title={o.newsletterTitle} text={o.newsletterText} /> : null}
88
+
89
+ <div className="mx-auto max-w-7xl px-5 sm:px-8">
90
+ <div className="grid grid-cols-1 gap-12 py-14 sm:grid-cols-2 lg:grid-cols-12 lg:gap-10">
91
+ <div className="space-y-5 lg:col-span-4">
92
+ <div className="flex">
93
+ <Brand dark size="sm" onClick={() => go('/')} />
94
+ </div>
95
+ {info.slogan || site.branding.tagline ? <p className="max-w-sm text-sm leading-relaxed text-(--ak-fg-2)">{info.slogan || site.branding.tagline}</p> : null}
96
+ <SocialIcons links={social} className="-ml-2 text-(--ak-fg)" />
97
+ </div>
98
+
99
+ {sections.length ? (
100
+ <div className="lg:col-span-4">
101
+ <p className="mb-5 text-[11px] font-bold uppercase tracking-[0.18em] text-(--ak-fg-2)">Rubriques</p>
102
+ <ul className="grid grid-cols-2 gap-x-6 gap-y-3">
103
+ {sections.map((l) => (
104
+ <li key={l.id}>
105
+ <button type="button" onClick={() => go(l.path, l.openInNewTab)} className="text-left text-sm font-semibold transition-colors hover:text-(--ak-accent-text)">
106
+ {l.label}
107
+ </button>
108
+ </li>
109
+ ))}
110
+ </ul>
111
+ </div>
112
+ ) : null}
113
+
114
+ <div className="space-y-8 sm:col-span-2 lg:col-span-4 lg:grid lg:grid-cols-2 lg:gap-8 lg:space-y-0">
115
+ {journal.length ? (
116
+ <div>
117
+ <p className="mb-5 text-[11px] font-bold uppercase tracking-[0.18em] text-(--ak-fg-2)">Le journal</p>
118
+ <ul className="space-y-3">
119
+ {journal.map((l) => (
120
+ <li key={l.id}>
121
+ <button type="button" onClick={() => go(l.path, 'openInNewTab' in l ? !!l.openInNewTab : undefined)} className="text-left text-sm text-(--ak-fg-2) transition-colors hover:text-(--ak-fg)">
122
+ {l.label}
123
+ </button>
124
+ </li>
125
+ ))}
126
+ </ul>
127
+ </div>
128
+ ) : null}
129
+ <div>
130
+ <p className="mb-5 text-[11px] font-bold uppercase tracking-[0.18em] text-(--ak-fg-2)">Rédaction</p>
131
+ <ul className="space-y-3 text-sm text-(--ak-fg-2)">
132
+ {info.contactEmail ? (
133
+ <li>
134
+ <a href={`mailto:${info.contactEmail}`} className="inline-flex items-center gap-2.5 break-all transition-colors hover:text-(--ak-fg)">
135
+ <Mail className="h-4 w-4 shrink-0 text-(--ak-accent-text)" />
136
+ {info.contactEmail}
137
+ </a>
138
+ </li>
139
+ ) : null}
140
+ {info.phone ? (
141
+ <li>
142
+ <a href={`tel:${info.phoneClean}`} className="inline-flex items-center gap-2.5 transition-colors hover:text-(--ak-fg)">
143
+ <Phone className="h-4 w-4 shrink-0 text-(--ak-accent-text)" />
144
+ {info.phone}
145
+ </a>
146
+ </li>
147
+ ) : null}
148
+ {info.address ? (
149
+ <li className="flex items-start gap-2.5">
150
+ <MapPin className="mt-0.5 h-4 w-4 shrink-0 text-(--ak-accent-text)" />
151
+ {info.address}
152
+ </li>
153
+ ) : null}
154
+ </ul>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <div className="border-t border-(--ak-rule)">
161
+ <div className="mx-auto flex max-w-7xl flex-col items-center justify-between gap-4 px-5 py-6 text-xs text-(--ak-fg-2) sm:px-8 lg:flex-row">
162
+ <span>
163
+ © {new Date().getFullYear()} {info.legalName || name}. Tous droits de reproduction réservés.
164
+ </span>
165
+ <div className="flex flex-wrap items-center justify-center gap-x-5 gap-y-2">
166
+ {legalLinks.map((l) => (
167
+ <button key={l.id} type="button" onClick={() => go(l.path)} className="transition-colors hover:text-(--ak-fg)">
168
+ {l.label}
169
+ </button>
170
+ ))}
171
+ <button type="button" onClick={openCookies} className="transition-colors hover:text-(--ak-fg)">
172
+ Gérer mes cookies
173
+ </button>
174
+ <button type="button" onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} className="flex h-9 w-9 items-center justify-center border border-(--ak-rule) text-(--ak-fg) transition-colors hover:bg-(--ak-fg) hover:text-(--ak-ink)" aria-label="Revenir en haut">
175
+ <ArrowUp className="h-4 w-4" />
176
+ </button>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </footer>
181
+ );
182
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Akhbar — thème presse pour Snapify
3
+ * © 2026 Hamdi Ayari
4
+ */
5
+ import React, { useEffect, useState } from 'react';
6
+ import { ChevronDown, Menu, Minus, Plus, Search, X } from 'lucide-react';
7
+ import { hasModuleSlot, ModuleSlot, useSnapify, type ThemeChromeProps } from '@snapify/sdk/theme';
8
+ import { followLink, newsActive, socialLinks, todayLabel, useMenus, useOptions, type MenuItemDto } from './site';
9
+ import { Brand } from './Brand';
10
+ import { SocialIcons } from './Social';
11
+ import { UrgentTicker } from './Ticker';
12
+ import { cx } from './kit';
13
+
14
+ export function Header({ path, navigate }: ThemeChromeProps) {
15
+ const { info } = useSnapify();
16
+ const o = useOptions();
17
+ const { primary, mobile } = useMenus();
18
+ const [drawer, setDrawer] = useState(false);
19
+ const [expanded, setExpanded] = useState<string | null>(null);
20
+ const [open, setOpen] = useState<string | null>(null);
21
+ const [stuck, setStuck] = useState(false);
22
+ const actions = hasModuleSlot('header-actions');
23
+ const social = socialLinks(o);
24
+ const centered = o.mastheadAlign !== 'left';
25
+
26
+ useEffect(() => {
27
+ const onScroll = () => setStuck(window.scrollY > 180);
28
+ onScroll();
29
+ window.addEventListener('scroll', onScroll, { passive: true });
30
+ return () => window.removeEventListener('scroll', onScroll);
31
+ }, []);
32
+
33
+ useEffect(() => {
34
+ setDrawer(false);
35
+ setOpen(null);
36
+ }, [path]);
37
+
38
+ useEffect(() => {
39
+ document.body.style.overflow = drawer ? 'hidden' : '';
40
+ return () => {
41
+ document.body.style.overflow = '';
42
+ };
43
+ }, [drawer]);
44
+
45
+ const go = (item: Pick<MenuItemDto, 'path' | 'openInNewTab'>) => {
46
+ setDrawer(false);
47
+ setOpen(null);
48
+ followLink(item.path, navigate, item.openInNewTab);
49
+ };
50
+ const isActive = (p: string) => (p === '/' ? path === '/' : path.startsWith(p.replace(/\/$/, '')));
51
+ const searchButton =
52
+ !actions && newsActive() ? (
53
+ <button type="button" onClick={() => go({ path: '/actualites/' })} className="flex h-10 w-10 items-center justify-center text-(--ak-ink) hover:text-(--ak-accent-text)" aria-label="Rechercher">
54
+ <Search className="h-5 w-5" />
55
+ </button>
56
+ ) : null;
57
+
58
+ return (
59
+ <>
60
+ {/* Bandeau supérieur : date, édition, réseaux */}
61
+ {o.showDate || o.edition || social.length ? (
62
+ <div className="ak-dark bg-(--ak-ink) text-(--ak-fg)">
63
+ <div className="mx-auto flex h-9 max-w-7xl items-center justify-between gap-4 px-4 text-[12px] sm:px-6 lg:px-8">
64
+ <p className="truncate">
65
+ {o.showDate ? <span className="font-semibold">{todayLabel()}</span> : null}
66
+ {o.showDate && o.edition ? <span className="mx-2 text-(--ak-fg-2)">|</span> : null}
67
+ {o.edition ? <span className="text-(--ak-fg-2)">{o.edition}</span> : null}
68
+ </p>
69
+ <SocialIcons links={social} className="hidden sm:flex" size="sm" />
70
+ </div>
71
+ </div>
72
+ ) : null}
73
+
74
+ {/* Manchette */}
75
+ <header className="bg-(--ak-paper)">
76
+ <div className={cx('mx-auto grid max-w-7xl items-center gap-4 px-4 sm:px-6 lg:px-8', centered ? 'grid-cols-[1fr_auto_1fr] py-6 sm:py-8' : 'grid-cols-[auto_1fr_auto] py-5')}>
77
+ <div className="flex items-center gap-2">
78
+ <button type="button" onClick={() => setDrawer(true)} className="-ml-2 inline-flex h-10 items-center gap-2 px-2 text-(--ak-ink) hover:text-(--ak-accent-text)" aria-label="Ouvrir le menu des rubriques">
79
+ <Menu className="h-[22px] w-[22px]" strokeWidth={1.8} />
80
+ <span className="hidden text-[12px] font-bold uppercase tracking-[0.12em] md:inline">Rubriques</span>
81
+ </button>
82
+ </div>
83
+ <div className={cx(!centered && 'justify-self-start')}>
84
+ <Brand onClick={() => go({ path: '/' })} tagline={centered} />
85
+ </div>
86
+ <div className="flex items-center justify-end gap-1 text-(--ak-ink)">
87
+ {actions ? <ModuleSlot name="header-actions" /> : searchButton}
88
+ </div>
89
+ </div>
90
+ </header>
91
+
92
+ {/* Barre des rubriques */}
93
+ <nav className={cx('z-50 border-y border-(--ak-ink) bg-(--ak-paper)', o.stickyNav ? 'sticky top-0' : 'relative', stuck && o.stickyNav && 'shadow-[0_10px_24px_-20px_rgba(0,0,0,0.5)]')} aria-label="Rubriques">
94
+ <div className="ak-double-rule">
95
+ <div className="mx-auto flex h-12 max-w-7xl items-center gap-2 px-4 sm:px-6 lg:px-8">
96
+ <div className={cx('hidden shrink-0 overflow-hidden transition-all duration-300 lg:block', stuck && o.stickyNav ? 'mr-3 w-40 opacity-100' : 'w-0 opacity-0')}>
97
+ <Brand size="sm" onClick={() => go({ path: '/' })} />
98
+ </div>
99
+ <div className={cx('ak-scroll-x flex h-full min-w-0 flex-1 items-stretch overflow-x-auto lg:overflow-visible', centered && !stuck && 'lg:justify-center')}>
100
+ {primary.map((item) =>
101
+ item.children?.length ? (
102
+ <div key={item.id} className="relative h-full" onMouseEnter={() => setOpen(item.id)} onMouseLeave={() => setOpen(null)}>
103
+ <button type="button" onClick={() => go(item)} aria-expanded={open === item.id} aria-current={isActive(item.path) ? 'page' : undefined} className="ak-nav-link gap-1">
104
+ {item.label}
105
+ <ChevronDown className={cx('hidden h-3.5 w-3.5 transition-transform lg:block', open === item.id && 'rotate-180')} />
106
+ </button>
107
+ <div className={cx('absolute left-0 top-full z-50 hidden min-w-[15rem] pt-px transition-all duration-150 lg:block', open === item.id ? 'visible opacity-100' : 'invisible opacity-0')}>
108
+ <div className="border border-(--ak-ink) bg-(--ak-card) py-2 shadow-[6px_6px_0_-1px_var(--ak-accent)]">
109
+ {item.children.map((child) => (
110
+ <button key={child.id} type="button" onClick={() => go(child)} className="block w-full px-4 py-2 text-left text-sm font-semibold text-(--ak-ink) hover:bg-(--ak-paper-2) hover:text-(--ak-accent-text)">
111
+ {child.label}
112
+ </button>
113
+ ))}
114
+ </div>
115
+ </div>
116
+ </div>
117
+ ) : (
118
+ <button key={item.id} type="button" onClick={() => go(item)} aria-current={isActive(item.path) ? 'page' : undefined} className={cx('ak-nav-link', item.highlight && 'text-(--ak-accent-text)!')}>
119
+ {item.label}
120
+ </button>
121
+ )
122
+ )}
123
+ </div>
124
+ </div>
125
+ </div>
126
+ <UrgentTicker enabled={o.ticker} navigate={navigate} />
127
+ </nav>
128
+
129
+ {/* Menu des rubriques : panneau latéral */}
130
+ <div className={cx('fixed inset-0 z-[60]', drawer ? 'visible' : 'invisible')} aria-hidden={!drawer}>
131
+ <div className={cx('absolute inset-0 bg-black/45 transition-opacity duration-300', drawer ? 'opacity-100' : 'opacity-0')} onClick={() => setDrawer(false)} />
132
+ <aside className={cx('absolute inset-y-0 left-0 flex w-[88%] max-w-sm flex-col bg-(--ak-paper) shadow-2xl transition-transform duration-300 ease-out', drawer ? 'translate-x-0' : '-translate-x-full')}>
133
+ <div className="flex h-20 shrink-0 items-center justify-between border-b-4 border-double border-(--ak-ink) px-5">
134
+ <Brand size="sm" onClick={() => go({ path: '/' })} />
135
+ <button type="button" onClick={() => setDrawer(false)} className="flex h-10 w-10 items-center justify-center text-(--ak-ink) hover:text-(--ak-accent-text)" aria-label="Fermer le menu">
136
+ <X className="h-5 w-5" />
137
+ </button>
138
+ </div>
139
+ {newsActive() ? (
140
+ <form
141
+ role="search"
142
+ className="relative border-b border-(--ak-line) p-4"
143
+ onSubmit={(e) => {
144
+ e.preventDefault();
145
+ const q = String(new FormData(e.currentTarget).get('q') || '').trim();
146
+ go({ path: q ? `/actualites/?q=${encodeURIComponent(q)}` : '/actualites/' });
147
+ }}
148
+ >
149
+ <Search className="pointer-events-none absolute left-8 top-1/2 h-4 w-4 -translate-y-1/2 text-(--ak-muted)" />
150
+ <input name="q" type="search" placeholder="Rechercher un article…" aria-label="Rechercher un article" maxLength={120} className="ak-search" />
151
+ </form>
152
+ ) : null}
153
+ <nav className="flex-1 overflow-y-auto px-5 py-2" aria-label="Menu">
154
+ {mobile.map((item) => (
155
+ <div key={item.id} className="border-b border-(--ak-line)">
156
+ <div className="flex items-center justify-between">
157
+ <button type="button" onClick={() => go(item)} className={cx('ak-display flex-1 py-3.5 text-left text-xl', item.highlight || isActive(item.path) ? 'text-(--ak-accent-text)' : 'text-(--ak-ink)')}>
158
+ {item.label}
159
+ </button>
160
+ {item.children?.length ? (
161
+ <button type="button" onClick={() => setExpanded(expanded === item.id ? null : item.id)} className="flex h-9 w-9 items-center justify-center text-(--ak-muted)" aria-label={`Sous-menu ${item.label}`} aria-expanded={expanded === item.id}>
162
+ {expanded === item.id ? <Minus className="h-4 w-4" /> : <Plus className="h-4 w-4" />}
163
+ </button>
164
+ ) : null}
165
+ </div>
166
+ {item.children?.length && expanded === item.id ? (
167
+ <div className="space-y-0.5 pb-3 pl-3">
168
+ {item.children.map((child) => (
169
+ <button key={child.id} type="button" onClick={() => go(child)} className="block w-full py-2 text-left text-sm font-semibold text-(--ak-muted) hover:text-(--ak-ink)">
170
+ {child.label}
171
+ </button>
172
+ ))}
173
+ </div>
174
+ ) : null}
175
+ </div>
176
+ ))}
177
+ </nav>
178
+ <div className="shrink-0 space-y-3 border-t border-(--ak-line) p-5 text-sm text-(--ak-muted)">
179
+ {o.showDate ? <p>{todayLabel()}</p> : null}
180
+ <SocialIcons links={social} />
181
+ {info.contactEmail ? (
182
+ <a href={`mailto:${info.contactEmail}`} className="block font-semibold text-(--ak-ink) hover:text-(--ak-accent-text)">
183
+ {info.contactEmail}
184
+ </a>
185
+ ) : null}
186
+ </div>
187
+ </aside>
188
+ </div>
189
+ </>
190
+ );
191
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Akhbar — thème presse pour Snapify
3
+ * © 2026 Hamdi Ayari
4
+ */
5
+ import React, { useEffect } from 'react';
6
+ import { useOptions, type AkhbarOptions } from './site';
7
+
8
+ const FONTS: Record<AkhbarOptions['typography'], string> = {
9
+ classic: 'family=Playfair+Display:ital,wght@0,600;0,700;0,800;1,600&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&family=Inter:wght@400;500;600;700;800',
10
+ editorial: 'family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,600;0,6..72,700;1,6..72,500&family=Inter:wght@400;500;600;700;800',
11
+ modern: 'family=Inter:wght@400;500;600;700;800;900',
12
+ brand: '',
13
+ };
14
+ const DATA_KEYS = ['akPalette', 'akAccent', 'akType', 'akCorners', 'akMotion'] as const;
15
+
16
+ /** Applique les options du thème (attributs data-ak-* lus par la feuille de style) et charge les polices. */
17
+ export function Layout({ children }: { children: React.ReactNode }) {
18
+ const o = useOptions();
19
+
20
+ useEffect(() => {
21
+ const families = FONTS[o.typography] ?? FONTS.classic;
22
+ if (!families) return;
23
+ const link = document.createElement('link');
24
+ link.id = 'ak-fonts';
25
+ link.rel = 'stylesheet';
26
+ link.href = `https://fonts.googleapis.com/css2?${families}&display=swap`;
27
+ document.getElementById('ak-fonts')?.remove();
28
+ document.head.appendChild(link);
29
+ return () => link.remove();
30
+ }, [o.typography]);
31
+
32
+ useEffect(() => {
33
+ const root = document.documentElement;
34
+ root.classList.add('ak-html');
35
+ Object.assign(root.dataset, {
36
+ akPalette: o.palette,
37
+ akAccent: o.accent,
38
+ akType: o.typography,
39
+ akCorners: o.corners,
40
+ akMotion: o.animations ? 'on' : 'off',
41
+ });
42
+ return () => {
43
+ root.classList.remove('ak-html');
44
+ for (const k of DATA_KEYS) delete root.dataset[k];
45
+ };
46
+ }, [o.palette, o.accent, o.typography, o.corners, o.animations]);
47
+
48
+ return <div className="ak-root">{children}</div>;
49
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Akhbar — thème presse pour Snapify
3
+ * © 2026 Hamdi Ayari
4
+ */
5
+ import React from 'react';
6
+ import { Facebook, Instagram, Linkedin, Twitter, Youtube } from 'lucide-react';
7
+ import type { socialLinks } from './site';
8
+ import { cx } from './kit';
9
+
10
+ const ICONS = { facebook: Facebook, instagram: Instagram, x: Twitter, youtube: Youtube, linkedin: Linkedin } as const;
11
+
12
+ export function SocialIcons({ links, className, size = 'md' }: { links: ReturnType<typeof socialLinks>; className?: string; size?: 'sm' | 'md' }) {
13
+ if (!links.length) return null;
14
+ return (
15
+ <div className={cx('flex items-center gap-1', className)}>
16
+ {links.map((s) => {
17
+ const Icon = ICONS[s.id];
18
+ return (
19
+ <a
20
+ key={s.id}
21
+ href={s.url}
22
+ target="_blank"
23
+ rel="noopener noreferrer"
24
+ aria-label={s.label}
25
+ className={cx('flex items-center justify-center opacity-80 transition hover:text-(--ak-accent-text) hover:opacity-100', size === 'sm' ? 'h-7 w-7' : 'h-9 w-9')}
26
+ >
27
+ <Icon className={size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'} />
28
+ </a>
29
+ );
30
+ })}
31
+ </div>
32
+ );
33
+ }